aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am12
-rw-r--r--src/ipaccess/Makefile.am16
-rw-r--r--src/ipaccess/abisip-find.c23
-rw-r--r--src/ipaccess/ipaccess-config.c199
-rw-r--r--src/ipaccess/ipaccess-proxy.c61
-rw-r--r--src/ipaccess/network_listen.c1
-rw-r--r--src/ipaccess/stubs.c24
-rw-r--r--src/libfilter/Makefile.am27
-rw-r--r--src/libfilter/bsc_msg_acc.c136
-rw-r--r--src/libfilter/bsc_msg_filter.c339
-rw-r--r--src/libfilter/bsc_msg_vty.c149
-rw-r--r--src/osmo-bsc/Makefile.am83
-rw-r--r--src/osmo-bsc/a_reset.c193
-rw-r--r--src/osmo-bsc/abis_nm.c791
-rw-r--r--src/osmo-bsc/abis_nm_vty.c9
-rw-r--r--src/osmo-bsc/abis_om2000.c1128
-rw-r--r--src/osmo-bsc/abis_om2000_vty.c294
-rw-r--r--src/osmo-bsc/abis_osmo.c226
-rw-r--r--src/osmo-bsc/abis_rsl.c2095
-rw-r--r--src/osmo-bsc/acc.c575
-rw-r--r--src/osmo-bsc/acc_ramp.c363
-rw-r--r--src/osmo-bsc/arfcn_range_encode.c340
-rw-r--r--src/osmo-bsc/assignment_fsm.c742
-rw-r--r--src/osmo-bsc/bsc_ctrl.c906
-rw-r--r--src/osmo-bsc/bsc_ctrl_commands.c500
-rw-r--r--src/osmo-bsc/bsc_ctrl_lookup.c33
-rw-r--r--src/osmo-bsc/bsc_init.c292
-rw-r--r--src/osmo-bsc/bsc_rf_ctrl.c125
-rw-r--r--src/osmo-bsc/bsc_sccp.c140
-rw-r--r--src/osmo-bsc/bsc_stats.c236
-rw-r--r--src/osmo-bsc/bsc_subscr_conn_fsm.c749
-rw-r--r--src/osmo-bsc/bsc_subscriber.c347
-rw-r--r--src/osmo-bsc/bsc_vty.c5791
-rw-r--r--src/osmo-bsc/bssmap_reset.c263
-rw-r--r--src/osmo-bsc/bts.c1764
-rw-r--r--src/osmo-bsc/bts_ctrl.c1580
-rw-r--r--src/osmo-bsc/bts_ericsson_rbs2000.c55
-rw-r--r--src/osmo-bsc/bts_init.c2
-rw-r--r--src/osmo-bsc/bts_ipaccess_nanobts.c672
-rw-r--r--src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c351
-rw-r--r--src/osmo-bsc/bts_nokia_site.c215
-rw-r--r--src/osmo-bsc/bts_osmobts.c210
-rw-r--r--src/osmo-bsc/bts_setup_ramp.c249
-rw-r--r--src/osmo-bsc/bts_siemens_bs11.c135
-rw-r--r--src/osmo-bsc/bts_sm.c112
-rw-r--r--src/osmo-bsc/bts_sysmobts.c63
-rw-r--r--src/osmo-bsc/bts_trx.c509
-rw-r--r--src/osmo-bsc/bts_trx_ctrl.c157
-rw-r--r--src/osmo-bsc/bts_trx_ts_ctrl.c151
-rw-r--r--src/osmo-bsc/bts_trx_ts_lchan_ctrl.c152
-rw-r--r--src/osmo-bsc/bts_trx_vty.c881
-rw-r--r--src/osmo-bsc/bts_unknown.c7
-rw-r--r--src/osmo-bsc/bts_vty.c5090
-rw-r--r--src/osmo-bsc/cbch_scheduler.c290
-rw-r--r--src/osmo-bsc/cbsp_link.c324
-rw-r--r--src/osmo-bsc/chan_alloc.c112
-rw-r--r--src/osmo-bsc/chan_counts.c310
-rw-r--r--src/osmo-bsc/codec_pref.c301
-rw-r--r--src/osmo-bsc/data_rate_pref.c165
-rw-r--r--src/osmo-bsc/e1_config.c47
-rw-r--r--src/osmo-bsc/gsm_04_08_rr.c649
-rw-r--r--src/osmo-bsc/gsm_04_80_utils.c42
-rw-r--r--src/osmo-bsc/gsm_08_08.c920
-rw-r--r--src/osmo-bsc/gsm_data.c1379
-rw-r--r--src/osmo-bsc/gsm_timers.c207
-rw-r--r--src/osmo-bsc/gsm_timers_vty.c118
-rw-r--r--src/osmo-bsc/handover_ctrl.c216
-rw-r--r--src/osmo-bsc/handover_decision.c21
-rw-r--r--src/osmo-bsc/handover_decision_2.c1328
-rw-r--r--src/osmo-bsc/handover_fsm.c661
-rw-r--r--src/osmo-bsc/handover_logic.c199
-rw-r--r--src/osmo-bsc/handover_vty.c30
-rw-r--r--src/osmo-bsc/lb.c827
-rw-r--r--src/osmo-bsc/lchan.c150
-rw-r--r--src/osmo-bsc/lchan_fsm.c1153
-rw-r--r--src/osmo-bsc/lchan_rtp_fsm.c355
-rw-r--r--src/osmo-bsc/lchan_select.c478
-rw-r--r--src/osmo-bsc/lcs_loc_req.c615
-rw-r--r--src/osmo-bsc/lcs_ta_req.c305
-rw-r--r--src/osmo-bsc/meas_feed.c103
-rw-r--r--src/osmo-bsc/meas_rep.c79
-rw-r--r--src/osmo-bsc/mgw_endpoint_fsm.c777
-rw-r--r--src/osmo-bsc/neighbor_ident.c554
-rw-r--r--src/osmo-bsc/neighbor_ident_ctrl.c753
-rw-r--r--src/osmo-bsc/neighbor_ident_vty.c802
-rw-r--r--src/osmo-bsc/net_init.c113
-rw-r--r--src/osmo-bsc/nm_bb_transc_fsm.c454
-rw-r--r--src/osmo-bsc/nm_bts_fsm.c443
-rw-r--r--src/osmo-bsc/nm_bts_sm_fsm.c326
-rw-r--r--src/osmo-bsc/nm_channel_fsm.c376
-rw-r--r--src/osmo-bsc/nm_common_fsm.c90
-rw-r--r--src/osmo-bsc/nm_gprs_cell_fsm.c432
-rw-r--r--src/osmo-bsc/nm_gprs_nse_fsm.c403
-rw-r--r--src/osmo-bsc/nm_gprs_nsvc_fsm.c434
-rw-r--r--src/osmo-bsc/nm_rcarrier_fsm.c455
-rw-r--r--src/osmo-bsc/osmo_bsc_bssap.c1595
-rw-r--r--src/osmo-bsc/osmo_bsc_ctrl.c778
-rw-r--r--src/osmo-bsc/osmo_bsc_filter.c72
-rw-r--r--src/osmo-bsc/osmo_bsc_grace.c118
-rw-r--r--src/osmo-bsc/osmo_bsc_lcls.c90
-rw-r--r--src/osmo-bsc/osmo_bsc_main.c670
-rw-r--r--src/osmo-bsc/osmo_bsc_mgcp.c205
-rw-r--r--src/osmo-bsc/osmo_bsc_msc.c435
-rw-r--r--src/osmo-bsc/osmo_bsc_sigtran.c524
-rw-r--r--src/osmo-bsc/osmo_bsc_vty.c1027
-rw-r--r--src/osmo-bsc/paging.c815
-rw-r--r--src/osmo-bsc/pcu_sock.c786
-rw-r--r--src/osmo-bsc/penalty_timers.c121
-rw-r--r--src/osmo-bsc/power_control.c476
-rw-r--r--src/osmo-bsc/rest_octets.c878
-rw-r--r--src/osmo-bsc/smscb.c1153
-rw-r--r--src/osmo-bsc/smscb_vty.c421
-rw-r--r--src/osmo-bsc/system_information.c748
-rw-r--r--src/osmo-bsc/timeslot_fsm.c305
-rw-r--r--src/osmo-bsc/vgcs_fsm.c1318
-rw-r--r--src/utils/Makefile.am23
-rw-r--r--src/utils/bs11_config.c29
-rw-r--r--src/utils/meas_db.c99
-rw-r--r--src/utils/meas_db.h6
-rw-r--r--src/utils/meas_json.c23
-rw-r--r--src/utils/meas_pcap2db.c12
-rw-r--r--src/utils/meas_udp2db.c13
-rw-r--r--src/utils/meas_vis.c36
123 files changed, 42587 insertions, 17548 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 6c63eead8..45d4df74b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -13,15 +13,11 @@ AM_CFLAGS = \
$(COVERAGE_CFLAGS) \
$(NULL)
-AM_LDFLAGS = \
- $(LIBOSMOCORE_LIBS) \
- $(LIBOSMOGSM_LIBS) \
- $(COVERAGE_LDFLAGS) \
- $(NULL)
-
SUBDIRS = \
- libfilter \
osmo-bsc \
utils \
- ipaccess \
$(NULL)
+
+if BUILD_IPA_UTILS
+SUBDIRS += ipaccess
+endif
diff --git a/src/ipaccess/Makefile.am b/src/ipaccess/Makefile.am
index d73aa4d77..9bd648ddf 100644
--- a/src/ipaccess/Makefile.am
+++ b/src/ipaccess/Makefile.am
@@ -9,9 +9,9 @@ AM_CFLAGS = \
$(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOGSM_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
- $(COVERAGE_CFLAGS) \
- $(LIBOSMOMGCPCLIENT_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
$(LIBOSMOSIGTRAN_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
$(NULL)
AM_LDFLAGS = \
@@ -22,8 +22,6 @@ OSMO_LIBS = \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOABIS_LIBS) \
- $(LIBOSMOMGCPCLIENT_LIBS) \
- $(LIBOSMOSIGTRAN_LIBS) \
$(NULL)
bin_PROGRAMS = \
@@ -50,22 +48,16 @@ ipaccess_config_SOURCES = \
# FIXME: resolve the bogus dependencies patched around here:
ipaccess_config_LDADD = \
- $(top_builddir)/src/osmo-bsc/abis_nm.o \
- $(top_builddir)/src/osmo-bsc/bts_ipaccess_nanobts.o \
- $(top_builddir)/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.o \
- $(top_builddir)/src/osmo-bsc/gsm_data.o \
- $(top_builddir)/src/osmo-bsc/gsm_timers.o \
- $(top_builddir)/src/osmo-bsc/net_init.o \
+ $(top_builddir)/src/osmo-bsc/libbsc.la \
$(OSMO_LIBS) \
$(NULL)
ipaccess_proxy_SOURCES = \
ipaccess-proxy.c \
stubs.c \
- $(top_srcdir)/src/osmo-bsc/gsm_data.c \
$(NULL)
ipaccess_proxy_LDADD = \
- $(top_builddir)/src/osmo-bsc/gsm_timers.o \
+ $(top_builddir)/src/osmo-bsc/libbsc.la \
$(OSMO_LIBS) \
$(NULL)
diff --git a/src/ipaccess/abisip-find.c b/src/ipaccess/abisip-find.c
index 21ed50e5a..a26bb55d7 100644
--- a/src/ipaccess/abisip-find.c
+++ b/src/ipaccess/abisip-find.c
@@ -53,7 +53,7 @@ static struct {
.format_json = false,
};
-static void print_help()
+static void print_help(void)
{
printf("\n");
printf("Usage: abisip-find [-l] [<interface-name>]\n");
@@ -265,7 +265,7 @@ LLIST_HEAD(base_stations);
void *ctx = NULL;
-void print_timestamp()
+void print_timestamp(void)
{
time_t now = time(NULL);
printf("\n\n----- %s\n", ctime(&now));
@@ -304,7 +304,7 @@ bool base_stations_add(struct base_station *new_bs)
return true;
}
-bool base_stations_timeout()
+bool base_stations_timeout(void)
{
struct base_station *bs, *next_bs;
time_t now = time(NULL);
@@ -323,7 +323,7 @@ bool base_stations_timeout()
return changed;
}
-void base_stations_print()
+void base_stations_print(void)
{
struct base_station *bs;
int count = 0;
@@ -403,10 +403,10 @@ static int read_response(int fd)
static int bfd_cb(struct osmo_fd *bfd, unsigned int flags)
{
- if (flags & BSC_FD_READ)
+ if (flags & OSMO_FD_READ)
return read_response(bfd->fd);
- if (flags & BSC_FD_WRITE) {
- bfd->when &= ~BSC_FD_WRITE;
+ if (flags & OSMO_FD_WRITE) {
+ osmo_fd_write_disable(bfd);
return bcast_find(bfd->fd);
}
return 0;
@@ -418,7 +418,7 @@ static void timer_cb(void *_data)
{
struct osmo_fd *bfd = _data;
- bfd->when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(bfd);
base_stations_bump(false);
@@ -446,13 +446,12 @@ int main(int argc, char **argv)
else if (cmdline_opts.send_interval >= cmdline_opts.list_view_timeout)
fprintf(stdout, "\nWARNING: the --timeout should be larger than --interval.\n\n");
- bfd.cb = bfd_cb;
- bfd.when = BSC_FD_READ | BSC_FD_WRITE;
- bfd.fd = udp_sock(cmdline_opts.ifname, cmdline_opts.bind_ip);
- if (bfd.fd < 0) {
+ rc = udp_sock(cmdline_opts.ifname, cmdline_opts.bind_ip);
+ if (rc < 0) {
perror("Cannot create local socket for broadcast udp");
exit(1);
}
+ osmo_fd_setup(&bfd, rc, OSMO_FD_READ | OSMO_FD_WRITE, bfd_cb, NULL, 0);
rc = osmo_fd_register(&bfd);
if (rc < 0) {
diff --git a/src/ipaccess/ipaccess-config.c b/src/ipaccess/ipaccess-config.c
index da19ce20a..3df9f61cb 100644
--- a/src/ipaccess/ipaccess-config.c
+++ b/src/ipaccess/ipaccess-config.c
@@ -27,6 +27,7 @@
#include <getopt.h>
#include <errno.h>
#include <ctype.h>
+#include <regex.h>
#include <inttypes.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
@@ -55,8 +56,7 @@
#include <osmocom/abis/abis.h>
#include <osmocom/gsm/protocol/gsm_12_21.h>
#include <osmocom/bsc/bss.h>
-
-struct gsm_network *bsc_gsmnet;
+#include <osmocom/bsc/bts.h>
static int net_listen_testnr;
static int restart;
@@ -91,7 +91,7 @@ static int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa)
bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bfd->cb = ipaccess_fd_cb;
- bfd->when = BSC_FD_READ | BSC_FD_WRITE;
+ bfd->when = OSMO_FD_READ | OSMO_FD_WRITE;
bfd->data = line;
bfd->priv_nr = E1INP_SIGN_OML;
@@ -129,7 +129,7 @@ static int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin)
{
struct e1inp_line *line;
struct e1inp_ts *sign_ts, *rsl_ts;
- struct e1inp_sign_link *oml_link, *rsl_link;
+ struct e1inp_sign_link *oml_link, *osmo_link, *rsl_link;
line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
if (!line)
@@ -140,23 +140,29 @@ static int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin)
fprintf(stderr, "cannot `ipa' driver, giving up.\n");
return -EINVAL;
}
- line->ops = &ipaccess_e1inp_line_ops;
+ e1inp_line_bind_ops(line, &ipaccess_e1inp_line_ops);
+ e1_set_pcap_fd2(line, -1); /* Disable writing to pcap */
+
+ sign_ts = e1inp_line_ipa_oml_ts(line);
+ rsl_ts = e1inp_line_ipa_rsl_ts(line, 0);
/* create E1 timeslots for signalling and TRAU frames */
- e1inp_ts_config_sign(&line->ts[1-1], line);
- e1inp_ts_config_sign(&line->ts[2-1], line);
+ e1inp_ts_config_sign(sign_ts, line);
+ e1inp_ts_config_sign(rsl_ts, line);
+ rsl_ts->driver.ipaccess.fd.fd = -1;
- /* create signalling links for TS1 */
- sign_ts = &line->ts[1-1];
- rsl_ts = &line->ts[2-1];
+ /* create signalling links for TRX0 */
oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
- bts->c0, 0xff, 0);
+ bts->c0, IPAC_PROTO_OML, 0);
+ osmo_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OSMO,
+ bts->c0, IPAC_PROTO_OSMO, 0);
rsl_link = e1inp_sign_link_create(rsl_ts, E1INP_SIGN_RSL,
- bts->c0, 0, 0);
+ bts->c0, IPAC_PROTO_RSL, 0);
/* create back-links from bts/trx */
bts->oml_link = oml_link;
- bts->c0->rsl_link = rsl_link;
+ bts->osmo_link = osmo_link;
+ bts->c0->rsl_link_primary = rsl_link;
/* default port at BTS for incoming connections is 3006 */
if (sin->sin_port == 0)
@@ -190,8 +196,11 @@ static void check_restart_or_exit(struct gsm_bts_trx *trx)
static int ipacc_msg_ack(uint8_t mt, struct gsm_bts_trx *trx)
{
+ if (mt != NM_MT_IPACC_SET_NVATTR_ACK && mt != NM_MT_IPACC_SET_ATTR_ACK)
+ return 0;
+
if (sw_load_state == 1) {
- fprintf(stderr, "The new software is activaed.\n");
+ fprintf(stderr, "The new software is activated.\n");
check_restart_or_exit(trx);
} else if (oml_state == 1) {
fprintf(stderr, "Set the NV Attributes.\n");
@@ -237,7 +246,7 @@ static int nwl_sig_cb(unsigned int subsys, unsigned int signal,
/* Create whitelist from results */
physconf_len = build_physconf(physconf_buf,
&trx->ipaccess.rxlev_stat);
- /* Start next test abbout BCCH channel usage */
+ /* Start next test about BCCH channel usage */
ipac_nwl_test_start(trx, NM_IPACC_TESTNO_BCCH_CHAN_USAGE,
physconf_buf, physconf_len);
break;
@@ -267,6 +276,8 @@ static int nwl_sig_cb(unsigned int subsys, unsigned int signal,
return 0;
}
+static const struct value_string ipa_nvflag_strs[];
+
static int print_attr_rep(struct msgb *mb)
{
/* Parse using nanoBTS own formatting for Get Attribute Response */
@@ -280,22 +291,67 @@ static int print_attr_rep(struct msgb *mb)
char oml_ip[20] = {0};
uint16_t oml_port = 0;
char unit_id[40] = {0};
+ unsigned int indent = 0;
- abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+ if (abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
abis_nm_tlv_attr_primary_oml(&tp, &ia, &oml_port);
osmo_strlcpy(oml_ip, inet_ntoa(ia), sizeof(oml_ip));
abis_nm_tlv_attr_unit_id(&tp, unit_id, sizeof(unit_id));
- fprintf(stdout, "{ \"primary_oml_ip\": \"%s\", \"primary_oml_port\": %" PRIu16 ", \"unit_id\": \"%s\" }\n",
- oml_ip, oml_port, unit_id);
+#define ENDL(last) \
+ fprintf(stdout, "%s\n", last ? "" : ",")
+#define print_offset(fmt, args...) \
+ fprintf(stdout, "%*s" fmt, indent * 4, "", ## args)
+#define print_field(field, fmt, args...) \
+ print_offset("\"%s\": \"" fmt "\"", field, ## args)
+
+ print_offset("{\n");
+ indent++;
+
+ print_field("primary_oml_ip", "%s", oml_ip); ENDL(false);
+ print_field("primary_oml_port", "%u", oml_port); ENDL(false);
+ print_field("unit_id", "%s", unit_id); ENDL(false);
+
+ uint16_t Fx = (TLVP_VAL(&tp, NM_ATT_IPACC_NV_FLAGS)[2] << 8)
+ | (TLVP_VAL(&tp, NM_ATT_IPACC_NV_FLAGS)[0] << 0);
+ uint16_t Mx = (TLVP_VAL(&tp, NM_ATT_IPACC_NV_FLAGS)[3] << 8)
+ | (TLVP_VAL(&tp, NM_ATT_IPACC_NV_FLAGS)[1] << 0);
+ const struct value_string *nvflag = ipa_nvflag_strs;
+
+ print_offset("\"nv_flags\": {\n");
+ indent++;
+
+ while (nvflag->value && nvflag->str) {
+ const char *val = (Fx & nvflag->value) ? "yes" : "no";
+ if (~Mx & nvflag->value)
+ val = "unknown";
+ print_field(nvflag->str, "%s", val);
+
+ nvflag++;
+
+ if (nvflag->value && nvflag->str)
+ ENDL(false); /* more fields to print */
+ else
+ ENDL(true); /* this was the last field */
+ }
+
+ indent--;
+ print_offset("}\n");
+
+ indent--;
+ print_offset("}\n");
+
return 0;
}
static int nm_state_event(int evt, uint8_t obj_class, void *obj,
- struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+ const struct gsm_nm_state *old_state, const struct gsm_nm_state *new_state,
struct abis_om_obj_inst *obj_inst);
static int nm_sig_cb(unsigned int subsys, unsigned int signal,
@@ -304,14 +360,23 @@ static int nm_sig_cb(unsigned int subsys, unsigned int signal,
struct ipacc_ack_signal_data *ipacc_data;
struct nm_statechg_signal_data *nsd;
struct msgb *oml_msg;
+ struct gsm_bts_trx *trx;
switch (signal) {
case S_NM_IPACC_NACK:
ipacc_data = signal_data;
- return ipacc_msg_nack(ipacc_data->msg_type);
+ return ipacc_msg_nack(ipacc_data->foh->msg_type);
case S_NM_IPACC_ACK:
ipacc_data = signal_data;
- return ipacc_msg_ack(ipacc_data->msg_type, ipacc_data->trx);
+ switch (ipacc_data->foh->obj_class) {
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_RADIO_CARRIER:
+ trx = gsm_bts_trx_num(ipacc_data->bts,
+ ipacc_data->foh->obj_inst.trx_nr);
+ return ipacc_msg_ack(ipacc_data->foh->msg_type, trx);
+ default:
+ return 0;
+ }
case S_NM_IPACC_RESTART_ACK:
if (!quiet)
printf("The BTS has acked the restart. Exiting.\n");
@@ -322,11 +387,10 @@ static int nm_sig_cb(unsigned int subsys, unsigned int signal,
printf("The BTS has nacked the restart. Exiting.\n");
exit(0);
break;
- case S_NM_STATECHG_OPER:
- case S_NM_STATECHG_ADM:
+ case S_NM_STATECHG:
nsd = signal_data;
- nm_state_event(signal, nsd->obj_class, nsd->obj, nsd->old_state,
- nsd->new_state, nsd->obj_inst);
+ nm_state_event(signal, nsd->obj_class, nsd->obj, &nsd->old_state,
+ &nsd->new_state, nsd->obj_inst);
break;
case S_NM_GET_ATTR_REP:
fprintf(stderr, "Received SIGNAL S_NM_GET_ATTR_REP\n");
@@ -340,6 +404,32 @@ static int nm_sig_cb(unsigned int subsys, unsigned int signal,
return 0;
}
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct input_signal_data *isd = signal_data;
+
+ if (subsys != SS_L_INPUT)
+ return -EINVAL;
+
+ fprintf(stderr, "%s(): Input signal '%s' received\n", __func__,
+ get_value_string(e1inp_signal_names, signal));
+
+ switch (signal) {
+ case S_L_INP_TEI_UP:
+ break;
+ case S_L_INP_TEI_DN:
+ fprintf(stderr, "Lost E1 %s link\n", e1inp_signtype_name(isd->link_type));
+ exit(1);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
/* callback function passed to the ABIS OML code */
static int percent;
static int percent_old;
@@ -500,7 +590,7 @@ static const struct value_string ipa_nvflag_strs[] = {
{ 0x0002, "static-gw" },
{ 0x0004, "no-dhcp-vsi" },
{ 0x0008, "dhcp-enabled" },
- { 0x0040, "led-disabled" },
+ { 0x0040, "led-enabled" },
{ 0x0100, "secondary-oml-enabled" },
{ 0x0200, "diag-enabled" },
{ 0x0400, "cli-enabled" },
@@ -515,8 +605,10 @@ static int ipa_nvflag_set(uint16_t *flags, uint16_t *mask, const char *name, int
{
int rc;
rc = get_string_value(ipa_nvflag_strs, name);
- if (rc < 0)
+ if (rc < 0) {
+ fprintf(stderr, "Unknown attribute '%s'\n", name);
return rc;
+ }
*mask |= rc;
if (en)
@@ -540,6 +632,7 @@ static void bootstrap_om(struct gsm_bts_trx *trx)
if (get_attr) {
msgb_put_u8(nmsg_get, NM_ATT_IPACC_PRIM_OML_CFG);
msgb_put_u8(nmsg_get, NM_ATT_IPACC_UNIT_ID);
+ msgb_put_u8(nmsg_get, NM_ATT_IPACC_NV_FLAGS);
}
if (unit_id) {
len = strlen(unit_id);
@@ -630,7 +723,7 @@ out_err:
}
static int nm_state_event(int evt, uint8_t obj_class, void *obj,
- struct gsm_nm_state *old_state, struct gsm_nm_state *new_state,
+ const struct gsm_nm_state *old_state, const struct gsm_nm_state *new_state,
struct abis_om_obj_inst *obj_inst)
{
if (obj_class == NM_OC_BASEB_TRANSC) {
@@ -639,9 +732,9 @@ static int nm_state_event(int evt, uint8_t obj_class, void *obj,
bootstrap_om(trx);
found_trx = 1;
}
- } else if (evt == S_NM_STATECHG_OPER &&
+ } else if (evt == S_NM_STATECHG &&
obj_class == NM_OC_RADIO_CARRIER &&
- new_state->availability == 3) {
+ new_state->availability == NM_AVSTATE_OFF_LINE) {
struct gsm_bts_trx *trx = obj;
if (net_listen_testnr)
@@ -842,33 +935,20 @@ static void analyze_firmware(const char *filename)
static bool check_unitid_fmt(const char* unit_id)
{
- const char *p = unit_id;
- bool must_digit = true;
- uint8_t remain_slash = 2;
+ regex_t regexp;
+ int rc;
if (strlen(unit_id) < 5)
goto wrong_fmt;
- while (*p != '\0') {
- if (*p != '/' && !isdigit(*p))
- goto wrong_fmt;
- if (*p == '/' && must_digit)
- goto wrong_fmt;
- if (*p == '/') {
- must_digit = true;
- remain_slash--;
- if (remain_slash < 0)
- goto wrong_fmt;
- } else {
- must_digit = false;
- }
- p++;
- }
+ rc = regcomp(&regexp, "^[0-9]+/[0-9]+/[0-9]+$", REG_EXTENDED | REG_NOSUB);
+ OSMO_ASSERT(!rc);
- if (*(p-1) == '/')
- goto wrong_fmt;
+ rc = regexec(&regexp, unit_id, 0, NULL, 0);
+ regfree(&regexp);
- return true;
+ if (rc == 0)
+ return true;
wrong_fmt:
fprintf(stderr, "ERROR: unit-id wrong format. Must be '\\d+/\\d+/\\d+'\n");
@@ -937,7 +1017,7 @@ static const struct log_info_cat log_categories[] = {
.name = "DNM",
.description = "A-bis Network Management / O&M (NM/OML)",
.color = "\033[1;36m",
- .loglevel = LOGL_DEBUG,
+ .loglevel = LOGL_NOTICE,
.enabled = 1,
},
};
@@ -1115,6 +1195,7 @@ int main(int argc, char **argv)
bts->oml_tei = stream_id;
osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
+ osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
osmo_signal_register_handler(SS_IPAC_NWL, nwl_sig_cb, NULL);
ipac_nwl_init();
@@ -1132,7 +1213,7 @@ int main(int argc, char **argv)
}
bts->oml_link->ts->sign.delay = 10;
- bts->c0->rsl_link->ts->sign.delay = 10;
+ bts->c0->rsl_link_primary->ts->sign.delay = 10;
while (1) {
rc = osmo_select_main(0);
if (rc < 0)
@@ -1141,15 +1222,3 @@ int main(int argc, char **argv)
exit(0);
}
-
-/* Stub */
-int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg)
-{
- return 0;
-}
-
-/* Stub */
-int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
-{
- return 0;
-}
diff --git a/src/ipaccess/ipaccess-proxy.c b/src/ipaccess/ipaccess-proxy.c
index 26c5bcd92..7ede28396 100644
--- a/src/ipaccess/ipaccess-proxy.c
+++ b/src/ipaccess/ipaccess-proxy.c
@@ -283,7 +283,7 @@ static int handle_udp_read(struct osmo_fd *bfd)
if (other_conn) {
/* enqueue the message for TX on the respective FD */
msgb_enqueue(&other_conn->tx_queue, msg);
- other_conn->fd.when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(&other_conn->fd);
} else
msgb_free(msg);
@@ -293,7 +293,7 @@ static int handle_udp_read(struct osmo_fd *bfd)
static int handle_udp_write(struct osmo_fd *bfd)
{
/* not implemented yet */
- bfd->when &= ~BSC_FD_WRITE;
+ osmo_fd_write_disable(bfd);
return -EIO;
}
@@ -303,9 +303,9 @@ static int udp_fd_cb(struct osmo_fd *bfd, unsigned int what)
{
int rc = 0;
- if (what & BSC_FD_READ)
+ if (what & OSMO_FD_READ)
rc = handle_udp_read(bfd);
- if (what & BSC_FD_WRITE)
+ if (what & OSMO_FD_WRITE)
rc = handle_udp_write(bfd);
return rc;
@@ -581,7 +581,7 @@ struct msgb *ipaccess_proxy_read_msg(struct osmo_fd *bfd, int *error)
msgb_put(msg, ret);
- /* then read te length as specified in header */
+ /* then read the length as specified in header */
msg->l2h = msg->data + sizeof(*hh);
len = ntohs(hh->len);
ret = recv(bfd->fd, msg->l2h, len, 0);
@@ -840,7 +840,7 @@ static int handle_tcp_read(struct osmo_fd *bfd)
/* enqueue packet towards BSC */
msgb_enqueue(&bsc_conn->tx_queue, msg);
/* mark respective filedescriptor as 'we want to write' */
- bsc_conn->fd.when |= BSC_FD_WRITE;
+ osmo_fd_write_enable(&bsc_conn->fd);
} else {
logp_ipbc_uid(DLINP, LOGL_INFO, ipbc, bfd->priv_nr >> 8);
LOGPC(DLINP, LOGL_INFO, "Dropping packet from %s, "
@@ -869,7 +869,7 @@ static int handle_tcp_write(struct osmo_fd *bfd)
/* get the next msg for this timeslot */
if (llist_empty(&ipc->tx_queue)) {
- bfd->when &= ~BSC_FD_WRITE;
+ osmo_fd_write_disable(bfd);
return 0;
}
lh = ipc->tx_queue.next;
@@ -897,12 +897,12 @@ static int proxy_ipaccess_fd_cb(struct osmo_fd *bfd, unsigned int what)
{
int rc = 0;
- if (what & BSC_FD_READ) {
+ if (what & OSMO_FD_READ) {
rc = handle_tcp_read(bfd);
if (rc < 0)
return rc;
}
- if (what & BSC_FD_WRITE)
+ if (what & OSMO_FD_WRITE)
rc = handle_tcp_write(bfd);
return rc;
@@ -917,7 +917,7 @@ static int listen_fd_cb(struct osmo_fd *listen_bfd, unsigned int what)
struct sockaddr_in sa;
socklen_t sa_len = sizeof(sa);
- if (!(what & BSC_FD_READ))
+ if (!(what & OSMO_FD_READ))
return 0;
ret = accept(listen_bfd->fd, (struct sockaddr *) &sa, &sa_len);
@@ -936,11 +936,7 @@ static int listen_fd_cb(struct osmo_fd *listen_bfd, unsigned int what)
}
bfd = &ipc->fd;
- bfd->fd = ret;
- bfd->data = ipc;
- bfd->priv_nr = listen_bfd->priv_nr;
- bfd->cb = proxy_ipaccess_fd_cb;
- bfd->when = BSC_FD_READ;
+ osmo_fd_setup(bfd, ret, OSMO_FD_READ, proxy_ipaccess_fd_cb, ipc, listen_bfd->priv_nr);
ret = osmo_fd_register(bfd);
if (ret < 0) {
LOGP(DLINP, LOGL_ERROR, "could not register FD\n");
@@ -1016,20 +1012,17 @@ static struct ipa_proxy_conn *connect_bsc(struct sockaddr_in *sa, int priv_nr, v
ipc->bts_conn = data;
- bfd = &ipc->fd;
- bfd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- bfd->cb = ipaccess_fd_cb;
- bfd->when = BSC_FD_READ | BSC_FD_WRITE;
- bfd->data = ipc;
- bfd->priv_nr = priv_nr;
-
- if (bfd->fd < 0) {
+ ret = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (ret < 0) {
LOGP(DLINP, LOGL_ERROR, "Could not create socket: %s\n",
strerror(errno));
talloc_free(ipc);
return NULL;
}
+ bfd = &ipc->fd;
+ osmo_fd_setup(bfd, ret, OSMO_FD_READ | OSMO_FD_WRITE, ipaccess_fd_cb, ipc, priv_nr);
+
ret = setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (ret < 0) {
LOGP(DLINP, LOGL_ERROR, "Could not set socket option\n");
@@ -1093,14 +1086,22 @@ static int ipaccess_proxy_setup(void)
return ret;
}
-static void signal_handler(int signal)
+static void signal_handler(int signum)
{
- fprintf(stdout, "signal %u received\n", signal);
+ fprintf(stdout, "signal %u received\n", signum);
- switch (signal) {
+ switch (signum) {
case SIGABRT:
- /* in case of abort, we want to obtain a talloc report
- * and then return to the caller, who will abort the process */
+ /* in case of abort, we want to obtain a talloc report and
+ * then run default SIGABRT handler, who will generate coredump
+ * and abort the process. abort() should do this for us after we
+ * return, but program wouldn't exit if an external SIGABRT is
+ * received.
+ */
+ talloc_report_full(tall_bsc_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
case SIGUSR1:
talloc_report_full(tall_bsc_ctx, stderr);
break;
@@ -1200,6 +1201,10 @@ static void handle_options(int argc, char** argv)
break;
}
}
+ if (argc > optind) {
+ fprintf(stderr, "Unsupported positional arguments on command line\n");
+ exit(2);
+ }
if ((options_mask & (IPA_PROXY_OPT_LISTEN_IP | IPA_PROXY_OPT_BSC_IP))
!= (IPA_PROXY_OPT_LISTEN_IP | IPA_PROXY_OPT_BSC_IP)) {
printf("ERROR: You have to specify `--listen' and `--bsc' "
diff --git a/src/ipaccess/network_listen.c b/src/ipaccess/network_listen.c
index bbaf79810..e9f5c93b0 100644
--- a/src/ipaccess/network_listen.c
+++ b/src/ipaccess/network_listen.c
@@ -34,6 +34,7 @@
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts_trx.h>
#include <osmocom/bsc/abis_nm.h>
#include <osmocom/bsc/signal.h>
#include <osmocom/bsc/debug.h>
diff --git a/src/ipaccess/stubs.c b/src/ipaccess/stubs.c
index cb561051a..8549397d2 100644
--- a/src/ipaccess/stubs.c
+++ b/src/ipaccess/stubs.c
@@ -1,6 +1,6 @@
/* Stubs required for linking */
-/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+/* (C) 2018-2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
@@ -19,28 +19,10 @@
*
*/
-#include <stdbool.h>
struct gsm_bts;
-struct gsm_bts_trx_ts;
-struct msgb;
-struct bsc_msc_data;
-bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts)
+int gsm_bts_check_cfg(struct gsm_bts *bts)
{
- /* No TS init required here. */
- return true;
-}
-
-int abis_rsl_rcvmsg(struct msgb *msg)
-{
- /* No RSL handling here */
+ /* No checks required here */
return 0;
}
-
-void paging_flush_bts(struct gsm_bts *bts, struct bsc_msc_data *msc)
-{
- /* No paging flushing */
-}
-
-void ts_fsm_alloc(struct gsm_bts_trx_ts *ts)
-{}
diff --git a/src/libfilter/Makefile.am b/src/libfilter/Makefile.am
deleted file mode 100644
index 8b0597bc6..000000000
--- a/src/libfilter/Makefile.am
+++ /dev/null
@@ -1,27 +0,0 @@
-AM_CPPFLAGS = \
- $(all_includes) \
- -I$(top_srcdir)/include \
- -I$(top_builddir) \
- $(NULL)
-
-AM_CFLAGS = \
- -Wall \
- $(LIBOSMOCORE_CFLAGS) \
- $(LIBOSMOGSM_CFLAGS) \
- $(LIBOSMOVTY_CFLAGS) \
- $(LIBOSMOABIS_CFLAGS) \
- $(LIBOSMOSIGTRAN_CFLAGS) \
- $(LIBOSMOLEGACYMGCP_CFLAGS) \
- $(COVERAGE_CFLAGS) \
- $(NULL)
-
-noinst_LIBRARIES = \
- libfilter.a \
- $(NULL)
-
-libfilter_a_SOURCES = \
- bsc_msg_filter.c \
- bsc_msg_acc.c \
- bsc_msg_vty.c \
- $(NULL)
-
diff --git a/src/libfilter/bsc_msg_acc.c b/src/libfilter/bsc_msg_acc.c
deleted file mode 100644
index de6c4d933..000000000
--- a/src/libfilter/bsc_msg_acc.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2010-2011 by On-Waves
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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/bsc_msg_filter.h>
-#include <osmocom/bsc/debug.h>
-
-#include <osmocom/gsm/protocol/gsm_04_08.h>
-
-#include <osmocom/core/rate_ctr.h>
-#include <osmocom/core/stats.h>
-
-#include <string.h>
-
-static const struct rate_ctr_desc acc_list_ctr_description[] = {
- [ACC_LIST_LOCAL_FILTER] = { "access-list:local-filter", "Rejected by rule for local"},
- [ACC_LIST_GLOBAL_FILTER]= { "access-list:global-filter", "Rejected by rule for global"},
-};
-
-static const struct rate_ctr_group_desc bsc_cfg_acc_list_desc = {
- .group_name_prefix = "nat:filter",
- .group_description = "NAT Access-List Statistics",
- .num_ctr = ARRAY_SIZE(acc_list_ctr_description),
- .ctr_desc = acc_list_ctr_description,
- .class_id = OSMO_STATS_CLASS_GLOBAL,
-};
-
-/*! Find an unused index for this rate counter group.
- * \param[in] head List of allocated ctr groups of the same type
- * \returns the largest used index number + 1, or 0 if none exist yet. */
-static unsigned int rate_ctr_get_unused_idx(struct llist_head *head)
-{
- unsigned int idx = 0;
- struct bsc_msg_acc_lst *lst;
-
- llist_for_each_entry(lst, head, list) {
- if (idx <= lst->stats->idx)
- idx = lst->stats->idx + 1;
- }
- return idx;
-}
-
-
-int bsc_msg_acc_lst_check_allow(struct bsc_msg_acc_lst *lst, const char *mi_string)
-{
- struct bsc_msg_acc_lst_entry *entry;
-
- llist_for_each_entry(entry, &lst->fltr_list, list) {
- if (!entry->imsi_allow)
- continue;
- if (regexec(&entry->imsi_allow_re, mi_string, 0, NULL, 0) == 0)
- return 0;
- }
-
- return 1;
-}
-
-struct bsc_msg_acc_lst *bsc_msg_acc_lst_find(struct llist_head *head, const char *name)
-{
- struct bsc_msg_acc_lst *lst;
-
- if (!name)
- return NULL;
-
- llist_for_each_entry(lst, head, list)
- if (strcmp(lst->name, name) == 0)
- return lst;
-
- return NULL;
-}
-
-struct bsc_msg_acc_lst *bsc_msg_acc_lst_get(void *ctx, struct llist_head *head, const char *name)
-{
- struct bsc_msg_acc_lst *lst;
- unsigned int new_idx;
-
- lst = bsc_msg_acc_lst_find(head, name);
- if (lst)
- return lst;
-
- lst = talloc_zero(ctx, struct bsc_msg_acc_lst);
- if (!lst) {
- LOGP(DNAT, LOGL_ERROR, "Failed to allocate access list\n");
- return NULL;
- }
-
- new_idx = rate_ctr_get_unused_idx(head);
- lst->stats = rate_ctr_group_alloc(lst, &bsc_cfg_acc_list_desc, new_idx);
- if (!lst->stats) {
- talloc_free(lst);
- return NULL;
- }
-
- INIT_LLIST_HEAD(&lst->fltr_list);
- lst->name = talloc_strdup(lst, name);
- llist_add_tail(&lst->list, head);
- return lst;
-}
-
-void bsc_msg_acc_lst_delete(struct bsc_msg_acc_lst *lst)
-{
- llist_del(&lst->list);
- rate_ctr_group_free(lst->stats);
- talloc_free(lst);
-}
-
-struct bsc_msg_acc_lst_entry *bsc_msg_acc_lst_entry_create(struct bsc_msg_acc_lst *lst)
-{
- struct bsc_msg_acc_lst_entry *entry;
-
- entry = talloc_zero(lst, struct bsc_msg_acc_lst_entry);
- if (!entry)
- return NULL;
-
- entry->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
- entry->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
- llist_add_tail(&entry->list, &lst->fltr_list);
- return entry;
-}
-
diff --git a/src/libfilter/bsc_msg_filter.c b/src/libfilter/bsc_msg_filter.c
deleted file mode 100644
index 1318689fa..000000000
--- a/src/libfilter/bsc_msg_filter.c
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Access filtering
- */
-/*
- * (C) 2010-2015 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2010-2012 by On-Waves
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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/bsc_msg_filter.h>
-
-#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/debug.h>
-#include <osmocom/bsc/ipaccess.h>
-#include <osmocom/bsc/gsm_04_08_rr.h>
-
-#include <osmocom/core/talloc.h>
-#include <osmocom/gsm/gsm0808.h>
-
-#include <osmocom/gsm/protocol/gsm_08_08.h>
-#include <osmocom/gsm/protocol/gsm_04_11.h>
-#include <osmocom/gsm/gsm48.h>
-
-static int bsc_filter_barr_find(struct rb_root *root, const char *imsi, int *cm, int *lu)
-{
- struct bsc_filter_barr_entry *n;
- n = rb_entry(root->rb_node, struct bsc_filter_barr_entry, node);
-
- while (n) {
- int rc = strcmp(imsi, n->imsi);
- if (rc == 0) {
- *cm = n->cm_reject_cause;
- *lu = n->lu_reject_cause;
- return 1;
- }
-
- n = rb_entry(
- (rc < 0) ? n->node.rb_left : n->node.rb_right,
- struct bsc_filter_barr_entry, node);
- };
-
- return 0;
-}
-
-
-static int lst_check_deny(struct bsc_msg_acc_lst *lst, const char *mi_string,
- int *cm_cause, int *lu_cause)
-{
- struct bsc_msg_acc_lst_entry *entry;
-
- llist_for_each_entry(entry, &lst->fltr_list, list) {
- if (!entry->imsi_deny)
- continue;
- if (regexec(&entry->imsi_deny_re, mi_string, 0, NULL, 0) == 0) {
- *cm_cause = entry->cm_reject_cause;
- *lu_cause = entry->lu_reject_cause;
- return 0;
- }
- }
-
- return 1;
-}
-
-/* apply white/black list */
-static int auth_imsi(struct bsc_filter_request *req,
- const char *imsi,
- struct bsc_filter_reject_cause *cause)
-{
- /*
- * Now apply blacklist/whitelist of the BSC and the NAT.
- * 1.) Check the global IMSI barr list
- * 2.) Allow directly if the IMSI is allowed at the BSC
- * 3.) Reject if the IMSI is not allowed at the BSC
- * 4.) Allow directly if the IMSI is allowed at the global level
- * 5.) Reject if the IMSI not allowed at the global level.
- */
- int cm, lu;
- struct bsc_msg_acc_lst *nat_lst = NULL;
- struct bsc_msg_acc_lst *bsc_lst = NULL;
-
- /* 1. global check for barred imsis */
- if (req->black_list && bsc_filter_barr_find(req->black_list, imsi, &cm, &lu)) {
- cause->cm_reject_cause = cm;
- cause->lu_reject_cause = lu;
- LOGP(DFILTER, LOGL_DEBUG,
- "Blocking subscriber IMSI %s with CM: %d LU: %d\n",
- imsi, cm, lu);
- return -4;
- }
-
-
- bsc_lst = bsc_msg_acc_lst_find(req->access_lists, req->local_lst_name);
- nat_lst = bsc_msg_acc_lst_find(req->access_lists, req->global_lst_name);
-
-
- if (bsc_lst) {
- /* 2. BSC allow */
- if (bsc_msg_acc_lst_check_allow(bsc_lst, imsi) == 0)
- return 1;
-
- /* 3. BSC deny */
- if (lst_check_deny(bsc_lst, imsi, &cm, &lu) == 0) {
- LOGP(DFILTER, LOGL_ERROR,
- "Filtering %s by imsi_deny on config nr: %d.\n", imsi, req->bsc_nr);
- rate_ctr_inc(&bsc_lst->stats->ctr[ACC_LIST_LOCAL_FILTER]);
- cause->cm_reject_cause = cm;
- cause->lu_reject_cause = lu;
- return -2;
- }
-
- }
-
- if (nat_lst) {
- /* 4. global allow */
- if (bsc_msg_acc_lst_check_allow(nat_lst, imsi) == 0)
- return 1;
-
- /* 5. global deny */
- if (lst_check_deny(nat_lst, imsi, &cm, &lu) == 0) {
- LOGP(DFILTER, LOGL_ERROR,
- "Filtering %s global imsi_deny on bsc nr: %d.\n", imsi, req->bsc_nr);
- rate_ctr_inc(&nat_lst->stats->ctr[ACC_LIST_GLOBAL_FILTER]);
- cause->cm_reject_cause = cm;
- cause->lu_reject_cause = lu;
- return -3;
- }
- }
-
- return 1;
-}
-
-static int _cr_check_loc_upd(void *ctx,
- uint8_t *data, unsigned int length,
- char **imsi)
-{
- uint8_t mi_type;
- struct gsm48_loc_upd_req *lu;
- char mi_string[GSM48_MI_SIZE];
-
- if (length < sizeof(*lu)) {
- LOGP(DFILTER, LOGL_ERROR,
- "LU does not fit. Length is %d \n", length);
- return -1;
- }
-
- lu = (struct gsm48_loc_upd_req *) data;
- mi_type = lu->mi[0] & GSM_MI_TYPE_MASK;
-
- /*
- * We can only deal with the IMSI. This will fail for a phone that
- * will send the TMSI of a previous network to us.
- */
- if (mi_type != GSM_MI_TYPE_IMSI)
- return 0;
-
- gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
- *imsi = talloc_strdup(ctx, mi_string);
- return 1;
-}
-
-static int _cr_check_cm_serv_req(void *ctx,
- uint8_t *data, unsigned int length,
- int *con_type, char **imsi)
-{
- static const uint32_t classmark_offset =
- offsetof(struct gsm48_service_request, classmark);
-
- char mi_string[GSM48_MI_SIZE];
- uint8_t mi_type;
- int rc;
- struct gsm48_service_request *req;
-
- /* unfortunately in Phase1 the classmark2 length is variable */
-
- if (length < sizeof(*req)) {
- LOGP(DFILTER, LOGL_ERROR,
- "CM Serv Req does not fit. Length is %d\n", length);
- return -1;
- }
-
- req = (struct gsm48_service_request *) data;
- if (req->cm_service_type == 0x8)
- *con_type = FLT_CON_TYPE_SSA;
- rc = gsm48_extract_mi((uint8_t *) &req->classmark,
- length - classmark_offset, mi_string, &mi_type);
- if (rc < 0) {
- LOGP(DFILTER, LOGL_ERROR, "Failed to parse the classmark2/mi. error: %d\n", rc);
- return -1;
- }
-
- /* we have to let the TMSI or such pass */
- if (mi_type != GSM_MI_TYPE_IMSI)
- return 0;
-
- *imsi = talloc_strdup(ctx, mi_string);
- return 1;
-}
-
-static int _cr_check_pag_resp(void *ctx,
- uint8_t *data, unsigned int length, char **imsi)
-{
- struct gsm48_pag_resp *resp;
- char mi_string[GSM48_MI_SIZE];
- uint8_t mi_type;
-
- if (length < sizeof(*resp)) {
- LOGP(DFILTER, LOGL_ERROR, "PAG RESP does not fit. Length was %d.\n", length);
- return -1;
- }
-
- resp = (struct gsm48_pag_resp *) data;
- if (gsm48_paging_extract_mi(resp, length, mi_string, &mi_type) < 0) {
- LOGP(DFILTER, LOGL_ERROR, "Failed to extract the MI.\n");
- return -1;
- }
-
- /* we need to let it pass for now */
- if (mi_type != GSM_MI_TYPE_IMSI)
- return 0;
-
- *imsi = talloc_strdup(ctx, mi_string);
- return 1;
-}
-
-static int _dt_check_id_resp(struct bsc_filter_request *req,
- uint8_t *data, unsigned int length,
- struct bsc_filter_state *state,
- struct bsc_filter_reject_cause *cause)
-{
- char mi_string[GSM48_MI_SIZE];
- uint8_t mi_type;
-
- if (length < 2) {
- LOGP(DFILTER, LOGL_ERROR, "mi does not fit.\n");
- return -1;
- }
-
- if (data[0] < length - 1) {
- LOGP(DFILTER, LOGL_ERROR, "mi length too big.\n");
- return -2;
- }
-
- mi_type = data[1] & GSM_MI_TYPE_MASK;
- gsm48_mi_to_string(mi_string, sizeof(mi_string), &data[1], data[0]);
-
- if (mi_type != GSM_MI_TYPE_IMSI)
- return 0;
-
- state->imsi_checked = 1;
- state->imsi = talloc_strdup(req->ctx, mi_string);
- return auth_imsi(req, mi_string, cause);
-}
-
-
-/* Filter out CR data... */
-int bsc_msg_filter_initial(struct gsm48_hdr *hdr48, size_t hdr48_len,
- struct bsc_filter_request *req,
- int *con_type,
- char **imsi, struct bsc_filter_reject_cause *cause)
-{
- int ret = 0;
- uint8_t msg_type, proto;
-
- *con_type = FLT_CON_TYPE_NONE;
- cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
- cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
- *imsi = NULL;
-
- proto = gsm48_hdr_pdisc(hdr48);
- msg_type = gsm48_hdr_msg_type(hdr48);
- if (proto == GSM48_PDISC_MM &&
- msg_type == GSM48_MT_MM_LOC_UPD_REQUEST) {
- *con_type = FLT_CON_TYPE_LU;
- ret = _cr_check_loc_upd(req->ctx, &hdr48->data[0],
- hdr48_len - sizeof(*hdr48), imsi);
- } else if (proto == GSM48_PDISC_MM &&
- msg_type == GSM48_MT_MM_CM_SERV_REQ) {
- *con_type = FLT_CON_TYPE_CM_SERV_REQ;
- ret = _cr_check_cm_serv_req(req->ctx, &hdr48->data[0],
- hdr48_len - sizeof(*hdr48),
- con_type, imsi);
- } else if (proto == GSM48_PDISC_RR &&
- msg_type == GSM48_MT_RR_PAG_RESP) {
- *con_type = FLT_CON_TYPE_PAG_RESP;
- ret = _cr_check_pag_resp(req->ctx, &hdr48->data[0],
- hdr48_len - sizeof(*hdr48), imsi);
- } else {
- /* We only want to filter the above, let other things pass */
- *con_type = FLT_CON_TYPE_OTHER;
- return 0;
- }
-
- /* check if we are done */
- if (ret != 1)
- return ret;
-
- /* the memory allocation failed */
- if (!*imsi)
- return -1;
-
- /* now check the imsi */
- return auth_imsi(req, *imsi, cause);
-}
-
-int bsc_msg_filter_data(struct gsm48_hdr *hdr48, size_t len,
- struct bsc_filter_request *req,
- struct bsc_filter_state *state,
- struct bsc_filter_reject_cause *cause)
-{
- uint8_t msg_type, proto;
-
- cause->cm_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
- cause->lu_reject_cause = GSM48_REJECT_PLMN_NOT_ALLOWED;
-
- if (state->imsi_checked)
- return 0;
-
- proto = gsm48_hdr_pdisc(hdr48);
- msg_type = gsm48_hdr_msg_type(hdr48);
- if (proto != GSM48_PDISC_MM || msg_type != GSM48_MT_MM_ID_RESP)
- return 0;
-
- return _dt_check_id_resp(req, &hdr48->data[0],
- len - sizeof(*hdr48), state, cause);
-}
diff --git a/src/libfilter/bsc_msg_vty.c b/src/libfilter/bsc_msg_vty.c
deleted file mode 100644
index b26f4f1a6..000000000
--- a/src/libfilter/bsc_msg_vty.c
+++ /dev/null
@@ -1,149 +0,0 @@
-/* (C) 2010-2015 by Holger Hans Peter Freyther
- * (C) 2010-2013 by On-Waves
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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/bsc_msg_filter.h>
-#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/vty.h>
-
-#include <osmocom/vty/misc.h>
-
-static struct llist_head *_acc_lst;
-static void *_ctx;
-
-static void bsc_msg_acc_lst_write_one(struct vty *vty, struct bsc_msg_acc_lst *lst)
-{
- struct bsc_msg_acc_lst_entry *entry;
-
- llist_for_each_entry(entry, &lst->fltr_list, list) {
- if (entry->imsi_allow)
- vty_out(vty, " access-list %s imsi-allow %s%s",
- lst->name, entry->imsi_allow, VTY_NEWLINE);
- if (entry->imsi_deny)
- vty_out(vty, " access-list %s imsi-deny %s %d %d%s",
- lst->name, entry->imsi_deny,
- entry->cm_reject_cause, entry->lu_reject_cause,
- VTY_NEWLINE);
- }
-}
-
-DEFUN(cfg_lst_no,
- cfg_lst_no_cmd,
- "no access-list NAME",
- NO_STR "Remove an access-list by name\n"
- "The access-list to remove\n")
-{
- struct bsc_msg_acc_lst *acc;
- acc = bsc_msg_acc_lst_find(_acc_lst, argv[0]);
- if (!acc)
- return CMD_WARNING;
-
- bsc_msg_acc_lst_delete(acc);
- return CMD_SUCCESS;
-}
-
-DEFUN(show_acc_lst,
- show_acc_lst_cmd,
- "show access-list NAME",
- SHOW_STR "IMSI access list\n" "Name of the access list\n")
-{
- struct bsc_msg_acc_lst *acc;
- acc = bsc_msg_acc_lst_find(_acc_lst, argv[0]);
- if (!acc)
- return CMD_WARNING;
-
- vty_out(vty, "access-list %s%s", acc->name, VTY_NEWLINE);
- bsc_msg_acc_lst_write_one(vty, acc);
- vty_out_rate_ctr_group(vty, " ", acc->stats);
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_lst_imsi_allow,
- cfg_lst_imsi_allow_cmd,
- "access-list NAME imsi-allow [REGEXP]",
- "Access list commands\n"
- "Name of the access list\n"
- "Add allowed IMSI to the list\n"
- "Regexp for IMSIs\n")
-{
- struct bsc_msg_acc_lst *acc;
- struct bsc_msg_acc_lst_entry *entry;
-
- acc = bsc_msg_acc_lst_get(_ctx, _acc_lst, argv[0]);
- if (!acc)
- return CMD_WARNING;
-
- entry = bsc_msg_acc_lst_entry_create(acc);
- if (!entry)
- return CMD_WARNING;
-
- if (gsm_parse_reg(acc, &entry->imsi_allow_re, &entry->imsi_allow, argc - 1, &argv[1]) != 0)
- return CMD_WARNING;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_lst_imsi_deny,
- cfg_lst_imsi_deny_cmd,
- "access-list NAME imsi-deny [REGEXP] (<0-256>) (<0-256>)",
- "Access list commands\n"
- "Name of the access list\n"
- "Add denied IMSI to the list\n"
- "Regexp for IMSIs\n"
- "CM Service Reject reason\n"
- "LU Reject reason\n")
-{
- struct bsc_msg_acc_lst *acc;
- struct bsc_msg_acc_lst_entry *entry;
-
- acc = bsc_msg_acc_lst_get(_ctx, _acc_lst, argv[0]);
- if (!acc)
- return CMD_WARNING;
-
- entry = bsc_msg_acc_lst_entry_create(acc);
- if (!entry)
- return CMD_WARNING;
-
- if (gsm_parse_reg(acc, &entry->imsi_deny_re, &entry->imsi_deny, argc - 1, &argv[1]) != 0)
- return CMD_WARNING;
- if (argc >= 3)
- entry->cm_reject_cause = atoi(argv[2]);
- if (argc >= 4)
- entry->lu_reject_cause = atoi(argv[3]);
- return CMD_SUCCESS;
-}
-
-void bsc_msg_acc_lst_write(struct vty *vty)
-{
- struct bsc_msg_acc_lst *lst;
- llist_for_each_entry(lst, _acc_lst, list) {
- bsc_msg_acc_lst_write_one(vty, lst);
- }
-}
-
-void bsc_msg_acc_lst_vty_init(void *ctx, struct llist_head *lst, int node)
-{
- _ctx = ctx;
- _acc_lst = lst;
- install_element_ve(&show_acc_lst_cmd);
-
- /* access-list */
- install_element(node, &cfg_lst_imsi_allow_cmd);
- install_element(node, &cfg_lst_imsi_deny_cmd);
- install_element(node, &cfg_lst_no_cmd);
-}
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index 364228d8c..10265235e 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -21,83 +21,132 @@ AM_LDFLAGS = \
$(COVERAGE_LDFLAGS) \
$(NULL)
-bin_PROGRAMS = \
- osmo-bsc \
- $(NULL)
+noinst_LTLIBRARIES = libbsc.la
-osmo_bsc_SOURCES = \
+libbsc_la_SOURCES = \
a_reset.c \
abis_nm.c \
abis_nm_vty.c \
abis_om2000.c \
abis_om2000_vty.c \
+ abis_osmo.c \
abis_rsl.c \
- acc_ramp.c \
- arfcn_range_encode.c \
+ acc.c \
assignment_fsm.c \
- bsc_ctrl_commands.c \
+ bsc_ctrl.c \
bsc_ctrl_lookup.c \
bsc_init.c \
bsc_rf_ctrl.c \
bsc_rll.c \
+ bsc_sccp.c \
+ bsc_stats.c \
bsc_subscr_conn_fsm.c \
bsc_subscriber.c \
bsc_vty.c \
+ bts.c \
+ bts_trx.c \
+ bts_trx_ctrl.c \
+ bts_trx_ts_ctrl.c \
+ bts_trx_ts_lchan_ctrl.c \
bts_ericsson_rbs2000.c \
bts_init.c \
bts_ipaccess_nanobts.c \
bts_ipaccess_nanobts_omlattr.c \
bts_nokia_site.c \
bts_siemens_bs11.c \
- bts_sysmobts.c \
+ bts_sm.c \
+ bts_osmobts.c \
bts_unknown.c \
+ bts_ctrl.c \
+ bts_setup_ramp.c \
+ bts_vty.c \
+ bts_trx_vty.c \
chan_alloc.c \
+ chan_counts.c \
codec_pref.c \
+ data_rate_pref.c \
e1_config.c \
gsm_04_08_rr.c \
- gsm_04_80_utils.c \
gsm_data.c \
- gsm_timers.c \
- gsm_timers_vty.c \
handover_cfg.c \
+ handover_ctrl.c \
handover_decision.c \
handover_decision_2.c \
handover_fsm.c \
handover_logic.c \
handover_vty.c \
+ vgcs_fsm.c \
+ lb.c \
+ lchan.c \
lchan_fsm.c \
lchan_rtp_fsm.c \
lchan_select.c \
+ lcs_loc_req.c \
+ lcs_ta_req.c \
meas_feed.c \
meas_rep.c \
- mgw_endpoint_fsm.c \
neighbor_ident.c \
neighbor_ident_vty.c \
+ neighbor_ident_ctrl.c \
net_init.c \
+ nm_common_fsm.c \
+ nm_bb_transc_fsm.c \
+ nm_bts_sm_fsm.c \
+ nm_bts_fsm.c \
+ nm_gprs_cell_fsm.c \
+ nm_gprs_nse_fsm.c \
+ nm_gprs_nsvc_fsm.c \
+ nm_channel_fsm.c \
+ nm_rcarrier_fsm.c \
gsm_08_08.c \
osmo_bsc_bssap.c \
- osmo_bsc_ctrl.c \
osmo_bsc_filter.c \
osmo_bsc_grace.c \
osmo_bsc_lcls.c \
- osmo_bsc_main.c \
+ osmo_bsc_mgcp.c \
osmo_bsc_msc.c \
osmo_bsc_sigtran.c \
- osmo_bsc_vty.c \
paging.c \
pcu_sock.c \
penalty_timers.c \
- rest_octets.c \
+ bssmap_reset.c \
system_information.c \
timeslot_fsm.c \
+ smscb.c \
+ smscb_vty.c \
+ cbch_scheduler.c \
+ cbsp_link.c \
+ power_control.c \
+ $(NULL)
+
+libbsc_la_LIBADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
+ $(COVERAGE_LDFLAGS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(LIBOSMOSIGTRAN_LIBS) \
+ $(LIBOSMOMGCPCLIENT_LIBS) \
+ -lm \
+ $(NULL)
+
+bin_PROGRAMS = \
+ osmo-bsc \
+ $(NULL)
+
+osmo_bsc_SOURCES = \
+ osmo_bsc_main.c \
$(NULL)
osmo_bsc_LDADD = \
- $(top_builddir)/src/libfilter/libfilter.a \
+ libbsc.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMONETIF_LIBS) \
$(COVERAGE_LDFLAGS) \
$(LIBOSMOABIS_LIBS) \
$(LIBOSMOSIGTRAN_LIBS) \
diff --git a/src/osmo-bsc/a_reset.c b/src/osmo-bsc/a_reset.c
index 3c2114219..c2c89e37b 100644
--- a/src/osmo-bsc/a_reset.c
+++ b/src/osmo-bsc/a_reset.c
@@ -18,166 +18,80 @@
*
*/
-#include <osmocom/core/logging.h>
-#include <osmocom/core/utils.h>
-#include <osmocom/core/timer.h>
-#include <osmocom/core/fsm.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/bsc/signal.h>
+
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/bssmap_reset.h>
+#include <osmocom/bsc/bsc_stats.h>
-#define RESET_RESEND_INTERVAL 2 /* sec */
-#define RESET_RESEND_TIMER_NO 4 /* See also 3GPP TS 48.008 Chapter 3.1.4.1.3.1 */
-#define BAD_CONNECTION_THRESOLD 3 /* connection failures */
-
-/* Reset context data (callbacks, state machine etc...) */
-struct reset_ctx {
- /* Connection failure counter. When this counter
- * reaches a certain threshold, the reset procedure
- * will be triggered */
- int conn_loss_counter;
-
- /* Callback function to be called when a connection
- * failure is detected and a rest must occur */
- void (*cb)(void *priv);
-
- /* Privated data for the callback function */
- void *priv;
-};
-
-enum reset_fsm_states {
- ST_DISC, /* Disconnected from remote end */
- ST_CONN, /* We have a confirmed connection */
-};
-
-enum reset_fsm_evt {
- EV_RESET_ACK, /* got reset acknowlegement from remote end */
- EV_N_DISCONNECT, /* lost a connection */
- EV_N_CONNECT, /* made a successful connection */
-};
-
-static const struct value_string fsm_event_names[] = {
- OSMO_VALUE_STRING(EV_RESET_ACK),
- OSMO_VALUE_STRING(EV_N_DISCONNECT),
- OSMO_VALUE_STRING(EV_N_CONNECT),
- {0, NULL}
-};
-
-/* Disconnected state */
-static void fsm_disc_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void a_reset_tx_reset(void *data)
{
- struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv;
- OSMO_ASSERT(reset_ctx);
- LOGPFSML(fi, LOGL_NOTICE, "SIGTRAN connection succeded.\n");
-
- reset_ctx->conn_loss_counter = 0;
- osmo_fsm_inst_state_chg(fi, ST_CONN, 0, 0);
+ struct bsc_msc_data *msc = data;
+ osmo_bsc_sigtran_tx_reset(msc);
}
-/* Connected state */
-static void fsm_conn_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void a_reset_tx_reset_ack(void *data)
{
- struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv;
- OSMO_ASSERT(reset_ctx);
-
- switch (event) {
- case EV_N_DISCONNECT:
- if (reset_ctx->conn_loss_counter >= BAD_CONNECTION_THRESOLD) {
- LOGPFSML(fi, LOGL_NOTICE, "SIGTRAN connection down, reconnecting...\n");
- osmo_fsm_inst_state_chg(fi, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO);
- } else
- reset_ctx->conn_loss_counter++;
- break;
- case EV_N_CONNECT:
- reset_ctx->conn_loss_counter = 0;
- break;
- }
+ struct bsc_msc_data *msc = data;
+ osmo_bsc_sigtran_tx_reset_ack(msc);
}
-/* Timer callback to retransmit the reset signal */
-static int fsm_reset_ack_timeout_cb(struct osmo_fsm_inst *fi)
+static void a_reset_link_up(void *data)
{
- struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv;
- OSMO_ASSERT(reset_ctx);
-
- LOGPFSML(fi, LOGL_NOTICE, "(re)sending BSSMAP RESET message...\n");
-
- reset_ctx->cb(reset_ctx->priv);
-
- osmo_fsm_inst_state_chg(fi, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO);
- return 0;
+ struct bsc_msc_data *msc = data;
+ LOGP(DMSC, LOGL_NOTICE, "(msc%d) BSSMAP association is up\n", msc->nr);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(msc->msc_statg, MSC_STAT_MSC_LINKS_ACTIVE), 1);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(msc->network->bsc_statg, BSC_STAT_NUM_MSC_CONNECTED), 1);
+ osmo_signal_dispatch(SS_MSC, S_MSC_CONNECTED, msc);
}
-static struct osmo_fsm_state reset_fsm_states[] = {
- [ST_DISC] = {
- .in_event_mask = (1 << EV_RESET_ACK),
- .out_state_mask = (1 << ST_DISC) | (1 << ST_CONN),
- .name = "DISC",
- .action = fsm_disc_cb,
- },
- [ST_CONN] = {
- .in_event_mask = (1 << EV_N_DISCONNECT) | (1 << EV_N_CONNECT),
- .out_state_mask = (1 << ST_DISC) | (1 << ST_CONN),
- .name = "CONN",
- .action = fsm_conn_cb,
- },
-};
-
-/* State machine definition */
-static struct osmo_fsm fsm = {
- .name = "A-RESET",
- .states = reset_fsm_states,
- .num_states = ARRAY_SIZE(reset_fsm_states),
- .log_subsys = DMSC,
- .timer_cb = fsm_reset_ack_timeout_cb,
- .event_names = fsm_event_names,
-};
+static void a_reset_link_lost(void *data)
+{
+ struct bsc_msc_data *msc = data;
+ LOGP(DMSC, LOGL_NOTICE, "(msc%d) BSSMAP association is down\n", msc->nr);
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(msc->msc_statg, MSC_STAT_MSC_LINKS_ACTIVE), 1);
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(msc->network->bsc_statg, BSC_STAT_NUM_MSC_CONNECTED), 1);
+ osmo_signal_dispatch(SS_MSC, S_MSC_LOST, msc);
+ osmo_bsc_sigtran_reset(msc);
+}
/* Create and start state machine which handles the reset/reset-ack procedure */
-void a_reset_alloc(struct bsc_msc_data *msc, const char *name, void *cb)
+void a_reset_alloc(struct bsc_msc_data *msc, const char *name)
{
- struct reset_ctx *reset_ctx;
- struct osmo_fsm_inst *reset_fsm;
-
- OSMO_ASSERT(msc);
- OSMO_ASSERT(name);
- OSMO_ASSERT(cb);
+ struct bssmap_reset_cfg cfg = {
+ .conn_cfm_failure_threshold = 3,
+ .ops = {
+ .tx_reset = a_reset_tx_reset,
+ .tx_reset_ack = a_reset_tx_reset_ack,
+ .link_up = a_reset_link_up,
+ .link_lost = a_reset_link_lost,
+ },
+ .data = msc,
+ };
/* There must not be any double allocation! */
- OSMO_ASSERT(msc->a.reset_fsm == NULL);
-
- /* Register the fsm description (if not already done) */
- if (osmo_fsm_find_by_name(fsm.name) != &fsm)
- osmo_fsm_register(&fsm);
-
- /* Allocate and configure a new fsm instance */
- reset_ctx = talloc_zero(msc, struct reset_ctx);
- OSMO_ASSERT(reset_ctx);
- reset_ctx->priv = msc;
- reset_ctx->cb = cb;
- reset_ctx->conn_loss_counter = 0;
- reset_fsm = osmo_fsm_inst_alloc(&fsm, msc, reset_ctx, LOGL_DEBUG, name);
- OSMO_ASSERT(reset_fsm);
-
- /* kick off reset-ack sending mechanism */
- osmo_fsm_inst_state_chg(reset_fsm, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO);
+ if (msc->a.bssmap_reset) {
+ LOGP(DMSC, LOGL_ERROR, "(msc%d) will not allocate a second reset FSM for this MSC\n", msc->nr);
+ return;
+ }
- msc->a.reset_fsm = reset_fsm;
+ msc->a.bssmap_reset = bssmap_reset_alloc(msc, name, &cfg);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(msc->network->bsc_statg, BSC_STAT_NUM_MSC_TOTAL), 1);
}
-/* Confirm that we sucessfully received a reset acknowlege message */
+/* Confirm that we successfully received a reset acknowledge message */
void a_reset_ack_confirm(struct bsc_msc_data *msc)
{
if (!msc)
return;
- if (!msc->a.reset_fsm)
+ if (!msc->a.bssmap_reset)
return;
- osmo_fsm_inst_dispatch(msc->a.reset_fsm, EV_RESET_ACK, NULL);
+ osmo_fsm_inst_dispatch(msc->a.bssmap_reset->fi, BSSMAP_RESET_EV_RX_RESET_ACK, NULL);
}
/* Report a failed connection */
@@ -186,10 +100,10 @@ void a_reset_conn_fail(struct bsc_msc_data *msc)
if (!msc)
return;
- if (!msc->a.reset_fsm)
+ if (!msc->a.bssmap_reset)
return;
- osmo_fsm_inst_dispatch(msc->a.reset_fsm, EV_N_DISCONNECT, NULL);
+ osmo_fsm_inst_dispatch(msc->a.bssmap_reset->fi, BSSMAP_RESET_EV_CONN_CFM_FAILURE, NULL);
}
/* Report a successful connection */
@@ -198,10 +112,10 @@ void a_reset_conn_success(struct bsc_msc_data *msc)
if (!msc)
return;
- if (!msc->a.reset_fsm)
+ if (!msc->a.bssmap_reset)
return;
- osmo_fsm_inst_dispatch(msc->a.reset_fsm, EV_N_CONNECT, NULL);
+ osmo_fsm_inst_dispatch(msc->a.bssmap_reset->fi, BSSMAP_RESET_EV_CONN_CFM_SUCCESS, NULL);
}
/* Check if we have a connection to a specified msc */
@@ -210,11 +124,8 @@ bool a_reset_conn_ready(struct bsc_msc_data *msc)
if (!msc)
return false;
- if (!msc->a.reset_fsm)
+ if (!msc->a.bssmap_reset)
return false;
- if (msc->a.reset_fsm->state == ST_CONN)
- return true;
-
- return false;
+ return bssmap_reset_is_conn_ready(msc->a.bssmap_reset);
}
diff --git a/src/osmo-bsc/abis_nm.c b/src/osmo-bsc/abis_nm.c
index f1306fcfb..afb7abc6b 100644
--- a/src/osmo-bsc/abis_nm.c
+++ b/src/osmo-bsc/abis_nm.c
@@ -48,14 +48,24 @@
#include <osmocom/bsc/signal.h>
#include <osmocom/abis/e1_input.h>
#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/nm_common_fsm.h>
#include <osmocom/gsm/bts_features.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
#define OM_ALLOC_SIZE 1024
#define OM_HEADROOM_SIZE 128
#define IPACC_SEGMENT_SIZE 245
-#define LOGPFOH(ss, lvl, foh, fmt, args ...) LOGP(ss, lvl, "%s: " fmt, abis_nm_dump_foh(foh), ## args)
-#define DEBUGPFOH(ss, foh, fmt, args ...) LOGPFOH(ss, LOGL_DEBUG, foh, fmt, ## args)
+/* max number of SW Description IEs we can parse */
+#define SW_DESCR_MAX 5
+
+#define LOGPMO(mo, ss, lvl, fmt, args ...) \
+ LOGP(ss, lvl, "OC=%s(%02x) INST=(%02x,%02x,%02x): " fmt, \
+ get_value_string(abis_nm_obj_class_names, (mo)->obj_class), \
+ (mo)->obj_class, (mo)->obj_inst.bts_nr, (mo)->obj_inst.trx_nr, \
+ (mo)->obj_inst.ts_nr, ## args)
int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const uint8_t *buf, int len)
{
@@ -162,6 +172,7 @@ int _abis_nm_sendmsg(struct msgb *msg)
if (!msg->dst) {
LOGP(DNM, LOGL_ERROR, "%s: msg->dst == NULL\n", __func__);
+ msgb_free(msg);
return -EINVAL;
}
@@ -203,7 +214,7 @@ static int abis_nm_rcvmsg_sw(struct msgb *mb);
static int update_admstate(struct gsm_bts *bts, uint8_t obj_class,
struct abis_om_obj_inst *obj_inst, uint8_t adm_state)
{
- struct gsm_nm_state *nm_state, new_state;
+ struct gsm_nm_state *nm_state;
struct nm_statechg_signal_data nsd;
memset(&nsd, 0, sizeof(nsd));
@@ -215,18 +226,18 @@ static int update_admstate(struct gsm_bts *bts, uint8_t obj_class,
if (!nm_state)
return -1;
- new_state = *nm_state;
- new_state.administrative = adm_state;
-
nsd.bts = bts;
nsd.obj_class = obj_class;
- nsd.old_state = nm_state;
- nsd.new_state = &new_state;
+ nsd.old_state = *nm_state;
+ nsd.new_state = *nm_state;
nsd.obj_inst = obj_inst;
- osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd);
+ nsd.new_state.administrative = adm_state;
+
+ /* Update current state before emitting signal: */
nm_state->administrative = adm_state;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd);
return 0;
}
@@ -237,70 +248,67 @@ static int abis_nm_rx_statechg_rep(struct msgb *mb)
struct e1inp_sign_link *sign_link = mb->dst;
struct gsm_bts *bts = sign_link->trx->bts;
struct tlv_parsed tp;
- struct gsm_nm_state *nm_state, new_state;
+ struct gsm_nm_state *nm_state;
+ struct nm_statechg_signal_data nsd;
- memset(&new_state, 0, sizeof(new_state));
+ memset(&nsd, 0, sizeof(nsd));
+ nsd.obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst);
+ if (!nsd.obj) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "unknown managed object\n");
+ return -EINVAL;
+ }
nm_state = gsm_objclass2nmstate(bts, foh->obj_class, &foh->obj_inst);
if (!nm_state) {
LOGPFOH(DNM, LOGL_ERROR, foh, "unknown managed object\n");
return -EINVAL;
}
- new_state = *nm_state;
+ nsd.obj_class = foh->obj_class;
+ nsd.old_state = *nm_state;
+ nsd.new_state = *nm_state;
+ nsd.obj_inst = &foh->obj_inst;
+ nsd.bts = bts;
+
+ if (abis_nm_tlv_parse(&tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
DEBUGPFOH(DNM, foh, "STATE CHG: ");
- abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
if (TLVP_PRESENT(&tp, NM_ATT_OPER_STATE)) {
- new_state.operational = *TLVP_VAL(&tp, NM_ATT_OPER_STATE);
+ nsd.new_state.operational = *TLVP_VAL(&tp, NM_ATT_OPER_STATE);
DEBUGPC(DNM, "OP_STATE=%s ",
- abis_nm_opstate_name(new_state.operational));
+ abis_nm_opstate_name(nsd.new_state.operational));
}
if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) {
if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0)
- new_state.availability = 0xff;
+ nsd.new_state.availability = NM_AVSTATE_OK;
else
- new_state.availability = *TLVP_VAL(&tp, NM_ATT_AVAIL_STATUS);
+ nsd.new_state.availability = *TLVP_VAL(&tp, NM_ATT_AVAIL_STATUS);
DEBUGPC(DNM, "AVAIL=%s(%02x) ",
- abis_nm_avail_name(new_state.availability),
- new_state.availability);
+ abis_nm_avail_name(nsd.new_state.availability),
+ nsd.new_state.availability);
} else
- new_state.availability = 0xff;
+ nsd.new_state.availability = NM_AVSTATE_OK;
if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) {
- new_state.administrative = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+ nsd.new_state.administrative = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
DEBUGPC(DNM, "ADM=%2s ",
get_value_string(abis_nm_adm_state_names,
- new_state.administrative));
+ nsd.new_state.administrative));
}
- DEBUGPC(DNM, "\n");
- if ((new_state.administrative != 0 && nm_state->administrative == 0) ||
- new_state.operational != nm_state->operational ||
- new_state.availability != nm_state->availability) {
- /* Update the operational state of a given object in our in-memory data
+ if ((nsd.new_state.administrative != 0 && nsd.old_state.administrative == 0) ||
+ nsd.new_state.operational != nsd.old_state.operational ||
+ nsd.new_state.availability != nsd.old_state.availability) {
+ DEBUGPC(DNM, "\n");
+ /* Update the state of a given object in our in-memory data
* structures and send an event to the higher layer */
- struct nm_statechg_signal_data nsd;
- nsd.obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst);
- nsd.obj_class = foh->obj_class;
- nsd.old_state = nm_state;
- nsd.new_state = &new_state;
- nsd.obj_inst = &foh->obj_inst;
- nsd.bts = bts;
- osmo_signal_dispatch(SS_NM, S_NM_STATECHG_OPER, &nsd);
- nm_state->operational = new_state.operational;
- nm_state->availability = new_state.availability;
- if (nm_state->administrative == 0)
- nm_state->administrative = new_state.administrative;
- }
-#if 0
- if (op_state == 1) {
- /* try to enable objects that are disabled */
- abis_nm_opstart(bts, foh->obj_class,
- foh->obj_inst.bts_nr,
- foh->obj_inst.trx_nr,
- foh->obj_inst.ts_nr);
+ *nm_state = nsd.new_state;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd);
+ } else {
+ DEBUGPC(DNM, "(No State change detected)\n");
}
-#endif
return 0;
}
@@ -311,7 +319,7 @@ static inline void log_oml_fail_rep(const struct gsm_bts *bts, const char *type,
enum abis_nm_pcause_type pcause = p_val[0];
enum abis_mm_event_causes cause = osmo_load16be(p_val + 1);
- LOGPC(DNM, LOGL_ERROR, "BTS %u: Failure Event Report: ", bts->nr);
+ LOGPMO(&bts->mo, DNM, LOGL_ERROR, "Failure Event Report: ");
if (type)
LOGPC(DNM, LOGL_ERROR, "Type=%s, ", type);
if (severity)
@@ -341,10 +349,10 @@ static inline void handle_manufact_report(struct gsm_bts *bts, const uint8_t *p_
switch (cause) {
case OSMO_EVT_PCU_VERS:
if (text) {
- LOGPC(DNM, LOGL_NOTICE, "BTS %u reported connected PCU version %s\n", bts->nr, text);
+ LOGPMO(&bts->mo, DNM, LOGL_NOTICE, "Reported connected PCU version %s\n", text);
osmo_strlcpy(bts->pcu_version, text, sizeof(bts->pcu_version));
} else {
- LOGPC(DNM, LOGL_ERROR, "BTS %u reported PCU disconnection.\n", bts->nr);
+ LOGPMO(&bts->mo, DNM, LOGL_ERROR, "Reported PCU disconnection.\n");
bts->pcu_version[0] = '\0';
}
break;
@@ -353,37 +361,94 @@ static inline void handle_manufact_report(struct gsm_bts *bts, const uint8_t *p_
};
}
-static int rx_fail_evt_rep(struct msgb *mb, struct gsm_bts *bts)
+/* Parse into newly allocated struct abis_nm_fail_evt_rep, caller must free it. */
+struct nm_fail_rep_signal_data *abis_nm_fail_evt_rep_parse(struct msgb *mb, struct gsm_bts *bts)
{
struct abis_om_hdr *oh = msgb_l2(mb);
struct abis_om_fom_hdr *foh = msgb_l3(mb);
- struct e1inp_sign_link *sign_link = mb->dst;
- struct tlv_parsed tp;
- int rc = 0;
+ struct nm_fail_rep_signal_data *sd;
const uint8_t *p_val = NULL;
char *p_text = NULL;
const char *e_type = NULL, *severity = NULL;
- abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data,
- oh->length-sizeof(*foh));
+ sd = talloc_zero(tall_bsc_ctx, struct nm_fail_rep_signal_data);
+ OSMO_ASSERT(sd);
+
+ if (abis_nm_tlv_parse(&sd->tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ goto fail;
+ }
- if (TLVP_PRESENT(&tp, NM_ATT_ADD_TEXT)) {
- p_val = TLVP_VAL(&tp, NM_ATT_ADD_TEXT);
- p_text = talloc_strndup(tall_bsc_ctx, (const char *) p_val,
- TLVP_LEN(&tp, NM_ATT_ADD_TEXT));
+ if (TLVP_PRESENT(&sd->tp, NM_ATT_ADD_TEXT)) {
+ const uint8_t *val = TLVP_VAL(&sd->tp, NM_ATT_ADD_TEXT);
+ p_text = talloc_strndup(sd, (const char *) val, TLVP_LEN(&sd->tp, NM_ATT_ADD_TEXT));
}
- if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE))
- e_type = abis_nm_event_type_name(*TLVP_VAL(&tp,
- NM_ATT_EVENT_TYPE));
+ if (TLVP_PRESENT(&sd->tp, NM_ATT_EVENT_TYPE))
+ e_type = abis_nm_event_type_name(*TLVP_VAL(&sd->tp, NM_ATT_EVENT_TYPE));
- if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY))
- severity = abis_nm_severity_name(*TLVP_VAL(&tp,
- NM_ATT_SEVERITY));
+ if (TLVP_PRESENT(&sd->tp, NM_ATT_SEVERITY))
+ severity = abis_nm_severity_name(*TLVP_VAL(&sd->tp, NM_ATT_SEVERITY));
- if (TLVP_PRESENT(&tp, NM_ATT_PROB_CAUSE)) {
- p_val = TLVP_VAL(&tp, NM_ATT_PROB_CAUSE);
+ if (TLVP_PRESENT(&sd->tp, NM_ATT_PROB_CAUSE))
+ p_val = TLVP_VAL(&sd->tp, NM_ATT_PROB_CAUSE);
+
+ sd->bts = bts;
+ sd->msg = mb;
+ if (e_type)
+ sd->parsed.event_type = e_type;
+ else
+ sd->parsed.event_type = talloc_strdup(sd, "<none>");
+ if (severity)
+ sd->parsed.severity = severity;
+ else
+ sd->parsed.severity = talloc_strdup(sd, "<none>");
+ if (p_text)
+ sd->parsed.additional_text = p_text;
+ else
+ sd->parsed.additional_text = talloc_strdup(sd, "<none>");
+ sd->parsed.probable_cause = p_val;
+
+ return sd;
+fail:
+ talloc_free(sd);
+ return NULL;
+}
+static int rx_fail_evt_rep(struct msgb *mb, struct gsm_bts *bts)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ struct nm_fail_rep_signal_data *sd;
+ int rc = 0;
+ const uint8_t *p_val;
+ const char *e_type, *severity, *p_text;
+ struct bts_oml_fail_rep *entry;
+
+ /* Store copy in bts->oml_fail_rep */
+ entry = talloc_zero(bts, struct bts_oml_fail_rep);
+ OSMO_ASSERT(entry);
+ entry->time = time(NULL);
+ entry->mb = msgb_copy_c(entry, mb, "OML failure report");
+ llist_add(&entry->list, &bts->oml_fail_rep);
+
+ /* Limit list size */
+ if (llist_count(&bts->oml_fail_rep) > 50) {
+ struct bts_oml_fail_rep *old = llist_last_entry(&bts->oml_fail_rep, struct bts_oml_fail_rep, list);
+ llist_del(&old->list);
+ talloc_free(old);
+ }
+
+ sd = abis_nm_fail_evt_rep_parse(mb, bts);
+ if (!sd) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Failed to parse Failure Event Report\n");
+ return -EINVAL;
+ }
+ e_type = sd->parsed.event_type;
+ severity = sd->parsed.severity;
+ p_text = sd->parsed.additional_text;
+ p_val = sd->parsed.probable_cause;
+
+ if (p_val) {
switch (p_val[0]) {
case NM_PCAUSE_T_MANUF:
handle_manufact_report(bts, p_val, e_type, severity,
@@ -393,14 +458,12 @@ static int rx_fail_evt_rep(struct msgb *mb, struct gsm_bts *bts)
log_oml_fail_rep(bts, e_type, severity, p_val, p_text);
};
} else {
- LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: Failure Event Report without "
- "Probable Cause?!\n", bts->nr);
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Failure Event Report without Probable Cause?!\n");
rc = -EINVAL;
}
- if (p_text)
- talloc_free(p_text);
-
+ osmo_signal_dispatch(SS_NM, S_NM_FAIL_REP, sd);
+ talloc_free(sd);
return rc;
}
@@ -419,7 +482,6 @@ static int abis_nm_rcvmsg_report(struct msgb *mb, struct gsm_bts *bts)
break;
case NM_MT_FAILURE_EVENT_REP:
rx_fail_evt_rep(mb, bts);
- osmo_signal_dispatch(SS_NM, S_NM_FAIL_REP, mb);
break;
case NM_MT_TEST_REP:
DEBUGPFOH(DNM, foh, "Test Report\n");
@@ -469,10 +531,10 @@ static inline bool handle_attr(const struct gsm_bts *bts, enum bts_attribute id,
{
switch (id) {
case BTS_TYPE_VARIANT:
- LOGP(DNM, LOGL_NOTICE, "BTS%u reported variant: %s\n", bts->nr, val);
+ LOGPMO(&bts->mo, DNM, LOGL_NOTICE, "Reported variant: %s\n", val);
break;
case BTS_SUB_MODEL:
- LOGP(DNM, LOGL_NOTICE, "BTS%u reported submodel: %s\n", bts->nr, val);
+ LOGPMO(&bts->mo, DNM, LOGL_NOTICE, "Reported submodel: %s\n", val);
break;
default:
return false;
@@ -481,17 +543,19 @@ static inline bool handle_attr(const struct gsm_bts *bts, enum bts_attribute id,
}
/* Parse Attribute Response Info - return pointer to the actual content */
-static inline const uint8_t *parse_attr_resp_info_unreported(uint8_t bts_nr, const uint8_t *ari, uint16_t ari_len, uint16_t *out_len)
+static inline const uint8_t *parse_attr_resp_info_unreported(const struct abis_om_fom_hdr *foh,
+ const uint8_t *ari, uint16_t ari_len,
+ uint16_t *out_len)
{
uint8_t num_unreported = ari[0], i;
- DEBUGP(DNM, "BTS%u Get Attributes Response Info: %u bytes total with %u unreported attributes\n",
- bts_nr, ari_len, num_unreported);
+ DEBUGPFOH(DNM, foh, "Get Attributes Response Info: %u bytes total "
+ "with %u unreported attributes\n", ari_len, num_unreported);
/* +1 because we have to account for number of unreported attributes, prefixing the list: */
for (i = 0; i < num_unreported; i++)
- LOGP(DNM, LOGL_ERROR, "BTS%u Attribute %s is unreported\n",
- bts_nr, get_value_string(abis_nm_att_names, ari[i + 1]));
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Attribute %s is unreported\n",
+ get_value_string(abis_nm_att_names, ari[i + 1]));
/* the data starts right after the list of unreported attributes + space for length of that list */
if (out_len)
@@ -500,6 +564,45 @@ static inline const uint8_t *parse_attr_resp_info_unreported(uint8_t bts_nr, con
return ari + num_unreported + 1; /* we have to account for 1st byte with number of unreported attributes */
}
+/* Parse Attribute Response Info content for 3GPP TS 52.021 ยง9.4.30 Manufacturer Id */
+static void parse_osmo_bts_features(struct gsm_bts *bts,
+ const uint8_t *data, uint16_t data_len)
+{
+ /* log potential BTS feature vector overflow */
+ if (data_len > sizeof(bts->_features_data)) {
+ LOGPMO(&bts->mo, DNM, LOGL_NOTICE,
+ "Get Attributes Response: feature vector is truncated "
+ "(from %u to %zu bytes)\n", data_len, sizeof(bts->_features_data));
+ data_len = sizeof(bts->_features_data);
+ }
+
+ /* check that max. expected BTS attribute is above given feature vector length */
+ if (data_len > OSMO_BYTES_FOR_BITS(_NUM_BTS_FEAT)) {
+ LOGPMO(&bts->mo, DNM, LOGL_NOTICE,
+ "Get Attributes Response: reported unexpectedly long (%u bytes) "
+ "feature vector - most likely it was compiled against newer BSC headers. "
+ "Consider upgrading your BSC to later version.\n", data_len);
+ }
+
+ memcpy(bts->_features_data, data, data_len);
+ bts->features_known = true;
+
+ /* Log each BTS feature in the reported vector */
+ for (unsigned int i = 0; i < data_len * 8; i++) {
+ if (!osmo_bts_has_feature(&bts->features, i))
+ continue;
+
+ if (i >= _NUM_BTS_FEAT) {
+ LOGPMO(&bts->mo, DNM, LOGL_NOTICE,
+ "Get Attributes Response: unknown feature 0x%02x is supported\n", i);
+ } else {
+ LOGPMO(&bts->mo, DNM, LOGL_NOTICE,
+ "Get Attributes Response: feature '%s' is supported\n",
+ osmo_bts_features_name(i));
+ }
+ }
+}
+
/* Handle 3GPP TS 52.021 ยง8.11.3 Get Attribute Response (with nanoBTS specific attribute formatting) */
static int parse_attr_resp_info_attr(struct gsm_bts *bts, const struct gsm_bts_trx *trx, struct abis_om_fom_hdr *foh, struct tlv_parsed *tp)
{
@@ -510,43 +613,34 @@ static int parse_attr_resp_info_attr(struct gsm_bts *bts, const struct gsm_bts_t
uint16_t port;
struct in_addr ia = {0};
char unit_id[40];
- struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR];
-
- /* Parse Attribute Response Info content for 3GPP TS 52.021 ยง9.4.30 Manufacturer Id */
- if (TLVP_PRES_LEN(tp, NM_ATT_MANUF_ID, 2)) {
- len = TLVP_LEN(tp, NM_ATT_MANUF_ID);
-
- /* log potential BTS feature vector overflow */
- if (len > sizeof(bts->_features_data))
- LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: feature vector is truncated to %u bytes\n",
- bts->nr, MAX_BTS_FEATURES/8);
-
- /* check that max. expected BTS attribute is above given feature vector length */
- if (len > OSMO_BYTES_FOR_BITS(_NUM_BTS_FEAT))
- LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: reported unexpectedly long (%u bytes) "
- "feature vector - most likely it was compiled against newer BSC headers. "
- "Consider upgrading your BSC to later version.\n",
- bts->nr, len);
-
- memcpy(bts->_features_data, TLVP_VAL(tp, NM_ATT_MANUF_ID), sizeof(bts->_features_data));
- for (i = 0; i < _NUM_BTS_FEAT; i++)
- if (osmo_bts_has_feature(&bts->features, i) != osmo_bts_has_feature(&bts->model->features, i))
- LOGP(DNM, LOGL_NOTICE, "BTS%u feature '%s' reported via OML does not match statically "
- "set feature: %u != %u. Please fix.\n", bts->nr,
- get_value_string(osmo_bts_features_descs, i),
- osmo_bts_has_feature(&bts->features, i), osmo_bts_has_feature(&bts->model->features, i));
+ switch (bts->type) {
+ case GSM_BTS_TYPE_OSMOBTS:
+ if (TLVP_PRES_LEN(tp, NM_ATT_MANUF_ID, 2)) {
+ parse_osmo_bts_features(bts, TLVP_VAL(tp, NM_ATT_MANUF_ID),
+ TLVP_LEN(tp, NM_ATT_MANUF_ID));
+ }
+ /* fall-through */
+ case GSM_BTS_TYPE_NANOBTS:
+ if (TLVP_PRESENT(tp, NM_ATT_IPACC_SUPP_FEATURES)) {
+ ipacc_parse_supp_features(bts, foh, TLVP_VAL(tp, NM_ATT_IPACC_SUPP_FEATURES),
+ TLVP_LEN(tp, NM_ATT_IPACC_SUPP_FEATURES));
+ }
+ break;
+ default:
+ break;
}
/* Parse Attribute Response Info content for 3GPP TS 52.021 ยง9.4.28 Manufacturer Dependent State */
/* this attribute does not make sense on BTS level, only on TRX level */
if (trx && TLVP_PRES_LEN(tp, NM_ATT_MANUF_STATE, 1)) {
data = TLVP_VAL(tp, NM_ATT_MANUF_STATE);
- LOGPFOH(DNM, LOGL_NOTICE, foh, "%s Get Attributes Response: nominal power is %u\n", gsm_trx_name(trx), *data);
+ LOGPFOH(DNM, LOGL_NOTICE, foh, "Get Attributes Response: nominal power is %u\n", *data);
}
/* Parse Attribute Response Info content for 3GPP TS 52.021 ยง9.4.61 SW Configuration */
if (TLVP_PRESENT(tp, NM_ATT_SW_CONFIG)) {
+ struct abis_nm_sw_desc sw_descr[SW_DESCR_MAX];
data = TLVP_VAL(tp, NM_ATT_SW_CONFIG);
len = TLVP_LEN(tp, NM_ATT_SW_CONFIG);
/* after parsing manufacturer-specific attributes there's list of replies in form of sw-conf structure: */
@@ -554,33 +648,32 @@ static int parse_attr_resp_info_attr(struct gsm_bts *bts, const struct gsm_bts_t
if (rc > 0) {
for (i = 0; i < rc; i++) {
if (!handle_attr(bts, str2btsattr((const char *)sw_descr[i].file_id),
- sw_descr[i].file_version, sw_descr[i].file_version_len))
- LOGPFOH(DNM, LOGL_NOTICE, foh, "BTS%u: ARI reported sw[%d/%d]: %s "
- "is %s\n", bts->nr, i, rc, sw_descr[i].file_id,
- sw_descr[i].file_version);
+ sw_descr[i].file_version, sw_descr[i].file_version_len)) {
+ LOGPFOH(DNM, LOGL_NOTICE, foh, "ARI reported sw[%d/%d]: %s is %s\n",
+ i, rc, sw_descr[i].file_id, sw_descr[i].file_version);
+ }
}
} else {
- LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: failed to parse SW-Config part of "
- "Get Attribute Response Info: %s\n", bts->nr, strerror(-rc));
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Failed to parse SW-Config part of "
+ "Get Attribute Response Info: %s\n", strerror(-rc));
}
}
if (abis_nm_tlv_attr_primary_oml(tp, &ia, &port) == 0) {
LOGPFOH(DNM, LOGL_NOTICE, foh,
- "BTS%u Get Attributes Response: Primary OML IP is %s:%u\n",
- bts->nr, inet_ntoa(ia), port);
+ "Get Attributes Response: Primary OML IP is %s:%u\n",
+ inet_ntoa(ia), port);
}
if (abis_nm_tlv_attr_unit_id(tp, unit_id, sizeof(unit_id)) == 0) {
- LOGPFOH(DNM, LOGL_NOTICE, foh, "BTS%u Get Attributes Response: Unit ID is %s\n",
- bts->nr, unit_id);
+ LOGPFOH(DNM, LOGL_NOTICE, foh, "Get Attributes Response: Unit ID is %s\n", unit_id);
}
/* nanoBTS provides Get Attribute Response Info at random position and only the unreported part of it. */
if (TLVP_PRES_LEN(tp, NM_ATT_GET_ARI, 1)) {
data = TLVP_VAL(tp, NM_ATT_GET_ARI);
len = TLVP_LEN(tp, NM_ATT_GET_ARI);
- parse_attr_resp_info_unreported(bts->nr, data, len, NULL);
+ parse_attr_resp_info_unreported(foh, data, len, NULL);
}
return 0;
@@ -593,34 +686,45 @@ static int parse_attr_resp_info(struct gsm_bts *bts, const struct gsm_bts_trx *t
uint16_t data_len;
if (!TLVP_PRES_LEN(tp, NM_ATT_GET_ARI, 1)) {
- LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: Get Attr Response without Response Info?!\n",
- bts->nr);
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Get Attr Response without Response Info?!\n");
return -EINVAL;
}
- data = parse_attr_resp_info_unreported(bts->nr, TLVP_VAL(tp, NM_ATT_GET_ARI), TLVP_LEN(tp, NM_ATT_GET_ARI),
+ data = parse_attr_resp_info_unreported(foh, TLVP_VAL(tp, NM_ATT_GET_ARI),
+ TLVP_LEN(tp, NM_ATT_GET_ARI),
&data_len);
/* After parsing unreported attribute id list inside Response info,
there's a list of reported attribute ids and their values, in a TLV
list form. */
- abis_nm_tlv_parse(tp, bts, data, data_len);
+ if (abis_nm_tlv_parse(tp, bts, data, data_len) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
+
return parse_attr_resp_info_attr(bts, trx, foh, tp);
}
/* Handle 3GPP TS 52.021 ยง8.11.3 Get Attribute Response */
-static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *trx)
+static int abis_nm_rx_get_attr_resp(struct msgb *mb)
{
struct abis_om_hdr *oh = msgb_l2(mb);
struct abis_om_fom_hdr *foh = msgb_l3(mb);
struct e1inp_sign_link *sign_link = mb->dst;
- struct gsm_bts *bts = trx ? trx->bts : sign_link->trx->bts;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ const struct gsm_bts_trx *trx;
struct tlv_parsed tp;
int rc;
- DEBUGPFOH(DNM, foh, "Get Attributes Response for BTS%u\n", bts->nr);
+ trx = foh->obj_class == NM_OC_BASEB_TRANSC ?
+ gsm_bts_trx_num(bts, foh->obj_inst.trx_nr) : NULL;
- abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+ DEBUGPFOH(DNM, foh, "Get Attributes Response\n");
+
+ if (abis_nm_tlv_parse(&tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
/* nanoBTS doesn't send Get Attribute Response Info, uses its own format */
if (bts->type != GSM_BTS_TYPE_NANOBTS)
@@ -628,6 +732,12 @@ static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *t
else
rc = parse_attr_resp_info_attr(bts, trx, foh, &tp);
+ if (gsm_bts_check_cfg(bts) != 0) {
+ LOGP(DLINP, LOGL_ERROR, "(bts=%u) BTS config invalid, dropping BTS!\n", bts->nr);
+ ipaccess_drop_oml_deferred(bts);
+ return -EINVAL;
+ }
+
osmo_signal_dispatch(SS_NM, S_NM_GET_ATTR_REP, mb);
return rc;
@@ -642,28 +752,37 @@ static int abis_nm_rx_sw_act_req(struct msgb *mb)
struct tlv_parsed tp;
const uint8_t *sw_config;
int ret, sw_config_len, len;
- struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR];
+ struct abis_nm_sw_desc sw_descr[SW_DESCR_MAX];
DEBUGPFOH(DNM, foh, "Software Activate Request, ACKing and Activating\n");
+ if (oh->length < sizeof(*foh)) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Software Activate Request with length too small: %u\n", oh->length);
+ return -EINVAL;
+ }
+
ret = abis_nm_sw_act_req_ack(sign_link->trx->bts, foh->obj_class,
foh->obj_inst.bts_nr,
foh->obj_inst.trx_nr,
foh->obj_inst.ts_nr, 0,
- foh->data, oh->length-sizeof(*foh));
+ foh->data, oh->length - sizeof(*foh));
if (ret != 0) {
LOGPFOH(DNM, LOGL_ERROR, foh, "Sending SW ActReq ACK failed: %d\n", ret);
return ret;
}
- abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
+ if (abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length - sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
+
sw_config = TLVP_VAL(&tp, NM_ATT_SW_CONFIG);
sw_config_len = TLVP_LEN(&tp, NM_ATT_SW_CONFIG);
if (!TLVP_PRESENT(&tp, NM_ATT_SW_CONFIG)) {
LOGPFOH(DNM, LOGL_ERROR, foh, "SW config not found! Can't continue.\n");
return -EINVAL;
} else {
- DEBUGP(DNM, "Found SW config: %s\n", osmo_hexdump(sw_config, sw_config_len));
+ DEBUGPFOH(DNM, foh, "Found SW config: %s\n", osmo_hexdump(sw_config, sw_config_len));
}
/* Parse up to two sw descriptions from the data */
@@ -693,12 +812,19 @@ static int abis_nm_rx_chg_adm_state_ack(struct msgb *mb)
struct tlv_parsed tp;
uint8_t adm_state;
- abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
+ if (abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length - sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
+
if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE))
return -EINVAL;
adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+ DEBUGPFOH(DNM, foh, "Rx Change Administrative State ACK %s\n",
+ get_value_string(abis_nm_adm_state_names, adm_state));
+
return update_admstate(sign_link->trx->bts, foh->obj_class, &foh->obj_inst, adm_state);
}
@@ -709,8 +835,12 @@ static int abis_nm_rx_lmt_event(struct msgb *mb)
struct e1inp_sign_link *sign_link = mb->dst;
struct tlv_parsed tp;
+ if (abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length - sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
+
DEBUGPFOH(DNM, foh, "LMT Event ");
- abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) &&
TLVP_LEN(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) >= 1) {
uint8_t onoff = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_LOGON_SESSION);
@@ -740,16 +870,14 @@ struct gsm_bts_trx_ts *abis_nm_get_ts(const struct msgb *oml_msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
struct e1inp_sign_link *sign_link = oml_msg->dst;
- struct gsm_bts_trx *trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(sign_link->trx->bts, foh->obj_inst.trx_nr);
uint8_t ts_nr = foh->obj_inst.ts_nr;
if (!trx) {
- LOGP(DNM, LOGL_ERROR, "%s Channel OPSTART ACK for sign_link without trx\n",
- abis_nm_dump_foh(foh));
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Channel OPSTART ACK for sign_link without trx\n");
return NULL;
}
if (ts_nr >= ARRAY_SIZE(trx->ts)) {
- LOGP(DNM, LOGL_ERROR, "bts%u-trx%u %s Channel OPSTART ACK for non-existent TS\n",
- trx->bts->nr, trx->nr, abis_nm_dump_foh(foh));
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Channel OPSTART ACK for non-existent TS\n");
return NULL;
}
return &trx->ts[ts_nr];
@@ -758,12 +886,43 @@ struct gsm_bts_trx_ts *abis_nm_get_ts(const struct msgb *oml_msg)
static int abis_nm_rx_opstart_ack(struct msgb *mb)
{
struct abis_om_fom_hdr *foh = msgb_l3(mb);
- struct e1inp_sign_link *sign_link = mb->dst;
- DEBUGPFOH(DNM, foh, "bts=%u Opstart ACK\n", sign_link->trx->bts->nr);
+ DEBUGPFOH(DNM, foh, "Opstart ACK\n");
osmo_signal_dispatch(SS_NM, S_NM_OPSTART_ACK, mb);
return 0;
}
+static int abis_nm_rx_opstart_nack(struct msgb *mb)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Opstart NACK\n");
+ osmo_signal_dispatch(SS_NM, S_NM_OPSTART_NACK, mb);
+ return 0;
+}
+
+static int abis_nm_rx_set_radio_attr_ack(struct msgb *mb)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ DEBUGPFOH(DNM, foh, "Set Radio Carrier Attributes ACK\n");
+ osmo_signal_dispatch(SS_NM, S_NM_SET_RADIO_ATTR_ACK, mb);
+ return 0;
+}
+
+static int abis_nm_rx_set_channel_attr_ack(struct msgb *mb)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ DEBUGPFOH(DNM, foh, "Set Channel Attributes ACK\n");
+ osmo_signal_dispatch(SS_NM, S_NM_SET_CHAN_ATTR_ACK, mb);
+ return 0;
+}
+
+static int abis_nm_rx_set_bts_attr_ack(struct msgb *mb)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ DEBUGPFOH(DNM, foh, "Set BTS Attributes ACK\n");
+ osmo_signal_dispatch(SS_NM, S_NM_SET_BTS_ATTR_ACK, mb);
+ return 0;
+}
+
bool all_trx_rsl_connected_unlocked(const struct gsm_bts *bts)
{
const struct gsm_bts_trx *trx;
@@ -775,36 +934,25 @@ bool all_trx_rsl_connected_unlocked(const struct gsm_bts *bts)
if (bts->gprs.cell.mo.nm_state.administrative == NM_STATE_LOCKED)
return false;
- if (bts->gprs.nse.mo.nm_state.administrative == NM_STATE_LOCKED)
+ if (bts->site_mgr->gprs.nse.mo.nm_state.administrative == NM_STATE_LOCKED)
return false;
- if (bts->gprs.nsvc[0].mo.nm_state.administrative == NM_STATE_LOCKED &&
- bts->gprs.nsvc[1].mo.nm_state.administrative == NM_STATE_LOCKED)
+ if (bts->site_mgr->gprs.nsvc[0].mo.nm_state.administrative == NM_STATE_LOCKED &&
+ bts->site_mgr->gprs.nsvc[1].mo.nm_state.administrative == NM_STATE_LOCKED)
return false;
}
llist_for_each_entry(trx, &bts->trx_list, list) {
- if (!trx->rsl_link)
+ if (!trx->rsl_link_primary)
return false;
if (!trx_is_usable(trx))
return false;
-
- if (trx->mo.nm_state.administrative == NM_STATE_LOCKED)
- return false;
}
return true;
}
-char *get_model_oml_status(const struct gsm_bts *bts)
-{
- if (bts->model->oml_status)
- return bts->model->oml_status(bts);
-
- return "unknown";
-}
-
void abis_nm_queue_send_next(struct gsm_bts *bts)
{
int wait = 0;
@@ -844,14 +992,17 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
struct nm_nack_signal_data nack_data;
struct tlv_parsed tp;
- LOGPFOH(DNM, LOGL_NOTICE, foh, "%s NACK ", abis_nm_nack_name(mt));
+ if (abis_nm_tlv_parse(&tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
- abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+ LOGPFOH(DNM, LOGL_NOTICE, foh, "%s NACK ", abis_nm_nack_name(mt));
if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
- DEBUGPC(DNM, "CAUSE=%s\n",
+ LOGPC(DNM, LOGL_NOTICE, "CAUSE=%s\n",
abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
else
- DEBUGPC(DNM, "\n");
+ LOGPC(DNM, LOGL_NOTICE, "\n");
nack_data.msg = mb;
nack_data.mt = mt;
@@ -888,13 +1039,16 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
ret = abis_nm_rx_lmt_event(mb);
break;
case NM_MT_OPSTART_ACK:
- abis_nm_rx_opstart_ack(mb);
+ ret = abis_nm_rx_opstart_ack(mb);
+ break;
+ case NM_MT_OPSTART_NACK:
+ ret = abis_nm_rx_opstart_nack(mb);
break;
case NM_MT_SET_CHAN_ATTR_ACK:
- DEBUGPFOH(DNM, foh, "Set Channel Attributes ACK\n");
+ abis_nm_rx_set_channel_attr_ack(mb);
break;
case NM_MT_SET_RADIO_ATTR_ACK:
- DEBUGPFOH(DNM, foh, "Set Radio Carrier Attributes ACK\n");
+ abis_nm_rx_set_radio_attr_ack(mb);
break;
case NM_MT_CONN_MDROP_LINK_ACK:
DEBUGPFOH(DNM, foh, "CONN MDROP LINK ACK\n");
@@ -908,10 +1062,22 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_NACK, NULL);
break;
case NM_MT_SET_BTS_ATTR_ACK:
- DEBUGPFOH(DNM, foh, "Set BTS Attribute ACK\n");
+ abis_nm_rx_set_bts_attr_ack(mb);
break;
case NM_MT_GET_ATTR_RESP:
- ret = abis_nm_rx_get_attr_resp(mb, gsm_bts_trx_num(bts, (foh)->obj_inst.trx_nr));
+ ret = abis_nm_rx_get_attr_resp(mb);
+ break;
+ case NM_MT_ESTABLISH_TEI_ACK:
+ case NM_MT_CONN_TERR_SIGN_ACK:
+ case NM_MT_DISC_TERR_SIGN_ACK:
+ case NM_MT_CONN_TERR_TRAF_ACK:
+ case NM_MT_DISC_MDROP_LINK_ACK:
+ case NM_MT_STOP_EVENT_REP_ACK:
+ case NM_MT_REST_EVENT_REP_ACK:
+ case NM_MT_BS11_BEGIN_DB_TX_ACK:
+ case NM_MT_BS11_END_DB_TX_ACK:
+ case NM_MT_BS11_SET_ATTR_ACK:
+ DEBUGPFOH(DNM, foh, "%s\n", get_value_string(abis_nm_msgtype_names, mt));
break;
default:
LOGPFOH(DNM, LOGL_ERROR, foh, "Unhandled message %s\n",
@@ -928,17 +1094,17 @@ static int abis_nm_rcvmsg_manuf(struct msgb *mb)
{
int rc;
struct e1inp_sign_link *sign_link = mb->dst;
- int bts_type = sign_link->trx->bts->type;
+ struct gsm_bts *bts = sign_link->trx->bts;
- switch (bts_type) {
+ switch (bts->type) {
case GSM_BTS_TYPE_NANOBTS:
case GSM_BTS_TYPE_OSMOBTS:
rc = abis_nm_rx_ipacc(mb);
- abis_nm_queue_send_next(sign_link->trx->bts);
+ abis_nm_queue_send_next(bts);
break;
default:
- LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this "
- "BTS type (%u)\n", bts_type);
+ LOGPMO(&bts->mo, DNM, LOGL_ERROR, "don't know how to parse OML for this "
+ "BTS type (%s)\n", btstype2str(bts->type));
rc = 0;
break;
}
@@ -1083,7 +1249,7 @@ static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg)
msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
sw->file_version);
} else {
- LOGP(DNM, LOGL_ERROR, "Please implement this for the BTS.\n");
+ LOGPMO(&sw->bts->mo, DNM, LOGL_ERROR, "Please implement this for the BTS.\n");
}
}
@@ -1181,7 +1347,7 @@ static int sw_load_segment(struct abis_nm_sw *sw)
break;
}
default:
- LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n");
+ LOGPMO(&sw->bts->mo, DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n");
/* FIXME: Other BTS types */
return -1;
}
@@ -1229,56 +1395,51 @@ static int sw_activate(struct abis_nm_sw *sw)
return abis_nm_sendmsg(sw->bts, msg);
}
-struct sdp_firmware {
- char magic[4];
- char more_magic[4];
- unsigned int header_length;
- unsigned int file_length;
-} __attribute__ ((packed));
-
static int parse_sdp_header(struct abis_nm_sw *sw)
{
+ const struct gsm_abis_mo *mo = &sw->bts->mo;
struct sdp_firmware firmware_header;
int rc;
struct stat stat;
rc = read(sw->fd, &firmware_header, sizeof(firmware_header));
if (rc != sizeof(firmware_header)) {
- LOGP(DNM, LOGL_ERROR, "Could not read SDP file header.\n");
+ LOGPMO(mo, DNM, LOGL_ERROR, "Could not read SDP file header.\n");
return -1;
}
if (strncmp(firmware_header.magic, " SDP", 4) != 0) {
- LOGP(DNM, LOGL_ERROR, "The magic number1 is wrong.\n");
+ LOGPMO(mo, DNM, LOGL_ERROR, "The magic number is wrong.\n");
return -1;
}
if (firmware_header.more_magic[0] != 0x10 ||
- firmware_header.more_magic[1] != 0x02 ||
- firmware_header.more_magic[2] != 0x00 ||
- firmware_header.more_magic[3] != 0x00) {
- LOGP(DNM, LOGL_ERROR, "The more magic number is wrong.\n");
+ firmware_header.more_magic[1] != 0x02) {
+ LOGPMO(mo, DNM, LOGL_ERROR, "The more magic number is wrong.\n");
return -1;
}
+ if (firmware_header.more_more_magic != 0x0000) {
+ LOGPMO(mo, DNM, LOGL_ERROR, "The more more magic number is wrong.\n");
+ return -1;
+ }
if (fstat(sw->fd, &stat) == -1) {
- LOGP(DNM, LOGL_ERROR, "Could not stat the file.\n");
+ LOGPMO(mo, DNM, LOGL_ERROR, "Could not stat the file.\n");
return -1;
}
if (ntohl(firmware_header.file_length) != stat.st_size) {
- LOGP(DNM, LOGL_ERROR, "The filesizes do not match.\n");
+ LOGPMO(mo, DNM, LOGL_ERROR, "The filesizes do not match.\n");
return -1;
}
/* go back to the start as we checked the whole filesize.. */
lseek(sw->fd, 0l, SEEK_SET);
- LOGP(DNM, LOGL_NOTICE, "The ipaccess SDP header is not fully understood."
- " There might be checksums in the file that are not"
- " verified and incomplete firmware might be flashed."
- " There is absolutely no WARRANTY that flashing will"
- " work.\n");
+ LOGPMO(mo, DNM, LOGL_NOTICE, "The ipaccess SDP header is not fully understood. "
+ "There might be checksums in the file that are not verified and incomplete "
+ "firmware might be flashed. There is absolutely no WARRANTY that "
+ "flashing will work.\n");
return 0;
}
@@ -1439,7 +1600,7 @@ static int abis_nm_rcvmsg_sw(struct msgb *mb)
switch (foh->msg_type) {
case NM_MT_LOAD_END_ACK:
sw_close_file(sw);
- DEBUGPFOH(DNM, foh, "Software Load End (BTS %u)\n", sw->bts->nr);
+ DEBUGPFOH(DNM, foh, "Software Load End\n");
sw->state = SW_STATE_NONE;
if (sw->cbfn)
sw->cbfn(GSM_HOOK_NM_SWLOAD,
@@ -1520,7 +1681,7 @@ int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname,
struct abis_nm_sw *sw = &g_sw;
int rc;
- DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n", bts->nr, fname);
+ LOGPMO(&bts->mo, DNM, LOGL_DEBUG, "Software Load (file \"%s\")\n", fname);
if (sw->state != SW_STATE_NONE)
return -EBUSY;
@@ -1537,13 +1698,13 @@ int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname,
break;
case GSM_BTS_TYPE_NANOBTS:
sw->obj_class = NM_OC_BASEB_TRANSC;
- sw->obj_instance[0] = sw->bts->nr;
+ sw->obj_instance[0] = sw->bts->bts_nr;
sw->obj_instance[1] = sw->trx_nr;
sw->obj_instance[2] = 0xff;
break;
case GSM_BTS_TYPE_UNKNOWN:
default:
- LOGPC(DNM, LOGL_ERROR, "Software Load not properly implemented.\n");
+ LOGPMO(&bts->mo, DNM, LOGL_ERROR, "Software Load not properly implemented.\n");
return -1;
break;
}
@@ -1588,7 +1749,7 @@ int abis_nm_software_activate(struct gsm_bts *bts, const char *fname,
struct abis_nm_sw *sw = &g_sw;
int rc;
- DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n", bts->nr, fname);
+ LOGPMO(&bts->mo, DNM, LOGL_DEBUG, "Activating Software (file \"%s\")\n", fname);
if (sw->state != SW_STATE_NONE)
return -EBUSY;
@@ -1685,9 +1846,8 @@ int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts,
ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
- DEBUGP(DNM, "CONNECT TERR TRAF Um=%s E1=(%u,%u,%u)\n",
- gsm_ts_name(ts),
- e1_port, e1_timeslot, e1_subslot);
+ LOGPMO(&ts->mo, DNM, LOGL_DEBUG, "CONNECT TERR TRAF E1=(%u,%u,%u)\n",
+ e1_port, e1_timeslot, e1_subslot);
return abis_nm_sendmsg(bts, msg);
}
@@ -1704,23 +1864,24 @@ int abis_nm_disc_terr_traf(struct abis_nm_h *h, struct abis_om_obj_inst *inst,
int abis_nm_get_attr(struct gsm_bts *bts, uint8_t obj_class, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
const uint8_t *attr, uint8_t attr_len)
{
+ const struct abis_om_fom_hdr *foh;
struct abis_om_hdr *oh;
struct msgb *msg;
if (bts->type != GSM_BTS_TYPE_OSMOBTS && bts->type != GSM_BTS_TYPE_NANOBTS) {
- LOGPC(DNM, LOGL_NOTICE, "Getting attributes from BTS%d type %s is not supported.\n",
- bts->nr, btstype2str(bts->type));
+ LOGPMO(&bts->mo, DNM, LOGL_NOTICE, "Getting attributes from BTS "
+ "type %s is not supported.\n", btstype2str(bts->type));
return -EINVAL;
}
- DEBUGP(DNM, "Get Attr (bts=%d)\n", bts->nr);
-
msg = nm_msgb_alloc();
oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
- fill_om_fom_hdr(oh, attr_len, NM_MT_GET_ATTR, obj_class,
- bts_nr, trx_nr, ts_nr);
+ foh = fill_om_fom_hdr(oh, TL16V_GROSS_LEN(attr_len), NM_MT_GET_ATTR,
+ obj_class, bts_nr, trx_nr, ts_nr);
msgb_tl16v_put(msg, NM_ATT_LIST_REQ_ATTR, attr_len, attr);
+ DEBUGPFOH(DNM, foh, "Tx Get Attributes (Request)\n");
+
return abis_nm_sendmsg(bts, msg);
}
@@ -1731,7 +1892,7 @@ int abis_nm_set_bts_attr(struct gsm_bts *bts, uint8_t *attr, int attr_len)
struct msgb *msg = nm_msgb_alloc();
uint8_t *cur;
- DEBUGP(DNM, "Set BTS Attr (bts=%d)\n", bts->nr);
+ LOGPMO(&bts->mo, DNM, LOGL_DEBUG, "Set BTS Attr\n");
oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
fill_om_fom_hdr(oh, attr_len, NM_MT_SET_BTS_ATTR, NM_OC_BTS, bts->bts_nr, 0xff, 0xff);
@@ -1748,7 +1909,7 @@ int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, uint8_t *attr, int attr_len)
struct msgb *msg = nm_msgb_alloc();
uint8_t *cur;
- DEBUGP(DNM, "Set TRX Attr (bts=%d,trx=%d)\n", trx->bts->nr, trx->nr);
+ LOG_TRX(trx, DNM, LOGL_DEBUG, "Set TRX Attr\n");
oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
fill_om_fom_hdr(oh, attr_len, NM_MT_SET_RADIO_ATTR, NM_OC_RADIO_CARRIER,
@@ -1779,7 +1940,7 @@ static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb,
switch (chan_comb) {
case NM_CHANC_TCHHalf:
case NM_CHANC_TCHHalf2:
- case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH:
+ case NM_CHANC_OSMO_DYN:
/* not supported */
*reason = "TCH/H is not supported.";
return -EINVAL;
@@ -1797,15 +1958,15 @@ static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb,
/* not allowed for TS0 of BCCH-TRX */
if (ts->trx == ts->trx->bts->c0 &&
ts->nr == 0) {
- *reason = "SDCCH/8 must be on TS0.";
+ *reason = "SDCCH/8 must not be on C0/TS0.";
return -EINVAL;
}
/* not on the same TRX that has a BCCH+SDCCH4
* combination */
if (ts->trx != ts->trx->bts->c0 &&
- (ts->trx->ts[0].nm_chan_comb == 5 ||
- ts->trx->ts[0].nm_chan_comb == 8)) {
+ (ts->trx->ts[0].nm_chan_comb == NM_CHANC_BCCHComb ||
+ ts->trx->ts[0].nm_chan_comb == NM_CHANC_SDCCH_CBCH)) {
*reason = "SDCCH/8 and BCCH must be on the same TRX.";
return -EINVAL;
}
@@ -1829,7 +1990,7 @@ static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb,
return -EINVAL;
}
break;
- case 8: /* this is not like 08.58, but in fact
+ case NM_CHANC_SDCCH_CBCH: /* this is not like 08.58, but in fact
* FCCH+SCH+BCCH+CCCH+SDCCH/4+SACCH/C4+CBCH */
/* FIXME: only one CBCH allowed per cell */
break;
@@ -1877,7 +2038,7 @@ static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb,
case NM_CHANC_TCHHalf:
case NM_CHANC_IPAC_TCHFull_TCHHalf:
case NM_CHANC_IPAC_TCHFull_PDCH:
- case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH:
+ case NM_CHANC_OSMO_DYN:
return 0;
default:
*reason = "TS1 must carry a CBCH, SDCCH or TCH.";
@@ -1909,7 +2070,7 @@ static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb,
return 0;
case NM_CHANC_IPAC_PDCH:
case NM_CHANC_IPAC_TCHFull_PDCH:
- case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH:
+ case NM_CHANC_OSMO_DYN:
if (ts->trx->nr == 0)
return 0;
else {
@@ -1939,17 +2100,14 @@ int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb)
struct abis_om_fom_hdr *foh;
uint8_t zero = 0x00;
struct msgb *msg = nm_msgb_alloc();
- uint8_t len = 2 + 2;
const char *reason = NULL;
- if (bts->type == GSM_BTS_TYPE_BS11)
- len += 4 + 2 + 2 + 3;
-
+ /* NOTE: message length will be set later, see down below */
oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
- foh = fill_om_fom_hdr(oh, len, NM_MT_SET_CHAN_ATTR, NM_OC_CHANNEL, bts->bts_nr,
+ foh = fill_om_fom_hdr(oh, 0, NM_MT_SET_CHAN_ATTR, NM_OC_CHANNEL, bts->bts_nr,
ts->trx->nr, ts->nr);
- DEBUGPFOH(DNM, foh, "Set Chan Attr %s\n", gsm_ts_name(ts));
+ DEBUGPFOH(DNM, foh, "Set Chan Attr\n");
if (verify_chan_comb(ts, chan_comb, &reason) < 0) {
LOGPFOH(DNM, LOGL_ERROR, foh, "Invalid Channel Combination %d on %s. Reason: %s\n",
chan_comb, gsm_ts_name(ts), reason);
@@ -1960,42 +2118,64 @@ int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb)
msgb_tv_put(msg, NM_ATT_CHAN_COMB, chan_comb);
if (ts->hopping.enabled) {
- unsigned int i;
- uint8_t *len;
+ unsigned int i, n;
+ uint16_t *u16 = NULL;
+ uint8_t *u8 = NULL;
msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn);
msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio);
- /* build the ARFCN list */
msgb_put_u8(msg, NM_ATT_ARFCN_LIST);
- len = msgb_put(msg, 1);
- *len = 0;
- for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+
+ /* 3GPP TS 12.21 defines this IE as TL16V */
+ if (bts->type != GSM_BTS_TYPE_BS11)
+ u16 = (uint16_t *) msgb_put(msg, 2);
+ else /* ... but BS-11 wants TLV instead */
+ u8 = (uint8_t *) msgb_put(msg, 1);
+
+ /* build the ARFCN list from pre-computed bitmap */
+ for (i = 0, n = 0; i < ts->hopping.arfcns.data_len*8; i++) {
if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) {
msgb_put_u16(msg, i);
- /* At least BS-11 wants a TLV16 here */
- if (bts->type == GSM_BTS_TYPE_BS11)
- *len += 1;
- else
- *len += sizeof(uint16_t);
+ n += 1;
}
}
+
+ /* BS-11 cannot handle more than 255 ARFCNs, because L is 8 bit.
+ * This is unlikely to happen, but better check than sorry... */
+ if (bts->type == GSM_BTS_TYPE_BS11 && n > 0xff) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Cannot handle %u (more than 255) "
+ "hopping channels\n", n);
+ msgb_free(msg);
+ return -EINVAL;
+ }
+
+ /* 3GPP TS 12.21 defines L as length of the V part (in octets) */
+ if (bts->type != GSM_BTS_TYPE_BS11)
+ *u16 = htons(n * sizeof(*u16));
+ else /* ... BS-11 wants the number of channels instead */
+ *u8 = n;
}
msgb_tv_put(msg, NM_ATT_TSC, gsm_ts_tsc(ts)); /* training sequence */
if (bts->type == GSM_BTS_TYPE_BS11)
msgb_tlv_put(msg, 0x59, 1, &zero);
+ msg->l2h = (uint8_t *) oh;
+ msg->l3h = (uint8_t *) foh;
+ oh->length = msgb_l3len(msg);
+
DEBUGPFOH(DNM, foh, "%s(): sending %s\n", __func__, msgb_hexdump(msg));
return abis_nm_sendmsg(bts, msg);
}
int abis_nm_sw_act_req_ack(struct gsm_bts *bts, uint8_t obj_class, uint8_t i1,
- uint8_t i2, uint8_t i3, int nack, uint8_t *attr, int att_len)
+ uint8_t i2, uint8_t i3, int nack,
+ const uint8_t *attr, unsigned int attr_len)
{
struct abis_om_hdr *oh;
struct msgb *msg = nm_msgb_alloc();
uint8_t msgtype = NM_MT_SW_ACT_REQ_ACK;
- uint8_t len = att_len;
+ uint8_t len = attr_len;
if (nack) {
len += 2;
@@ -2003,12 +2183,10 @@ int abis_nm_sw_act_req_ack(struct gsm_bts *bts, uint8_t obj_class, uint8_t i1,
}
oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
- fill_om_fom_hdr(oh, att_len, msgtype, obj_class, i1, i2, i3);
+ fill_om_fom_hdr(oh, len, msgtype, obj_class, i1, i2, i3);
- if (attr) {
- uint8_t *ptr = msgb_put(msg, att_len);
- memcpy(ptr, attr, att_len);
- }
+ if (attr != NULL && attr_len > 0)
+ memcpy(msgb_put(msg, attr_len), attr, attr_len);
if (nack)
msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP);
@@ -2105,10 +2283,13 @@ int abis_nm_perform_test(struct gsm_bts *bts, uint8_t obj_class,
{
struct abis_om_hdr *oh;
- DEBUGP(DNM, "PEFORM TEST %s\n", abis_nm_test_name(test_nr));
+ DEBUGP(DNM, "PERFORM TEST %s\n", abis_nm_test_name(test_nr));
- if (!msg)
+ if (!msg) {
msg = nm_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+ }
msgb_tv_push(msg, NM_ATT_AUTON_REPORT, auton_report);
msgb_tv_push(msg, NM_ATT_TEST_NO, test_nr);
@@ -2479,8 +2660,8 @@ struct file_list_entry *fl_dequeue(struct llist_head *queue)
static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw)
{
+ struct file_list_entry *fle;
char linebuf[255];
- struct llist_head *lh, *lh2;
FILE *swl;
int rc = 0;
@@ -2489,10 +2670,8 @@ static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw)
return -ENODEV;
/* zero the stale file list, if any */
- llist_for_each_safe(lh, lh2, &bs11_sw->file_list) {
- llist_del(lh);
- talloc_free(lh);
- }
+ while ((fle = fl_dequeue(&bs11_sw->file_list)))
+ talloc_free(fle);
while (fgets(linebuf, sizeof(linebuf), swl)) {
char file_id[12+1];
@@ -2690,10 +2869,6 @@ int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, uint8_t bport, enum abi
return abis_nm_sendmsg(bts, msg);
}
-/* ip.access nanoBTS specific commands */
-static const char ipaccess_magic[] = "com.ipaccess";
-
-
static int abis_nm_rx_ipacc(struct msgb *msg)
{
struct in_addr addr;
@@ -2703,21 +2878,31 @@ static int abis_nm_rx_ipacc(struct msgb *msg)
struct tlv_parsed tp;
struct ipacc_ack_signal_data signal;
struct e1inp_sign_link *sign_link = msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct gsm_bts_trx *trx;
foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen);
- if (strncmp((char *)&oh->data[1], ipaccess_magic, idstrlen)) {
+ if (strncmp((char *)&oh->data[1], abis_nm_ipa_magic, idstrlen)) {
LOGPFOH(DNM, LOGL_ERROR, foh, "id string is not com.ipaccess !?!\n");
return -EINVAL;
}
- abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
+ if (abis_nm_tlv_parse(&tp, bts, foh->data, oh->length - sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
+
+ /* The message might be received over the main OML link, so we cannot
+ * just use sign_link->trx. Resolve it by number from the FOM header. */
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
- DEBUGPFOH(DNM, foh, "IPACCESS(0x%02x): ", foh->msg_type);
+ DEBUGPFOH(DNM, foh, "Rx IPACCESS(0x%02x): %s\n", foh->msg_type,
+ osmo_hexdump(foh->data, oh->length - sizeof(*foh)));
switch (foh->msg_type) {
case NM_MT_IPACC_RSL_CONNECT_ACK:
- DEBUGPC(DNM, "RSL CONNECT ACK ");
+ DEBUGPFOH(DNM, foh, "RSL CONNECT ACK ");
if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP)) {
memcpy(&addr,
TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP), sizeof(addr));
@@ -2730,7 +2915,9 @@ static int abis_nm_rx_ipacc(struct msgb *msg)
DEBUGPC(DNM, "STREAM=0x%02x ",
*TLVP_VAL(&tp, NM_ATT_IPACC_STREAM_ID));
DEBUGPC(DNM, "\n");
- osmo_timer_del(&sign_link->trx->rsl_connect_timeout);
+ if (!trx)
+ goto obj_inst_error;
+ osmo_timer_del(&trx->rsl_connect_timeout);
break;
case NM_MT_IPACC_RSL_CONNECT_NACK:
LOGPFOH(DNM, LOGL_ERROR, foh, "RSL CONNECT NACK ");
@@ -2739,7 +2926,9 @@ static int abis_nm_rx_ipacc(struct msgb *msg)
abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
else
LOGPC(DNM, LOGL_ERROR, "\n");
- osmo_timer_del(&sign_link->trx->rsl_connect_timeout);
+ if (!trx)
+ goto obj_inst_error;
+ osmo_timer_del(&trx->rsl_connect_timeout);
break;
case NM_MT_IPACC_SET_NVATTR_ACK:
DEBUGPFOH(DNM, foh, "SET NVATTR ACK\n");
@@ -2777,7 +2966,7 @@ static int abis_nm_rx_ipacc(struct msgb *msg)
LOGPC(DNM, LOGL_ERROR, "\n");
break;
default:
- DEBUGPC(DNM, "unknown\n");
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Unknown message\n");
break;
}
@@ -2786,13 +2975,15 @@ static int abis_nm_rx_ipacc(struct msgb *msg)
case NM_MT_IPACC_RSL_CONNECT_NACK:
case NM_MT_IPACC_SET_NVATTR_NACK:
case NM_MT_IPACC_GET_NVATTR_NACK:
- signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr);
- signal.msg_type = foh->msg_type;
+ signal.bts = bts;
+ signal.foh = foh;
osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal);
break;
+ case NM_MT_IPACC_RSL_CONNECT_ACK:
case NM_MT_IPACC_SET_NVATTR_ACK:
- signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr);
- signal.msg_type = foh->msg_type;
+ case NM_MT_IPACC_SET_ATTR_ACK:
+ signal.bts = bts;
+ signal.foh = foh;
osmo_signal_dispatch(SS_NM, S_NM_IPACC_ACK, &signal);
break;
default:
@@ -2800,6 +2991,10 @@ static int abis_nm_rx_ipacc(struct msgb *msg)
}
return 0;
+
+obj_inst_error:
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Unknown object instance\n");
+ return -EINVAL;
}
/* send an ip-access manufacturer specific message */
@@ -2819,9 +3014,8 @@ int abis_nm_ipaccess_msg(struct gsm_bts *bts, uint8_t msg_type,
oh->mdisc = ABIS_OM_MDISC_MANUF;
/* add the ip.access magic */
- data = msgb_put(msg, sizeof(ipaccess_magic)+1);
- *data++ = sizeof(ipaccess_magic);
- memcpy(data, ipaccess_magic, sizeof(ipaccess_magic));
+ msgb_lv_put(msg, sizeof(abis_nm_ipa_magic),
+ (const uint8_t *) abis_nm_ipa_magic);
/* fill the 12.21 FOM header */
foh = (struct abis_om_fom_hdr *) msgb_put(msg, sizeof(*foh));
@@ -2853,43 +3047,51 @@ static void rsl_connect_timeout(void *data)
struct gsm_bts_trx *trx = data;
struct ipacc_ack_signal_data signal;
- LOGP(DRSL, LOGL_NOTICE, "(bts=%d,trx=%d) RSL connection request timed out\n", trx->bts->nr, trx->nr);
-
- /* Fake an RSL CONECT NACK message from the BTS. */
- signal.trx = trx;
- signal.msg_type = NM_MT_IPACC_RSL_CONNECT_NACK;
+ LOG_TRX(trx, DRSL, LOGL_NOTICE, "RSL connection request timed out\n");
+
+ /* Fake an RSL CONNECT NACK message from the BTS. */
+ struct abis_om_fom_hdr foh = {
+ .msg_type = NM_MT_IPACC_RSL_CONNECT_NACK,
+ .obj_class = NM_OC_BASEB_TRANSC,
+ .obj_inst = {
+ .bts_nr = trx->bts->bts_nr,
+ .trx_nr = trx->nr,
+ .ts_nr = 0xff,
+ },
+ };
+ signal.foh = &foh;
+ signal.bts = trx->bts;
osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal);
}
int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx,
uint32_t ip, uint16_t port, uint8_t stream)
{
+ struct msgb *attr;
struct in_addr ia;
- uint8_t attr[] = { NM_ATT_IPACC_STREAM_ID, 0,
- NM_ATT_IPACC_DST_IP_PORT, 0, 0,
- NM_ATT_IPACC_DST_IP, 0, 0, 0, 0 };
-
- int attr_len = sizeof(attr);
int error;
osmo_timer_setup(&trx->rsl_connect_timeout, rsl_connect_timeout, trx);
- ia.s_addr = htonl(ip);
- attr[1] = stream;
- attr[3] = port >> 8;
- attr[4] = port & 0xff;
- memcpy(attr + 6, &ia.s_addr, sizeof(uint32_t));
+ attr = msgb_alloc(32, "RSL-connect-attr");
+ msgb_tv_put(attr, NM_ATT_IPACC_STREAM_ID, stream);
+ msgb_tv16_put(attr, NM_ATT_IPACC_DST_IP_PORT, port);
/* if ip == 0, we use the default IP */
- if (ip == 0)
- attr_len -= 5;
+ if (ip != 0) {
+ ia.s_addr = htonl(ip);
+ msgb_tv_fixed_put(attr, NM_ATT_IPACC_DST_IP, 4, (void*)&ia.s_addr);
+ } else {
+ ia = (struct in_addr){};
+ }
- LOGP(DNM, LOGL_INFO, "(bts=%d,trx=%d) IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
- trx->bts->nr, trx->nr, inet_ntoa(ia), port, stream);
+ LOG_TRX(trx, DNM, LOGL_INFO, "IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+ inet_ntoa(ia), port, stream);
error = abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_RSL_CONNECT,
NM_OC_BASEB_TRANSC, trx->bts->bts_nr,
- trx->nr, 0xff, attr, attr_len);
+ trx->nr, 0xff, attr->data, attr->len);
+ msgb_free(attr);
if (error == 0)
osmo_timer_schedule(&trx->rsl_connect_timeout, 60, 0);
@@ -2904,7 +3106,7 @@ int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx)
oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
fill_om_fom_hdr(oh, 0, NM_MT_IPACC_RESTART, NM_OC_BASEB_TRANSC,
- trx->bts->nr, trx->nr, 0xff);
+ trx->bts->bts_nr, trx->nr, 0xff);
return abis_nm_sendmsg_direct(trx->bts, msg);
}
@@ -2928,27 +3130,6 @@ void abis_nm_ipaccess_cgi(uint8_t *buf, struct gsm_bts *bts)
memcpy(&_buf->rac, &ci, sizeof(ci));
}
-void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason)
-{
- uint8_t new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
-
-
- if (!trx->bts || !trx->bts->oml_link) {
- /* Set initial state which will be sent when BTS connects. */
- trx->mo.nm_state.administrative = new_state;
- return;
- }
-
- LOGP(DNM, LOGL_NOTICE, "(bts=%d,trx=%d) Requesting administrative state change %s -> %s [%s]\n",
- trx->bts->nr, trx->nr,
- get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative),
- get_value_string(abis_nm_adm_state_names, new_state), reason);
-
- abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER,
- trx->bts->bts_nr, trx->nr, 0xff,
- new_state);
-}
-
static const struct value_string ipacc_testres_names[] = {
{ NM_IPACC_TESTRES_SUCCESS, "SUCCESS" },
{ NM_IPACC_TESTRES_TIMEOUT, "TIMEOUT" },
diff --git a/src/osmo-bsc/abis_nm_vty.c b/src/osmo-bsc/abis_nm_vty.c
index 3019eb828..bbd2d157f 100644
--- a/src/osmo-bsc/abis_nm_vty.c
+++ b/src/osmo-bsc/abis_nm_vty.c
@@ -1,4 +1,4 @@
-/* VTY interface for A-bis OML (Netowrk Management) */
+/* VTY interface for A-bis OML (Network Management) */
/* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org>
*
@@ -54,11 +54,6 @@ struct oml_node_state {
uint8_t obj_inst[3];
};
-static int dummy_config_write(struct vty *v)
-{
- return CMD_SUCCESS;
-}
-
/* FIXME: auto-generate those strings from the value_string lists */
#define NM_OBJCLASS_VTY "(site-manager|bts|radio-carrier|baseband-transceiver|channel|adjc|handover|power-contorl|btse|rack|test|envabtse|bport|gprs-nse|gprs-cell|gprs-nsvc|siemenshw)"
#define NM_OBJCLASS_VTY_HELP "Site Manager Object\n" \
@@ -165,7 +160,7 @@ DEFUN(oml_chg_adm_state, oml_chg_adm_state_cmd,
}
DEFUN(oml_opstart, oml_opstart_cmd,
- "opstart", "Send an OPSTART message to the object")
+ "opstart", "Send an OPSTART message to the object\n")
{
struct oml_node_state *oms = vty->index;
diff --git a/src/osmo-bsc/abis_om2000.c b/src/osmo-bsc/abis_om2000.c
index 9715dfc17..69a86b54d 100644
--- a/src/osmo-bsc/abis_om2000.c
+++ b/src/osmo-bsc/abis_om2000.c
@@ -30,6 +30,7 @@
#include <arpa/inet.h>
+#include <osmocom/core/byteswap.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/core/talloc.h>
@@ -42,10 +43,23 @@
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/abis_om2000.h>
#include <osmocom/bsc/signal.h>
-#include <osmocom/bsc/gsm_timers.h>
#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/abis/e1_input.h>
+static inline void abis_om2000_fsm_transc_becomes_enabled(struct gsm_bts_trx *trx)
+{
+ nm_obj_fsm_becomes_enabled_disabled(trx->bts, trx, NM_OC_RADIO_CARRIER, true);
+ nm_obj_fsm_becomes_enabled_disabled(trx->bts, &trx->bb_transc, NM_OC_BASEB_TRANSC, true);
+}
+
+static inline void abis_om2000_fsm_transc_becomes_disabled(struct gsm_bts_trx *trx)
+{
+ nm_obj_fsm_becomes_enabled_disabled(trx->bts, trx, NM_OC_RADIO_CARRIER, false);
+ nm_obj_fsm_becomes_enabled_disabled(trx->bts, &trx->bb_transc, NM_OC_BASEB_TRANSC, false);
+}
+
/* FIXME: move to libosmocore */
struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm,
struct osmo_fsm_inst *parent,
@@ -54,20 +68,11 @@ struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm,
{
struct osmo_fsm_inst *fi;
- fi = osmo_fsm_inst_alloc(fsm, parent, NULL, parent->log_level,
- id ? id : parent->id);
- if (!fi) {
- /* indicate immediate termination to caller */
- osmo_fsm_inst_dispatch(parent, parent_term_event, NULL);
+ fi = osmo_fsm_inst_alloc_child(fsm, parent, parent_term_event);
+ if (!fi)
return NULL;
- }
-
- LOGPFSM(fi, "is child of %s\n", osmo_fsm_inst_name(parent));
-
- fi->proc.parent = parent;
- fi->proc.parent_term_event = parent_term_event;
- llist_add(&fi->proc.child, &parent->proc.children);
-
+ if (id)
+ osmo_fsm_inst_update_id_f_sanitize(fi, '-', id);
return fi;
}
@@ -76,6 +81,7 @@ struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm,
#define OM_HEADROOM_SIZE 128
#define OM2K_TIMEOUT 10
+#define TRX_LAPD_TIMEOUT 5
#define TRX_FSM_TIMEOUT 60
#define BTS_FSM_TIMEOUT 60
@@ -193,6 +199,10 @@ enum abis_om2k_msgtype {
OM2K_MSGT_TX_CONF_RES_NACK = 0x00b5,
OM2K_MSGT_TX_CONF_RES = 0x00b6,
+ OM2K_MSGT_CAPA_HW_INFOS_REP_ACK = 0x00e4,
+ OM2K_MSGT_CAPA_HW_INFOS_REP_NACK = 0x00e5,
+ OM2K_MSGT_CAPA_HW_INFOS_REP = 0x00e6,
+
OM2K_MSGT_CAPA_REQ = 0x00e8,
OM2K_MSGT_CAPA_REQ_ACK = 0x00ea,
OM2K_MSGT_CAPA_REQ_REJ = 0x00eb,
@@ -203,6 +213,30 @@ enum abis_om2k_msgtype {
OM2K_MSGT_NEGOT_REQ_ACK = 0x0104,
OM2K_MSGT_NEGOT_REQ_NACK = 0x0105,
OM2K_MSGT_NEGOT_REQ = 0x0106,
+
+ OM2K_MSGT_BTS_INITIATED_REQ_ACK = 0x0108,
+ OM2K_MSGT_BTS_INITIATED_REQ_NACK = 0x0109,
+ OM2K_MSGT_BTS_INITIATED_REQ = 0x010a,
+
+ OM2K_MSGT_RADIO_CHAN_REL_CMD = 0x010c,
+ OM2K_MSGT_RADIO_CHAN_REL_COMPL = 0x010e,
+ OM2K_MSGT_RADIO_CHAN_REL_REJ = 0x010f,
+
+ OM2K_MSGT_FEATURE_CTRL_CMD = 0x0118,
+ OM2K_MSGT_FEATURE_CTRL_COMPL = 0x011a,
+ OM2K_MSGT_FEATURE_CTRL_REJ = 0x011b,
+
+ OM2K_MSGT_MCTR_CONF_REQ = 0x012c,
+ OM2K_MSGT_MCTR_CONF_REQ_ACK = 0x012e,
+ OM2K_MSGT_MCTR_CONF_REQ_REJ = 0x012f,
+
+ OM2K_MSGT_MCTR_CONF_RES_ACK = 0x0130,
+ OM2K_MSGT_MCTR_CONF_RES_NACK = 0x0131,
+ OM2K_MSGT_MCTR_CONF_RES = 0x0132,
+
+ OM2K_MSGT_MCTR_STATS_REP_ACK = 0x0134,
+ OM2K_MSGT_MCTR_STATS_REP_NACK = 0x0135,
+ OM2K_MSGT_MCTR_STATS_REP = 0x0136,
};
enum abis_om2k_dei {
@@ -273,6 +307,21 @@ enum abis_om2k_dei {
OM2K_DEI_FS_OFFSET = 0x98,
OM2K_DEI_EXT_COND_MAP_2_EXT = 0x9c,
OM2K_DEI_TSS_MO_STATE = 0x9d,
+ OM2K_DEI_CONFIG_TYPE = 0x9e,
+ OM2K_DEI_JITTER_SIZE = 0x9f,
+ OM2K_DEI_PACKING_ALGO = 0xa0,
+ OM2K_DEI_TRXC_LIST = 0xa8,
+ OM2K_DEI_MAX_ALLOWED_POWER = 0xa9,
+ OM2K_DEI_MAX_ALLOWED_NUM_TRXCS = 0xaa,
+ OM2K_DEI_MCTR_FEAT_STATUS_BMAP = 0xab,
+ OM2K_DEI_SEEN_UNKNOWN_D2 = 0xd2,
+};
+
+enum abis_om2k_mostate {
+ OM2K_MOSTATE_RESET = 0x00,
+ OM2K_MOSTATE_STARTED = 0x01,
+ OM2K_MOSTATE_ENABLED = 0x02,
+ OM2K_MOSTATE_DISABLED = 0x03,
};
const struct tlv_definition om2k_att_tlvdef = {
@@ -344,6 +393,7 @@ const struct tlv_definition om2k_att_tlvdef = {
[OM2K_DEI_FS_OFFSET] = { TLV_TYPE_FIXED, 5 },
[OM2K_DEI_EXT_COND_MAP_2_EXT] = { TLV_TYPE_FIXED, 4 },
[OM2K_DEI_TSS_MO_STATE] = { TLV_TYPE_FIXED, 4 },
+ [OM2K_DEI_SEEN_UNKNOWN_D2] = { TLV_TYPE_FIXED, 6 },
},
};
@@ -522,6 +572,15 @@ static const struct value_string om2k_msgcode_vals[] = {
{ 0x0118, "Feature Control Command" },
{ 0x011a, "Feature Control Complete" },
{ 0x011b, "Feature Control Reject" },
+ { 0x012c, "MCTR Configuration Request" },
+ { 0x012e, "MCTR Configuration Request Accept" },
+ { 0x012f, "MCTR Configuration Request Reject" },
+ { 0x0130, "MCTR Configuration Result ACK" },
+ { 0x0131, "MCTR Configuration Result NACK" },
+ { 0x0132, "MCTR Configuration Result" },
+ { 0x0134, "MCTR Statistics report ACK" },
+ { 0x0135, "MCTR Statistics report NACK" },
+ { 0x0136, "MCTR Statistics report" },
{ 0, NULL }
};
@@ -654,16 +713,25 @@ static const struct value_string om2k_attr_vals[] = {
{ 0x9b, "Master TX Chain Delay" },
{ 0x9c, "External Condition Class 2 Extension" },
{ 0x9d, "TSs MO State" },
+ { 0x9e, "Configuration Type" },
+ { 0x9f, "Jitter Size" },
+ { 0xa0, "Packing Algorithm" },
+ { 0xa8, "TRXC List" },
+ { 0xa9, "Maximum Allowed Power" },
+ { 0xaa, "Maximum Allowed Number of TRXCs" },
+ { 0xab, "MCTR Feature Status Bitmap" },
{ 0, NULL }
};
const struct value_string om2k_mo_class_short_vals[] = {
{ 0x01, "TRXC" },
+ { 0x02, "TG" },
{ 0x03, "TS" },
{ 0x04, "TF" },
{ 0x05, "IS" },
{ 0x06, "CON" },
{ 0x07, "DP" },
+ { 0x08, "MCTR" },
{ 0x0a, "CF" },
{ 0x0b, "TX" },
{ 0x0c, "RX" },
@@ -714,6 +782,9 @@ get_om2k_mo(struct gsm_bts *bts, const struct abis_om2k_mo *abis_mo)
struct gsm_bts_trx *trx;
switch (abis_mo->class) {
+ case OM2K_MO_CLS_DP:
+ mo = &bts->rbs2000.dp.om2k_mo;
+ break;
case OM2K_MO_CLS_CF:
mo = &bts->rbs2000.cf.om2k_mo;
break;
@@ -726,7 +797,9 @@ get_om2k_mo(struct gsm_bts *bts, const struct abis_om2k_mo *abis_mo)
case OM2K_MO_CLS_TF:
mo = &bts->rbs2000.tf.om2k_mo;
break;
-
+ case OM2K_MO_CLS_MCTR:
+ mo = &bts->rbs2000.mctr.om2k_mo;
+ break;
case OM2K_MO_CLS_TRXC:
trx = gsm_bts_trx_num(bts, abis_mo->inst);
if (!trx)
@@ -785,7 +858,7 @@ static int om2k_decode_msg(struct om2k_decoded_msg *odm, struct msgb *msg)
return abis_om2k_msg_tlv_parse(&odm->tp, o2h);
}
-static char *om2k_mo_name(const struct abis_om2k_mo *mo)
+const char *abis_om2k_mo_name(const struct abis_om2k_mo *mo)
{
static char mo_buf[64];
@@ -797,8 +870,7 @@ static char *om2k_mo_name(const struct abis_om2k_mo *mo)
}
/* resolve the gsm_nm_state data structure for a given MO */
-static struct gsm_nm_state *
-mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+static struct gsm_nm_state *mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
{
struct gsm_bts_trx *trx;
struct gsm_nm_state *nm_state = NULL;
@@ -818,6 +890,9 @@ mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
return NULL;
nm_state = &trx->ts[mo->inst].mo.nm_state;
break;
+ case OM2K_MO_CLS_MCTR:
+ nm_state = &bts->rbs2000.mctr.mo.nm_state;
+ break;
case OM2K_MO_CLS_TF:
nm_state = &bts->rbs2000.tf.mo.nm_state;
break;
@@ -850,7 +925,7 @@ mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
return nm_state;
}
-static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo)
+static void *mo2obj(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
{
struct gsm_bts_trx *trx;
@@ -866,6 +941,7 @@ static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo)
if (mo->inst >= ARRAY_SIZE(trx->ts))
return NULL;
return &trx->ts[mo->inst];
+ case OM2K_MO_CLS_MCTR:
case OM2K_MO_CLS_TF:
case OM2K_MO_CLS_IS:
case OM2K_MO_CLS_CON:
@@ -877,56 +953,147 @@ static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo)
return NULL;
}
-static void update_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo,
- uint8_t mo_state)
+/* Derive an OML Availability state from an OM2000 MO state */
+static enum abis_nm_avail_state abis_nm_av_state_from_om2k_av_state(struct abis_om2k_mo *mo, uint8_t mo_state)
+{
+ bool has_enabled_state;
+
+ switch (mo->class) {
+ case OM2K_MO_CLS_CF:
+ case OM2K_MO_CLS_TRXC:
+ has_enabled_state = false;
+ break;
+ default:
+ has_enabled_state = true;
+ break;
+ }
+
+ switch (mo_state) {
+ case OM2K_MOSTATE_RESET:
+ return NM_AVSTATE_POWER_OFF;
+ case OM2K_MOSTATE_STARTED:
+ return has_enabled_state ? NM_AVSTATE_OFF_LINE : NM_AVSTATE_OK;
+ case OM2K_MOSTATE_ENABLED:
+ return NM_AVSTATE_OK;
+ case OM2K_MOSTATE_DISABLED:
+ return NM_AVSTATE_POWER_OFF;
+ default:
+ return NM_AVSTATE_DEGRADED;
+ }
+}
+
+/* The OM2000 -> 12.21 mapping we do doesn't have a separate bb_transc MO,
+ * for compatibility reasons we pretend to have it anyway. */
+static void update_bb_trxc_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo, uint8_t mo_state)
{
- struct gsm_nm_state *nm_state = mo2nm_state(bts, mo);
- struct gsm_nm_state new_state;
struct nm_statechg_signal_data nsd;
+ struct gsm_bts_trx *trx;
- if (!nm_state)
+ trx = gsm_bts_trx_num(bts, mo->inst);
+ if (!trx)
return;
- new_state = *nm_state;
- /* NOTICE: 12.21 Availability state values != OM2000 */
- new_state.availability = mo_state;
-
memset(&nsd, 0, sizeof(nsd));
nsd.bts = bts;
nsd.obj = mo2obj(bts, mo);
- nsd.old_state = nm_state;
- nsd.new_state = &new_state;
+ nsd.old_state = trx->bb_transc.mo.nm_state;
+ nsd.new_state = trx->bb_transc.mo.nm_state;
nsd.om2k_mo = mo;
- osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd);
-
- nm_state->availability = new_state.availability;
+ nsd.new_state.availability = abis_nm_av_state_from_om2k_av_state(mo, mo_state);
+ trx->bb_transc.mo.nm_state.availability = nsd.new_state.availability;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd);
}
-static void update_op_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
- uint8_t op_state)
+static void update_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo, uint8_t mo_state)
{
struct gsm_nm_state *nm_state = mo2nm_state(bts, mo);
- struct gsm_nm_state new_state;
+ struct nm_statechg_signal_data nsd;
if (!nm_state)
return;
- new_state = *nm_state;
+ memset(&nsd, 0, sizeof(nsd));
+
+ nsd.bts = bts;
+ nsd.obj = mo2obj(bts, mo);
+ nsd.old_state = *nm_state;
+ nsd.new_state = *nm_state;
+ nsd.om2k_mo = mo;
+
+ nsd.new_state.availability = abis_nm_av_state_from_om2k_av_state(mo, mo_state);
+
+ /* Update current state before emitting signal: */
+ nm_state->availability = nsd.new_state.availability;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd);
+
+ /* When the TRXC MO is updated, also update the BB TRXC accordingly. */
+ if (mo->class == OM2K_MO_CLS_TRXC)
+ update_bb_trxc_mo_state(bts, mo, mo_state);
+}
+
+/* Derive an OML Operational state from an OM2000 OP state */
+static enum abis_nm_op_state abis_nm_op_state_from_om2k_op_state(uint8_t op_state)
+{
switch (op_state) {
case 1:
- new_state.operational = NM_OPSTATE_ENABLED;
- break;
+ return NM_OPSTATE_ENABLED;
case 0:
- new_state.operational = NM_OPSTATE_DISABLED;
- break;
+ return NM_OPSTATE_DISABLED;
default:
- new_state.operational = NM_OPSTATE_NULL;
- break;
+ return NM_OPSTATE_NULL;
}
+}
+
+/* (see comment in update_bb_trxc_mo_state() above) */
+static void update_bb_trxc_op_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo, uint8_t op_state)
+{
+ struct nm_statechg_signal_data nsd;
+ struct gsm_bts_trx *trx;
- nm_state->operational = new_state.operational;
+ trx = gsm_bts_trx_num(bts, mo->inst);
+ if (!trx)
+ return;
+
+ memset(&nsd, 0, sizeof(nsd));
+
+ nsd.bts = bts;
+ nsd.obj = mo2obj(bts, mo);
+ nsd.old_state = trx->bb_transc.mo.nm_state;
+ nsd.new_state = trx->bb_transc.mo.nm_state;
+ nsd.om2k_mo = mo;
+
+ nsd.new_state.operational = abis_nm_op_state_from_om2k_op_state(op_state);
+ trx->bb_transc.mo.nm_state.operational = nsd.new_state.operational;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd);
+}
+
+static void update_op_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo, uint8_t op_state)
+{
+ struct gsm_nm_state *nm_state = mo2nm_state(bts, mo);
+ struct nm_statechg_signal_data nsd;
+
+ if (!nm_state)
+ return;
+
+ memset(&nsd, 0, sizeof(nsd));
+
+ nsd.bts = bts;
+ nsd.obj = mo2obj(bts, mo);
+ nsd.old_state = *nm_state;
+ nsd.new_state = *nm_state;
+ nsd.om2k_mo = mo;
+
+ nsd.new_state.operational = abis_nm_op_state_from_om2k_op_state(op_state);
+
+ /* Update current state before emitting signal: */
+ nm_state->operational = nsd.new_state.operational;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd);
+
+ /* When the TRXC MO is updated, also update the BB TRXC accordingly. */
+ if (mo->class == OM2K_MO_CLS_TRXC)
+ update_bb_trxc_op_state(bts, mo, op_state);
}
static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
@@ -945,20 +1112,20 @@ static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
case OM2K_MO_CLS_TX:
case OM2K_MO_CLS_RX:
/* Route through per-TRX OML Link to the appropriate TRX */
- trx = gsm_bts_trx_by_nr(bts, o2h->mo.inst);
+ trx = gsm_bts_trx_num(bts, o2h->mo.inst);
if (!trx) {
- LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to "
- "non-existing TRX\n", om2k_mo_name(&o2h->mo));
+ LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to non-existing TRX\n",
+ abis_om2k_mo_name(&o2h->mo));
return -ENODEV;
}
msg->dst = trx->oml_link;
break;
case OM2K_MO_CLS_TS:
/* Route through per-TRX OML Link to the appropriate TRX */
- trx = gsm_bts_trx_by_nr(bts, o2h->mo.assoc_so);
+ trx = gsm_bts_trx_num(bts, o2h->mo.assoc_so);
if (!trx) {
- LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to "
- "non-existing TRX\n", om2k_mo_name(&o2h->mo));
+ LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to non-existing TRX\n",
+ abis_om2k_mo_name(&o2h->mo));
return -ENODEV;
}
msg->dst = trx->oml_link;
@@ -972,8 +1139,7 @@ static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
return _abis_nm_sendmsg(msg);
}
-static void fill_om2k_hdr(struct abis_om2k_hdr *o2h, const struct abis_om2k_mo *mo,
- uint16_t msg_type)
+static void fill_om2k_hdr(struct abis_om2k_hdr *o2h, const struct abis_om2k_mo *mo, uint16_t msg_type)
{
o2h->om.mdisc = ABIS_OM_MDISC_FOM;
o2h->om.placement = ABIS_OM_PLACEMENT_ONLY;
@@ -991,8 +1157,7 @@ static int abis_om2k_cal_time_resp(struct gsm_bts *bts)
struct tm *tm;
o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
- fill_om2k_hdr(o2k, &bts->rbs2000.cf.om2k_mo.addr,
- OM2K_MSGT_CAL_TIME_RESP);
+ fill_om2k_hdr(o2k, &bts->rbs2000.cf.om2k_mo.addr, OM2K_MSGT_CAL_TIME_RESP);
tm_t = time(NULL);
tm = localtime(&tm_t);
@@ -1008,8 +1173,7 @@ static int abis_om2k_cal_time_resp(struct gsm_bts *bts)
return abis_om2k_sendmsg(bts, msg);
}
-static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
- uint8_t msg_type)
+static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo, uint16_t msg_type)
{
struct msgb *msg = om2k_msgb_alloc();
struct abis_om2k_hdr *o2k;
@@ -1017,8 +1181,7 @@ static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *m
o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
fill_om2k_hdr(o2k, mo, msg_type);
- DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
- get_value_string(om2k_msgcode_vals, msg_type));
+ DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(mo), get_value_string(om2k_msgcode_vals, msg_type));
return abis_om2k_sendmsg(bts, msg);
}
@@ -1063,8 +1226,7 @@ int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISABLE_REQ);
}
-int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
- uint8_t operational)
+int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo, uint8_t operational)
{
struct msgb *msg = om2k_msgb_alloc();
struct abis_om2k_hdr *o2k;
@@ -1074,7 +1236,7 @@ int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
msgb_tv_put(msg, OM2K_DEI_OP_INFO, operational);
- DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+ DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(mo),
get_value_string(om2k_msgcode_vals, OM2K_MSGT_OP_INFO));
/* we update the state here... and send the signal at ACK */
@@ -1088,6 +1250,21 @@ int abis_om2k_tx_cap_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CAPA_REQ);
}
+int abis_om2k_tx_arb(struct gsm_bts *bts, struct abis_om2k_mo *mo,
+ uint16_t req, uint8_t *buf, int buf_len)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+
+ o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+ fill_om2k_hdr(o2k, mo, req);
+
+ if (buf_len)
+ memcpy(msgb_put(msg, buf_len), buf, buf_len);
+
+ return abis_om2k_sendmsg(bts, msg);
+}
+
static void om2k_fill_is_conn_grp(struct om2k_is_conn_grp *grp, uint16_t icp1,
uint16_t icp2, uint8_t cont_idx)
{
@@ -1119,19 +1296,16 @@ int abis_om2k_tx_is_conf_req(struct gsm_bts *bts)
om2k_fill_is_conn_grp(&cg[i++], grp->icp1, grp->icp2, grp->ci);
o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
- fill_om2k_hdr(o2k, &bts->rbs2000.is.om2k_mo.addr,
- OM2K_MSGT_IS_CONF_REQ);
+ fill_om2k_hdr(o2k, &bts->rbs2000.is.om2k_mo.addr, OM2K_MSGT_IS_CONF_REQ);
msgb_tv_put(msg, OM2K_DEI_LIST_NR, 1);
msgb_tv_put(msg, OM2K_DEI_END_LIST_NR, 1);
- msgb_tlv_put(msg, OM2K_DEI_IS_CONN_LIST,
- num_grps * sizeof(*cg), (uint8_t *)cg);
+ msgb_tlv_put(msg, OM2K_DEI_IS_CONN_LIST, num_grps * sizeof(*cg), (uint8_t *)cg);
talloc_free(cg);
- DEBUGP(DNM, "Tx MO=%s %s\n",
- om2k_mo_name(&bts->rbs2000.is.om2k_mo.addr),
+ DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(&bts->rbs2000.is.om2k_mo.addr),
get_value_string(om2k_msgcode_vals, OM2K_MSGT_IS_CONF_REQ));
return abis_om2k_sendmsg(bts, msg);
@@ -1177,18 +1351,42 @@ int abis_om2k_tx_con_conf_req(struct gsm_bts *bts)
/* pre-pend the OM2K header */
o2k = (struct abis_om2k_hdr *) msgb_push(msg, sizeof(*o2k));
- fill_om2k_hdr(o2k, &bts->rbs2000.con.om2k_mo.addr,
- OM2K_MSGT_CON_CONF_REQ);
+ fill_om2k_hdr(o2k, &bts->rbs2000.con.om2k_mo.addr, OM2K_MSGT_CON_CONF_REQ);
- DEBUGP(DNM, "Tx MO=%s %s\n",
- om2k_mo_name(&bts->rbs2000.con.om2k_mo.addr),
+ DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(&bts->rbs2000.con.om2k_mo.addr),
get_value_string(om2k_msgcode_vals, OM2K_MSGT_CON_CONF_REQ));
return abis_om2k_sendmsg(bts, msg);
}
-static void om2k_trx_to_mo(struct abis_om2k_mo *mo,
- const struct gsm_bts_trx *trx,
+int abis_om2k_tx_mctr_conf_req(struct gsm_bts *bts)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+ struct gsm_bts_trx *trx;
+ uint8_t trxc_list = 0;
+ const uint8_t features[] = { 0x00 };
+
+ /* build trxc list */
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ trxc_list |= (1 << trx->nr);
+
+ /* fill message */
+ msgb_tv16_put(msg, OM2K_DEI_TRXC_LIST, osmo_swab16(trxc_list)); /* Read as LE by the BTS ... */
+ msgb_tv_put (msg, OM2K_DEI_MAX_ALLOWED_POWER, 0x31);
+ msgb_tv_put (msg, OM2K_DEI_MAX_ALLOWED_NUM_TRXCS, 0x08);
+ msgb_tlv_put (msg, OM2K_DEI_MCTR_FEAT_STATUS_BMAP, 1, features);
+
+ /* pre-pend the OM2K header */
+ o2k = (struct abis_om2k_hdr *) msgb_push(msg, sizeof(*o2k));
+ fill_om2k_hdr(o2k, &bts->rbs2000.mctr.om2k_mo.addr, OM2K_MSGT_MCTR_CONF_REQ);
+ DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(&bts->rbs2000.mctr.om2k_mo.addr),
+ get_value_string(om2k_msgcode_vals, OM2K_MSGT_MCTR_CONF_REQ));
+
+ return abis_om2k_sendmsg(bts, msg);
+}
+
+static void om2k_trx_to_mo(struct abis_om2k_mo *mo, const struct gsm_bts_trx *trx,
enum abis_om2k_mo_cls cls)
{
mo->class = cls;
@@ -1197,8 +1395,7 @@ static void om2k_trx_to_mo(struct abis_om2k_mo *mo,
mo->assoc_so = 255;
}
-static void om2k_ts_to_mo(struct abis_om2k_mo *mo,
- const struct gsm_bts_trx_ts *ts)
+static void om2k_ts_to_mo(struct abis_om2k_mo *mo, const struct gsm_bts_trx_ts *ts)
{
mo->class = OM2K_MO_CLS_TS;
mo->bts = 0;
@@ -1218,8 +1415,9 @@ int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx)
o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
fill_om2k_hdr(o2k, &mo, OM2K_MSGT_RX_CONF_REQ);
- msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, trx->arfcn);
- msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */
+ /* OM2K_DEI_FREQ_SPEC_RX: Using trx_nr as "RX address" only works for single MCTR case */
+ msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, 0x8000 | ((uint16_t)trx->nr << 10));
+ msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, trx->rbs2000.rx_diversity);
return abis_om2k_sendmsg(trx->bts, msg);
}
@@ -1236,9 +1434,10 @@ int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx)
o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TX_CONF_REQ);
- msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_TX, trx->arfcn);
+ /* OM2K_DEI_FREQ_SPEC_TX: Using trx_nr as "TX address" only works for single MCTR case */
+ msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_TX, trx->arfcn | ((uint16_t)trx->nr << 10));
msgb_tv_put(msg, OM2K_DEI_POWER, trx->nominal_power-trx->max_power_red);
- msgb_tv_put(msg, OM2K_DEI_FILLING_MARKER, 0); /* Filling enabled */
+ msgb_tv_put(msg, OM2K_DEI_FILLING_MARKER, trx != trx->bts->c0); /* Filling enabled for C0 only */
msgb_tv_put(msg, OM2K_DEI_BCC, trx->bts->bsic & 0x7);
/* Dedication Information is optional */
@@ -1260,16 +1459,13 @@ int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts)
struct abis_om2k_hdr *o2k;
o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
- fill_om2k_hdr(o2k, &bts->rbs2000.tf.om2k_mo.addr,
- OM2K_MSGT_TF_CONF_REQ);
+ fill_om2k_hdr(o2k, &bts->rbs2000.tf.om2k_mo.addr, OM2K_MSGT_TF_CONF_REQ);
msgb_tv_put(msg, OM2K_DEI_TF_MODE, OM2K_TF_MODE_STANDALONE);
- msgb_tv_put(msg, OM2K_DEI_TF_SYNC_SRC, 0x00);
- msgb_tv_fixed_put(msg, OM2K_DEI_FS_OFFSET,
- sizeof(fs_offset_undef), fs_offset_undef);
+ msgb_tv_put(msg, OM2K_DEI_TF_SYNC_SRC, bts->rbs2000.sync_src);
+ msgb_tv_fixed_put(msg, OM2K_DEI_FS_OFFSET, sizeof(fs_offset_undef), fs_offset_undef);
- DEBUGP(DNM, "Tx MO=%s %s\n",
- om2k_mo_name(&bts->rbs2000.tf.om2k_mo.addr),
+ DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(&bts->rbs2000.tf.om2k_mo.addr),
get_value_string(om2k_msgcode_vals, OM2K_MSGT_TF_CONF_REQ));
return abis_om2k_sendmsg(bts, msg);
@@ -1288,7 +1484,7 @@ static uint8_t pchan2comb(enum gsm_phys_chan_config pchan)
case GSM_PCHAN_TCH_H:
case GSM_PCHAN_PDCH:
case GSM_PCHAN_TCH_F_PDCH:
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
return 8;
default:
return 0;
@@ -1298,25 +1494,54 @@ static uint8_t pchan2comb(enum gsm_phys_chan_config pchan)
static uint8_t ts2comb(struct gsm_bts_trx_ts *ts)
{
if (ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH) {
- LOGP(DNM, LOGL_ERROR, "%s pchan %s not intended for use"
- " with OM2000, use %s instead\n",
- gsm_ts_and_pchan_name(ts),
- gsm_pchan_name(GSM_PCHAN_TCH_F_PDCH),
- gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH));
+ LOGP(DNM, LOGL_ERROR, "%s pchan %s not intended for use with OM2000, use %s instead\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(GSM_PCHAN_TCH_F_PDCH),
+ gsm_pchan_name(GSM_PCHAN_OSMO_DYN));
/* If we allowed initialization of TCH/F_PDCH, it would fail
* when we try to send the ip.access specific RSL PDCH Act
* message for it. Rather fail completely right now: */
return 0;
}
- return pchan2comb(ts->pchan_is);
+ return pchan2comb(ts->pchan_from_config);
}
-static int put_freq_list(uint8_t *buf, uint16_t arfcn)
+static int put_freq_list(uint8_t *buf, struct gsm_bts_trx_ts *ts, uint16_t arfcn)
{
- buf[0] = 0x00; /* TX/RX address */
+ struct gsm_bts_trx *t, *trx = NULL;
+
+ /* Find the TRX that's configured for that ARFCN */
+ llist_for_each_entry(t, &ts->trx->bts->trx_list, list)
+ if (t->arfcn == arfcn) {
+ trx = t;
+ break;
+ }
+
+ if (!trx) {
+ LOGP(DNM, LOGL_ERROR, "Trying to use ARFCN %d for hopping with no TRX configured for it", arfcn);
+ return 0;
+ }
+
+ /*
+ * [7:4] - TX address
+ * This must be the same number that was used when configuring the TX
+ * MO object with that target arfcn
+ *
+ * [3:0] - RX address
+ * The logical TRX number we're configuring the hopping sequence for
+ * This must basically match the MO object instance number
+ *
+ * ATM since we only support 1 MCTR, we use trx->nr
+ */
+ buf[0] = (trx->nr << 4) | ts->trx->nr;
+
+ /* ARFCN Number */
buf[1] = (arfcn >> 8);
buf[2] = (arfcn & 0xff);
+ /* C0 marker */
+ if (trx == trx->bts->c0)
+ buf[1] |= 0x04;
+
return 3;
}
@@ -1330,10 +1555,10 @@ static int om2k_gen_freq_list(uint8_t *list, struct gsm_bts_trx_ts *ts)
unsigned int i;
for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
if (bitvec_get_bit_pos(&ts->hopping.arfcns, i))
- cur += put_freq_list(cur, i);
+ cur += put_freq_list(cur, ts, i);
}
} else
- cur += put_freq_list(cur, ts->trx->arfcn);
+ cur += put_freq_list(cur, ts, ts->trx->arfcn);
len = cur - list;
@@ -1366,12 +1591,12 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
msgb_tv_put(msg, OM2K_DEI_HSN, ts->hopping.hsn);
msgb_tv_put(msg, OM2K_DEI_MAIO, ts->hopping.maio);
msgb_tv_put(msg, OM2K_DEI_BSIC, ts->trx->bts->bsic);
- msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */
+ msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, ts->trx->rbs2000.rx_diversity);
msgb_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0);
msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */
/* Optional: Interference Rejection Combining */
msgb_tv_put(msg, OM2K_DEI_INTERF_REJ_COMB, 0x00);
- switch (ts->pchan_is) {
+ switch (ts->pchan_from_config) {
case GSM_PCHAN_CCCH:
msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06);
msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01);
@@ -1381,7 +1606,7 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
break;
case GSM_PCHAN_CCCH_SDCCH4:
msgb_tv_put(msg, OM2K_DEI_T3105,
- T_def_get(ts->trx->bts->network->T_defs, 3105, T_MS, -1) / 10);
+ osmo_tdef_get(ts->trx->bts->network->T_defs, 3105, OSMO_TDEF_MS, -1) / 10);
msgb_tv_put(msg, OM2K_DEI_NY1, 35);
msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06);
msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0);
@@ -1396,7 +1621,7 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
break;
case GSM_PCHAN_SDCCH8_SACCH8C:
msgb_tv_put(msg, OM2K_DEI_T3105,
- T_def_get(ts->trx->bts->network->T_defs, 3105, T_MS, -1) / 10);
+ osmo_tdef_get(ts->trx->bts->network->T_defs, 3105, OSMO_TDEF_MS, -1) / 10);
msgb_tv_put(msg, OM2K_DEI_NY1, 35);
msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0);
msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts));
@@ -1407,7 +1632,7 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
break;
default:
msgb_tv_put(msg, OM2K_DEI_T3105,
- T_def_get(ts->trx->bts->network->T_defs, 3105, T_MS, -1) / 10);
+ osmo_tdef_get(ts->trx->bts->network->T_defs, 3105, OSMO_TDEF_MS, -1) / 10);
msgb_tv_put(msg, OM2K_DEI_NY1, 35);
msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts));
/* Disable RF RESOURCE INDICATION on idle channels */
@@ -1415,7 +1640,7 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS,
sizeof(icm_bound_params), icm_bound_params);
msgb_tv_put(msg, OM2K_DEI_TTA, 10); /* Timer for Time Alignment */
- if (ts->pchan_is == GSM_PCHAN_TCH_H)
+ if (ts->pchan_from_config == GSM_PCHAN_TCH_H)
msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 1); /* TCH/H */
else
msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 0); /* TCH/F */
@@ -1423,15 +1648,19 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
msgb_tv_put(msg, OM2K_DEI_LSC_FILT_TIME, 10); /* units of 100ms */
msgb_tv_put(msg, OM2K_DEI_CALL_SUPV_TIME, 8);
msgb_tv_put(msg, OM2K_DEI_ENCR_ALG, 0x00);
- /* Not sure what those below mean */
- msgb_tv_put(msg, 0x9e, 0x00);
- msgb_tv_put(msg, 0x9f, 0x37);
- msgb_tv_put(msg, 0xa0, 0x01);
+
+ /* Those are only use for superchannel */
+ if (ts->trx->bts->rbs2000.use_superchannel) {
+ msgb_tv_put(msg, OM2K_DEI_CONFIG_TYPE, 0x00); /* 1-bit, lsb */
+ msgb_tv_put(msg, OM2K_DEI_JITTER_SIZE, 0x37);
+ msgb_tv_put(msg, OM2K_DEI_PACKING_ALGO, 0x01);
+ }
+
break;
}
DEBUGP(DNM, "Tx MO=%s %s\n",
- om2k_mo_name(&mo),
+ abis_om2k_mo_name(&mo),
get_value_string(om2k_msgcode_vals, OM2K_MSGT_TS_CONF_REQ));
return abis_om2k_sendmsg(ts->trx->bts, msg);
@@ -1445,7 +1674,9 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
#define S(x) (1 << (x))
enum om2k_event_name {
+ OM2K_MO_EVT_RESET,
OM2K_MO_EVT_START,
+ OM2K_MO_EVT_CHILD_TERM,
OM2K_MO_EVT_RX_CONN_COMPL,
OM2K_MO_EVT_RX_RESET_COMPL,
OM2K_MO_EVT_RX_START_REQ_ACCEPT,
@@ -1458,7 +1689,9 @@ enum om2k_event_name {
};
static const struct value_string om2k_event_names[] = {
+ { OM2K_MO_EVT_RESET, "RESET" },
{ OM2K_MO_EVT_START, "START" },
+ { OM2K_MO_EVT_CHILD_TERM, "CHILD-TERM" },
{ OM2K_MO_EVT_RX_CONN_COMPL, "RX-CONN-COMPL" },
{ OM2K_MO_EVT_RX_RESET_COMPL, "RX-RESET-COMPL" },
{ OM2K_MO_EVT_RX_START_REQ_ACCEPT, "RX-RESET-REQ-ACCEPT" },
@@ -1490,6 +1723,7 @@ struct om2k_mo_fsm_priv {
struct gsm_bts_trx *trx;
struct om2k_mo *mo;
uint8_t ts_nr;
+ uint32_t done_event;
};
static void om2k_mo_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -1501,20 +1735,17 @@ static void om2k_mo_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data
switch (omfp->mo->addr.class) {
case OM2K_MO_CLS_CF:
/* no Connect required, is always connected */
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, OM2K_TIMEOUT, 0);
abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr);
break;
case OM2K_MO_CLS_TRXC:
/* no Connect required, start with Reset */
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL, OM2K_TIMEOUT, 0);
abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr);
break;
default:
/* start with Connect */
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CONN_COMPL,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CONN_COMPL, OM2K_TIMEOUT, 0);
abis_om2k_tx_connect_cmd(omfp->trx->bts, &omfp->mo->addr);
break;
}
@@ -1528,14 +1759,12 @@ static void om2k_mo_st_wait_conn_compl(struct osmo_fsm_inst *fi, uint32_t event,
#if 0
case OM2K_MO_CLS_TF:
/* skip the reset, hope that helps */
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, OM2K_TIMEOUT, 0);
abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr);
break;
#endif
default:
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL, OM2K_TIMEOUT, 0);
abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr);
break;
}
@@ -1545,8 +1774,7 @@ static void om2k_mo_st_wait_res_compl(struct osmo_fsm_inst *fi, uint32_t event,
{
struct om2k_mo_fsm_priv *omfp = fi->priv;
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, OM2K_TIMEOUT, 0);
abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr);
}
@@ -1556,8 +1784,7 @@ static void om2k_mo_st_wait_start_accept(struct osmo_fsm_inst *fi, uint32_t even
switch (omd->msg_type) {
case OM2K_MSGT_START_REQ_ACK:
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_RES,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_RES, OM2K_TIMEOUT, 0);
break;
case OM2K_MSGT_START_REQ_REJ:
osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
@@ -1574,21 +1801,18 @@ static void om2k_mo_st_wait_start_res(struct osmo_fsm_inst *fi, uint32_t event,
case OM2K_MO_CLS_CF:
case OM2K_MO_CLS_TRXC:
/* Transition directly to Operational Info */
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT, OM2K_TIMEOUT, 0);
abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1);
return;
case OM2K_MO_CLS_DP:
- /* Transition directoy to WAIT_ENABLE_ACCEPT */
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT,
- OM2K_TIMEOUT, 0);
+ /* Transition directory to WAIT_ENABLE_ACCEPT */
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, OM2K_TIMEOUT, 0);
abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr);
return;
#if 0
case OM2K_MO_CLS_TF:
/* skip the config, hope that helps speeding things up */
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, OM2K_TIMEOUT, 0);
abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr);
return;
#endif
@@ -1606,6 +1830,9 @@ static void om2k_mo_st_wait_start_res(struct osmo_fsm_inst *fi, uint32_t event,
case OM2K_MO_CLS_CON:
abis_om2k_tx_con_conf_req(omfp->trx->bts);
break;
+ case OM2K_MO_CLS_MCTR:
+ abis_om2k_tx_mctr_conf_req(omfp->trx->bts);
+ break;
case OM2K_MO_CLS_TX:
abis_om2k_tx_tx_conf_req(omfp->trx);
break;
@@ -1648,8 +1875,7 @@ static void om2k_mo_st_wait_cfg_res(struct osmo_fsm_inst *fi, uint32_t event, vo
return;
}
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, OM2K_TIMEOUT, 0);
abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr);
}
@@ -1663,11 +1889,9 @@ static void om2k_mo_st_wait_enable_accept(struct osmo_fsm_inst *fi, uint32_t eve
osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
break;
case OM2K_MSGT_ENABLE_REQ_ACK:
- if (omfp->mo->addr.class == OM2K_MO_CLS_IS &&
- omfp->trx->bts->rbs2000.use_superchannel)
+ if (omfp->mo->addr.class == OM2K_MO_CLS_IS && omfp->trx->bts->rbs2000.use_superchannel)
e1inp_ericsson_set_altc(omfp->trx->bts->oml_link->ts->line, 1);
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_RES,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_RES, OM2K_TIMEOUT, 0);
}
}
@@ -1677,8 +1901,7 @@ static void om2k_mo_st_wait_enable_res(struct osmo_fsm_inst *fi, uint32_t event,
//struct om2k_decoded_msg *omd = data;
/* TODO: check if state is actually enabled now? */
- osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT,
- OM2K_TIMEOUT, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT, OM2K_TIMEOUT, 0);
abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1);
}
@@ -1690,8 +1913,9 @@ static void om2k_mo_st_wait_opinfo_accept(struct osmo_fsm_inst *fi, uint32_t eve
static void om2k_mo_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct om2k_mo_fsm_priv *omfp = fi->priv;
- omfp->mo->fsm = NULL;
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+
+ if (fi->proc.parent)
+ osmo_fsm_inst_dispatch(fi->proc.parent, omfp->done_event, NULL);
}
static void om2k_mo_s_error_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
@@ -1702,11 +1926,24 @@ static void om2k_mo_s_error_onenter(struct osmo_fsm_inst *fi, uint32_t prev_stat
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
}
+static void om2k_mo_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case OM2K_MO_EVT_RESET:
+ osmo_fsm_inst_broadcast_children(fi, event, data);
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_INIT, 0, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
static const struct osmo_fsm_state om2k_is_states[] = {
[OM2K_ST_INIT] = {
.name = "INIT",
.in_event_mask = S(OM2K_MO_EVT_START),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR) |
S(OM2K_ST_WAIT_CONN_COMPL) |
S(OM2K_ST_WAIT_START_ACCEPT) |
@@ -1717,6 +1954,7 @@ static const struct osmo_fsm_state om2k_is_states[] = {
.name = "WAIT-CONN-COMPL",
.in_event_mask = S(OM2K_MO_EVT_RX_CONN_COMPL),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR) |
S(OM2K_ST_WAIT_START_ACCEPT) |
S(OM2K_ST_WAIT_RES_COMPL),
@@ -1726,6 +1964,7 @@ static const struct osmo_fsm_state om2k_is_states[] = {
.name = "WAIT-RES-COMPL",
.in_event_mask = S(OM2K_MO_EVT_RX_RESET_COMPL),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR) |
S(OM2K_ST_WAIT_START_ACCEPT),
.action = om2k_mo_st_wait_res_compl,
@@ -1734,6 +1973,7 @@ static const struct osmo_fsm_state om2k_is_states[] = {
.name = "WAIT-START-ACCEPT",
.in_event_mask = S(OM2K_MO_EVT_RX_START_REQ_ACCEPT),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR) |
S(OM2K_ST_WAIT_START_RES),
.action =om2k_mo_st_wait_start_accept,
@@ -1742,15 +1982,18 @@ static const struct osmo_fsm_state om2k_is_states[] = {
.name = "WAIT-START-RES",
.in_event_mask = S(OM2K_MO_EVT_RX_START_RES),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR) |
S(OM2K_ST_WAIT_CFG_ACCEPT) |
- S(OM2K_ST_WAIT_OPINFO_ACCEPT),
+ S(OM2K_ST_WAIT_OPINFO_ACCEPT) |
+ S(OM2K_ST_WAIT_ENABLE_ACCEPT),
.action = om2k_mo_st_wait_start_res,
},
[OM2K_ST_WAIT_CFG_ACCEPT] = {
.name = "WAIT-CFG-ACCEPT",
.in_event_mask = S(OM2K_MO_EVT_RX_CFG_REQ_ACCEPT),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR) |
S(OM2K_ST_WAIT_CFG_RES),
.action = om2k_mo_st_wait_cfg_accept,
@@ -1759,6 +2002,7 @@ static const struct osmo_fsm_state om2k_is_states[] = {
.name = "WAIT-CFG-RES",
.in_event_mask = S(OM2K_MO_EVT_RX_CFG_RES),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR) |
S(OM2K_ST_WAIT_ENABLE_ACCEPT),
.action = om2k_mo_st_wait_cfg_res,
@@ -1767,6 +2011,7 @@ static const struct osmo_fsm_state om2k_is_states[] = {
.name = "WAIT-ENABLE-ACCEPT",
.in_event_mask = S(OM2K_MO_EVT_RX_ENA_REQ_ACCEPT),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR) |
S(OM2K_ST_WAIT_ENABLE_RES),
.action = om2k_mo_st_wait_enable_accept,
@@ -1775,6 +2020,7 @@ static const struct osmo_fsm_state om2k_is_states[] = {
.name = "WAIT-ENABLE-RES",
.in_event_mask = S(OM2K_MO_EVT_RX_ENA_RES),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR) |
S(OM2K_ST_WAIT_OPINFO_ACCEPT),
.action = om2k_mo_st_wait_enable_res,
@@ -1783,19 +2029,20 @@ static const struct osmo_fsm_state om2k_is_states[] = {
.name = "WAIT-OPINFO-ACCEPT",
.in_event_mask = S(OM2K_MO_EVT_RX_OPINFO_ACC),
.out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_INIT) |
S(OM2K_ST_ERROR),
.action = om2k_mo_st_wait_opinfo_accept,
},
[OM2K_ST_DONE] = {
.name = "DONE",
.in_event_mask = 0,
- .out_state_mask = 0,
+ .out_state_mask = S(OM2K_ST_INIT),
.onenter = om2k_mo_s_done_onenter,
},
[OM2K_ST_ERROR] = {
.name = "ERROR",
.in_event_mask = 0,
- .out_state_mask = 0,
+ .out_state_mask = S(OM2K_ST_INIT),
.onenter = om2k_mo_s_error_onenter,
},
@@ -1812,23 +2059,24 @@ static struct osmo_fsm om2k_mo_fsm = {
.states = om2k_is_states,
.num_states = ARRAY_SIZE(om2k_is_states),
.log_subsys = DNM,
+ .allstate_event_mask = S(OM2K_MO_EVT_RESET),
+ .allstate_action = om2k_mo_allstate,
.event_names = om2k_event_names,
.timer_cb = om2k_mo_timer_cb,
};
-struct osmo_fsm_inst *om2k_mo_fsm_start(struct osmo_fsm_inst *parent,
- uint32_t term_event,
- struct gsm_bts_trx *trx, struct om2k_mo *mo)
+static struct osmo_fsm_inst *om2k_mo_fsm_alloc(struct osmo_fsm_inst *parent, uint32_t done_event,
+ struct gsm_bts_trx *trx, struct om2k_mo *mo)
{
struct osmo_fsm_inst *fi;
struct om2k_mo_fsm_priv *omfp;
char idbuf[64];
- snprintf(idbuf, sizeof(idbuf), "%s-%s", parent->id,
- om2k_mo_name(&mo->addr));
+ snprintf(idbuf, sizeof(idbuf), "%s-%s-%02x-%02x-%02x", parent->id,
+ get_value_string(om2k_mo_class_short_vals, mo->addr.class),
+ mo->addr.bts, mo->addr.assoc_so, mo->addr.inst);
- fi = osmo_fsm_inst_alloc_child_id(&om2k_mo_fsm, parent,
- term_event, idbuf);
+ fi = osmo_fsm_inst_alloc_child_id(&om2k_mo_fsm, parent, OM2K_MO_EVT_CHILD_TERM, idbuf);
if (!fi)
return NULL;
@@ -1836,74 +2084,71 @@ struct osmo_fsm_inst *om2k_mo_fsm_start(struct osmo_fsm_inst *parent,
omfp = talloc_zero(fi, struct om2k_mo_fsm_priv);
omfp->mo = mo;
omfp->trx = trx;
+ omfp->done_event = done_event;
fi->priv = omfp;
- osmo_fsm_inst_dispatch(fi, OM2K_MO_EVT_START, NULL);
-
return fi;
}
+static void om2k_mo_fsm_start(struct om2k_mo *mo)
+{
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_START, NULL);
+}
+
int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo,
struct om2k_decoded_msg *odm)
{
switch (odm->msg_type) {
case OM2K_MSGT_CONNECT_COMPL:
case OM2K_MSGT_CONNECT_REJ:
- osmo_fsm_inst_dispatch(mo->fsm,
- OM2K_MO_EVT_RX_CONN_COMPL, odm);
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_CONN_COMPL, odm);
break;
case OM2K_MSGT_RESET_COMPL:
case OM2K_MSGT_RESET_REJ:
- osmo_fsm_inst_dispatch(mo->fsm,
- OM2K_MO_EVT_RX_RESET_COMPL, odm);
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_RESET_COMPL, odm);
break;
case OM2K_MSGT_START_REQ_ACK:
case OM2K_MSGT_START_REQ_REJ:
- osmo_fsm_inst_dispatch(mo->fsm,
- OM2K_MO_EVT_RX_START_REQ_ACCEPT, odm);
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_START_REQ_ACCEPT, odm);
break;
case OM2K_MSGT_START_RES:
- osmo_fsm_inst_dispatch(mo->fsm,
- OM2K_MO_EVT_RX_START_RES, odm);
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_START_RES, odm);
break;
case OM2K_MSGT_CON_CONF_REQ_ACK:
case OM2K_MSGT_IS_CONF_REQ_ACK:
+ case OM2K_MSGT_MCTR_CONF_REQ_ACK:
case OM2K_MSGT_RX_CONF_REQ_ACK:
case OM2K_MSGT_TF_CONF_REQ_ACK:
case OM2K_MSGT_TS_CONF_REQ_ACK:
case OM2K_MSGT_TX_CONF_REQ_ACK:
- osmo_fsm_inst_dispatch(mo->fsm,
- OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, odm);
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, odm);
break;
case OM2K_MSGT_CON_CONF_RES:
case OM2K_MSGT_IS_CONF_RES:
+ case OM2K_MSGT_MCTR_CONF_RES:
case OM2K_MSGT_RX_CONF_RES:
case OM2K_MSGT_TF_CONF_RES:
case OM2K_MSGT_TS_CONF_RES:
case OM2K_MSGT_TX_CONF_RES:
- osmo_fsm_inst_dispatch(mo->fsm,
- OM2K_MO_EVT_RX_CFG_RES, odm);
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_CFG_RES, odm);
break;
case OM2K_MSGT_ENABLE_REQ_ACK:
case OM2K_MSGT_ENABLE_REQ_REJ:
- osmo_fsm_inst_dispatch(mo->fsm,
- OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, odm);
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, odm);
break;
case OM2K_MSGT_ENABLE_RES:
- osmo_fsm_inst_dispatch(mo->fsm,
- OM2K_MO_EVT_RX_ENA_RES, odm);
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_ENA_RES, odm);
break;
case OM2K_MSGT_OP_INFO_ACK:
case OM2K_MSGT_OP_INFO_REJ:
- osmo_fsm_inst_dispatch(mo->fsm,
- OM2K_MO_EVT_RX_OPINFO_ACC, odm);
+ osmo_fsm_inst_dispatch(mo->fsm, OM2K_MO_EVT_RX_OPINFO_ACC, odm);
break;
default:
return -1;
@@ -1917,7 +2162,9 @@ int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo,
***********************************************************************/
enum om2k_trx_event {
- OM2K_TRX_EVT_START,
+ OM2K_TRX_EVT_RESET = OM2K_MO_EVT_RESET,
+ OM2K_TRX_EVT_START = OM2K_MO_EVT_START,
+ OM2K_TRX_EVT_CHILD_TERM = OM2K_MO_EVT_CHILD_TERM,
OM2K_TRX_EVT_TRXC_DONE,
OM2K_TRX_EVT_TX_DONE,
OM2K_TRX_EVT_RX_DONE,
@@ -1926,7 +2173,9 @@ enum om2k_trx_event {
};
static struct value_string om2k_trx_events[] = {
+ { OM2K_TRX_EVT_RESET, "RESET" },
{ OM2K_TRX_EVT_START, "START" },
+ { OM2K_TRX_EVT_CHILD_TERM, "CHILD-TERM" },
{ OM2K_TRX_EVT_TRXC_DONE, "TRXC-DONE" },
{ OM2K_TRX_EVT_TX_DONE, "TX-DONE" },
{ OM2K_TRX_EVT_RX_DONE, "RX-DONE" },
@@ -1941,13 +2190,15 @@ enum om2k_trx_state {
OM2K_TRX_S_WAIT_TX,
OM2K_TRX_S_WAIT_RX,
OM2K_TRX_S_WAIT_TS,
+ OM2K_TRX_S_SEND_SI,
OM2K_TRX_S_DONE,
OM2K_TRX_S_ERROR
};
struct om2k_trx_fsm_priv {
struct gsm_bts_trx *trx;
- uint8_t next_ts_nr;
+ uint8_t cur_ts_nr;
+ uint32_t done_event;
};
static void om2k_trx_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -1955,10 +2206,8 @@ static void om2k_trx_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data
struct om2k_trx_fsm_priv *otfp = fi->priv;
/* First initialize TRXC */
- osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TRXC,
- TRX_FSM_TIMEOUT, 0);
- om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TRXC_DONE, otfp->trx,
- &otfp->trx->rbs2000.trxc.om2k_mo);
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TRXC, TRX_FSM_TIMEOUT, 0);
+ om2k_mo_fsm_start(&otfp->trx->rbs2000.trxc.om2k_mo);
}
static void om2k_trx_s_wait_trxc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -1966,10 +2215,8 @@ static void om2k_trx_s_wait_trxc(struct osmo_fsm_inst *fi, uint32_t event, void
struct om2k_trx_fsm_priv *otfp = fi->priv;
/* Initialize TX after TRXC */
- osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TX,
- TRX_FSM_TIMEOUT, 0);
- om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TX_DONE, otfp->trx,
- &otfp->trx->rbs2000.tx.om2k_mo);
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TX, TRX_FSM_TIMEOUT, 0);
+ om2k_mo_fsm_start(&otfp->trx->rbs2000.tx.om2k_mo);
}
static void om2k_trx_s_wait_tx(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -1977,10 +2224,8 @@ static void om2k_trx_s_wait_tx(struct osmo_fsm_inst *fi, uint32_t event, void *d
struct om2k_trx_fsm_priv *otfp = fi->priv;
/* Initialize RX after TX */
- osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_RX,
- TRX_FSM_TIMEOUT, 0);
- om2k_mo_fsm_start(fi, OM2K_TRX_EVT_RX_DONE, otfp->trx,
- &otfp->trx->rbs2000.rx.om2k_mo);
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_RX, TRX_FSM_TIMEOUT, 0);
+ om2k_mo_fsm_start(&otfp->trx->rbs2000.rx.om2k_mo);
}
static void om2k_trx_s_wait_rx(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -1989,12 +2234,10 @@ static void om2k_trx_s_wait_rx(struct osmo_fsm_inst *fi, uint32_t event, void *d
struct gsm_bts_trx_ts *ts;
/* Initialize Timeslots after TX */
- osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TS,
- TRX_FSM_TIMEOUT, 0);
- otfp->next_ts_nr = 0;
- ts = &otfp->trx->ts[otfp->next_ts_nr++];
- om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx,
- &ts->rbs2000.om2k_mo);
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TS, TRX_FSM_TIMEOUT, 0);
+ otfp->cur_ts_nr = 0;
+ ts = &otfp->trx->ts[otfp->cur_ts_nr];
+ om2k_mo_fsm_start(&ts->rbs2000.om2k_mo);
}
static void om2k_trx_s_wait_ts(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -2002,64 +2245,140 @@ static void om2k_trx_s_wait_ts(struct osmo_fsm_inst *fi, uint32_t event, void *d
struct om2k_trx_fsm_priv *otfp = fi->priv;
struct gsm_bts_trx_ts *ts;
- if (otfp->next_ts_nr < 8) {
+ /* next ? */
+ if (++otfp->cur_ts_nr < 8) {
/* iterate to the next timeslot */
- ts = &otfp->trx->ts[otfp->next_ts_nr++];
- om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx,
- &ts->rbs2000.om2k_mo);
+ ts = &otfp->trx->ts[otfp->cur_ts_nr];
+ om2k_mo_fsm_start(&ts->rbs2000.om2k_mo);
} else {
/* only after all 8 TS */
- osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_DONE, 0, 0);
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_SEND_SI, 0, 0);
}
}
+static void om2k_trx_s_send_si(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct om2k_trx_fsm_priv *otfp = fi->priv;
+
+ if (gsm_bts_trx_set_system_infos(otfp->trx) == 0)
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_DONE, 0, 0);
+ else
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_ERROR, 0, 0);
+}
+
static void om2k_trx_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct om2k_trx_fsm_priv *otfp = fi->priv;
- gsm_bts_trx_set_system_infos(otfp->trx);
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ struct nm_statechg_signal_data nsd;
+ struct nm_statechg_signal_data nsd_bb_transc;
+ struct gsm_bts_trx *trx = otfp->trx;
+ unsigned int i;
+
+ memset(&nsd, 0, sizeof(nsd));
+
+ nsd.bts = trx->bts;
+ nsd.obj = trx;
+ nsd.old_state = trx->mo.nm_state;
+ nsd.new_state = trx->mo.nm_state;
+ nsd.om2k_mo = &trx->rbs2000.trxc.om2k_mo.addr;
+
+ /* See e1_config:bts_isdn_sign_link() / OS#4914 */
+ nsd.new_state.administrative = NM_STATE_UNLOCKED;
+ trx->mo.nm_state.administrative = nsd.new_state.administrative;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd);
+
+ /* The OM2000 -> 12.21 mapping we do doesn't have separate bb_transc MO,
+ * for compatibility reasons we pretend to have it anyway and mirror the
+ * mo state of the TRXC on it. */
+ memset(&nsd_bb_transc, 0, sizeof(nsd));
+ nsd_bb_transc.bts = trx->bts;
+ nsd_bb_transc.obj = &trx->bb_transc;
+ nsd_bb_transc.old_state = trx->bb_transc.mo.nm_state;
+ nsd_bb_transc.new_state = trx->bb_transc.mo.nm_state;
+ nsd_bb_transc.om2k_mo = &trx->rbs2000.trxc.om2k_mo.addr;
+ nsd_bb_transc.new_state.administrative = NM_STATE_UNLOCKED;
+ trx->bb_transc.mo.nm_state.administrative = nsd_bb_transc.new_state.administrative;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd_bb_transc);
+
+ abis_om2000_fsm_transc_becomes_enabled(trx);
+
+ if (fi->proc.parent)
+ osmo_fsm_inst_dispatch(fi->proc.parent, otfp->done_event, NULL);
+
+ /* Notify the timeslot FSM that all TRX initialization steps are done. */
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+ osmo_fsm_inst_dispatch(trx->ts[i].fi, TS_EV_OML_READY, NULL);
+}
+
+static void om2k_trx_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_trx_fsm_priv *otfp = fi->priv;
+
+ switch (event) {
+ case OM2K_TRX_EVT_RESET:
+ abis_om2000_fsm_transc_becomes_disabled(otfp->trx);
+ osmo_fsm_inst_broadcast_children(fi, event, data);
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_INIT, 0, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
}
static const struct osmo_fsm_state om2k_trx_states[] = {
[OM2K_TRX_S_INIT] = {
.in_event_mask = S(OM2K_TRX_EVT_START),
- .out_state_mask = S(OM2K_TRX_S_WAIT_TRXC),
+ .out_state_mask = S(OM2K_TRX_S_WAIT_TRXC) |
+ S(OM2K_TRX_S_INIT),
.name = "INIT",
.action = om2k_trx_s_init,
},
[OM2K_TRX_S_WAIT_TRXC] = {
.in_event_mask = S(OM2K_TRX_EVT_TRXC_DONE),
.out_state_mask = S(OM2K_TRX_S_ERROR) |
- S(OM2K_TRX_S_WAIT_TX),
+ S(OM2K_TRX_S_WAIT_TX) |
+ S(OM2K_TRX_S_INIT),
.name = "WAIT-TRXC",
.action = om2k_trx_s_wait_trxc,
},
[OM2K_TRX_S_WAIT_TX] = {
.in_event_mask = S(OM2K_TRX_EVT_TX_DONE),
.out_state_mask = S(OM2K_TRX_S_ERROR) |
- S(OM2K_TRX_S_WAIT_RX),
+ S(OM2K_TRX_S_WAIT_RX) |
+ S(OM2K_TRX_S_INIT),
.name = "WAIT-TX",
.action = om2k_trx_s_wait_tx,
},
[OM2K_TRX_S_WAIT_RX] = {
.in_event_mask = S(OM2K_TRX_EVT_RX_DONE),
.out_state_mask = S(OM2K_TRX_S_ERROR) |
- S(OM2K_TRX_S_WAIT_TS),
+ S(OM2K_TRX_S_WAIT_TS) |
+ S(OM2K_TRX_S_INIT),
.name = "WAIT-RX",
.action = om2k_trx_s_wait_rx,
},
[OM2K_TRX_S_WAIT_TS] = {
.in_event_mask = S(OM2K_TRX_EVT_TS_DONE),
.out_state_mask = S(OM2K_TRX_S_ERROR) |
- S(OM2K_TRX_S_DONE),
+ S(OM2K_TRX_S_SEND_SI) |
+ S(OM2K_TRX_S_INIT),
.name = "WAIT-TS",
.action = om2k_trx_s_wait_ts,
},
+ [OM2K_TRX_S_SEND_SI] = {
+ .out_state_mask = S(OM2K_TRX_S_ERROR) |
+ S(OM2K_TRX_S_DONE) |
+ S(OM2K_TRX_S_INIT),
+ .name = "SEND-SI",
+ .onenter = om2k_trx_s_send_si,
+ },
[OM2K_TRX_S_DONE] = {
+ .out_state_mask = S(OM2K_TRX_S_INIT),
.name = "DONE",
.onenter = om2k_trx_s_done_onenter,
},
[OM2K_TRX_S_ERROR] = {
+ .out_state_mask = S(OM2K_TRX_S_INIT),
.name = "ERROR",
},
};
@@ -2075,71 +2394,94 @@ static struct osmo_fsm om2k_trx_fsm = {
.states = om2k_trx_states,
.num_states = ARRAY_SIZE(om2k_trx_states),
.log_subsys = DNM,
+ .allstate_event_mask = S(OM2K_TRX_EVT_RESET),
+ .allstate_action = om2k_trx_allstate,
.event_names = om2k_trx_events,
.timer_cb = om2k_trx_timer_cb,
};
-struct osmo_fsm_inst *om2k_trx_fsm_start(struct osmo_fsm_inst *parent,
- struct gsm_bts_trx *trx,
- uint32_t term_event)
+static struct osmo_fsm_inst *om2k_trx_fsm_alloc(struct osmo_fsm_inst *parent,
+ struct gsm_bts_trx *trx, uint32_t done_event)
{
struct osmo_fsm_inst *fi;
struct om2k_trx_fsm_priv *otfp;
char idbuf[32];
- snprintf(idbuf, sizeof(idbuf), "%u/%u", trx->bts->nr, trx->nr);
+ OSMO_ASSERT(!trx->rbs2000.trx_fi);
- fi = osmo_fsm_inst_alloc_child_id(&om2k_trx_fsm, parent, term_event,
- idbuf);
+ snprintf(idbuf, sizeof(idbuf), "%u-%u", trx->bts->nr, trx->nr);
+
+ fi = osmo_fsm_inst_alloc_child_id(&om2k_trx_fsm, parent, OM2K_MO_EVT_CHILD_TERM, idbuf);
if (!fi)
return NULL;
otfp = talloc_zero(fi, struct om2k_trx_fsm_priv);
otfp->trx = trx;
+ otfp->done_event = done_event;
fi->priv = otfp;
- osmo_fsm_inst_dispatch(fi, OM2K_TRX_EVT_START, NULL);
-
return fi;
}
+void om2k_trx_fsm_start(struct gsm_bts_trx *trx)
+{
+ struct osmo_fsm_inst *bts_fi = trx->bts->rbs2000.bts_fi;
+ OSMO_ASSERT(trx->rbs2000.trx_fi);
+
+ /* suppress if BTS is not yet brought up */
+ if (bts_fi->state == OM2K_BTS_S_DONE || bts_fi->state == OM2K_BTS_S_WAIT_TRX)
+ return;
+
+ osmo_fsm_inst_dispatch(trx->rbs2000.trx_fi, OM2K_TRX_EVT_START, NULL);
+}
+
+void om2k_trx_fsm_reset(struct gsm_bts_trx *trx)
+{
+ struct osmo_fsm_inst *bts_fi = trx->bts->rbs2000.bts_fi;
+ OSMO_ASSERT(trx->rbs2000.trx_fi);
+ OSMO_ASSERT(trx->rbs2000.trx_fi);
+
+ /* suppress if BTS is not yet brought up */
+ if (bts_fi->state == OM2K_BTS_S_DONE || bts_fi->state == OM2K_BTS_S_WAIT_TRX)
+ return;
+
+ osmo_fsm_inst_dispatch(trx->rbs2000.trx_fi, OM2K_TRX_EVT_RESET, NULL);
+}
/***********************************************************************
* OM2000 BTS Finite State Machine, initializes CF and all siblings
***********************************************************************/
enum om2k_bts_event {
- OM2K_BTS_EVT_START,
+ OM2K_BTS_EVT_RESET = OM2K_MO_EVT_RESET,
+ OM2K_BTS_EVT_START = OM2K_MO_EVT_START,
+ OM2K_BTS_EVT_CHILD_TERM = OM2K_MO_EVT_CHILD_TERM,
OM2K_BTS_EVT_CF_DONE,
OM2K_BTS_EVT_IS_DONE,
OM2K_BTS_EVT_CON_DONE,
OM2K_BTS_EVT_TF_DONE,
+ OM2K_BTS_EVT_MCTR_DONE,
+ OM2K_BTS_EVT_TRX_LAPD_UP,
OM2K_BTS_EVT_TRX_DONE,
+ OM2K_BTS_EVT_TRX_TERM,
OM2K_BTS_EVT_STOP,
};
static const struct value_string om2k_bts_events[] = {
+ { OM2K_BTS_EVT_RESET, "RESET" },
{ OM2K_BTS_EVT_START, "START" },
+ { OM2K_BTS_EVT_CHILD_TERM, "CHILD-TERM" },
{ OM2K_BTS_EVT_CF_DONE, "CF-DONE" },
{ OM2K_BTS_EVT_IS_DONE, "IS-DONE" },
{ OM2K_BTS_EVT_CON_DONE, "CON-DONE" },
{ OM2K_BTS_EVT_TF_DONE, "TF-DONE" },
+ { OM2K_BTS_EVT_MCTR_DONE, "MCTR-DONE" },
+ { OM2K_BTS_EVT_TRX_LAPD_UP, "TRX-LAPD-UP" },
{ OM2K_BTS_EVT_TRX_DONE, "TRX-DONE" },
{ OM2K_BTS_EVT_STOP, "STOP" },
{ 0, NULL }
};
-enum om2k_bts_state {
- OM2K_BTS_S_INIT,
- OM2K_BTS_S_WAIT_CF,
- OM2K_BTS_S_WAIT_IS,
- OM2K_BTS_S_WAIT_CON,
- OM2K_BTS_S_WAIT_TF,
- OM2K_BTS_S_WAIT_TRX,
- OM2K_BTS_S_DONE,
- OM2K_BTS_S_ERROR,
-};
-
struct om2k_bts_fsm_priv {
struct gsm_bts *bts;
uint8_t next_trx_nr;
@@ -2151,10 +2493,8 @@ static void om2k_bts_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data
struct gsm_bts *bts = obfp->bts;
OSMO_ASSERT(event == OM2K_BTS_EVT_START);
- osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CF,
- BTS_FSM_TIMEOUT, 0);
- om2k_mo_fsm_start(fi, OM2K_BTS_EVT_CF_DONE, bts->c0,
- &bts->rbs2000.cf.om2k_mo);
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CF, BTS_FSM_TIMEOUT, 0);
+ om2k_mo_fsm_start(&bts->rbs2000.cf.om2k_mo);
}
static void om2k_bts_s_wait_cf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -2165,8 +2505,7 @@ static void om2k_bts_s_wait_cf(struct osmo_fsm_inst *fi, uint32_t event, void *d
OSMO_ASSERT(event == OM2K_BTS_EVT_CF_DONE);
/* TF can take a long time to initialize, wait for 10min */
osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TF, 600, 0);
- om2k_mo_fsm_start(fi, OM2K_BTS_EVT_TF_DONE, bts->c0,
- &bts->rbs2000.tf.om2k_mo);
+ om2k_mo_fsm_start(&bts->rbs2000.tf.om2k_mo);
}
static void om2k_bts_s_wait_tf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -2176,10 +2515,14 @@ static void om2k_bts_s_wait_tf(struct osmo_fsm_inst *fi, uint32_t event, void *d
OSMO_ASSERT(event == OM2K_BTS_EVT_TF_DONE);
- osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CON,
- BTS_FSM_TIMEOUT, 0);
- om2k_mo_fsm_start(fi, OM2K_BTS_EVT_CON_DONE, bts->c0,
- &bts->rbs2000.con.om2k_mo);
+ if (!llist_count(&bts->rbs2000.con.conn_groups)) {
+ /* skip CON object if we have no configuration for it */
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_IS, BTS_FSM_TIMEOUT, 0);
+ om2k_mo_fsm_start(&bts->rbs2000.is.om2k_mo);
+ } else {
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CON, BTS_FSM_TIMEOUT, 0);
+ om2k_mo_fsm_start(&bts->rbs2000.con.om2k_mo);
+ }
}
static void om2k_bts_s_wait_con(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -2189,24 +2532,44 @@ static void om2k_bts_s_wait_con(struct osmo_fsm_inst *fi, uint32_t event, void *
OSMO_ASSERT(event == OM2K_BTS_EVT_CON_DONE);
- osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_IS,
- BTS_FSM_TIMEOUT, 0);
- om2k_mo_fsm_start(fi, OM2K_BTS_EVT_IS_DONE, bts->c0,
- &bts->rbs2000.is.om2k_mo);
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_IS, BTS_FSM_TIMEOUT, 0);
+ om2k_mo_fsm_start(&bts->rbs2000.is.om2k_mo);
}
static void om2k_bts_s_wait_is(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct om2k_bts_fsm_priv *obfp = fi->priv;
- struct gsm_bts_trx *trx;
+ struct gsm_bts *bts = obfp->bts;
OSMO_ASSERT(event == OM2K_BTS_EVT_IS_DONE);
- osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX,
- BTS_FSM_TIMEOUT, 0);
+ /* If we're running OML >= G12R13, start MCTR, else skip directly to TRX */
+ if (bts->rbs2000.om2k_version[0].active >= 0x0c0d) {
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_MCTR, BTS_FSM_TIMEOUT, 0);
+ om2k_mo_fsm_start(&bts->rbs2000.mctr.om2k_mo);
+ } else {
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX_LAPD, TRX_LAPD_TIMEOUT, 0);
+ }
+}
+
+static void om2k_bts_s_wait_mctr(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ OSMO_ASSERT(event == OM2K_BTS_EVT_MCTR_DONE);
+
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX_LAPD, TRX_LAPD_TIMEOUT, 0);
+}
+
+static void om2k_bts_s_wait_trx_lapd(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_bts_fsm_priv *obfp = fi->priv;
+ struct gsm_bts_trx *trx;
+
+ OSMO_ASSERT(event == OM2K_BTS_EVT_TRX_LAPD_UP);
+
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX, BTS_FSM_TIMEOUT, 0);
obfp->next_trx_nr = 0;
trx = gsm_bts_trx_num(obfp->bts, obfp->next_trx_nr++);
- om2k_trx_fsm_start(fi, trx, OM2K_BTS_EVT_TRX_DONE);
+ om2k_trx_fsm_start(trx);
}
static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -2218,7 +2581,7 @@ static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void *
if (obfp->next_trx_nr < obfp->bts->num_trx) {
struct gsm_bts_trx *trx;
trx = gsm_bts_trx_num(obfp->bts, obfp->next_trx_nr++);
- om2k_trx_fsm_start(fi, trx, OM2K_BTS_EVT_TRX_DONE);
+ om2k_trx_fsm_start(trx);
} else {
osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_DONE, 0, 0);
}
@@ -2226,63 +2589,106 @@ static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void *
static void om2k_bts_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static void om2k_bts_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case OM2K_BTS_EVT_RESET:
+ osmo_fsm_inst_broadcast_children(fi, event, data);
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_INIT, 0, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
}
static const struct osmo_fsm_state om2k_bts_states[] = {
[OM2K_BTS_S_INIT] = {
.in_event_mask = S(OM2K_BTS_EVT_START),
- .out_state_mask = S(OM2K_BTS_S_WAIT_CF),
+ .out_state_mask = S(OM2K_BTS_S_WAIT_CF) |
+ S(OM2K_BTS_S_INIT),
.name = "INIT",
.action = om2k_bts_s_init,
},
[OM2K_BTS_S_WAIT_CF] = {
.in_event_mask = S(OM2K_BTS_EVT_CF_DONE),
.out_state_mask = S(OM2K_BTS_S_ERROR) |
- S(OM2K_BTS_S_WAIT_TF),
+ S(OM2K_BTS_S_WAIT_TF) |
+ S(OM2K_BTS_S_INIT),
.name = "WAIT-CF",
.action = om2k_bts_s_wait_cf,
},
[OM2K_BTS_S_WAIT_TF] = {
.in_event_mask = S(OM2K_BTS_EVT_TF_DONE),
.out_state_mask = S(OM2K_BTS_S_ERROR) |
- S(OM2K_BTS_S_WAIT_CON),
+ S(OM2K_BTS_S_WAIT_CON) |
+ S(OM2K_BTS_S_WAIT_IS) |
+ S(OM2K_BTS_S_INIT),
.name = "WAIT-TF",
.action = om2k_bts_s_wait_tf,
},
[OM2K_BTS_S_WAIT_CON] = {
.in_event_mask = S(OM2K_BTS_EVT_CON_DONE),
.out_state_mask = S(OM2K_BTS_S_ERROR) |
- S(OM2K_BTS_S_WAIT_IS),
+ S(OM2K_BTS_S_WAIT_IS) |
+ S(OM2K_BTS_S_INIT),
.name = "WAIT-CON",
.action = om2k_bts_s_wait_con,
},
[OM2K_BTS_S_WAIT_IS] = {
.in_event_mask = S(OM2K_BTS_EVT_IS_DONE),
.out_state_mask = S(OM2K_BTS_S_ERROR) |
- S(OM2K_BTS_S_WAIT_TRX),
+ S(OM2K_BTS_S_WAIT_MCTR) |
+ S(OM2K_BTS_S_WAIT_TRX_LAPD) |
+ S(OM2K_BTS_S_INIT),
.name = "WAIT-IS",
.action = om2k_bts_s_wait_is,
},
+ [OM2K_BTS_S_WAIT_MCTR] = {
+ .in_event_mask = S(OM2K_BTS_EVT_MCTR_DONE),
+ .out_state_mask = S(OM2K_BTS_S_ERROR) |
+ S(OM2K_BTS_S_WAIT_TRX_LAPD) |
+ S(OM2K_BTS_S_INIT),
+ .name = "WAIT-MCTR",
+ .action = om2k_bts_s_wait_mctr,
+ },
+ [OM2K_BTS_S_WAIT_TRX_LAPD] = {
+ .in_event_mask = S(OM2K_BTS_EVT_TRX_LAPD_UP),
+ .out_state_mask = S(OM2K_BTS_S_WAIT_TRX) |
+ S(OM2K_BTS_S_INIT),
+ .name = "WAIT-TRX-LAPD",
+ .action = om2k_bts_s_wait_trx_lapd,
+ },
[OM2K_BTS_S_WAIT_TRX] = {
.in_event_mask = S(OM2K_BTS_EVT_TRX_DONE),
.out_state_mask = S(OM2K_BTS_S_ERROR) |
- S(OM2K_BTS_S_DONE),
+ S(OM2K_BTS_S_DONE) |
+ S(OM2K_BTS_S_INIT),
.name = "WAIT-TRX",
.action = om2k_bts_s_wait_trx,
},
[OM2K_BTS_S_DONE] = {
+ .out_state_mask = S(OM2K_BTS_S_INIT),
.name = "DONE",
.onenter = om2k_bts_s_done_onenter,
},
[OM2K_BTS_S_ERROR] = {
+ .out_state_mask = S(OM2K_BTS_S_INIT),
.name = "ERROR",
},
};
static int om2k_bts_timer_cb(struct osmo_fsm_inst *fi)
{
- osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_ERROR, 0, 0);
+ switch (fi->state) {
+ case OM2K_BTS_S_WAIT_TRX_LAPD:
+ osmo_fsm_inst_dispatch(fi, OM2K_BTS_EVT_TRX_LAPD_UP, NULL);
+ break;
+ default:
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_ERROR, 0, 0);
+ break;
+ }
return 0;
}
@@ -2291,31 +2697,44 @@ static struct osmo_fsm om2k_bts_fsm = {
.states = om2k_bts_states,
.num_states = ARRAY_SIZE(om2k_bts_states),
.log_subsys = DNM,
+ .allstate_event_mask = S(OM2K_BTS_EVT_RESET),
+ .allstate_action = om2k_bts_allstate,
.event_names = om2k_bts_events,
.timer_cb = om2k_bts_timer_cb,
};
-struct osmo_fsm_inst *
-om2k_bts_fsm_start(struct gsm_bts *bts)
+static struct osmo_fsm_inst *
+om2k_bts_fsm_alloc(struct gsm_bts *bts)
{
struct osmo_fsm_inst *fi;
struct om2k_bts_fsm_priv *obfp;
char idbuf[16];
+ OSMO_ASSERT(!bts->rbs2000.bts_fi);
+
snprintf(idbuf, sizeof(idbuf), "%u", bts->nr);
- fi = osmo_fsm_inst_alloc(&om2k_bts_fsm, bts, NULL,
- LOGL_DEBUG, idbuf);
+ fi = osmo_fsm_inst_alloc(&om2k_bts_fsm, bts, NULL, LOGL_DEBUG, idbuf);
if (!fi)
return NULL;
+
fi->priv = obfp = talloc_zero(fi, struct om2k_bts_fsm_priv);
obfp->bts = bts;
- osmo_fsm_inst_dispatch(fi, OM2K_BTS_EVT_START, NULL);
-
return fi;
}
+void om2k_bts_fsm_start(struct gsm_bts *bts)
+{
+ OSMO_ASSERT(bts->rbs2000.bts_fi);
+ osmo_fsm_inst_dispatch(bts->rbs2000.bts_fi, OM2K_BTS_EVT_START, NULL);
+}
+
+void om2k_bts_fsm_reset(struct gsm_bts *bts)
+{
+ OSMO_ASSERT(bts->rbs2000.bts_fi);
+ osmo_fsm_inst_dispatch(bts->rbs2000.bts_fi, OM2K_BTS_EVT_RESET, NULL);
+}
/***********************************************************************
* OM2000 Negotiation
@@ -2332,15 +2751,15 @@ static int abis_om2k_tx_negot_req_ack(struct gsm_bts *bts, const struct abis_om2
msgb_tlv_put(msg, OM2K_DEI_NEGOT_REC2, len, data);
- DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+ DEBUGP(DNM, "Tx MO=%s %s\n", abis_om2k_mo_name(mo),
get_value_string(om2k_msgcode_vals, OM2K_MSGT_NEGOT_REQ_ACK));
return abis_om2k_sendmsg(bts, msg);
}
struct iwd_version {
- uint8_t gen_char[3+1];
- uint8_t rev_char[3+1];
+ char gen_char[3+1];
+ char rev_char[3+1];
};
struct iwd_type {
@@ -2351,11 +2770,13 @@ struct iwd_type {
static int om2k_rx_negot_req(struct msgb *msg)
{
struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
struct abis_om2k_hdr *o2h = msgb_l2(msg);
struct iwd_type iwd_types[16];
uint8_t num_iwd_types = o2h->data[2];
uint8_t *cur = o2h->data+3;
- unsigned int i, v;
+ unsigned int i;
+ int v;
uint8_t out_buf[1024];
uint8_t *out_cur = out_buf+1;
@@ -2386,25 +2807,59 @@ static int om2k_rx_negot_req(struct msgb *msg)
/* Select the last version for each IWD type */
for (i = 0; i < ARRAY_SIZE(iwd_types); i++) {
struct iwd_type *type = &iwd_types[i];
- struct iwd_version *last_v;
+ struct iwd_version *sel_v = NULL, *alt_v = NULL;
+ uint16_t sel_ver, alt_ver = 0;
+ int gen, rev;
if (type->num_vers == 0)
continue;
out_num_types++;
- last_v = &type->v[type->num_vers-1];
+ for (v = type->num_vers-1; v >= 0; v--) {
+ if ((sscanf(type->v[v].gen_char, "G%2d", &gen) != 1) ||
+ (sscanf(type->v[v].rev_char, "R%2d", &rev) != 1))
+ continue;
+ sel_ver = (gen << 8) | rev;
+
+ if (!alt_v) {
+ alt_ver = sel_ver;
+ alt_v = &type->v[v];
+ }
+
+ if ((bts->rbs2000.om2k_version[i].limit != 0) &&
+ (bts->rbs2000.om2k_version[i].limit < sel_ver))
+ continue;
+
+ sel_v = &type->v[v];
+ break;
+ }
+ if (!sel_v) {
+ if (!alt_v) {
+ LOGP(DNM, LOGL_ERROR, "Couldn't find valid version for IWD Type %u."
+ "Skipping IWD ... this will most likely fail\n", i);
+ continue;
+ } else {
+ sel_v = alt_v;
+ sel_ver = alt_ver;
+ LOGP(DNM, LOGL_ERROR, "Couldn't find suitable version for IWD Type %u."
+ "Fallback to Gen %s Rev %s\n", i,
+ sel_v->gen_char, sel_v->rev_char);
+ }
+ }
+
+ bts->rbs2000.om2k_version[i].active = sel_ver;
*out_cur++ = i;
- memcpy(out_cur, last_v->gen_char, 3);
+ memcpy(out_cur, sel_v->gen_char, 3);
out_cur += 3;
- memcpy(out_cur, last_v->rev_char, 3);
+ memcpy(out_cur, sel_v->rev_char, 3);
out_cur += 3;
}
out_buf[0] = out_num_types;
- return abis_om2k_tx_negot_req_ack(sign_link->trx->bts, &o2h->mo, out_buf, out_cur - out_buf);
+ return abis_om2k_tx_negot_req_ack(bts, &o2h->mo, out_buf, out_cur - out_buf);
}
@@ -2418,18 +2873,16 @@ static int om2k_rx_nack(struct msgb *msg)
uint16_t msg_type = ntohs(o2h->msg_type);
struct tlv_parsed tp;
- LOGP(DNM, LOGL_ERROR, "Rx MO=%s %s", om2k_mo_name(&o2h->mo),
+ LOGP(DNM, LOGL_ERROR, "Rx MO=%s %s", abis_om2k_mo_name(&o2h->mo),
get_value_string(om2k_msgcode_vals, msg_type));
abis_om2k_msg_tlv_parse(&tp, o2h);
if (TLVP_PRESENT(&tp, OM2K_DEI_REASON_CODE))
- LOGPC(DNM, LOGL_ERROR, ", Reason 0x%02x",
- *TLVP_VAL(&tp, OM2K_DEI_REASON_CODE));
+ LOGPC(DNM, LOGL_ERROR, ", Reason 0x%02x", *TLVP_VAL(&tp, OM2K_DEI_REASON_CODE));
if (TLVP_PRESENT(&tp, OM2K_DEI_RESULT_CODE))
LOGPC(DNM, LOGL_ERROR, ", Result %s",
- get_value_string(om2k_result_strings,
- *TLVP_VAL(&tp, OM2K_DEI_RESULT_CODE)));
+ get_value_string(om2k_result_strings, *TLVP_VAL(&tp, OM2K_DEI_RESULT_CODE)));
LOGPC(DNM, LOGL_ERROR, "\n");
return 0;
@@ -2443,8 +2896,7 @@ static int process_mo_state(struct gsm_bts *bts, struct om2k_decoded_msg *odm)
return -EIO;
mo_state = *TLVP_VAL(&odm->tp, OM2K_DEI_MO_STATE);
- LOGP(DNM, LOGL_DEBUG, "Rx MO=%s %s, MO State: %s\n",
- om2k_mo_name(&odm->o2h.mo),
+ LOGP(DNM, LOGL_DEBUG, "Rx MO=%s %s, MO State: %s\n", abis_om2k_mo_name(&odm->o2h.mo),
get_value_string(om2k_msgcode_vals, odm->msg_type),
get_value_string(om2k_mostate_vals, mo_state));
@@ -2452,10 +2904,8 @@ static int process_mo_state(struct gsm_bts *bts, struct om2k_decoded_msg *odm)
* not yield an enabled mo-state */
if (odm->msg_type == OM2K_MSGT_ENABLE_RES
&& mo_state != OM2K_MO_S_ENABLED) {
- LOGP(DNM, LOGL_ERROR,
- "Rx MO=%s %s Failed to enable MO State!\n",
- om2k_mo_name(&odm->o2h.mo),
- get_value_string(om2k_msgcode_vals, odm->msg_type));
+ LOGP(DNM, LOGL_ERROR, "Rx MO=%s %s Failed to enable MO State!\n",
+ abis_om2k_mo_name(&odm->o2h.mo), get_value_string(om2k_msgcode_vals, odm->msg_type));
}
update_mo_state(bts, &odm->o2h.mo, mo_state);
@@ -2495,7 +2945,7 @@ static bool display_fault_bits(const uint8_t *vect, uint16_t len,
}
sprintf(string + strlen(string), ")\n");
- DEBUGP(DNM, "Rx MO=%s %s", om2k_mo_name(mo), string);
+ DEBUGP(DNM, "Rx MO=%s %s", abis_om2k_mo_name(mo), string);
return true;
}
@@ -2525,8 +2975,7 @@ static void display_fault_maps(const uint8_t *src, unsigned int src_len,
src++;
src_len--;
if (msg_code != OM2K_MSGT_FAULT_REP) {
- LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault report: invalid message code!\n",
- om2k_mo_name(mo));
+ LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault report: invalid message code!\n", abis_om2k_mo_name(mo));
return;
}
@@ -2539,22 +2988,19 @@ static void display_fault_maps(const uint8_t *src, unsigned int src_len,
/* Bail if an the maximum number of TLV fields
* have been parsed */
- if (tlv_count >= 11) {
- LOGP(DNM, LOGL_ERROR,
- "Rx MO=%s Fault Report: too many tlv elements!\n",
- om2k_mo_name(mo));
+ if (tlv_count >= 20) {
+ LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault Report: too many tlv elements!\n",
+ abis_om2k_mo_name(mo));
return;
}
/* Parse TLV field */
- rc = tlv_parse_one(&tag, &tag_len, &val, &om2k_att_tlvdef,
- src + src_pos, src_len - src_pos);
+ rc = tlv_parse_one(&tag, &tag_len, &val, &om2k_att_tlvdef, src + src_pos, src_len - src_pos);
if (rc > 0)
src_pos += rc;
else {
- LOGP(DNM, LOGL_ERROR,
- "Rx MO=%s Fault Report: invalid tlv element!\n",
- om2k_mo_name(mo));
+ LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault Report: invalid tlv element!\n",
+ abis_om2k_mo_name(mo));
return;
}
@@ -2581,8 +3027,7 @@ static void display_fault_maps(const uint8_t *src, unsigned int src_len,
}
if (!faults_present) {
- DEBUGP(DNM, "Rx MO=%s Fault Report: All faults ceased!\n",
- om2k_mo_name(mo));
+ DEBUGP(DNM, "Rx MO=%s Fault Report: All faults ceased!\n", abis_om2k_mo_name(mo));
}
}
@@ -2599,28 +3044,24 @@ int abis_om2k_rcvmsg(struct msgb *msg)
/* Various consistency checks */
if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
- LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
- oh->placement);
+ LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", oh->placement);
if (oh->placement != ABIS_OM_PLACEMENT_FIRST)
return -EINVAL;
}
if (oh->sequence != 0) {
- LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
- oh->sequence);
+ LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", oh->sequence);
return -EINVAL;
}
msg->l3h = (unsigned char *)o2h + sizeof(*o2h);
if (oh->mdisc != ABIS_OM_MDISC_FOM) {
- LOGP(DNM, LOGL_ERROR, "unknown ABIS OM2000 message discriminator 0x%x\n",
- oh->mdisc);
+ LOGP(DNM, LOGL_ERROR, "unknown ABIS OM2000 message discriminator 0x%x\n", oh->mdisc);
return -EINVAL;
}
- DEBUGP(DNM, "Rx MO=%s %s (%s)\n", om2k_mo_name(&o2h->mo),
- get_value_string(om2k_msgcode_vals, msg_type),
- osmo_hexdump(msg->l2h, msgb_l2len(msg)));
+ DEBUGP(DNM, "Rx MO=%s %s (%s)\n", abis_om2k_mo_name(&o2h->mo),
+ get_value_string(om2k_msgcode_vals, msg_type), osmo_hexdump(msg->l2h, msgb_l2len(msg)));
om2k_decode_msg(&odm, msg);
@@ -2629,11 +3070,13 @@ int abis_om2k_rcvmsg(struct msgb *msg)
switch (msg_type) {
case OM2K_MSGT_CAL_TIME_REQ:
rc = abis_om2k_cal_time_resp(bts);
- break;
+ /* we receive this from MOs without FSM (https://osmocom.org/issues/4670) */
+ goto no_mo;
case OM2K_MSGT_FAULT_REP:
display_fault_maps(msg->l2h, msgb_l2len(msg), &o2h->mo);
rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_FAULT_REP_ACK);
- break;
+ /* we receive this from MOs without FSM (https://osmocom.org/issues/4643) */
+ goto no_mo;
case OM2K_MSGT_NEGOT_REQ:
rc = om2k_rx_negot_req(msg);
break;
@@ -2648,6 +3091,9 @@ int abis_om2k_rcvmsg(struct msgb *msg)
case OM2K_MSGT_CON_CONF_RES:
rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CON_CONF_RES_ACK);
break;
+ case OM2K_MSGT_MCTR_CONF_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_MCTR_CONF_RES_ACK);
+ break;
case OM2K_MSGT_TX_CONF_RES:
rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TX_CONF_RES_ACK);
break;
@@ -2669,9 +3115,15 @@ int abis_om2k_rcvmsg(struct msgb *msg)
case OM2K_MSGT_TEST_RES:
rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TEST_RES_ACK);
break;
+ case OM2K_MSGT_CAPA_HW_INFOS_REP:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CAPA_HW_INFOS_REP_ACK);
+ break;
case OM2K_MSGT_CAPA_RES:
rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CAPA_RES_ACK);
break;
+ case OM2K_MSGT_MCTR_STATS_REP:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_MCTR_STATS_REP_ACK);
+ break;
/* ERrors */
case OM2K_MSGT_START_REQ_REJ:
case OM2K_MSGT_CONNECT_REJ:
@@ -2680,6 +3132,7 @@ int abis_om2k_rcvmsg(struct msgb *msg)
case OM2K_MSGT_TEST_REQ_REJ:
case OM2K_MSGT_CON_CONF_REQ_REJ:
case OM2K_MSGT_IS_CONF_REQ_REJ:
+ case OM2K_MSGT_MCTR_CONF_REQ_REJ:
case OM2K_MSGT_TX_CONF_REQ_REJ:
case OM2K_MSGT_RX_CONF_REQ_REJ:
case OM2K_MSGT_TS_CONF_REQ_REJ:
@@ -2695,26 +3148,24 @@ int abis_om2k_rcvmsg(struct msgb *msg)
mo = get_om2k_mo(bts, &o2h->mo);
if (!mo) {
LOGP(DNM, LOGL_ERROR, "Couldn't resolve MO for OM2K msg "
- "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type),
- msgb_hexdump(msg));
- return 0;
+ "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type), msgb_hexdump(msg));
+ goto no_mo;
}
if (!mo->fsm) {
LOGP(DNM, LOGL_ERROR, "MO object should not generate any message. fsm == NULL "
- "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type),
- msgb_hexdump(msg));
- return 0;
+ "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type), msgb_hexdump(msg));
+ goto no_mo;
}
/* Dispatch message to that MO */
om2k_mo_fsm_recvmsg(bts, mo, &odm);
+no_mo:
msgb_free(msg);
return rc;
}
-static void om2k_mo_init(struct om2k_mo *mo, uint8_t class,
- uint8_t bts_nr, uint8_t assoc_so, uint8_t inst)
+static void om2k_mo_init(struct om2k_mo *mo, uint8_t class, uint8_t bts_nr, uint8_t assoc_so, uint8_t inst)
{
mo->addr.class = class;
mo->addr.bts = bts_nr;
@@ -2726,46 +3177,65 @@ static void om2k_mo_init(struct om2k_mo *mo, uint8_t class,
void abis_om2k_trx_init(struct gsm_bts_trx *trx)
{
struct gsm_bts *bts = trx->bts;
+ struct osmo_fsm_inst *trx_fi;
unsigned int i;
OSMO_ASSERT(bts->type == GSM_BTS_TYPE_RBS2000);
- om2k_mo_init(&trx->rbs2000.trxc.om2k_mo, OM2K_MO_CLS_TRXC,
- bts->nr, 255, trx->nr);
- om2k_mo_init(&trx->rbs2000.tx.om2k_mo, OM2K_MO_CLS_TX,
- bts->nr, 255, trx->nr);
- om2k_mo_init(&trx->rbs2000.rx.om2k_mo, OM2K_MO_CLS_RX,
- bts->nr, 255, trx->nr);
+ trx_fi = om2k_trx_fsm_alloc(trx->bts->rbs2000.bts_fi, trx, OM2K_BTS_EVT_TRX_DONE);
+ trx->rbs2000.trx_fi = trx_fi;
+ trx->rbs2000.rx_diversity = OM2K_RX_DIVERSITY_A;
+
+ om2k_mo_init(&trx->rbs2000.trxc.om2k_mo, OM2K_MO_CLS_TRXC, bts->nr, 255, trx->nr);
+ om2k_mo_fsm_alloc(trx_fi, OM2K_TRX_EVT_TRXC_DONE, trx, &trx->rbs2000.trxc.om2k_mo);
+
+ om2k_mo_init(&trx->rbs2000.tx.om2k_mo, OM2K_MO_CLS_TX, bts->nr, 255, trx->nr);
+ om2k_mo_fsm_alloc(trx_fi, OM2K_TRX_EVT_TX_DONE, trx, &trx->rbs2000.tx.om2k_mo);
+
+ om2k_mo_init(&trx->rbs2000.rx.om2k_mo, OM2K_MO_CLS_RX, bts->nr, 255, trx->nr);
+ om2k_mo_fsm_alloc(trx_fi, OM2K_TRX_EVT_RX_DONE, trx, &trx->rbs2000.rx.om2k_mo);
for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
struct gsm_bts_trx_ts *ts = &trx->ts[i];
- om2k_mo_init(&ts->rbs2000.om2k_mo, OM2K_MO_CLS_TS,
- bts->nr, trx->nr, i);
+ om2k_mo_init(&ts->rbs2000.om2k_mo, OM2K_MO_CLS_TS, bts->nr, trx->nr, i);
+ om2k_mo_fsm_alloc(trx_fi, OM2K_TRX_EVT_TS_DONE, trx, &ts->rbs2000.om2k_mo);
OSMO_ASSERT(ts->fi);
- osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL);
}
}
/* initialize the OM2K_MO members of gsm_bts */
void abis_om2k_bts_init(struct gsm_bts *bts)
{
+ struct osmo_fsm_inst *bts_fi;
+
OSMO_ASSERT(bts->type == GSM_BTS_TYPE_RBS2000);
- om2k_mo_init(&bts->rbs2000.cf.om2k_mo, OM2K_MO_CLS_CF,
- bts->nr, 0xFF, 0);
- om2k_mo_init(&bts->rbs2000.is.om2k_mo, OM2K_MO_CLS_IS,
- bts->nr, 0xFF, 0);
- om2k_mo_init(&bts->rbs2000.con.om2k_mo, OM2K_MO_CLS_CON,
- bts->nr, 0xFF, 0);
- om2k_mo_init(&bts->rbs2000.dp.om2k_mo, OM2K_MO_CLS_DP,
- bts->nr, 0xFF, 0);
- om2k_mo_init(&bts->rbs2000.tf.om2k_mo, OM2K_MO_CLS_TF,
- bts->nr, 0xFF, 0);
+ bts_fi = om2k_bts_fsm_alloc(bts);
+ bts->rbs2000.bts_fi = bts_fi;
+ bts->rbs2000.sync_src = OM2K_SYNC_SRC_INTERNAL;
+
+ om2k_mo_init(&bts->rbs2000.cf.om2k_mo, OM2K_MO_CLS_CF, bts->nr, 0xFF, 0);
+ om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_CF_DONE, bts->c0, &bts->rbs2000.cf.om2k_mo);
+
+ om2k_mo_init(&bts->rbs2000.is.om2k_mo, OM2K_MO_CLS_IS, bts->nr, 0xFF, 0);
+ om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_IS_DONE, bts->c0, &bts->rbs2000.is.om2k_mo);
+
+ om2k_mo_init(&bts->rbs2000.con.om2k_mo, OM2K_MO_CLS_CON, bts->nr, 0xFF, 0);
+ om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_CON_DONE, bts->c0, &bts->rbs2000.con.om2k_mo);
+
+ om2k_mo_init(&bts->rbs2000.dp.om2k_mo, OM2K_MO_CLS_DP, bts->nr, 0xFF, 0);
+
+ om2k_mo_init(&bts->rbs2000.tf.om2k_mo, OM2K_MO_CLS_TF, bts->nr, 0xFF, 0);
+ om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_TF_DONE, bts->c0, &bts->rbs2000.tf.om2k_mo);
+
+ om2k_mo_init(&bts->rbs2000.mctr.om2k_mo, OM2K_MO_CLS_MCTR, bts->nr, 0xFF, 0);
+ om2k_mo_fsm_alloc(bts_fi, OM2K_BTS_EVT_MCTR_DONE, bts->c0, &bts->rbs2000.mctr.om2k_mo);
+ // FIXME: There can be multiple MCTRs ...
}
static __attribute__((constructor)) void abis_om2k_init(void)
{
- osmo_fsm_register(&om2k_mo_fsm);
- osmo_fsm_register(&om2k_bts_fsm);
- osmo_fsm_register(&om2k_trx_fsm);
+ OSMO_ASSERT(osmo_fsm_register(&om2k_mo_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&om2k_bts_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&om2k_trx_fsm) == 0);
}
diff --git a/src/osmo-bsc/abis_om2000_vty.c b/src/osmo-bsc/abis_om2000_vty.c
index faf39c106..76048071a 100644
--- a/src/osmo-bsc/abis_om2000_vty.c
+++ b/src/osmo-bsc/abis_om2000_vty.c
@@ -33,6 +33,7 @@
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/signal.h>
#include <osmocom/bsc/abis_om2000.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/bsc/vty.h>
#include <osmocom/vty/vty.h>
@@ -40,6 +41,8 @@
#include <osmocom/vty/logging.h>
#include <osmocom/vty/telnet_interface.h>
+#define X(x) (1 << x)
+
static struct cmd_node om2k_node = {
OM2K_NODE,
"%s(om2k)# ",
@@ -60,22 +63,20 @@ struct oml_node_state {
struct con_group *cg;
};
-static int dummy_config_write(struct vty *v)
-{
- return CMD_SUCCESS;
-}
-
/* FIXME: auto-generate those strings from the value_string lists */
-#define OM2K_OBJCLASS_VTY "(trxc|ts|tf|is|con|dp|cf|tx|rx)"
+#define OM2K_OBJCLASS_VTY "(trxc|tg|ts|tf|is|con|dp|mctr|cf|tx|rx)"
#define OM2K_OBJCLASS_VTY_HELP "TRX Controller\n" \
+ "Trunk Group\n" \
"Timeslot\n" \
"Timing Function\n" \
"Interface Switch\n" \
"Abis Concentrator\n" \
"Digital Path\n" \
+ "Multi Carrier Transceiver\n" \
"Central Function\n" \
"Transmitter\n" \
"Receiver\n"
+#define OM2K_VTY_HELP "Configure OM2K specific parameters\n"
DEFUN(om2k_class_inst, om2k_class_inst_cmd,
"bts <0-255> om2000 class " OM2K_OBJCLASS_VTY
@@ -253,6 +254,34 @@ DEFUN(om2k_cap_req, om2k_cap_req_cmd,
return CMD_SUCCESS;
}
+DEFUN(om2k_arb, om2k_arb_cmd,
+ "arbitrary <0-65535> [HEXSTRING]",
+ "Send arbitrary OM2k message\n"
+ "Command identifier\n"
+ "Hex Encoded payload\n")
+{
+ struct oml_node_state *oms = vty->index;
+ uint8_t buf[128];
+ int rc;
+ int cmd = atoi(argv[0]);
+
+ if (argc >= 2) {
+ rc = osmo_hexparse(argv[1], buf, sizeof(buf));
+ if (rc < 0 || rc > sizeof(buf)) {
+ vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else {
+ rc = 0;
+ }
+
+ abis_om2k_tx_arb(oms->bts, &oms->mo, cmd, buf, rc);
+
+ return CMD_SUCCESS;
+}
+
+
+
static struct con_group *con_group_find_or_create(struct gsm_bts *bts, uint8_t cg)
{
struct con_group *ent;
@@ -312,10 +341,11 @@ static int con_group_del_path(struct con_group *cg, uint16_t ccp,
return -ENOENT;
}
-DEFUN(cfg_om2k_con_group, cfg_om2k_con_group_cmd,
- "con-connection-group <1-31>",
- "Configure a CON (Concentrator) Connection Group\n"
- "CON Connection Group Number\n")
+DEFUN_ATTR(cfg_om2k_con_group, cfg_om2k_con_group_cmd,
+ "con-connection-group <1-31>",
+ "Configure a CON (Concentrator) Connection Group\n"
+ "CON Connection Group Number\n",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
struct con_group *cg;
@@ -340,10 +370,11 @@ DEFUN(cfg_om2k_con_group, cfg_om2k_con_group_cmd,
return CMD_SUCCESS;
}
-DEFUN(del_om2k_con_group, del_om2k_con_group_cmd,
- "del-connection-group <1-31>",
- "Delete a CON (Concentrator) Connection Group\n"
- "CON Connection Group Number\n")
+DEFUN_ATTR(del_om2k_con_group, del_om2k_con_group_cmd,
+ "del-connection-group <1-31>",
+ "Delete a CON (Concentrator) Connection Group\n"
+ "CON Connection Group Number\n",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_bts *bts = vty->index;
int rc;
@@ -368,12 +399,13 @@ DEFUN(del_om2k_con_group, del_om2k_con_group_cmd,
#define CON_PATH_HELP "CON Path (In/Out)\n" \
"Add CON Path to Concentration Group\n" \
"Delete CON Path from Concentration Group\n" \
- "CON Conection Point\n" \
+ "CON Connection Point\n" \
"Contiguity Index\n" \
-DEFUN(cfg_om2k_con_path_dec, cfg_om2k_con_path_dec_cmd,
- "con-path (add|del) <0-2047> <0-255> deconcentrated <0-63>",
- CON_PATH_HELP "De-concentrated in/outlet\n" "TEI Value\n")
+DEFUN_USRATTR(cfg_om2k_con_path_dec, cfg_om2k_con_path_dec_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "con-path (add|del) <0-2047> <0-255> deconcentrated <0-63>",
+ CON_PATH_HELP "De-concentrated in/outlet\n" "TEI Value\n")
{
struct con_group *cg = vty->index;
uint16_t ccp = atoi(argv[1]);
@@ -393,9 +425,10 @@ DEFUN(cfg_om2k_con_path_dec, cfg_om2k_con_path_dec_cmd,
return CMD_SUCCESS;
}
-DEFUN(cfg_om2k_con_path_conc, cfg_om2k_con_path_conc_cmd,
- "con-path (add|del) <0-2047> <0-255> concentrated <1-16>",
- CON_PATH_HELP "Concentrated in/outlet\n" "Tag Number\n")
+DEFUN_USRATTR(cfg_om2k_con_path_conc, cfg_om2k_con_path_conc_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "con-path (add|del) <0-2047> <0-255> concentrated <1-16>",
+ CON_PATH_HELP "Concentrated in/outlet\n" "Tag Number\n")
{
struct con_group *cg = vty->index;
uint16_t ccp = atoi(argv[1]);
@@ -415,11 +448,12 @@ DEFUN(cfg_om2k_con_path_conc, cfg_om2k_con_path_conc_cmd,
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd,
- "abis-lower-transport (single-timeslot|super-channel)",
- "Configure thee Abis Lower Transport\n"
- "Single Timeslot (classic Abis)\n"
- "SuperChannel (Packet Abis)\n")
+DEFUN_USRATTR(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "abis-lower-transport (single-timeslot|super-channel)",
+ "Configure thee Abis Lower Transport\n"
+ "Single Timeslot (classic Abis)\n"
+ "SuperChannel (Packet Abis)\n")
{
struct gsm_bts *bts = vty->index;
@@ -437,11 +471,68 @@ DEFUN(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd,
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd,
- "is-connection-list (add|del) <0-2047> <0-2047> <0-255>",
- "Interface Switch Connection List\n"
- "Add to IS list\n" "Delete from IS list\n"
- "ICP1\n" "ICP2\n" "Contiguity Index\n")
+DEFUN_USRATTR(cfg_bts_om2k_sync, cfg_bts_om2k_sync_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "om2000 sync-source (internal|external)",
+ OM2K_VTY_HELP
+ "TF Synchronization Source\n"
+ "Use Internal (E1)\n"
+ "USe External (GPS)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ if (bts->type != GSM_BTS_TYPE_RBS2000) {
+ vty_out(vty, "%% Command only works for RBS2000%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!strcmp(argv[0], "internal"))
+ bts->rbs2000.sync_src = OM2K_SYNC_SRC_INTERNAL;
+ else if (!strcmp(argv[0], "external"))
+ bts->rbs2000.sync_src = OM2K_SYNC_SRC_EXTERNAL;
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_om2k_version_limit, cfg_bts_om2k_version_limit_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "om2000 version-limit (oml|rsl) gen <0-99> rev <0-99>",
+ OM2K_VTY_HELP
+ "Configure optional maximum protocol version to negotiate\n"
+ "Limit OML IWD version\n" "Limit RSL IWD version\n"
+ "Generation limit\n"
+ "Generation number to limit to (inclusive)\n"
+ "Revision limit\n"
+ "Revision number to limit to (inclusive)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int iwd;
+
+ if (bts->type != GSM_BTS_TYPE_RBS2000) {
+ vty_out(vty, "%% Command only works for RBS2000%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "oml"))
+ iwd = 0;
+ else if (!strcmp(argv[0], "rsl"))
+ iwd = 1;
+ else {
+ vty_out(vty, "%% Invalid IWD%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->rbs2000.om2k_version[iwd].limit = (atoi(argv[1]) << 8) | atoi(argv[2]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "is-connection-list (add|del) <0-2047> <0-2047> <0-255>",
+ "Interface Switch Connection List\n"
+ "Add to IS list\n" "Delete from IS list\n"
+ "ICP1\n" "ICP2\n" "Contiguity Index\n")
{
struct gsm_bts *bts = vty->index;
uint16_t icp1 = atoi(argv[1]);
@@ -478,6 +569,33 @@ DEFUN(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd,
return CMD_SUCCESS;
}
+DEFUN_USRATTR(cfg_trx_om2k_rx_diversity,
+ cfg_trx_om2k_rx_diversity_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "om2000 rx-diversity-mode (a|ab|b)",
+ OM2K_VTY_HELP
+ "RX Diversity\n"
+ "Antenna TX/RX (A)\n"
+ "Both Antennas\n"
+ "Antenna RX (B)\n")
+
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ if (trx->bts->type != GSM_BTS_TYPE_RBS2000) {
+ vty_out(vty, "%% Command only works for RBS2000%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "a"))
+ trx->rbs2000.rx_diversity = OM2K_RX_DIVERSITY_A;
+ else if (!strcmp(argv[0], "ab"))
+ trx->rbs2000.rx_diversity = OM2K_RX_DIVERSITY_AB;
+ else if (!strcmp(argv[0], "b"))
+ trx->rbs2000.rx_diversity = OM2K_RX_DIVERSITY_B;
+ return CMD_SUCCESS;
+}
DEFUN(om2k_conf_req, om2k_conf_req_cmd,
"configuration-request",
@@ -492,8 +610,11 @@ DEFUN(om2k_conf_req, om2k_conf_req_cmd,
case OM2K_MO_CLS_IS:
abis_om2k_tx_is_conf_req(bts);
break;
+ case OM2K_MO_CLS_CON:
+ abis_om2k_tx_con_conf_req(bts);
+ break;
case OM2K_MO_CLS_TS:
- trx = gsm_bts_trx_by_nr(bts, oms->mo.assoc_so);
+ trx = gsm_bts_trx_num(bts, oms->mo.assoc_so);
if (!trx) {
vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr,
oms->mo.assoc_so, VTY_NEWLINE);
@@ -510,7 +631,7 @@ DEFUN(om2k_conf_req, om2k_conf_req_cmd,
case OM2K_MO_CLS_RX:
case OM2K_MO_CLS_TX:
case OM2K_MO_CLS_TRXC:
- trx = gsm_bts_trx_by_nr(bts, oms->mo.inst);
+ trx = gsm_bts_trx_num(bts, oms->mo.inst);
if (!trx) {
vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr,
oms->mo.inst, VTY_NEWLINE);
@@ -554,10 +675,29 @@ static void dump_con_group(struct vty *vty, struct con_group *cg)
}
}
+static const struct value_string om2k_rx_diversity_names[4] = {
+ { OM2K_RX_DIVERSITY_A, "a" },
+ { OM2K_RX_DIVERSITY_AB, "ab" },
+ { OM2K_RX_DIVERSITY_B, "b" },
+ { 0, NULL }
+};
+
+static const char *rx_diversity2str(enum om2k_rx_diversity type)
+{
+ return get_value_string(om2k_rx_diversity_names, type);
+}
+
+void abis_om2k_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ vty_out(vty, " om2000 rx-diversity-mode %s%s",
+ rx_diversity2str(trx->rbs2000.rx_diversity), VTY_NEWLINE);
+}
+
void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts)
{
struct is_conn_group *igrp;
struct con_group *cgrp;
+ unsigned int i;
llist_for_each_entry(igrp, &bts->rbs2000.is.conn_groups, list)
vty_out(vty, " is-connection-list add %u %u %u%s",
@@ -571,10 +711,93 @@ void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts)
if (bts->rbs2000.use_superchannel)
vty_out(vty, " abis-lower-transport super-channel%s",
VTY_NEWLINE);
+ for (i = 0; i < 2; i++)
+ if (bts->rbs2000.om2k_version[i].limit)
+ vty_out(vty, " om2000 version-limit %s gen %02d rev %02d%s",
+ i ? "rsl" : "oml",
+ (bts->rbs2000.om2k_version[i].limit >> 8),
+ (bts->rbs2000.om2k_version[i].limit & 0xff),
+ VTY_NEWLINE);
+ vty_out(vty, " om2000 sync-source %s%s",
+ bts->rbs2000.sync_src != OM2K_SYNC_SRC_EXTERNAL
+ ? "internal" : "external",
+ VTY_NEWLINE);
+}
+
+static void vty_dump_om2k_mo(struct vty *vty, const struct om2k_mo *mo, const char *pfx)
+{
+ unsigned int pfx_len = strlen(pfx);
+ const char *mo_name = abis_om2k_mo_name(&mo->addr);
+ unsigned int pfx_mo_len = pfx_len + strlen(mo_name);
+ unsigned int pfx2_len;
+ char pfx2[23];
+ int i;
+
+ /* generate padding after MO class to align the state names in the same column */
+ if (pfx_mo_len > sizeof(pfx2)-1)
+ pfx2_len = 0;
+ else
+ pfx2_len = sizeof(pfx2)-1 - pfx_mo_len;
+ for (i = 0; i < pfx2_len; i++)
+ pfx2[i] = ' ';
+ pfx2[pfx2_len] = '\0';
+
+ vty_out(vty, "%s%s%s %s%s", pfx, mo_name, pfx2,
+ mo->fsm ? osmo_fsm_inst_state_name(mo->fsm) : "[NULL]",
+ VTY_NEWLINE);
+}
+
+DEFUN(show_om2k_mo, show_om2k_mo_cmd,
+ "show bts <0-255> om2k-mo",
+ SHOW_STR "Display information about a BTS\n"
+ "BTS number\n" "OM2000 Managed Object information\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int bts_nr = atoi(argv[0]);
+ struct gsm_bts *bts = gsm_bts_num(net, bts_nr);
+ struct gsm_bts_trx *trx;
+
+ if (!bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bts->type != GSM_BTS_TYPE_RBS2000) {
+ vty_out(vty, "%% BTS is not using OM2000%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "BTS %3u OM2K-FSM state %s%s", bts->nr,
+ osmo_fsm_inst_state_name(bts->rbs2000.bts_fi), VTY_NEWLINE);
+ vty_dump_om2k_mo(vty, &bts->rbs2000.cf.om2k_mo, " ");
+ vty_dump_om2k_mo(vty, &bts->rbs2000.con.om2k_mo, " ");
+ vty_dump_om2k_mo(vty, &bts->rbs2000.is.om2k_mo, " ");
+ vty_dump_om2k_mo(vty, &bts->rbs2000.dp.om2k_mo, " ");
+ vty_dump_om2k_mo(vty, &bts->rbs2000.tf.om2k_mo, " ");
+ vty_dump_om2k_mo(vty, &bts->rbs2000.mctr.om2k_mo, " ");
+
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ int tn;
+
+ vty_out(vty, " TRX %u OM2K-FSM state %s%s", trx->nr,
+ osmo_fsm_inst_state_name(trx->rbs2000.trx_fi), VTY_NEWLINE);
+ vty_dump_om2k_mo(vty, &trx->rbs2000.trxc.om2k_mo, " ");
+ vty_dump_om2k_mo(vty, &trx->rbs2000.rx.om2k_mo, " ");
+ vty_dump_om2k_mo(vty, &trx->rbs2000.tx.om2k_mo, " ");
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ vty_dump_om2k_mo(vty, &ts->rbs2000.om2k_mo, " ");
+ }
+ }
+
+ return CMD_SUCCESS;
}
int abis_om2k_vty_init(void)
{
+ install_element_ve(&show_om2k_mo_cmd);
install_element(ENABLE_NODE, &om2k_class_inst_cmd);
install_element(ENABLE_NODE, &om2k_classnum_inst_cmd);
install_node(&om2k_node, dummy_config_write);
@@ -590,6 +813,7 @@ int abis_om2k_vty_init(void)
install_element(OM2K_NODE, &om2k_test_cmd);
install_element(OM2K_NODE, &om2k_cap_req_cmd);
install_element(OM2K_NODE, &om2k_conf_req_cmd);
+ install_element(OM2K_NODE, &om2k_arb_cmd);
install_node(&om2k_con_group_node, dummy_config_write);
install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_dec_cmd);
@@ -597,8 +821,12 @@ int abis_om2k_vty_init(void)
install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd);
install_element(BTS_NODE, &cfg_bts_alt_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_om2k_sync_cmd);
+ install_element(BTS_NODE, &cfg_bts_om2k_version_limit_cmd);
install_element(BTS_NODE, &cfg_om2k_con_group_cmd);
install_element(BTS_NODE, &del_om2k_con_group_cmd);
+ install_element(TRX_NODE, &cfg_trx_om2k_rx_diversity_cmd);
+
return 0;
}
diff --git a/src/osmo-bsc/abis_osmo.c b/src/osmo-bsc/abis_osmo.c
new file mode 100644
index 000000000..48774b4e8
--- /dev/null
+++ b/src/osmo-bsc/abis_osmo.c
@@ -0,0 +1,226 @@
+/* Osmocom specific protocols over Abis (IPA) */
+
+/* (C) 2021 sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/abis_osmo.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/pcuif_proto.h>
+#include <osmocom/bsc/neighbor_ident.h>
+
+#define OM_HEADROOM_SIZE 128
+
+////////////////////////////////////////
+// OSMO ABIS extensions (PCU)
+///////////////////////////////////////
+#define PCUIF_HDR_SIZE ( sizeof(struct gsm_pcu_if) - sizeof(((struct gsm_pcu_if *)0)->u) )
+
+static struct msgb *abis_osmo_pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr, size_t extra_size)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ msg = msgb_alloc_headroom(OM_HEADROOM_SIZE + sizeof(struct gsm_pcu_if) + extra_size,
+ OM_HEADROOM_SIZE, "IPA/ABIS/OSMO");
+ /* Only header is filled, caller is responible for reserving + filling
+ * message type specific contents: */
+ msgb_put(msg, PCUIF_HDR_SIZE);
+ pcu_prim = (struct gsm_pcu_if *) msgb_data(msg);
+ pcu_prim->msg_type = msg_type;
+ pcu_prim->bts_nr = bts_nr;
+ return msg;
+}
+
+/* Send a OML NM Message from BSC to BTS */
+static int abis_osmo_pcu_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_PCU);
+ return abis_osmo_sendmsg(bts, msg);
+}
+
+static int abis_osmo_pcu_tx_neigh_addr_cnf(struct gsm_bts *bts, const struct gsm_pcu_if_neigh_addr_req *naddr_req,
+ uint8_t err_code, const struct osmo_cell_global_id_ps *cgi_ps)
+{
+ struct msgb *msg = abis_osmo_pcu_msgb_alloc(PCU_IF_MSG_CONTAINER, bts->bts_nr, sizeof(struct gsm_pcu_if_neigh_addr_cnf));
+ struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msgb_data(msg);
+ struct gsm_pcu_if_neigh_addr_cnf *naddr_cnf = (struct gsm_pcu_if_neigh_addr_cnf *)&pcu_prim->u.container.data[0];
+
+ msgb_put(msg, sizeof(pcu_prim->u.container) + sizeof(struct gsm_pcu_if_neigh_addr_cnf));
+ pcu_prim->u.container.msg_type = PCU_IF_MSG_NEIGH_ADDR_CNF;
+ osmo_store16be(sizeof(struct gsm_pcu_if_neigh_addr_cnf), &pcu_prim->u.container.length);
+
+ naddr_cnf->orig_req = *naddr_req;
+ naddr_cnf->err_code = err_code;
+ if (err_code == 0) {
+ osmo_store16be(cgi_ps->rai.lac.plmn.mcc, &naddr_cnf->cgi_ps.mcc);
+ osmo_store16be(cgi_ps->rai.lac.plmn.mnc, &naddr_cnf->cgi_ps.mnc);
+ naddr_cnf->cgi_ps.mnc_3_digits = cgi_ps->rai.lac.plmn.mnc_3_digits;
+ osmo_store16be(cgi_ps->rai.lac.lac, &naddr_cnf->cgi_ps.lac);
+ naddr_cnf->cgi_ps.rac = cgi_ps->rai.rac;
+ osmo_store16be(cgi_ps->cell_identity, &naddr_cnf->cgi_ps.cell_identity);
+ }
+
+ return abis_osmo_pcu_sendmsg(bts, msg);
+}
+
+static int rcvmsg_pcu_neigh_addr_req(struct gsm_bts *bts, const struct gsm_pcu_if_neigh_addr_req *naddr_req)
+{
+
+ struct cell_ab ab;
+ uint16_t local_lac, local_ci;
+ struct osmo_cell_global_id_ps cgi_ps;
+ int rc;
+
+ local_lac = osmo_load16be(&naddr_req->local_lac);
+ local_ci = osmo_load16be(&naddr_req->local_ci);
+ ab = (struct cell_ab){
+ .arfcn = osmo_load16be(&naddr_req->tgt_arfcn),
+ .bsic = naddr_req->tgt_bsic,
+ };
+
+ LOGP(DNM, LOGL_INFO, "(bts=%d) Rx Neighbor Address Resolution Req (ARFCN=%u,BSIC=%u) from (LAC=%u,CI=%u)\n",
+ bts->nr, ab.arfcn, ab.bsic, local_lac, local_ci);
+
+ if (!cell_ab_valid(&ab)) {
+ rc = 2;
+ goto do_fail;
+ }
+
+ if (neighbor_address_resolution(bts->network, &ab, local_lac, local_ci, &cgi_ps) < 0) {
+ rc = 1;
+ goto do_fail;
+ }
+ return abis_osmo_pcu_tx_neigh_addr_cnf(bts, naddr_req, 0, &cgi_ps);
+
+do_fail:
+ return abis_osmo_pcu_tx_neigh_addr_cnf(bts, naddr_req, rc, NULL);
+}
+
+
+static int rcvmsg_pcu_container(struct gsm_bts *bts, struct gsm_pcu_if_container *container, size_t container_len)
+{
+ int rc;
+ uint16_t data_length = osmo_load16be(&container->length);
+
+ if (container_len < sizeof(*container) + data_length) {
+ LOGP(DNM, LOGL_ERROR, "ABIS_OSMO_PCU CONTAINER message inside (%d) too short\n",
+ container->msg_type);
+ return -EINVAL;
+ }
+
+ LOGP(DNM, LOGL_INFO, "(bts=%d) Rx ABIS_OSMO_PCU CONTAINER msg type %u\n",
+ bts->nr, container->msg_type);
+
+ switch (container->msg_type) {
+ case PCU_IF_MSG_NEIGH_ADDR_REQ:
+ if (data_length < sizeof(struct gsm_pcu_if_neigh_addr_req)) {
+ LOGP(DNM, LOGL_ERROR, "ABIS_OSMO_PCU CONTAINER ANR_CNF message too short\n");
+ return -EINVAL;
+ }
+ rc = rcvmsg_pcu_neigh_addr_req(bts, (struct gsm_pcu_if_neigh_addr_req *)&container->data);
+ break;
+ default:
+ LOGP(DNM, LOGL_NOTICE, "(bts=%d) Rx ABIS_OSMO_PCU unexpected msg type (%u) inside container!\n",
+ bts->nr, container->msg_type);
+ rc = -1;
+ }
+
+ return rc;
+}
+
+static int rcvmsg_pcu(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct gsm_pcu_if *pcu_prim;
+ int rc;
+
+ if (msgb_l2len(msg) < PCUIF_HDR_SIZE) {
+ LOGP(DNM, LOGL_ERROR, "ABIS_OSMO_PCU message too short\n");
+ return -EIO;
+ }
+
+ pcu_prim = msgb_l2(msg);
+ LOGP(DNM, LOGL_INFO, "(bts=%d) Rx ABIS_OSMO_PCU msg type %u\n",
+ pcu_prim->bts_nr, pcu_prim->msg_type);
+
+ switch (pcu_prim->msg_type) {
+ case PCU_IF_MSG_CONTAINER:
+ if (msgb_l2len(msg) < PCUIF_HDR_SIZE + sizeof(pcu_prim->u.container)) {
+ LOGP(DNM, LOGL_ERROR, "ABIS_OSMO_PCU CONTAINER message too short\n");
+ rc = -EINVAL;
+ } else {
+ rc = rcvmsg_pcu_container(bts, &pcu_prim->u.container, msgb_l2len(msg) - PCUIF_HDR_SIZE);
+ }
+ break;
+ default:
+ LOGP(DNM, LOGL_NOTICE, "(bts=%d) Rx ABIS_OSMO_PCU unexpected msg type %u!\n",
+ pcu_prim->bts_nr, pcu_prim->msg_type);
+ rc = -1;
+ }
+
+ return rc;
+}
+
+////////////////////////////////////////
+// OSMO ABIS extensions (generic code)
+///////////////////////////////////////
+
+/* High-Level API */
+/* Entry-point where L2 OSMO from BTS enters the NM code */
+int abis_osmo_rcvmsg(struct msgb *msg)
+{
+ int rc;
+ struct e1inp_sign_link *link = msg->dst;
+ struct gsm_bts *bts = link->trx->bts;
+ uint8_t *osmo_type = msgb_l2(msg);
+ msg->l2h = osmo_type + 1;
+
+ switch (*osmo_type) {
+ case IPAC_PROTO_EXT_PCU:
+ rc = rcvmsg_pcu(bts, msg);
+ break;
+ default:
+ LOGP(DNM, LOGL_ERROR, "IPAC_PROTO_EXT 0x%x not supported!\n",
+ *osmo_type);
+ rc = -EINVAL;
+ }
+
+ msgb_free(msg);
+ return rc;
+}
+
+
+/* Send a OML NM Message from BSC to BTS */
+int abis_osmo_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+ msg->dst = bts->osmo_link;
+
+ msg->l2h = msg->data;
+
+ return abis_sendmsg(msg);
+
+}
diff --git a/src/osmo-bsc/abis_rsl.c b/src/osmo-bsc/abis_rsl.c
index 8ffb07e8f..49e8b52c3 100644
--- a/src/osmo-bsc/abis_rsl.c
+++ b/src/osmo-bsc/abis_rsl.c
@@ -1,7 +1,7 @@
/* GSM Radio Signalling Link messages on the A-bis interface
* 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
-/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2008-2019 by Harald Welte <laforge@gnumonks.org>
* (C) 2012 by Holger Hans Peter Freyther
*
* All Rights Reserved
@@ -24,6 +24,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
+#include <inttypes.h>
#include <netinet/in.h>
#include <arpa/inet.h>
@@ -43,18 +44,22 @@
#include <osmocom/gsm/rsl.h>
#include <osmocom/core/talloc.h>
#include <osmocom/bsc/pcu_if.h>
+#include <osmocom/bsc/pcuif_proto.h>
#include <osmocom/bsc/gsm_08_08.h>
#include <osmocom/netif/rtp.h>
-#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/core/tdef.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_rtp_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
-
-#define RSL_ALLOC_SIZE 1024
-#define RSL_ALLOC_HEADROOM 128
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/power_control.h>
+#include <osmocom/bsc/chan_counts.h>
+#include <osmocom/bsc/lchan.h>
+#include <osmocom/bsc/vgcs_fsm.h>
static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan,
struct gsm_meas_rep *resp)
@@ -70,26 +75,26 @@ static void count_codecs(struct gsm_bts *bts, struct gsm_lchan *lchan)
OSMO_ASSERT(bts);
if (lchan->type == GSM_LCHAN_TCH_H) {
- switch (lchan->tch_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode)) {
case GSM48_CMODE_SPEECH_AMR:
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_H]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_AMR_H));
break;
case GSM48_CMODE_SPEECH_V1:
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_HR]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_V1_HR));
break;
default:
break;
}
} else if (lchan->type == GSM_LCHAN_TCH_F) {
- switch (lchan->tch_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode)) {
case GSM48_CMODE_SPEECH_AMR:
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_F]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_AMR_F));
break;
case GSM48_CMODE_SPEECH_V1:
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_FR]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_V1_FR));
break;
case GSM48_CMODE_SPEECH_EFR:
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_EFR]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CODEC_EFR));
break;
default:
break;
@@ -144,12 +149,6 @@ static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
return lchan;
}
-static struct msgb *rsl_msgb_alloc(void)
-{
- return msgb_alloc_headroom(RSL_ALLOC_SIZE, RSL_ALLOC_HEADROOM,
- "RSL");
-}
-
static void pad_macblock(uint8_t *out, const uint8_t *in, int len)
{
memcpy(out, in, len);
@@ -158,13 +157,44 @@ static void pad_macblock(uint8_t *out, const uint8_t *in, int len)
memset(out+len, 0x2b, GSM_MACBLOCK_LEN - len);
}
-/* Chapter 9.3.7: Encryption Information */
+/* Chapter 9.3.7: Encryption Information
+ * Return negative on error, number of bytes written to 'out' on success.
+ * 'out' must provide room for 17 bytes. */
static int build_encr_info(uint8_t *out, struct gsm_lchan *lchan)
{
- *out++ = lchan->encr.alg_id & 0xff;
- if (lchan->encr.key_len)
- memcpy(out, lchan->encr.key, lchan->encr.key_len);
- return lchan->encr.key_len + 1;
+ out[0] = ALG_A5_NR_TO_RSL(lchan->encr.alg_a5_n);
+ switch (out[0]) {
+ case GSM0808_ALG_ID_A5_1:
+ case GSM0808_ALG_ID_A5_2:
+ case GSM0808_ALG_ID_A5_3:
+ if (!lchan->encr.key_len) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "A5/%d encryption chosen, but missing Kc\n", lchan->encr.alg_a5_n);
+ return -EINVAL;
+ }
+ /* fall through */
+ case GSM0808_ALG_ID_A5_0:
+ /* When A5/0 is chosen, no encryption is active, so technically, no key is needed. However, 3GPP TS
+ * 48.058 9.3.7 Encryption Information stays quite silent about presence or absence of a key for A5/0.
+ * The only thing specified is how to indicate the length of the key; the possibility that the key may
+ * be zero length is not explicitly mentioned. So it seems that we should always send the key along,
+ * even for A5/0. Currently our ttcn3 test suite does expect the key to be present also for A5/0, see
+ * f_cipher_mode() in bsc/MSC_ConnectionHandler.ttcn. */
+ if (lchan->encr.key_len)
+ memcpy(&out[1], lchan->encr.key, lchan->encr.key_len);
+ return 1 + lchan->encr.key_len;
+
+ case GSM0808_ALG_ID_A5_4:
+ if (!lchan->encr.kc128_present) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "A5/4 encryption chosen, but missing Kc128\n");
+ return -EINVAL;
+ }
+ memcpy(&out[1], lchan->encr.kc128, sizeof(lchan->encr.kc128));
+ return 1 + sizeof(lchan->encr.kc128);
+
+ default:
+ LOG_LCHAN(lchan, LOGL_ERROR, "A5/%d encryption not supported\n", lchan->encr.alg_a5_n);
+ return -EINVAL;
+ }
}
/* If the TLV contain an RSL Cause IE, return pointer to the cause value. If there is no Cause IE, return
@@ -191,8 +221,52 @@ static const char *rsl_cause_name(struct tlv_parsed *tp)
return "";
}
+static void add_power_control_params(struct msgb *msg, enum abis_rsl_ie iei,
+ const struct gsm_lchan *lchan)
+{
+ const struct gsm_bts *bts = lchan->ts->trx->bts;
+ const struct gsm_power_ctrl_params *cp;
+
+ /* Since {MS,BS}_POWER_PARAM IE content is operator dependent, it's not
+ * known how different BTS models will interpret an empty IE, so let's
+ * better skip sending it unless we know for sure what each expects. */
+ if (bts->model->power_ctrl_enc_rsl_params == NULL)
+ return;
+
+ if (iei == RSL_IE_MS_POWER_PARAM)
+ cp = &bts->ms_power_ctrl;
+ else
+ cp = &bts->bs_power_ctrl;
+
+ /* These parameters are only valid for dynamic mode */
+ if (cp->mode != GSM_PWR_CTRL_MODE_DYN_BTS)
+ return;
+
+ /* No dynamic BS power control if the maximum is 0 dB */
+ if (cp->dir == GSM_PWR_CTRL_DIR_DL) {
+ if (lchan->bs_power_db == 0)
+ return;
+ }
+
+ /* Put tag first, length will be updated later */
+ uint8_t *ie_len = msgb_tl_put(msg, iei);
+ uint8_t msg_len = msgb_length(msg);
+
+ if (bts->model->power_ctrl_enc_rsl_params(msg, cp) != 0) {
+ LOGP(DRSL, LOGL_ERROR, "Failed to encode MS/BS Power Control "
+ "parameters, omitting this IE (tag 0x%02x)\n", iei);
+ msgb_get(msg, msg_len - 2);
+ return;
+ }
+
+ /* Update length part of the containing IE */
+ *ie_len = msgb_length(msg) - msg_len;
+}
+
/* Send a BCCH_INFO message as per Chapter 8.5.1 */
-int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len)
+/* Allow test to overwrite it */
+__attribute__((weak)) int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type,
+ const uint8_t *data, int len)
{
struct abis_rsl_dchan_hdr *dh;
const struct gsm_bts *bts = trx->bts;
@@ -221,12 +295,13 @@ int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type,
msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data);
}
- msg->dst = trx->rsl_link;
+ msg->dst = trx->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
-int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type,
+/* Allow test to overwrite it */
+__attribute__((weak)) int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type,
const uint8_t *data, int len)
{
struct abis_rsl_common_hdr *ch;
@@ -240,7 +315,7 @@ int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type,
if (data)
msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data);
- msg->dst = trx->rsl_link;
+ msg->dst = trx->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
@@ -249,8 +324,12 @@ int rsl_sacch_info_modify(struct gsm_lchan *lchan, uint8_t type,
const uint8_t *data, int len)
{
struct abis_rsl_dchan_hdr *dh;
- struct msgb *msg = rsl_msgb_alloc();
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ struct msgb *msg;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
+
+ msg = rsl_msgb_alloc();
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_SACCH_INFO_MODIFY);
@@ -260,7 +339,7 @@ int rsl_sacch_info_modify(struct gsm_lchan *lchan, uint8_t type,
if (data)
msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data);
- msg->dst = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
@@ -269,7 +348,10 @@ int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db)
{
struct abis_rsl_dchan_hdr *dh;
struct msgb *msg;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t bs_power_enc;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
db = abs(db);
if (db > 30)
@@ -277,56 +359,59 @@ int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db)
msg = rsl_msgb_alloc();
- lchan->bs_power = db/2;
+ bs_power_enc = db / 2;
if (fpc)
- lchan->bs_power |= 0x10;
+ bs_power_enc |= 0x10;
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_BS_POWER_CONTROL);
dh->chan_nr = chan_nr;
- msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power);
+ msgb_tv_put(msg, RSL_IE_BS_POWER, bs_power_enc);
+
+ /* BS Power Control Parameters (if supported by BTS model) */
+ add_power_control_params(msg, RSL_IE_BS_POWER_PARAM, lchan);
- msg->dst = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
-int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm)
+int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan)
{
struct abis_rsl_dchan_hdr *dh;
struct msgb *msg;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
- int ctl_lvl;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
- ctl_lvl = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, dbm);
- if (ctl_lvl < 0)
- return ctl_lvl;
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Tx MS POWER CONTROL (ms_power_lvl=%" PRIu8 ")\n",
+ lchan->ms_power);
msg = rsl_msgb_alloc();
- lchan->ms_power = ctl_lvl;
-
- if (fpc)
- lchan->ms_power |= 0x20;
-
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_MS_POWER_CONTROL);
dh->chan_nr = chan_nr;
msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
- msg->dst = lchan->ts->trx->rsl_link;
+ /* MS Power Control Parameters (if supported by BTS model) */
+ add_power_control_params(msg, RSL_IE_MS_POWER_PARAM, lchan);
+
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm,
- struct gsm_lchan *lchan)
+ struct gsm_lchan *lchan,
+ const struct channel_mode_and_rate *ch_mode_rate,
+ enum lchan_type_for type_for)
{
+ int rc;
memset(cm, 0, sizeof(*cm));
- /* FIXME: what to do with data calls ? */
cm->dtx_dtu = 0;
if (lchan->ts->trx->bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED)
cm->dtx_dtu |= RSL_CMOD_DTXu;
@@ -334,33 +419,57 @@ static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm,
cm->dtx_dtu |= RSL_CMOD_DTXd;
/* set TCH Speech/Data */
- cm->spd_ind = lchan->rsl_cmode;
-
- if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN &&
- lchan->tch_mode != GSM48_CMODE_SIGN)
- LOGP(DRSL, LOGL_ERROR, "unsupported: rsl_mode == signalling, "
- "but tch_mode != signalling\n");
+ rc = chan_mode_to_rsl_cmod_spd(ch_mode_rate->chan_mode);
+ if (rc < 0) {
+ LOGP(DRSL, LOGL_ERROR, "unsupported: chan_mode = 0x%02x\n", ch_mode_rate->chan_mode);
+ return rc;
+ }
+ cm->spd_ind = rc;
switch (lchan->type) {
case GSM_LCHAN_SDCCH:
cm->chan_rt = RSL_CMOD_CRT_SDCCH;
break;
case GSM_LCHAN_TCH_F:
- cm->chan_rt = RSL_CMOD_CRT_TCH_Bm;
+ switch (type_for) {
+ case LCHAN_TYPE_FOR_VAMOS:
+ cm->chan_rt = RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm;
+ break;
+ case LCHAN_TYPE_FOR_VGCS:
+ cm->chan_rt = RSL_CMOD_CRT_TCH_GROUP_Bm;
+ break;
+ case LCHAN_TYPE_FOR_VBS:
+ cm->chan_rt = RSL_CMOD_CRT_TCH_BCAST_Bm;
+ break;
+ default:
+ cm->chan_rt = RSL_CMOD_CRT_TCH_Bm;
+ }
break;
case GSM_LCHAN_TCH_H:
- cm->chan_rt = RSL_CMOD_CRT_TCH_Lm;
+ switch (type_for) {
+ case LCHAN_TYPE_FOR_VAMOS:
+ cm->chan_rt = RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm;
+ break;
+ case LCHAN_TYPE_FOR_VGCS:
+ cm->chan_rt = RSL_CMOD_CRT_TCH_GROUP_Lm;
+ break;
+ case LCHAN_TYPE_FOR_VBS:
+ cm->chan_rt = RSL_CMOD_CRT_TCH_BCAST_Lm;
+ break;
+ default:
+ cm->chan_rt = RSL_CMOD_CRT_TCH_Lm;
+ }
break;
case GSM_LCHAN_NONE:
case GSM_LCHAN_UNKNOWN:
default:
LOGP(DRSL, LOGL_ERROR,
"unsupported activation lchan->type %u %s\n",
- lchan->type, gsm_lchant_name(lchan->type));
+ lchan->type, gsm_chan_t_name(lchan->type));
return -EINVAL;
}
- switch (lchan->tch_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(ch_mode_rate->chan_mode)) {
case GSM48_CMODE_SIGN:
cm->chan_rate = 0;
break;
@@ -376,95 +485,113 @@ static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm,
case GSM48_CMODE_DATA_14k5:
case GSM48_CMODE_DATA_12k0:
case GSM48_CMODE_DATA_6k0:
- switch (lchan->csd_mode) {
- case LCHAN_CSD_M_NT:
- /* non-transparent CSD with RLP */
- switch (lchan->tch_mode) {
- case GSM48_CMODE_DATA_14k5:
- cm->chan_rate = RSL_CMOD_SP_NT_14k5;
- break;
- case GSM48_CMODE_DATA_12k0:
- cm->chan_rate = RSL_CMOD_SP_NT_12k0;
- break;
- case GSM48_CMODE_DATA_6k0:
- cm->chan_rate = RSL_CMOD_SP_NT_6k0;
- break;
- default:
- LOGP(DRSL, LOGL_ERROR,
- "unsupported lchan->tch_mode %u\n",
- lchan->tch_mode);
- return -EINVAL;
- }
- break;
- /* transparent data services below */
- case LCHAN_CSD_M_T_1200_75:
- cm->chan_rate = RSL_CMOD_CSD_T_1200_75;
- break;
- case LCHAN_CSD_M_T_600:
- cm->chan_rate = RSL_CMOD_CSD_T_600;
- break;
- case LCHAN_CSD_M_T_1200:
- cm->chan_rate = RSL_CMOD_CSD_T_1200;
- break;
- case LCHAN_CSD_M_T_2400:
- cm->chan_rate = RSL_CMOD_CSD_T_2400;
- break;
- case LCHAN_CSD_M_T_9600:
- cm->chan_rate = RSL_CMOD_CSD_T_9600;
- break;
- case LCHAN_CSD_M_T_14400:
- cm->chan_rate = RSL_CMOD_CSD_T_14400;
- break;
- case LCHAN_CSD_M_T_29000:
- cm->chan_rate = RSL_CMOD_CSD_T_29000;
- break;
- case LCHAN_CSD_M_T_32000:
- cm->chan_rate = RSL_CMOD_CSD_T_32000;
- break;
- default:
- LOGP(DRSL, LOGL_ERROR,
- "unsupported lchan->csd_mode %u\n",
- lchan->csd_mode);
- return -EINVAL;
+ case GSM48_CMODE_DATA_3k6:
+ /* 3GPP TS 48.058 ยง 9.3.6 Channel Mode octet 6 */
+ if (ch_mode_rate->data_transparent) {
+ cm->chan_rate = ch_mode_rate->data_rate.t;
+ } else {
+ cm->chan_rate = ch_mode_rate->data_rate.nt;
+ cm->chan_rate |= 0x40;
}
break;
default:
- LOGP(DRSL, LOGL_ERROR,
- "unsupported lchan->tch_mode %u\n",
- lchan->tch_mode);
+ LOGP(DRSL, LOGL_ERROR, "unsupported channel mode %u\n", ch_mode_rate->chan_mode);
return -EINVAL;
}
return 0;
}
-static void mr_config_for_bts(struct gsm_lchan *lchan, struct msgb *msg)
+static int put_mr_config_for_bts(struct msgb *msg, const struct gsm48_multi_rate_conf *mr_conf_filtered,
+ const struct amr_multirate_conf *mr_modes)
{
- uint8_t len;
+ msgb_put_u8(msg, RSL_IE_MR_CONFIG);
+ return gsm48_multirate_config(msg, mr_conf_filtered, mr_modes->bts_mode, mr_modes->num_modes);
+}
+
+/* indicate FACCH/SACCH Repetition to be performed by BTS,
+ * see also: 3GPP TS 44.006, section 10 and 11 */
+static void put_rep_acch_cap_ie(const struct gsm_lchan *lchan,
+ struct msgb *msg)
+{
+ struct abis_rsl_osmo_rep_acch_cap *cap;
+ const struct gsm_bts *bts = lchan->ts->trx->bts;
- if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR)
+ /* The RSL_IE_OSMO_REP_ACCH_CAP IE is a proprietary IE, that can only
+ * be used with osmo-bts type BTSs */
+ if (!(bts->model->type == GSM_BTS_TYPE_OSMOBTS
+ && osmo_bts_has_feature(&bts->features, BTS_FEAT_ACCH_REP)))
return;
- len = lchan->mr_bts_lv[0];
- if (!len) {
- LOG_LCHAN(lchan, LOGL_ERROR, "Missing Multirate Config (len is zero)\n");
+ cap = (struct abis_rsl_osmo_rep_acch_cap*) msg->tail;
+ msgb_tlv_put(msg, RSL_IE_OSMO_REP_ACCH_CAP, sizeof(*cap),
+ (uint8_t *)&bts->rep_acch_cap);
+
+ if (!(lchan->conn && lchan->conn->cm3_valid
+ && lchan->conn->cm3.repeated_acch_capability)) {
+ /* MS supports only FACCH repetition for command frames, so
+ * we mask out all other features, even when they are enabled
+ * on this BTS. */
+ cap->dl_facch_all = 0;
+ cap->dl_sacch = 0;
+ cap->ul_sacch = 0;
+ }
+}
+
+/* indicate Temporary overpower of SACCH and FACCH channels */
+static void put_top_acch_cap_ie(const struct gsm_lchan *lchan,
+ const struct rsl_ie_chan_mode *cm,
+ struct msgb *msg)
+{
+ const struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ /* The BTS_FEAT_ACCH_TEMP_OVP IE is a proprietary IE, that can only be used with osmo-bts type BTSs */
+ if (!(bts->model->type == GSM_BTS_TYPE_OSMOBTS && osmo_bts_has_feature(&bts->features, BTS_FEAT_ACCH_TEMP_OVP)))
return;
+
+ /* Check if TOP is permitted for the given Channel Mode */
+ switch (bts->top_acch_chan_mode) {
+ case TOP_ACCH_CHAN_MODE_SPEECH_V3:
+ if (cm->spd_ind != RSL_CMOD_SPD_SPEECH)
+ return;
+ if (cm->chan_rate != RSL_CMOD_SP_GSM3)
+ return;
+ break;
+ case TOP_ACCH_CHAN_MODE_ANY:
+ break;
}
- msgb_tlv_put(msg, RSL_IE_MR_CONFIG, lchan->mr_bts_lv[0],
- lchan->mr_bts_lv + 1);
+
+ msgb_tlv_put(msg, RSL_IE_OSMO_TEMP_OVP_ACCH_CAP,
+ sizeof(bts->top_acch_cap),
+ (void *)&bts->top_acch_cap);
+}
+
+/* Write RSL_IE_OSMO_TRAINING_SEQUENCE to msgb. The tsc_set argument's range is 1-4, tsc argument range is 0-7. */
+static void put_osmo_training_sequence_ie(struct msgb *msg, uint8_t tsc_set, uint8_t tsc)
+{
+ uint8_t *len = msgb_tl_put(msg, RSL_IE_OSMO_TRAINING_SEQUENCE);
+ *len = 2;
+ /* Convert from spec conforming "human readable" TSC Set 1-4 to 0-3 on the wire */
+ msgb_put_u8(msg, tsc_set - 1);
+ /* TSC is 0-7 both on the wire and in spec descriptions */
+ msgb_put_u8(msg, tsc);
}
/* Chapter 8.4.1 */
int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref)
{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct gsm_bts *bts = trx->bts;
struct abis_rsl_dchan_hdr *dh;
struct msgb *msg;
int rc;
uint8_t *len;
- uint8_t ta;
struct rsl_ie_chan_mode cm;
struct gsm48_chan_desc cd;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
DEBUGP(DRSL, "%s Tx RSL Channel Activate with act_type=%s\n",
gsm_ts_and_pchan_name(lchan->ts),
@@ -473,7 +600,7 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref)
/* PDCH activation is a job for rsl_tx_dyn_ts_pdch_act_deact(); */
OSMO_ASSERT(act_type != RSL_ACT_OSMO_PDCH);
- rc = channel_mode_from_lchan(&cm, lchan);
+ rc = channel_mode_from_lchan(&cm, lchan, &lchan->activate.ch_mode_rate, lchan->activate.info.type_for);
if (rc < 0) {
LOGP(DRSL, LOGL_ERROR,
"%s Cannot find channel mode from lchan type\n",
@@ -481,20 +608,18 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref)
return rc;
}
- ta = lchan->rqd_ta;
-
- /* BS11 requires TA shifted by 2 bits */
- if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11)
- ta <<= 2;
-
memset(&cd, 0, sizeof(cd));
- gsm48_lchan2chan_desc(&cd, lchan);
+ rc = gsm48_lchan2chan_desc(&cd, lchan, lchan->activate.tsc, true);
+ if (rc) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Error encoding Channel Number\n");
+ return rc;
+ }
msg = rsl_msgb_alloc();
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV);
- dh->chan_nr = gsm_lchan2chan_nr(lchan);
+ dh->chan_nr = chan_nr;
msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type);
msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
@@ -511,21 +636,24 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref)
len = msgb_put(msg, 1);
msgb_tv_fixed_put(msg, GSM48_IE_CHANDESC_2, sizeof(cd), (const uint8_t *) &cd);
- if (lchan->ts->hopping.enabled)
- msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len,
- lchan->ts->hopping.ma_data);
- else
- msgb_tlv_put(msg, GSM48_IE_MA_AFTER, 0, NULL);
+ /* See 3GPP TS 48.058 (version 15.0.0), section 9.3.5 "Channel Identification".
+ * The 3GPP TS 24.008 "Mobile Allocation" shall for compatibility reasons
+ * be included but empty, i.e. the length shall be zero. */
+ msgb_tlv_put(msg, GSM48_IE_MA_AFTER, 0, NULL);
/* update the calculated size */
msg->l3h = len + 1;
*len = msgb_l3len(msg);
- if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+ if (lchan->encr.alg_a5_n > 0) {
uint8_t encr_info[MAX_A5_KEY_LEN+2];
rc = build_encr_info(encr_info, lchan);
if (rc > 0)
msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+ if (rc < 0) {
+ msgb_free(msg);
+ return rc;
+ }
}
switch (act_type) {
@@ -537,14 +665,60 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref)
break;
}
- msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power);
- msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
- msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta);
- mr_config_for_bts(lchan, msg);
+ if (bts->bs_power_ctrl.mode != GSM_PWR_CTRL_MODE_NONE)
+ msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power_db / 2);
+ if (bts->ms_power_ctrl.mode != GSM_PWR_CTRL_MODE_NONE)
+ msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
+
+ if (lchan->activate.info.ta_known) {
+ uint8_t ta = lchan->activate.info.ta;
+ /* BS11 requires TA shifted by 2 bits */
+ if (bts->type == GSM_BTS_TYPE_BS11)
+ ta <<= 2;
+ msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta);
+ } else if ((act_type & 0x06) == 0x00) {
+ /* Note '4)' in section 8.4.1: The Timing Advance element must be
+ * included if activation type is intra cell channel change. */
+ LOG_LCHAN(lchan, LOGL_NOTICE, "Timing Advance IE shall be present, "
+ "but the actual value is not known => assuming 0\n");
+ msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, 0);
+ }
+
+ /* BS/MS Power Control Parameters (if supported by BTS model) */
+ add_power_control_params(msg, RSL_IE_BS_POWER_PARAM, lchan);
+ add_power_control_params(msg, RSL_IE_MS_POWER_PARAM, lchan);
+
+ if (cm.chan_rate == RSL_CMOD_SP_GSM3) {
+ rc = put_mr_config_for_bts(msg, &lchan->activate.mr_conf_filtered,
+ (lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half);
+ if (rc) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n");
+ msgb_free(msg);
+ return rc;
+ }
+ }
+
+ put_rep_acch_cap_ie(lchan, msg);
+ put_top_acch_cap_ie(lchan, &cm, msg);
- msg->dst = lchan->ts->trx->rsl_link;
+ /* Selecting a specific TSC Set is only applicable to VAMOS mode */
+ if (lchan->activate.info.type_for == LCHAN_TYPE_FOR_VAMOS && lchan->activate.tsc_set >= 1)
+ put_osmo_training_sequence_ie(msg, lchan->activate.tsc_set, lchan->activate.tsc);
- rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_TOTAL]);
+ msg->dst = rsl_chan_link(lchan);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_TOTAL));
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_SDCCH));
+ break;
+ case GSM_LCHAN_TCH_H:
+ case GSM_LCHAN_TCH_F:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_TCH));
+ break;
+ default:
+ break;
+ }
return abis_rsl_sendmsg(msg);
}
@@ -556,10 +730,14 @@ int rsl_chan_mode_modify_req(struct gsm_lchan *lchan)
struct msgb *msg;
int rc;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
struct rsl_ie_chan_mode cm;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
- rc = channel_mode_from_lchan(&cm, lchan);
+ rc = channel_mode_from_lchan(&cm, lchan, &lchan->modify.ch_mode_rate, lchan->modify.info.type_for);
if (rc < 0)
return rc;
@@ -571,16 +749,37 @@ int rsl_chan_mode_modify_req(struct gsm_lchan *lchan)
msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
(uint8_t *) &cm);
- if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+ if (lchan->encr.alg_a5_n > 0) {
uint8_t encr_info[MAX_A5_KEY_LEN+2];
rc = build_encr_info(encr_info, lchan);
if (rc > 0)
msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+ if (rc < 0) {
+ msgb_free(msg);
+ return rc;
+ }
}
- mr_config_for_bts(lchan, msg);
+ if (cm.chan_rate == RSL_CMOD_SP_GSM3) {
+ rc = put_mr_config_for_bts(msg, &lchan->modify.mr_conf_filtered,
+ (lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half);
+ if (rc) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n");
+ msgb_free(msg);
+ return rc;
+ }
+ }
- msg->dst = lchan->ts->trx->rsl_link;
+ put_rep_acch_cap_ie(lchan, msg);
+ put_top_acch_cap_ie(lchan, &cm, msg);
+
+ /* Selecting a specific TSC Set is only applicable to VAMOS mode. Send this Osmocom specific IE only to OsmoBTS
+ * types. */
+ if (lchan->modify.info.type_for == LCHAN_TYPE_FOR_VAMOS && lchan->modify.tsc_set >= 1 &&
+ bts->model->type == GSM_BTS_TYPE_OSMOBTS)
+ put_osmo_training_sequence_ie(msg, lchan->modify.tsc_set, lchan->modify.tsc);
+
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
@@ -590,11 +789,14 @@ int rsl_encryption_cmd(struct msgb *msg)
{
struct abis_rsl_dchan_hdr *dh;
struct gsm_lchan *lchan = msg->lchan;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
uint8_t encr_info[MAX_A5_KEY_LEN+2];
uint8_t l3_len = msg->len;
int rc;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
+
/* First push the L3 IE tag and length */
msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len);
@@ -612,7 +814,7 @@ int rsl_encryption_cmd(struct msgb *msg)
init_dchan_hdr(dh, RSL_MT_ENCR_CMD);
dh->chan_nr = chan_nr;
- msg->dst = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
@@ -623,12 +825,16 @@ int rsl_deact_sacch(struct gsm_lchan *lchan)
struct abis_rsl_dchan_hdr *dh;
struct msgb *msg = rsl_msgb_alloc();
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
+
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_DEACTIVATE_SACCH);
- dh->chan_nr = gsm_lchan2chan_nr(lchan);
+ dh->chan_nr = chan_nr;
msg->lchan = lchan;
- msg->dst = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan));
@@ -641,29 +847,44 @@ int rsl_tx_rf_chan_release(struct gsm_lchan *lchan)
struct abis_rsl_dchan_hdr *dh;
struct msgb *msg;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
+
msg = rsl_msgb_alloc();
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_RF_CHAN_REL);
- dh->chan_nr = gsm_lchan2chan_nr(lchan);
+ dh->chan_nr = chan_nr;
msg->lchan = lchan;
- msg->dst = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
-int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, uint8_t len,
- uint8_t *ms_ident, uint8_t chan_needed, bool is_gprs)
+int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group,
+ const struct osmo_mobile_identity *mi,
+ uint8_t chan_needed, bool is_gprs)
{
- struct abis_rsl_dchan_hdr *dh;
+ struct abis_rsl_cchan_hdr *cch;
struct msgb *msg = rsl_msgb_alloc();
+ uint8_t *l;
+ int rc;
- dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
- init_dchan_hdr(dh, RSL_MT_PAGING_CMD);
- dh->chan_nr = RSL_CHAN_PCH_AGCH;
+ cch = (struct abis_rsl_cchan_hdr *) msgb_put(msg, sizeof(*cch));
+ rsl_init_cchan_hdr(cch, RSL_MT_PAGING_CMD);
+ cch->chan_nr = RSL_CHAN_PCH_AGCH;
msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group);
- msgb_tlv_put(msg, RSL_IE_MS_IDENTITY, len-2, ms_ident+2);
+
+ l = msgb_tl_put(msg, RSL_IE_MS_IDENTITY);
+ rc = osmo_mobile_identity_encode_msgb(msg, mi, false);
+ if (rc < 0) {
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ *l = rc;
+
msgb_tv_put(msg, RSL_IE_CHAN_NEEDED, chan_needed);
/* Ericsson wants to have this IE in case a paging message
@@ -671,11 +892,50 @@ int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, uint8_t len,
if (bts->type == GSM_BTS_TYPE_RBS2000 && is_gprs)
msgb_tv_put(msg, RSL_IE_ERIC_PACKET_PAG_IND, 0);
- msg->dst = bts->c0->rsl_link;
+ msg->dst = bts->c0->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
+/* Chapter 8.5.10: NOTIFICATION COMMAND */
+int rsl_notification_cmd(struct gsm_bts *bts, struct gsm_lchan *lchan, struct gsm0808_group_callref *gc, uint8_t *drx)
+{
+ struct abis_rsl_cchan_hdr *cch;
+ struct msgb *msg = rsl_msgb_alloc();
+ struct gsm48_chan_desc cd;
+ uint8_t sti = (lchan) ? RSL_CMD_INDICATOR_START : RSL_CMD_INDICATOR_STOP;
+ uint8_t *t;
+ int rc;
+
+ cch = (struct abis_rsl_cchan_hdr *) msgb_put(msg, sizeof(*cch));
+ rsl_init_cchan_hdr(cch, RSL_MT_NOT_CMD);
+ cch->chan_nr = RSL_CHAN_PCH_AGCH;
+
+ msgb_tlv_put(msg, RSL_IE_CMD_INDICATOR, 1, &sti);
+
+ /* Use TLV encoding from TS 08.08. Change different IE type. */
+ t = msg->tail;
+ gsm0808_enc_group_callref(msg, gc);
+ *t = RSL_IE_GROUP_CALL_REF;
+
+ if (lchan) {
+ memset(&cd, 0, sizeof(cd));
+ rc = gsm48_lchan2chan_desc(&cd, lchan, lchan->activate.tsc, true);
+ if (rc) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Error encoding Channel Number\n");
+ msgb_free(msg);
+ return rc;
+ }
+ msgb_tlv_put(msg, RSL_IE_CHAN_DESC, sizeof(cd), (const uint8_t *)&cd);
+
+ if (drx)
+ msgb_tlv_put(msg, RSL_IE_NCH_DRX_INFO, 1, drx);
+ }
+
+ msg->dst = bts->c0->rsl_link_primary;
+ return abis_rsl_sendmsg(msg);
+}
+
int rsl_forward_layer3_info(struct gsm_lchan *lchan, const uint8_t *l3_info, uint8_t l3_info_len)
{
struct msgb *msg;
@@ -691,30 +951,21 @@ int rsl_forward_layer3_info(struct gsm_lchan *lchan, const uint8_t *l3_info, uin
return rsl_data_request(msg, 0);
}
-int imsi_str2bcd(uint8_t *bcd_out, const char *str_in)
-{
- int i, len = strlen(str_in);
-
- for (i = 0; i < len; i++) {
- int num = str_in[i] - 0x30;
- if (num < 0 || num > 9)
- return -1;
- if (i % 2 == 0)
- bcd_out[i/2] = num;
- else
- bcd_out[i/2] |= (num << 4);
- }
-
- return 0;
-}
-
/* Chapter 8.5.6 */
-struct msgb *rsl_imm_assign_cmd_common(struct gsm_bts *bts, uint8_t len, uint8_t *val)
+struct msgb *rsl_imm_assign_cmd_common(const struct gsm_bts *bts, uint8_t len, const uint8_t *val)
{
- struct msgb *msg = rsl_msgb_alloc();
+ struct msgb *msg;
struct abis_rsl_dchan_hdr *dh;
uint8_t buf[GSM_MACBLOCK_LEN];
+ if (len > sizeof(buf)) {
+ LOGP(DRSL, LOGL_ERROR,
+ "Cannot send IMMEDIATE ASSIGNMENT message with excessive length (%u)\n", len);
+ return NULL;
+ }
+
+ msg = rsl_msgb_alloc();
+
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_IMMEDIATE_ASSIGN_CMD);
dh->chan_nr = RSL_CHAN_PCH_AGCH;
@@ -731,12 +982,12 @@ struct msgb *rsl_imm_assign_cmd_common(struct gsm_bts *bts, uint8_t len, uint8_t
break;
}
- msg->dst = bts->c0->rsl_link;
+ msg->dst = bts->c0->rsl_link_primary;
return msg;
}
/* Chapter 8.5.6 */
-int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val)
+int rsl_imm_assign_cmd(const struct gsm_bts *bts, uint8_t len, const uint8_t *val)
{
struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val);
if (!msg)
@@ -744,17 +995,24 @@ int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val)
return abis_rsl_sendmsg(msg);
}
-/* Chapter 8.5.6 */
-int rsl_ericsson_imm_assign_cmd(struct gsm_bts *bts, uint32_t tlli, uint8_t len, uint8_t *val)
+/* Chapter 8.5.6 Immediate Assignment Command (with Ericcson vendor specific RSL extension) */
+int rsl_ericsson_imm_assign_cmd(const struct gsm_bts *bts, uint32_t msg_id, uint8_t len,
+ const uint8_t *val, uint8_t pag_grp, bool confirm)
{
struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val);
if (!msg)
return 1;
+ /* Append ericsson proprietary paging group IE, this will instruct the BTS to
+ * send this immediate assignment through PCH instead of AGCH. */
+ msgb_tv_put(msg, RSL_IE_ERIC_PAGING_GROUP, pag_grp);
+
/* ericsson can handle a reference at the end of the message which is used in
* the confirm message. The confirm message is only sent if the trailer is present */
- msgb_put_u8(msg, RSL_IE_ERIC_MOBILE_ID);
- msgb_put_u32(msg, tlli);
+ if (confirm) {
+ msgb_put_u8(msg, RSL_IE_ERIC_MOBILE_ID);
+ msgb_put_u32(msg, msg_id);
+ }
return abis_rsl_sendmsg(msg);
}
@@ -762,37 +1020,178 @@ int rsl_ericsson_imm_assign_cmd(struct gsm_bts *bts, uint32_t tlli, uint8_t len,
/* Send Siemens specific MS RF Power Capability Indication */
int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci)
{
- struct msgb *msg = rsl_msgb_alloc();
+ struct msgb *msg;
struct abis_rsl_dchan_hdr *dh;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
+
+ msg = rsl_msgb_alloc();
+
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_SIEMENS_MRPCI);
dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
- dh->chan_nr = gsm_lchan2chan_nr(lchan);
+ dh->chan_nr = chan_nr;
msgb_tv_put(msg, RSL_IE_SIEMENS_MRPCI, *(uint8_t *)mrpci);
DEBUGP(DRSL, "%s TX Siemens MRPCI 0x%02x\n",
gsm_lchan_name(lchan), *(uint8_t *)mrpci);
- msg->dst = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
+/* For 3GPP TS 52.402 unsuccReqsForService, we need to decode the DTAP and count CM Service Reject messages. */
+static void count_unsucc_reqs_for_service(const struct msgb *msg)
+{
+ struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+ const struct gsm48_hdr *gh;
+ uint8_t pdisc, mtype;
+ uint8_t cause;
+
+ if (msgb_l3len(msg) < sizeof(*gh))
+ return;
+
+ gh = msgb_l3(msg);
+ pdisc = gsm48_hdr_pdisc(gh);
+ mtype = gsm48_hdr_msg_type(gh);
+
+ if (pdisc != GSM48_PDISC_MM || mtype != GSM48_MT_MM_CM_SERV_REJ)
+ return;
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ));
+
+ cause = gh->data[0];
+ switch (cause) {
+ case GSM48_REJECT_IMSI_UNKNOWN_IN_HLR:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_IMSI_UNKNOWN_IN_HLR));
+ break;
+ case GSM48_REJECT_ILLEGAL_MS:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_ILLEGAL_MS));
+ break;
+ case GSM48_REJECT_IMSI_UNKNOWN_IN_VLR:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_IMSI_UNKNOWN_IN_VLR));
+ break;
+ case GSM48_REJECT_IMEI_NOT_ACCEPTED:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_IMEI_NOT_ACCEPTED));
+ break;
+ case GSM48_REJECT_ILLEGAL_ME:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_ILLEGAL_ME));
+ break;
+ case GSM48_REJECT_PLMN_NOT_ALLOWED:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_PLMN_NOT_ALLOWED));
+ break;
+ case GSM48_REJECT_LOC_NOT_ALLOWED:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_LOC_NOT_ALLOWED));
+ break;
+ case GSM48_REJECT_ROAMING_NOT_ALLOWED:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_ROAMING_NOT_ALLOWED));
+ break;
+ case GSM48_REJECT_NETWORK_FAILURE:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_NETWORK_FAILURE));
+ break;
+ case GSM48_REJECT_SYNCH_FAILURE:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_SYNCH_FAILURE));
+ break;
+ case GSM48_REJECT_CONGESTION:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_CONGESTION));
+ break;
+ case GSM48_REJECT_SRV_OPT_NOT_SUPPORTED:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_SRV_OPT_NOT_SUPPORTED));
+ break;
+ case GSM48_REJECT_RQD_SRV_OPT_NOT_SUPPORTED:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_RQD_SRV_OPT_NOT_SUPPORTED));
+ break;
+ case GSM48_REJECT_SRV_OPT_TMP_OUT_OF_ORDER:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_SRV_OPT_TMP_OUT_OF_ORDER));
+ break;
+ case GSM48_REJECT_CALL_CAN_NOT_BE_IDENTIFIED:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_CALL_CAN_NOT_BE_IDENTIFIED));
+ break;
+ case GSM48_REJECT_INCORRECT_MESSAGE:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_INCORRECT_MESSAGE));
+ break;
+ case GSM48_REJECT_INVALID_MANDANTORY_INF:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_INVALID_MANDANTORY_INF));
+ break;
+ case GSM48_REJECT_MSG_TYPE_NOT_IMPLEMENTED:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_MSG_TYPE_NOT_IMPLEMENTED));
+ break;
+ case GSM48_REJECT_MSG_TYPE_NOT_COMPATIBLE:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_MSG_TYPE_NOT_COMPATIBLE));
+ break;
+ case GSM48_REJECT_INF_ELEME_NOT_IMPLEMENTED:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_INF_ELEME_NOT_IMPLEMENTED));
+ break;
+ case GSM48_REJECT_CONDTIONAL_IE_ERROR:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_CONDTIONAL_IE_ERROR));
+ break;
+ case GSM48_REJECT_MSG_NOT_COMPATIBLE:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_MSG_NOT_COMPATIBLE));
+ break;
+ default:
+ if (cause >= 48 && cause <= 63) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_RETRY_IN_NEW_CELL));
+ break;
+ }
+ /* else fall thru */
+ case GSM48_REJECT_PROTOCOL_ERROR:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CM_SERV_REJ_PROTOCOL_ERROR));
+ break;
+ }
+}
+
/* Send "DATA REQUEST" message with given L3 Info payload */
/* Chapter 8.3.1 */
int rsl_data_request(struct msgb *msg, uint8_t link_id)
{
+ int chan_nr;
+
if (msg->lchan == NULL) {
LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n");
+ msgb_free(msg);
return -EINVAL;
}
- rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, gsm_lchan2chan_nr(msg->lchan),
- link_id, 1);
+ count_unsucc_reqs_for_service(msg);
- msg->dst = msg->lchan->ts->trx->rsl_link;
+ chan_nr = gsm_lchan2chan_nr(msg->lchan, true);
+ if (chan_nr < 0) {
+ msgb_free(msg);
+ return chan_nr;
+ }
+
+ rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, chan_nr, link_id, 1);
+
+ msg->dst = rsl_chan_link(msg->lchan);
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/* Send "UNIT DATA REQUEST" message with given L3 Info payload */
+/* Chapter 8.3.11 */
+int rsl_unit_data_request(struct msgb *msg, uint8_t link_id)
+{
+ int chan_nr;
+
+ if (msg->lchan == NULL) {
+ LOGP(DRSL, LOGL_ERROR, "cannot send UNIT DATA REQUEST to unknown lchan\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
+
+ chan_nr = gsm_lchan2chan_nr(msg->lchan, true);
+ if (chan_nr < 0) {
+ msgb_free(msg);
+ return chan_nr;
+ }
+
+ rsl_rll_push_l3(msg, RSL_MT_UNIT_DATA_REQ, chan_nr, link_id, 1);
+
+ msg->dst = rsl_chan_link(msg->lchan);
return abis_rsl_sendmsg(msg);
}
@@ -802,10 +1201,12 @@ int rsl_data_request(struct msgb *msg, uint8_t link_id)
int rsl_establish_request(struct gsm_lchan *lchan, uint8_t link_id)
{
struct msgb *msg;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
- msg = rsl_rll_simple(RSL_MT_EST_REQ, gsm_lchan2chan_nr(lchan),
- link_id, 0);
- msg->dst = lchan->ts->trx->rsl_link;
+ msg = rsl_rll_simple(RSL_MT_EST_REQ, chan_nr, link_id, 0);
+ msg->dst = rsl_chan_link(lchan);
DEBUGP(DRLL, "%s RSL RLL ESTABLISH REQ (link_id=0x%02x)\n",
gsm_lchan_name(lchan), link_id);
@@ -823,13 +1224,15 @@ int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id,
{
struct msgb *msg;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
- msg = rsl_rll_simple(RSL_MT_REL_REQ, gsm_lchan2chan_nr(lchan),
- link_id, 0);
+ msg = rsl_rll_simple(RSL_MT_REL_REQ, chan_nr, link_id, 0);
/* 0 is normal release, 1 is local end */
msgb_tv_put(msg, RSL_IE_RELEASE_MODE, release_mode);
- msg->dst = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
DEBUGP(DRLL, "%s RSL RLL RELEASE REQ (link_id=0x%02x, reason=%u)\n",
gsm_lchan_name(lchan), link_id, release_mode);
@@ -842,7 +1245,7 @@ int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id,
static bool msg_for_osmocom_dyn_ts(struct msgb *msg)
{
struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
- if (msg->lchan->ts->pchan_on_init != GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ if (msg->lchan->ts->pchan_on_init != GSM_PCHAN_OSMO_DYN)
return false;
/* dyn TS messages always come in on the first lchan of a timeslot */
if (msg->lchan->nr != 0)
@@ -858,7 +1261,7 @@ static int rsl_rx_chan_act_nack(struct msgb *msg)
struct gsm_lchan *lchan = msg->lchan;
const uint8_t *cause_p;
- rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msg->lchan->ts->trx->bts->bts_ctrs, BTS_CTR_CHAN_ACT_NACK));
if (dh->ie_chan != RSL_IE_CHAN_NR) {
LOG_LCHAN(msg->lchan, LOGL_ERROR, "Invalid IE: expected CHAN_NR IE (0x%x), got 0x%x\n",
@@ -866,7 +1269,12 @@ static int rsl_rx_chan_act_nack(struct msgb *msg)
return -EINVAL;
}
- rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(dh->c.msg_type));
+ return -EINVAL;
+ }
+
cause_p = rsl_cause(&tp);
LOG_LCHAN(lchan, LOGL_ERROR, "CHANNEL ACTIVATE NACK%s\n", rsl_cause_name(&tp));
@@ -882,86 +1290,131 @@ static int rsl_rx_conn_fail(struct msgb *msg)
{
struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
struct gsm_lchan *lchan = msg->lchan;
+ struct rate_ctr_group *bts_ctrs = lchan->ts->trx->bts->bts_ctrs;
struct tlv_parsed tp;
const uint8_t *cause_p;
- rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(dh->c.msg_type));
+ return -EINVAL;
+ }
+
cause_p = rsl_cause(&tp);
LOG_LCHAN(lchan, LOGL_ERROR, "CONNECTION FAIL%s\n", rsl_cause_name(&tp));
- rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_CHAN_RF_FAIL));
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_CHAN_RF_FAIL_SDCCH));
+ break;
+ case GSM_LCHAN_TCH_H:
+ case GSM_LCHAN_TCH_F:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_CHAN_RF_FAIL_TCH));
+ break;
+ default:
+ break;
+ }
+
+ /* Report to VGCS FSM */
+ if (lchan_is_asci(lchan)) {
+ if (lchan->conn && lchan->conn->vgcs_chan.fi) {
+ uint8_t cause = GSM0808_CAUSE_RADIO_INTERFACE_FAILURE;
+ osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_FAIL, &cause);
+ }
+ return 0;
+ }
/* If the lchan is associated with a conn, we shall notify the MSC of the RSL Conn Failure, and
* the connection will presumably be torn down and lead to an lchan release. During initial
* Channel Request from the MS, an lchan has no conn yet, so in that case release now. */
if (!lchan->conn)
- lchan_release(lchan, false, true, *cause_p);
+ lchan_release(lchan, false, true, *cause_p, NULL);
else
osmo_fsm_inst_dispatch(lchan->conn->fi, GSCON_EV_RSL_CONN_FAIL, (void*)cause_p);
return 0;
}
-static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru,
- const char *prefix)
+static void print_meas_rep_uni(struct osmo_strbuf *sb,
+ const struct gsm_meas_rep_unidir *mru,
+ const char *prefix)
{
- DEBUGPC(DMEAS, "RXL-FULL-%s=%3ddBm RXL-SUB-%s=%3ddBm ",
- prefix, rxlev2dbm(mru->full.rx_lev),
- prefix, rxlev2dbm(mru->sub.rx_lev));
- DEBUGPC(DMEAS, "RXQ-FULL-%s=%d RXQ-SUB-%s=%d ",
- prefix, mru->full.rx_qual, prefix, mru->sub.rx_qual);
+ OSMO_STRBUF_PRINTF(*sb, "RXL-FULL-%s=%3ddBm RXL-SUB-%s=%3ddBm ",
+ prefix, rxlev2dbm(mru->full.rx_lev),
+ prefix, rxlev2dbm(mru->sub.rx_lev));
+ OSMO_STRBUF_PRINTF(*sb, "RXQ-FULL-%s=%d RXQ-SUB-%s=%d ",
+ prefix, mru->full.rx_qual, prefix, mru->sub.rx_qual);
}
-static void print_meas_rep(struct gsm_lchan *lchan, struct gsm_meas_rep *mr)
+static int print_meas_rep_buf(char *buf, size_t len, const struct gsm_meas_rep *mr)
{
- int i;
- const char *name = "";
- struct bsc_subscr *bsub = NULL;
+ struct osmo_strbuf sb = { .buf = buf, .len = len };
- if (lchan && lchan->conn) {
- bsub = lchan->conn->bsub;
- if (bsub) {
- log_set_context(LOG_CTX_BSC_SUBSCR, bsub);
- name = bsc_subscr_name(bsub);
- } else
- name = lchan->name;
- }
-
- DEBUGP(DMEAS, "[%s] MEASUREMENT RESULT NR=%d ", name, mr->nr);
+ OSMO_STRBUF_PRINTF(sb, "MEASUREMENT RESULT NR=%d ", mr->nr);
if (mr->flags & MEAS_REP_F_DL_DTX)
- DEBUGPC(DMEAS, "DTXd ");
+ OSMO_STRBUF_PRINTF(sb, "DTXd ");
- print_meas_rep_uni(&mr->ul, "ul");
- DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power);
+ print_meas_rep_uni(&sb, &mr->ul, "ul");
+ OSMO_STRBUF_PRINTF(sb, "BS_POWER=%ddB ", mr->bs_power_db);
if (mr->flags & MEAS_REP_F_MS_TO)
- DEBUGPC(DMEAS, "MS_TO=%d ", mr->ms_timing_offset);
+ OSMO_STRBUF_PRINTF(sb, "MS_TO=%d ", mr->ms_timing_offset);
if (mr->flags & MEAS_REP_F_MS_L1) {
- DEBUGPC(DMEAS, "L1_MS_PWR=%3ddBm ", mr->ms_l1.pwr);
- DEBUGPC(DMEAS, "L1_FPC=%u ",
- mr->flags & MEAS_REP_F_FPC ? 1 : 0);
- DEBUGPC(DMEAS, "L1_TA=%u ", mr->ms_l1.ta);
+ OSMO_STRBUF_PRINTF(sb, "L1_MS_PWR=%3ddBm ", mr->ms_l1.pwr);
+ OSMO_STRBUF_PRINTF(sb, "L1_FPC=%u ", mr->flags & MEAS_REP_F_FPC ? 1 : 0);
+ OSMO_STRBUF_PRINTF(sb, "L1_TA=%u ", mr->ms_l1.ta);
}
if (mr->flags & MEAS_REP_F_UL_DTX)
- DEBUGPC(DMEAS, "DTXu ");
+ OSMO_STRBUF_PRINTF(sb, "DTXu ");
if (mr->flags & MEAS_REP_F_BA1)
- DEBUGPC(DMEAS, "BA1 ");
+ OSMO_STRBUF_PRINTF(sb, "BA1 ");
if (!(mr->flags & MEAS_REP_F_DL_VALID))
- DEBUGPC(DMEAS, "NOT VALID ");
+ OSMO_STRBUF_PRINTF(sb, "NOT VALID ");
else
- print_meas_rep_uni(&mr->dl, "dl");
+ print_meas_rep_uni(&sb, &mr->dl, "dl");
- DEBUGPC(DMEAS, "NUM_NEIGH=%u\n", mr->num_cell);
- if (mr->num_cell == 7)
- return;
- for (i = 0; i < mr->num_cell; i++) {
- struct gsm_meas_rep_cell *mrc = &mr->cell[i];
- DEBUGP(DMEAS, "IDX=%u ARFCN=%u BSIC=%u => %d dBm\n",
- mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev));
+ OSMO_STRBUF_PRINTF(sb, "NUM_NEIGH=%u", mr->num_cell);
+
+ return sb.chars_needed;
+}
+
+static char *print_meas_rep_c(void *ctx, const struct gsm_meas_rep *mr)
+{
+ /* A naive count of required characters gets me to ~200, so 256 should be safe to get a large enough buffer on
+ * the first time. */
+ OSMO_NAME_C_IMPL(ctx, 256, "ERROR", print_meas_rep_buf, mr)
+}
+
+static void print_meas_rep(struct gsm_lchan *lchan, const struct gsm_meas_rep *mr)
+{
+ int i;
+ const char *name = "";
+ struct bsc_subscr *bsub = NULL;
+
+ if (lchan && lchan->conn) {
+ bsub = lchan->conn->bsub;
+ if (bsub) {
+ log_set_context(LOG_CTX_BSC_SUBSCR, bsub);
+ name = bsc_subscr_name(bsub);
+ } else {
+ name = lchan->name;
+ }
+ }
+
+ DEBUGP(DMEAS, "[%s] %s\n", name, print_meas_rep_c(OTC_SELECT, mr));
+
+ if (mr->num_cell != 7
+ && log_check_level(DMEAS, LOGL_DEBUG)) {
+ for (i = 0; i < mr->num_cell; i++) {
+ const struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+ DEBUGP(DMEAS, "IDX=%u ARFCN=%u BSIC=%u RXLEV=%ddBm\n",
+ mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev));
+ }
}
if (bsub)
@@ -989,6 +1442,7 @@ static int rsl_rx_meas_res(struct msgb *msg)
uint8_t len;
const uint8_t *val;
int rc;
+ uint8_t bs_power_enc;
if (!lchan_may_receive_data(msg->lchan)) {
LOG_LCHAN(msg->lchan, LOGL_DEBUG, "MEAS RES for inactive channel\n");
@@ -998,7 +1452,11 @@ static int rsl_rx_meas_res(struct msgb *msg)
memset(mr, 0, sizeof(*mr));
mr->lchan = msg->lchan;
- rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(dh->c.msg_type));
+ return -EINVAL;
+ }
if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) ||
!TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) ||
@@ -1022,7 +1480,8 @@ static int rsl_rx_meas_res(struct msgb *msg)
mr->ul.sub.rx_qual = val[2] & 0x7;
}
- mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER);
+ bs_power_enc = *TLVP_VAL(&tp, RSL_IE_BS_POWER);
+ mr->bs_power_db = (bs_power_enc & 0x0f) * 2;
/* Optional Parts */
if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET)) {
@@ -1040,12 +1499,13 @@ static int rsl_rx_meas_res(struct msgb *msg)
if (val[0] & 0x04)
mr->flags |= MEAS_REP_F_FPC;
mr->ms_l1.ta = val[1];
- /* BS11 and Nokia reports TA shifted by 2 bits */
+ /* BS11, Nokia and RBS report TA shifted by 2 bits */
if (msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11
- || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE)
+ || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE
+ || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_RBS2000)
mr->ms_l1.ta >>= 2;
- /* store TA for next assignment/handover */
- mr->lchan->rqd_ta = mr->ms_l1.ta;
+ /* store TA for handover decision, and for intra-cell re-assignment */
+ mr->lchan->last_ta = mr->ms_l1.ta;
}
if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO);
@@ -1061,6 +1521,8 @@ static int rsl_rx_meas_res(struct msgb *msg)
print_meas_rep(msg->lchan, mr);
+ lchan_ms_pwr_ctrl(msg->lchan, mr);
+
send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr);
return 0;
@@ -1075,7 +1537,11 @@ static int rsl_rx_hando_det(struct msgb *msg)
.msg = msg,
};
- rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(dh->c.msg_type));
+ return -EINVAL;
+ }
if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY))
d.access_delay = TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY);
@@ -1091,6 +1557,66 @@ static int rsl_rx_hando_det(struct msgb *msg)
return 0;
}
+/* Chapter 8.4.21: TALKER DETECTION */
+static int rsl_rx_talker_det(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+ struct tlv_parsed tp;
+ /* We use this struct, because it has same data. */
+ struct handover_rr_detect_data d = {
+ .msg = msg,
+ };
+
+ if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(dh->c.msg_type));
+ return -EINVAL;
+ }
+
+ if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY))
+ d.access_delay = TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY);
+
+ if (!msg->lchan->conn || !msg->lchan->conn->vgcs_chan.fi) {
+ LOGP(DRSL, LOGL_ERROR, "%s TALKER DETECTION but no VGCS channel\n",
+ gsm_lchan_name(msg->lchan));
+ return 0;
+ }
+
+ osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_DET, &d);
+
+ return 0;
+}
+
+/* Chapter 8.4.22: LISTENER DETECTION */
+static int rsl_rx_listener_det(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+ struct tlv_parsed tp;
+ /* We use this struct, because it has same data. */
+ struct handover_rr_detect_data d = {
+ .msg = msg,
+ };
+
+ if (rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(dh->c.msg_type));
+ return -EINVAL;
+ }
+
+ if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY))
+ d.access_delay = TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY);
+
+ if (!msg->lchan->conn || !msg->lchan->conn->vgcs_chan.fi) {
+ LOGP(DRSL, LOGL_ERROR, "%s LISTENER DETECTION but no VGCS channel\n",
+ gsm_lchan_name(msg->lchan));
+ return 0;
+ }
+
+ osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_LISTENER_DET, &d);
+
+ return 0;
+}
+
static int rsl_rx_ipacc_pdch(struct msgb *msg, char *name, uint32_t ts_ev)
{
struct gsm_bts_trx_ts *ts = msg->lchan->ts;
@@ -1111,6 +1637,9 @@ static int abis_rsl_rx_dchan(struct msgb *msg)
int rc = 0;
struct e1inp_sign_link *sign_link = msg->dst;
+ if (msgb_l2len(msg) < sizeof(*rslh))
+ return -EINVAL;
+
if (rslh->ie_chan != RSL_IE_CHAN_NR) {
LOGP(DRSL, LOGL_ERROR,
"Rx RSL DCHAN: invalid RSL header, expecting Channel Number IE tag, got 0x%x\n",
@@ -1136,6 +1665,10 @@ static int abis_rsl_rx_dchan(struct msgb *msg)
switch (rslh->c.msg_type) {
case RSL_MT_CHAN_ACTIV_ACK:
+ /* Ignore acknowlegement of channel reactivation, if a VGCS/VBS channel was reactivated to assign
+ * the calling subscriber to it. */
+ if (msg->lchan->conn && msg->lchan->conn->assignment.req.vgcs)
+ break;
if (msg_for_osmocom_dyn_ts(msg))
osmo_fsm_inst_dispatch(msg->lchan->ts->fi, TS_EV_PDCH_ACT_ACK, NULL);
else {
@@ -1155,6 +1688,12 @@ static int abis_rsl_rx_dchan(struct msgb *msg)
case RSL_MT_HANDO_DET:
rc = rsl_rx_hando_det(msg);
break;
+ case RSL_MT_TALKER_DET:
+ rc = rsl_rx_talker_det(msg);
+ break;
+ case RSL_MT_LISTENER_DET:
+ rc = rsl_rx_listener_det(msg);
+ break;
case RSL_MT_RF_CHAN_REL_ACK:
if (msg_for_osmocom_dyn_ts(msg))
osmo_fsm_inst_dispatch(msg->lchan->ts->fi, TS_EV_PDCH_DEACT_ACK, NULL);
@@ -1164,10 +1703,12 @@ static int abis_rsl_rx_dchan(struct msgb *msg)
case RSL_MT_MODE_MODIFY_ACK:
LOG_LCHAN(msg->lchan, LOGL_DEBUG, "CHANNEL MODE MODIFY ACK\n");
count_codecs(sign_link->trx->bts, msg->lchan);
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RSL_CHAN_MODE_MODIFY_ACK, NULL);
break;
case RSL_MT_MODE_MODIFY_NACK:
LOG_LCHAN(msg->lchan, LOGL_DEBUG, "CHANNEL MODE MODIFY NACK\n");
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_MODE_MODIFY_NACK]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_MODE_MODIFY_NACK));
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK, NULL);
break;
case RSL_MT_IPAC_PDCH_ACT_ACK:
rc = rsl_rx_ipacc_pdch(msg, "ACT ACK", TS_EV_PDCH_ACT_ACK);
@@ -1183,20 +1724,18 @@ static int abis_rsl_rx_dchan(struct msgb *msg)
break;
case RSL_MT_PHY_CONTEXT_CONF:
case RSL_MT_PREPROC_MEAS_RES:
- case RSL_MT_TALKER_DET:
- case RSL_MT_LISTENER_DET:
case RSL_MT_REMOTE_CODEC_CONF_REP:
case RSL_MT_MR_CODEC_MOD_ACK:
case RSL_MT_MR_CODEC_MOD_NACK:
case RSL_MT_MR_CODEC_MOD_PER:
LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unimplemented Abis RSL DChan msg 0x%02x\n",
rslh->c.msg_type);
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN));
break;
default:
LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unknown Abis RSL DChan msg 0x%02x\n",
rslh->c.msg_type);
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN));
return -EINVAL;
}
@@ -1209,7 +1748,14 @@ static int rsl_rx_error_rep(struct msgb *msg)
struct tlv_parsed tp;
struct e1inp_sign_link *sign_link = msg->dst;
- rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh));
+ if (msgb_l2len(msg) < sizeof(*rslh))
+ return -EINVAL;
+
+ if (rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh)) < 0) {
+ LOGP(DRSL, LOGL_ERROR, "%s Failed to parse RSL %s\n",
+ gsm_trx_name(sign_link->trx), rsl_or_ipac_msg_name(rslh->msg_type));
+ return -EINVAL;
+ }
LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT%s\n",
gsm_trx_name(sign_link->trx), rsl_cause_name(&tp));
@@ -1217,6 +1763,81 @@ static int rsl_rx_error_rep(struct msgb *msg)
return 0;
}
+static int rsl_rx_resource_indication(struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+ struct tlv_parsed tp;
+ struct e1inp_sign_link *sign_link = msg->dst;
+ struct tlv_p_entry *res_info_ie;
+ struct gsm_bts_trx *trx = sign_link->trx;
+ struct gsm_lchan *lchan;
+ int ts_nr;
+ int i;
+
+ LOGP(DRSL, LOGL_DEBUG, "%s Rx Resource Indication\n", gsm_trx_name(trx));
+
+ /* First clear out all ratings, because only the last resource indication counts. If we can't parse the message,
+ * then there are no ratings. */
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
+ lchan->interf_dbm = INTERF_DBM_UNKNOWN;
+ lchan->interf_band = INTERF_BAND_UNKNOWN;
+ }
+ }
+
+ if (rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh)) < 0) {
+ LOGP(DRSL, LOGL_ERROR, "%s Failed to parse RSL %s\n",
+ gsm_trx_name(trx), rsl_or_ipac_msg_name(rslh->msg_type));
+ return -EINVAL;
+ }
+
+ res_info_ie = TLVP_GET(&tp, RSL_IE_RESOURCE_INFO);
+ if (!res_info_ie) {
+ LOGP(DRSL, LOGL_ERROR, "Rx Resource Indication: missing Resource Info IE\n");
+ return -EINVAL;
+ }
+
+ /* The IE value is defined in 3GPP TS 48.058 9.3.21 Resource Information:
+ * one octet channel nr, one octet interference level, channel nr, interference level, ...
+ * Where channel nr is cbits + tn (as usual),
+ * and interference level is a 3bit value in the most significant bits of the octet.
+ * Evaluate each pair and update interference ratings for all lchans in this trx. */
+
+ /* There must be an even amount of octets in the value */
+ if (res_info_ie->len & 1) {
+ LOGP(DRSL, LOGL_ERROR, "Rx Resource Indication: Resource Info IE has odd length\n");
+ return -EINVAL;
+ }
+
+ /* Now iterate the reported levels and update corresponding lchans.
+ * Note that an empty res_info_ie can also make sense, if no lchans are idle and no interference ratings are
+ * present. The practical effect of the message then is to invalidate previous interference ratings. */
+ for (i = 0; i < res_info_ie->len; i += 2) {
+ struct gsm_bts *bts = trx->bts;
+ uint8_t chan_nr = res_info_ie->val[i];
+ uint8_t interf_band = res_info_ie->val[i + 1] >> 5;
+
+ lchan = lchan_lookup(trx, chan_nr, "Abis RSL Rx Resource Indication: ");
+ if (!lchan)
+ continue;
+
+ /* Store the actual received index */
+ lchan->interf_band = interf_band;
+ /* Clamp the index to 5 before accessing array of interference band bounds */
+ interf_band = OSMO_MIN(interf_band, ARRAY_SIZE(bts->interf_meas_params_used.bounds_dbm)-1);
+ /* FIXME: when testing with ip.access nanoBTS, we observe a value range of 1..6. According to spec, it
+ * seems like values 0..5 are intended: 3GPP TS 48.058 9.3.21 Resource Information says:
+ * "The Interf Band field (bits 6-8) indicates in binary the interference level expressed as one of five
+ * possible interference level bands as defined by O&M."
+ * and 3GPP TS 52.021 9.4.25 "Interference level Boundaries" (OML) defines values 0, X1, X2, X3, X4, X5.
+ * If nanoBTS sends 6, the above code clamps it to 5, so that we lose one band in accuracy. */
+ lchan->interf_dbm = -((int16_t)bts->interf_meas_params_used.bounds_dbm[interf_band]);
+ }
+
+ return 0;
+}
+
static int abis_rsl_rx_trx(struct msgb *msg)
{
struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
@@ -1229,7 +1850,7 @@ static int abis_rsl_rx_trx(struct msgb *msg)
break;
case RSL_MT_RF_RES_IND:
/* interference on idle channels of TRX */
- //DEBUGP(DRSL, "%s RF Resource Indication\n", gsm_trx_name(sign_link->trx));
+ rc = rsl_rx_resource_indication(msg);
break;
case RSL_MT_OVERLOAD:
/* indicate CCCH / ACCH / processor overload */
@@ -1245,7 +1866,7 @@ static int abis_rsl_rx_trx(struct msgb *msg)
default:
LOGP(DRSL, LOGL_NOTICE, "%s Unknown Abis RSL TRX message "
"type 0x%02x\n", gsm_trx_name(sign_link->trx), rslh->msg_type);
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN));
return -EINVAL;
}
return rc;
@@ -1283,7 +1904,15 @@ static int rsl_send_imm_ass_rej(struct gsm_bts *bts,
/* we need to subtract 1 byte from sizeof(*iar) since ia includes the l2_plen field */
iar->l2_plen = GSM48_LEN2PLEN((sizeof(*iar)-1));
- return rsl_imm_assign_cmd(bts, sizeof(*iar), (uint8_t *) iar);
+ /* IAR Rest Octets:
+ * 0... .... = Extended RA: Not Present
+ * .0.. .... = Extended RA: Not Present
+ * ..0. .... = Extended RA: Not Present
+ * ...0 .... = Extended RA: Not Present
+ * .... L... = Additions in Rel-13: Not Present */
+ iar->rest[0] = GSM_MACBLOCK_PADDING & 0x0f;
+
+ return rsl_imm_assign_cmd(bts, sizeof(*iar) + 1, buf);
}
int rsl_tx_imm_ass_rej(struct gsm_bts *bts, struct gsm48_req_ref *rqd_ref)
@@ -1291,166 +1920,533 @@ int rsl_tx_imm_ass_rej(struct gsm_bts *bts, struct gsm48_req_ref *rqd_ref)
uint8_t wait_ind;
wait_ind = bts->T3122;
if (!wait_ind)
- wait_ind = T_def_get(bts->network->T_defs, 3122, T_S, -1);
+ wait_ind = osmo_tdef_get(bts->network->T_defs, 3122, OSMO_TDEF_S, -1);
if (!wait_ind)
wait_ind = GSM_T3122_DEFAULT;
/* The BTS will gather multiple CHAN RQD and reject up to 4 MS at the same time. */
return rsl_send_imm_ass_rej(bts, rqd_ref, wait_ind);
}
+struct chan_rqd {
+ struct llist_head entry;
+ struct gsm_bts *bts;
+ struct gsm48_req_ref ref;
+ enum gsm_chreq_reason_t reason;
+ uint8_t ta;
+ /* set to true to mark that the release of the release_lchan is in progress */
+ struct gsm_lchan *release_lchan;
+ time_t timestamp;
+};
+
/* Handle packet channel rach requests */
-static int rsl_rx_pchan_rqd(struct msgb *msg, struct gsm_bts *bts)
+static int rsl_rx_pchan_rqd(struct chan_rqd *rqd)
{
- struct gsm48_req_ref *rqd_ref;
- struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg);
- rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1];
- uint8_t ra = rqd_ref->ra;
- uint8_t t1, t2, t3;
uint32_t fn;
uint8_t rqd_ta;
uint8_t is_11bit;
+ struct gsm_time gsm_time;
/* Process rach request and forward contained information to PCU */
- if (ra == 0x7F) {
+ if (rqd->ref.ra == 0x7F) {
is_11bit = 1;
/* FIXME: Also handle 11 bit rach requests */
- LOGP(DRSL, LOGL_ERROR, "BTS %d eleven bit access burst not supported yet!\n",bts->nr);
+ LOGP(DRSL, LOGL_ERROR, "BTS %d eleven bit access burst not supported yet!\n", rqd->bts->nr);
return -EINVAL;
} else {
is_11bit = 0;
- t1 = rqd_ref->t1;
- t2 = rqd_ref->t2;
- t3 = rqd_ref->t3_low | (rqd_ref->t3_high << 3);
- fn = (51 * ((t3-t2) % 26) + t3 + 51 * 26 * t1);
+ rqd_ta = rqd->ta;
- rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2];
+ gsm_time.t1 = rqd->ref.t1;
+ gsm_time.t2 = rqd->ref.t2;
+ gsm_time.t3 = rqd->ref.t3_low | (rqd->ref.t3_high << 3);
+ fn = gsm_gsmtime2fn(&gsm_time);
+
+ LOG_BTS(rqd->bts, DRSL, LOGL_INFO, "CHAN RQD: fn(t1=%u,t3=%u,t2=%u) = %u\n",
+ gsm_time.t1, gsm_time.t3, gsm_time.t2, fn);
}
- return pcu_tx_rach_ind(bts, rqd_ta, ra, fn, is_11bit,
+ return pcu_tx_rach_ind(rqd->bts, rqd_ta, rqd->ref.ra, fn, is_11bit,
GSM_L1_BURST_TYPE_ACCESS_0);
}
+/* Protect against RACH DoS attack: If an excessive amount of RACH requests queues up it is likely that the current BTS
+ * is under RACH DoS attack. To prevent excessive memory usage, remove all expired or at least one of the oldest channel
+ * requests from the queue to prevent the queue from growing indefinetly. */
+static void reduce_rach_dos(struct gsm_bts *bts)
+{
+ time_t timestamp_current = time(NULL);
+ struct chan_rqd *rqd;
+ struct chan_rqd *rqd_tmp;
+ unsigned int rqd_count = 0;
+
+ /* Drop all expired channel requests in the list */
+ llist_for_each_entry_safe(rqd, rqd_tmp, &bts->chan_rqd_queue, entry) {
+ /* If the channel request is older than the rach expiry timeout we drop it. This also means that the
+ * queue is under its overflow limit again. */
+ if (timestamp_current - rqd->timestamp > bts->rach_expiry_timeout) {
+ LOG_BTS(bts, DRSL, LOGL_INFO, "CHAN RQD: tossing expired channel request"
+ "(ra=0x%02x, neci=0x%02x, chreq_reason=0x%02x)\n",
+ rqd->ref.ra, bts->network->neci, rqd->reason);
+ llist_del(&rqd->entry);
+ talloc_free(rqd);
+ } else {
+ rqd_count++;
+ }
+ }
+
+ /* If we find more than 255 (256) unexpired channel requests in the queue it is very likely that there is a
+ * problem with RACH dos on this BTS. We drop the first entry in the list to clip the growth of the list. */
+ if (rqd_count > 255) {
+ LOG_BTS(bts, DRSL, LOGL_INFO, "CHAN RQD: more than 255 queued RACH requests -- RACH DoS attack?\n");
+ rqd = llist_first_entry(&bts->chan_rqd_queue, struct chan_rqd, entry);
+ llist_del(&rqd->entry);
+ talloc_free(rqd);
+ }
+}
+
+/* Flush all channel requests pending on this BTS */
+void abis_rsl_chan_rqd_queue_flush(struct gsm_bts *bts)
+{
+ struct chan_rqd *rqd;
+ struct chan_rqd *rqd_tmp;
+
+ llist_for_each_entry_safe(rqd, rqd_tmp, &bts->chan_rqd_queue, entry) {
+ llist_del(&rqd->entry);
+ talloc_free(rqd);
+ }
+}
+
/* MS has requested a channel on the RACH */
static int rsl_rx_chan_rqd(struct msgb *msg)
{
- struct lchan_activate_info info;
struct e1inp_sign_link *sign_link = msg->dst;
struct gsm_bts *bts = sign_link->trx->bts;
struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg);
- struct gsm48_req_ref *rqd_ref;
- enum gsm_chan_t lctype;
- enum gsm_chreq_reason_t chreq_reason;
- struct gsm_lchan *lchan;
- uint8_t rqd_ta;
+ struct chan_rqd *rqd;
+
+ reduce_rach_dos(bts);
+
+ rqd = talloc_zero(bts, struct chan_rqd);
+ OSMO_ASSERT(rqd);
+
+ rqd->bts = bts;
+ rqd->timestamp = time(NULL);
/* parse request reference to be used in immediate assign */
- if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE)
+ if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE) {
+ talloc_free(rqd);
return -EINVAL;
-
- rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1];
+ }
+ memcpy(&rqd->ref, &rqd_hdr->data[1], sizeof(rqd->ref));
/* parse access delay and use as TA */
- if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY)
+ if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY) {
+ talloc_free(rqd);
return -EINVAL;
- rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2];
+ }
+ rqd->ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2];
+ if (rqd->ta > bts->rach_max_delay) {
+ LOG_BTS(bts, DRSL, LOGL_INFO, "Ignoring CHAN RQD: Access Delay(%d) greater than %u\n",
+ rqd->ta, bts->rach_max_delay);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_MAX_DELAY_EXCEEDED));
+ talloc_free(rqd);
+ return -EINVAL;
+ }
/* Determine channel request cause code */
- chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci);
- LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: reason: %s (ra=0x%02x, neci=0x%02x, chreq_reason=0x%02x)\n",
- msg->lchan->ts->trx->bts->nr,
- get_value_string(gsm_chreq_descs, chreq_reason),
- rqd_ref->ra, bts->network->neci, chreq_reason);
+ rqd->reason = get_reason_by_chreq(rqd->ref.ra, bts->network->neci);
+ LOG_BTS(bts, DRSL, LOGL_INFO, "CHAN RQD: reason: %s (ra=0x%02x, t1=%d, t3=%d, t2=%d, neci=0x%02x, chreq_reason=0x%02x)\n",
+ get_value_string(gsm_chreq_descs, rqd->reason), rqd->ref.ra,
+ rqd->ref.t1, rqd->ref.t3_high << 3 | rqd->ref.t3_low, rqd->ref.t2,
+ bts->network->neci, rqd->reason);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_TOTAL));
+ switch (rqd->reason) {
+ case GSM_CHREQ_REASON_EMERG:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_EMERG));
+ break;
+ case GSM_CHREQ_REASON_CALL:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_CALL));
+ break;
+ case GSM_CHREQ_REASON_LOCATION_UPD:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_LOCATION_UPD));
+ break;
+ case GSM_CHREQ_REASON_PAG:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_PAG));
+ break;
+ case GSM_CHREQ_REASON_PDCH:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_PDCH));
+ break;
+ case GSM_CHREQ_REASON_OTHER:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_OTHER));
+ break;
+ default:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_ATTEMPTED_UNKNOWN));
+ break;
+ }
+
+ /* Block emergency calls if we explicitly disable them via sysinfo. */
+ if (rqd->reason == GSM_CHREQ_REASON_EMERG) {
+ if (bts->si_common.rach_control.t2 & 0x4) {
+ LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD: MS attempts EMERGENCY CALL although EMERGENCY CALLS "
+ "are not allowed in sysinfo (cfg: network / bts / rach emergency call allowed 0)\n");
+ rsl_tx_imm_ass_rej(bts, &rqd->ref);
+ talloc_free(rqd);
+ return 0;
+ }
+ }
+
+ /* Enqueue request */
+ llist_add_tail(&rqd->entry, &bts->chan_rqd_queue);
+
+ /* Forward the request directly. Most request will be finished with one attempt so no queuing will be
+ * necessary. */
+ abis_rsl_chan_rqd_queue_poll(bts);
+
+ return 0;
+}
+
+/* Find any busy TCH/H or TCH/F lchan */
+static struct gsm_lchan *get_any_lchan(struct gsm_bts *bts)
+{
+ int trx_nr;
+ int ts_nr;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan_est = NULL;
+ struct gsm_lchan *lchan_any = NULL;
+ struct gsm_lchan *lchan;
+
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_for_n_lchans(lchan, ts, ts->max_primary_lchans) {
+ if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H) {
+ if (lchan->fi->state == LCHAN_ST_ESTABLISHED) {
+ if (!lchan_est || bts->chan_alloc_chan_req_reverse)
+ lchan_est = lchan;
+ } else {
+ if (!lchan_any || bts->chan_alloc_chan_req_reverse)
+ lchan_any = lchan;
+ }
+ }
+ }
+ }
+ }
+
+ if (lchan_est)
+ return lchan_est;
+ else if (lchan_any)
+ return lchan_any;
+ return NULL;
+}
+
+/* Ensure that an incoming emergency call gets priority, if all voice channels are busy, terminate one regular call.
+ * Return true if freeing of a busy lchan is in progress, but not done yet, return false when done (either successfully
+ * or unsuccessfully). */
+static bool force_free_lchan_for_emergency(struct chan_rqd *rqd)
+{
+ /* If the request is not about an emergency call, we may exit early, without doing anything. */
+ if (rqd->reason != GSM_CHREQ_REASON_EMERG)
+ return false;
+
+ /* First check the situation on the BTS, if we have TCH/H or TCH/F resources available for another (EMERGENCY)
+ * call. If yes, then no (further) action has to be carried out. */
+ if (lchan_avail_by_type(rqd->bts, GSM_LCHAN_TCH_F, SELECT_FOR_MS_CHAN_REQ, NULL, true)) {
+ LOG_BTS(rqd->bts, DRSL, LOGL_NOTICE,
+ "CHAN RQD/EMERGENCY-PRIORITY: at least one TCH/F is (now) available!\n");
+ return false;
+ }
+ if (lchan_avail_by_type(rqd->bts, GSM_LCHAN_TCH_H, SELECT_FOR_MS_CHAN_REQ, NULL, true)) {
+ LOG_BTS(rqd->bts, DRSL, LOGL_NOTICE,
+ "CHAN RQD/EMERGENCY-PRIORITY: at least one TCH/H is (now) available!\n");
+ return false;
+ }
+
+ /* No free TCH/F or TCH/H was found, we now select one of the busy lchans and initiate a release on that lchan.
+ * This will take a short amount of time. We need to come back and check regularly to see if we managed to
+ * free up another lchan. */
+ if (!rqd->release_lchan) {
+ struct gsm_lchan *release_lchan;
+ /* Pick any busy TCH/F or TCH/H lchan and initiate a channel
+ * release to make room for the incoming emergency call */
+ rqd->release_lchan = release_lchan = get_any_lchan(rqd->bts);
+ if (!release_lchan) {
+ /* It can not happen that we first find out that there
+ * is no TCH/H or TCH/F available and at the same time
+ * we ware unable to find any busy TCH/H or TCH/F. In
+ * this case, the BTS probably does not have any
+ * voice channels configured? */
+ LOG_BTS(rqd->bts, DRSL, LOGL_NOTICE,
+ "CHAN RQD/EMERGENCY-PRIORITY: no TCH/H or TCH/F available - check VTY config!\n");
+ return false;
+ }
+
+ LOG_BTS(rqd->bts, DRSL, LOGL_NOTICE,
+ "CHAN RQD/EMERGENCY-PRIORITY: inducing termination of lchan %s (state:%s) in favor of incoming EMERGENCY CALL!\n",
+ gsm_lchan_name(release_lchan), osmo_fsm_inst_state_name(release_lchan->fi));
+
+ /* Make sure the Clear Request to the MSC has the proper cause */
+ if (release_lchan->conn)
+ gscon_bssmap_clear(release_lchan->conn, GSM0808_CAUSE_PREEMPTION);
+ /* The gscon FSM would only release the lchan after the MSC responds with a Clear Command.
+ * But we need it released right now. Also with the right RR cause. */
+ lchan_release(release_lchan, !!(release_lchan->conn), true, GSM48_RR_CAUSE_PREMPTIVE_REL,
+ gscon_last_eutran_plmn(release_lchan->conn));
+
+ /* Also release any overlapping VAMOS multiplexes on this lchan */
+ release_lchan = gsm_lchan_primary_to_vamos(release_lchan);
+ if (release_lchan)
+ lchan_release(release_lchan, !!(release_lchan->conn), true, GSM48_RR_CAUSE_PREMPTIVE_REL,
+ gscon_last_eutran_plmn(release_lchan->conn));
+ } else {
+ /* if BTS has shut down, give up... */
+ if (rqd->release_lchan->ts->fi->state == TS_ST_NOT_INITIALIZED)
+ return false;
+
+ OSMO_ASSERT(rqd->release_lchan->fi);
+
+ LOG_BTS(rqd->bts, DRSL, LOGL_NOTICE,
+ "CHAN RQD/EMERGENCY-PRIORITY: still terminating lchan %s (state:%s) in favor of incoming EMERGENCY CALL!\n",
+ gsm_lchan_name(rqd->release_lchan), osmo_fsm_inst_state_name(rqd->release_lchan->fi));
- /* Handle PDCH related rach requests (in case of BSC-co-located-PCU */
- if (chreq_reason == GSM_CHREQ_REASON_PDCH)
- return rsl_rx_pchan_rqd(msg, bts);
+ /* If the channel was released in error (not established), the
+ * lchan FSM automatically blocks the LCHAN for a short time.
+ * This is not acceptable in an emergency situation, so we skip
+ * this waiting period. */
+ if (rqd->release_lchan->fi->state == LCHAN_ST_WAIT_AFTER_ERROR)
+ lchan_fsm_skip_error(rqd->release_lchan);
+ }
+
+ /* We are still in the process of releasing a busy lchan in favvor of the incoming emergency call. */
+ return true;
+}
+
+struct gsm_lchan *_select_sdcch_for_call(struct gsm_bts *bts, const struct chan_rqd *rqd, enum gsm_chan_t lctype)
+{
+ struct gsm_lchan *lchan = NULL;
+ int free_tchf, free_tchh;
+ bool needs_dyn_switch;
+
+ lchan = lchan_avail_by_type(bts, GSM_LCHAN_SDCCH, SELECT_FOR_MS_CHAN_REQ, NULL, false);
+ if (!lchan)
+ return NULL;
+
+ needs_dyn_switch = lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN &&
+ lchan->ts->pchan_is != GSM_PCHAN_SDCCH8_SACCH8C;
+
+ free_tchf = bts->chan_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F];
+ free_tchh = bts->chan_counts.val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H];
+ if (free_tchf == 0 && free_tchh == 0) {
+ LOG_BTS(bts, DRSL, LOGL_INFO,
+ "CHAN RQD: 0x%x Requesting %s reason=call but no TCH available\n",
+ rqd->ref.ra, gsm_chan_t_name(lctype));
+ return NULL;
+ }
+
+ /* There's a TCH available and we'll not switch any dyn ts, so we are
+ * fine (we can switch one of them to SDCCH8 and still have one left) */
+ if (!needs_dyn_switch)
+ goto select_lchan;
+
+ /* We need to switch, but there's at least 2 TCH TS available so we are fine: */
+ if (free_tchf > 1 || free_tchh > 2)
+ goto select_lchan;
+
+ /* At this point (needs_dyn_switch==true), following cases are possible:
+ * [A] H=0, F=1
+ * [B] H=1, F=0
+ * [B] H=1, F=1
+ * [C] H=2, F=1
+ * If condition [C] is met, it means there's 1 dynamic TS (because a dyn
+ * TS is counted both as 1 free TCH/F and 2 free TCH/H at the same time)
+ * and it's the same as the dynamic TS available for SDCCH requiring
+ * switch, so selecting it would basically leave us without free TCH, so
+ * avoid selecting it. Regarding the other conditions, it basically
+ * results in them being different TS than the one we want to switch, so
+ * we are fine selecting the TS for SDCCH */
+ if (free_tchf == 1 && free_tchh == 2) {
+ LOG_BTS(bts, DRSL, LOGL_INFO,
+ "CHAN RQD: 0x%x Requesting %s reason=call but dyn TS switch to "
+ "SDCCH would starve the single available TCH timeslot\n",
+ rqd->ref.ra, gsm_chan_t_name(lctype));
+ return NULL;
+ }
+
+select_lchan:
+ lchan_select_set_type(lchan, GSM_LCHAN_SDCCH);
+ return lchan;
+}
+
+void abis_rsl_chan_rqd_queue_poll(struct gsm_bts *bts)
+{
+ struct lchan_activate_info info;
+ enum gsm_chan_t lctype;
+ struct gsm_lchan *lchan = NULL;
+ struct chan_rqd *rqd;
+
+ rqd = llist_first_entry_or_null(&bts->chan_rqd_queue, struct chan_rqd, entry);
+ if (!rqd)
+ return;
+
+ /* Handle PDCH related rach requests (in case of BSC-co-located-PCU) */
+ if (rqd->reason == GSM_CHREQ_REASON_PDCH) {
+ if (rsl_rx_pchan_rqd(rqd) == 0)
+ goto leave;
+ }
+
+ /* Ensure that emergency calls will get priority over regular calls, however releasing
+ * lchan in favor of an emergency call may take some time, so we exit here. The lchan_fsm
+ * will poll again when an lchan becomes available. */
+ if (force_free_lchan_for_emergency(rqd))
+ return;
/* determine channel type (SDCCH/TCH_F/TCH_H) based on
* request reference RA */
- lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra);
-
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL]);
+ lctype = get_ctype_by_chreq(bts->network, rqd->ref.ra);
/* check availability / allocate channel
*
- * - First try to allocate SDCCH.
- * - If SDCCH is not available, try whatever MS requested, if not SDCCH.
- * - If there is still no channel available, reject channel request.
- *
- * lchan_alloc() possibly tries to allocate larger lchans.
+ * - First check for EMERGENCY call attempts,
+ * - then try to allocate SDCCH.
+ * - If SDCCH is not available, try a TCH/H (less bandwidth).
+ * - If there is still no channel available, try a TCH/F.
*
- * Note: If the MS requests not TCH/H, we don't know if the phone
- * supports TCH/H, so we must assign TCH/F or SDCCH.
*/
- lchan = lchan_select_by_type(bts, GSM_LCHAN_SDCCH);
- if (!lchan && lctype != GSM_LCHAN_SDCCH) {
- LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s "
- "0x%x, retrying with %s\n",
- msg->lchan->ts->trx->bts->nr,
- gsm_lchant_name(GSM_LCHAN_SDCCH), rqd_ref->ra,
- gsm_lchant_name(lctype));
- lchan = lchan_select_by_type(bts, lctype);
- }
- if (!lchan && lctype == GSM_LCHAN_SDCCH) {
- LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s "
- "0x%x, retrying with %s\n",
- msg->lchan->ts->trx->bts->nr,
- gsm_lchant_name(GSM_LCHAN_SDCCH), rqd_ref->ra,
- gsm_lchant_name(GSM_LCHAN_TCH_H));
- lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_H);
- }
- if (!lchan && lctype == GSM_LCHAN_SDCCH) {
- LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s "
- "0x%x, retrying with %s\n",
- msg->lchan->ts->trx->bts->nr,
- gsm_lchant_name(GSM_LCHAN_SDCCH), rqd_ref->ra,
- gsm_lchant_name(GSM_LCHAN_TCH_F));
- lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_F);
+
+ if (rqd->reason == GSM_CHREQ_REASON_CALL) {
+ lchan = _select_sdcch_for_call(bts, rqd, lctype);
+ } else if (rqd->reason != GSM_CHREQ_REASON_EMERG) {
+ lchan = lchan_select_by_type(bts, GSM_LCHAN_SDCCH,
+ SELECT_FOR_MS_CHAN_REQ,
+ NULL);
+ }
+ /* else: Emergency calls will be put on a free TCH/H or TCH/F directly
+ * in the code below, all other channel requests will get an SDCCH first
+ * (if possible). */
+
+ if (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_ALWAYS ||
+ (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_VOICE &&
+ gsm_chreq_reason_is_voicecall(rqd->reason)) ||
+ (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_EMERG &&
+ rqd->reason == GSM_CHREQ_REASON_EMERG)) {
+ if (!lchan) {
+ LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD[%s]: no resources for %s 0x%x, retrying with %s\n",
+ get_value_string(gsm_chreq_descs, rqd->reason), gsm_chan_t_name(GSM_LCHAN_SDCCH),
+ rqd->ref.ra, gsm_chan_t_name(GSM_LCHAN_TCH_H));
+ lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_H,
+ SELECT_FOR_MS_CHAN_REQ,
+ NULL);
+ }
+ if (!lchan) {
+ LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD[%s]: no resources for %s 0x%x, retrying with %s\n",
+ get_value_string(gsm_chreq_descs, rqd->reason), gsm_chan_t_name(GSM_LCHAN_SDCCH),
+ rqd->ref.ra, gsm_chan_t_name(GSM_LCHAN_TCH_F));
+ lchan = lchan_select_by_type(bts, GSM_LCHAN_TCH_F,
+ SELECT_FOR_MS_CHAN_REQ,
+ NULL);
+ }
}
if (!lchan) {
- LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s 0x%x\n",
- msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra);
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL]);
- rsl_tx_imm_ass_rej(bts, rqd_ref);
- return 0;
+ LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD[%s]: no resources for %s 0x%x\n",
+ get_value_string(gsm_chreq_descs, rqd->reason), gsm_chan_t_name(lctype), rqd->ref.ra);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_NO_CHANNEL));
+ rsl_tx_imm_ass_rej(bts, &rqd->ref);
+ llist_del(&rqd->entry);
+ talloc_free(rqd);
+ return;
}
/* save the RACH data as we need it after the CHAN ACT ACK */
lchan->rqd_ref = talloc_zero(bts, struct gsm48_req_ref);
OSMO_ASSERT(lchan->rqd_ref);
- *(lchan->rqd_ref) = *rqd_ref;
- lchan->rqd_ta = rqd_ta;
+ *(lchan->rqd_ref) = rqd->ref;
LOG_LCHAN(lchan, LOGL_DEBUG, "MS: Channel Request: reason=%s ra=0x%02x ta=%d\n",
- gsm_chreq_name(chreq_reason), rqd_ref->ra, rqd_ta);
+ gsm_chreq_name(rqd->reason), rqd->ref.ra, rqd->ta);
info = (struct lchan_activate_info){
- .activ_for = FOR_MS_CHANNEL_REQUEST,
- .chan_mode = GSM48_CMODE_SIGN,
+ .activ_for = ACTIVATE_FOR_MS_CHANNEL_REQUEST,
+ .chreq_reason = rqd->reason,
+ .ch_mode_rate = {
+ .chan_mode = GSM48_CMODE_SIGN,
+ .chan_rate = CH_RATE_SDCCH,
+ },
+ .ta = rqd->ta,
+ .ta_known = true,
+ .imm_ass_time = bts->imm_ass_time,
};
lchan_activate(lchan, &info);
- return 0;
+
+leave:
+ llist_del(&rqd->entry);
+ talloc_free(rqd);
+ return;
+}
+
+static void imm_ass_rate_ctr(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL));
+ switch (lchan->activate.info.chreq_reason) {
+ case GSM_CHREQ_REASON_EMERG:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_EMERG));
+ break;
+ case GSM_CHREQ_REASON_CALL:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_CALL));
+ break;
+ case GSM_CHREQ_REASON_LOCATION_UPD:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_LOCATION_UPD));
+ break;
+ case GSM_CHREQ_REASON_PAG:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_PAG));
+ break;
+ case GSM_CHREQ_REASON_PDCH:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_PDCH));
+ break;
+ case GSM_CHREQ_REASON_OTHER:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_OTHER));
+ break;
+ default:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_SUCCESSFUL_UNKNOWN));
+ break;
+ }
}
int rsl_tx_imm_assignment(struct gsm_lchan *lchan)
{
+ int rc;
struct gsm_bts *bts = lchan->ts->trx->bts;
uint8_t buf[GSM_MACBLOCK_LEN];
struct gsm48_imm_ass *ia = (struct gsm48_imm_ass *) buf;
+ enum gsm_phys_chan_config pchan;
- /* create IMMEDIATE ASSIGN 04.08 messge */
+ /* create IMMEDIATE ASSIGN 04.08 message */
memset(ia, 0, sizeof(*ia));
/* we set ia->l2_plen once we know the length of the MA below */
ia->proto_discr = GSM48_PDISC_RR;
ia->msg_type = GSM48_MT_RR_IMM_ASS;
ia->page_mode = GSM48_PM_SAME;
- gsm48_lchan2chan_desc(&ia->chan_desc, lchan);
+
+ /* In case the dyn TS is not ready yet, ts->pchan_is still reflects the previous pchan type; so get the pchan
+ * kind from lchan->type, which already reflects the target type. This only happens for dynamic timeslots.
+ * gsm_pchan_by_lchan_type() isn't always exact, which is fine for dyn TS with their limited pchan kinds. */
+ if (lchan_state_is(lchan, LCHAN_ST_WAIT_TS_READY))
+ pchan = gsm_pchan_by_lchan_type(lchan->type);
+ else
+ pchan = lchan->ts->pchan_is;
+ rc = gsm48_lchan_and_pchan2chan_desc(&ia->chan_desc, lchan, pchan, lchan->tsc, true);
+ if (rc) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Error encoding Channel Number\n");
+ return rc;
+ }
/* use request reference extracted from CHAN_RQD */
memcpy(&ia->req_ref, lchan->rqd_ref, sizeof(ia->req_ref));
- ia->timing_advance = lchan->rqd_ta;
+ ia->timing_advance = lchan->last_ta;
if (!lchan->ts->hopping.enabled) {
ia->mob_alloc_len = 0;
} else {
@@ -1461,10 +2457,15 @@ int rsl_tx_imm_assignment(struct gsm_lchan *lchan)
ia->l2_plen = GSM48_LEN2PLEN((sizeof(*ia)-1) + ia->mob_alloc_len);
/* send IMMEDIATE ASSIGN CMD on RSL to BTS (to send on CCCH to MS) */
- return rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (uint8_t *) ia);
+ rc = rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (uint8_t *) ia);
+
+ if (!rc)
+ imm_ass_rate_ctr(lchan);
+
+ return rc;
}
-/* current load on the CCCH */
+/* 5.4 and 8.5.2 Rx CCCH Load Ind */
static int rsl_rx_ccch_load(struct msgb *msg)
{
struct e1inp_sign_link *sign_link = msg->dst;
@@ -1472,25 +2473,37 @@ static int rsl_rx_ccch_load(struct msgb *msg)
struct ccch_signal_data sd;
sd.bts = sign_link->trx->bts;
- sd.rach_slot_count = -1;
- sd.rach_busy_count = -1;
- sd.rach_access_count = -1;
+ sd.rach_slot_count = UINT16_MAX;
+ sd.rach_busy_count = UINT16_MAX;
+ sd.rach_access_count = UINT16_MAX;
switch (rslh->data[0]) {
case RSL_IE_PAGING_LOAD:
sd.pg_buf_space = rslh->data[1] << 8 | rslh->data[2];
- if (is_ipaccess_bts(sign_link->trx->bts) && sd.pg_buf_space == 0xffff) {
- /* paging load below configured threshold, use 50 as default */
- sd.pg_buf_space = 50;
+ if (is_ipa_abisip_bts(sd.bts) && sd.pg_buf_space == UINT16_MAX) {
+ sd.pg_buf_space = paging_estimate_available_slots(sd.bts, sd.bts->ccch_load_ind_period);
}
- paging_update_buffer_space(sign_link->trx->bts, sd.pg_buf_space);
osmo_signal_dispatch(SS_CCCH, S_CCCH_PAGING_LOAD, &sd);
break;
case RSL_IE_RACH_LOAD:
- if (msg->data_len >= 7) {
+ if (msgb_length(msg) >= 7) {
+ int32_t busy_percent, access_percent;
+ /* build data for signal */
sd.rach_slot_count = rslh->data[2] << 8 | rslh->data[3];
sd.rach_busy_count = rslh->data[4] << 8 | rslh->data[5];
sd.rach_access_count = rslh->data[6] << 8 | rslh->data[7];
+ /* update stats group */
+ if (sd.rach_slot_count) {
+ access_percent = (int32_t) sd.rach_access_count * 100 / (int32_t) sd.rach_slot_count;
+ busy_percent = (int32_t) sd.rach_busy_count * 100 / (int32_t) sd.rach_slot_count;
+ } else {
+ access_percent = 0;
+ busy_percent = 100;
+ }
+
+ osmo_stat_item_set(osmo_stat_item_group_get_item(sd.bts->bts_statg, BTS_STAT_RACH_BUSY), busy_percent);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(sd.bts->bts_statg, BTS_STAT_RACH_ACCESS), access_percent);
+ /* dispatch signal */
osmo_signal_dispatch(SS_CCCH, S_CCCH_RACH_LOAD, &sd);
}
break;
@@ -1501,12 +2514,47 @@ static int rsl_rx_ccch_load(struct msgb *msg)
return 0;
}
+/* 8.5.9 current load on the CBCH (Cell Broadcast) */
+static int rsl_rx_cbch_load(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link = msg->dst;
+ struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+ struct gsm_bts *bts = sign_link->trx->bts;
+ bool cbch_extended = false;
+ bool is_overflow = false;
+ int8_t load_info;
+ struct tlv_parsed tp;
+ uint8_t slot_count;
+
+ if (rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh)) < 0) {
+ LOGP(DRSL, LOGL_ERROR, "%s Failed to parse RSL %s\n",
+ gsm_trx_name(sign_link->trx), rsl_or_ipac_msg_name(rslh->c.msg_type));
+ return -EINVAL;
+ }
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_CBCH_LOAD_INFO)) {
+ LOG_BTS(bts, DRSL, LOGL_ERROR, "CBCH LOAD IND without mandatory CBCH Load Info IE\n");
+ return -1;
+ }
+ /* 9.4.43 */
+ load_info = *TLVP_VAL(&tp, RSL_IE_CBCH_LOAD_INFO);
+ if (load_info & 0x80)
+ is_overflow = true;
+ slot_count = load_info & 0x0F;
+
+ if (TLVP_PRES_LEN(&tp, RSL_IE_SMSCB_CHAN_INDICATOR, 1) &&
+ (*TLVP_VAL(&tp, RSL_IE_SMSCB_CHAN_INDICATOR) & 0x0F) == 0x01)
+ cbch_extended = true;
+
+ return bts_smscb_rx_cbch_load_ind(bts, cbch_extended, is_overflow, slot_count);
+}
+
/* Ericsson specific: Immediate Assign Sent */
static int rsl_rx_ericsson_imm_assign_sent(struct msgb *msg)
{
struct e1inp_sign_link *sign_link = msg->dst;
struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
- uint32_t tlli;
+ uint32_t msg_id;
LOGP(DRSL, LOGL_INFO, "IMM.ass sent\n");
msgb_pull(msg, sizeof(*dh));
@@ -1518,8 +2566,8 @@ static int rsl_rx_ericsson_imm_assign_sent(struct msgb *msg)
LOGP(DRSL, LOGL_ERROR, "unsupported IMM.ass message format! (please fix)\n");
else {
msgb_pull(msg, 1); /* drop previous data to use msg_pull_u32 */
- tlli = msgb_pull_u32(msg);
- pcu_tx_imm_ass_sent(sign_link->trx->bts, tlli);
+ msg_id = msgb_pull_u32(msg);
+ pcu_tx_data_cnf(sign_link->trx->bts, msg_id, PCU_IF_SAPI_PCH_2);
}
return 0;
}
@@ -1528,8 +2576,12 @@ static int abis_rsl_rx_cchan(struct msgb *msg)
{
struct e1inp_sign_link *sign_link = msg->dst;
struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+ struct rate_ctr_group *bts_ctrs = sign_link->trx->bts->bts_ctrs;
int rc = 0;
+ if (msgb_l2len(msg) < sizeof(*rslh))
+ return -EINVAL;
+
msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr,
"Abis RSL rx CCHAN: ");
@@ -1544,10 +2596,12 @@ static int abis_rsl_rx_cchan(struct msgb *msg)
break;
case RSL_MT_DELETE_IND:
/* CCCH overloaded, IMM_ASSIGN was dropped */
+ LOGPLCHAN(msg->lchan, DRSL, LOGL_NOTICE, "DELETE INDICATION (Downlink CCCH overload)\n");
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_RSL_DELETE_IND));
+ break;
case RSL_MT_CBCH_LOAD_IND:
/* current load on the CBCH */
- LOGP(DRSL, LOGL_NOTICE, "Unimplemented Abis RSL TRX message "
- "type %s\n", rsl_msg_name(rslh->c.msg_type));
+ rc = rsl_rx_cbch_load(msg);
break;
case RSL_MT_ERICSSON_IMM_ASS_SENT:
rc = rsl_rx_ericsson_imm_assign_sent(msg);
@@ -1555,7 +2609,7 @@ static int abis_rsl_rx_cchan(struct msgb *msg)
default:
LOGP(DRSL, LOGL_NOTICE, "Unknown Abis RSL TRX message type "
"0x%02x\n", rslh->c.msg_type);
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_RSL_UNKNOWN));
return -EINVAL;
}
@@ -1568,9 +2622,14 @@ static int rsl_rx_rll_err_ind(struct msgb *msg)
struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
uint8_t rlm_cause;
- rsl_tlv_parse(&tp, rllh->data, msgb_l2len(msg) - sizeof(*rllh));
+ if (rsl_tlv_parse(&tp, rllh->data, msgb_l2len(msg) - sizeof(*rllh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(rllh->c.msg_type));
+ return -EINVAL;
+ }
+
if (!TLVP_PRESENT(&tp, RSL_IE_RLM_CAUSE)) {
- LOG_LCHAN(msg->lchan, LOGL_ERROR, "ERROR INDICATION without mandantory cause.\n");
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "ERROR INDICATION without mandatory cause.\n");
return -1;
}
@@ -1579,10 +2638,17 @@ static int rsl_rx_rll_err_ind(struct msgb *msg)
rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND);
- rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RLL_ERR]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msg->lchan->ts->trx->bts->bts_ctrs, BTS_CTR_CHAN_RLL_ERR));
osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_ERR_IND, &rlm_cause);
+ /* Report to VGCS FSM */
+ if (lchan_is_asci(msg->lchan)) {
+ if (msg->lchan->conn && msg->lchan->conn->vgcs_chan.fi) {
+ uint8_t cause = GSM0808_CAUSE_RADIO_INTERFACE_FAILURE;
+ osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_FAIL, &cause);
+ }
+ }
return 0;
}
@@ -1597,17 +2663,30 @@ static int abis_rsl_rx_rll(struct msgb *msg)
struct e1inp_sign_link *sign_link = msg->dst;
struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
int rc = 0;
- uint8_t sapi = rllh->link_id & 0x7;
+ uint8_t sapi;
+
+ if (msgb_l2len(msg) < sizeof(*rllh))
+ return -1;
+ sapi = rllh->link_id & 0x7;
msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, "Abis RSL rx RLL: ");
+ if (OSMO_UNLIKELY(msg->lchan == NULL))
+ return -1;
switch (rllh->c.msg_type) {
case RSL_MT_DATA_IND:
LOG_LCHAN(msg->lchan, LOGL_DEBUG, "SAPI=%u DATA INDICATION\n", sapi);
- if (msgb_l2len(msg) >
- sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+
+ if (msgb_l2len(msg) > (sizeof(*rllh) + 3) &&
rllh->data[0] == RSL_IE_L3_INFO) {
msg->l3h = &rllh->data[3];
+ /* Data message on a VGCS channel is handled by VGCS FSM only. */
+ if (lchan_is_asci(msg->lchan)) {
+ if (msg->lchan->conn && msg->lchan->conn->vgcs_chan.fi)
+ osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_DATA,
+ msg);
+ return 0;
+ }
return gsm0408_rcvmsg(msg, rllh->link_id);
}
break;
@@ -1647,15 +2726,21 @@ static int abis_rsl_rx_rll(struct msgb *msg)
msg->lchan->sapis[sapi] = LCHAN_SAPI_MS;
osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_ESTABLISH_IND, msg);
- if (msgb_l2len(msg) >
- sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+ /* Establishment message on a VGCS channel is handled by VGCS FSM only. */
+ if (lchan_is_asci(msg->lchan)) {
+ if (msg->lchan->conn && msg->lchan->conn->vgcs_chan.fi)
+ osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_EST, msg);
+ break;
+ }
+
+ if (msgb_l2len(msg) > (sizeof(*rllh) + 3) &&
rllh->data[0] == RSL_IE_L3_INFO) {
msg->l3h = &rllh->data[3];
return gsm0408_rcvmsg(msg, rllh->link_id);
}
break;
case RSL_MT_EST_CONF:
- LOG_LCHAN(msg->lchan, LOGL_ERROR, "SAPI=%u ESTABLISH CONFIRM\n", sapi);
+ LOG_LCHAN(msg->lchan, LOGL_DEBUG, "SAPI=%u ESTABLISH CONFIRM\n", sapi);
msg->lchan->sapis[sapi] = LCHAN_SAPI_NET;
rll_indication(msg->lchan, rllh->link_id,
BSC_RLLR_IND_EST_CONF);
@@ -1663,6 +2748,14 @@ static int abis_rsl_rx_rll(struct msgb *msg)
case RSL_MT_REL_IND:
/* BTS informs us of having received DISC from MS */
osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RLL_REL_IND, &rllh->link_id);
+
+ /* Report to VGCS FSM */
+ if (lchan_is_asci(msg->lchan)) {
+ if (msg->lchan->conn && msg->lchan->conn->vgcs_chan.fi) {
+ uint8_t cause = GSM0808_CAUSE_CALL_CONTROL;
+ osmo_fsm_inst_dispatch(msg->lchan->conn->vgcs_chan.fi, VGCS_EV_TALKER_REL, &cause);
+ }
+ }
break;
case RSL_MT_REL_CONF:
/* BTS informs us of having received UA from MS,
@@ -1680,15 +2773,77 @@ static int abis_rsl_rx_rll(struct msgb *msg)
default:
LOG_LCHAN(msg->lchan, LOGL_NOTICE, "SAPI=%u Unknown Abis RLL message type 0x%02x\n",
sapi, rllh->c.msg_type);
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN));
}
return rc;
}
+/* Return an ip.access RTP CSD FMT value (uint8_t) or negative on error. */
+int ipacc_rtp_csd_fmt_transp(const struct channel_mode_and_rate *ch_mode_rate,
+ const enum rsl_ipac_rtp_csd_format_d format_d)
+{
+ uint8_t ret = format_d;
+
+ switch (ch_mode_rate->data_rate.t) {
+ case RSL_CMOD_CSD_T_32k0:
+ case RSL_CMOD_CSD_T_29k0:
+ ret |= RSL_IPAC_RTP_CSD_IR_32k << 4;
+ break;
+ case RSL_CMOD_CSD_T_14k4:
+ case RSL_CMOD_CSD_T_9k6:
+ ret |= RSL_IPAC_RTP_CSD_IR_16k << 4;
+ break;
+ case RSL_CMOD_CSD_T_4k8:
+ case RSL_CMOD_CSD_T_2k4:
+ case RSL_CMOD_CSD_T_1k2:
+ case RSL_CMOD_CSD_T_600:
+ case RSL_CMOD_CSD_T_1200_75:
+ ret |= RSL_IPAC_RTP_CSD_IR_8k << 4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+/* Return an ip.access RTP CSD FMT value (uint8_t) or negative on error. */
+int ipacc_rtp_csd_fmt_non_transp(const struct channel_mode_and_rate *ch_mode_rate,
+ const enum rsl_ipac_rtp_csd_format_d format_d)
+{
+ uint8_t ret = format_d;
+
+ switch (ch_mode_rate->data_rate.nt) {
+ case RSL_CMOD_CSD_NTA_43k5_14k5:
+ case RSL_CMOD_CSD_NTA_43k5_29k0:
+ case RSL_CMOD_CSD_NTA_14k5_43k5:
+ case RSL_CMOD_CSD_NTA_29k0_43k5:
+ case RSL_CMOD_CSD_NT_43k5:
+ ret |= RSL_IPAC_RTP_CSD_IR_64k << 4;
+ break;
+ case RSL_CMOD_CSD_NTA_29k0_14k5:
+ case RSL_CMOD_CSD_NTA_14k5_29k0:
+ case RSL_CMOD_CSD_NT_28k8:
+ ret |= RSL_IPAC_RTP_CSD_IR_32k << 4;
+ break;
+ case RSL_CMOD_CSD_NT_14k5:
+ case RSL_CMOD_CSD_NT_12k0:
+ ret |= RSL_IPAC_RTP_CSD_IR_16k << 4;
+ break;
+ case RSL_CMOD_CSD_NT_6k0:
+ ret |= RSL_IPAC_RTP_CSD_IR_8k << 4;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
/* Return an ip.access BTS speech mode value (uint8_t) or negative on error. */
int ipacc_speech_mode(enum gsm48_chan_mode tch_mode, enum gsm_chan_t type)
{
- switch (tch_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(tch_mode)) {
case GSM48_CMODE_SPEECH_V1:
switch (type) {
case GSM_LCHAN_TCH_F:
@@ -1736,7 +2891,12 @@ void ipacc_speech_mode_set_direction(uint8_t *speech_mode, bool send)
/* Return an ip.access BTS payload type value (uint8_t) or negative on error. */
int ipacc_payload_type(enum gsm48_chan_mode tch_mode, enum gsm_chan_t type)
{
- switch (tch_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(tch_mode)) {
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ return RTP_PT_CSDATA;
case GSM48_CMODE_SPEECH_V1:
switch (type) {
case GSM_LCHAN_TCH_F:
@@ -1821,11 +2981,16 @@ static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv, const
port = ntohs(port);
lchan->abis_ip.connect_port = port;
}
+ if (TLVP_PRESENT(tv, RSL_IE_OSMO_OSMUX_CID)) {
+ lchan->abis_ip.osmux.remote_cid_present = true;
+ lchan->abis_ip.osmux.remote_cid = tlvp_val8(tv, RSL_IE_OSMO_OSMUX_CID, 0);
+ }
LOG_LCHAN(lchan, LOGL_DEBUG, "Rx IPACC %s ACK:"
- " BTS=%s:%u conn_id=%u rtp_payload2=0x%02x speech_mode=0x%02x\n",
+ " BTS=%s:%u conn_id=%u rtp_payload2=0x%02x speech_mode=0x%02x osmux_use=%d osmux_loc_cid=%d\n",
label, ip_to_a(lchan->abis_ip.bound_ip), lchan->abis_ip.bound_port,
- lchan->abis_ip.conn_id, lchan->abis_ip.rtp_payload2, lchan->abis_ip.speech_mode);
+ lchan->abis_ip.conn_id, lchan->abis_ip.rtp_payload2, lchan->abis_ip.speech_mode,
+ lchan->abis_ip.osmux.use, lchan->abis_ip.osmux.local_cid);
}
/*! Send Issue IPA RSL CRCX to configure the RTP port of the BTS.
@@ -1833,22 +2998,43 @@ static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv, const
*/
int rsl_tx_ipacc_crcx(const struct gsm_lchan *lchan)
{
- struct msgb *msg = rsl_msgb_alloc();
+ struct msgb *msg;
struct abis_rsl_dchan_hdr *dh;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
+
+ msg = rsl_msgb_alloc();
+
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_IPAC_CRCX);
dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
- dh->chan_nr = gsm_lchan2chan_nr(lchan);
+ dh->chan_nr = chan_nr;
+
+ if (lchan->current_ch_indctr == GSM0808_CHAN_DATA) {
+ msgb_tv_put(msg, RSL_IE_IPAC_RTP_CSD_FMT, lchan->abis_ip.rtp_csd_fmt);
+
+ LOG_LCHAN(lchan, LOGL_DEBUG,
+ "Sending IPACC CRCX to BTS: rtp_csd_fmt=0x%02x RTP_PAYLOAD=%d (CSD) osmux_use=%d osmux_loc_cid=%d\n",
+ lchan->abis_ip.rtp_csd_fmt, lchan->abis_ip.rtp_payload,
+ lchan->abis_ip.osmux.use, lchan->abis_ip.osmux.local_cid);
+ } else {
+ /* 0x1- == receive-only, 0x-1 == EFR codec */
+ msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+
+ LOG_LCHAN(lchan, LOGL_DEBUG,
+ "Sending IPACC CRCX to BTS: speech_mode=0x%02x RTP_PAYLOAD=%d osmux_use=%d osmux_loc_cid=%d\n",
+ lchan->abis_ip.speech_mode, lchan->abis_ip.rtp_payload,
+ lchan->abis_ip.osmux.use, lchan->abis_ip.osmux.local_cid);
+ }
- /* 0x1- == receive-only, 0x-1 == EFR codec */
- msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
+ if (lchan->abis_ip.osmux.use)
+ msgb_tlv_put(msg, RSL_IE_OSMO_OSMUX_CID, 1, &lchan->abis_ip.osmux.local_cid);
- LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC CRCX to BTS: speech_mode=0x%02x RTP_PAYLOAD=%d\n",
- lchan->abis_ip.speech_mode, lchan->abis_ip.rtp_payload);
- msg->dst = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
@@ -1860,26 +3046,39 @@ int rsl_tx_ipacc_crcx(const struct gsm_lchan *lchan)
*/
struct msgb *rsl_make_ipacc_mdcx(const struct gsm_lchan *lchan, uint32_t dest_ip, uint16_t dest_port)
{
- struct msgb *msg = rsl_msgb_alloc();
+ struct msgb *msg;
struct abis_rsl_dchan_hdr *dh;
uint32_t *att_ip;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return NULL;
+
+ msg = rsl_msgb_alloc();
+
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, RSL_MT_IPAC_MDCX);
dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
- dh->chan_nr = gsm_lchan2chan_nr(lchan);
+ dh->chan_nr = chan_nr;
msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP);
att_ip = (uint32_t *)msgb_put(msg, sizeof(uint32_t));
*att_ip = htonl(dest_ip);
msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, dest_port);
- msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+
+ if (lchan->current_ch_indctr == GSM0808_CHAN_DATA)
+ msgb_tv_put(msg, RSL_IE_IPAC_RTP_CSD_FMT, lchan->abis_ip.rtp_csd_fmt);
+ else
+ msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode);
+
msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload);
if (lchan->abis_ip.rtp_payload2)
msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, lchan->abis_ip.rtp_payload2);
+ if (lchan->abis_ip.osmux.use)
+ msgb_tlv_put(msg, RSL_IE_OSMO_OSMUX_CID, 1, &lchan->abis_ip.osmux.local_cid);
- msg->dst = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
return msg;
}
@@ -1892,14 +3091,27 @@ int rsl_tx_ipacc_mdcx(const struct gsm_lchan *lchan)
{
struct msgb *msg = rsl_make_ipacc_mdcx(lchan, lchan->abis_ip.connect_ip, lchan->abis_ip.connect_port);
- LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC MDCX to BTS:"
- " %s:%u rtp_payload=%u rtp_payload2=%u conn_id=%u speech_mode=0x%02x\n",
- ip_to_a(lchan->abis_ip.connect_ip),
- lchan->abis_ip.connect_port,
- lchan->abis_ip.rtp_payload,
- lchan->abis_ip.rtp_payload2,
- lchan->abis_ip.conn_id,
- lchan->abis_ip.speech_mode);
+ if (!msg)
+ return -EINVAL;
+
+ if (lchan->current_ch_indctr == GSM0808_CHAN_DATA)
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC MDCX to BTS:"
+ " %s:%u rtp_payload=%u (CSD) rtp_payload2=%u conn_id=%u rtp_csd_fmt=0x%02x\n",
+ ip_to_a(lchan->abis_ip.connect_ip),
+ lchan->abis_ip.connect_port,
+ lchan->abis_ip.rtp_payload,
+ lchan->abis_ip.rtp_payload2,
+ lchan->abis_ip.conn_id,
+ lchan->abis_ip.rtp_csd_fmt);
+ else
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Sending IPACC MDCX to BTS:"
+ " %s:%u rtp_payload=%u rtp_payload2=%u conn_id=%u speech_mode=0x%02x\n",
+ ip_to_a(lchan->abis_ip.connect_ip),
+ lchan->abis_ip.connect_port,
+ lchan->abis_ip.rtp_payload,
+ lchan->abis_ip.rtp_payload2,
+ lchan->abis_ip.conn_id,
+ lchan->abis_ip.speech_mode);
return abis_rsl_sendmsg(msg);
}
@@ -1911,7 +3123,7 @@ static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg)
struct gsm_lchan *lchan = msg->lchan;
if (!lchan->fi_rtp) {
- LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: CRCX ACK message for unconfigured lchan");
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: CRCX ACK message for unconfigured lchan\n");
return -EINVAL;
}
@@ -1919,7 +3131,12 @@ static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg)
* address and port number to which it has bound the given logical
* channel */
- rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ if (rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(dh->c.msg_type));
+ return -EINVAL;
+ }
+
if (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) ||
!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) ||
!TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) {
@@ -1927,6 +3144,15 @@ static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg)
return -EINVAL;
}
+ if (!lchan->abis_ip.osmux.use && TLVP_PRESENT(&tv, RSL_IE_OSMO_OSMUX_CID)) {
+ LOGP(DRSL, LOGL_NOTICE, "Received unexpected IE Osmux CID\n");
+ return -EINVAL;
+ }
+ if (lchan->abis_ip.osmux.use && !TLVP_PRESENT(&tv, RSL_IE_OSMO_OSMUX_CID)) {
+ LOGP(DRSL, LOGL_NOTICE, "Mandatory IE Osmux CID missing\n");
+ return -EINVAL;
+ }
+
ipac_parse_rtp(lchan, &tv, "CRCX");
osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_IPACC_CRCX_ACK, 0);
@@ -1939,10 +3165,10 @@ static int abis_rsl_rx_ipacc_crcx_nack(struct msgb *msg)
struct e1inp_sign_link *sign_link = msg->dst;
struct gsm_lchan *lchan = msg->lchan;
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_IPA_NACK));
if (!lchan->fi_rtp) {
- LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: CRCX NACK message for unconfigured lchan");
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: CRCX NACK message for unconfigured lchan\n");
return -EINVAL;
}
osmo_fsm_inst_dispatch(msg->lchan->fi_rtp, LCHAN_RTP_EV_IPACC_CRCX_NACK, 0);
@@ -1956,7 +3182,7 @@ static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg)
struct gsm_lchan *lchan = msg->lchan;
if (!lchan->fi_rtp) {
- LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: MDCX ACK message for unconfigured lchan");
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: MDCX ACK message for unconfigured lchan\n");
return -EINVAL;
}
@@ -1964,7 +3190,12 @@ static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg)
* it now tells us the IP address and port number to which it has
* connected the given logical channel */
- rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ if (rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(dh->c.msg_type));
+ return -EINVAL;
+ }
+
ipac_parse_rtp(lchan, &tv, "MDCX");
osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_IPACC_MDCX_ACK, 0);
@@ -1977,10 +3208,10 @@ static int abis_rsl_rx_ipacc_mdcx_nack(struct msgb *msg)
struct e1inp_sign_link *sign_link = msg->dst;
struct gsm_lchan *lchan = msg->lchan;
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_IPA_NACK));
if (!lchan->fi_rtp) {
- LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: MDCX NACK message for unconfigured lchan");
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: MDCX NACK message for unconfigured lchan\n");
return -EINVAL;
}
osmo_fsm_inst_dispatch(msg->lchan->fi_rtp, LCHAN_RTP_EV_IPACC_MDCX_NACK, 0);
@@ -1992,7 +3223,12 @@ static int abis_rsl_rx_ipacc_dlcx_ind(struct msgb *msg)
struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
struct tlv_parsed tv;
- rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ if (rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg) - sizeof(*dh)) < 0) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "Failed to parse RSL %s\n",
+ rsl_or_ipac_msg_name(dh->c.msg_type));
+ return -EINVAL;
+ }
+
LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Rx IPACC DLCX IND%s\n",
rsl_cause_name(&tv));
@@ -2005,6 +3241,9 @@ static int abis_rsl_rx_ipacc(struct msgb *msg)
struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
int rc = 0;
+ if (msgb_l2len(msg) < sizeof(*rllh))
+ return -EINVAL;
+
msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr,
"Abis RSL rx IPACC: ");
@@ -2046,7 +3285,7 @@ static int abis_rsl_rx_ipacc(struct msgb *msg)
default:
LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unknown ip.access msg_type 0x%02x\n",
rllh->c.msg_type);
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN));
break;
}
@@ -2076,22 +3315,28 @@ static int send_osmocom_style_pdch_chan_act(struct gsm_bts_trx_ts *ts, bool acti
}
}
- msg->dst = ts->trx->rsl_link;
+ msg->dst = ts->trx->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
/*! Tx simplified channel (de-)activation message for non-standard ip.access dyn TS PDCH type. */
static int send_ipacc_style_pdch_act(struct gsm_bts_trx_ts *ts, bool activate)
{
- struct msgb *msg = rsl_msgb_alloc();
+ struct msgb *msg;
struct abis_rsl_dchan_hdr *dh;
+ int chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_TCH_F, ts->nr, 0, false);
+ if (chan_nr < 0)
+ return chan_nr;
+
+ msg = rsl_msgb_alloc();
+
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
init_dchan_hdr(dh, activate ? RSL_MT_IPAC_PDCH_ACT : RSL_MT_IPAC_PDCH_DEACT);
dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
- dh->chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_TCH_F, ts->nr, 0);
+ dh->chan_nr = chan_nr;
- msg->dst = ts->trx->rsl_link;
+ msg->dst = ts->trx->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
@@ -2102,7 +3347,7 @@ int rsl_tx_dyn_ts_pdch_act_deact(struct gsm_bts_trx_ts *ts, bool activate)
const char *act;
switch (ts->pchan_on_init) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
what = "Osmocom dyn TS";
act = activate? "PDCH Chan Activ" : "PDCH Chan RF Release";
@@ -2175,16 +3420,35 @@ int abis_rsl_rcvmsg(struct msgb *msg)
default:
LOGP(DRSL, LOGL_NOTICE, "unknown RSL message discriminator "
"0x%02x\n", rslh->msg_discr);
- rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(sign_link->trx->bts->bts_ctrs, BTS_CTR_RSL_UNKNOWN));
rc = -EINVAL;
}
msgb_free(msg);
return rc;
}
+/* Send an Osmocom-specific Abis RSL message for ETWS Primary Notification */
+int rsl_etws_pn_command(struct gsm_bts *bts, uint8_t chan_nr, const uint8_t *data, int len)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *msg = rsl_msgb_alloc();
+ if (!msg)
+ return -1;
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ init_dchan_hdr(dh, RSL_MT_OSMO_ETWS_CMD);
+ dh->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN;
+ dh->chan_nr = chan_nr;
+
+ msgb_tlv_put(msg, RSL_IE_SMSCB_MSG, len, data);
+
+ msg->dst = bts->c0->rsl_link_primary;
+
+ return abis_rsl_sendmsg(msg);
+}
+
int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number,
struct rsl_ie_cb_cmd_type cb_command,
- const uint8_t *data, int len)
+ bool use_extended_cbch, const uint8_t *data, int len)
{
struct abis_rsl_dchan_hdr *dh;
struct msgb *cb_cmd;
@@ -2200,8 +3464,10 @@ int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number,
msgb_tv_put(cb_cmd, RSL_IE_CB_CMD_TYPE, *(uint8_t*)&cb_command);
msgb_tlv_put(cb_cmd, RSL_IE_SMSCB_MSG, len, data);
+ if (use_extended_cbch)
+ msgb_tv_put(cb_cmd, RSL_IE_SMSCB_CHAN_INDICATOR, 0x01);
- cb_cmd->dst = bts->c0->rsl_link;
+ cb_cmd->dst = bts->c0->rsl_link_primary;
return abis_rsl_sendmsg(cb_cmd);
}
@@ -2215,7 +3481,7 @@ int rsl_nokia_si_begin(struct gsm_bts_trx *trx)
ch->msg_discr = ABIS_RSL_MDISC_TRX;
ch->msg_type = 0x40; /* Nokia SI Begin */
- msg->dst = trx->rsl_link;
+ msg->dst = trx->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
@@ -2231,7 +3497,7 @@ int rsl_nokia_si_end(struct gsm_bts_trx *trx)
msgb_tv_put(msg, 0xFD, 0x00); /* Nokia Pagemode Info, No paging reorganisation required */
- msg->dst = trx->rsl_link;
+ msg->dst = trx->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
@@ -2248,7 +3514,12 @@ int rsl_bs_power_control(struct gsm_bts_trx *trx, uint8_t channel, uint8_t reduc
msgb_tv_put(msg, RSL_IE_CHAN_NR, channel);
msgb_tv_put(msg, RSL_IE_BS_POWER, reduction); /* reduction in 2dB steps */
- msg->dst = trx->rsl_link;
+ msg->dst = trx->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
+
+struct e1inp_sign_link *rsl_chan_link(const struct gsm_lchan *lchan)
+{
+ return lchan->ts->trx->rsl_link_primary;
+}
diff --git a/src/osmo-bsc/acc.c b/src/osmo-bsc/acc.c
new file mode 100644
index 000000000..80a35766c
--- /dev/null
+++ b/src/osmo-bsc/acc.c
@@ -0,0 +1,575 @@
+/* (C) 2018-2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Stefan Sperling <ssperling@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <strings.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/acc.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts.h>
+
+/*
+ * Check if an ACC has been permanently barred for a BTS,
+ * e.g. with the 'rach access-control-class' VTY command.
+ */
+static bool acc_is_permanently_barred(struct gsm_bts *bts, unsigned int acc)
+{
+ OSMO_ASSERT(acc <= 9);
+ if (acc == 8 || acc == 9)
+ return (bts->si_common.rach_control.t2 & (1 << (acc - 8)));
+ return (bts->si_common.rach_control.t3 & (1 << (acc)));
+}
+
+/*!
+ * Return bitmasks which correspond to access control classes that are currently
+ * denied access. Ramping is only concerned with those bits which control access
+ * for ACCs 0-9, and any of the other bits will always be set to zero in these masks, i.e.
+ * it is safe to OR these bitmasks with the corresponding fields in struct gsm48_rach_control.
+ * \param[in] acc_mgr Pointer to acc_mgr structure.
+ */
+static inline uint8_t acc_mgr_get_barred_t2(struct acc_mgr *acc_mgr)
+{
+ return ((~acc_mgr->allowed_subset_mask) >> 8) & 0x03;
+};
+static inline uint8_t acc_mgr_get_barred_t3(struct acc_mgr *acc_mgr)
+{
+ return (~acc_mgr->allowed_subset_mask) & 0xff;
+}
+
+static uint8_t acc_mgr_subset_len(struct acc_mgr *acc_mgr)
+{
+ return OSMO_MIN(acc_mgr->len_allowed_ramp, acc_mgr->len_allowed_adm);
+}
+
+static void acc_mgr_enable_rotation_cond(struct acc_mgr *acc_mgr)
+{
+ if (acc_mgr->allowed_permanent_count && acc_mgr->allowed_subset_mask_count &&
+ acc_mgr->allowed_permanent_count != acc_mgr->allowed_subset_mask_count) {
+ if (!osmo_timer_pending(&acc_mgr->rotate_timer))
+ osmo_timer_schedule(&acc_mgr->rotate_timer, acc_mgr->rotation_time_sec, 0);
+ } else {
+ /* No rotation needed, disable rotation timer (if pending) */
+ osmo_timer_del(&acc_mgr->rotate_timer);
+ }
+}
+
+static void acc_mgr_gen_subset(struct acc_mgr *acc_mgr, bool update_si)
+{
+ uint8_t acc;
+
+ acc_mgr->allowed_subset_mask = 0; /* clean mask */
+ acc_mgr->allowed_subset_mask_count = 0;
+ acc_mgr->allowed_permanent_count = 0;
+
+ for (acc = 0; acc < 10; acc++) {
+ if (acc_is_permanently_barred(acc_mgr->bts, acc))
+ continue;
+ acc_mgr->allowed_permanent_count++;
+ if (acc_mgr->allowed_subset_mask_count < acc_mgr_subset_len(acc_mgr)) {
+ acc_mgr->allowed_subset_mask |= (1 << acc);
+ acc_mgr->allowed_subset_mask_count++;
+ }
+ }
+
+ acc_mgr_enable_rotation_cond(acc_mgr);
+
+ LOG_BTS(acc_mgr->bts, DRSL, LOGL_INFO,
+ "ACC: New ACC allowed subset 0x%03" PRIx16 " (active_len=%" PRIu8
+ ", ramp_len=%" PRIu8 ", adm_len=%" PRIu8 ", perm_len=%" PRIu8 ", rotation=%s)\n",
+ acc_mgr->allowed_subset_mask, acc_mgr->allowed_subset_mask_count,
+ acc_mgr->len_allowed_ramp, acc_mgr->len_allowed_adm,
+ acc_mgr->allowed_permanent_count,
+ osmo_timer_pending(&(acc_mgr)->rotate_timer) ? "on" : "off");
+
+ /* Trigger SI data update, acc_mgr_apply_acc will bew called */
+ if (update_si)
+ gsm_bts_set_system_infos(acc_mgr->bts);
+}
+
+static uint8_t get_highest_allowed_acc(uint16_t mask)
+{
+ int i;
+
+ for (i = 9; i >= 0; i--) {
+ if (mask & (1 << i))
+ return i;
+ }
+ OSMO_ASSERT(0);
+ return 0;
+}
+
+static uint8_t get_lowest_allowed_acc(uint16_t mask)
+{
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ if (mask & (1 << i))
+ return i;
+ }
+ OSMO_ASSERT(0);
+ return 0;
+}
+
+#define LOG_ACC_CHG(acc_mgr, level, old_mask, verb_str) \
+ LOG_BTS((acc_mgr)->bts, DRSL, level, \
+ "ACC: %s ACC allowed active subset 0x%03" PRIx16 " -> 0x%03" PRIx16 \
+ " (active_len=%" PRIu8 ", ramp_len=%" PRIu8 ", adm_len=%" PRIu8 \
+ ", perm_len=%" PRIu8 ", rotation=%s)\n", \
+ verb_str, old_mask, (acc_mgr)->allowed_subset_mask, \
+ (acc_mgr)->allowed_subset_mask_count, \
+ (acc_mgr)->len_allowed_ramp, (acc_mgr)->len_allowed_adm, \
+ (acc_mgr)->allowed_permanent_count, \
+ osmo_timer_pending(&(acc_mgr)->rotate_timer) ? "on" : "off")
+
+/* Call when either adm_len or ramp_len changed (and values have been updated) */
+static void acc_mgr_subset_length_changed(struct acc_mgr *acc_mgr)
+{
+ uint16_t old_mask = acc_mgr->allowed_subset_mask;
+ uint8_t curr_len = acc_mgr->allowed_subset_mask_count;
+ uint8_t new_len = acc_mgr_subset_len(acc_mgr);
+ int8_t diff = new_len - curr_len;
+ uint8_t i;
+
+ if (curr_len == new_len)
+ return;
+
+ if (new_len == 0) {
+ acc_mgr->allowed_subset_mask = 0;
+ acc_mgr->allowed_subset_mask_count = 0;
+ acc_mgr_enable_rotation_cond(acc_mgr);
+ LOG_ACC_CHG(acc_mgr, LOGL_INFO, old_mask, "update");
+ gsm_bts_set_system_infos(acc_mgr->bts);
+ return;
+ }
+
+ if (curr_len == 0) {
+ acc_mgr_gen_subset(acc_mgr, true);
+ return;
+ }
+
+ /* Try to add new ACCs to the set starting from highest one (since we rotate rolling up) */
+ if (diff > 0) { /* curr_len < new_len */
+ uint8_t highest = get_highest_allowed_acc(acc_mgr->allowed_subset_mask);
+ /* It's fine skipping highest in the loop since it's known to be already set: */
+ for (i = (highest + 1) % 10; i != highest; i = (i + 1) % 10) {
+ if (acc_is_permanently_barred(acc_mgr->bts, i))
+ continue;
+ if (acc_mgr->allowed_subset_mask & (1 << i))
+ continue; /* already in set */
+ acc_mgr->allowed_subset_mask |= (1 << i);
+ acc_mgr->allowed_subset_mask_count++;
+ diff--;
+ if (diff == 0)
+ break;
+ }
+ } else { /* curr_len > new_len, try removing from lowest one. */
+ uint8_t lowest = get_lowest_allowed_acc(acc_mgr->allowed_subset_mask);
+ i = lowest;
+ do {
+ if ((acc_mgr->allowed_subset_mask & (1 << i))) {
+ acc_mgr->allowed_subset_mask &= ~(1 << i);
+ acc_mgr->allowed_subset_mask_count--;
+ diff++;
+ if (diff == 0)
+ break;
+ }
+ i = (i + 1) % 10;
+ } while(i != lowest);
+ }
+
+ acc_mgr_enable_rotation_cond(acc_mgr);
+ LOG_ACC_CHG(acc_mgr, LOGL_INFO, old_mask, "update");
+
+ /* if we updated the set, notify about it */
+ if (curr_len != acc_mgr->allowed_subset_mask_count)
+ gsm_bts_set_system_infos(acc_mgr->bts);
+
+}
+
+/* Eg: (2,3,4) -> first=2; last=4. (3,7,8) -> first=3, last=8; (8,9,2) -> first=8, last=2 */
+void get_subset_limits(struct acc_mgr *acc_mgr, uint8_t *first, uint8_t *last)
+{
+ uint8_t lowest = get_lowest_allowed_acc(acc_mgr->allowed_subset_mask);
+ uint8_t highest = get_highest_allowed_acc(acc_mgr->allowed_subset_mask);
+ /* check if there's unselected ACCs between lowest and highest, that
+ * means subset is wrapping around, eg: (8,9,1)
+ * Assumption: The permanent set is bigger than the current selected subset */
+ bool is_wrapped = false;
+ uint8_t i = (lowest + 1) % 10;
+ if (lowest != highest) { /* len(allowed_subset_mask) > 1 */
+ i = (lowest + 1) % 10;
+ do {
+ if (!acc_is_permanently_barred(acc_mgr->bts, i) &&
+ !(acc_mgr->allowed_subset_mask & (1 << i))) {
+ is_wrapped = true;
+ break;
+ }
+ i = (i + 1) % 10;
+ } while (i != (highest + 1) % 10);
+ }
+
+ if (is_wrapped) {
+ /* Assumption: "i" is pointing to the lowest dynamically barred ACC.
+ Example: 11 1000 00>0<1. */
+ *last = i - 1;
+ while (acc_is_permanently_barred(acc_mgr->bts, *last))
+ *last -= 1;
+ *first = i + 1;
+ while (acc_is_permanently_barred(acc_mgr->bts, *first) ||
+ !(acc_mgr->allowed_subset_mask & (1 << (*first))))
+ *first += 1;
+ } else {
+ *first = lowest;
+ *last = highest;
+ }
+}
+static void do_acc_rotate_step(void *data)
+{
+ struct acc_mgr *acc_mgr = data;
+ uint8_t i;
+ uint8_t first, last;
+ uint16_t old_mask = acc_mgr->allowed_subset_mask;
+
+ /* Assumption: The size of the subset didn't change, that's handled by
+ * acc_mgr_subset_length_changed()
+ */
+
+ /* Assumption: Rotation timer has been disabled if no ACC is allowed */
+ OSMO_ASSERT(acc_mgr->allowed_subset_mask_count != 0);
+
+ /* One ACC is rotated at a time: Drop first ACC and add next from last ACC */
+ get_subset_limits(acc_mgr, &first, &last);
+
+ acc_mgr->allowed_subset_mask &= ~(1 << first);
+ i = (last + 1) % 10;
+ do {
+ if (!acc_is_permanently_barred(acc_mgr->bts, i) &&
+ !(acc_mgr->allowed_subset_mask & (1 << i))) {
+ /* found first one which can be allowed, do it and be done */
+ acc_mgr->allowed_subset_mask |= (1 << i);
+ break;
+ }
+ i = (i + 1 ) % 10;
+ } while (i != (last + 1) % 10);
+
+ osmo_timer_schedule(&acc_mgr->rotate_timer, acc_mgr->rotation_time_sec, 0);
+
+ if (old_mask != acc_mgr->allowed_subset_mask) {
+ LOG_ACC_CHG(acc_mgr, LOGL_INFO, old_mask, "rotate");
+ gsm_bts_set_system_infos(acc_mgr->bts);
+ }
+}
+
+void acc_mgr_init(struct acc_mgr *acc_mgr, struct gsm_bts *bts)
+{
+ acc_mgr->bts = bts;
+ acc_mgr->len_allowed_adm = 10; /* Allow all by default */
+ acc_mgr->len_allowed_ramp = 10;
+ acc_mgr->rotation_time_sec = ACC_MGR_QUANTUM_DEFAULT;
+ osmo_timer_setup(&acc_mgr->rotate_timer, do_acc_rotate_step, acc_mgr);
+ /* FIXME: Don't update SI yet, avoid crash due to bts->model being NULL */
+ acc_mgr_gen_subset(acc_mgr, false);
+}
+
+uint8_t acc_mgr_get_len_allowed_adm(struct acc_mgr *acc_mgr)
+{
+ return acc_mgr->len_allowed_adm;
+}
+
+uint8_t acc_mgr_get_len_allowed_ramp(struct acc_mgr *acc_mgr)
+{
+ return acc_mgr->len_allowed_ramp;
+}
+
+void acc_mgr_set_len_allowed_adm(struct acc_mgr *acc_mgr, uint8_t len_allowed_adm)
+{
+ uint8_t old_len;
+
+ OSMO_ASSERT(len_allowed_adm <= 10);
+
+ if (acc_mgr->len_allowed_adm == len_allowed_adm)
+ return;
+
+ LOG_BTS(acc_mgr->bts, DRSL, LOGL_DEBUG,
+ "ACC: administrative rotate subset size set to %" PRIu8 "\n", len_allowed_adm);
+
+ old_len = acc_mgr_subset_len(acc_mgr);
+ acc_mgr->len_allowed_adm = len_allowed_adm;
+ if (old_len != acc_mgr_subset_len(acc_mgr))
+ acc_mgr_subset_length_changed(acc_mgr);
+}
+void acc_mgr_set_len_allowed_ramp(struct acc_mgr *acc_mgr, uint8_t len_allowed_ramp)
+{
+ uint8_t old_len;
+
+ OSMO_ASSERT(len_allowed_ramp <= 10);
+
+ if (acc_mgr->len_allowed_ramp == len_allowed_ramp)
+ return;
+
+ LOG_BTS(acc_mgr->bts, DRSL, LOGL_DEBUG,
+ "ACC: ramping rotate subset size set to %" PRIu8 "\n", len_allowed_ramp);
+
+ old_len = acc_mgr_subset_len(acc_mgr);
+ acc_mgr->len_allowed_ramp = len_allowed_ramp;
+ if (old_len != acc_mgr_subset_len(acc_mgr))
+ acc_mgr_subset_length_changed(acc_mgr);
+}
+
+void acc_mgr_set_rotation_time(struct acc_mgr *acc_mgr, uint32_t rotation_time_sec)
+{
+ LOG_BTS(acc_mgr->bts, DRSL, LOGL_DEBUG,
+ "ACC: rotate subset time set to %" PRIu32 " seconds\n", rotation_time_sec);
+ acc_mgr->rotation_time_sec = rotation_time_sec;
+}
+
+void acc_mgr_perm_subset_changed(struct acc_mgr *acc_mgr, struct gsm48_rach_control *rach_control)
+{
+ /* Even if amount is the same, the allowed/barred ones may have changed,
+ * so let's retrigger generation of an entire subset rather than
+ * rotating it */
+ acc_mgr_gen_subset(acc_mgr, true);
+}
+
+/*!
+ * Potentially mark certain Access Control Classes (ACCs) as barred in accordance to ACC policy.
+ * \param[in] acc_mgr Pointer to acc_mgr structure.
+ * \param[in] rach_control RACH control parameters in which barred ACCs will be configured.
+ */
+void acc_mgr_apply_acc(struct acc_mgr *acc_mgr, struct gsm48_rach_control *rach_control)
+{
+ rach_control->t2 |= acc_mgr_get_barred_t2(acc_mgr);
+ rach_control->t3 |= acc_mgr_get_barred_t3(acc_mgr);
+}
+
+
+//////////////////////////
+// acc_ramp
+//////////////////////////
+static void do_acc_ramping_step(void *data)
+{
+ struct acc_ramp *acc_ramp = data;
+ struct gsm_bts *bts = acc_ramp->bts;
+ struct acc_mgr *acc_mgr = &bts->acc_mgr;
+
+ uint8_t old_len = acc_mgr_get_len_allowed_ramp(acc_mgr);
+ uint8_t new_len = old_len;
+
+ /* Remark dec: Never decrease back to 0, it is desirable to always allow at
+ * least 1 ACC at ramping lvl to allow subscribers to eventually use the
+ * network. If total barring is desired, it can be controlled by the
+ * adminsitrative subset length through VTY.
+ * Remark inc: Never try going over the admin subset size, since it
+ * wouldn't change final subset size anyway and it would create a fake
+ * sense of safe load handling capacity. If then load became high, being
+ * on upper size would mean the BTS requires more time to effectively
+ * drop down the final subset size, hence delaying recovery.
+ */
+ if (bts->chan_load_avg > acc_ramp->chan_load_upper_threshold)
+ new_len = (uint8_t)OSMO_MAX(1, (int)(old_len - acc_ramp->step_size));
+ else if (bts->chan_load_avg < acc_ramp->chan_load_lower_threshold)
+ new_len = OSMO_MIN(acc_mgr_get_len_allowed_adm(acc_mgr),
+ old_len + acc_ramp->step_size);
+ else
+ new_len = old_len;
+
+ if (new_len != old_len) {
+ LOG_BTS(bts, DRSL, LOGL_DEBUG,
+ "ACC RAMP: changing ramping subset size %" PRIu8
+ " -> %" PRIu8 ", chan_load_avg=%" PRIu8 "%%\n",
+ old_len, new_len, bts->chan_load_avg);
+ acc_mgr_set_len_allowed_ramp(acc_mgr, new_len);
+ }
+
+ osmo_timer_schedule(&acc_ramp->step_timer, acc_ramp->step_interval_sec, 0);
+}
+
+/* Implements osmo_signal_cbfn() -- trigger or abort ACC ramping upon changes RF lock state. */
+static int acc_ramp_nm_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct nm_running_chg_signal_data *nsd;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ if (signal != S_NM_RUNNING_CHG)
+ return 0;
+ nsd = signal_data;
+ bts = nsd->bts;
+ switch (nsd->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ trx = (struct gsm_bts_trx *)nsd->obj;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ trx = gsm_bts_bb_trx_get_trx((struct gsm_bts_bb_trx *)nsd->obj);
+ break;
+ default:
+ return 0;
+ }
+
+ /* We only care about state changes of the first TRX. */
+ if (trx != trx->bts->c0)
+ return 0;
+
+ LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: nm_obj=%s running=%u\n",
+ get_value_string(abis_nm_obj_class_names, nsd->obj_class), nsd->running);
+ if (nsd->running) {
+ /* Trigger ramping only if TRX 0 is already usable. That usually
+ * means RCARRIER+BBTRANSC NM objects are running (op=enabled
+ * adm=unlocked) */
+ if (trx_is_usable(trx)) {
+ LOG_BTS(bts, DPAG, LOGL_INFO, "ACC RAMP: C0 becomes available\n");
+ acc_ramp_trigger(&trx->bts->acc_ramp);
+ } else {
+ LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: ignoring state change "
+ "because TRX is not usable\n");
+ }
+ } else {
+ acc_ramp_abort(&trx->bts->acc_ramp);
+ }
+ return 0;
+}
+
+/*!
+ * Initialize an acc_ramp data structure.
+ * Storage for this structure must be provided by the caller.
+ *
+ * By default, ACC ramping is disabled and all ACCs are allowed.
+ *
+ * \param[in] acc_ramp Pointer to acc_ramp structure to be initialized.
+ * \param[in] bts BTS which uses this ACC ramp data structure.
+ */
+void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts)
+{
+ acc_ramp->bts = bts;
+ acc_ramp_set_enabled(acc_ramp, false);
+ acc_ramp->step_size = ACC_RAMP_STEP_SIZE_DEFAULT;
+ acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN;
+ acc_ramp->chan_load_lower_threshold = ACC_RAMP_CHAN_LOAD_THRESHOLD_LOW;
+ acc_ramp->chan_load_upper_threshold = ACC_RAMP_CHAN_LOAD_THRESHOLD_UP;
+ osmo_timer_setup(&acc_ramp->step_timer, do_acc_ramping_step, acc_ramp);
+}
+
+/*!
+ * Change the ramping step size which controls how many ACCs will be allowed per ramping step.
+ * Returns negative on error (step_size out of range), else zero.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ * \param[in] step_size The new step size value.
+ */
+int acc_ramp_set_step_size(struct acc_ramp *acc_ramp, unsigned int step_size)
+{
+ if (step_size < ACC_RAMP_STEP_SIZE_MIN || step_size > ACC_RAMP_STEP_SIZE_MAX)
+ return -ERANGE;
+
+ acc_ramp->step_size = step_size;
+ LOG_BTS(acc_ramp->bts, DRSL, LOGL_DEBUG, "ACC RAMP: ramping step size set to %u\n", step_size);
+ return 0;
+}
+
+/*!
+ * Change the ramping step interval to a fixed value. Unless this function is called,
+ * the interval is automatically scaled to the BTS channel load average.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ * \param[in] step_interval The new fixed step interval in seconds.
+ */
+int acc_ramp_set_step_interval(struct acc_ramp *acc_ramp, unsigned int step_interval)
+{
+ if (step_interval < ACC_RAMP_STEP_INTERVAL_MIN || step_interval > ACC_RAMP_STEP_INTERVAL_MAX)
+ return -ERANGE;
+
+ acc_ramp->step_interval_sec = step_interval;
+ LOG_BTS(acc_ramp->bts, DRSL, LOGL_DEBUG, "ACC RAMP: ramping step interval set to %u seconds\n",
+ step_interval);
+ return 0;
+}
+
+/*!
+ * Change the ramping channel load thresholds. They control how ramping subset
+ * size of allowed ACCs changes in relation to current channel load (%, 0-100):
+ * Under the lower threshold, subset size may be increased; above the upper
+ * threshold, subset size may be decreased.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ * \param[in] low_threshold The new minimum threshold: values under it allow for increasing the ramping subset size.
+ * \param[in] up_threshold The new maximum threshold: values under it allow for increasing the ramping subset size.
+ */
+int acc_ramp_set_chan_load_thresholds(struct acc_ramp *acc_ramp, unsigned int low_threshold, unsigned int up_threshold)
+{
+ /* for instance, high=49 and lower=50 makes sense:
+ [50-100] -> decrease, [0-49] -> increase */
+ if ((int)up_threshold - (int)low_threshold < -1)
+ return -ERANGE;
+
+ acc_ramp->chan_load_lower_threshold = low_threshold;
+ acc_ramp->chan_load_upper_threshold = up_threshold;
+ return 0;
+}
+
+/*!
+ * Determine if ACC ramping should be started according to configuration, and
+ * begin the ramping process if the necessary conditions are present.
+ * Perform at least one ramping step to allow 'step_size' ACCs.
+ * If 'step_size' is ACC_RAMP_STEP_SIZE_MAX, or if ACC ramping is disabled,
+ * all ACCs will be allowed immediately.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+void acc_ramp_trigger(struct acc_ramp *acc_ramp)
+{
+ if (acc_ramp_is_enabled(acc_ramp)) {
+ if (osmo_timer_pending(&acc_ramp->step_timer))
+ return; /* Already started, nothing to do */
+
+ /* Set all available ACCs to barred and start ramping up. */
+ acc_mgr_set_len_allowed_ramp(&acc_ramp->bts->acc_mgr, 0);
+ if (acc_ramp->chan_load_lower_threshold == 0 &&
+ acc_ramp->chan_load_upper_threshold == 100) {
+ LOG_BTS(acc_ramp->bts, DRSL, LOGL_ERROR,
+ "ACC RAMP: starting ramp up with 0 ACCs and "
+ "no possibility to grow the allowed subset size! "
+ "Check VTY cmd access-control-class-ramping-chan-load\n");
+ }
+ do_acc_ramping_step(acc_ramp);
+ } else {
+ /* Abort any previously running ramping process and allow all available ACCs. */
+ acc_ramp_abort(acc_ramp);
+ }
+}
+
+/*!
+ * Abort the ramping process and allow all available ACCs immediately.
+ * \param[in] acc_ramp Pointer to acc_ramp structure.
+ */
+void acc_ramp_abort(struct acc_ramp *acc_ramp)
+{
+ osmo_timer_del(&acc_ramp->step_timer);
+
+ acc_mgr_set_len_allowed_ramp(&acc_ramp->bts->acc_mgr, 10);
+}
+
+void acc_ramp_global_init(void)
+{
+ osmo_signal_register_handler(SS_NM, acc_ramp_nm_sig_cb, NULL);
+}
diff --git a/src/osmo-bsc/acc_ramp.c b/src/osmo-bsc/acc_ramp.c
deleted file mode 100644
index bc2e3fb73..000000000
--- a/src/osmo-bsc/acc_ramp.c
+++ /dev/null
@@ -1,363 +0,0 @@
-/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
- *
- * Author: Stefan Sperling <ssperling@sysmocom.de>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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 <strings.h>
-#include <errno.h>
-#include <stdbool.h>
-
-#include <osmocom/bsc/debug.h>
-#include <osmocom/bsc/acc_ramp.h>
-#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/chan_alloc.h>
-#include <osmocom/bsc/signal.h>
-#include <osmocom/bsc/abis_nm.h>
-
-/*
- * Check if an ACC has been permanently barred for a BTS,
- * e.g. with the 'rach access-control-class' VTY command.
- */
-static bool acc_is_permanently_barred(struct gsm_bts *bts, unsigned int acc)
-{
- OSMO_ASSERT(acc <= 9);
- if (acc == 8 || acc == 9)
- return (bts->si_common.rach_control.t2 & (1 << (acc - 8)));
- return (bts->si_common.rach_control.t3 & (1 << (acc)));
-}
-
-static void allow_one_acc(struct acc_ramp *acc_ramp, unsigned int acc)
-{
- OSMO_ASSERT(acc <= 9);
- if (acc_ramp->barred_accs & (1 << acc))
- LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: allowing Access Control Class %u\n", acc_ramp->bts->nr, acc);
- acc_ramp->barred_accs &= ~(1 << acc);
-}
-
-static void barr_one_acc(struct acc_ramp *acc_ramp, unsigned int acc)
-{
- OSMO_ASSERT(acc <= 9);
- if ((acc_ramp->barred_accs & (1 << acc)) == 0)
- LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: barring Access Control Class %u\n", acc_ramp->bts->nr, acc);
- acc_ramp->barred_accs |= (1 << acc);
-}
-
-static void barr_all_accs(struct acc_ramp *acc_ramp)
-{
- unsigned int acc;
- for (acc = 0; acc < 10; acc++) {
- if (!acc_is_permanently_barred(acc_ramp->bts, acc))
- barr_one_acc(acc_ramp, acc);
- }
-}
-
-static void allow_all_accs(struct acc_ramp *acc_ramp)
-{
- unsigned int acc;
- for (acc = 0; acc < 10; acc++) {
- if (!acc_is_permanently_barred(acc_ramp->bts, acc))
- allow_one_acc(acc_ramp, acc);
- }
-}
-
-static unsigned int get_next_step_interval(struct acc_ramp *acc_ramp)
-{
- struct gsm_bts *bts = acc_ramp->bts;
- uint64_t load;
-
- if (acc_ramp->step_interval_is_fixed)
- return acc_ramp->step_interval_sec;
-
- /* Scale the step interval to current channel load average. */
- load = (bts->chan_load_avg << 8); /* convert to fixed-point */
- acc_ramp->step_interval_sec = ((load * ACC_RAMP_STEP_INTERVAL_MAX) / 100) >> 8;
- if (acc_ramp->step_interval_sec < ACC_RAMP_STEP_SIZE_MIN)
- acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN;
- else if (acc_ramp->step_interval_sec > ACC_RAMP_STEP_INTERVAL_MAX)
- acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MAX;
-
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: step interval set to %u seconds based on %u%% channel load average\n",
- bts->nr, acc_ramp->step_interval_sec, bts->chan_load_avg);
- return acc_ramp->step_interval_sec;
-}
-
-static void do_acc_ramping_step(void *data)
-{
- struct acc_ramp *acc_ramp = data;
- int i;
-
- /* Shortcut in case we only do one ramping step. */
- if (acc_ramp->step_size == ACC_RAMP_STEP_SIZE_MAX) {
- allow_all_accs(acc_ramp);
- gsm_bts_set_system_infos(acc_ramp->bts);
- return;
- }
-
- /* Allow 'step_size' ACCs, starting from ACC0. ACC9 will be allowed last. */
- for (i = 0; i < acc_ramp->step_size; i++) {
- int idx = ffs(acc_ramp_get_barred_t3(acc_ramp));
- if (idx > 0) {
- /* One of ACC0-ACC7 is still bared. */
- unsigned int acc = idx - 1;
- if (!acc_is_permanently_barred(acc_ramp->bts, acc))
- allow_one_acc(acc_ramp, acc);
- } else {
- idx = ffs(acc_ramp_get_barred_t2(acc_ramp));
- if (idx == 1 || idx == 2) {
- /* ACC8 or ACC9 is still barred. */
- unsigned int acc = idx - 1 + 8;
- if (!acc_is_permanently_barred(acc_ramp->bts, acc))
- allow_one_acc(acc_ramp, acc);
- } else {
- /* All ACCs are now allowed. */
- break;
- }
- }
- }
-
- gsm_bts_set_system_infos(acc_ramp->bts);
-
- /* If we have not allowed all ACCs yet, schedule another ramping step. */
- if (acc_ramp_get_barred_t2(acc_ramp) != 0x00 ||
- acc_ramp_get_barred_t3(acc_ramp) != 0x00)
- osmo_timer_schedule(&acc_ramp->step_timer, get_next_step_interval(acc_ramp), 0);
-}
-
-/* Implements osmo_signal_cbfn() -- trigger or abort ACC ramping upon changes RF lock state. */
-static int acc_ramp_nm_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
-{
- struct nm_statechg_signal_data *nsd = signal_data;
- struct acc_ramp *acc_ramp = handler_data;
- struct gsm_bts_trx *trx = NULL;
- bool trigger_ramping = false, abort_ramping = false;
-
- /* Handled signals map to an Administrative State Change ACK, or a State Changed Event Report. */
- if (signal != S_NM_STATECHG_ADM && signal != S_NM_STATECHG_OPER)
- return 0;
-
- if (nsd->obj_class != NM_OC_RADIO_CARRIER)
- return 0;
-
- trx = nsd->obj;
-
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: administrative state %s -> %s\n",
- acc_ramp->bts->nr, trx->nr,
- get_value_string(abis_nm_adm_state_names, nsd->old_state->administrative),
- get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative));
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: operational state %s -> %s\n",
- acc_ramp->bts->nr, trx->nr,
- abis_nm_opstate_name(nsd->old_state->operational),
- abis_nm_opstate_name(nsd->new_state->operational));
-
- /* We only care about state changes of the first TRX. */
- if (trx->nr != 0)
- return 0;
-
- /* RSL must already be up. We cannot send RACH system information to the BTS otherwise. */
- if (trx->rsl_link == NULL) {
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change because RSL link is down\n",
- acc_ramp->bts->nr, trx->nr);
- return 0;
- }
-
- /* Trigger or abort ACC ramping based on the new state of this TRX. */
- if (nsd->old_state->administrative != nsd->new_state->administrative) {
- switch (nsd->new_state->administrative) {
- case NM_STATE_UNLOCKED:
- if (nsd->old_state->operational != nsd->new_state->operational) {
- /*
- * Administrative and operational state have both changed.
- * Trigger ramping only if TRX 0 will be both enabled and unlocked.
- */
- if (nsd->new_state->operational == NM_OPSTATE_ENABLED)
- trigger_ramping = true;
- else
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change "
- "because TRX is transitioning into operational state '%s'\n",
- acc_ramp->bts->nr, trx->nr,
- abis_nm_opstate_name(nsd->new_state->operational));
- } else {
- /*
- * Operational state has not changed.
- * Trigger ramping only if TRX 0 is already usable.
- */
- if (trx_is_usable(trx))
- trigger_ramping = true;
- else
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change "
- "because TRX is not usable\n", acc_ramp->bts->nr, trx->nr);
- }
- break;
- case NM_STATE_LOCKED:
- case NM_STATE_SHUTDOWN:
- abort_ramping = true;
- break;
- case NM_STATE_NULL:
- default:
- LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized administrative state '0x%x' "
- "reported for TRX 0\n", acc_ramp->bts->nr, nsd->new_state->administrative);
- break;
- }
- }
- if (nsd->old_state->operational != nsd->new_state->operational) {
- switch (nsd->new_state->operational) {
- case NM_OPSTATE_ENABLED:
- if (nsd->old_state->administrative != nsd->new_state->administrative) {
- /*
- * Administrative and operational state have both changed.
- * Trigger ramping only if TRX 0 will be both enabled and unlocked.
- */
- if (nsd->new_state->administrative == NM_STATE_UNLOCKED)
- trigger_ramping = true;
- else
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change "
- "because TRX is transitioning into administrative state '%s'\n",
- acc_ramp->bts->nr, trx->nr,
- get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative));
- } else {
- /*
- * Administrative state has not changed.
- * Trigger ramping only if TRX 0 is already unlocked.
- */
- if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
- trigger_ramping = true;
- else
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change "
- "because TRX is in administrative state '%s'\n",
- acc_ramp->bts->nr, trx->nr,
- get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative));
- }
- break;
- case NM_OPSTATE_DISABLED:
- abort_ramping = true;
- break;
- case NM_OPSTATE_NULL:
- default:
- LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized operational state '0x%x' "
- "reported for TRX 0\n", acc_ramp->bts->nr, nsd->new_state->administrative);
- break;
- }
- }
-
- if (trigger_ramping)
- acc_ramp_trigger(acc_ramp);
- else if (abort_ramping)
- acc_ramp_abort(acc_ramp);
-
- return 0;
-}
-
-/*!
- * Initialize an acc_ramp data structure.
- * Storage for this structure must be provided by the caller.
- *
- * By default, ACC ramping is disabled and all ACCs are allowed.
- *
- * \param[in] acc_ramp Pointer to acc_ramp structure to be initialized.
- * \param[in] bts BTS which uses this ACC ramp data structure.
- */
-void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts)
-{
- acc_ramp->bts = bts;
- acc_ramp_set_enabled(acc_ramp, false);
- acc_ramp->step_size = ACC_RAMP_STEP_SIZE_DEFAULT;
- acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN;
- acc_ramp->step_interval_is_fixed = false;
- allow_all_accs(acc_ramp);
- osmo_timer_setup(&acc_ramp->step_timer, do_acc_ramping_step, acc_ramp);
- osmo_signal_register_handler(SS_NM, acc_ramp_nm_sig_cb, acc_ramp);
-}
-
-/*!
- * Change the ramping step size which controls how many ACCs will be allowed per ramping step.
- * Returns negative on error (step_size out of range), else zero.
- * \param[in] acc_ramp Pointer to acc_ramp structure.
- * \param[in] step_size The new step size value.
- */
-int acc_ramp_set_step_size(struct acc_ramp *acc_ramp, unsigned int step_size)
-{
- if (step_size < ACC_RAMP_STEP_SIZE_MIN || step_size > ACC_RAMP_STEP_SIZE_MAX)
- return -ERANGE;
-
- acc_ramp->step_size = step_size;
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step size set to %u\n", acc_ramp->bts->nr, step_size);
- return 0;
-}
-
-/*!
- * Change the ramping step interval to a fixed value. Unless this function is called,
- * the interval is automatically scaled to the BTS channel load average.
- * \param[in] acc_ramp Pointer to acc_ramp structure.
- * \param[in] step_interval The new fixed step interval in seconds.
- */
-int acc_ramp_set_step_interval(struct acc_ramp *acc_ramp, unsigned int step_interval)
-{
- if (step_interval < ACC_RAMP_STEP_INTERVAL_MIN || step_interval > ACC_RAMP_STEP_INTERVAL_MAX)
- return -ERANGE;
-
- acc_ramp->step_interval_sec = step_interval;
- acc_ramp->step_interval_is_fixed = true;
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to %u seconds\n",
- acc_ramp->bts->nr, step_interval);
- return 0;
-}
-
-/*!
- * Clear a previously set fixed ramping step interval, so that the interval
- * is again automatically scaled to the BTS channel load average.
- * \param[in] acc_ramp Pointer to acc_ramp structure.
- */
-void acc_ramp_set_step_interval_dynamic(struct acc_ramp *acc_ramp)
-{
- acc_ramp->step_interval_is_fixed = false;
- LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to 'dynamic'\n",
- acc_ramp->bts->nr);
-}
-
-/*!
- * Determine if ACC ramping should be started according to configuration, and
- * begin the ramping process if the necessary conditions are present.
- * Perform at least one ramping step to allow 'step_size' ACCs.
- * If 'step_size' is ACC_RAMP_STEP_SIZE_MAX, or if ACC ramping is disabled,
- * all ACCs will be allowed immediately.
- * \param[in] acc_ramp Pointer to acc_ramp structure.
- */
-void acc_ramp_trigger(struct acc_ramp *acc_ramp)
-{
- /* Abort any previously running ramping process and allow all available ACCs. */
- acc_ramp_abort(acc_ramp);
-
- if (acc_ramp_is_enabled(acc_ramp)) {
- /* Set all available ACCs to barred and start ramping up. */
- barr_all_accs(acc_ramp);
- do_acc_ramping_step(acc_ramp);
- }
-}
-
-/*!
- * Abort the ramping process and allow all available ACCs immediately.
- * \param[in] acc_ramp Pointer to acc_ramp structure.
- */
-void acc_ramp_abort(struct acc_ramp *acc_ramp)
-{
- if (osmo_timer_pending(&acc_ramp->step_timer))
- osmo_timer_del(&acc_ramp->step_timer);
-
- allow_all_accs(acc_ramp);
-}
diff --git a/src/osmo-bsc/arfcn_range_encode.c b/src/osmo-bsc/arfcn_range_encode.c
deleted file mode 100644
index 54d98a967..000000000
--- a/src/osmo-bsc/arfcn_range_encode.c
+++ /dev/null
@@ -1,340 +0,0 @@
-/* gsm 04.08 system information (si) encoding and decoding
- * 3gpp ts 04.08 version 7.21.0 release 1998 / etsi ts 100 940 v7.21.0 */
-
-/*
- * (C) 2012 Holger Hans Peter Freyther
- * (C) 2012 by On-Waves
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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/arfcn_range_encode.h>
-#include <osmocom/bsc/debug.h>
-
-#include <osmocom/gsm/protocol/gsm_04_08.h>
-
-#include <osmocom/core/utils.h>
-
-#include <errno.h>
-
-static inline int greatest_power_of_2_lesser_or_equal_to(int index)
-{
- int power_of_2 = 1;
-
- do {
- power_of_2 *= 2;
- } while (power_of_2 <= index);
-
- /* now go back one step */
- return power_of_2 / 2;
-}
-
-static inline int mod(int data, int range)
-{
- int res = data % range;
- while (res < 0)
- res += range;
- return res;
-}
-
-/**
- * Determine at which index to split the ARFCNs to create an
- * equally size partition for the given range. Return -1 if
- * no such partition exists.
- */
-int range_enc_find_index(enum gsm48_range range, const int *freqs, const int size)
-{
- int i, j, n;
-
- const int RANGE_DELTA = (range - 1) / 2;
-
- for (i = 0; i < size; ++i) {
- n = 0;
- for (j = 0; j < size; ++j) {
- if (mod(freqs[j] - freqs[i], range) <= RANGE_DELTA)
- n += 1;
- }
-
- if (n - 1 == (size - 1) / 2)
- return i;
- }
-
- return -1;
-}
-
-/* Worker for range_enc_arfcns(), do not call directly. */
-int _range_enc_arfcns(enum gsm48_range range,
- const int *arfcns, int size, int *out,
- const int index)
-{
- int split_at;
- int i;
-
- /*
- * The below is a GNU extension and we can remove it when
- * we move to a quicksort like in-situ swap with the pivot.
- */
- int arfcns_left[size / 2];
- int arfcns_right[size / 2];
- int l_size;
- int r_size;
- int l_origin;
- int r_origin;
-
- /* Now do the processing */
- split_at = range_enc_find_index(range, arfcns, size);
- if (split_at < 0)
- return -EINVAL;
-
- /* we now know where to split */
- out[index] = 1 + arfcns[split_at];
-
- /* calculate the work that needs to be done for the leafs */
- l_origin = mod(arfcns[split_at] + ((range - 1) / 2) + 1, range);
- r_origin = mod(arfcns[split_at] + 1, range);
- for (i = 0, l_size = 0, r_size = 0; i < size; ++i) {
- if (mod(arfcns[i] - l_origin, range) < range / 2)
- arfcns_left[l_size++] = mod(arfcns[i] - l_origin, range);
- if (mod(arfcns[i] - r_origin, range) < range / 2)
- arfcns_right[r_size++] = mod(arfcns[i] - r_origin, range);
- }
-
- /*
- * Now recurse and we need to make this iterative... but as the
- * tree is balanced the stack will not be too deep.
- */
- if (l_size)
- range_enc_arfcns(range / 2, arfcns_left, l_size,
- out, index + greatest_power_of_2_lesser_or_equal_to(index + 1));
- if (r_size)
- range_enc_arfcns((range - 1) / 2, arfcns_right, r_size,
- out, index + (2 * greatest_power_of_2_lesser_or_equal_to(index + 1)));
- return 0;
-}
-
-/**
- * Range encode the ARFCN list.
- * \param range The range to use.
- * \param arfcns The list of ARFCNs
- * \param size The size of the list of ARFCNs
- * \param out Place to store the W(i) output.
- */
-int range_enc_arfcns(enum gsm48_range range,
- const int *arfcns, int size, int *out,
- const int index)
-{
- if (size <= 0)
- return 0;
-
- if (size == 1) {
- out[index] = 1 + arfcns[0];
- return 0;
- }
-
- return _range_enc_arfcns(range, arfcns, size, out, index);
-}
-
-/*
- * The easiest is to use f0 == arfcns[0]. This means that under certain
- * circumstances we can encode less ARFCNs than possible with an optimal f0.
- *
- * TODO: Solve the optimisation problem and pick f0 so that the max distance
- * is the smallest. Taking into account the modulo operation. I think picking
- * size/2 will be the optimal arfcn.
- */
-/**
- * This implements the range determination as described in GSM 04.08 J4. The
- * result will be a base frequency f0 and the range to use. Note that for range
- * 1024 encoding f0 always refers to ARFCN 0 even if it is not an element of
- * the arfcns list.
- *
- * \param[in] arfcns The input frequencies, they must be sorted, lowest number first
- * \param[in] size The length of the array
- * \param[out] f0 The selected F0 base frequency. It might not be inside the list
- */
-int range_enc_determine_range(const int *arfcns, const int size, int *f0)
-{
- int max = 0;
-
- /* don't dereference arfcns[] array if size is 0 */
- if (size == 0)
- return ARFCN_RANGE_128;
-
- /*
- * Go for the easiest. And pick arfcns[0] == f0.
- */
- max = arfcns[size - 1] - arfcns[0];
- *f0 = arfcns[0];
-
- if (max < 128 && size <= 29)
- return ARFCN_RANGE_128;
- if (max < 256 && size <= 22)
- return ARFCN_RANGE_256;
- if (max < 512 && size <= 18)
- return ARFCN_RANGE_512;
- if (max < 1024 && size <= 17) {
- *f0 = 0;
- return ARFCN_RANGE_1024;
- }
-
- return ARFCN_RANGE_INVALID;
-}
-
-static void write_orig_arfcn(uint8_t *chan_list, int f0)
-{
- chan_list[0] |= (f0 >> 9) & 1;
- chan_list[1] = (f0 >> 1);
- chan_list[2] = (f0 & 1) << 7;
-}
-
-static void write_all_wn(uint8_t *chan_list, int bit_offs,
- int *w, int w_size, int w1_len)
-{
- int octet_offs = 0; /* offset into chan_list */
- int wk_len = w1_len; /* encoding size in bits of w[k] */
- int k; /* 1 based */
- int level = 0; /* tree level, top level = 0 */
- int lvl_left = 1; /* nodes per tree level */
-
- /* W(2^i) to W(2^(i+1)-1) are on w1_len-i bits when present */
-
- for (k = 1; k <= w_size; k++) {
- int wk_left = wk_len;
- DEBUGP(DRR,
- "k=%d, wk_len=%d, offs=%d:%d, level=%d, "
- "lvl_left=%d\n",
- k, wk_len, octet_offs, bit_offs, level, lvl_left);
-
- while (wk_left > 0) {
- int cur_bits = 8 - bit_offs;
- int cur_mask;
- int wk_slice;
-
- if (cur_bits > wk_left)
- cur_bits = wk_left;
-
- cur_mask = ((1 << cur_bits) - 1);
-
- DEBUGP(DRR,
- " wk_left=%d, cur_bits=%d, offs=%d:%d\n",
- wk_left, cur_bits, octet_offs, bit_offs);
-
- /* advance */
- wk_left -= cur_bits;
- bit_offs += cur_bits;
-
- /* right aligned wk data for current out octet */
- wk_slice = (w[k-1] >> wk_left) & cur_mask;
-
- /* cur_bits now contains the number of bits
- * that are to be copied from wk to the chan_list.
- * wk_left is set to the number of bits that must
- * not yet be copied.
- * bit_offs points after the bit area that is going to
- * be overwritten:
- *
- * wk_left
- * |
- * v
- * wk: WWWWWWWWWWW
- * |||||<-- wk_slice, cur_bits=5
- * --WWWWW-
- * ^
- * |
- * bit_offs
- */
-
- DEBUGP(DRR,
- " wk=%02x, slice=%02x/%02x, cl=%02x\n",
- w[k-1], wk_slice, cur_mask, wk_slice << (8 - bit_offs));
-
- chan_list[octet_offs] &= ~(cur_mask << (8 - bit_offs));
- chan_list[octet_offs] |= wk_slice << (8 - bit_offs);
-
- /* adjust output */
- if (bit_offs == 8) {
- bit_offs = 0;
- octet_offs += 1;
- }
- }
-
- /* adjust bit sizes */
- lvl_left -= 1;
- if (!lvl_left) {
- /* completed tree level, advance to next */
- level += 1;
- lvl_left = 1 << level;
- wk_len -= 1;
- }
- }
-}
-
-int range_enc_range128(uint8_t *chan_list, int f0, int *w)
-{
- chan_list[0] = 0x8C;
- write_orig_arfcn(chan_list, f0);
-
- write_all_wn(&chan_list[2], 1, w, 28, 7);
- return 0;
-}
-
-int range_enc_range256(uint8_t *chan_list, int f0, int *w)
-{
- chan_list[0] = 0x8A;
- write_orig_arfcn(chan_list, f0);
-
- write_all_wn(&chan_list[2], 1, w, 21, 8);
- return 0;
-}
-
-int range_enc_range512(uint8_t *chan_list, int f0, int *w)
-{
- chan_list[0] = 0x88;
- write_orig_arfcn(chan_list, f0);
-
- write_all_wn(&chan_list[2], 1, w, 17, 9);
- return 0;
-}
-
-int range_enc_range1024(uint8_t *chan_list, int f0, int f0_included, int *w)
-{
- chan_list[0] = 0x80 | (f0_included << 2);
-
- write_all_wn(&chan_list[0], 6, w, 16, 10);
- return 0;
-}
-
-int range_enc_filter_arfcns(int *arfcns,
- const int size, const int f0, int *f0_included)
-{
- int i, j = 0;
- *f0_included = 0;
-
- for (i = 0; i < size; ++i) {
- /*
- * Appendix J.4 says the following:
- * All frequencies except F(0), minus F(0) + 1.
- * I assume we need to exclude it here.
- */
- if (arfcns[i] == f0) {
- *f0_included = 1;
- continue;
- }
-
- arfcns[j++] = mod(arfcns[i] - (f0 + 1), 1024);
- }
-
- return j;
-}
diff --git a/src/osmo-bsc/assignment_fsm.c b/src/osmo-bsc/assignment_fsm.c
index 0d0369aee..5e98a28e8 100644
--- a/src/osmo-bsc/assignment_fsm.c
+++ b/src/osmo-bsc/assignment_fsm.c
@@ -20,27 +20,29 @@
*
*/
+#include <osmocom/core/tdef.h>
#include <osmocom/gsm/gsm0808.h>
-#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/gsm_timers.h>
#include <osmocom/bsc/lchan_fsm.h>
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/osmo_bsc_lcls.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/gsm_08_08.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/abis_rsl.h>
-
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bsc_stats.h>
+#include <osmocom/bsc/lchan.h>
#include <osmocom/bsc/assignment_fsm.h>
static struct osmo_fsm assignment_fsm;
-struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi)
+static struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi)
{
OSMO_ASSERT(fi);
OSMO_ASSERT(fi->fsm == &assignment_fsm);
@@ -48,21 +50,21 @@ struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi)
return fi->priv;
}
-static const struct state_timeout assignment_fsm_timeouts[32] = {
- [ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = { .T=10 },
- [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = { .keep_timer=true },
- [ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED] = { .keep_timer=true },
- [ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T=23042 },
+static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = {
+ [ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = { .T = 10 },
+ [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = { .keep_timer = true },
+ [ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED] = { .keep_timer = true },
+ [ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T = -9 },
};
/* Transition to a state, using the T timer defined in assignment_fsm_timeouts.
* The actual timeout value is in turn obtained from network->T_defs.
* Assumes local variable fi exists. */
#define assignment_fsm_state_chg(state) \
- fsm_inst_state_chg_T(fi, state, \
- assignment_fsm_timeouts, \
- ((struct gsm_subscriber_connection*)(fi->priv))->network->T_defs, \
- 5)
+ osmo_tdef_fsm_inst_state_chg(fi, state, \
+ assignment_fsm_timeouts, \
+ ((struct gsm_subscriber_connection*)(fi->priv))->network->T_defs, \
+ 5)
/* Log failure and transition to ASSIGNMENT_ST_FAILURE, which triggers the appropriate actions. */
#define assignment_fail(cause, fmt, args...) do { \
@@ -70,17 +72,41 @@ static const struct state_timeout assignment_fsm_timeouts[32] = {
_conn->assignment.failure_cause = cause; \
LOG_ASSIGNMENT(_conn, LOGL_ERROR, "Assignment failed in state %s, cause %s: " fmt "\n", \
osmo_fsm_inst_state_name(fi), gsm0808_cause_name(cause), ## args); \
- assignment_count_result(BSC_CTR_ASSIGNMENT_ERROR); \
+ assignment_count_result(CTR_ASSIGNMENT_ERROR); \
on_assignment_failure(_conn); \
- } while(0)
+ } while (0)
/* Assume presence of local var 'conn' as struct gsm_subscriber_connection */
#define assignment_count(counter) do { \
+ struct gsm_bts *bts = conn_get_bts(conn); \
LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: %s %s\n", \
- bsc_ctr_description[counter].name, \
- bsc_ctr_description[counter].description); \
- rate_ctr_inc(&conn->network->bsc_ctrs->ctr[counter]); \
- } while(0)
+ bsc_ctr_description[BSC_##counter].name, \
+ bsc_ctr_description[BSC_##counter].description); \
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->network->bsc_ctrs, BSC_##counter)); \
+ if (bts) { \
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_##counter)); \
+ switch (gsm48_chan_mode_to_non_vamos(conn->assignment.req.ch_mode_rate_list[0].chan_mode)) { \
+ case GSM48_CMODE_SIGN: \
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_##counter##_SIGN)); \
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: bts%u %s %s\n", \
+ bts->nr, \
+ bts_ctr_description[BTS_##counter##_SIGN].name, \
+ bts_ctr_description[BTS_##counter##_SIGN].description); \
+ break; \
+ case GSM48_CMODE_SPEECH_V1: \
+ case GSM48_CMODE_SPEECH_EFR: \
+ case GSM48_CMODE_SPEECH_AMR: \
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_##counter##_SPEECH)); \
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: bts%u %s %s\n", \
+ bts->nr, \
+ bts_ctr_description[BTS_##counter##_SPEECH].name, \
+ bts_ctr_description[BTS_##counter##_SPEECH].description); \
+ break; \
+ default: \
+ break; \
+ } \
+ } \
+ } while (0)
#define assignment_count_result(counter) do { \
if (!conn->assignment.result_rate_ctr_done) { \
@@ -88,24 +114,27 @@ static const struct state_timeout assignment_fsm_timeouts[32] = {
conn->assignment.result_rate_ctr_done = true; \
} else \
LOG_ASSIGNMENT(conn, LOGL_DEBUG, \
- "result rate counter already recorded, NOT counting as: %s %s", \
- bsc_ctr_description[counter].name, \
- bsc_ctr_description[counter].description); \
- } while(0)
+ "result rate counter already recorded, NOT counting as: %s %s\n", \
+ bsc_ctr_description[BSC_##counter].name, \
+ bsc_ctr_description[BSC_##counter].description); \
+ } while (0)
void assignment_reset(struct gsm_subscriber_connection *conn)
{
if (conn->assignment.new_lchan) {
struct gsm_lchan *lchan = conn->assignment.new_lchan;
conn->assignment.new_lchan = NULL;
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
}
if (conn->assignment.created_ci_for_msc) {
- gscon_forget_mgw_endpoint_ci(conn, conn->assignment.created_ci_for_msc);
+ /* Store ci pointer locally, because gscon_forget_mgw_endpoint_ci() NULLs
+ * conn->assignment.created_ci_for_msc. */
+ struct osmo_mgcpc_ep_ci *ci = conn->assignment.created_ci_for_msc;
+ gscon_forget_mgw_endpoint_ci(conn, ci);
/* If this is the last endpoint released, the mgw_endpoint_fsm will terminate and tell
* the gscon about it. */
- mgw_endpoint_ci_dlcx(conn->assignment.created_ci_for_msc);
+ osmo_mgcpc_ep_ci_dlcx(ci);
}
conn->assignment = (struct assignment_fsm_data){
@@ -115,12 +144,19 @@ void assignment_reset(struct gsm_subscriber_connection *conn)
static void on_assignment_failure(struct gsm_subscriber_connection *conn)
{
- struct msgb *resp = gsm0808_create_assignment_failure(conn->assignment.failure_cause, NULL);
-
- if (!resp)
- LOG_ASSIGNMENT(conn, LOGL_ERROR, "Unable to compose BSSMAP Assignment Failure message\n");
- else
- gscon_sigtran_send(conn, resp);
+ /* Send Assignment Failure to MSC only when the assignment was requested via BSSAP. Do not send anything to the
+ * MSC if re-assignment was requested for congestion resolution, for VAMOS multiplexing, or by VTY. */
+ if (conn->assignment.req.assign_for == ASSIGN_FOR_BSSMAP_REQ) {
+ struct msgb *resp = gsm0808_create_assignment_failure(conn->assignment.failure_cause, NULL);
+
+ if (!resp) {
+ LOG_ASSIGNMENT(conn, LOGL_ERROR, "Unable to compose BSSMAP Assignment Failure message\n");
+ } else {
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs,
+ MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_FAILURE));
+ gscon_sigtran_send(conn, resp);
+ }
+ }
/* If assignment failed as early as in assignment_fsm_start(), there may not be an fi yet. */
if (conn->assignment.fi) {
@@ -129,6 +165,13 @@ static void on_assignment_failure(struct gsm_subscriber_connection *conn)
}
}
+void bssap_extend_osmux(struct msgb *msg, uint8_t cid)
+{
+ OSMO_ASSERT(msg->l3h[1] == msgb_l3len(msg) - 2); /*TL not in len */
+ msgb_tv_put(msg, GSM0808_IE_OSMO_OSMUX_CID, cid);
+ msg->l3h[1] = msgb_l3len(msg) - 2;
+}
+
static void send_assignment_complete(struct gsm_subscriber_connection *conn)
{
int rc;
@@ -136,49 +179,83 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn)
struct gsm0808_speech_codec *sc_ptr = NULL;
struct sockaddr_storage addr_local;
struct sockaddr_storage *addr_local_p = NULL;
+ uint8_t osmux_cid = 0;
int perm_spch = 0;
uint8_t chosen_channel;
struct msgb *resp;
struct gsm_lchan *lchan = conn->lchan;
struct osmo_fsm_inst *fi = conn->fi;
- chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode);
+ if (!lchan) {
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, "Assignment interrupted: primary lchan lost");
+ return;
+ }
+
+ chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_mode);
if (!chosen_channel) {
assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
"Unable to compose Chosen Channel for mode=%s type=%s",
- get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
- gsm_lchant_name(lchan->type));
+ get_value_string(gsm48_chan_mode_names, lchan->current_ch_mode_rate.chan_mode),
+ gsm_chan_t_name(lchan->type));
return;
}
- /* Generate voice related fields */
- if (conn->assignment.requires_voice_stream) {
- perm_spch = gsm0808_permitted_speech(lchan->type, lchan->tch_mode);
-
- if (gscon_is_aoip(conn)) {
- if (!mgwep_ci_get_crcx_info_to_sockaddr(conn->user_plane.mgw_endpoint_ci_msc,
+ if (gscon_is_aoip(conn) && bsc_chan_ind_requires_rtp_stream(conn->assignment.ch_indctr)) {
+ if (!osmo_mgcpc_ep_ci_get_crcx_info_to_sockaddr(conn->user_plane.mgw_endpoint_ci_msc,
&addr_local)) {
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Unable to compose RTP address of MGW -> MSC");
+ return;
+ }
+ addr_local_p = &addr_local;
+ }
+
+ /* Generate rtp related fields */
+ switch (conn->assignment.ch_indctr) {
+ case GSM0808_CHAN_SPEECH:
+ perm_spch = gsm0808_permitted_speech(lchan->type, lchan->current_ch_mode_rate.chan_mode);
+
+ /* below is AoIP specific logic */
+ if (!gscon_is_aoip(conn))
+ break;
+
+ if (conn->assignment.req.use_osmux) {
+ if (!osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(conn->user_plane.mgw_endpoint_ci_msc,
+ &osmux_cid)) {
assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
- "Unable to compose RTP address of MGW -> MSC");
+ "Unable to compose Osmux CID of MGW -> MSC");
return;
}
- addr_local_p = &addr_local;
}
- /* Only AoIP networks include a speech codec (choosen) in the
- * assignment complete message. */
- if (gscon_is_aoip(conn)) {
- /* Extrapolate speech codec from speech mode */
- gsm0808_speech_codec_from_chan_type(&sc, perm_spch);
- sc.cfg = conn->assignment.req.s15_s0;
- sc_ptr = &sc;
- }
+ /* Extrapolate speech codec from speech mode */
+ gsm0808_speech_codec_from_chan_type(&sc, perm_spch);
+ sc.cfg = conn->lchan->current_ch_mode_rate.s15_s0;
+ sc_ptr = &sc;
+ break;
+ case GSM0808_CHAN_DATA:
+ /* below is AoIP specific logic */
+ if (!gscon_is_aoip(conn))
+ break;
+
+ /* The coding of Speech Codec Element for the CSData Codec Type
+ * is defined in 3GPP TS 48.008 section 3.2.2.103 */
+ sc = (struct gsm0808_speech_codec) {
+ .pi = true, /* PI indicates CSDoIP support */
+ .pt = false, /* PT indicates CSDoTDM support */
+ .type = GSM0808_SCT_CSD,
+ .cfg = 0, /* R2/R3 not set (redundancy not supported) */
+ };
+ sc_ptr = &sc;
+ break;
+ default:
+ break;
}
- resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause,
- chosen_channel,
- lchan->encr.alg_id, perm_spch,
- addr_local_p, sc_ptr, NULL);
+ resp = gsm0808_create_ass_compl2(lchan->abis_ip.ass_compl.rr_cause,
+ chosen_channel,
+ ALG_A5_NR_TO_BSSAP(lchan->encr.alg_a5_n), perm_spch,
+ addr_local_p, sc_ptr, NULL, lcls_get_status(conn));
if (!resp) {
assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
@@ -186,9 +263,11 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn)
return;
}
- /* Add LCLS BSS-Status IE in case there is any LCLS status for this connection */
- bssmap_add_lcls_status_if_needed(conn, resp);
+ if (gscon_is_aoip(conn) && bsc_chan_ind_requires_rtp_stream(conn->assignment.ch_indctr) &&
+ conn->assignment.req.use_osmux)
+ bssap_extend_osmux(resp, osmux_cid);
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_COMPLETE));
rc = gscon_sigtran_send(conn, resp);
if (rc) {
assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
@@ -200,72 +279,91 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn)
static void assignment_success(struct gsm_subscriber_connection *conn)
{
- /* Take on the new lchan */
- gscon_change_primary_lchan(conn, conn->assignment.new_lchan);
- conn->assignment.new_lchan = NULL;
+ struct gsm_bts *bts;
+ bool lchan_changed = (conn->assignment.new_lchan != NULL && !conn->assignment.req.vgcs);
+
+ /* Take on the new lchan. If there only was a Channel Mode Modify, then there is no new lchan to take on.
+ * In case of VGCS/VBS channel, the assignment is handled by its state machine. This subscriber connection will
+ * be released by MSC. */
+ if (lchan_changed) {
+ gscon_change_primary_lchan(conn, conn->assignment.new_lchan);
+
+ OSMO_ASSERT((bts = conn_get_bts(conn)) != NULL);
+ if (is_siemens_bts(bts) && ts_is_tch(conn->lchan->ts)) {
+ /* HACK: store the actual Classmark 2 LV from the subscriber and use it here! */
+ uint8_t cm2_lv[] = { 0x02, 0x00, 0x00 };
+ send_siemens_mrpci(conn->lchan, cm2_lv);
+ }
- /* apply LCLS configuration (if any) */
- lcls_apply_config(conn);
+ /* apply LCLS configuration (if any) */
+ lcls_apply_config(conn);
+ }
+ conn->assignment.new_lchan = NULL;
- send_assignment_complete(conn);
- /* If something went wrong during send_assignment_complete(), the fi will be gone from
- * error handling in there. Almost a success, but then again the whole thing failed. */
- if (!conn->assignment.fi) {
- /* The lchan was ready, and we failed to tell the MSC about it. By releasing this lchan,
- * the conn will notice that its primary lchan is gone and should clean itself up. */
- lchan_release(conn->lchan, true, true, RSL_ERR_EQUIPMENT_FAIL);
- return;
+ if (conn->assignment.req.assign_for == ASSIGN_FOR_BSSMAP_REQ) {
+ send_assignment_complete(conn);
+ /* If something went wrong during send_assignment_complete(), the fi will be gone from
+ * error handling in there. Almost a success, but then again the whole thing failed. */
+ if (!conn->assignment.fi) {
+ /* The lchan was ready, and we failed to tell the MSC about it. By releasing this lchan,
+ * the conn will notice that its primary lchan is gone and should clean itself up. */
+ lchan_release(conn->lchan, true, true, RSL_ERR_EQUIPMENT_FAIL,
+ gscon_last_eutran_plmn(conn));
+ return;
+ }
}
/* Rembered this only for error handling: should assignment fail, assignment_reset() will release
* the MGW endpoint right away. If successful, the conn continues to use the endpoint. */
conn->assignment.created_ci_for_msc = NULL;
- /* New RTP information is now accepted */
+ /* New RTP information is now accepted. If there is no RTP stream, this information is zero / empty. Either way
+ * store the result of this assignment. */
conn->user_plane.msc_assigned_cic = conn->assignment.req.msc_assigned_cic;
osmo_strlcpy(conn->user_plane.msc_assigned_rtp_addr, conn->assignment.req.msc_rtp_addr,
sizeof(conn->user_plane.msc_assigned_rtp_addr));
conn->user_plane.msc_assigned_rtp_port = conn->assignment.req.msc_rtp_port;
+ assignment_count_result(CTR_ASSIGNMENT_COMPLETED);
+
LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Assignment successful\n");
osmo_fsm_inst_term(conn->assignment.fi, OSMO_FSM_TERM_REGULAR, 0);
-
- assignment_count_result(BSC_CTR_ASSIGNMENT_COMPLETED);
}
-static void assignment_fsm_update_id(struct gsm_subscriber_connection *conn)
+void assignment_fsm_update_id(struct gsm_subscriber_connection *conn)
{
- struct gsm_lchan *new_lchan = conn->assignment.new_lchan;
+ /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan.
+ * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */
+ struct gsm_lchan *new_lchan = (conn->assignment.new_lchan ? : conn->lchan);
if (!new_lchan) {
osmo_fsm_inst_update_id(conn->assignment.fi, conn->fi->id);
return;
}
- osmo_fsm_inst_update_id_f(conn->assignment.fi, "%s_%u-%u-%u-%s%s%s-%u",
- conn->fi->id,
- new_lchan->ts->trx->bts->nr, new_lchan->ts->trx->nr, new_lchan->ts->nr,
- gsm_pchan_id(new_lchan->ts->pchan_on_init),
- (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is)? "" : "as",
- (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is)? ""
- : gsm_pchan_id(new_lchan->ts->pchan_is),
- new_lchan->nr);
+ osmo_fsm_inst_update_id_f_sanitize(conn->assignment.fi, '_', "%s_%u-%u-%u-%s%s%s-%s%u",
+ conn->fi->id,
+ new_lchan->ts->trx->bts->nr, new_lchan->ts->trx->nr, new_lchan->ts->nr,
+ gsm_pchan_name(new_lchan->ts->pchan_on_init),
+ (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is) ? "" : "as",
+ (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is) ?
+ "" : gsm_pchan_name(new_lchan->ts->pchan_is),
+ new_lchan->vamos.is_secondary ? "shadow" : "",
+ new_lchan->nr - (new_lchan->vamos.is_secondary ?
+ new_lchan->ts->max_primary_lchans : 0));
}
-#if 0
-Code using this is currently disabled, search for lchan_type_compat_with_mode below.
-
-static bool lchan_type_compat_with_mode(enum gsm_chan_t type,
- enum gsm48_chan_mode chan_mode, int full_rate)
+static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct channel_mode_and_rate *ch_mode_rate)
{
- switch (chan_mode) {
+ enum gsm48_chan_mode chan_mode = ch_mode_rate->chan_mode;
+ enum channel_rate chan_rate = ch_mode_rate->chan_rate;
+
+ switch (gsm48_chan_mode_to_non_vamos(chan_mode)) {
case GSM48_CMODE_SIGN:
switch (type) {
- case GSM_LCHAN_TCH_F:
- case GSM_LCHAN_TCH_H:
- case GSM_LCHAN_SDCCH:
- return true;
- default:
- return false;
+ case GSM_LCHAN_TCH_F: return chan_rate == CH_RATE_FULL;
+ case GSM_LCHAN_TCH_H: return chan_rate == CH_RATE_HALF;
+ case GSM_LCHAN_SDCCH: return chan_rate == CH_RATE_SDCCH;
+ default: return false;
}
case GSM48_CMODE_SPEECH_V1:
@@ -273,12 +371,12 @@ static bool lchan_type_compat_with_mode(enum gsm_chan_t type,
case GSM48_CMODE_DATA_3k6:
case GSM48_CMODE_DATA_6k0:
/* these services can all run on TCH/H, but we may have
- * an explicit override by the 'full_rate' argument */
+ * an explicit override by the 'chan_rate' argument */
switch (type) {
case GSM_LCHAN_TCH_F:
- return full_rate;
+ return chan_rate == CH_RATE_FULL;
case GSM_LCHAN_TCH_H:
- return !full_rate;
+ return chan_rate == CH_RATE_HALF;
default:
return false;
}
@@ -293,148 +391,320 @@ static bool lchan_type_compat_with_mode(enum gsm_chan_t type,
return false;
}
}
-#endif
-void assignment_fsm_init()
+static __attribute__((constructor)) void assignment_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&assignment_fsm) == 0);
}
+static int chan_mode_to_ch_indctr(enum gsm48_chan_mode chan_mode)
+{
+ switch (gsm48_chan_mode_to_non_vamos(chan_mode)) {
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ return GSM0808_CHAN_DATA;
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_SPEECH_AMR:
+ return GSM0808_CHAN_SPEECH;
+ case GSM48_CMODE_SIGN:
+ return GSM0808_CHAN_SIGN;
+ default:
+ return -EINVAL;
+ }
+}
+
+/* Check if the incoming assignment request has a channel mode that is
+ * inconsistent with ch_indctr, e.g. GSM48_CMODE_DATA_14k5 and
+ * GSM0808_CHAN_SPEECH */
+static int check_chan_mode_rate_against_ch_indctr(struct gsm_subscriber_connection *conn)
+{
+ struct assignment_request *req = &conn->assignment.req;
+ struct osmo_fsm_inst *fi = conn->fi;
+ int i;
+ int rc;
+
+ for (i = 0; i < req->n_ch_mode_rate; i++) {
+ rc = chan_mode_to_ch_indctr(req->ch_mode_rate_list[i].chan_mode);
+ if (rc < 0) {
+ assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP,
+ "Channel mode not supported (prev level %d): %s", i,
+ gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode));
+ return -EINVAL;
+ }
+
+ if (rc != req->ch_indctr) {
+ assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP,
+ "Channel mode %s has ch_indctr %d, channel type has ch_indctr %d",
+ gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode),
+ rc, req->ch_indctr);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+/* Decide if we should re-use an existing lchan. For this we check if the
+ * current lchan is compatible with one of the requested modes. */
+static bool reuse_existing_lchan(struct gsm_subscriber_connection *conn)
+{
+ struct assignment_request *req = &conn->assignment.req;
+ int i;
+
+ if (!conn->lchan)
+ return false;
+
+ /* Check if the currently existing lchan is compatible with the
+ * preferred rate/codec. */
+ for (i = 0; i < req->n_ch_mode_rate; i++) {
+ if (!lchan_type_compat_with_mode(conn->lchan->type, &req->ch_mode_rate_list[i]))
+ continue;
+ conn->assignment.selected_ch_mode_rate = req->ch_mode_rate_list[i];
+ return true;
+ }
+
+ return false;
+}
+
+static int _reassignment_request(enum assign_for assign_for, struct gsm_lchan *lchan, struct gsm_lchan *to_lchan,
+ enum gsm_chan_t new_lchan_type, int tsc_set, int tsc)
+{
+ struct gsm_subscriber_connection *conn = lchan->conn;
+ struct assignment_request req = {
+ .assign_for = assign_for,
+ .aoip = gscon_is_aoip(conn),
+ .msc_assigned_cic = conn->user_plane.msc_assigned_cic,
+ .msc_rtp_port = conn->user_plane.msc_assigned_rtp_port,
+ .n_ch_mode_rate = 1,
+ .ch_mode_rate_list = { lchan->current_ch_mode_rate },
+ .target_lchan = to_lchan,
+ .tsc_set = {
+ .present = (tsc_set >= 0),
+ .val = tsc_set,
+ },
+ .tsc = {
+ .present = (tsc >= 0),
+ .val = tsc,
+ },
+
+ .ch_indctr = chan_mode_to_ch_indctr(lchan->current_ch_mode_rate.chan_mode),
+ };
+
+ if (to_lchan)
+ new_lchan_type = to_lchan->type;
+ req.ch_mode_rate_list[0].chan_rate = chan_t_to_chan_rate(new_lchan_type);
+ /* lchan activation will automatically convert chan_mode to a VAMOS equivalent if required.
+ * So rather always pass the plain non-VAMOS mode. */
+ req.ch_mode_rate_list[0].chan_mode = gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode);
+
+ OSMO_STRLCPY_ARRAY(req.msc_rtp_addr, conn->user_plane.msc_assigned_rtp_addr);
+
+ if (conn->user_plane.mgw_endpoint_ci_msc) {
+ req.use_osmux = osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(conn->user_plane.mgw_endpoint_ci_msc,
+ &req.osmux_cid);
+ }
+
+ return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_ASSIGNMENT_START, &req);
+}
+
+int reassignment_request_to_lchan(enum assign_for assign_for, struct gsm_lchan *lchan, struct gsm_lchan *to_lchan,
+ int tsc_set, int tsc)
+{
+ return _reassignment_request(assign_for, lchan, to_lchan, 0, tsc_set, tsc);
+}
+
+int reassignment_request_to_chan_type(enum assign_for assign_for, struct gsm_lchan *lchan,
+ enum gsm_chan_t new_lchan_type)
+{
+ return _reassignment_request(assign_for, lchan, NULL, new_lchan_type, -1, -1);
+}
+
void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts *bts,
struct assignment_request *req)
{
+ static const char *rate_names[] = {
+ [CH_RATE_SDCCH] = "SDCCH",
+ [CH_RATE_HALF] = "HR",
+ [CH_RATE_FULL] = "FR",
+ };
struct osmo_fsm_inst *fi;
- struct lchan_activate_info info;
+ int i;
OSMO_ASSERT(conn);
OSMO_ASSERT(conn->fi);
OSMO_ASSERT(!conn->assignment.fi);
OSMO_ASSERT(!conn->assignment.new_lchan);
- assignment_count(BSC_CTR_ASSIGNMENT_ATTEMPTED);
-
fi = osmo_fsm_inst_alloc_child(&assignment_fsm, conn->fi, GSCON_EV_ASSIGNMENT_END);
OSMO_ASSERT(fi);
conn->assignment.fi = fi;
fi->priv = conn;
+ /* Create a copy of the request data and use that copy from now on. */
conn->assignment.req = *req;
+ req = &conn->assignment.req;
- switch (req->chan_mode) {
-
- case GSM48_CMODE_SPEECH_V1:
- case GSM48_CMODE_SPEECH_EFR:
- case GSM48_CMODE_SPEECH_AMR:
- conn->assignment.requires_voice_stream = true;
- /* Select an lchan below. */
- break;
-
- case GSM48_CMODE_SIGN:
- conn->assignment.requires_voice_stream = false;
- /* Select an lchan below. */
- break;
+ assignment_count(CTR_ASSIGNMENT_ATTEMPTED);
- default:
- assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP,
- "Channel mode not supported: %s",
- gsm48_chan_mode_name(req->chan_mode));
+ if (check_chan_mode_rate_against_ch_indctr(conn) < 0)
return;
- }
-
-#if 0
- -------
- This bit of code would re-use an existing lchan if it already satisfies the
- mode and rate requested by the MSC.
-
- That is a nice idea per se, but the only practical benefit is when we were out
- of SDCCH channels and assigned a TCH channel for signalling, and now that TCH
- should take on the voice stream. This scenario has not been tested.
-
- A much more common scenario, though, is that a call is coming in while another
- call is ongoing. Then, the user may choose to hang up the current call, and
- continue on the new call instead. In that scenario, the MSC will send us
- another Assignment Command with a different remote RTP address to route RTP to.
- The proper way now would be to bump the lchan_fsm so that it re-uses everything
- that is in place, only MDCXes the result to a different remote RTP port. This
- is currently not implemented and would require more states in the FSM. With
- this code enabled, we try to re-use the existing lchan but do not re-route the
- RTP: the old call is hung up and the new call gets no audio.
+ conn->assignment.ch_indctr = req->ch_indctr;
- However, if we simply drop the old lchan and create a new one, we can trivially
- set up a new lchan the same way we always do, and switching over to the new
- caller works, without a single line of code added here.
-
- Hence, disable this lchan re-use until we can re-use an lchan properly.
-
- Test scenario:
-
- - from A, call B.
- - from C, call B; B rings during ongoing call.
- - in B, pick up the call, choose to drop the old call.
- -------
-
- if (conn->lchan
- && lchan_type_compat_with_mode(conn->lchan->type, req->chan_mode, req->full_rate)) {
+ if (!req->target_lchan && reuse_existing_lchan(conn)) {
+ /* The already existing lchan is suitable for this mode */
+ conn->assignment.new_lchan = NULL;
- if (conn->lchan->tch_mode == req->chan_mode) {
- /* current lchan suffices and already is in the right mode. We're done. */
+ /* If the requested mode and the current TCH mode matches up, just send the
+ * assignment complete directly and be done with the assignment procedure. */
+ if (conn->lchan->current_ch_mode_rate.chan_mode == conn->assignment.selected_ch_mode_rate.chan_mode) {
LOG_ASSIGNMENT(conn, LOGL_DEBUG,
- "Current lchan is compatible with requested chan_mode,"
+ "Current lchan mode is compatible with requested chan_mode,"
" sending BSSMAP Assignment Complete directly."
" requested chan_mode=%s; current lchan is %s\n",
- gsm48_chan_mode_name(req->chan_mode),
+ gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode),
gsm_lchan_name(conn->lchan));
- send_assignment_complete(conn);
+
+ if (req->assign_for == ASSIGN_FOR_BSSMAP_REQ)
+ send_assignment_complete(conn);
+ /* If something went wrong during send_assignment_complete(),
+ * the fi will be gone from error handling in there. */
+ if (conn->assignment.fi) {
+ assignment_count_result(CTR_ASSIGNMENT_COMPLETED);
+ osmo_fsm_inst_term(conn->assignment.fi, OSMO_FSM_TERM_REGULAR, 0);
+ }
return;
}
- /* FIXME: send Channel Mode Modify to put the current lchan in the right mode, and kick
- * off its RTP stream setup code path. See gsm48_lchan_modify() and
- * gsm48_rx_rr_modif_ack(), and see lchan_fsm.h LCHAN_EV_CHAN_MODE_MODIF_* */
- LOG_ASSIGNMENT(conn, LOGL_ERROR,
- "NOT IMPLEMENTED:"
- " Current lchan would be compatible, we should send Channel Mode Modify\n");
+ /* The requested mode does not match the current TCH mode but the lchan is
+ * compatible. We will initiate a mode modify procedure. */
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG,
+ "Current lchan mode is not compatible with requested chan_mode,"
+ " so we will modify it. requested chan_mode=%s; current lchan is %s\n",
+ gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode),
+ gsm_lchan_name(conn->lchan));
+
+ assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED);
+ return;
}
-#endif
- conn->assignment.new_lchan = lchan_select_by_chan_mode(bts, req->chan_mode, req->full_rate);
+ if (req->vgcs) {
+ /* When assigning to a VGCS/VBS, the target lchan is already defined. */
+ conn->assignment.new_lchan = req->target_lchan;
+ } else if (req->target_lchan) {
+ bool matching_mode;
+
+ /* The caller already picked a target lchan to assign to. No need to try re-using the current lchan or
+ * picking a new one. */
+ if (!lchan_state_is(req->target_lchan, LCHAN_ST_UNUSED)) {
+ assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ "Assignment to lchan %s requested, but lchan is already in use (state=%s)\n",
+ gsm_lchan_name(req->target_lchan),
+ osmo_fsm_inst_state_name(req->target_lchan->fi));
+ return;
+ }
+ conn->assignment.new_lchan = req->target_lchan;
+ matching_mode = false;
+ for (i = 0; i < req->n_ch_mode_rate; i++) {
+ if (!lchan_type_compat_with_mode(conn->assignment.new_lchan->type, &req->ch_mode_rate_list[i]))
+ continue;
+ conn->assignment.selected_ch_mode_rate = req->ch_mode_rate_list[i];
+ matching_mode = true;
+ }
+ if (!matching_mode) {
+ OSMO_ASSERT(conn->lchan);
+ assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ "Assignment of lchan %s to %s type %s requested, but lchan is not compatible",
+ gsm_lchan_name(conn->lchan),
+ gsm_lchan_name(req->target_lchan),
+ gsm_chan_t_name(conn->assignment.new_lchan->type));
+ return;
+ }
+ } else {
+ /* Try to allocate a new lchan in order of preference */
+ for (i = 0; i < req->n_ch_mode_rate; i++) {
+ conn->assignment.new_lchan = lchan_select_by_chan_mode(bts,
+ req->ch_mode_rate_list[i].chan_mode,
+ req->ch_mode_rate_list[i].chan_rate,
+ SELECT_FOR_ASSIGNMENT, conn->lchan);
+ if (!conn->assignment.new_lchan)
+ continue;
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "selected new lchan %s for mode[%d] = %s channel_rate=%d\n",
+ gsm_lchan_name(conn->assignment.new_lchan),
+ i, gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode),
+ req->ch_mode_rate_list[i].chan_rate);
+
+ conn->assignment.selected_ch_mode_rate = req->ch_mode_rate_list[i];
+ break;
+ }
+ }
+
+ /* Check whether the lchan allocation was successful or not and tear
+ * down the assignment in case of failure. */
if (!conn->assignment.new_lchan) {
- assignment_count_result(BSC_CTR_ASSIGNMENT_NO_CHANNEL);
+ assignment_count_result(CTR_ASSIGNMENT_NO_CHANNEL);
assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
"BSSMAP Assignment Command:"
- " No lchan available for: chan_mode=%s, full_rate=%i\n",
- get_value_string(gsm48_chan_mode_names, req->chan_mode), req->full_rate);
+ " No lchan available for: pref=%s:%s / alt1=%s:%s / alt2=%s:%s",
+ gsm48_chan_mode_name(req->ch_mode_rate_list[0].chan_mode),
+ rate_names[req->ch_mode_rate_list[0].chan_rate],
+ req->n_ch_mode_rate > 1 ? gsm48_chan_mode_name(req->ch_mode_rate_list[1].chan_mode) : "",
+ req->n_ch_mode_rate > 1 ? rate_names[req->ch_mode_rate_list[1].chan_rate] : "",
+ req->n_ch_mode_rate > 2 ? gsm48_chan_mode_name(req->ch_mode_rate_list[2].chan_mode) : "",
+ req->n_ch_mode_rate > 2 ? rate_names[req->ch_mode_rate_list[2].chan_rate] : ""
+ );
return;
}
assignment_fsm_update_id(conn);
- LOG_ASSIGNMENT(conn, LOGL_INFO, "Starting Assignment: chan_mode=%s, full_rate=%d,"
- " aoip=%s MSC-rtp=%s:%u\n",
- gsm48_chan_mode_name(req->chan_mode), req->full_rate,
- req->aoip ? "yes" : "no", req->msc_rtp_addr, req->msc_rtp_port);
-
- assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE);
- info = (struct lchan_activate_info){
- .activ_for = FOR_ASSIGNMENT,
+ LOG_ASSIGNMENT(conn, LOGL_INFO, "Starting Assignment: chan_mode=%s, chan_type=%s,"
+ " aoip=%s MSC-rtp=%s:%u (osmux=%s)\n",
+ gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode),
+ rate_names[conn->assignment.selected_ch_mode_rate.chan_rate],
+ req->aoip ? "yes" : "no", req->msc_rtp_addr, req->msc_rtp_port,
+ req->use_osmux ? "yes" : "no");
+
+ /* Wait for lchan to become active before send assignment. In case of VGCS/VBS directly send assignment,
+ * because the channel is already active. */
+ assignment_fsm_state_chg(req->vgcs ? ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE : ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE);
+}
+
+static void assignment_fsm_wait_lchan_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ struct assignment_request *req = &conn->assignment.req;
+ struct lchan_activate_info activ_info = {
+ .activ_for = ACTIVATE_FOR_ASSIGNMENT,
.for_conn = conn,
- .chan_mode = req->chan_mode,
- .s15_s0 = req->s15_s0,
- .requires_voice_stream = conn->assignment.requires_voice_stream,
+ .ch_mode_rate = conn->assignment.selected_ch_mode_rate,
+ .encr = conn->lchan->encr,
+ .ch_indctr = conn->assignment.ch_indctr,
.msc_assigned_cic = req->msc_assigned_cic,
.re_use_mgw_endpoint_from_lchan = conn->lchan,
+ .ta = conn->lchan->last_ta,
+ .ta_known = true,
+ .tsc_set = req->tsc_set,
+ .tsc = req->tsc,
};
- lchan_activate(conn->assignment.new_lchan, &info);
+ if (conn->assignment.new_lchan->vamos.is_secondary)
+ activ_info.type_for = LCHAN_TYPE_FOR_VAMOS;
+ lchan_activate(conn->assignment.new_lchan, &activ_info);
}
-static void assignment_fsm_wait_lchan(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void assignment_fsm_wait_lchan_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
switch (event) {
case ASSIGNMENT_EV_LCHAN_ACTIVE:
- if (data != conn->assignment.new_lchan)
+ if (data != conn->assignment.new_lchan) {
+ LOG_ASSIGNMENT(conn, LOGL_ERROR, "Some unrelated lchan was activated, ignoring: %s\n",
+ gsm_lchan_name(data));
return;
+ }
/* The TS may have changed its pchan_is */
assignment_fsm_update_id(conn);
@@ -452,6 +722,15 @@ static void assignment_fsm_wait_rr_ass_complete_onenter(struct osmo_fsm_inst *fi
int rc;
struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ /* There may be situations where the SDCCH gets released while the TCH is still being activated. We will then
+ * receive ChanActivAck message from the BTS when the TCH is ready. Since the SDCCH is already released by
+ * then conn->lchan will be NULL in this case. */
+ if (!conn->lchan) {
+ assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ "Unable to send RR Assignment Command: conn without lchan");
+ return;
+ }
+
rc = gsm48_send_rr_ass_cmd(conn->lchan, conn->assignment.new_lchan,
conn->lchan->ms_power);
@@ -482,7 +761,7 @@ static void assignment_fsm_wait_rr_ass_complete(struct osmo_fsm_inst *fi, uint32
return;
case ASSIGNMENT_EV_RR_ASSIGNMENT_FAIL:
- assignment_count_result(BSC_CTR_ASSIGNMENT_FAILED);
+ assignment_count_result(CTR_ASSIGNMENT_FAILED);
assignment_fail(get_cause(data), "Rx RR Assignment Failure");
return;
@@ -498,7 +777,7 @@ static void assignment_fsm_wait_lchan_established_onenter(struct osmo_fsm_inst *
struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
/* Do we still need to wait for the RTP stream at all? */
if (lchan_state_is(conn->assignment.new_lchan, LCHAN_ST_ESTABLISHED)) {
- LOG_ASSIGNMENT(conn, LOGL_DEBUG, "lchan fully established, no need to wait");
+ LOG_ASSIGNMENT(conn, LOGL_DEBUG, "lchan fully established, no need to wait\n");
assignment_fsm_post_lchan_established(fi);
}
}
@@ -519,7 +798,7 @@ static void assignment_fsm_wait_lchan_established(struct osmo_fsm_inst *fi, uint
static void assignment_fsm_post_lchan_established(struct osmo_fsm_inst *fi)
{
struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
- if (conn->assignment.requires_voice_stream)
+ if (bsc_chan_ind_requires_rtp_stream(conn->assignment.ch_indctr))
assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC);
else
assignment_success(conn);
@@ -529,15 +808,17 @@ static void assignment_fsm_wait_mgw_endpoint_to_msc_onenter(struct osmo_fsm_inst
{
struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
- OSMO_ASSERT(conn->assignment.requires_voice_stream);
+ OSMO_ASSERT(bsc_chan_ind_requires_rtp_stream(conn->assignment.ch_indctr));
LOG_ASSIGNMENT(conn, LOGL_DEBUG,
"Connecting MGW endpoint to the MSC's RTP port: %s:%u\n",
conn->assignment.req.msc_rtp_addr,
conn->assignment.req.msc_rtp_port);
+ /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan.
+ * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */
if (!gscon_connect_mgw_to_msc(conn,
- conn->assignment.new_lchan,
+ conn->assignment.new_lchan ? : conn->lchan,
conn->assignment.req.msc_rtp_addr,
conn->assignment.req.msc_rtp_port,
fi,
@@ -560,7 +841,7 @@ static void assignment_fsm_wait_mgw_endpoint_to_msc(struct osmo_fsm_inst *fi, ui
/* For AoIP, we created the MGW endpoint. Ensure it is really there, and log it. */
if (gscon_is_aoip(conn)) {
const struct mgcp_conn_peer *mgw_info;
- mgw_info = mgwep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc);
+ mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc);
if (!mgw_info) {
assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE,
"Unable to retrieve RTP port info allocated by MGW for"
@@ -583,18 +864,57 @@ static void assignment_fsm_wait_mgw_endpoint_to_msc(struct osmo_fsm_inst *fi, ui
}
}
+static void assignment_fsm_wait_lchan_modified_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+ struct gsm_lchan *lchan = conn->lchan;
+ struct assignment_request *req = &conn->assignment.req;
+ struct lchan_modify_info modif_info = {
+ .modify_for = MODIFY_FOR_ASSIGNMENT,
+ .ch_mode_rate = conn->assignment.selected_ch_mode_rate,
+ .ch_indctr = conn->assignment.ch_indctr,
+ .msc_assigned_cic = req->msc_assigned_cic,
+ /* keep previous training sequence code. TSC is always present, TSC Set may or may not be an explicit
+ * value. */
+ .tsc_set = {
+ .present = (lchan->tsc_set >= 0),
+ .val = lchan->tsc_set,
+ },
+ .tsc = {
+ .present = true,
+ .val = lchan->tsc,
+ },
+ };
+ lchan_mode_modify(lchan, &modif_info);
+}
+
+static void assignment_fsm_wait_lchan_modified(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case ASSIGNMENT_EV_LCHAN_MODIFIED:
+ assignment_fsm_post_lchan_established(fi);
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
#define S(x) (1 << (x))
static const struct osmo_fsm_state assignment_fsm_states[] = {
[ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = {
.name = "WAIT_LCHAN_ACTIVE",
- .action = assignment_fsm_wait_lchan,
+ .onenter = assignment_fsm_wait_lchan_active_onenter,
+ .action = assignment_fsm_wait_lchan_active,
.in_event_mask = 0
| S(ASSIGNMENT_EV_LCHAN_ACTIVE)
,
.out_state_mask = 0
| S(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE)
| S(ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE)
+ | S(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED)
,
},
[ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = {
@@ -630,11 +950,23 @@ static const struct osmo_fsm_state assignment_fsm_states[] = {
| S(ASSIGNMENT_EV_MSC_MGW_FAIL)
,
},
+ [ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED] = {
+ .name = "WAIT_LCHAN_MODIFIED",
+ .onenter = assignment_fsm_wait_lchan_modified_onenter,
+ .action = assignment_fsm_wait_lchan_modified,
+ .in_event_mask = 0
+ | S(ASSIGNMENT_EV_LCHAN_MODIFIED)
+ ,
+ .out_state_mask = 0
+ | S(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC)
+ ,
+ },
};
static const struct value_string assignment_fsm_event_names[] = {
OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ACTIVE),
OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ESTABLISHED),
+ OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_MODIFIED),
OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ERROR),
OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_OK),
OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_FAIL),
@@ -644,22 +976,28 @@ static const struct value_string assignment_fsm_event_names[] = {
{}
};
-void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
+
+ /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan.
+ * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */
+ struct gsm_lchan *new_lchan = conn->assignment.new_lchan ? : conn->lchan;
+
switch (event) {
case ASSIGNMENT_EV_CONN_RELEASING:
- assignment_count_result(BSC_CTR_ASSIGNMENT_STOPPED);
+ assignment_count_result(CTR_ASSIGNMENT_STOPPED);
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
return;
case ASSIGNMENT_EV_LCHAN_ERROR:
- if (data != conn->assignment.new_lchan)
+ if (data != new_lchan)
return;
- assignment_fail(conn->assignment.new_lchan->activate.gsm0808_error_cause,
- "Failed to activate lchan %s",
- gsm_lchan_name(conn->assignment.new_lchan));
+ assignment_fail(new_lchan->activate.gsm0808_error_cause,
+ "Failed to %s lchan %s",
+ conn->assignment.new_lchan ? "activate" : "modify",
+ gsm_lchan_name(new_lchan));
return;
default:
@@ -667,15 +1005,15 @@ void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, vo
}
}
-int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi)
+static int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
- assignment_count_result(BSC_CTR_ASSIGNMENT_TIMEOUT);
+ assignment_count_result(CTR_ASSIGNMENT_TIMEOUT);
assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, "Timeout");
return 0;
}
-void assignment_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+static void assignment_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct gsm_subscriber_connection *conn = assignment_fi_conn(fi);
assignment_reset(conn);
diff --git a/src/osmo-bsc/bsc_ctrl.c b/src/osmo-bsc/bsc_ctrl.c
new file mode 100644
index 000000000..aff1d83e6
--- /dev/null
+++ b/src/osmo-bsc/bsc_ctrl.c
@@ -0,0 +1,906 @@
+/*
+ * (C) 2011 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2011 by On-Waves
+ * (C) 2011-2015 by Holger Hans Peter Freyther
+ * (C) 2013-2015 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <time.h>
+
+#include <osmocom/gsm/ipa.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/osmo_bsc_rf.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/a_reset.h>
+#include <osmocom/bsc/ctrl.h>
+#include <osmocom/bsc/handover_ctrl.h>
+#include <osmocom/bsc/neighbor_ident.h>
+
+static int verify_net_apply_config_file(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ FILE *cfile;
+
+ if (!cmd->value || cmd->value[0] == '\0')
+ return -1;
+
+ cfile = fopen(cmd->value, "r");
+ if (!cfile)
+ return -1;
+
+ fclose(cfile);
+
+ return 0;
+}
+static int set_net_apply_config_file(struct ctrl_cmd *cmd, void *_data)
+{
+ int rc;
+ FILE *cfile;
+ unsigned cmd_ret = CTRL_CMD_ERROR;
+
+ LOGP(DCTRL, LOGL_NOTICE, "Applying VTY snippet from %s...\n", cmd->value);
+ cfile = fopen(cmd->value, "r");
+ if (!cfile) {
+ LOGP(DCTRL, LOGL_NOTICE, "Applying VTY snippet from %s: fopen() failed: %d\n",
+ cmd->value, errno);
+ cmd->reply = "NoFile";
+ return cmd_ret;
+ }
+
+ rc = vty_read_config_filep(cfile, NULL);
+ LOGP(DCTRL, LOGL_NOTICE, "Applying VTY snippet from %s returned %d\n", cmd->value, rc);
+ if (rc) {
+ cmd->reply = talloc_asprintf(cmd, "ParseError=%d", rc);
+ if (!cmd->reply)
+ cmd->reply = "OOM";
+ goto close_ret;
+ }
+
+ rc = neighbors_check_cfg();
+ if (rc) {
+ cmd->reply = talloc_asprintf(cmd, "Errors in neighbor configuration");
+ if (!cmd->reply)
+ cmd->reply = "OOM";
+ goto close_ret;
+ }
+
+ cmd->reply = "OK";
+ cmd_ret = CTRL_CMD_REPLY;
+close_ret:
+ fclose(cfile);
+ return cmd_ret;
+}
+CTRL_CMD_DEFINE_WO(net_apply_config_file, "apply-config-file");
+
+static int verify_net_write_config_file(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ return 0;
+}
+static int set_net_write_config_file(struct ctrl_cmd *cmd, void *_data)
+{
+ const char *cfile_name;
+ unsigned cmd_ret = CTRL_CMD_ERROR;
+
+ if (strcmp(cmd->value, "overwrite"))
+ host_config_set(cmd->value);
+
+ cfile_name = host_config_file();
+
+ LOGP(DCTRL, LOGL_NOTICE, "Writing VTY config to file %s...\n", cfile_name);
+ if (osmo_vty_write_config_file(cfile_name) < 0)
+ goto ret;
+
+ cmd->reply = "OK";
+ cmd_ret = CTRL_CMD_REPLY;
+ret:
+ return cmd_ret;
+}
+CTRL_CMD_DEFINE_WO(net_write_config_file, "write-config-file");
+
+CTRL_CMD_DEFINE(net_mcc, "mcc");
+static int get_net_mcc(struct ctrl_cmd *cmd, void *_data)
+{
+ struct gsm_network *net = cmd->node;
+ cmd->reply = talloc_asprintf(cmd, "%s", osmo_mcc_name(net->plmn.mcc));
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+ return CTRL_CMD_REPLY;
+}
+static int set_net_mcc(struct ctrl_cmd *cmd, void *_data)
+{
+ struct gsm_network *net = cmd->node;
+ uint16_t mcc;
+ if (osmo_mcc_from_str(cmd->value, &mcc))
+ return -1;
+ net->plmn.mcc = mcc;
+ return get_net_mcc(cmd, _data);
+}
+static int verify_net_mcc(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (osmo_mcc_from_str(value, NULL))
+ return -1;
+ return 0;
+}
+
+CTRL_CMD_DEFINE(net_mnc, "mnc");
+static int get_net_mnc(struct ctrl_cmd *cmd, void *_data)
+{
+ struct gsm_network *net = cmd->node;
+ cmd->reply = talloc_asprintf(cmd, "%s", osmo_mnc_name(net->plmn.mnc, net->plmn.mnc_3_digits));
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+ return CTRL_CMD_REPLY;
+}
+static int set_net_mnc(struct ctrl_cmd *cmd, void *_data)
+{
+ struct gsm_network *net = cmd->node;
+ struct osmo_plmn_id plmn = net->plmn;
+ if (osmo_mnc_from_str(cmd->value, &plmn.mnc, &plmn.mnc_3_digits)) {
+ cmd->reply = "Error while decoding MNC";
+ return CTRL_CMD_ERROR;
+ }
+ net->plmn = plmn;
+ return get_net_mnc(cmd, _data);
+}
+static int verify_net_mnc(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (osmo_mnc_from_str(value, NULL, NULL))
+ return -1;
+ return 0;
+}
+
+static int set_net_apply_config(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ if (!is_ipa_abisip_bts(bts))
+ continue;
+
+ /*
+ * The ip.access nanoBTS seems to be unreliable on BSSGP
+ * so let's us just reboot it. For the sysmoBTS we can just
+ * restart the process as all state is gone.
+ */
+ if (!is_osmobts(bts) && strcmp(cmd->value, "restart") == 0) {
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry_reverse(trx, &bts->trx_list, list)
+ abis_nm_ipaccess_restart(trx);
+ } else
+ ipaccess_drop_oml(bts, "ctrl net.apply-configuration");
+ }
+
+ cmd->reply = "Tried to drop the BTS";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_apply_config, "apply-configuration");
+
+static int verify_net_mcc_mnc_apply(struct ctrl_cmd *cmd, const char *value, void *d)
+{
+ char *tmp, *saveptr, *mcc, *mnc;
+ int rc = 0;
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ mcc = strtok_r(tmp, ",", &saveptr);
+ mnc = strtok_r(NULL, ",", &saveptr);
+
+ if (osmo_mcc_from_str(mcc, NULL) || osmo_mnc_from_str(mnc, NULL, NULL))
+ rc = -1;
+
+ talloc_free(tmp);
+ return rc;
+}
+
+static int set_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+ char *tmp, *saveptr, *mcc_str, *mnc_str;
+ struct osmo_plmn_id plmn;
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
+
+ mcc_str = strtok_r(tmp, ",", &saveptr);
+ mnc_str = strtok_r(NULL, ",", &saveptr);
+
+ if (osmo_mcc_from_str(mcc_str, &plmn.mcc)) {
+ cmd->reply = "Error while decoding MCC";
+ talloc_free(tmp);
+ return CTRL_CMD_ERROR;
+ }
+
+ if (osmo_mnc_from_str(mnc_str, &plmn.mnc, &plmn.mnc_3_digits)) {
+ cmd->reply = "Error while decoding MNC";
+ talloc_free(tmp);
+ return CTRL_CMD_ERROR;
+ }
+
+ talloc_free(tmp);
+
+ if (!osmo_plmn_cmp(&net->plmn, &plmn)) {
+ cmd->reply = "Nothing changed";
+ return CTRL_CMD_REPLY;
+ }
+
+ net->plmn = plmn;
+
+ return set_net_apply_config(cmd, data);
+
+oom:
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+}
+CTRL_CMD_DEFINE_WO(net_mcc_mnc_apply, "mcc-mnc-apply");
+
+static int get_net_rf_lock(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+ struct gsm_bts *bts;
+ const char *policy_name;
+
+ policy_name = osmo_bsc_rf_get_policy_name(net->rf_ctrl->policy);
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ struct gsm_bts_trx *trx;
+
+ /* Exclude the BTS from the global lock */
+ if (bts->excl_from_rf_lock)
+ continue;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->mo.nm_state.availability == NM_AVSTATE_OK &&
+ trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) {
+ cmd->reply = talloc_asprintf(cmd,
+ "state=on,policy=%s,bts=%u,trx=%u",
+ policy_name, bts->nr, trx->nr);
+ return CTRL_CMD_REPLY;
+ }
+ }
+ }
+
+ cmd->reply = talloc_asprintf(cmd, "state=off,policy=%s",
+ policy_name);
+ return CTRL_CMD_REPLY;
+}
+
+#define TIME_FORMAT_RFC2822 "%a, %d %b %Y %T %z"
+
+static int set_net_rf_lock(struct ctrl_cmd *cmd, void *data)
+{
+ int locked = atoi(cmd->value);
+ struct gsm_network *net = cmd->node;
+ time_t now = time(NULL);
+ char now_buf[64];
+ struct osmo_bsc_rf *rf;
+
+ if (!net) {
+ cmd->reply = "net not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ rf = net->rf_ctrl;
+
+ if (!rf) {
+ cmd->reply = "RF Ctrl is not enabled in the BSC Configuration";
+ return CTRL_CMD_ERROR;
+ }
+
+ talloc_free(rf->last_rf_lock_ctrl_command);
+ strftime(now_buf, sizeof(now_buf), TIME_FORMAT_RFC2822, gmtime(&now));
+ rf->last_rf_lock_ctrl_command =
+ talloc_asprintf(rf, "rf_locked %u (%s)", locked, now_buf);
+
+ osmo_bsc_rf_schedule_lock(rf, locked == 1 ? '0' : '1');
+
+ cmd->reply = talloc_asprintf(cmd, "%u", locked);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int verify_net_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ int locked = atoi(cmd->value);
+
+ if ((locked != 0) && (locked != 1))
+ return 1;
+
+ return 0;
+}
+CTRL_CMD_DEFINE(net_rf_lock, "rf_locked");
+
+static int get_net_bts_num(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%u", net->num_bts);
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(net_bts_num, "number-of-bts");
+
+/* Return a list of the states of each TRX for all BTS:
+ * <bts_nr>,<trx_nr>,<opstate>,<adminstate>,<rf_policy>,<rsl_status>;<bts_nr>,<trx_nr>,...;...;
+ * For details on the string, see bsc_rf_states_c();
+ */
+static int get_net_rf_states(struct ctrl_cmd *cmd, void *data)
+{
+ cmd->reply = bsc_rf_states_c(cmd);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(net_rf_states, "rf_states");
+
+CTRL_CMD_DEFINE(net_timezone, "timezone");
+static int get_net_timezone(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = (struct gsm_network *)cmd->node;
+
+ struct gsm_tz *tz = &net->tz;
+ if (tz->override)
+ cmd->reply = talloc_asprintf(cmd, "%d,%d,%d",
+ tz->hr, tz->mn, tz->dst);
+ else
+ cmd->reply = talloc_asprintf(cmd, "off");
+
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_net_timezone(struct ctrl_cmd *cmd, void *data)
+{
+ char *saveptr, *hourstr, *minstr, *dststr, *tmp = 0;
+ int override = 0;
+ struct gsm_network *net = (struct gsm_network *)cmd->node;
+ struct gsm_tz *tz = &net->tz;
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
+
+ hourstr = strtok_r(tmp, ",", &saveptr);
+ minstr = strtok_r(NULL, ",", &saveptr);
+ dststr = strtok_r(NULL, ",", &saveptr);
+
+ if (hourstr != NULL) {
+ override = strcasecmp(hourstr, "off") != 0;
+ if (override) {
+ tz->hr = atol(hourstr);
+ tz->mn = minstr ? atol(minstr) : 0;
+ tz->dst = dststr ? atol(dststr) : 0;
+ }
+ }
+
+ tz->override = override;
+
+
+ talloc_free(tmp);
+ tmp = NULL;
+
+ return get_net_timezone(cmd, data);
+
+oom:
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+}
+
+static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ char *saveptr, *hourstr, *minstr, *dststr, *tmp;
+ int override, tz_hours, tz_mins, tz_dst;
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ hourstr = strtok_r(tmp, ",", &saveptr);
+ minstr = strtok_r(NULL, ",", &saveptr);
+ dststr = strtok_r(NULL, ",", &saveptr);
+
+ if (hourstr == NULL)
+ goto err;
+
+ override = strcasecmp(hourstr, "off") != 0;
+
+ if (!override) {
+ talloc_free(tmp);
+ return 0;
+ }
+
+ if (minstr == NULL || dststr == NULL)
+ goto err;
+
+ tz_hours = atol(hourstr);
+ tz_mins = atol(minstr);
+ tz_dst = atol(dststr);
+
+ talloc_free(tmp);
+ tmp = NULL;
+
+ if ((tz_hours < -19) || (tz_hours > 19) ||
+ (tz_mins < 0) || (tz_mins >= 60) || (tz_mins % 15 != 0) ||
+ (tz_dst < 0) || (tz_dst > 2))
+ goto err;
+
+ return 0;
+
+err:
+ talloc_free(tmp);
+ cmd->reply = talloc_strdup(cmd, "The format is <hours>,<mins>,<dst> or 'off' where -19 <= hours <= 19, mins in {0, 15, 30, 45}, and 0 <= dst <= 2");
+ return 1;
+}
+
+CTRL_CMD_DEFINE_RO(bts_connection_status, "bts_connection_status");
+static int bts_connection_status = 0;
+
+static int get_bts_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+ if (bts_connection_status)
+ cmd->reply = "connected";
+ else
+ cmd->reply = "disconnected";
+ return CTRL_CMD_REPLY;
+}
+
+static int bts_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+ struct ctrl_cmd *cmd;
+ struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
+ struct gsm_bts *bts;
+ int bts_current_status;
+
+ if (signal != S_L_INP_TEI_DN && signal != S_L_INP_TEI_UP) {
+ return 0;
+ }
+
+ bts_current_status = 0;
+ /* Check if OML on at least one BTS is up */
+ llist_for_each_entry(bts, &gsmnet->bts_list, list) {
+ if (bts->oml_link) {
+ bts_current_status = 1;
+ break;
+ }
+ }
+ if (bts_connection_status == 0 && bts_current_status == 1) {
+ LOGP(DCTRL, LOGL_DEBUG, "BTS connection (re)established, sending TRAP.\n");
+ } else if (bts_connection_status == 1 && bts_current_status == 0) {
+ LOGP(DCTRL, LOGL_DEBUG, "No more BTS connected, sending TRAP.\n");
+ } else {
+ return 0;
+ }
+
+ cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
+ return 0;
+ }
+
+ bts_connection_status = bts_current_status;
+
+ cmd->id = "0";
+ cmd->variable = "bts_connection_status";
+
+ get_bts_connection_status(cmd, NULL);
+
+ ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+
+ talloc_free(cmd);
+
+ return 0;
+}
+
+CTRL_CMD_DEFINE_RO(msc_connection_status, "connection_status");
+static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+ struct bsc_msc_data *msc = (struct bsc_msc_data *)cmd->node;
+
+ if (msc == NULL) {
+ cmd->reply = "msc not found";
+ return CTRL_CMD_ERROR;
+ }
+ if (a_reset_conn_ready(msc))
+ cmd->reply = "connected";
+ else
+ cmd->reply = "disconnected";
+ return CTRL_CMD_REPLY;
+}
+
+/* Backwards compat. */
+CTRL_CMD_DEFINE_RO(msc0_connection_status, "msc_connection_status");
+
+static int get_msc0_connection_status(struct ctrl_cmd *cmd, void *data)
+{
+ struct bsc_msc_data *msc = osmo_msc_data_find(bsc_gsmnet, 0);
+ void *old_node = cmd->node;
+ int rc;
+
+ cmd->node = msc;
+ rc = get_msc_connection_status(cmd, data);
+ cmd->node = old_node;
+
+ return rc;
+}
+
+static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+ struct ctrl_cmd *cmd;
+ struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
+ struct bsc_msc_data *msc = (struct bsc_msc_data *)signal_data;
+
+ if (signal == S_MSC_LOST) {
+ LOGP(DCTRL, LOGL_DEBUG, "MSC connection lost, sending TRAP.\n");
+ } else if (signal == S_MSC_CONNECTED) {
+ LOGP(DCTRL, LOGL_DEBUG, "MSC connection (re)established, sending TRAP.\n");
+ } else {
+ return 0;
+ }
+
+ cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
+ return 0;
+ }
+
+ cmd->id = "0";
+ cmd->variable = talloc_asprintf(cmd, "msc.%d.connection_status", msc->nr);
+ cmd->node = msc;
+
+ get_msc_connection_status(cmd, NULL);
+
+ ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+
+ if (msc->nr == 0) {
+ /* Backwards compat. */
+ cmd->variable = "msc_connection_status";
+ ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+ }
+
+ talloc_free(cmd);
+
+ return 0;
+}
+
+static int msc_signal_handler(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct msc_signal_data *msc;
+ struct gsm_network *net;
+ struct gsm_bts *bts;
+
+ if (subsys != SS_MSC)
+ return 0;
+ if (signal != S_MSC_AUTHENTICATED)
+ return 0;
+
+ msc = signal_data;
+
+ net = msc->data->network;
+ llist_for_each_entry(bts, &net->bts_list, list)
+ ctrl_generate_bts_location_state_trap(bts, msc->data);
+
+ return 0;
+}
+
+/* Obtain SS7 application server currently handling given MSC (DPC) */
+static struct osmo_ss7_as *msc_get_ss7_as(struct bsc_msc_data *msc)
+{
+ struct osmo_ss7_route *rt;
+ struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(msc->a.sccp);
+ rt = osmo_ss7_route_lookup(ss7, msc->a.msc_addr.pc);
+ if (!rt)
+ return NULL;
+ return rt->dest.as;
+}
+
+static int _ss7_as_send(struct osmo_ss7_as *as, struct msgb *msg)
+{
+ struct osmo_ss7_asp *asp;
+ unsigned int i;
+
+ /* FIXME: unify with xua_as_transmit_msg() and perform proper ASP lookup */
+ for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) {
+ asp = as->cfg.asps[i];
+ if (!asp)
+ continue;
+ /* FIXME: deal with multiple ASPs per AS */
+ return osmo_ss7_asp_send(asp, msg);
+ }
+ msgb_free(msg);
+ return -1;
+}
+
+int bsc_sccplite_msc_send(struct bsc_msc_data *msc, struct msgb *msg)
+{
+ struct osmo_ss7_as *as;
+
+ as = msc_get_ss7_as(msc);
+ if (!as) {
+ msgb_free(msg);
+ return -1;
+ }
+
+ /* don't attempt to send CTRL on a non-SCCPlite AS */
+ if (as->cfg.proto != OSMO_SS7_ASP_PROT_IPA)
+ return 0;
+
+ return _ss7_as_send(as, msg);
+}
+
+/* Encode a CTRL command and send it to the given ASP
+ * \param[in] asp ASP through which we shall send the encoded message
+ * \param[in] cmd decoded CTRL command to be encoded and sent. Ownership is *NOT*
+ * transferred, to permit caller to send the same CMD to several ASPs.
+ * Caller must hence free 'cmd' itself.
+ * \returns 0 on success; negative on error */
+static int sccplite_asp_ctrl_cmd_send(struct osmo_ss7_asp *asp, struct ctrl_cmd *cmd)
+{
+ /* this is basically like libosmoctrl:ctrl_cmd_send(), not for a dedicated
+ * CTRL connection but for the CTRL piggy-back on the IPA/SCCPlite link */
+ struct msgb *msg;
+
+ /* don't attempt to send CTRL on a non-SCCPlite ASP */
+ if (osmo_ss7_asp_get_proto(asp) != OSMO_SS7_ASP_PROT_IPA)
+ return 0;
+
+ msg = ctrl_cmd_make(cmd);
+ if (!msg)
+ return -1;
+
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+ ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+
+ return osmo_ss7_asp_send(asp, msg);
+}
+
+/* Ownership of 'cmd' is *NOT* transferred, to permit caller to send the same CMD to several ASPs.
+ * Caller must hence free 'cmd' itself. */
+static int sccplite_msc_ctrl_cmd_send(struct bsc_msc_data *msc, struct ctrl_cmd *cmd)
+{
+ struct msgb *msg;
+
+ msg = ctrl_cmd_make(cmd);
+ if (!msg)
+ return -1;
+
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+ ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+
+ return bsc_sccplite_msc_send(msc, msg);
+}
+
+/* receive + process a CTRL command from the piggy-back on the IPA/SCCPlite link.
+ * Transfers msg ownership. */
+int bsc_sccplite_rx_ctrl(struct osmo_ss7_asp *asp, struct msgb *msg)
+{
+ struct ctrl_cmd *cmd;
+ bool parse_failed;
+ int rc;
+
+ /* caller has already ensured ipaccess_head + ipaccess_head_ext */
+ OSMO_ASSERT(msg->l2h);
+
+ /* prase raw (ASCII) CTRL command into ctrl_cmd */
+ cmd = ctrl_cmd_parse3(asp, msg, &parse_failed);
+ OSMO_ASSERT(cmd);
+ msgb_free(msg);
+ if (cmd->type == CTRL_TYPE_ERROR && parse_failed)
+ goto send_reply;
+
+ /* handle the CTRL command */
+ ctrl_cmd_handle(bsc_gsmnet->ctrl, cmd, bsc_gsmnet);
+
+send_reply:
+ rc = sccplite_asp_ctrl_cmd_send(asp, cmd);
+ talloc_free(cmd);
+ return rc;
+}
+
+
+void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_data *msc_data)
+{
+ struct ctrl_cmd *trap;
+ struct ctrl_handle *ctrl;
+
+ ctrl = msc_data->network->ctrl;
+
+ trap = ctrl_cmd_trap(cmd);
+ if (!trap) {
+
+ LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n");
+ return;
+ }
+
+ ctrl_cmd_send_to_all(ctrl, trap);
+ sccplite_msc_ctrl_cmd_send(msc_data, trap);
+
+ talloc_free(trap);
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_notification, "notification");
+static int set_net_notification(struct ctrl_cmd *cmd, void *data)
+{
+ struct ctrl_cmd *trap;
+ struct gsm_network *net;
+
+ net = cmd->node;
+
+ trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!trap) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
+ goto handled;
+ }
+
+ trap->id = "0";
+ trap->variable = "notification";
+ trap->reply = talloc_strdup(trap, cmd->value);
+
+ /*
+ * This should only be sent to local systems. In the future
+ * we might even ask for systems to register to receive
+ * the notifications.
+ */
+ ctrl_cmd_send_to_all(net->ctrl, trap);
+ talloc_free(trap);
+
+handled:
+ return CTRL_CMD_HANDLED;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_inform_msc, "inform-msc-v1");
+static int set_net_inform_msc(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net;
+ struct bsc_msc_data *msc;
+
+ net = cmd->node;
+ llist_for_each_entry(msc, &net->mscs, entry) {
+ struct ctrl_cmd *trap;
+
+ trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!trap) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
+ continue;
+ }
+
+ trap->id = "0";
+ trap->variable = "inform-msc-v1";
+ trap->reply = talloc_strdup(trap, cmd->value);
+ sccplite_msc_ctrl_cmd_send(msc, trap);
+ talloc_free(trap);
+ }
+
+
+ return CTRL_CMD_HANDLED;
+}
+
+/* Return full information about all logical channels.
+ * format: show-lchan.full
+ * result format: New line delimited list of <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>,
+ * <ms power>,<interference dbm>, <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>,
+ * <ipa bound conn id>,<ipa conn ip>,<ipa conn port>,<ipa conn speech mode>
+ */
+static int get_net_show_lchan_full(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+ int bts_nr;
+ bool first_bts = true;
+ char *bts_dump;
+
+ cmd->reply = talloc_strdup(cmd, "");
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ bts_dump = bts_lchan_dump_full_ctrl(cmd, gsm_bts_num(net, bts_nr));
+ if (!bts_dump) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+ if (!strlen(bts_dump))
+ continue;
+ cmd->reply = talloc_asprintf_append(cmd->reply, first_bts ? "%s" : "\n%s", bts_dump);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+ first_bts = false;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(net_show_lchan_full, "show-lchan full");
+
+static int bsc_base_ctrl_cmds_install(struct gsm_network *net)
+{
+ int rc = 0;
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config_file);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_write_config_file);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mnc);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc_mnc_apply);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_lock);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_bts_num);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_states);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_timezone);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_msc0_connection_status);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_notification);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_inform_msc);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_show_lchan_full);
+
+ rc |= ctrl_cmd_install(CTRL_NODE_MSC, &cmd_msc_connection_status);
+
+ rc |= osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net);
+ rc |= osmo_signal_register_handler(SS_MSC, &msc_connection_status_trap_cb, net);
+ rc |= osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL);
+
+ return rc;
+}
+
+
+int bsc_ctrl_cmds_install(struct gsm_network *net)
+{
+ int rc;
+
+ rc = bsc_base_ctrl_cmds_install(net);
+ if (rc)
+ goto end;
+ rc = bsc_ho_ctrl_cmds_install(net);
+ if (rc)
+ goto end;
+ rc = bsc_bts_ctrl_cmds_install();
+ if (rc)
+ goto end;
+end:
+ return rc;
+}
diff --git a/src/osmo-bsc/bsc_ctrl_commands.c b/src/osmo-bsc/bsc_ctrl_commands.c
deleted file mode 100644
index 171feaff0..000000000
--- a/src/osmo-bsc/bsc_ctrl_commands.c
+++ /dev/null
@@ -1,500 +0,0 @@
-/*
- * (C) 2013-2015 by Holger Hans Peter Freyther
- * (C) 2013-2015 by sysmocom s.f.m.c. GmbH
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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 <errno.h>
-#include <time.h>
-
-#include <osmocom/ctrl/control_cmd.h>
-#include <osmocom/gsm/gsm48.h>
-#include <osmocom/bsc/ipaccess.h>
-#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/abis_nm.h>
-#include <osmocom/bsc/debug.h>
-#include <osmocom/bsc/chan_alloc.h>
-#include <osmocom/bsc/osmo_bsc_rf.h>
-#include <osmocom/bsc/bsc_msc_data.h>
-
-CTRL_CMD_DEFINE(net_mcc, "mcc");
-static int get_net_mcc(struct ctrl_cmd *cmd, void *_data)
-{
- struct gsm_network *net = cmd->node;
- cmd->reply = talloc_asprintf(cmd, "%s", osmo_mcc_name(net->plmn.mcc));
- if (!cmd->reply) {
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
- }
- return CTRL_CMD_REPLY;
-}
-static int set_net_mcc(struct ctrl_cmd *cmd, void *_data)
-{
- struct gsm_network *net = cmd->node;
- uint16_t mcc;
- if (osmo_mcc_from_str(cmd->value, &mcc))
- return -1;
- net->plmn.mcc = mcc;
- return get_net_mcc(cmd, _data);
-}
-static int verify_net_mcc(struct ctrl_cmd *cmd, const char *value, void *_data)
-{
- if (osmo_mcc_from_str(value, NULL))
- return -1;
- return 0;
-}
-
-CTRL_CMD_DEFINE(net_mnc, "mnc");
-static int get_net_mnc(struct ctrl_cmd *cmd, void *_data)
-{
- struct gsm_network *net = cmd->node;
- cmd->reply = talloc_asprintf(cmd, "%s", osmo_mnc_name(net->plmn.mnc, net->plmn.mnc_3_digits));
- if (!cmd->reply) {
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
- }
- return CTRL_CMD_REPLY;
-}
-static int set_net_mnc(struct ctrl_cmd *cmd, void *_data)
-{
- struct gsm_network *net = cmd->node;
- struct osmo_plmn_id plmn = net->plmn;
- if (osmo_mnc_from_str(cmd->value, &plmn.mnc, &plmn.mnc_3_digits)) {
- cmd->reply = "Error while decoding MNC";
- return CTRL_CMD_ERROR;
- }
- net->plmn = plmn;
- return get_net_mnc(cmd, _data);
-}
-static int verify_net_mnc(struct ctrl_cmd *cmd, const char *value, void *_data)
-{
- if (osmo_mnc_from_str(value, NULL, NULL))
- return -1;
- return 0;
-}
-
-static int set_net_apply_config(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_network *net = cmd->node;
- struct gsm_bts *bts;
-
- llist_for_each_entry(bts, &net->bts_list, list) {
- if (!is_ipaccess_bts(bts))
- continue;
-
- /*
- * The ip.access nanoBTS seems to be unrelaible on BSSGP
- * so let's us just reboot it. For the sysmoBTS we can just
- * restart the process as all state is gone.
- */
- if (!is_sysmobts_v2(bts) && strcmp(cmd->value, "restart") == 0) {
- struct gsm_bts_trx *trx;
- llist_for_each_entry_reverse(trx, &bts->trx_list, list)
- abis_nm_ipaccess_restart(trx);
- } else
- ipaccess_drop_oml(bts);
- }
-
- cmd->reply = "Tried to drop the BTS";
- return CTRL_CMD_REPLY;
-}
-
-CTRL_CMD_DEFINE_WO_NOVRF(net_apply_config, "apply-configuration");
-
-static int verify_net_mcc_mnc_apply(struct ctrl_cmd *cmd, const char *value, void *d)
-{
- char *tmp, *saveptr, *mcc, *mnc;
- int rc = 0;
-
- tmp = talloc_strdup(cmd, value);
- if (!tmp)
- return 1;
-
- mcc = strtok_r(tmp, ",", &saveptr);
- mnc = strtok_r(NULL, ",", &saveptr);
-
- if (osmo_mcc_from_str(mcc, NULL) || osmo_mnc_from_str(mnc, NULL, NULL))
- rc = -1;
-
- talloc_free(tmp);
- return rc;
-}
-
-static int set_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_network *net = cmd->node;
- char *tmp, *saveptr, *mcc_str, *mnc_str;
- struct osmo_plmn_id plmn;
-
- tmp = talloc_strdup(cmd, cmd->value);
- if (!tmp)
- goto oom;
-
- mcc_str = strtok_r(tmp, ",", &saveptr);
- mnc_str = strtok_r(NULL, ",", &saveptr);
-
- if (osmo_mcc_from_str(mcc_str, &plmn.mcc)) {
- cmd->reply = "Error while decoding MCC";
- talloc_free(tmp);
- return CTRL_CMD_ERROR;
- }
-
- if (osmo_mnc_from_str(mnc_str, &plmn.mnc, &plmn.mnc_3_digits)) {
- cmd->reply = "Error while decoding MNC";
- talloc_free(tmp);
- return CTRL_CMD_ERROR;
- }
-
- talloc_free(tmp);
-
- if (!osmo_plmn_cmp(&net->plmn, &plmn)) {
- cmd->reply = "Nothing changed";
- return CTRL_CMD_REPLY;
- }
-
- net->plmn = plmn;
-
- return set_net_apply_config(cmd, data);
-
-oom:
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
-}
-CTRL_CMD_DEFINE_WO(net_mcc_mnc_apply, "mcc-mnc-apply");
-
-/* BTS related commands below */
-CTRL_CMD_DEFINE_RANGE(bts_lac, "location-area-code", struct gsm_bts, location_area_code, 0, 65535);
-CTRL_CMD_DEFINE_RANGE(bts_ci, "cell-identity", struct gsm_bts, cell_identity, 0, 65535);
-
-static int set_bts_apply_config(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_bts *bts = cmd->node;
-
- if (!is_ipaccess_bts(bts)) {
- cmd->reply = "BTS is not IP based";
- return CTRL_CMD_ERROR;
- }
-
- ipaccess_drop_oml(bts);
- cmd->reply = "Tried to drop the BTS";
- return CTRL_CMD_REPLY;
-}
-
-CTRL_CMD_DEFINE_WO_NOVRF(bts_apply_config, "apply-configuration");
-
-static int set_bts_si(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_bts *bts = cmd->node;
- int rc;
-
- rc = gsm_bts_set_system_infos(bts);
- if (rc != 0) {
- cmd->reply = "Failed to generate SI";
- return CTRL_CMD_ERROR;
- }
-
- cmd->reply = "Generated new System Information";
- return CTRL_CMD_REPLY;
-}
-CTRL_CMD_DEFINE_WO_NOVRF(bts_si, "send-new-system-informations");
-
-static int get_bts_chan_load(struct ctrl_cmd *cmd, void *data)
-{
- int i;
- struct pchan_load pl;
- struct gsm_bts *bts;
- const char *space = "";
-
- bts = cmd->node;
- memset(&pl, 0, sizeof(pl));
- bts_chan_load(&pl, bts);
-
- cmd->reply = talloc_strdup(cmd, "");
-
- for (i = 0; i < ARRAY_SIZE(pl.pchan); ++i) {
- const struct load_counter *lc = &pl.pchan[i];
-
- /* These can never have user load */
- if (i == GSM_PCHAN_NONE)
- continue;
- if (i == GSM_PCHAN_CCCH)
- continue;
- if (i == GSM_PCHAN_PDCH)
- continue;
- if (i == GSM_PCHAN_UNKNOWN)
- continue;
-
- cmd->reply = talloc_asprintf_append(cmd->reply,
- "%s%s,%u,%u",
- space, gsm_pchan_name(i), lc->used, lc->total);
- if (!cmd->reply)
- goto error;
- space = " ";
- }
-
- return CTRL_CMD_REPLY;
-
-error:
- cmd->reply = "Memory allocation failure";
- return CTRL_CMD_ERROR;
-}
-
-CTRL_CMD_DEFINE_RO(bts_chan_load, "channel-load");
-
-static int get_bts_oml_conn(struct ctrl_cmd *cmd, void *data)
-{
- const struct gsm_bts *bts = cmd->node;
-
- cmd->reply = get_model_oml_status(bts);
-
- return CTRL_CMD_REPLY;
-}
-
-CTRL_CMD_DEFINE_RO(bts_oml_conn, "oml-connection-state");
-
-static int get_bts_oml_up(struct ctrl_cmd *cmd, void *data)
-{
- const struct gsm_bts *bts = cmd->node;
-
- cmd->reply = talloc_asprintf(cmd, "%llu", bts_uptime(bts));
- if (!cmd->reply) {
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
- }
-
- return CTRL_CMD_REPLY;
-}
-
-CTRL_CMD_DEFINE_RO(bts_oml_up, "oml-uptime");
-
-static int verify_bts_gprs_mode(struct ctrl_cmd *cmd, const char *value, void *_data)
-{
- int valid;
- enum bts_gprs_mode mode;
- struct gsm_bts *bts = cmd->node;
-
- mode = bts_gprs_mode_parse(value, &valid);
- if (!valid) {
- cmd->reply = "Mode is not known";
- return 1;
- }
-
- if (!bts_gprs_mode_is_compat(bts, mode)) {
- cmd->reply = "bts does not support this mode";
- return 1;
- }
-
- return 0;
-}
-
-static int get_bts_gprs_mode(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_bts *bts = cmd->node;
-
- cmd->reply = talloc_strdup(cmd, bts_gprs_mode_name(bts->gprs.mode));
- return CTRL_CMD_REPLY;
-}
-
-static int set_bts_gprs_mode(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_bts *bts = cmd->node;
-
- bts->gprs.mode = bts_gprs_mode_parse(cmd->value, NULL);
- return get_bts_gprs_mode(cmd, data);
-}
-
-CTRL_CMD_DEFINE(bts_gprs_mode, "gprs-mode");
-
-static int get_bts_rf_state(struct ctrl_cmd *cmd, void *data)
-{
- const char *oper, *admin, *policy;
- struct gsm_bts *bts = cmd->node;
-
- if (!bts) {
- cmd->reply = "bts not found.";
- return CTRL_CMD_ERROR;
- }
-
- oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts));
- admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
- policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
-
- cmd->reply = talloc_asprintf(cmd, "%s,%s,%s", oper, admin, policy);
- if (!cmd->reply) {
- cmd->reply = "OOM.";
- return CTRL_CMD_ERROR;
- }
-
- return CTRL_CMD_REPLY;
-}
-CTRL_CMD_DEFINE_RO(bts_rf_state, "rf_state");
-
-static int get_net_rf_lock(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_network *net = cmd->node;
- struct gsm_bts *bts;
- const char *policy_name;
-
- policy_name = osmo_bsc_rf_get_policy_name(net->bsc_data->rf_ctrl->policy);
-
- llist_for_each_entry(bts, &net->bts_list, list) {
- struct gsm_bts_trx *trx;
-
- /* Exclude the BTS from the global lock */
- if (bts->excl_from_rf_lock)
- continue;
-
- llist_for_each_entry(trx, &bts->trx_list, list) {
- if (trx->mo.nm_state.availability == NM_AVSTATE_OK &&
- trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) {
- cmd->reply = talloc_asprintf(cmd,
- "state=on,policy=%s,bts=%u,trx=%u",
- policy_name, bts->nr, trx->nr);
- return CTRL_CMD_REPLY;
- }
- }
- }
-
- cmd->reply = talloc_asprintf(cmd, "state=off,policy=%s",
- policy_name);
- return CTRL_CMD_REPLY;
-}
-
-#define TIME_FORMAT_RFC2822 "%a, %d %b %Y %T %z"
-
-static int set_net_rf_lock(struct ctrl_cmd *cmd, void *data)
-{
- int locked = atoi(cmd->value);
- struct gsm_network *net = cmd->node;
- time_t now = time(NULL);
- char now_buf[64];
- struct osmo_bsc_rf *rf;
-
- if (!net) {
- cmd->reply = "net not found.";
- return CTRL_CMD_ERROR;
- }
-
- rf = net->bsc_data->rf_ctrl;
-
- if (!rf) {
- cmd->reply = "RF Ctrl is not enabled in the BSC Configuration";
- return CTRL_CMD_ERROR;
- }
-
- talloc_free(rf->last_rf_lock_ctrl_command);
- strftime(now_buf, sizeof(now_buf), TIME_FORMAT_RFC2822, gmtime(&now));
- rf->last_rf_lock_ctrl_command =
- talloc_asprintf(rf, "rf_locked %u (%s)", locked, now_buf);
-
- osmo_bsc_rf_schedule_lock(rf, locked == 1 ? '0' : '1');
-
- cmd->reply = talloc_asprintf(cmd, "%u", locked);
- if (!cmd->reply) {
- cmd->reply = "OOM.";
- return CTRL_CMD_ERROR;
- }
-
- return CTRL_CMD_REPLY;
-}
-
-static int verify_net_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data)
-{
- int locked = atoi(cmd->value);
-
- if ((locked != 0) && (locked != 1))
- return 1;
-
- return 0;
-}
-CTRL_CMD_DEFINE(net_rf_lock, "rf_locked");
-
-static int get_net_bts_num(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_network *net = cmd->node;
-
- cmd->reply = talloc_asprintf(cmd, "%u", net->num_bts);
- return CTRL_CMD_REPLY;
-}
-CTRL_CMD_DEFINE_RO(net_bts_num, "number-of-bts");
-
-/* TRX related commands below here */
-CTRL_HELPER_GET_INT(trx_max_power, struct gsm_bts_trx, max_power_red);
-static int verify_trx_max_power(struct ctrl_cmd *cmd, const char *value, void *_data)
-{
- int tmp = atoi(value);
-
- if (tmp < 0 || tmp > 22) {
- cmd->reply = "Value must be between 0 and 22";
- return -1;
- }
-
- if (tmp & 1) {
- cmd->reply = "Value must be even";
- return -1;
- }
-
- return 0;
-}
-CTRL_CMD_DEFINE_RANGE(trx_arfcn, "arfcn", struct gsm_bts_trx, arfcn, 0, 1023);
-
-static int set_trx_max_power(struct ctrl_cmd *cmd, void *_data)
-{
- struct gsm_bts_trx *trx = cmd->node;
- int old_power;
-
- /* remember the old value, set the new one */
- old_power = trx->max_power_red;
- trx->max_power_red = atoi(cmd->value);
-
- /* Maybe update the value */
- if (old_power != trx->max_power_red) {
- LOGP(DCTRL, LOGL_NOTICE,
- "%s updating max_pwr_red(%d)\n",
- gsm_trx_name(trx), trx->max_power_red);
- abis_nm_update_max_power_red(trx);
- }
-
- return get_trx_max_power(cmd, _data);
-}
-CTRL_CMD_DEFINE(trx_max_power, "max-power-reduction");
-
-int bsc_base_ctrl_cmds_install(void)
-{
- int rc = 0;
- rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mnc);
- rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc);
- rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config);
- rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc_mnc_apply);
- rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_lock);
- rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_bts_num);
-
- rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_lac);
- rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_ci);
- rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_apply_config);
- rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si);
- rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_chan_load);
- rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_conn);
- rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_up);
- rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_gprs_mode);
- rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rf_state);
-
- rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_max_power);
- rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_arfcn);
-
- return rc;
-}
diff --git a/src/osmo-bsc/bsc_ctrl_lookup.c b/src/osmo-bsc/bsc_ctrl_lookup.c
index 38d1ba4ea..63d0c866c 100644
--- a/src/osmo-bsc/bsc_ctrl_lookup.c
+++ b/src/osmo-bsc/bsc_ctrl_lookup.c
@@ -6,18 +6,14 @@
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * GNU Affero General Public License for more details.
*
*/
@@ -29,6 +25,7 @@
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/bts.h>
extern vector ctrl_node_vec;
@@ -46,6 +43,7 @@ static int bsc_ctrl_node_lookup(void *data, vector vline, int *node_type,
struct gsm_bts *bts = NULL;
struct gsm_bts_trx *trx = NULL;
struct gsm_bts_trx_ts *ts = NULL;
+ struct gsm_lchan *lchan = NULL;
struct bsc_msc_data *msc = NULL;
char *token = vector_slot(vline, *i);
long num;
@@ -92,6 +90,20 @@ static int bsc_ctrl_node_lookup(void *data, vector vline, int *node_type,
goto err_missing;
*node_data = ts;
*node_type = CTRL_NODE_TS;
+ } else if (!strcmp(token, "lchan")) {
+ if (*node_type != CTRL_NODE_TS || !*node_data)
+ goto err_missing;
+ ts = *node_data;
+ (*i)++;
+ if (!ctrl_parse_get_num(vline, *i, &num))
+ goto err_index;
+
+ if ((num >= 0) && (num < TS_MAX_LCHAN))
+ lchan = &ts->lchan[num];
+ if (!lchan)
+ goto err_missing;
+ *node_data = lchan;
+ *node_type = CTRL_NODE_LCHAN;
} else if (!strcmp(token, "msc")) {
if (*node_type != CTRL_NODE_ROOT || !net)
goto err_missing;
@@ -114,10 +126,7 @@ err_index:
return -ERANGE;
}
-struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net,
- const char *bind_addr, uint16_t port)
+struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net, uint16_t port)
{
- return ctrl_interface_setup_dynip2(net, bind_addr, port,
- bsc_ctrl_node_lookup,
- _LAST_CTRL_NODE_BSC);
+ return ctrl_interface_setup2(net, port, bsc_ctrl_node_lookup, _LAST_CTRL_NODE_BSC);
}
diff --git a/src/osmo-bsc/bsc_init.c b/src/osmo-bsc/bsc_init.c
index 7d29d4fbe..c6c3e79a5 100644
--- a/src/osmo-bsc/bsc_init.c
+++ b/src/osmo-bsc/bsc_init.c
@@ -36,11 +36,19 @@
#include <osmocom/bsc/handover_cfg.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/lb.h>
+#include <osmocom/bsc/bsc_stats.h>
+
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/gsm/protocol/gsm_48_049.h>
#include <time.h>
#include <limits.h>
#include <stdbool.h>
+struct gsm_network *bsc_gsmnet;
+
int bsc_shutdown_net(struct gsm_network *net)
{
struct gsm_bts *bts;
@@ -53,213 +61,121 @@ int bsc_shutdown_net(struct gsm_network *net)
return 0;
}
-unsigned long long bts_uptime(const struct gsm_bts *bts)
-{
- struct timespec tp;
-
- if (!bts->uptime || !bts->oml_link) {
- LOGP(DNM, LOGL_ERROR, "BTS %u OML link uptime unavailable\n", bts->nr);
- return 0;
- }
-
- if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) {
- LOGP(DNM, LOGL_ERROR, "BTS %u uptime computation failure: %s\n", bts->nr, strerror(errno));
- return 0;
- }
-
- /* monotonic clock helps to ensure that the conversion is valid */
- return difftime(tp.tv_sec, bts->uptime);
-}
-
-static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len)
-{
- struct gsm_bts *bts = trx->bts;
- int rc, j;
-
- if (si_len) {
- DEBUGP(DRR, "SI%s: %s\n", get_value_string(osmo_sitype_strs, i),
- osmo_hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN));
- } else
- DEBUGP(DRR, "SI%s: OFF\n", get_value_string(osmo_sitype_strs, i));
-
- switch (i) {
- case SYSINFO_TYPE_5:
- case SYSINFO_TYPE_5bis:
- case SYSINFO_TYPE_5ter:
- case SYSINFO_TYPE_6:
- rc = rsl_sacch_filling(trx, osmo_sitype2rsl(i),
- si_len ? GSM_BTS_SI(bts, i) : NULL, si_len);
- break;
- case SYSINFO_TYPE_2quater:
- if (si_len == 0) {
- rc = rsl_bcch_info(trx, i, NULL, 0);
- break;
- }
- rc = 0;
- for (j = 0; j <= bts->si2q_count; j++)
- rc = rsl_bcch_info(trx, i, (const uint8_t *)GSM_BTS_SI2Q(bts, j), GSM_MACBLOCK_LEN);
- break;
- default:
- rc = rsl_bcch_info(trx, i, si_len ? GSM_BTS_SI(bts, i) : NULL, si_len);
- break;
- }
-
- return rc;
-}
+/* XXX hard-coded for now */
+#define T3122_CHAN_LOAD_SAMPLE_INTERVAL 1 /* in seconds */
-/* set all system information types for a TRX */
-int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx)
+static void update_t3122_chan_load_timer(void *data)
{
- int i, rc;
- struct gsm_bts *bts = trx->bts;
- uint8_t gen_si[_MAX_SYSINFO_TYPE], n_si = 0, n;
- int si_len[_MAX_SYSINFO_TYPE];
-
- bts->si_common.cell_sel_par.ms_txpwr_max_ccch =
- ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
- bts->si_common.cell_sel_par.neci = bts->network->neci;
-
- /* Zero/forget the state of the dynamically computed SIs, leeping the static ones */
- bts->si_valid = bts->si_mode_static;
-
- /* First, we determine which of the SI messages we actually need */
-
- if (trx == bts->c0) {
- /* 1...4 are always present on a C0 TRX */
- gen_si[n_si++] = SYSINFO_TYPE_1;
- gen_si[n_si++] = SYSINFO_TYPE_2;
- gen_si[n_si++] = SYSINFO_TYPE_2bis;
- gen_si[n_si++] = SYSINFO_TYPE_2ter;
- gen_si[n_si++] = SYSINFO_TYPE_2quater;
- gen_si[n_si++] = SYSINFO_TYPE_3;
- gen_si[n_si++] = SYSINFO_TYPE_4;
-
- /* 13 is always present on a C0 TRX of a GPRS BTS */
- if (bts->gprs.mode != BTS_GPRS_NONE)
- gen_si[n_si++] = SYSINFO_TYPE_13;
- }
-
- /* 5 and 6 are always present on every TRX */
- gen_si[n_si++] = SYSINFO_TYPE_5;
- gen_si[n_si++] = SYSINFO_TYPE_5bis;
- gen_si[n_si++] = SYSINFO_TYPE_5ter;
- gen_si[n_si++] = SYSINFO_TYPE_6;
-
- /* Second, we generate the selected SI via RSL */
-
- for (n = 0; n < n_si; n++) {
- i = gen_si[n];
- /* Only generate SI if this SI is not in "static" (user-defined) mode */
- if (!(bts->si_mode_static & (1 << i))) {
- /* Set SI as being valid. gsm_generate_si() might unset
- * it, if SI is not required. */
- bts->si_valid |= (1 << i);
- rc = gsm_generate_si(bts, i);
- if (rc < 0)
- goto err_out;
- si_len[i] = rc;
- } else {
- if (i == SYSINFO_TYPE_5 || i == SYSINFO_TYPE_5bis
- || i == SYSINFO_TYPE_5ter)
- si_len[i] = 18;
- else if (i == SYSINFO_TYPE_6)
- si_len[i] = 11;
- else
- si_len[i] = 23;
- }
- }
-
- /* Third, we send the selected SI via RSL */
-
- for (n = 0; n < n_si; n++) {
- i = gen_si[n];
- /* 3GPP TS 08.58 ยง8.5.1 BCCH INFORMATION. If we don't currently
- * have this SI, we send a zero-length RSL BCCH FILLING /
- * SACCH FILLING in order to deactivate the SI, in case it
- * might have previously been active */
- if (!GSM_BTS_HAS_SI(bts, i)) {
- if (bts->si_unused_send_empty)
- rc = rsl_si(trx, i, 0);
- else
- rc = 0; /* some nanoBTS fw don't like receiving empty unsupported SI */
- } else
- rc = rsl_si(trx, i, si_len[i]);
- if (rc < 0)
- return rc;
- }
+ struct gsm_network *net = data;
+ struct gsm_bts *bts;
- /* Make sure the PCU is aware (in case anything GPRS related has
- * changed in SI */
- pcu_info_update(bts);
+ llist_for_each_entry(bts, &net->bts_list, list)
+ bts_update_t3122_chan_load(bts);
- return 0;
-err_out:
- LOGP(DRR, LOGL_ERROR, "Cannot generate SI%s for BTS %u: error <%s>, "
- "most likely a problem with neighbor cell list generation\n",
- get_value_string(osmo_sitype_strs, i), bts->nr, strerror(-rc));
- return rc;
+ /* Keep this timer ticking. */
+ osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0);
}
-/* set all system information types for a BTS */
-int gsm_bts_set_system_infos(struct gsm_bts *bts)
+static void bsc_store_bts_uptime(void *data)
{
- struct gsm_bts_trx *trx;
-
- /* Generate a new ID */
- bts->bcch_change_mark += 1;
- bts->bcch_change_mark %= 0x7;
-
- llist_for_each_entry(trx, &bts->trx_list, list) {
- int rc;
+ struct gsm_network *net = data;
+ struct gsm_bts *bts;
- rc = gsm_bts_trx_set_system_infos(trx);
- if (rc != 0)
- return rc;
- }
+ llist_for_each_entry(bts, &net->bts_list, list)
+ bts_store_uptime(bts);
- return 0;
+ /* Keep this timer ticking. */
+ osmo_timer_schedule(&net->bts_store_uptime_timer, BTS_STORE_UPTIME_INTERVAL, 0);
}
-/* XXX hard-coded for now */
-#define T3122_CHAN_LOAD_SAMPLE_INTERVAL 1 /* in seconds */
-
-static void update_t3122_chan_load_timer(void *data)
+static void bsc_store_bts_lchan_durations(void *data)
{
struct gsm_network *net = data;
struct gsm_bts *bts;
llist_for_each_entry(bts, &net->bts_list, list)
- bts_update_t3122_chan_load(bts);
+ bts_store_lchan_durations(bts);
/* Keep this timer ticking. */
- osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0);
+ osmo_timer_schedule(&net->bts_store_lchan_durations_timer, BTS_STORE_LCHAN_DURATIONS_INTERVAL, 0);
}
static struct gsm_network *bsc_network_init(void *ctx)
{
struct gsm_network *net = gsm_network_init(ctx);
- net->bsc_data = talloc_zero(net, struct osmo_bsc_data);
- if (!net->bsc_data) {
- talloc_free(net);
- return NULL;
+ net->cbc = talloc_zero(net, struct bsc_cbc_link);
+ if (!net->cbc) {
+ goto err_out;
}
/* Init back pointer */
- net->bsc_data->auto_off_timeout = -1;
- net->bsc_data->network = net;
- INIT_LLIST_HEAD(&net->bsc_data->mscs);
+ net->auto_off_timeout = -1;
+ INIT_LLIST_HEAD(&net->mscs);
net->ho = ho_cfg_init(net, NULL);
net->hodec2.congestion_check_interval_s = HO_CFG_CONGESTION_CHECK_DEFAULT;
- net->neighbor_bss_cells = neighbor_ident_init(net);
/* init statistics */
net->bsc_ctrs = rate_ctr_group_alloc(net, &bsc_ctrg_desc, 0);
- if (!net->bsc_ctrs) {
- talloc_free(net);
- return NULL;
- }
+ if (!net->bsc_ctrs)
+ goto err_out;
+ net->bsc_statg = osmo_stat_item_group_alloc(net, &bsc_statg_desc, 0);
+ if (!net->bsc_statg)
+ goto err_free_bsc_ctr;
+
+ /* init statistics */
+ net->bts_unknown_ctrs = rate_ctr_group_alloc(net, &bts_ctrg_desc, BTS_STAT_IDX_UNKNOWN);
+ if (!net->bts_unknown_ctrs)
+ goto err_free_bsc_ctr_stat;
+ net->bts_unknown_statg = osmo_stat_item_group_alloc(net, &bts_statg_desc, BTS_STAT_IDX_UNKNOWN);
+ if (!net->bts_unknown_statg)
+ goto err_free_all;
+
+ net->all_allocated.sdcch = (struct osmo_time_cc){
+ .cfg = {
+ .gran_usec = 1*1000000,
+ .forget_sum_usec = 60*1000000,
+ .rate_ctr = rate_ctr_group_get_ctr(net->bsc_ctrs, BSC_CTR_ALL_ALLOCATED_SDCCH),
+ .T_gran = -16,
+ .T_round_threshold = -17,
+ .T_forget_sum = -18,
+ .T_defs = net->T_defs,
+ },
+ };
+ net->all_allocated.static_sdcch = (struct osmo_time_cc){
+ .cfg = {
+ .gran_usec = 1*1000000,
+ .forget_sum_usec = 60*1000000,
+ .rate_ctr = rate_ctr_group_get_ctr(net->bsc_ctrs, BSC_CTR_ALL_ALLOCATED_STATIC_SDCCH),
+ .T_gran = -16,
+ .T_round_threshold = -17,
+ .T_forget_sum = -18,
+ .T_defs = net->T_defs,
+ },
+ };
+ net->all_allocated.tch = (struct osmo_time_cc){
+ .cfg = {
+ .gran_usec = 1*1000000,
+ .forget_sum_usec = 60*1000000,
+ .rate_ctr = rate_ctr_group_get_ctr(net->bsc_ctrs, BSC_CTR_ALL_ALLOCATED_TCH),
+ .T_gran = -16,
+ .T_round_threshold = -17,
+ .T_forget_sum = -18,
+ .T_defs = net->T_defs,
+ },
+ };
+ net->all_allocated.static_tch = (struct osmo_time_cc){
+ .cfg = {
+ .gran_usec = 1*1000000,
+ .forget_sum_usec = 60*1000000,
+ .rate_ctr = rate_ctr_group_get_ctr(net->bsc_ctrs, BSC_CTR_ALL_ALLOCATED_STATIC_TCH),
+ .T_gran = -16,
+ .T_round_threshold = -17,
+ .T_forget_sum = -18,
+ .T_defs = net->T_defs,
+ },
+ };
INIT_LLIST_HEAD(&net->bts_rejected);
gsm_net_update_ctype(net);
@@ -272,7 +188,34 @@ static struct gsm_network *bsc_network_init(void *ctx)
osmo_timer_setup(&net->t3122_chan_load_timer, update_t3122_chan_load_timer, net);
osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0);
+ /* Init uptime tracking timer. */
+ osmo_timer_setup(&net->bts_store_uptime_timer, bsc_store_bts_uptime, net);
+ osmo_timer_schedule(&net->bts_store_uptime_timer, BTS_STORE_UPTIME_INTERVAL, 0);
+
+ /* Init lchan duration tracking timer. */
+ osmo_timer_setup(&net->bts_store_lchan_durations_timer, bsc_store_bts_lchan_durations, net);
+ osmo_timer_schedule(&net->bts_store_lchan_durations_timer, BTS_STORE_LCHAN_DURATIONS_INTERVAL, 0);
+
+ net->cbc->net = net;
+ net->cbc->mode = BSC_CBC_LINK_MODE_DISABLED;
+ net->cbc->server.local_addr = bsc_cbc_default_server_local_addr;
+ /* For CBSP client mode: default remote CBSP server port is CBSP_TCP_PORT == 48049. Leave the IP address unset.
+ * Also leave the local bind for the CBSP client disabled (unconfigured). */
+ net->cbc->client.remote_addr = (struct osmo_sockaddr_str){ .port = CBSP_TCP_PORT, };
+ net->cbc->client.local_addr = (struct osmo_sockaddr_str){};
+
+ net->pcu_sock_wqueue_len_max = BSC_PCU_SOCK_WQUEUE_LEN_DEFAULT;
return net;
+
+err_free_all:
+ rate_ctr_group_free(net->bts_unknown_ctrs);
+err_free_bsc_ctr_stat:
+ osmo_stat_item_group_free(net->bsc_statg);
+err_free_bsc_ctr:
+ rate_ctr_group_free(net->bsc_ctrs);
+err_out:
+ talloc_free(net);
+ return NULL;
}
int bsc_network_alloc(void)
@@ -288,6 +231,7 @@ int bsc_network_alloc(void)
struct gsm_bts *bsc_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type, uint8_t bsic)
{
struct gsm_bts *bts = gsm_bts_alloc_register(net, type, bsic);
+ OSMO_ASSERT(bts != NULL);
bts->ho = ho_cfg_init(bts, net->ho);
diff --git a/src/osmo-bsc/bsc_rf_ctrl.c b/src/osmo-bsc/bsc_rf_ctrl.c
index 791abf6cb..108f7e9ce 100644
--- a/src/osmo-bsc/bsc_rf_ctrl.c
+++ b/src/osmo-bsc/bsc_rf_ctrl.c
@@ -26,6 +26,7 @@
#include <osmocom/bsc/signal.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
@@ -106,12 +107,12 @@ enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_bts(struct gsm_bts *bt
enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts)
{
- struct osmo_bsc_data *bsc_data = bts->network->bsc_data;
+ struct gsm_network *net = bts->network;
- if (!bsc_data)
+ if (!net || !net->rf_ctrl)
return OSMO_BSC_RF_POLICY_UNKNOWN;
- switch (bsc_data->rf_ctrl->policy) {
+ switch (net->rf_ctrl->policy) {
case S_RF_ON:
return OSMO_BSC_RF_POLICY_ON;
case S_RF_OFF:
@@ -123,6 +124,96 @@ enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts)
}
}
+enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_trx(struct gsm_bts_trx *trx)
+{
+ if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED)
+ return OSMO_BSC_RF_OPSTATE_OPERATIONAL;
+ return OSMO_BSC_RF_OPSTATE_INOPERATIONAL;
+}
+
+enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_trx(struct gsm_bts_trx *trx)
+{
+ if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
+ return OSMO_BSC_RF_ADMINSTATE_UNLOCKED;
+ return OSMO_BSC_RF_ADMINSTATE_LOCKED;
+}
+
+/* Return a string listing the state of the given TRX.
+ * For details, see bsc_rf_states_c().
+ */
+static int bsc_rf_state_of_trx_buf(char *buf, size_t buflen, struct gsm_bts_trx *trx)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_PRINTF(sb, "%u,%u,%s,%s,%s,%s;",
+ trx->bts->nr, trx->nr,
+ osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_trx(trx)),
+ osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_trx(trx)),
+ osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(trx->bts)),
+ trx->rsl_link_primary ? "rsl-up" : "rsl-down");
+ return sb.chars_needed;
+}
+
+/* Same as bsc_rf_states_of_bts_c() but return result in a fixed-size buffer.
+ * For details, see bsc_rf_states_c().
+ * Return the amount of characters that would be written to the buffer if it is large enough, like snprintf(). */
+static int bsc_rf_states_of_bts_buf(char *buf, size_t buflen, struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ OSMO_STRBUF_APPEND(sb, bsc_rf_state_of_trx_buf, trx);
+ }
+ return sb.chars_needed;
+}
+
+/* Return a string listing the states of each TRX for the given BTS.
+ * For details, see bsc_rf_states_c().
+ *
+ * \param ctx Talloc context to allocate the returned string from.
+ * \param bts BTS of which to list the TRX states.
+ * \return talloc allocated string.
+ */
+char *bsc_rf_states_of_bts_c(void *ctx, struct gsm_bts *bts)
+{
+ OSMO_NAME_C_IMPL(ctx, 256, "ERROR", bsc_rf_states_of_bts_buf, bts);
+}
+
+/* Same as bsc_rf_states_c() but return result in a fixed-size buffer.
+ * For details, see bsc_rf_states_c().
+ * Return the amount of characters that would be written to the buffer if it is large enough, like snprintf(). */
+static int bsc_rf_states_buf(char *buf, size_t buflen)
+{
+ struct gsm_bts *bts;
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ OSMO_STRBUF_APPEND(sb, bsc_rf_states_of_bts_buf, bts);
+ }
+ return sb.chars_needed;
+}
+
+/* Return a string listing the states of all TRX of all BTS.
+ * The string has the form:
+ * <bts_nr>,<trx_nr>,<opstate>,<adminstate>,<rf_policy>,<rsl_status>;<bts_nr>,<trx_nr>,...;...;
+ * (always terminates in a semicolon).
+ *
+ * Meaning of the elements:
+ * - bts_nr: 0..255 -- BTS index.
+ * - trx_nr: 0..255 -- TRX index.
+ * - opstate: inoperational|operational -- whether RF is active.
+ * - adminstate: unlocked|locked -- whether the TRX is configured as RF-locked.
+ * - rf_policy: off|on|grace|unknown -- which state RF should be in according to user rf_lock requests.
+ * - rsl_status: rsl-up|rsl-down -- 'rsl-up' if an RSL link to the TRX is currently present.
+ *
+ * \param ctx Talloc context to allocate the returned string from.
+ * \return talloc allocated string.
+ */
+char *bsc_rf_states_c(void *ctx)
+{
+ OSMO_NAME_C_IMPL(ctx, 4096, "ERROR", bsc_rf_states_buf);
+}
+
static int lock_each_trx(struct gsm_network *net, bool lock)
{
struct gsm_bts *bts;
@@ -212,7 +303,7 @@ static void rf_check_cb(void *_data)
struct gsm_bts_trx *trx;
/* don't bother to check a booting or missing BTS */
- if (!bts->oml_link || !is_ipaccess_bts(bts))
+ if (!bts->oml_link || !is_ipa_abisip_bts(bts))
continue;
/* Exclude the BTS from the global lock */
@@ -227,7 +318,7 @@ static void rf_check_cb(void *_data)
trx->mo.nm_state.operational != NM_OPSTATE_ENABLED ||
trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) {
LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n");
- ipaccess_drop_oml(bts);
+ ipaccess_drop_oml(bts, "rf check");
break;
}
}
@@ -267,9 +358,9 @@ static int enter_grace(struct osmo_bsc_rf *rf)
}
osmo_timer_setup(&rf->grace_timeout, grace_timeout, rf);
- osmo_timer_schedule(&rf->grace_timeout, rf->gsm_network->bsc_data->mid_call_timeout, 0);
+ osmo_timer_schedule(&rf->grace_timeout, rf->gsm_network->mid_call_timeout, 0);
LOGP(DLINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n",
- rf->gsm_network->bsc_data->mid_call_timeout);
+ rf->gsm_network->mid_call_timeout);
send_signal(rf, S_RF_GRACE);
return 0;
@@ -371,9 +462,7 @@ static int rf_ctrl_accept(struct osmo_fd *bfd, unsigned int what)
}
osmo_wqueue_init(&conn->queue, 10);
- conn->queue.bfd.data = conn;
- conn->queue.bfd.fd = fd;
- conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE;
+ osmo_fd_setup(&conn->queue.bfd, fd, OSMO_FD_READ | OSMO_FD_WRITE, osmo_wqueue_bfd_cb, conn, 0);
conn->queue.read_cb = rf_read_cmd;
conn->queue.write_cb = rf_write_cmd;
conn->rf = rf;
@@ -400,7 +489,6 @@ static int msc_signal_handler(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
struct gsm_network *net;
- struct msc_signal_data *msc;
struct osmo_bsc_rf *rf;
/* check if we want to handle this signal */
@@ -408,23 +496,20 @@ static int msc_signal_handler(unsigned int subsys, unsigned int signal,
return 0;
net = handler_data;
- msc = signal_data;
/* check if we have the needed information */
- if (!net->bsc_data)
- return 0;
- if (msc->data->type != MSC_CON_TYPE_NORMAL)
+ if (!net)
return 0;
- rf = net->bsc_data->rf_ctrl;
+ rf = net->rf_ctrl;
switch (signal) {
case S_MSC_LOST:
- if (net->bsc_data->auto_off_timeout < 0)
+ if (net->auto_off_timeout < 0)
return 0;
if (osmo_timer_pending(&rf->auto_off_timer))
return 0;
osmo_timer_schedule(&rf->auto_off_timer,
- net->bsc_data->auto_off_timeout, 0);
+ net->auto_off_timeout, 0);
break;
case S_MSC_CONNECTED:
osmo_timer_del(&rf->auto_off_timer);
@@ -487,9 +572,7 @@ static int rf_create_socket(struct osmo_bsc_rf *rf, const char *path)
return -1;
}
- bfd->when = BSC_FD_READ;
- bfd->cb = rf_ctrl_accept;
- bfd->data = rf;
+ osmo_fd_setup(bfd, bfd->fd, OSMO_FD_READ, rf_ctrl_accept, rf, 0);
if (osmo_fd_register(bfd) != 0) {
LOGP(DLINP, LOGL_ERROR, "Failed to register bfd.\n");
diff --git a/src/osmo-bsc/bsc_sccp.c b/src/osmo-bsc/bsc_sccp.c
new file mode 100644
index 000000000..323d7fd4f
--- /dev/null
+++ b/src/osmo-bsc/bsc_sccp.c
@@ -0,0 +1,140 @@
+/* Generic SCCP handling across all OsmoBSC users */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.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/core/utils.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/lb.h>
+
+void bscp_sccp_conn_node_init(struct bscp_sccp_conn_node *sccp_conn, struct gsm_subscriber_connection *gscon)
+{
+ sccp_conn->conn_id = SCCP_CONN_ID_UNSET;
+ sccp_conn->gscon = gscon;
+}
+
+struct bsc_sccp_inst *bsc_sccp_inst_alloc(void *ctx)
+{
+ struct bsc_sccp_inst *bsc_sccp;
+
+ bsc_sccp = talloc_zero(ctx, struct bsc_sccp_inst);
+ OSMO_ASSERT(bsc_sccp);
+ bsc_sccp->next_id = 1;
+
+ return bsc_sccp;
+}
+
+int bsc_sccp_inst_register_gscon(struct bsc_sccp_inst *bsc_sccp, struct bscp_sccp_conn_node *sccp_conn)
+{
+ struct rb_node **n = &(bsc_sccp->connections.rb_node);
+ struct rb_node *parent = NULL;
+ uint32_t conn_id = sccp_conn->conn_id;
+
+ OSMO_ASSERT(conn_id != SCCP_CONN_ID_UNSET);
+
+ while (*n) {
+ struct bscp_sccp_conn_node *it = container_of(*n, struct bscp_sccp_conn_node, node);
+
+ parent = *n;
+ if (conn_id < it->conn_id) {
+ n = &((*n)->rb_left);
+ } else if (conn_id > it->conn_id) {
+ n = &((*n)->rb_right);
+ } else {
+ LOGP(DMSC, LOGL_ERROR,
+ "Trying to reserve already reserved conn_id %u\n", conn_id);
+ return -EEXIST;
+ }
+ }
+
+ rb_link_node(&sccp_conn->node, parent, n);
+ rb_insert_color(&sccp_conn->node, &bsc_sccp->connections);
+ return 0;
+}
+
+void bsc_sccp_inst_unregister_gscon(struct bsc_sccp_inst *bsc_sccp, struct bscp_sccp_conn_node *sccp_conn)
+{
+ OSMO_ASSERT(sccp_conn->conn_id != SCCP_CONN_ID_UNSET);
+ rb_erase(&sccp_conn->node, &bsc_sccp->connections);
+}
+
+/* Helper function to Check if the given connection id is already assigned */
+struct gsm_subscriber_connection *bsc_sccp_inst_get_gscon_by_conn_id(const struct bsc_sccp_inst *bsc_sccp, uint32_t conn_id)
+{
+ const struct rb_node *node = bsc_sccp->connections.rb_node;
+
+ OSMO_ASSERT(conn_id != SCCP_CONN_ID_UNSET);
+ /* Range (0..SCCP_CONN_ID_MAX) expected, see bsc_sccp_inst_next_conn_id() */
+ OSMO_ASSERT(conn_id <= SCCP_CONN_ID_MAX);
+
+ while (node) {
+ struct bscp_sccp_conn_node *sccp_conn = container_of(node, struct bscp_sccp_conn_node, node);
+ if (conn_id < sccp_conn->conn_id)
+ node = node->rb_left;
+ else if (conn_id > sccp_conn->conn_id)
+ node = node->rb_right;
+ else
+ return sccp_conn->gscon;
+ }
+
+ return NULL;
+}
+
+/* We need an unused SCCP conn_id across all SCCP users. */
+uint32_t bsc_sccp_inst_next_conn_id(struct bsc_sccp_inst *bsc_sccp)
+{
+ uint32_t first_id, test_id;
+
+ first_id = test_id = bsc_sccp->next_id;
+
+ /* SUA: RFC3868 sec 3.10.4:
+ * The source reference number is a 4 octet long integer.
+ * This is allocated by the source SUA instance.
+ * M3UA/SCCP: ITU-T Q.713 sec 3.3:
+ * The "source local reference" parameter field is a three-octet field containing a
+ * reference number which is generated and used by the local node to identify the
+ * connection section after the connection section is set up.
+ * The coding "all ones" is reserved for future use.
+ *Hence, as we currently use the connection ID also as local reference,
+ *let's simply use 24 bit ids to fit all link types (excluding 0x00ffffff).
+ */
+
+ while (bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, test_id)) {
+ /* Optimized modulo operation (% SCCP_CONN_ID_MAX) using bitwise AND plus CMP: */
+ test_id = (test_id + 1) & 0x00FFFFFF;
+ if (OSMO_UNLIKELY(test_id == 0x00FFFFFF))
+ test_id = 0;
+
+ /* Did a whole loop, all used, fail */
+ if (OSMO_UNLIKELY(test_id == first_id))
+ return SCCP_CONN_ID_UNSET;
+ }
+
+ bsc_sccp->next_id = test_id;
+ /* Optimized modulo operation (% SCCP_CONN_ID_MAX) using bitwise AND plus CMP: */
+ bsc_sccp->next_id = (bsc_sccp->next_id + 1) & 0x00FFFFFF;
+ if (OSMO_UNLIKELY(bsc_sccp->next_id == 0x00FFFFFF))
+ bsc_sccp->next_id = 0;
+
+ return test_id;
+}
diff --git a/src/osmo-bsc/bsc_stats.c b/src/osmo-bsc/bsc_stats.c
new file mode 100644
index 000000000..ce307207c
--- /dev/null
+++ b/src/osmo-bsc/bsc_stats.c
@@ -0,0 +1,236 @@
+/* osmo-bsc statistics */
+/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/bsc_stats.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/stat_item.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/chan_counts.h>
+
+const struct rate_ctr_desc bsc_ctr_description[] = {
+ [BSC_CTR_ASSIGNMENT_ATTEMPTED] = {"assignment:attempted", "Assignment attempts"},
+ [BSC_CTR_ASSIGNMENT_COMPLETED] = {"assignment:completed", "Assignment completed"},
+ [BSC_CTR_ASSIGNMENT_STOPPED] = {"assignment:stopped", "Connection ended during Assignment"},
+ [BSC_CTR_ASSIGNMENT_NO_CHANNEL] = {"assignment:no_channel", "Failure to allocate lchan for Assignment"},
+ [BSC_CTR_ASSIGNMENT_TIMEOUT] = {"assignment:timeout", "Assignment timed out"},
+ [BSC_CTR_ASSIGNMENT_FAILED] = {"assignment:failed", "Received Assignment Failure message"},
+ [BSC_CTR_ASSIGNMENT_ERROR] = {"assignment:error", "Assignment failed for other reason"},
+
+ [BSC_CTR_HANDOVER_ATTEMPTED] = {"handover:attempted", "Handover attempts"},
+ [BSC_CTR_HANDOVER_COMPLETED] = {"handover:completed", "Handover completed"},
+ [BSC_CTR_HANDOVER_STOPPED] = {"handover:stopped", "Connection ended during HO"},
+ [BSC_CTR_HANDOVER_NO_CHANNEL] = {"handover:no_channel", "Failure to allocate lchan for HO"},
+ [BSC_CTR_HANDOVER_TIMEOUT] = {"handover:timeout", "Handover timed out"},
+ [BSC_CTR_HANDOVER_FAILED] = {"handover:failed", "Received Handover Fail messages"},
+ [BSC_CTR_HANDOVER_ERROR] = {"handover:error", "Handover failed for other reason"},
+
+ [BSC_CTR_INTRA_CELL_HO_ATTEMPTED] = {"intra_cell_ho:attempted", "Intra-Cell handover attempts"},
+ [BSC_CTR_INTRA_CELL_HO_COMPLETED] = {"intra_cell_ho:completed", "Intra-Cell handover completed"},
+ [BSC_CTR_INTRA_CELL_HO_STOPPED] = {"intra_cell_ho:stopped", "Connection ended during HO"},
+ [BSC_CTR_INTRA_CELL_HO_NO_CHANNEL] = {"intra_cell_ho:no_channel", "Failure to allocate lchan for HO"},
+ [BSC_CTR_INTRA_CELL_HO_TIMEOUT] = {"intra_cell_ho:timeout", "Handover timed out"},
+ [BSC_CTR_INTRA_CELL_HO_FAILED] = {"intra_cell_ho:failed", "Received Handover Fail messages"},
+ [BSC_CTR_INTRA_CELL_HO_ERROR] = {"intra_cell_ho:error", "Intra-cell handover failed for other reason"},
+
+ [BSC_CTR_INTRA_BSC_HO_ATTEMPTED] = {"intra_bsc_ho:attempted", "Intra-BSC inter-cell handover attempts"},
+ [BSC_CTR_INTRA_BSC_HO_COMPLETED] = {"intra_bsc_ho:completed", "Intra-BSC inter-cell handover completed"},
+ [BSC_CTR_INTRA_BSC_HO_STOPPED] = {"intra_bsc_ho:stopped", "Connection ended during HO"},
+ [BSC_CTR_INTRA_BSC_HO_NO_CHANNEL] = {"intra_bsc_ho:no_channel", "Failure to allocate lchan for HO"},
+ [BSC_CTR_INTRA_BSC_HO_TIMEOUT] = {"intra_bsc_ho:timeout", "Handover timed out"},
+ [BSC_CTR_INTRA_BSC_HO_FAILED] = {"intra_bsc_ho:failed", "Received Handover Fail messages"},
+ [BSC_CTR_INTRA_BSC_HO_ERROR] = {"intra_bsc_ho:error", "Intra-BSC inter-cell HO failed for other reason"},
+
+ [BSC_CTR_INTER_BSC_HO_OUT_ATTEMPTED] = {"interbsc_ho_out:attempted",
+ "Attempts to handover to remote BSS"},
+ [BSC_CTR_INTER_BSC_HO_OUT_COMPLETED] = {"interbsc_ho_out:completed",
+ "Handover to remote BSS completed"},
+ [BSC_CTR_INTER_BSC_HO_OUT_STOPPED] = {"interbsc_ho_out:stopped", "Connection ended during HO"},
+ [BSC_CTR_INTER_BSC_HO_OUT_TIMEOUT] = {"interbsc_ho_out:timeout", "Handover timed out"},
+ [BSC_CTR_INTER_BSC_HO_OUT_FAILED] = {"interbsc_ho_out:failed", "Received Handover Fail message"},
+ [BSC_CTR_INTER_BSC_HO_OUT_ERROR] = {"interbsc_ho_out:error",
+ "Handover to remote BSS failed for other reason"},
+
+ [BSC_CTR_INTER_BSC_HO_IN_ATTEMPTED] = {"interbsc_ho_in:attempted",
+ "Attempts to handover from remote BSS"},
+ [BSC_CTR_INTER_BSC_HO_IN_COMPLETED] = {"interbsc_ho_in:completed",
+ "Handover from remote BSS completed"},
+ [BSC_CTR_INTER_BSC_HO_IN_STOPPED] = {"interbsc_ho_in:stopped", "Connection ended during HO"},
+ [BSC_CTR_INTER_BSC_HO_IN_NO_CHANNEL] = {"interbsc_ho_in:no_channel",
+ "Failure to allocate lchan for HO"},
+ [BSC_CTR_INTER_BSC_HO_IN_TIMEOUT] = {"interbsc_ho_in:timeout", "Handover from remote BSS timed out"},
+ [BSC_CTR_INTER_BSC_HO_IN_FAILED] = {"interbsc_ho_in:failed", "Received Handover Fail message"},
+ [BSC_CTR_INTER_BSC_HO_IN_ERROR] = {"interbsc_ho_in:error",
+ "Handover from remote BSS failed for other reason"},
+
+ [BSC_CTR_SRVCC_ATTEMPTED] = {"srvcc:attempted", "Intra-BSC SRVCC attempts"},
+ [BSC_CTR_SRVCC_COMPLETED] = {"srvcc:completed", "Intra-BSC SRVCC completed"},
+ [BSC_CTR_SRVCC_STOPPED] = {"srvcc:stopped", "Connection ended during HO"},
+ [BSC_CTR_SRVCC_NO_CHANNEL] = {"srvcc:no_channel", "Failure to allocate lchan for HO"},
+ [BSC_CTR_SRVCC_TIMEOUT] = {"srvcc:timeout", "SRVCC timed out"},
+ [BSC_CTR_SRVCC_FAILED] = {"srvcc:failed", "Received SRVCC Fail messages"},
+ [BSC_CTR_SRVCC_ERROR] = {"srvcc:error", "Re-assignment failed for other reason"},
+
+ [BSC_CTR_PAGING_ATTEMPTED] = {"paging:attempted", "Paging attempts for a subscriber"},
+ [BSC_CTR_PAGING_DETACHED] = {"paging:detached", "Paging request send failures because no responsible BTS was found"},
+ [BSC_CTR_PAGING_RESPONDED] = {"paging:responded", "Paging attempts with successful response"},
+ [BSC_CTR_PAGING_EXPIRED] = {"paging:expired", "Paging Request expired because of timeout T3113"},
+ [BSC_CTR_PAGING_NO_ACTIVE_PAGING] = {"paging:no_active_paging", "Paging response without an active paging request (arrived after paging expiration?)"},
+
+ [BSC_CTR_UNKNOWN_UNIT_ID] = {"abis:unknown_unit_id", "Connection attempts from unknown IPA CCM Unit ID"},
+
+ [BSC_CTR_MSCPOOL_SUBSCR_NO_MSC] = {"mscpool:subscr:no_msc",
+ "Complete Layer 3 requests lost because no connected MSC is found available"},
+ [BSC_CTR_MSCPOOL_EMERG_FORWARDED] = {"mscpool:emerg:forwarded",
+ "Emergency call requests forwarded to an MSC (see also per-MSC counters"},
+ [BSC_CTR_MSCPOOL_EMERG_LOST] = {"mscpool:emerg:lost",
+ "Emergency call requests lost because no MSC was found available"},
+ [BSC_CTR_ALL_ALLOCATED_SDCCH] = {"all_allocated:sdcch", "Cumulative counter of seconds where all SDCCH channels were allocated"},
+ [BSC_CTR_ALL_ALLOCATED_STATIC_SDCCH] = {"all_allocated:static_sdcch",
+ "Cumulative counter of seconds where all non-dynamic SDCCH channels were allocated"},
+ [BSC_CTR_ALL_ALLOCATED_TCH] = {"all_allocated:tch", "Cumulative counter of seconds where all TCH channels were allocated"},
+ [BSC_CTR_ALL_ALLOCATED_STATIC_TCH] = {"all_allocated:static_tch",
+ "Cumulative counter of seconds where all non-dynamic TCH channels were allocated"},
+};
+
+const struct rate_ctr_group_desc bsc_ctrg_desc = {
+ "bsc",
+ "base station controller",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(bsc_ctr_description),
+ bsc_ctr_description,
+};
+
+static const struct osmo_stat_item_desc bsc_stat_desc[] = {
+ [BSC_STAT_NUM_BTS_OML_CONNECTED] = { "num_bts:oml_connected", "Number of BTS for this BSC where OML is up", "", 16, 0 },
+ [BSC_STAT_NUM_BTS_ALL_TRX_RSL_CONNECTED] = { "num_bts:all_trx_rsl_connected", "Number of BTS for this BSC where RSL is up for all TRX", "", 16, 0 },
+ [BSC_STAT_NUM_BTS_TOTAL] = { "num_bts:total", "Number of configured BTS for this BSC", "", 16, 0 },
+ [BSC_STAT_NUM_TRX_RSL_CONNECTED] = { "num_trx:rsl_connected", "Number of TRX where RSL is up, total sum across all BTS", "", 16, 0 },
+ [BSC_STAT_NUM_TRX_TOTAL] = { "num_trx:total", "Number of configured TRX, total sum across all BTS", "", 1, 0 },
+ [BSC_STAT_NUM_MSC_CONNECTED] = { "num_msc:connected", "Number of actively connected MSCs", "", 16, 0 },
+ [BSC_STAT_NUM_MSC_TOTAL] = { "num_msc:total", "Number of configured MSCs, not necessarily connected", "", 1, 0 },
+};
+
+const struct osmo_stat_item_group_desc bsc_statg_desc = {
+ .group_name_prefix = "bsc",
+ .group_description = "base station controller",
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+ .num_items = ARRAY_SIZE(bsc_stat_desc),
+ .item_desc = bsc_stat_desc,
+};
+
+/* Count all BTS and TRX OML and RSL stati and update stat items */
+void bsc_update_connection_stats(struct gsm_network *net)
+{
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+
+ /* Nr of configured BTS and total sum of configured TRX across all BTS */
+ int num_bts = 0;
+ int num_trx_total = 0;
+ /* Nr of BTS where OML is up */
+ int bts_oml_connected = 0;
+ /* Nr of TRX across all BTS where RSL is up */
+ int trx_rsl_connected_total = 0;
+ /* Nr of BTS that have all TRX RSL up */
+ int bts_rsl_all_trx_connected = 0;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ bool oml_connected = false;
+ int num_trx = 0;
+ int trx_rsl_connected = 0;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ /* If any one trx is usable, it means OML for this BTS is connected */
+ if (trx_is_usable(trx))
+ oml_connected = true;
+
+ /* Count nr of TRX for this BTS */
+ num_trx++;
+ if (trx->ts[0].is_rsl_ready)
+ trx_rsl_connected++;
+ }
+
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_NUM_TRX_RSL_CONNECTED),
+ trx_rsl_connected);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_NUM_TRX_TOTAL),
+ num_trx);
+
+ num_trx_total += num_trx;
+ trx_rsl_connected_total += trx_rsl_connected;
+
+ num_bts++;
+ if (oml_connected)
+ bts_oml_connected++;
+ if (trx_rsl_connected == num_trx)
+ bts_rsl_all_trx_connected++;
+
+ all_allocated_update_bts(bts);
+ }
+
+ osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_BTS_OML_CONNECTED),
+ bts_oml_connected);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_BTS_ALL_TRX_RSL_CONNECTED),
+ bts_rsl_all_trx_connected);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_BTS_TOTAL), num_bts);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_TRX_RSL_CONNECTED),
+ trx_rsl_connected_total);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(net->bsc_statg, BSC_STAT_NUM_TRX_TOTAL), num_trx_total);
+
+ /* This is optional, just running this to catch bugs in chan_counts accounting. If there is a bug, there will be
+ * a DLGLOBAL ERROR logged, and the error gets fixed. */
+ chan_counts_bsc_verify();
+}
+
+static void all_allocated_update(struct all_allocated *all_allocated, const struct chan_counts *c)
+{
+ osmo_time_cc_set_flag(&all_allocated->sdcch,
+ c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
+ && !c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
+
+ osmo_time_cc_set_flag(&all_allocated->static_sdcch,
+ c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_SDCCH]
+ && !c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_SDCCH]);
+
+ osmo_time_cc_set_flag(&all_allocated->tch,
+ (c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ + c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
+ && !(c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ + c->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
+
+ osmo_time_cc_set_flag(&all_allocated->static_tch,
+ (c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_F]
+ + c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_MAX_TOTAL][GSM_LCHAN_TCH_H])
+ && !(c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F]
+ + c->val[CHAN_COUNTS1_STATIC][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H]));
+}
+
+void all_allocated_update_bts(struct gsm_bts *bts)
+{
+ all_allocated_update(&bts->all_allocated, &bts->chan_counts);
+}
+
+void all_allocated_update_bsc(void)
+{
+ struct gsm_network *net = bsc_gsmnet;
+ all_allocated_update(&net->all_allocated, &net->chan_counts);
+}
diff --git a/src/osmo-bsc/bsc_subscr_conn_fsm.c b/src/osmo-bsc/bsc_subscr_conn_fsm.c
index fc34968e7..f1f48bc35 100644
--- a/src/osmo-bsc/bsc_subscr_conn_fsm.c
+++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c
@@ -16,6 +16,8 @@
*
*/
+#include <limits.h>
+
#include <osmocom/core/fsm.h>
#include <osmocom/core/logging.h>
#include <osmocom/gsm/gsm0808.h>
@@ -26,9 +28,11 @@
#include <osmocom/bsc/a_reset.h>
#include <osmocom/bsc/gsm_08_08.h>
#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_rtp_fsm.h>
+#include <osmocom/bsc/lchan.h>
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/osmo_bsc_sigtran.h>
#include <osmocom/bsc/osmo_bsc_lcls.h>
@@ -37,12 +41,16 @@
#include <osmocom/bsc/penalty_timers.h>
#include <osmocom/bsc/bsc_rll.h>
#include <osmocom/bsc/abis_rsl.h>
-#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/core/tdef.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
#include <osmocom/bsc/assignment_fsm.h>
-#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+#include <osmocom/bsc/codec_pref.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
+#include <osmocom/mgcp_client/mgcp_client_pool.h>
#include <osmocom/core/byteswap.h>
+#include <osmocom/bsc/lb.h>
+#include <osmocom/bsc/lcs_loc_req.h>
+#include <osmocom/bsc/vgcs_fsm.h>
#define S(x) (1 << (x))
@@ -54,6 +62,8 @@
enum gscon_fsm_states {
ST_INIT,
+ /* wait for initial BSSMAP after the MSC opened a new SCCP connection */
+ ST_WAIT_INITIAL_USER_DATA,
/* waiting for CC from MSC */
ST_WAIT_CC,
/* active connection */
@@ -61,15 +71,18 @@ enum gscon_fsm_states {
ST_ASSIGNMENT,
ST_HANDOVER,
/* BSSMAP CLEAR has been received */
- ST_CLEARING,
+ ST_WAIT_CLEAR_CMD,
+ ST_WAIT_SCCP_RLSD,
};
static const struct value_string gscon_fsm_event_names[] = {
{GSCON_EV_A_CONN_IND, "MT-CONNECT.ind"},
- {GSCON_EV_A_CONN_REQ, "MO-CONNECT.req"},
+ {GSCON_EV_A_INITIAL_USER_DATA, "A_INITIAL_USER_DATA"},
+ {GSCON_EV_MO_COMPL_L3, "MO_COMPL_L3"},
{GSCON_EV_A_CONN_CFM, "MO-CONNECT.cfm"},
{GSCON_EV_A_CLEAR_CMD, "CLEAR_CMD"},
- {GSCON_EV_A_DISC_IND, "DISCONNET.ind"},
+ {GSCON_EV_A_DISC_IND, "DISCONNECT.ind"},
+ {GSCON_EV_A_COMMON_ID_IND, "COMMON_ID.ind"},
{GSCON_EV_ASSIGNMENT_START, "ASSIGNMENT_START"},
{GSCON_EV_ASSIGNMENT_END, "ASSIGNMENT_END"},
{GSCON_EV_HANDOVER_START, "HANDOVER_START"},
@@ -82,22 +95,25 @@ static const struct value_string gscon_fsm_event_names[] = {
{GSCON_EV_LCLS_FAIL, "LCLS_FAIL"},
{GSCON_EV_FORGET_LCHAN, "FORGET_LCHAN"},
{GSCON_EV_FORGET_MGW_ENDPOINT, "FORGET_MGW_ENDPOINT"},
+ {GSCON_EV_LCS_LOC_REQ_END, "LCS_LOC_REQ_END"},
{}
};
-struct state_timeout conn_fsm_timeouts[32] = {
- [ST_WAIT_CC] = { .T = 993210 },
- [ST_CLEARING] = { .T = 999 },
+struct osmo_tdef_state_timeout conn_fsm_timeouts[32] = {
+ [ST_WAIT_INITIAL_USER_DATA] = { .T = -25 },
+ [ST_WAIT_CC] = { .T = -3210 },
+ [ST_WAIT_CLEAR_CMD] = { .T = -4 },
+ [ST_WAIT_SCCP_RLSD] = { .T = -4 },
};
/* Transition to a state, using the T timer defined in conn_fsm_timeouts.
* The actual timeout value is in turn obtained from network->T_defs.
* Assumes local variable 'conn' exists. */
#define conn_fsm_state_chg(state) \
- fsm_inst_state_chg_T(conn->fi, state, \
- conn_fsm_timeouts, \
- conn->network->T_defs, \
- -1)
+ osmo_tdef_fsm_inst_state_chg(conn->fi, state, \
+ conn_fsm_timeouts, \
+ conn->network->T_defs, \
+ -1)
/* forward MT DTAP from BSSAP side to RSL side */
static inline void submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg)
@@ -117,7 +133,7 @@ int gscon_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg)
return -ENOMEM;
/* Make sure that we only attempt to send SCCP messages if we have
- * a life SCCP connection. Otherwise drop the message. */
+ * a live SCCP connection. Otherwise drop the message. */
if (conn->fi->state == ST_INIT || conn->fi->state == ST_WAIT_CC) {
LOGPFSML(conn->fi, LOGL_ERROR, "No active SCCP connection, dropping message\n");
msgb_free(msg);
@@ -130,18 +146,85 @@ int gscon_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg)
return rc;
}
-static void gscon_bssmap_clear(struct gsm_subscriber_connection *conn,
- enum gsm0808_cause cause)
+void gscon_bssmap_clear(struct gsm_subscriber_connection *conn, enum gsm0808_cause cause)
+{
+ /* already clearing? */
+ switch (conn->fi->state) {
+ case ST_WAIT_CLEAR_CMD:
+ case ST_WAIT_SCCP_RLSD:
+ return;
+ default:
+ break;
+ }
+
+ conn->clear_cause = cause;
+ conn_fsm_state_chg(ST_WAIT_CLEAR_CMD);
+}
+
+static void gscon_fsm_wait_clear_cmd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
- struct msgb *resp = gsm0808_create_clear_rqst(cause);
+ struct msgb *resp;
int rc;
+ struct gsm_subscriber_connection *conn = fi->priv;
+ enum gsm0808_cause cause = conn->clear_cause;
+
+ if (!conn->sccp.msc) {
+ LOGPFSML(fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message, no MSC for this conn\n");
+ goto nothing_sent;
+ }
+
+ LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST(%s) to MSC\n", gsm0808_cause_name(cause));
+ resp = gsm0808_create_clear_rqst(cause);
if (!resp) {
- LOGPFSML(conn->fi, LOGL_ERROR, "Unable to compose BSSMAP Clear Request message\n");
- return;
+ LOGPFSML(fi, LOGL_ERROR, "Unable to compose BSSMAP Clear Request message\n");
+ goto nothing_sent;
}
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CLEAR_RQST));
rc = osmo_bsc_sigtran_send(conn, resp);
- if (rc < 0)
+ if (rc < 0) {
LOGPFSML(conn->fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message\n");
+ goto nothing_sent;
+ }
+ return;
+
+nothing_sent:
+ /* Normally, we request a CLEAR from the MSC and terminate as soon as the CLEAR COMMAND has been issued by the
+ * MSC. But if we are trying to clear without being able to send anything to the MSC, we might as well shut down
+ * the conn right away now. */
+ conn_fsm_state_chg(ST_WAIT_SCCP_RLSD);
+}
+
+void gscon_fsm_wait_sccp_rlsd_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ /* According to 3GPP 48.008 3.1.9.1. "The BSS need not wait for the radio channel
+ * release to be completed or for the guard timer to expire before returning the
+ * CLEAR COMPLETE message" */
+ if (!gscon_sigtran_send(conn, gsm0808_create_clear_complete()))
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CLEAR_COMPLETE));
+
+ /* Give the handover_fsm a chance to book this as handover success before tearing down everything,
+ * making it look like a sudden death failure. */
+ if (conn->ho.fi)
+ osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_CONN_RELEASING, NULL);
+
+ if (conn->lcs.loc_req)
+ osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_CONN_CLEAR, NULL);
+
+ if (conn->vgcs_call.fi)
+ osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_CLEANUP, NULL);
+
+ if (conn->vgcs_chan.fi)
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.fi, VGCS_EV_CLEANUP, NULL);
+
+ gscon_release_lchans(conn, true, bsc_gsm48_rr_cause_from_gsm0808_cause(conn->clear_cause));
+ osmo_mgcpc_ep_clear(conn->user_plane.mgw_endpoint);
+
+ /* If there is no SCCP connection at all, then no need to wait for an SCCP RLSD. */
+ if (!conn->sccp.msc || conn->sccp.state != SUBSCR_SCCP_ST_CONNECTED)
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
}
/* forward MO DTAP from RSL side to BSSAP side */
@@ -153,6 +236,7 @@ static void forward_dtap(struct gsm_subscriber_connection *conn, struct msgb *ms
OSMO_ASSERT(conn);
resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg));
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_DTAP));
gscon_sigtran_send(conn, resp);
}
@@ -169,85 +253,167 @@ static void gscon_release_lchan(struct gsm_subscriber_connection *conn, struct g
conn->lchan = NULL;
if (conn->ho.fi && conn->ho.new_lchan == lchan)
conn->ho.new_lchan = NULL;
+ if (conn->vgcs_chan.new_lchan == lchan)
+ conn->vgcs_chan.new_lchan = NULL;
if (conn->assignment.new_lchan == lchan)
conn->assignment.new_lchan = NULL;
- lchan_release(lchan, do_rr_release, err, cause_rr);
+ lchan_release(lchan, do_rr_release, err, cause_rr,
+ gscon_last_eutran_plmn(conn));
}
-void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_release)
+void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_release, enum gsm48_rr_cause cause_rr)
{
if (conn->ho.fi)
handover_end(conn, HO_RESULT_CONN_RELEASE);
assignment_reset(conn);
- gscon_release_lchan(conn, conn->lchan, do_rr_release, false, 0);
+ gscon_release_lchan(conn, conn->lchan, do_rr_release, false, cause_rr);
}
-static void handle_bssap_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_prim *scu_prim)
+static int validate_initial_user_data(struct osmo_fsm_inst *fi, struct msgb *msg)
{
- struct gsm_subscriber_connection *conn = fi->priv;
- struct msgb *msg = scu_prim->oph.msg;
struct bssmap_header *bs;
- uint8_t bssmap_type;
+ enum BSS_MAP_MSG_TYPE bssmap_type;
msg->l3h = msgb_l2(msg);
if (!msgb_l3(msg)) {
LOGPFSML(fi, LOGL_ERROR, "internal error: no l3 in msg\n");
- goto refuse;
+ return -EINVAL;
}
if (msgb_l3len(msg) < sizeof(*bs)) {
- LOGPFSML(fi, LOGL_NOTICE, "message too short for BSSMAP header (%u < %zu)\n",
+ LOGPFSML(fi, LOGL_ERROR, "message too short for BSSMAP header (%u < %zu)\n",
msgb_l3len(msg), sizeof(*bs));
- goto refuse;
+ return -EINVAL;
}
bs = (struct bssmap_header*)msgb_l3(msg);
if (msgb_l3len(msg) < (bs->length + sizeof(*bs))) {
- LOGPFSML(fi, LOGL_NOTICE,
+ LOGPFSML(fi, LOGL_ERROR,
"message too short for length indicated in BSSMAP header (%u < %u)\n",
msgb_l3len(msg), bs->length);
- goto refuse;
+ return -EINVAL;
}
switch (bs->type) {
case BSSAP_MSG_BSS_MANAGEMENT:
break;
default:
- LOGPFSML(fi, LOGL_NOTICE,
- "message type not allowed for N-CONNECT: %s\n", gsm0808_bssap_name(bs->type));
- goto refuse;
+ LOGPFSML(fi, LOGL_ERROR,
+ "message type not allowed for initial BSSMAP: %s\n", gsm0808_bssap_name(bs->type));
+ return -EINVAL;
}
msg->l4h = &msg->l3h[sizeof(*bs)];
+
+ /* Validate initial message type. See also BSC_Tests.TC_outbound_connect. */
+ bssmap_type = msg->l4h[0];
+ switch (bssmap_type) {
+ case BSS_MAP_MSG_HANDOVER_RQST:
+ case BSS_MAP_MSG_PERFORM_LOCATION_RQST:
+ case BSS_MAP_MSG_VGCS_VBS_SETUP:
+ case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST:
+ return 0;
+
+ default:
+ LOGPFSML(fi, LOGL_ERROR, "No support for initial BSSMAP: %s: %s\n",
+ gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type));
+ return -EINVAL;
+ }
+}
+
+static void handle_initial_user_data(struct osmo_fsm_inst *fi, struct msgb *msg)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ struct bssmap_header *bs;
+ enum BSS_MAP_MSG_TYPE bssmap_type;
+
+ /* validate_initial_user_data() must be called before this */
+ OSMO_ASSERT(msgb_l4(msg));
+
+ bs = msgb_l3(msg);
bssmap_type = msg->l4h[0];
- LOGPFSML(fi, LOGL_DEBUG, "Rx N-CONNECT: %s: %s\n", gsm0808_bssap_name(bs->type),
+ /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() (OS#2969) */
+
+ LOGPFSML(fi, LOGL_DEBUG, "Rx initial BSSMAP: %s: %s\n", gsm0808_bssap_name(bs->type),
gsm0808_bssmap_name(bssmap_type));
switch (bssmap_type) {
case BSS_MAP_MSG_HANDOVER_RQST:
- /* First off, accept the new conn. */
- osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
- &scu_prim->u.connect.called_addr, NULL, 0);
+ rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_HANDOVER_RQST]);
+ /* Inter-BSC incoming Handover Request, another BSS is handovering to us. */
+ handover_start_inter_bsc_in(conn, msg);
+ return;
- /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */
- conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED;
+ case BSS_MAP_MSG_PERFORM_LOCATION_RQST:
+ rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST]);
+ /* Location Services: MSC asks for location of an IDLE subscriber */
+ conn_fsm_state_chg(ST_ACTIVE);
+ lcs_loc_req_start(conn, msg);
+ return;
- /* Inter-BSC MT Handover Request, another BSS is handovering to us. */
- handover_start_inter_bsc_in(conn, msg);
+ case BSS_MAP_MSG_VGCS_VBS_SETUP:
+ rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_VGCS_VBS_SETUP]);
+ /* VGCS: MSC asks vor voice group/bcast call. */
+ conn_fsm_state_chg(ST_ACTIVE);
+ vgcs_vbs_call_start(conn, msg);
+ return;
+
+ case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST:
+ rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_RX_DT1_VGCS_VBS_ASSIGN_RQST]);
+ /* VGCS: MSC asks vor resource (channel) for voice group/bcast call. */
+ conn_fsm_state_chg(ST_ACTIVE);
+ vgcs_vbs_chan_start(conn, msg);
return;
+
default:
- break;
+ LOGPFSML(fi, LOGL_ERROR, "No support for initial BSSMAP: %s: %s\n",
+ gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type));
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ return;
}
+}
+
+static void handle_sccp_n_connect(struct osmo_fsm_inst *fi, struct osmo_scu_prim *scu_prim)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ struct msgb *msg = scu_prim->oph.msg;
+
+ /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */
+ conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED;
+
+ msg->l3h = msgb_l2(msg);
- LOGPFSML(fi, LOGL_NOTICE, "No support for N-CONNECT: %s: %s\n",
- gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type));
-refuse:
- osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
- &scu_prim->u.connect.called_addr, 0);
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ /* If (BSSMAP) user data is included, validate it before accepting the connection */
+ if (msgb_l3(msg) && msgb_l3len(msg)) {
+ if (validate_initial_user_data(fi, msg)) {
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ return;
+ }
+ }
+
+ /* accept the new conn. */
+ if (osmo_sccp_tx_conn_resp(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id,
+ &scu_prim->u.connect.called_addr, NULL, 0)) {
+ LOGPFSML(fi, LOGL_ERROR, "Cannot send SCCP CONN RESP\n");
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ return;
+ }
+
+ /* The initial user data may already be included in this N-Connect, or it may come later in a separate message.
+ * If it is already included, also go to ST_WAIT_INITIAL_USER_DATA now, so that we don't have to tend to two
+ * separate code paths doing the same thing (handling of HANDOVER_END). */
+ OSMO_ASSERT(conn_fsm_state_chg(ST_WAIT_INITIAL_USER_DATA) == 0);
+
+ /* It is usually a bad idea to continue using a fi after a state change, because the fi might terminate during
+ * the state change. In this case it is certain that the fi stays around for the initial user data. */
+ if (msgb_l3(msg) && msgb_l3len(msg)) {
+ handle_initial_user_data(fi, msg);
+ } else {
+ LOGPFSML(fi, LOGL_DEBUG, "N-Connect does not contain user data (no BSSMAP message included)\n");
+ }
}
static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -256,17 +422,11 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
struct osmo_scu_prim *scu_prim = NULL;
struct msgb *msg = NULL;
int rc;
- enum handover_result ho_result;
switch (event) {
- case GSCON_EV_A_CONN_REQ:
+ case GSCON_EV_MO_COMPL_L3:
/* RLL ESTABLISH IND with initial L3 Message */
msg = data;
- /* FIXME: Extract Mobile ID and update FSM using osmo_fsm_inst_set_id()
- * i.e. we will probably extract the mobile identity earlier, where the
- * imsi filter code is. Then we could just use it here.
- * related: OS#2969 */
-
rc = osmo_bsc_sigtran_open_conn(conn, msg);
if (rc < 0) {
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
@@ -289,11 +449,28 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
return;
}
- /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id()
- * related: OS2969 (same as above) */
+ handle_sccp_n_connect(fi, scu_prim);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
- handle_bssap_n_connect(fi, scu_prim);
+static void gscon_fsm_wait_initial_user_data(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ struct msgb *msg = data;
+ enum handover_result ho_result;
+
+ switch (event) {
+ case GSCON_EV_A_INITIAL_USER_DATA:
+ if (validate_initial_user_data(fi, msg)) {
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ return;
+ }
+ handle_initial_user_data(fi, msg);
break;
+
case GSCON_EV_HANDOVER_END:
ho_result = HO_RESULT_ERROR;
if (data)
@@ -310,12 +487,10 @@ static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
return;
}
LOG_HO(conn, LOGL_ERROR,
- "Conn is in state %s, the only accepted handover kind is inter-BSC MT\n",
+ "Conn is in state %s, the only accepted handover kind is inter-BSC incoming handover\n",
osmo_fsm_inst_state_name(conn->fi));
}
gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
- if (conn->fi->state != ST_CLEARING)
- osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, 999);
return;
default:
OSMO_ASSERT(false);
@@ -328,8 +503,18 @@ static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *da
struct gsm_subscriber_connection *conn = fi->priv;
switch (event) {
case GSCON_EV_A_CONN_CFM:
- /* MSC has confirmed the connection, we now change into the
- * active state and wait there for further operations */
+ /* MSC has confirmed the connection */
+
+ if (!conn->lchan) {
+ /* If associated lchan was released while we were waiting for the
+ confirmed connection, then instead simply drop the connection */
+ LOGPFSML(fi, LOGL_INFO,
+ "Connection confirmed but lchan was dropped previously, clearing conn\n");
+ gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ break;
+ }
+
+ /* We now change into the active state and wait there for further operations. */
conn_fsm_state_chg(ST_ACTIVE);
/* if there's user payload, forward it just like EV_MT_DTAP */
/* FIXME: Question: if there's user payload attached to the CC, forward it like EV_MT_DTAP? */
@@ -339,13 +524,6 @@ static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *da
}
}
-static void gscon_fsm_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
- struct gsm_subscriber_connection *conn = fi->priv;
- if (!conn->lchan)
- gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
-}
-
/* We're on an active subscriber connection, passing DTAP back and forth */
static void gscon_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
@@ -368,7 +546,6 @@ static void gscon_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *dat
return;
case GSCON_EV_HANDOVER_START:
- rate_ctr_inc(&conn->network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]);
/* Rely on handover_fsm timeout */
if (osmo_fsm_inst_state_chg(fi, ST_HANDOVER, 0, 0))
LOGPFSML(fi, LOGL_ERROR, "Cannot transition to HANDOVER state, discarding\n");
@@ -385,6 +562,21 @@ static void gscon_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *dat
case GSCON_EV_TX_SCCP:
gscon_sigtran_send(conn, (struct msgb *)data);
break;
+
+ case GSCON_EV_LCS_LOC_REQ_END:
+ /* On the A-interface, there is nothing to do. If there still is an lchan, the conn should stay open. If
+ * not, it is up to the MSC to send a Clear Command.
+ * On the Lb-interface, tear down the SCCP connection. */
+ lb_close_conn(conn);
+ break;
+
+ case GSCON_EV_MO_COMPL_L3:
+ /* It is possible to have an A-interface conn already established without an lchan being active, during
+ * a Perform Location Request (LCS). */
+ /* RLL ESTABLISH IND with initial L3 Message */
+ gscon_sigtran_send(conn, (struct msgb*)data);
+ break;
+
default:
OSMO_ASSERT(false);
}
@@ -454,31 +646,120 @@ static bool same_mgw_info(const struct mgcp_conn_peer *a, const struct mgcp_conn
return true;
}
+static struct mgcp_client *select_mgw(struct gsm_subscriber_connection *conn, struct gsm_lchan *for_lchan)
+{
+ struct mgcp_client_pool_member *mgcp_pmemb;
+ struct mgcp_client *mgcp_client;
+ struct gsm_bts *bts = for_lchan->ts->trx->bts;
+
+ /* If BTS is not pinned to a given MGW, let regular allocation which
+ * spreads load over available MGWs: */
+ if (bts->mgw_pool_target == -1)
+ goto pick_any;
+
+ /* BTS is pinned to an MGW, retrieve pointer to it: */
+ mgcp_pmemb = mgcp_client_pool_find_member_by_nr(conn->network->mgw.mgw_pool, bts->mgw_pool_target);
+ if (!mgcp_pmemb) {
+ if (!bts->mgw_pool_target_strict) {
+ LOGPFSML(conn->fi, LOGL_NOTICE,
+ "mgw pool-target %u not found! selecting another one.\n", bts->mgw_pool_target);
+ goto pick_any;
+ } else {
+ LOGPFSML(conn->fi, LOGL_ERROR, "mgw pool-target %u not found!\n", bts->mgw_pool_target);
+ return NULL;
+ }
+ }
+ if (mgcp_client_pool_member_is_blocked(mgcp_pmemb)) {
+ if (!bts->mgw_pool_target_strict) {
+ LOGPFSML(conn->fi, LOGL_NOTICE,
+ "mgw pool-target %u is administratively blocked! selecting another one.\n",
+ bts->mgw_pool_target);
+ goto pick_any;
+ } else {
+ LOGPFSML(conn->fi, LOGL_ERROR, "mgw pool-target %u is administratively blocked!\n",
+ bts->mgw_pool_target);
+ return NULL;
+ }
+ }
+
+ mgcp_client = mgcp_client_pool_member_get(mgcp_pmemb);
+ if (!mgcp_client) {
+ if (!bts->mgw_pool_target_strict) {
+ LOGPFSML(conn->fi, LOGL_NOTICE,
+ "mgw pool-target %u is not connected! selecting another one.\n",
+ bts->mgw_pool_target);
+ goto pick_any;
+ } else {
+ LOGPFSML(conn->fi, LOGL_ERROR, "mgw pool-target %u is not connected!\n",
+ bts->mgw_pool_target);
+ return NULL;
+ }
+ }
+ return mgcp_client;
+
+pick_any:
+ mgcp_client = mgcp_client_pool_get(conn->network->mgw.mgw_pool);
+ return mgcp_client;
+}
+
/* Make sure a conn->user_plane.mgw_endpoint is allocated with the proper mgw endpoint name. For
* SCCPlite, pass in msc_assigned_cic the CIC received upon BSSMAP Assignment Command or BSSMAP Handover
* Request form the MSC (which is only stored in conn->user_plane after success). Ignored for AoIP. */
-struct mgw_endpoint *gscon_ensure_mgw_endpoint(struct gsm_subscriber_connection *conn,
- uint16_t msc_assigned_cic)
+struct osmo_mgcpc_ep *gscon_ensure_mgw_endpoint(struct gsm_subscriber_connection *conn,
+ uint16_t msc_assigned_cic, struct gsm_lchan *for_lchan)
{
+ const char *epname;
+ struct mgcp_client *mgcp_client = NULL;
+
+ if (!conn) {
+ LOG_LCHAN(for_lchan, LOGL_ERROR, "no conn!\n");
+ return NULL;
+ }
+
if (conn->user_plane.mgw_endpoint)
return conn->user_plane.mgw_endpoint;
+ if (gscon_is_sccplite(conn) || gscon_is_aoip(conn)) {
+ /* Get MGCP client from pool */
+ mgcp_client = select_mgw(conn, for_lchan);
+ if (!mgcp_client) {
+ LOGPFSML(conn->fi, LOGL_ERROR,
+ "cannot ensure MGW endpoint -- no MGW configured, check configuration!\n");
+ return NULL;
+ }
+ }
+
if (gscon_is_sccplite(conn)) {
/* derive endpoint name from CIC on A interface side */
conn->user_plane.mgw_endpoint =
- mgw_endpoint_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT,
- conn->network->mgw.client, conn->fi->id,
- "%x@%s", msc_assigned_cic,
- mgcp_client_endpoint_domain(conn->network->mgw.client));
+ osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT,
+ mgcp_client,
+ conn->network->mgw.tdefs,
+ conn->fi->id,
+ "%x@%s", msc_assigned_cic,
+ mgcp_client_endpoint_domain(mgcp_client));
LOGPFSML(conn->fi, LOGL_DEBUG, "MGW endpoint name derived from CIC 0x%x: %s\n",
- msc_assigned_cic, mgw_endpoint_name(conn->user_plane.mgw_endpoint));
+ msc_assigned_cic, osmo_mgcpc_ep_name(conn->user_plane.mgw_endpoint));
} else if (gscon_is_aoip(conn)) {
- /* use dynamic RTPBRIDGE endpoint allocation in MGW */
+ if (is_ipa_abisip_bts(for_lchan->ts->trx->bts))
+ /* use dynamic RTPBRIDGE endpoint allocation in MGW */
+ epname = mgcp_client_rtpbridge_wildcard(mgcp_client);
+ else {
+ uint8_t i460_bit_offs;
+ if (for_lchan->ts->e1_link.e1_ts_ss == E1_SUBSLOT_FULL)
+ i460_bit_offs = 0;
+ else
+ i460_bit_offs = for_lchan->ts->e1_link.e1_ts_ss * 2;
+
+ epname = mgcp_client_e1_epname(conn, mgcp_client, for_lchan->ts->e1_link.e1_nr,
+ for_lchan->ts->e1_link.e1_ts, 16,
+ i460_bit_offs);
+ }
+
conn->user_plane.mgw_endpoint =
- mgw_endpoint_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT,
- conn->network->mgw.client, conn->fi->id,
- "%s", mgcp_client_rtpbridge_wildcard(conn->network->mgw.client));
+ osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT, mgcp_client,
+ conn->network->mgw.tdefs, conn->fi->id, "%s", epname);
} else {
LOGPFSML(conn->fi, LOGL_ERROR, "Conn is neither SCCPlite nor AoIP!?\n");
return NULL;
@@ -493,10 +774,10 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
struct osmo_fsm_inst *notify,
uint32_t event_success, uint32_t event_failure,
void *notify_data,
- struct mgwep_ci **created_ci)
+ struct osmo_mgcpc_ep_ci **created_ci)
{
int rc;
- struct mgwep_ci *ci;
+ struct osmo_mgcpc_ep_ci *ci;
struct mgcp_conn_peer mgw_info;
enum mgcp_verb verb;
@@ -513,8 +794,10 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
mgw_info = (struct mgcp_conn_peer){
.port = port,
- .call_id = conn->sccp.conn_id,
+ .call_id = conn->sccp.conn.conn_id,
.ptime = 20,
+ .x_osmo_osmux_use = conn->assignment.req.use_osmux,
+ .x_osmo_osmux_cid = conn->assignment.req.osmux_cid,
};
mgcp_pick_codec(&mgw_info, for_lchan, false);
@@ -526,24 +809,26 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
ci = conn->user_plane.mgw_endpoint_ci_msc;
if (ci) {
- const struct mgcp_conn_peer *prev_crcx_info = mgwep_ci_get_rtp_info(ci);
+ const struct mgcp_conn_peer *prev_crcx_info = osmo_mgcpc_ep_ci_get_remote_rtp_info(ci);
if (!conn->user_plane.mgw_endpoint) {
- LOGPFSML(conn->fi, LOGL_ERROR, "Internal error: conn has a CI but no endoint\n");
+ LOGPFSML(conn->fi, LOGL_ERROR, "Internal error: conn has a CI but no endpoint\n");
return false;
}
if (!prev_crcx_info) {
LOGPFSML(conn->fi, LOGL_ERROR, "There already is an MGW connection for the MSC side,"
" but it seems to be broken. Will not CRCX another one (%s)\n",
- mgwep_ci_name(ci));
+ osmo_mgcpc_ep_ci_name(ci));
return false;
}
if (same_mgw_info(&mgw_info, prev_crcx_info)) {
LOGPFSML(conn->fi, LOGL_DEBUG,
"MSC side MGW endpoint ci is already configured to %s\n",
- mgwep_ci_name(ci));
+ osmo_mgcpc_ep_ci_name(ci));
+ /* Immediately dispatch the success event */
+ osmo_fsm_inst_dispatch(notify, event_success, notify_data);
return true;
}
@@ -551,7 +836,7 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
} else
verb = MGCP_VERB_CRCX;
- gscon_ensure_mgw_endpoint(conn, for_lchan->activate.info.msc_assigned_cic);
+ gscon_ensure_mgw_endpoint(conn, for_lchan->activate.info.msc_assigned_cic, for_lchan);
if (!conn->user_plane.mgw_endpoint) {
LOGPFSML(conn->fi, LOGL_ERROR, "Unable to allocate endpoint info\n");
@@ -559,7 +844,7 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
}
if (!ci) {
- ci = mgw_endpoint_ci_add(conn->user_plane.mgw_endpoint, "to-MSC");
+ ci = osmo_mgcpc_ep_ci_add(conn->user_plane.mgw_endpoint, "to-MSC");
if (created_ci)
*created_ci = ci;
conn->user_plane.mgw_endpoint_ci_msc = ci;
@@ -569,7 +854,7 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
return false;
}
- mgw_endpoint_ci_request(ci, verb, &mgw_info, notify, event_success, event_failure, notify_data);
+ osmo_mgcpc_ep_ci_request(ci, verb, &mgw_info, notify, event_success, event_failure, notify_data);
return true;
}
@@ -578,42 +863,60 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
static const struct osmo_fsm_state gscon_fsm_states[] = {
[ST_INIT] = {
.name = "INIT",
- .in_event_mask = S(GSCON_EV_A_CONN_REQ) | S(GSCON_EV_A_CONN_IND)
- | S(GSCON_EV_HANDOVER_END),
- .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_CLEARING),
+ .in_event_mask = S(GSCON_EV_MO_COMPL_L3) | S(GSCON_EV_A_CONN_IND),
+ .out_state_mask = 0
+ | S(ST_WAIT_INITIAL_USER_DATA)
+ | S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD),
.action = gscon_fsm_init,
+ },
+ [ST_WAIT_INITIAL_USER_DATA] = {
+ .name = "WAIT_INITIAL_USER_DATA",
+ .in_event_mask = 0
+ | S(GSCON_EV_A_INITIAL_USER_DATA)
+ | S(GSCON_EV_HANDOVER_END)
+ ,
+ .out_state_mask = S(ST_WAIT_CC) | S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD),
+ .action = gscon_fsm_wait_initial_user_data,
},
[ST_WAIT_CC] = {
.name = "WAIT_CC",
.in_event_mask = S(GSCON_EV_A_CONN_CFM),
- .out_state_mask = S(ST_ACTIVE),
+ .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD),
.action = gscon_fsm_wait_cc,
},
[ST_ACTIVE] = {
.name = "ACTIVE",
.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_ASSIGNMENT_START) |
- S(GSCON_EV_HANDOVER_START),
- .out_state_mask = S(ST_CLEARING) | S(ST_ASSIGNMENT) |
+ S(GSCON_EV_HANDOVER_START)
+ | S(GSCON_EV_LCS_LOC_REQ_END)
+ | S(GSCON_EV_MO_COMPL_L3)
+ ,
+ .out_state_mask = S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD) | S(ST_ASSIGNMENT) |
S(ST_HANDOVER),
- .onenter = gscon_fsm_active_onenter,
.action = gscon_fsm_active,
},
[ST_ASSIGNMENT] = {
.name = "ASSIGNMENT",
.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_ASSIGNMENT_END),
- .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING),
+ .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD),
.action = gscon_fsm_assignment,
},
[ST_HANDOVER] = {
.name = "HANDOVER",
.in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_HANDOVER_END),
- .out_state_mask = S(ST_ACTIVE) | S(ST_CLEARING),
+ .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD),
.action = gscon_fsm_handover,
},
- [ST_CLEARING] = {
- .name = "CLEARING",
- /* dead end state */
- },
+ [ST_WAIT_CLEAR_CMD] = {
+ .name = "WAIT_CLEAR_CMD",
+ .onenter = gscon_fsm_wait_clear_cmd_onenter,
+ .out_state_mask = S(ST_WAIT_SCCP_RLSD),
+ },
+ [ST_WAIT_SCCP_RLSD] = {
+ .name = "WAIT_SCCP_RLSD",
+ .onenter = gscon_fsm_wait_sccp_rlsd_onenter,
+ .in_event_mask = S(GSCON_EV_HANDOVER_END),
+ },
};
void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan *new_lchan)
@@ -621,6 +924,19 @@ void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct g
/* On release, do not receive release events that look like the primary lchan is gone. */
struct gsm_lchan *old_lchan = conn->lchan;
+ OSMO_ASSERT(new_lchan);
+
+ if (old_lchan == new_lchan)
+ return;
+
+ if (!old_lchan)
+ LOGPFSML(conn->fi, LOGL_DEBUG, "setting primary lchan for this conn to %s\n",
+ new_lchan->fi? osmo_fsm_inst_name(new_lchan->fi) : gsm_lchan_name(new_lchan));
+ else
+ LOGPFSML(conn->fi, LOGL_DEBUG, "primary lchan for this conn changes from %s to %s\n",
+ old_lchan->fi? osmo_fsm_inst_name(old_lchan->fi) : gsm_lchan_name(old_lchan),
+ new_lchan->fi? osmo_fsm_inst_name(new_lchan->fi) : gsm_lchan_name(new_lchan));
+
conn->lchan = new_lchan;
conn->lchan->conn = conn;
@@ -628,7 +944,7 @@ void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct g
osmo_fsm_inst_dispatch(conn->lchan->fi_rtp, LCHAN_RTP_EV_ESTABLISHED, 0);
if (old_lchan && (old_lchan != new_lchan))
- gscon_release_lchan(conn, old_lchan, false, false, 0);
+ gscon_release_lchan(conn, old_lchan, false, false, GSM48_RR_CAUSE_NORMAL);
}
void gscon_lchan_releasing(struct gsm_subscriber_connection *conn, struct gsm_lchan *lchan)
@@ -645,14 +961,26 @@ void gscon_lchan_releasing(struct gsm_subscriber_connection *conn, struct gsm_lc
if (conn->ho.fi)
osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_LCHAN_ERROR, lchan);
}
+ if (conn->vgcs_chan.new_lchan == lchan) {
+ if (conn->vgcs_chan.fi)
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.fi, VGCS_EV_LCHAN_ERROR, lchan);
+ }
if (conn->lchan == lchan) {
lchan_forget_conn(conn->lchan);
conn->lchan = NULL;
}
- if (!conn->lchan) {
- if (conn->fi->state != ST_CLEARING)
- osmo_fsm_inst_state_chg(conn->fi, ST_CLEARING, 60, 999);
- gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ /* If the conn has no lchan anymore, it was released by the BTS and needs to Clear towards MSC.
+ * However, if a Location Request is still busy, do not send Clear Request. */
+ if (!conn->lchan && !conn->lcs.loc_req) {
+ switch (conn->fi->state) {
+ case ST_WAIT_CC:
+ /* The SCCP connection was not yet confirmed by a CC, the BSSAP is not fully established
+ yet so we cannot release it. First wait for the CC, and release in gscon_fsm_wait_cc(). */
+ break;
+ default:
+ gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ break;
+ }
}
}
@@ -673,6 +1001,10 @@ void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan
conn->ho.new_lchan = NULL;
detach_label = "ho.new_lchan";
}
+ if (conn->vgcs_chan.new_lchan == lchan) {
+ conn->vgcs_chan.new_lchan = NULL;
+ detach_label = "vgcs.new_lchan";
+ }
if (conn->lchan == lchan) {
conn->lchan = NULL;
detach_label = "primary lchan";
@@ -688,54 +1020,63 @@ void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan
osmo_fsm_inst_name(conn->fi), detach_label);
}
- if (conn->fi->state != ST_CLEARING
- && !conn->lchan
+ if (!conn->lchan
&& !conn->ho.new_lchan
- && !conn->assignment.new_lchan)
+ && !conn->assignment.new_lchan
+ && !conn->vgcs_chan.new_lchan
+ && !conn->lcs.loc_req)
gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
}
static void gscon_forget_mgw_endpoint(struct gsm_subscriber_connection *conn)
{
+ struct mgcp_client *mgcp_client;
+
+ /* Put MGCP client back into MGW pool */
+ mgcp_client = osmo_mgcpc_ep_client(conn->user_plane.mgw_endpoint);
+ mgcp_client_pool_put(mgcp_client);
+
+ /* Be sure that the endpoint CI we are maintaining in user_plane
+ * is also removed from the other locations as well. */
+ gscon_forget_mgw_endpoint_ci(conn, conn->user_plane.mgw_endpoint_ci_msc);
+
conn->user_plane.mgw_endpoint = NULL;
conn->user_plane.mgw_endpoint_ci_msc = NULL;
+ conn->ho.created_ci_for_msc = NULL;
lchan_forget_mgw_endpoint(conn->lchan);
lchan_forget_mgw_endpoint(conn->assignment.new_lchan);
+ lchan_forget_mgw_endpoint(conn->vgcs_chan.new_lchan);
lchan_forget_mgw_endpoint(conn->ho.new_lchan);
}
-void gscon_forget_mgw_endpoint_ci(struct gsm_subscriber_connection *conn, struct mgwep_ci *ci)
+void gscon_forget_mgw_endpoint_ci(struct gsm_subscriber_connection *conn, struct osmo_mgcpc_ep_ci *ci)
{
- if (ci != conn->user_plane.mgw_endpoint_ci_msc)
- return;
- conn->user_plane.mgw_endpoint_ci_msc = NULL;
+ if (conn->ho.created_ci_for_msc == ci)
+ conn->ho.created_ci_for_msc = NULL;
+
+ if (conn->user_plane.mgw_endpoint_ci_msc == ci)
+ conn->user_plane.mgw_endpoint_ci_msc = NULL;
+
+ if (conn->assignment.created_ci_for_msc == ci)
+ conn->assignment.created_ci_for_msc = NULL;
}
static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
+ const struct tlv_parsed *tp;
+ struct osmo_mobile_identity mi_imsi;
/* Regular allstate event processing */
switch (event) {
case GSCON_EV_A_CLEAR_CMD:
- /* MSC tells us to cleanly shut down */
- if (conn->fi->state != ST_CLEARING)
- osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, 999);
- LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) after BSSMAP Clear Command\n");
- gscon_release_lchans(conn, true);
- /* FIXME: Release all terestrial resources in ST_CLEARING */
- /* According to 3GPP 48.008 3.1.9.1. "The BSS need not wait for the radio channel
- * release to be completed or for the guard timer to expire before returning the
- * CLEAR COMPLETE message" */
-
- /* Close MGCP connections */
- mgw_endpoint_clear(conn->user_plane.mgw_endpoint);
-
- gscon_sigtran_send(conn, gsm0808_create_clear_complete());
+ OSMO_ASSERT(data);
+ conn->clear_cause = *(const enum gsm0808_cause *)data;
+ conn_fsm_state_chg(ST_WAIT_SCCP_RLSD);
break;
case GSCON_EV_A_DISC_IND:
- /* MSC or SIGTRAN network has hard-released SCCP connection,
- * terminate the FSM now. */
+ /* MSC or SIGTRAN network has hard-released SCCP connection, terminate the FSM now.
+ * Cleanup is done in gscon_pre_term() and gscon_cleanup(). */
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data);
break;
case GSCON_EV_FORGET_MGW_ENDPOINT:
@@ -745,14 +1086,43 @@ static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *d
if (conn->lchan) {
conn->lchan->release.in_error = true;
conn->lchan->release.rsl_error_cause = data ? *(uint8_t*)data : RSL_ERR_IE_ERROR;
+ conn->lchan->release.rr_cause =
+ bsc_gsm48_rr_cause_from_rsl_cause(conn->lchan->release.rsl_error_cause);
}
- gscon_bssmap_clear(conn, GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
+ /* Request BSSMAP Clear, but do not abort an ongoing Location Request */
+ if (!conn->lcs.loc_req)
+ gscon_bssmap_clear(conn, GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
break;
case GSCON_EV_MGW_MDCX_RESP_MSC:
LOGPFSML(fi, LOGL_DEBUG, "Rx MDCX of MSC side (LCLS?)\n");
break;
case GSCON_EV_LCLS_FAIL:
break;
+ case GSCON_EV_A_COMMON_ID_IND:
+ OSMO_ASSERT(data);
+ tp = data;
+ if (osmo_mobile_identity_decode(&mi_imsi, TLVP_VAL(tp, GSM0808_IE_IMSI), TLVP_LEN(tp, GSM0808_IE_IMSI), false)
+ || mi_imsi.type != GSM_MI_TYPE_IMSI) {
+ LOGPFSML(fi, LOGL_ERROR, "CommonID: could not parse IMSI\n");
+ return;
+ }
+ if (!conn->bsub)
+ conn->bsub = bsc_subscr_find_or_create_by_imsi(conn->network->bsc_subscribers, mi_imsi.imsi,
+ BSUB_USE_CONN);
+ else {
+ /* we already have a bsc_subscr associated; maybe that subscriber has no IMSI yet? */
+ if (!conn->bsub->imsi[0])
+ bsc_subscr_set_imsi(conn->bsub, mi_imsi.imsi);
+ }
+ if (TLVP_PRESENT(tp, GSM0808_IE_LAST_USED_EUTRAN_PLMN_ID)) {
+ conn->fast_return.allowed = true; /* Always allowed for CSFB */
+ conn->fast_return.last_eutran_plmn_valid = true;
+ osmo_plmn_from_bcd(TLVP_VAL(tp, GSM0808_IE_LAST_USED_EUTRAN_PLMN_ID), &conn->fast_return.last_eutran_plmn);
+ LOGPFSML(fi, LOGL_DEBUG, "subscr comes from E-UTRAN PLMN %s\n",
+ osmo_plmn_name(&conn->fast_return.last_eutran_plmn));
+ }
+ gscon_update_id(conn);
+ break;
default:
OSMO_ASSERT(false);
break;
@@ -765,19 +1135,27 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau
lchan_forget_conn(conn->lchan);
lchan_forget_conn(conn->assignment.new_lchan);
+ lchan_forget_conn(conn->vgcs_chan.new_lchan);
lchan_forget_conn(conn->ho.new_lchan);
+ lb_close_conn(conn);
+
if (conn->sccp.state != SUBSCR_SCCP_ST_NONE) {
LOGPFSML(fi, LOGL_DEBUG, "Disconnecting SCCP\n");
struct bsc_msc_data *msc = conn->sccp.msc;
/* FIXME: include a proper cause value / error message? */
- osmo_sccp_tx_disconn(msc->a.sccp_user, conn->sccp.conn_id, &msc->a.bsc_addr, 0);
+ osmo_sccp_tx_disconn(msc->a.sccp_user, conn->sccp.conn.conn_id, &msc->a.bsc_addr, 0);
conn->sccp.state = SUBSCR_SCCP_ST_NONE;
}
+ if (conn->sccp.conn.conn_id != SCCP_CONN_ID_UNSET && conn->sccp.msc) {
+ struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(conn->sccp.msc->a.sccp);
+ bsc_sccp_inst_unregister_gscon(bsc_sccp, &conn->sccp.conn);
+ conn->sccp.conn.conn_id = SCCP_CONN_ID_UNSET;
+ }
if (conn->bsub) {
LOGPFSML(fi, LOGL_DEBUG, "Putting bsc_subscr\n");
- bsc_subscr_put(conn->bsub);
+ bsc_subscr_put(conn->bsub, BSUB_USE_CONN);
conn->bsub = NULL;
}
@@ -788,8 +1166,21 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau
static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct gsm_subscriber_connection *conn = fi->priv;
+ struct mgcp_client *mgcp_client;
+
+ /* Put MGCP client back into MGW pool */
+ mgcp_client = osmo_mgcpc_ep_client(conn->user_plane.mgw_endpoint);
+ mgcp_client_pool_put(mgcp_client);
- mgw_endpoint_clear(conn->user_plane.mgw_endpoint);
+ osmo_mgcpc_ep_clear(conn->user_plane.mgw_endpoint);
+ conn->user_plane.mgw_endpoint = NULL;
+ conn->user_plane.mgw_endpoint_ci_msc = NULL;
+
+ if (conn->ho.fi)
+ osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_CONN_RELEASING, NULL);
+
+ if (conn->lcs.loc_req)
+ osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_CONN_CLEAR, NULL);
if (conn->lcls.fi) {
/* request termination of LCLS FSM */
@@ -797,13 +1188,19 @@ static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause ca
conn->lcls.fi = NULL;
}
+ if (conn->vgcs_call.fi)
+ osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_CLEANUP, NULL);
+
+ if (conn->vgcs_chan.fi)
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.fi, VGCS_EV_CLEANUP, NULL);
+
LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) because this conn is terminating\n");
- gscon_release_lchans(conn, true);
+ gscon_release_lchans(conn, true, bsc_gsm48_rr_cause_from_gsm0808_cause(conn->clear_cause));
/* drop pending messages */
gscon_dtap_queue_flush(conn, 0);
- penalty_timers_free(&conn->hodec2.penalty_timers);
+ penalty_timers_clear(&conn->hodec2.penalty_timers, NULL);
}
static int gscon_timer_cb(struct osmo_fsm_inst *fi)
@@ -811,8 +1208,8 @@ static int gscon_timer_cb(struct osmo_fsm_inst *fi)
struct gsm_subscriber_connection *conn = fi->priv;
switch (fi->T) {
- case 993210:
- gscon_release_lchan(conn, conn->lchan, true, true, RSL_ERR_INTERWORKING);
+ case -3210:
+ gscon_release_lchan(conn, conn->lchan, true, true, GSM48_RR_CAUSE_ABNORMAL_TIMER);
/* MSC has not responded/confirmed connection with CC, this
* could indicate a bad SCCP connection. We now inform the the
@@ -825,11 +1222,12 @@ static int gscon_timer_cb(struct osmo_fsm_inst *fi)
* gscon_cleanup() above) */
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
break;
- case 999:
+ case -4:
/* The MSC has sent a BSSMAP Clear Command, we acknowledged that, but the conn was never
* disconnected. */
- LOGPFSML(fi, LOGL_ERROR, "Long after a BSSMAP Clear Command, the conn is still not"
- " released. For sanity, discarding this conn now.\n");
+ LOGPFSML(fi, LOGL_ERROR, "Long after expecting %s, the conn is still not"
+ " released. For sanity, discarding this conn now.\n",
+ fi->state == ST_WAIT_CLEAR_CMD ? "BSSMAP Clear Command" : "SCCP RLSD");
a_reset_conn_fail(conn->sccp.msc);
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
break;
@@ -844,7 +1242,8 @@ static struct osmo_fsm gscon_fsm = {
.name = "SUBSCR_CONN",
.states = gscon_fsm_states,
.num_states = ARRAY_SIZE(gscon_fsm_states),
- .allstate_event_mask = S(GSCON_EV_A_DISC_IND) | S(GSCON_EV_A_CLEAR_CMD) | S(GSCON_EV_RSL_CONN_FAIL) |
+ .allstate_event_mask = S(GSCON_EV_A_DISC_IND) | S(GSCON_EV_A_CLEAR_CMD) | S(GSCON_EV_A_COMMON_ID_IND) |
+ S(GSCON_EV_RSL_CONN_FAIL) |
S(GSCON_EV_LCLS_FAIL) |
S(GSCON_EV_FORGET_LCHAN) |
S(GSCON_EV_FORGET_MGW_ENDPOINT),
@@ -856,7 +1255,7 @@ static struct osmo_fsm gscon_fsm = {
.event_names = gscon_fsm_event_names,
};
-void bsc_subscr_conn_fsm_init()
+static __attribute__((constructor)) void bsc_subscr_conn_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&gscon_fsm) == 0);
OSMO_ASSERT(osmo_fsm_register(&lcls_fsm) == 0);
@@ -873,8 +1272,11 @@ struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *ne
conn->network = net;
INIT_LLIST_HEAD(&conn->dtap_queue);
- /* BTW, penalty timers will be initialized on-demand. */
- conn->sccp.conn_id = -1;
+ INIT_LLIST_HEAD(&conn->hodec2.penalty_timers);
+ bscp_sccp_conn_node_init(&conn->sccp.conn, conn);
+ bscp_sccp_conn_node_init(&conn->lcs.lb.conn, conn);
+ /* Default clear cause (on RR translates to GSM48_RR_CAUSE_ABNORMAL_UNSPEC) */
+ conn->clear_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE;
/* don't allocate from 'conn' context, as gscon_cleanup() will call talloc_free(conn) before
* libosmocore will call talloc_free(conn->fi), i.e. avoid use-after-free during cleanup */
@@ -951,17 +1353,28 @@ static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, en
* fire after a lchan_release call and before the S_CHALLOC_FREED
* is called. Check if a conn is set before proceeding.
*/
- if (!lchan->conn)
+ if (!lchan->conn) {
+ msgb_free(msg);
return;
+ }
switch (rllr_ind) {
case BSC_RLLR_IND_EST_CONF:
- rsl_data_request(msg, OBSC_LINKID_CB(msg));
+ rsl_data_request(msg, link_id);
break;
case BSC_RLLR_IND_REL_IND:
+ bsc_sapi_n_reject(lchan->conn, RSL_LINK_ID2DLCI(link_id),
+ GSM0808_CAUSE_MS_NOT_EQUIPPED);
+ msgb_free(msg);
+ break;
case BSC_RLLR_IND_ERR_IND:
case BSC_RLLR_IND_TIMEOUT:
- bsc_sapi_n_reject(lchan->conn, OBSC_LINKID_CB(msg));
+ bsc_sapi_n_reject(lchan->conn, RSL_LINK_ID2DLCI(link_id),
+ GSM0808_CAUSE_BSS_NOT_EQUIPPED);
+ msgb_free(msg);
+ break;
+ default:
+ LOGPLCHAN(lchan, DRLL, LOGL_NOTICE, "Received unknown rllr_ind %u\n", rllr_ind);
msgb_free(msg);
break;
}
@@ -973,7 +1386,6 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn,
{
uint8_t sapi;
int rc;
- struct msgb *resp = NULL;
if (!conn->lchan) {
LOGP(DMSC, LOGL_ERROR,
@@ -986,7 +1398,6 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn,
sapi = link_id & 0x7;
msg->lchan = conn->lchan;
- msg->dst = msg->lchan->ts->trx->rsl_link;
/* If we are on a TCH and need to submit a SMS (on SAPI=3) we need to use the SACH */
if (allow_sacch && sapi != 0) {
@@ -1002,7 +1413,7 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn,
rc = rll_establish(msg->lchan, sapi, rll_ind_cb, msg);
if (rc) {
msgb_free(msg);
- bsc_sapi_n_reject(conn, link_id);
+ bsc_sapi_n_reject(conn, RSL_LINK_ID2DLCI(link_id), GSM0808_CAUSE_BSS_NOT_EQUIPPED);
goto failed_to_send;
}
return;
@@ -1016,9 +1427,7 @@ static void gsm0808_send_rsl_dtap(struct gsm_subscriber_connection *conn,
failed_to_send:
LOGPFSML(conn->fi, LOGL_ERROR, "Tx BSSMAP CLEAR REQUEST to MSC\n");
- resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE);
- gscon_sigtran_send(conn, resp);
- osmo_fsm_inst_state_chg(conn->fi, ST_ACTIVE, 0, 0);
+ gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
}
void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn,
@@ -1036,8 +1445,9 @@ void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn,
/* Compose an FSM ID, if possible from the current subscriber information */
void gscon_update_id(struct gsm_subscriber_connection *conn)
{
- osmo_fsm_inst_update_id_f(conn->fi, "conn%u%s%s",
- conn->sccp.conn_id,
+ osmo_fsm_inst_update_id_f(conn->fi, "msc%u-conn%u%s%s",
+ conn->sccp.msc ? conn->sccp.msc->nr : UINT_MAX,
+ conn->sccp.conn.conn_id,
conn->bsub? "_" : "",
conn->bsub? bsc_subscr_id(conn->bsub) : "");
}
@@ -1047,14 +1457,7 @@ bool gscon_is_aoip(struct gsm_subscriber_connection *conn)
if (!conn || !conn->sccp.msc)
return false;
- switch (conn->sccp.msc->a.asp_proto) {
- case OSMO_SS7_ASP_PROT_SUA:
- case OSMO_SS7_ASP_PROT_M3UA:
- return true;
-
- default:
- return false;
- }
+ return msc_is_aoip(conn->sccp.msc);
}
bool gscon_is_sccplite(struct gsm_subscriber_connection *conn)
@@ -1062,11 +1465,5 @@ bool gscon_is_sccplite(struct gsm_subscriber_connection *conn)
if (!conn || !conn->sccp.msc)
return false;
- switch (conn->sccp.msc->a.asp_proto) {
- case OSMO_SS7_ASP_PROT_IPA:
- return true;
-
- default:
- return false;
- }
+ return msc_is_sccplite(conn->sccp.msc);
}
diff --git a/src/osmo-bsc/bsc_subscriber.c b/src/osmo-bsc/bsc_subscriber.c
index 2541883d5..1f69d442d 100644
--- a/src/osmo-bsc/bsc_subscriber.c
+++ b/src/osmo-bsc/bsc_subscriber.c
@@ -30,51 +30,189 @@
#include <osmocom/core/logging.h>
#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/debug.h>
-static struct bsc_subscr *bsc_subscr_alloc(struct llist_head *list)
+static void bsc_subscr_free(struct bsc_subscr *bsub);
+
+static int bsub_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, const char *file, int line)
+{
+ struct bsc_subscr *bsub = e->use_count->talloc_object;
+ int32_t total;
+ int level;
+
+ if (!e->use)
+ return -EINVAL;
+
+ total = osmo_use_count_total(&bsub->use_count);
+
+ if (total == 0
+ || (total == 1 && old_use_count == 0 && e->count == 1))
+ level = LOGL_INFO;
+ else
+ level = LOGL_DEBUG;
+
+ LOGPSRC(DREF, level, file, line, "%s: %s %s: now used by %s\n",
+ bsc_subscr_name(bsub),
+ (e->count - old_use_count) > 0? "+" : "-", e->use,
+ osmo_use_count_to_str_c(OTC_SELECT, &bsub->use_count));
+
+ if (e->count < 0)
+ return -ERANGE;
+
+ if (total == 0)
+ bsc_subscr_free(bsub);
+ return 0;
+}
+
+struct bsc_subscr_store *bsc_subscr_store_alloc(void *ctx)
+{
+ struct bsc_subscr_store *bsubst;
+
+ bsubst = talloc_zero(ctx, struct bsc_subscr_store);
+ if (!bsubst)
+ return NULL;
+
+ INIT_LLIST_HEAD(&bsubst->bsub_list);
+ return bsubst;
+}
+
+static struct bsc_subscr *bsc_subscr_alloc(struct bsc_subscr_store *bsubst)
{
struct bsc_subscr *bsub;
- bsub = talloc_zero(list, struct bsc_subscr);
+ bsub = talloc_zero(bsubst, struct bsc_subscr);
if (!bsub)
return NULL;
- llist_add_tail(&bsub->entry, list);
+ bsub->store = bsubst;
+ bsub->tmsi = GSM_RESERVED_TMSI;
+ bsub->use_count = (struct osmo_use_count){
+ .talloc_object = bsub,
+ .use_cb = bsub_use_cb,
+ };
+ INIT_LLIST_HEAD(&bsub->active_paging_requests);
+
+ llist_add_tail(&bsub->entry, &bsubst->bsub_list);
return bsub;
}
-struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list,
- const char *imsi)
+struct bsc_subscr *bsc_subscr_find_by_imsi(struct bsc_subscr_store *bsubst,
+ const char *imsi,
+ const char *use_token)
{
struct bsc_subscr *bsub;
if (!imsi || !*imsi)
return NULL;
- llist_for_each_entry(bsub, list, entry) {
- if (!strcmp(bsub->imsi, imsi))
- return bsc_subscr_get(bsub);
+ llist_for_each_entry(bsub, &bsubst->bsub_list, entry) {
+ if (!strcmp(bsub->imsi, imsi)) {
+ bsc_subscr_get(bsub, use_token);
+ return bsub;
+ }
}
return NULL;
}
-struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list,
- uint32_t tmsi)
+static struct bsc_subscr *bsc_subscr_find_by_imei(struct bsc_subscr_store *bsubst,
+ const char *imei,
+ const char *use_token)
{
struct bsc_subscr *bsub;
+ if (!imei || !*imei)
+ return NULL;
+
+ llist_for_each_entry(bsub, &bsubst->bsub_list, entry) {
+ if (!strcmp(bsub->imei, imei)) {
+ bsc_subscr_get(bsub, use_token);
+ return bsub;
+ }
+ }
+ return NULL;
+}
+
+static struct bsc_subscr *bsc_subscr_find_by_tmsi(struct bsc_subscr_store *bsubst,
+ uint32_t tmsi,
+ const char *use_token)
+{
+ const struct rb_node *node = bsubst->bsub_tree_tmsi.rb_node;
+ struct bsc_subscr *bsub;
+
if (tmsi == GSM_RESERVED_TMSI)
return NULL;
- llist_for_each_entry(bsub, list, entry) {
- if (bsub->tmsi == tmsi)
- return bsc_subscr_get(bsub);
+ while (node) {
+ bsub = container_of(node, struct bsc_subscr, node_tmsi);
+ if (tmsi < bsub->tmsi)
+ node = node->rb_left;
+ else if (tmsi > bsub->tmsi)
+ node = node->rb_right;
+ else {
+ bsc_subscr_get(bsub, use_token);
+ return bsub;
+ }
}
+
return NULL;
}
+static int bsc_subscr_store_insert_bsub_tmsi(struct bsc_subscr *bsub)
+{
+ struct bsc_subscr_store *bsubst = bsub->store;
+ struct rb_node **n = &(bsubst->bsub_tree_tmsi.rb_node);
+ struct rb_node *parent = NULL;
+
+ OSMO_ASSERT(bsub->tmsi != GSM_RESERVED_TMSI);
+
+ while (*n) {
+ struct bsc_subscr *it;
+
+ it = container_of(*n, struct bsc_subscr, node_tmsi);
+
+ parent = *n;
+ if (bsub->tmsi < it->tmsi) {
+ n = &((*n)->rb_left);
+ } else if (bsub->tmsi > it->tmsi) {
+ n = &((*n)->rb_right);
+ } else {
+ LOGP(DMSC, LOGL_ERROR, "Trying to reserve already reserved tmsi %u\n", bsub->tmsi);
+ return -EEXIST;
+ }
+ }
+
+ rb_link_node(&bsub->node_tmsi, parent, n);
+ rb_insert_color(&bsub->node_tmsi, &bsubst->bsub_tree_tmsi);
+ return 0;
+}
+
+int bsc_subscr_set_tmsi(struct bsc_subscr *bsub, uint32_t tmsi)
+{
+ int rc = 0;
+
+ if (!bsub)
+ return -EINVAL;
+
+ if (bsub->tmsi == tmsi)
+ return 0;
+
+ /* bsub was already inserted, remove and re-insert with new tmsi */
+ if (bsub->tmsi != GSM_RESERVED_TMSI)
+ rb_erase(&bsub->node_tmsi, &bsub->store->bsub_tree_tmsi);
+
+ bsub->tmsi = tmsi;
+
+ /* If new tmsi is set, insert bsub into rbtree: */
+ if (bsub->tmsi != GSM_RESERVED_TMSI) {
+ if ((rc = bsc_subscr_store_insert_bsub_tmsi(bsub)) < 0)
+ bsub->tmsi = GSM_RESERVED_TMSI;
+ }
+
+ return rc;
+}
+
void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi)
{
if (!bsub)
@@ -82,85 +220,127 @@ void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi)
osmo_strlcpy(bsub->imsi, imsi, sizeof(bsub->imsi));
}
-struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list,
- const char *imsi)
+void bsc_subscr_set_imei(struct bsc_subscr *bsub, const char *imei)
+{
+ if (!bsub)
+ return;
+ osmo_strlcpy(bsub->imei, imei, sizeof(bsub->imei));
+}
+
+struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct bsc_subscr_store *bsubst,
+ const char *imsi,
+ const char *use_token)
{
struct bsc_subscr *bsub;
- bsub = bsc_subscr_find_by_imsi(list, imsi);
+ bsub = bsc_subscr_find_by_imsi(bsubst, imsi, use_token);
if (bsub)
return bsub;
- bsub = bsc_subscr_alloc(list);
+ bsub = bsc_subscr_alloc(bsubst);
+ if (!bsub)
+ return NULL;
bsc_subscr_set_imsi(bsub, imsi);
- return bsc_subscr_get(bsub);
+ bsc_subscr_get(bsub, use_token);
+ return bsub;
}
-struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list,
- uint32_t tmsi)
+static struct bsc_subscr *bsc_subscr_find_or_create_by_imei(struct bsc_subscr_store *bsubst,
+ const char *imei,
+ const char *use_token)
{
struct bsc_subscr *bsub;
- bsub = bsc_subscr_find_by_tmsi(list, tmsi);
+ bsub = bsc_subscr_find_by_imei(bsubst, imei, use_token);
if (bsub)
return bsub;
- bsub = bsc_subscr_alloc(list);
- bsub->tmsi = tmsi;
- return bsc_subscr_get(bsub);
+ bsub = bsc_subscr_alloc(bsubst);
+ if (!bsub)
+ return NULL;
+ bsc_subscr_set_imei(bsub, imei);
+ bsc_subscr_get(bsub, use_token);
+ return bsub;
}
-const char *bsc_subscr_name(struct bsc_subscr *bsub)
+struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct bsc_subscr_store *bsubst,
+ uint32_t tmsi,
+ const char *use_token)
{
- static char buf[32];
+ struct bsc_subscr *bsub;
+ bsub = bsc_subscr_find_by_tmsi(bsubst, tmsi, use_token);
+ if (bsub)
+ return bsub;
+ bsub = bsc_subscr_alloc(bsubst);
if (!bsub)
- return "unknown";
+ return NULL;
+ if (bsc_subscr_set_tmsi(bsub, tmsi) < 0) {
+ bsc_subscr_free(bsub);
+ return NULL;
+ }
+ bsc_subscr_get(bsub, use_token);
+ return bsub;
+}
+
+struct bsc_subscr *bsc_subscr_find_or_create_by_mi(struct bsc_subscr_store *bsubst, const struct osmo_mobile_identity *mi,
+ const char *use_token)
+{
+ if (!mi)
+ return NULL;
+ switch (mi->type) {
+ case GSM_MI_TYPE_IMSI:
+ return bsc_subscr_find_or_create_by_imsi(bsubst, mi->imsi, use_token);
+ case GSM_MI_TYPE_IMEI:
+ return bsc_subscr_find_or_create_by_imei(bsubst, mi->imei, use_token);
+ case GSM_MI_TYPE_TMSI:
+ return bsc_subscr_find_or_create_by_tmsi(bsubst, mi->tmsi, use_token);
+ default:
+ return NULL;
+ }
+}
+
+static int bsc_subscr_name_buf(char *buf, size_t buflen, struct bsc_subscr *bsub)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_PRINTF(sb, "subscr");
+ if (!bsub) {
+ OSMO_STRBUF_PRINTF(sb, "-null");
+ return sb.chars_needed;
+ }
if (bsub->imsi[0])
- snprintf(buf, sizeof(buf), "IMSI:%s", bsub->imsi);
- else
- snprintf(buf, sizeof(buf), "TMSI:0x%08x", bsub->tmsi);
- return buf;
+ OSMO_STRBUF_PRINTF(sb, "-IMSI-%s", bsub->imsi);
+ else if (bsub->imei[0])
+ OSMO_STRBUF_PRINTF(sb, "-IMEI-%s", bsub->imei);
+ if (bsub->tmsi != GSM_RESERVED_TMSI)
+ OSMO_STRBUF_PRINTF(sb, "-TMSI-0x%08x", bsub->tmsi);
+ return sb.chars_needed;
+}
+
+static char *bsc_subscr_name_c(void *ctx, struct bsc_subscr *bsub)
+{
+ OSMO_NAME_C_IMPL(ctx, 64, "ERROR", bsc_subscr_name_buf, bsub)
+}
+
+const char *bsc_subscr_name(struct bsc_subscr *bsub)
+{
+ return bsc_subscr_name_c(OTC_SELECT, bsub);
}
/* Like bsc_subscr_name() but returns only characters approved by osmo_identifier_valid(), useful for
* osmo_fsm_inst IDs. */
const char *bsc_subscr_id(struct bsc_subscr *bsub)
{
- static char buf[32];
- if (!bsub)
- return "unknown";
- if (bsub->imsi[0])
- snprintf(buf, sizeof(buf), "IMSI%s", bsub->imsi);
- else
- snprintf(buf, sizeof(buf), "TMSI%08x", bsub->tmsi);
- return buf;
+ return bsc_subscr_name(bsub);
}
static void bsc_subscr_free(struct bsc_subscr *bsub)
{
+ OSMO_ASSERT(llist_empty(&bsub->active_paging_requests));
+
+ if (bsub->tmsi != GSM_RESERVED_TMSI)
+ rb_erase(&bsub->node_tmsi, &bsub->store->bsub_tree_tmsi);
+
llist_del(&bsub->entry);
talloc_free(bsub);
}
-struct bsc_subscr *_bsc_subscr_get(struct bsc_subscr *bsub,
- const char *file, int line)
-{
- OSMO_ASSERT(bsub->use_count < INT_MAX);
- bsub->use_count++;
- LOGPSRC(DREF, LOGL_DEBUG, file, line,
- "BSC subscr %s usage increases to: %d\n",
- bsc_subscr_name(bsub), bsub->use_count);
- return bsub;
-}
-
-struct bsc_subscr *_bsc_subscr_put(struct bsc_subscr *bsub,
- const char *file, int line)
-{
- bsub->use_count--;
- LOGPSRC(DREF, bsub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR,
- file, line,
- "BSC subscr %s usage decreases to: %d\n",
- bsc_subscr_name(bsub), bsub->use_count);
- if (bsub->use_count <= 0)
- bsc_subscr_free(bsub);
- return NULL;
-}
+#define BSUB_USE_LOG_FILTER "log_filter"
void log_set_filter_bsc_subscr(struct log_target *target,
struct bsc_subscr *bsc_subscr)
@@ -169,13 +349,52 @@ void log_set_filter_bsc_subscr(struct log_target *target,
/* free the old data */
if (*fsub) {
- bsc_subscr_put(*fsub);
+ bsc_subscr_put(*fsub, BSUB_USE_LOG_FILTER);
*fsub = NULL;
}
if (bsc_subscr) {
target->filter_map |= (1 << LOG_FLT_BSC_SUBSCR);
- *fsub = bsc_subscr_get(bsc_subscr);
+ *fsub = bsc_subscr;
+ bsc_subscr_get(*fsub, BSUB_USE_LOG_FILTER);
} else
target->filter_map &= ~(1 << LOG_FLT_BSC_SUBSCR);
}
+
+void bsc_subscr_add_active_paging_request(struct bsc_subscr *bsub, struct gsm_paging_request *req)
+{
+ bsub->active_paging_requests_len++;
+ bsc_subscr_get(bsub, BSUB_USE_PAGING_REQUEST);
+ llist_add_tail(&req->bsub_entry, &bsub->active_paging_requests);
+}
+
+void bsc_subscr_remove_active_paging_request(struct bsc_subscr *bsub, struct gsm_paging_request *req)
+{
+ llist_del(&req->bsub_entry);
+ bsub->active_paging_requests_len--;
+ bsc_subscr_put(bsub, BSUB_USE_PAGING_REQUEST);
+}
+
+void bsc_subscr_remove_active_paging_request_all(struct bsc_subscr *bsub)
+{
+ /* Avoid accessing bsub after reaching 0 active_paging_request_len,
+ * since it could be freed during put(): */
+ unsigned remaining = bsub->active_paging_requests_len;
+ while (remaining > 0) {
+ struct gsm_paging_request *req;
+ req = llist_first_entry(&bsub->active_paging_requests,
+ struct gsm_paging_request, bsub_entry);
+ bsc_subscr_remove_active_paging_request(bsub, req);
+ remaining--;
+ }
+}
+
+struct gsm_paging_request *bsc_subscr_find_req_by_bts(const struct bsc_subscr *bsub, const struct gsm_bts *bts)
+{
+ struct gsm_paging_request *req;
+ llist_for_each_entry(req, &bsub->active_paging_requests, bsub_entry) {
+ if (req->bts == bts)
+ return req;
+ }
+ return NULL;
+}
diff --git a/src/osmo-bsc/bsc_vty.c b/src/osmo-bsc/bsc_vty.c
index 032305e4b..8690a47c3 100644
--- a/src/osmo-bsc/bsc_vty.c
+++ b/src/osmo-bsc/bsc_vty.c
@@ -1,4 +1,4 @@
-/* OpenBSC interface to quagga VTY */
+/* OsmoBSC interface to quagga VTY */
/* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
*
@@ -22,6 +22,8 @@
#include <unistd.h>
#include <time.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
#include <osmocom/vty/vty.h>
@@ -29,103 +31,49 @@
#include <osmocom/vty/stats.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/misc.h>
-#include <osmocom/gsm/protocol/gsm_04_08.h>
-#include <osmocom/gsm/gsm0502.h>
+#include <osmocom/vty/tdef_vty.h>
#include <osmocom/ctrl/control_if.h>
-#include <osmocom/gsm/gsm48.h>
-#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/gsm23236.h>
+#include <osmocom/gsm/gsm0502.h>
-#include <arpa/inet.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
+#include <osmocom/mgcp_client/mgcp_client_pool.h>
-#include <osmocom/core/linuxlist.h>
-#include <osmocom/core/socket.h>
+#include <osmocom/bsc/vty.h>
#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/abis/e1_input.h>
#include <osmocom/bsc/abis_nm.h>
#include <osmocom/bsc/abis_om2000.h>
-#include <osmocom/core/utils.h>
-#include <osmocom/gsm/gsm_utils.h>
-#include <osmocom/gsm/abis_nm.h>
#include <osmocom/bsc/chan_alloc.h>
-#include <osmocom/bsc/meas_rep.h>
-#include <osmocom/bsc/vty.h>
-#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/bsc/bts_setup_ramp.h>
#include <osmocom/bsc/system_information.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/ipaccess.h>
#include <osmocom/bsc/abis_rsl.h>
-#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/osmo_bsc_rf.h>
-#include <osmocom/bsc/pcu_if.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/handover_cfg.h>
#include <osmocom/bsc/handover_vty.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
-#include <osmocom/bsc/acc_ramp.h>
#include <osmocom/bsc/meas_feed.h>
-#include <osmocom/bsc/neighbor_ident.h>
-#include <osmocom/bsc/handover.h>
-#include <osmocom/bsc/gsm_timers.h>
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_select.h>
-#include <osmocom/bsc/gsm_timers.h>
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/assignment_fsm.h>
+#include <osmocom/bsc/bssmap_reset.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/lchan.h>
+#include <osmocom/bsc/pcu_if.h>
#include <inttypes.h>
#include "../../bscconfig.h"
-#define BTS_NR_STR "BTS Number\n"
-#define TRX_NR_STR "TRX Number\n"
-#define TS_NR_STR "Timeslot Number\n"
-#define SS_NR_STR "Sub-slot Number\n"
-#define LCHAN_NR_STR "Logical Channel Number\n"
-#define BTS_TRX_STR BTS_NR_STR TRX_NR_STR
-#define BTS_TRX_TS_STR BTS_TRX_STR TS_NR_STR
-#define BTS_TRX_TS_LCHAN_STR BTS_TRX_TS_STR LCHAN_NR_STR
-#define BTS_NR_TRX_TS_STR2 \
- "BTS for manual command\n" BTS_NR_STR \
- "TRX for manual command\n" TRX_NR_STR \
- "Timeslot for manual command\n" TS_NR_STR
-#define BTS_NR_TRX_TS_SS_STR2 \
- BTS_NR_TRX_TS_STR2 \
- "Sub-slot for manual command\n" SS_NR_STR
-
-/* FIXME: this should go to some common file */
-static const struct value_string gprs_ns_timer_strs[] = {
- { 0, "tns-block" },
- { 1, "tns-block-retries" },
- { 2, "tns-reset" },
- { 3, "tns-reset-retries" },
- { 4, "tns-test" },
- { 5, "tns-alive" },
- { 6, "tns-alive-retries" },
- { 0, NULL }
-};
-
-static const struct value_string gprs_bssgp_cfg_strs[] = {
- { 0, "blocking-timer" },
- { 1, "blocking-retries" },
- { 2, "unblocking-retries" },
- { 3, "reset-timer" },
- { 4, "reset-retries" },
- { 5, "suspend-timer" },
- { 6, "suspend-retries" },
- { 7, "resume-timer" },
- { 8, "resume-retries" },
- { 9, "capability-update-timer" },
- { 10, "capability-update-retries" },
- { 0, NULL }
-};
-
-static const struct value_string bts_neigh_mode_strs[] = {
- { NL_MODE_AUTOMATIC, "automatic" },
- { NL_MODE_MANUAL, "manual" },
- { NL_MODE_MANUAL_SI5SEP, "manual-si5" },
- { 0, NULL }
-};
+#define X(x) (1 << x)
const struct value_string bts_loc_fix_names[] = {
{ BTS_LOC_FIX_INVALID, "invalid" },
@@ -134,30 +82,12 @@ const struct value_string bts_loc_fix_names[] = {
{ 0, NULL }
};
-struct cmd_node net_node = {
+static struct cmd_node net_node = {
GSMNET_NODE,
"%s(config-net)# ",
1,
};
-struct cmd_node bts_node = {
- BTS_NODE,
- "%s(config-net-bts)# ",
- 1,
-};
-
-struct cmd_node trx_node = {
- TRX_NODE,
- "%s(config-net-bts-trx)# ",
- 1,
-};
-
-struct cmd_node ts_node = {
- TS_NODE,
- "%s(config-net-bts-trx-ts)# ",
- 1,
-};
-
static struct gsm_network *vty_global_gsm_network = NULL;
struct gsm_network *gsmnet_from_vty(struct vty *v)
@@ -172,12 +102,40 @@ struct gsm_network *gsmnet_from_vty(struct vty *v)
return vty_global_gsm_network;
}
-static int dummy_config_write(struct vty *v)
+int dummy_config_write(struct vty *v)
{
return CMD_SUCCESS;
}
-static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
+/* resolve a gsm_bts_trx_ts basd on the given numeric identifiers */
+static struct gsm_bts_trx_ts *vty_get_ts(struct vty *vty, const char *bts_str, const char *trx_str,
+ const char *ts_str)
+{
+ int bts_nr = atoi(bts_str);
+ int trx_nr = atoi(trx_str);
+ int ts_nr = atoi(ts_str);
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+
+ bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+ return NULL;
+ }
+
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ if (!trx) {
+ vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE);
+ return NULL;
+ }
+
+ ts = &trx->ts[ts_nr];
+
+ return ts;
+}
+
+void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
{
vty_out(vty,"Oper '%s', Admin '%s', Avail '%s'%s",
abis_nm_opstate_name(nms->operational),
@@ -185,7 +143,7 @@ static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
abis_nm_avail_name(nms->availability), VTY_NEWLINE);
}
-static void dump_pchan_load_vty(struct vty *vty, char *prefix,
+void dump_pchan_load_vty(struct vty *vty, char *prefix,
const struct pchan_load *pl)
{
int i;
@@ -253,14 +211,17 @@ static void net_dump_vty(struct vty *vty, struct gsm_network *net)
dump_pchan_load_vty(vty, " ", &pl);
/* show rf */
- if (net->bsc_data)
+ if (net->rf_ctrl)
vty_out(vty, " Last RF Command: %s%s",
- net->bsc_data->rf_ctrl->last_state_command,
+ net->rf_ctrl->last_state_command,
VTY_NEWLINE);
- if (net->bsc_data)
+ if (net->rf_ctrl)
vty_out(vty, " Last RF Lock Command: %s%s",
- net->bsc_data->rf_ctrl->last_rf_lock_ctrl_command,
+ net->rf_ctrl->last_rf_lock_ctrl_command,
VTY_NEWLINE);
+
+ if (net->pcu_sock_path)
+ vty_out(vty, " PCU Socket Path: %s%s", net->pcu_sock_path, VTY_NEWLINE);
}
DEFUN(bsc_show_net, bsc_show_net_cmd, "show network",
@@ -271,259 +232,9 @@ DEFUN(bsc_show_net, bsc_show_net_cmd, "show network",
return CMD_SUCCESS;
}
-
-static void e1isl_dump_vty(struct vty *vty, struct e1inp_sign_link *e1l)
-{
- struct e1inp_line *line;
-
- if (!e1l) {
- vty_out(vty, " None%s", VTY_NEWLINE);
- return;
- }
-
- line = e1l->ts->line;
-
- vty_out(vty, " E1 Line %u, Type %s: Timeslot %u, Mode %s%s",
- line->num, line->driver->name, e1l->ts->num,
- e1inp_signtype_name(e1l->type), VTY_NEWLINE);
- vty_out(vty, " E1 TEI %u, SAPI %u%s",
- e1l->tei, e1l->sapi, VTY_NEWLINE);
-}
-
-/*! Dump the IP addresses and ports of the input signal link's timeslot.
- * This only makes sense for links connected with ipaccess.
- * Example output: "(r=10.1.42.1:55416<->l=10.1.42.123:3003)" */
-static void e1isl_dump_vty_tcp(struct vty *vty, const struct e1inp_sign_link *e1l)
-{
- if (e1l) {
- char *name = osmo_sock_get_name(NULL, e1l->ts->driver.ipaccess.fd.fd);
- vty_out(vty, "%s", name);
- talloc_free(name);
- }
- vty_out(vty, "%s", VTY_NEWLINE);
-}
-
-static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv)
-{
- int count = 0;
- int i;
- for (i = 0; i < 1024; i++) {
- if (!bitvec_get_bit_pos(bv, i))
- continue;
- vty_out(vty, " %u", i);
- count ++;
- }
- if (!count)
- vty_out(vty, " (none)");
- else
- vty_out(vty, " (%d)", count);
-}
-
-static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts)
-{
- unsigned int i;
- bool no_features = true;
- vty_out(vty, " Features:%s", VTY_NEWLINE);
-
- for (i = 0; i < _NUM_BTS_FEAT; i++) {
- if (osmo_bts_has_feature(&bts->features, i)) {
- vty_out(vty, " %03u ", i);
- vty_out(vty, "%-40s%s", osmo_bts_feature_name(i), VTY_NEWLINE);
- no_features = false;
- }
- }
-
- if (no_features)
- vty_out(vty, " (not available)%s", VTY_NEWLINE);
-}
-
-static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
-{
- struct pchan_load pl;
- unsigned long long sec;
- struct gsm_bts_trx *trx;
- int ts_hopping_total;
- int ts_non_hopping_total;
-
- vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, "
- "BSIC %u (NCC=%u, BCC=%u) and %u TRX%s",
- bts->nr, btstype2str(bts->type), gsm_band_name(bts->band),
- bts->cell_identity,
- bts->location_area_code, bts->bsic,
- bts->bsic >> 3, bts->bsic & 7,
- bts->num_trx, VTY_NEWLINE);
- vty_out(vty, " Description: %s%s",
- bts->description ? bts->description : "(null)", VTY_NEWLINE);
-
- vty_out(vty, " ARFCNs:");
- ts_hopping_total = 0;
- ts_non_hopping_total = 0;
- llist_for_each_entry(trx, &bts->trx_list, list) {
- int ts_nr;
- int ts_hopping = 0;
- int ts_non_hopping = 0;
- for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
- struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
- if (ts->hopping.enabled)
- ts_hopping++;
- else
- ts_non_hopping++;
- }
-
- if (ts_non_hopping)
- vty_out(vty, " %u", trx->arfcn);
- ts_hopping_total += ts_hopping;
- ts_non_hopping_total += ts_non_hopping;
- }
- if (ts_hopping_total) {
- if (ts_non_hopping_total)
- vty_out(vty, " / Hopping on %d of %d timeslots",
- ts_hopping_total, ts_hopping_total + ts_non_hopping_total);
- else
- vty_out(vty, " Hopping on all %d timeslots", ts_hopping_total);
- }
- vty_out(vty, "%s", VTY_NEWLINE);
-
- if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH))
- vty_out(vty, " PCU version %s connected%s", bts->pcu_version,
- VTY_NEWLINE);
- vty_out(vty, " MS Max power: %u dBm%s", bts->ms_max_power, VTY_NEWLINE);
- vty_out(vty, " Minimum Rx Level for Access: %i dBm%s",
- rxlev2dbm(bts->si_common.cell_sel_par.rxlev_acc_min),
- VTY_NEWLINE);
- vty_out(vty, " Cell Reselection Hysteresis: %u dBm%s",
- bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
- vty_out(vty, " Access Control Class ramping: %senabled%s",
- acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "not ", VTY_NEWLINE);
- if (acc_ramp_is_enabled(&bts->acc_ramp)) {
- if (!acc_ramp_step_interval_is_dynamic(&bts->acc_ramp))
- vty_out(vty, " Access Control Class ramping step interval: %u seconds%s",
- acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE);
- else
- vty_out(vty, " Access Control Class ramping step interval: dynamic%s", VTY_NEWLINE);
- vty_out(vty, " enabling %u Access Control Class%s per ramping step%s",
- acc_ramp_get_step_size(&bts->acc_ramp),
- acc_ramp_get_step_size(&bts->acc_ramp) > 1 ? "es" : "", VTY_NEWLINE);
- }
- vty_out(vty, " RACH TX-Integer: %u%s", bts->si_common.rach_control.tx_integer,
- VTY_NEWLINE);
- vty_out(vty, " RACH Max transmissions: %u%s",
- rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
- VTY_NEWLINE);
- if (bts->si_common.rach_control.cell_bar)
- vty_out(vty, " CELL IS BARRED%s", VTY_NEWLINE);
- if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED)
- vty_out(vty, " Uplink DTX: %s%s",
- (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ?
- "enabled" : "forced", VTY_NEWLINE);
- else
- vty_out(vty, " Uplink DTX: not enabled%s", VTY_NEWLINE);
- vty_out(vty, " Downlink DTX: %senabled%s", bts->dtxd ? "" : "not ",
- VTY_NEWLINE);
- vty_out(vty, " Channel Description Attachment: %s%s",
- (bts->si_common.chan_desc.att) ? "yes" : "no", VTY_NEWLINE);
- vty_out(vty, " Channel Description BS-PA-MFRMS: %u%s",
- bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE);
- vty_out(vty, " Channel Description BS-AG_BLKS-RES: %u%s",
- bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE);
- vty_out(vty, " System Information present: 0x%08x, static: 0x%08x%s",
- bts->si_valid, bts->si_mode_static, VTY_NEWLINE);
- vty_out(vty, " Early Classmark Sending: 2G %s, 3G %s%s%s",
- bts->early_classmark_allowed ? "allowed" : "forbidden",
- bts->early_classmark_allowed_3g ? "allowed" : "forbidden",
- bts->early_classmark_allowed_3g && !bts->early_classmark_allowed ?
- " (forbidden by 2G bit)" : "",
- VTY_NEWLINE);
- if (bts->pcu_sock_path)
- vty_out(vty, " PCU Socket Path: %s%s", bts->pcu_sock_path, VTY_NEWLINE);
- if (is_ipaccess_bts(bts))
- vty_out(vty, " Unit ID: %u/%u/0, OML Stream ID 0x%02x%s",
- bts->ip_access.site_id, bts->ip_access.bts_id,
- bts->oml_tei, VTY_NEWLINE);
- else if (bts->type == GSM_BTS_TYPE_NOKIA_SITE)
- vty_out(vty, " Skip Reset: %d%s",
- bts->nokia.skip_reset, VTY_NEWLINE);
- vty_out(vty, " NM State: ");
- net_dump_nmstate(vty, &bts->mo.nm_state);
- vty_out(vty, " Site Mgr NM State: ");
- net_dump_nmstate(vty, &bts->site_mgr.mo.nm_state);
-
- if (bts->gprs.mode != BTS_GPRS_NONE) {
- vty_out(vty, " GPRS NSE: ");
- net_dump_nmstate(vty, &bts->gprs.nse.mo.nm_state);
- vty_out(vty, " GPRS CELL: ");
- net_dump_nmstate(vty, &bts->gprs.cell.mo.nm_state);
- vty_out(vty, " GPRS NSVC0: ");
- net_dump_nmstate(vty, &bts->gprs.nsvc[0].mo.nm_state);
- vty_out(vty, " GPRS NSVC1: ");
- net_dump_nmstate(vty, &bts->gprs.nsvc[1].mo.nm_state);
- } else
- vty_out(vty, " GPRS: not configured%s", VTY_NEWLINE);
-
- vty_out(vty, " Paging: %u pending requests, %u free slots%s",
- paging_pending_requests_nr(bts),
- bts->paging.available_slots, VTY_NEWLINE);
- if (is_ipaccess_bts(bts)) {
- vty_out(vty, " OML Link: ");
- e1isl_dump_vty_tcp(vty, bts->oml_link);
- vty_out(vty, " OML Link state: %s", get_model_oml_status(bts));
- sec = bts_uptime(bts);
- if (sec)
- vty_out(vty, " %llu days %llu hours %llu min. %llu sec.",
- OSMO_SEC2DAY(sec), OSMO_SEC2HRS(sec), OSMO_SEC2MIN(sec), sec % 60);
- vty_out(vty, "%s", VTY_NEWLINE);
- } else {
- vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE);
- e1isl_dump_vty(vty, bts->oml_link);
- }
-
- vty_out(vty, " Neighbor Cells: ");
- switch (bts->neigh_list_manual_mode) {
- default:
- case NL_MODE_AUTOMATIC:
- vty_out(vty, "Automatic");
- /* generate_bcch_chan_list() should populate si_common.neigh_list */
- break;
- case NL_MODE_MANUAL:
- vty_out(vty, "Manual");
- break;
- case NL_MODE_MANUAL_SI5SEP:
- vty_out(vty, "Manual/separate SI5");
- break;
- }
- vty_out(vty, ", ARFCNs:");
- vty_out_neigh_list(vty, &bts->si_common.neigh_list);
- if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) {
- vty_out(vty, " SI5:");
- vty_out_neigh_list(vty, &bts->si_common.si5_neigh_list);
- }
- vty_out(vty, "%s", VTY_NEWLINE);
-
- /* FIXME: chan_desc */
- memset(&pl, 0, sizeof(pl));
- bts_chan_load(&pl, bts);
- vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE);
- dump_pchan_load_vty(vty, " ", &pl);
-
- vty_out(vty, " Channel Requests : %"PRIu64" total, %"PRIu64" no channel%s",
- bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL].current,
- bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL].current,
- VTY_NEWLINE);
- vty_out(vty, " Channel Failures : %"PRIu64" rf_failures, %"PRIu64" rll failures%s",
- bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL].current,
- bts->bts_ctrs->ctr[BTS_CTR_CHAN_RLL_ERR].current,
- VTY_NEWLINE);
- vty_out(vty, " BTS failures : %"PRIu64" OML, %"PRIu64" RSL%s",
- bts->bts_ctrs->ctr[BTS_CTR_BTS_OML_FAIL].current,
- bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL].current,
- VTY_NEWLINE);
-
- bts_dump_vty_features(vty, bts);
-}
-
DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]",
SHOW_STR "Display information about a BTS\n"
- "BTS number")
+ "BTS number\n")
{
struct gsm_network *net = gsmnet_from_vty(vty);
int bts_nr;
@@ -546,483 +257,107 @@ DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]",
return CMD_SUCCESS;
}
-DEFUN(show_rejected_bts, show_rejected_bts_cmd, "show rejected-bts",
- SHOW_STR "Display recently rejected BTS devices\n")
+DEFUN(show_bts_brief, show_bts_brief_cmd, "show bts brief",
+ SHOW_STR "Display information about a BTS\n"
+ "Display availability status of all BTS\n")
{
- struct gsm_bts_rejected *pos;
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
- /* empty list */
- struct llist_head *rejected = &gsmnet_from_vty(vty)->bts_rejected;
- if (llist_empty(rejected)) {
- vty_out(vty, "No BTS has been rejected.%s", VTY_NEWLINE);
- return CMD_SUCCESS;
+ /* Print OML state of BTSs. */
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ vty_out(vty, "BTS %d:", bts->nr);
+ bts_dump_vty_oml_link_state(vty, bts);
}
- /* table head */
- vty_out(vty, "Date Site ID BTS ID IP%s", VTY_NEWLINE);
- vty_out(vty, "------------------- ------- ------ ---------------%s", VTY_NEWLINE);
-
- /* table body */
- llist_for_each_entry(pos, rejected, list) {
- /* timestamp formatted like: "2018-10-24 15:04:52" */
- char buf[20];
- strftime(buf, sizeof(buf), "%F %T", localtime(&pos->time));
-
- vty_out(vty, "%s %7u %6u %15s%s", buf, pos->site_id, pos->bts_id, pos->ip, VTY_NEWLINE);
- }
return CMD_SUCCESS;
}
-/* utility functions */
-static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line,
- const char *ts, const char *ss)
-{
- e1_link->e1_nr = atoi(line);
- e1_link->e1_ts = atoi(ts);
- if (!strcmp(ss, "full"))
- e1_link->e1_ts_ss = 255;
- else
- e1_link->e1_ts_ss = atoi(ss);
-}
-
-static void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link,
- const char *prefix)
-{
- if (!e1_link->e1_ts)
- return;
-
- if (e1_link->e1_ts_ss == 255)
- vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s",
- prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE);
- else
- vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s",
- prefix, e1_link->e1_nr, e1_link->e1_ts,
- e1_link->e1_ts_ss, VTY_NEWLINE);
-}
-
-
-static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts)
-{
- vty_out(vty, " timeslot %u%s", ts->nr, VTY_NEWLINE);
- if (ts->tsc != -1)
- vty_out(vty, " training_sequence_code %u%s", ts->tsc, VTY_NEWLINE);
- if (ts->pchan_from_config != GSM_PCHAN_NONE)
- vty_out(vty, " phys_chan_config %s%s",
- gsm_pchan_name(ts->pchan_from_config), VTY_NEWLINE);
- vty_out(vty, " hopping enabled %u%s",
- ts->hopping.enabled, VTY_NEWLINE);
- if (ts->hopping.enabled) {
- unsigned int i;
- vty_out(vty, " hopping sequence-number %u%s",
- ts->hopping.hsn, VTY_NEWLINE);
- vty_out(vty, " hopping maio %u%s",
- ts->hopping.maio, VTY_NEWLINE);
- for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
- if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i))
- continue;
- vty_out(vty, " hopping arfcn add %u%s",
- i, VTY_NEWLINE);
- }
- }
- config_write_e1_link(vty, &ts->e1_link, " ");
-
- if (ts->trx->bts->model->config_write_ts)
- ts->trx->bts->model->config_write_ts(vty, ts);
-}
-
-static void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx)
-{
- int i;
-
- vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE);
- if (trx->description)
- vty_out(vty, " description %s%s", trx->description,
- VTY_NEWLINE);
- vty_out(vty, " rf_locked %u%s",
- trx->mo.nm_state.administrative == NM_STATE_LOCKED ? 1 : 0,
- VTY_NEWLINE);
- vty_out(vty, " arfcn %u%s", trx->arfcn, VTY_NEWLINE);
- vty_out(vty, " nominal power %u%s", trx->nominal_power, VTY_NEWLINE);
- vty_out(vty, " max_power_red %u%s", trx->max_power_red, VTY_NEWLINE);
- config_write_e1_link(vty, &trx->rsl_e1_link, " rsl ");
- vty_out(vty, " rsl e1 tei %u%s", trx->rsl_tei, VTY_NEWLINE);
-
- if (trx->bts->model->config_write_trx)
- trx->bts->model->config_write_trx(vty, trx);
-
- for (i = 0; i < TRX_NR_TS; i++)
- config_write_ts_single(vty, &trx->ts[i]);
-}
-
-static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts)
-{
- unsigned int i;
- vty_out(vty, " gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode),
- VTY_NEWLINE);
- if (bts->gprs.mode == BTS_GPRS_NONE)
- return;
-
- vty_out(vty, " gprs 11bit_rach_support_for_egprs %u%s",
- bts->gprs.supports_egprs_11bit_rach, VTY_NEWLINE);
-
- vty_out(vty, " gprs routing area %u%s", bts->gprs.rac,
- VTY_NEWLINE);
- vty_out(vty, " gprs network-control-order nc%u%s",
- bts->gprs.net_ctrl_ord, VTY_NEWLINE);
- if (!bts->gprs.ctrl_ack_type_use_block)
- vty_out(vty, " gprs control-ack-type-rach%s", VTY_NEWLINE);
- vty_out(vty, " gprs cell bvci %u%s", bts->gprs.cell.bvci,
- VTY_NEWLINE);
- for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++)
- vty_out(vty, " gprs cell timer %s %u%s",
- get_value_string(gprs_bssgp_cfg_strs, i),
- bts->gprs.cell.timer[i], VTY_NEWLINE);
- vty_out(vty, " gprs nsei %u%s", bts->gprs.nse.nsei,
- VTY_NEWLINE);
- for (i = 0; i < ARRAY_SIZE(bts->gprs.nse.timer); i++)
- vty_out(vty, " gprs ns timer %s %u%s",
- get_value_string(gprs_ns_timer_strs, i),
- bts->gprs.nse.timer[i], VTY_NEWLINE);
- for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
- struct gsm_bts_gprs_nsvc *nsvc =
- &bts->gprs.nsvc[i];
- struct in_addr ia;
-
- ia.s_addr = htonl(nsvc->remote_ip);
- vty_out(vty, " gprs nsvc %u nsvci %u%s", i,
- nsvc->nsvci, VTY_NEWLINE);
- vty_out(vty, " gprs nsvc %u local udp port %u%s", i,
- nsvc->local_port, VTY_NEWLINE);
- vty_out(vty, " gprs nsvc %u remote udp port %u%s", i,
- nsvc->remote_port, VTY_NEWLINE);
- vty_out(vty, " gprs nsvc %u remote ip %s%s", i,
- inet_ntoa(ia), VTY_NEWLINE);
- }
-}
-
-/* Write the model data if there is one */
-static void config_write_bts_model(struct vty *vty, struct gsm_bts *bts)
-{
- struct gsm_bts_trx *trx;
-
- if (!bts->model)
- return;
-
- if (bts->model->config_write_bts)
- bts->model->config_write_bts(vty, bts);
-
- llist_for_each_entry(trx, &bts->trx_list, list)
- config_write_trx_single(vty, trx);
-}
-
-static void write_amr_modes(struct vty *vty, const char *prefix,
- const char *name, struct amr_mode *modes, int num)
-{
- int i;
-
- vty_out(vty, " %s threshold %s", prefix, name);
- for (i = 0; i < num - 1; i++)
- vty_out(vty, " %d", modes[i].threshold);
- vty_out(vty, "%s", VTY_NEWLINE);
- vty_out(vty, " %s hysteresis %s", prefix, name);
- for (i = 0; i < num - 1; i++)
- vty_out(vty, " %d", modes[i].hysteresis);
- vty_out(vty, "%s", VTY_NEWLINE);
-}
-
-static void config_write_bts_amr(struct vty *vty, struct gsm_bts *bts,
- struct amr_multirate_conf *mr, int full)
+DEFUN(show_bts_fail_rep, show_bts_fail_rep_cmd, "show bts <0-255> fail-rep [reset]",
+ SHOW_STR "Display information about a BTS\n"
+ "BTS number\n" "OML failure reports\n"
+ "Clear the list of failure reports after showing them\n")
{
- struct gsm48_multi_rate_conf *mr_conf;
- const char *prefix = (full) ? "amr tch-f" : "amr tch-h";
- int i, num;
-
- if (!(mr->gsm48_ie[1]))
- return;
-
- mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct bts_oml_fail_rep *entry;
+ struct gsm_bts *bts;
+ int bts_nr;
- num = 0;
- vty_out(vty, " %s modes", prefix);
- for (i = 0; i < ((full) ? 8 : 6); i++) {
- if ((mr->gsm48_ie[1] & (1 << i))) {
- vty_out(vty, " %d", i);
- num++;
- }
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
}
- vty_out(vty, "%s", VTY_NEWLINE);
- if (num > 4)
- num = 4;
- if (num > 1) {
- write_amr_modes(vty, prefix, "ms", mr->ms_mode, num);
- write_amr_modes(vty, prefix, "bts", mr->bts_mode, num);
- }
- vty_out(vty, " %s start-mode ", prefix);
- if (mr_conf->icmi) {
- num = 0;
- for (i = 0; i < ((full) ? 8 : 6) && num < 4; i++) {
- if ((mr->gsm48_ie[1] & (1 << i)))
- num++;
- if (mr_conf->smod == num - 1) {
- vty_out(vty, "%d%s", num, VTY_NEWLINE);
- break;
- }
- }
- } else
- vty_out(vty, "auto%s", VTY_NEWLINE);
-}
-static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
-{
- int i;
- uint8_t tmp;
-
- vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE);
- vty_out(vty, " type %s%s", btstype2str(bts->type), VTY_NEWLINE);
- if (bts->description)
- vty_out(vty, " description %s%s", bts->description, VTY_NEWLINE);
- vty_out(vty, " band %s%s", gsm_band_name(bts->band), VTY_NEWLINE);
- vty_out(vty, " cell_identity %u%s", bts->cell_identity, VTY_NEWLINE);
- vty_out(vty, " location_area_code %u%s", bts->location_area_code,
- VTY_NEWLINE);
- if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED)
- vty_out(vty, " dtx uplink%s%s",
- (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ? "" : " force",
- VTY_NEWLINE);
- if (bts->dtxd)
- vty_out(vty, " dtx downlink%s", VTY_NEWLINE);
- vty_out(vty, " base_station_id_code %u%s", bts->bsic, VTY_NEWLINE);
- vty_out(vty, " ms max power %u%s", bts->ms_max_power, VTY_NEWLINE);
- vty_out(vty, " cell reselection hysteresis %u%s",
- bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
- vty_out(vty, " rxlev access min %u%s",
- bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE);
-
- if (bts->si_common.cell_ro_sel_par.present) {
- struct gsm48_si_selection_params *sp;
- sp = &bts->si_common.cell_ro_sel_par;
-
- if (sp->cbq)
- vty_out(vty, " cell bar qualify %u%s",
- sp->cbq, VTY_NEWLINE);
-
- if (sp->cell_resel_off)
- vty_out(vty, " cell reselection offset %u%s",
- sp->cell_resel_off*2, VTY_NEWLINE);
-
- if (sp->temp_offs == 7)
- vty_out(vty, " temporary offset infinite%s",
- VTY_NEWLINE);
- else if (sp->temp_offs)
- vty_out(vty, " temporary offset %u%s",
- sp->temp_offs*10, VTY_NEWLINE);
-
- if (sp->penalty_time == 31)
- vty_out(vty, " penalty time reserved%s",
- VTY_NEWLINE);
- else if (sp->penalty_time)
- vty_out(vty, " penalty time %u%s",
- (sp->penalty_time*20)+20, VTY_NEWLINE);
+ bts = gsm_bts_num(net, bts_nr);
+ if (llist_empty(&bts->oml_fail_rep)) {
+ vty_out(vty, "No failure reports received.%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
}
- if (gsm_bts_get_radio_link_timeout(bts) < 0)
- vty_out(vty, " radio-link-timeout infinite%s", VTY_NEWLINE);
- else
- vty_out(vty, " radio-link-timeout %d%s",
- gsm_bts_get_radio_link_timeout(bts), VTY_NEWLINE);
-
- vty_out(vty, " channel allocator %s%s",
- bts->chan_alloc_reverse ? "descending" : "ascending",
- VTY_NEWLINE);
- vty_out(vty, " rach tx integer %u%s",
- bts->si_common.rach_control.tx_integer, VTY_NEWLINE);
- vty_out(vty, " rach max transmission %u%s",
- rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
- VTY_NEWLINE);
+ llist_for_each_entry(entry, &bts->oml_fail_rep, list) {
+ struct nm_fail_rep_signal_data *sd;
+ char timestamp[20]; /* format like 2020-03-23 14:24:00 */
+ enum abis_nm_pcause_type pcause;
+ enum abis_mm_event_causes cause;
- vty_out(vty, " channel-descrption attach %u%s",
- bts->si_common.chan_desc.att, VTY_NEWLINE);
- vty_out(vty, " channel-descrption bs-pa-mfrms %u%s",
- bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE);
- vty_out(vty, " channel-descrption bs-ag-blks-res %u%s",
- bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE);
-
- if (bts->rach_b_thresh != -1)
- vty_out(vty, " rach nm busy threshold %u%s",
- bts->rach_b_thresh, VTY_NEWLINE);
- if (bts->rach_ldavg_slots != -1)
- vty_out(vty, " rach nm load average %u%s",
- bts->rach_ldavg_slots, VTY_NEWLINE);
- if (bts->si_common.rach_control.cell_bar)
- vty_out(vty, " cell barred 1%s", VTY_NEWLINE);
- if ((bts->si_common.rach_control.t2 & 0x4) == 0)
- vty_out(vty, " rach emergency call allowed 1%s", VTY_NEWLINE);
- if ((bts->si_common.rach_control.t3) != 0)
- for (i = 0; i < 8; i++)
- if (bts->si_common.rach_control.t3 & (0x1 << i))
- vty_out(vty, " rach access-control-class %d barred%s", i, VTY_NEWLINE);
- if ((bts->si_common.rach_control.t2 & 0xfb) != 0)
- for (i = 0; i < 8; i++)
- if ((i != 2) && (bts->si_common.rach_control.t2 & (0x1 << i)))
- vty_out(vty, " rach access-control-class %d barred%s", i+8, VTY_NEWLINE);
- vty_out(vty, " %saccess-control-class-ramping%s", acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "no ", VTY_NEWLINE);
- if (!acc_ramp_step_interval_is_dynamic(&bts->acc_ramp)) {
- vty_out(vty, " access-control-class-ramping-step-interval %u%s",
- acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE);
- } else {
- vty_out(vty, " access-control-class-ramping-step-interval dynamic%s", VTY_NEWLINE);
- }
- vty_out(vty, " access-control-class-ramping-step-size %u%s", acc_ramp_get_step_size(&bts->acc_ramp),
- VTY_NEWLINE);
- if (!bts->si_unused_send_empty)
- vty_out(vty, " no system-information unused-send-empty%s", VTY_NEWLINE);
- for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) {
- if (bts->si_mode_static & (1 << i)) {
- vty_out(vty, " system-information %s mode static%s",
- get_value_string(osmo_sitype_strs, i), VTY_NEWLINE);
- vty_out(vty, " system-information %s static %s%s",
- get_value_string(osmo_sitype_strs, i),
- osmo_hexdump_nospc(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN),
- VTY_NEWLINE);
- }
- }
- vty_out(vty, " early-classmark-sending %s%s",
- bts->early_classmark_allowed ? "allowed" : "forbidden", VTY_NEWLINE);
- vty_out(vty, " early-classmark-sending-3g %s%s",
- bts->early_classmark_allowed_3g ? "allowed" : "forbidden", VTY_NEWLINE);
- switch (bts->type) {
- case GSM_BTS_TYPE_NANOBTS:
- case GSM_BTS_TYPE_OSMOBTS:
- vty_out(vty, " ip.access unit_id %u %u%s",
- bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE);
- if (bts->ip_access.rsl_ip) {
- struct in_addr ia;
- ia.s_addr = htonl(bts->ip_access.rsl_ip);
- vty_out(vty, " ip.access rsl-ip %s%s", inet_ntoa(ia),
- VTY_NEWLINE);
+ strftime(timestamp, sizeof(timestamp), "%F %T", localtime(&entry->time));
+ sd = abis_nm_fail_evt_rep_parse(entry->mb, bts);
+ if (!sd) {
+ vty_out(vty, "[%s] (failed to parse report)%s", timestamp, VTY_NEWLINE);
+ continue;
}
- vty_out(vty, " oml ip.access stream_id %u line %u%s",
- bts->oml_tei, bts->oml_e1_link.e1_nr, VTY_NEWLINE);
- break;
- case GSM_BTS_TYPE_NOKIA_SITE:
- vty_out(vty, " nokia_site skip-reset %d%s", bts->nokia.skip_reset, VTY_NEWLINE);
- vty_out(vty, " nokia_site no-local-rel-conf %d%s",
- bts->nokia.no_loc_rel_cnf, VTY_NEWLINE);
- vty_out(vty, " nokia_site bts-reset-timer %d%s", bts->nokia.bts_reset_timer_cnf, VTY_NEWLINE);
- /* fall through: Nokia requires "oml e1" parameters also */
- default:
- config_write_e1_link(vty, &bts->oml_e1_link, " oml ");
- vty_out(vty, " oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE);
- break;
- }
+ pcause = sd->parsed.probable_cause[0];
+ cause = osmo_load16be(sd->parsed.probable_cause + 1);
- /* if we have a limit, write it */
- if (bts->paging.free_chans_need >= 0)
- vty_out(vty, " paging free %d%s", bts->paging.free_chans_need, VTY_NEWLINE);
+ vty_out(vty, "[%s] Type=%s, Severity=%s, ", timestamp, sd->parsed.event_type, sd->parsed.severity);
+ vty_out(vty, "Probable cause=%s: ", get_value_string(abis_nm_pcause_type_names, pcause));
+ if (pcause == NM_PCAUSE_T_MANUF)
+ vty_out(vty, "%s, ", get_value_string(abis_mm_event_cause_names, cause));
+ else
+ vty_out(vty, "%04X, ", cause);
+ vty_out(vty, "Additional text=%s%s", sd->parsed.additional_text, VTY_NEWLINE);
- vty_out(vty, " neighbor-list mode %s%s",
- get_value_string(bts_neigh_mode_strs, bts->neigh_list_manual_mode), VTY_NEWLINE);
- if (bts->neigh_list_manual_mode != NL_MODE_AUTOMATIC) {
- for (i = 0; i < 1024; i++) {
- if (bitvec_get_bit_pos(&bts->si_common.neigh_list, i))
- vty_out(vty, " neighbor-list add arfcn %u%s",
- i, VTY_NEWLINE);
- }
- }
- if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) {
- for (i = 0; i < 1024; i++) {
- if (bitvec_get_bit_pos(&bts->si_common.si5_neigh_list, i))
- vty_out(vty, " si5 neighbor-list add arfcn %u%s",
- i, VTY_NEWLINE);
- }
+ talloc_free(sd);
}
- for (i = 0; i < MAX_EARFCN_LIST; i++) {
- struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
- if (e->arfcn[i] != OSMO_EARFCN_INVALID) {
- vty_out(vty, " si2quater neighbor-list add earfcn %u "
- "thresh-hi %u", e->arfcn[i], e->thresh_hi);
-
- vty_out(vty, " thresh-lo %u",
- e->thresh_lo_valid ? e->thresh_lo : 32);
-
- vty_out(vty, " prio %u",
- e->prio_valid ? e->prio : 8);
-
- vty_out(vty, " qrxlv %u",
- e->qrxlm_valid ? e->qrxlm : 32);
-
- tmp = e->meas_bw[i];
- vty_out(vty, " meas %u",
- (tmp != OSMO_EARFCN_MEAS_INVALID) ? tmp : 8);
-
- vty_out(vty, "%s", VTY_NEWLINE);
+ /* Optionally clear the list */
+ if (argc > 1) {
+ while (!llist_empty(&bts->oml_fail_rep)) {
+ struct bts_oml_fail_rep *old = llist_last_entry(&bts->oml_fail_rep, struct bts_oml_fail_rep,
+ list);
+ llist_del(&old->list);
+ talloc_free(old);
}
}
- for (i = 0; i < bts->si_common.uarfcn_length; i++) {
- vty_out(vty, " si2quater neighbor-list add uarfcn %u %u %u%s",
- bts->si_common.data.uarfcn_list[i],
- bts->si_common.data.scramble_list[i] & ~(1 << 9),
- (bts->si_common.data.scramble_list[i] >> 9) & 1,
- VTY_NEWLINE);
- }
-
- neighbor_ident_vty_write(vty, " ", bts);
-
- vty_out(vty, " codec-support fr");
- if (bts->codec.hr)
- vty_out(vty, " hr");
- if (bts->codec.efr)
- vty_out(vty, " efr");
- if (bts->codec.amr)
- vty_out(vty, " amr");
- vty_out(vty, "%s", VTY_NEWLINE);
-
- config_write_bts_amr(vty, bts, &bts->mr_full, 1);
- config_write_bts_amr(vty, bts, &bts->mr_half, 0);
-
- config_write_bts_gprs(vty, bts);
-
- if (bts->excl_from_rf_lock)
- vty_out(vty, " rf-lock-exclude%s", VTY_NEWLINE);
-
- if (bts->force_combined_si_set)
- vty_out(vty, " %sforce-combined-si%s",
- bts->force_combined_si ? "" : "no ", VTY_NEWLINE);
-
- for (i = 0; i < ARRAY_SIZE(bts->depends_on); ++i) {
- int j;
-
- if (bts->depends_on[i] == 0)
- continue;
-
- for (j = 0; j < sizeof(bts->depends_on[i]) * 8; ++j) {
- int bts_nr;
+ return CMD_SUCCESS;
+}
- if ((bts->depends_on[i] & (1<<j)) == 0)
- continue;
+DEFUN(show_rejected_bts, show_rejected_bts_cmd, "show rejected-bts",
+ SHOW_STR "Display recently rejected BTS devices\n")
+{
+ struct gsm_bts_rejected *pos;
- bts_nr = (i * sizeof(bts->depends_on[i]) * 8) + j;
- vty_out(vty, " depends-on-bts %d%s", bts_nr, VTY_NEWLINE);
- }
+ /* empty list */
+ struct llist_head *rejected = &gsmnet_from_vty(vty)->bts_rejected;
+ if (llist_empty(rejected)) {
+ vty_out(vty, "No BTS has been rejected.%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
}
- if (bts->pcu_sock_path)
- vty_out(vty, " pcu-socket %s%s", bts->pcu_sock_path, VTY_NEWLINE);
- ho_vty_write_bts(vty, bts);
-
- config_write_bts_model(vty, bts);
-}
-
-static int config_write_bts(struct vty *v)
-{
- struct gsm_network *gsmnet = gsmnet_from_vty(v);
- struct gsm_bts *bts;
+ /* table head */
+ vty_out(vty, "Date Site ID BTS ID IP%s", VTY_NEWLINE);
+ vty_out(vty, "------------------- ------- ------ ---------------%s", VTY_NEWLINE);
- llist_for_each_entry(bts, &gsmnet->bts_list, list)
- config_write_bts_single(v, bts);
+ /* table body */
+ llist_for_each_entry(pos, rejected, list) {
+ /* timestamp formatted like: "2018-10-24 15:04:52" */
+ char buf[20];
+ strftime(buf, sizeof(buf), "%F %T", localtime(&pos->time));
+ vty_out(vty, "%s %7u %6u %15s%s", buf, pos->site_id, pos->bts_id, pos->ip, VTY_NEWLINE);
+ }
return CMD_SUCCESS;
}
@@ -1030,6 +365,7 @@ static int config_write_net(struct vty *vty)
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
int i;
+ struct osmo_nri_range *r;
vty_out(vty, "network%s", VTY_NEWLINE);
vty_out(vty, " network country code %s%s", osmo_mcc_name(gsmnet->plmn.mcc), VTY_NEWLINE);
@@ -1046,8 +382,6 @@ static int config_write_net(struct vty *vty)
ho_vty_write_net(vty, gsmnet);
- T_defs_vty_write(vty, " ");
-
if (!gsmnet->dyn_ts_allow_tch_f)
vty_out(vty, " dyn_ts_allow_tch_f 0%s", VTY_NEWLINE);
if (gsmnet->tz.override != 0) {
@@ -1060,12 +394,14 @@ static int config_write_net(struct vty *vty)
gsmnet->tz.hr, gsmnet->tz.mn, VTY_NEWLINE);
}
- /* writing T3212 from the common T_defs_vty_write() instead. */
+ /* Timer introspection commands (generic osmo_tdef API) */
+ osmo_tdef_vty_groups_write(vty, " ");
{
uint16_t meas_port;
char *meas_host;
const char *meas_scenario;
+ unsigned int max_len = meas_feed_txqueue_max_length_get();
meas_feed_cfg_get(&meas_host, &meas_port);
meas_scenario = meas_feed_scenario_get();
@@ -1076,40 +412,34 @@ static int config_write_net(struct vty *vty)
if (strlen(meas_scenario) > 0)
vty_out(vty, " meas-feed scenario %s%s",
meas_scenario, VTY_NEWLINE);
+ if (max_len != MEAS_FEED_TXQUEUE_MAX_LEN_DEFAULT)
+ vty_out(vty, " meas-feed write-queue-max-length %u%s",
+ max_len, VTY_NEWLINE);
}
- return CMD_SUCCESS;
-}
+ if (gsmnet->allow_unusable_timeslots)
+ vty_out(vty, " allow-unusable-timeslots%s", VTY_NEWLINE);
-static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx, bool print_rsl, bool show_connected)
-{
- if (show_connected && !trx->rsl_link)
- return;
-
- if (!show_connected && trx->rsl_link)
- return;
-
- vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s",
- trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE);
- vty_out(vty, "Description: %s%s",
- trx->description ? trx->description : "(null)", VTY_NEWLINE);
- vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, "
- "resulting BS power: %d dBm%s",
- trx->nominal_power, trx->max_power_red,
- trx->nominal_power - trx->max_power_red, VTY_NEWLINE);
- vty_out(vty, " NM State: ");
- net_dump_nmstate(vty, &trx->mo.nm_state);
- if (print_rsl)
- vty_out(vty, " RSL State: %s%s", trx->rsl_link? "connected" : "disconnected", VTY_NEWLINE);
- vty_out(vty, " Baseband Transceiver NM State: ");
- net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state);
- if (is_ipaccess_bts(trx->bts)) {
- vty_out(vty, " ip.access stream ID: 0x%02x ", trx->rsl_tei);
- e1isl_dump_vty_tcp(vty, trx->rsl_link);
- } else {
- vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE);
- e1isl_dump_vty(vty, trx->rsl_link);
+ if (gsmnet->nri_bitlen != OSMO_NRI_BITLEN_DEFAULT)
+ vty_out(vty, " nri bitlen %u%s", gsmnet->nri_bitlen, VTY_NEWLINE);
+
+ llist_for_each_entry(r, &gsmnet->null_nri_ranges->entries, entry) {
+ vty_out(vty, " nri null add %d", r->first);
+ if (r->first != r->last)
+ vty_out(vty, " %d", r->last);
+ vty_out(vty, "%s", VTY_NEWLINE);
}
+
+ if (gsmnet->pcu_sock_path)
+ vty_out(vty, " pcu-socket %s%s", gsmnet->pcu_sock_path, VTY_NEWLINE);
+ if (gsmnet->pcu_sock_wqueue_len_max != BSC_PCU_SOCK_WQUEUE_LEN_DEFAULT)
+ vty_out(vty, " pcu-socket-wqueue-length %u%s", gsmnet->pcu_sock_wqueue_len_max,
+ VTY_NEWLINE);
+
+ neighbor_ident_vty_write_network(vty, " ");
+ mgcp_client_pool_config_write(vty, " ");
+
+ return CMD_SUCCESS;
}
static void trx_dump_vty_all(struct vty *vty, struct gsm_bts_trx *trx)
@@ -1168,55 +498,6 @@ DEFUN(show_trx,
return CMD_SUCCESS;
}
-/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots.
- * Don't do anything if the ts is not dynamic. */
-static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts)
-{
- enum gsm_phys_chan_config target;
- if (ts_is_pchan_switching(ts, &target)) {
- vty_out(vty, " switching %s -> %s", gsm_pchan_name(ts->pchan_is),
- gsm_pchan_name(target));
- } else if (ts->pchan_is != ts->pchan_on_init) {
- vty_out(vty, " as %s", gsm_pchan_name(ts->pchan_is));
- }
-}
-
-static void vty_out_dyn_ts_details(struct vty *vty, struct gsm_bts_trx_ts *ts)
-{
- /* show dyn TS details, if applicable */
- switch (ts->pchan_on_init) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
- vty_out(vty, " Osmocom Dyn TS:");
- vty_out_dyn_ts_status(vty, ts);
- vty_out(vty, VTY_NEWLINE);
- break;
- case GSM_PCHAN_TCH_F_PDCH:
- vty_out(vty, " IPACC Dyn PDCH TS:");
- vty_out_dyn_ts_status(vty, ts);
- vty_out(vty, VTY_NEWLINE);
- break;
- default:
- /* no dyn ts */
- break;
- }
-}
-
-static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts)
-{
- vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s",
- ts->trx->bts->nr, ts->trx->nr, ts->nr,
- gsm_pchan_name(ts->pchan_on_init));
- if (ts->pchan_is != ts->pchan_on_init)
- vty_out(vty, " (%s mode)", gsm_pchan_name(ts->pchan_is));
- vty_out(vty, ", TSC %u%s NM State: ", gsm_ts_tsc(ts), VTY_NEWLINE);
- vty_out_dyn_ts_details(vty, ts);
- net_dump_nmstate(vty, &ts->mo.nm_state);
- if (!is_ipaccess_bts(ts->trx->bts))
- vty_out(vty, " E1 Line %u, Timeslot %u, Subslot %u%s",
- ts->e1_link.e1_nr, ts->e1_link.e1_ts,
- ts->e1_link.e1_ts_ss, VTY_NEWLINE);
-}
-
DEFUN(show_ts,
show_ts_cmd,
"show timeslot [<0-255>] [<0-255>] [<0-7>]",
@@ -1293,47 +574,14 @@ DEFUN(show_ts,
return CMD_SUCCESS;
}
-static void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub)
+void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub)
{
if (strlen(bsub->imsi))
vty_out(vty, " IMSI: %s%s", bsub->imsi, VTY_NEWLINE);
if (bsub->tmsi != GSM_RESERVED_TMSI)
vty_out(vty, " TMSI: 0x%08x%s", bsub->tmsi,
VTY_NEWLINE);
- vty_out(vty, " Use count: %d%s", bsub->use_count, VTY_NEWLINE);
-}
-
-static void meas_rep_dump_uni_vty(struct vty *vty,
- struct gsm_meas_rep_unidir *mru,
- const char *prefix,
- const char *dir)
-{
- vty_out(vty, "%s RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ",
- prefix, dir, rxlev2dbm(mru->full.rx_lev),
- dir, rxlev2dbm(mru->sub.rx_lev));
- vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s",
- dir, mru->full.rx_qual, dir, mru->sub.rx_qual,
- VTY_NEWLINE);
-}
-
-static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr,
- const char *prefix)
-{
- vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE);
- vty_out(vty, "%s Flags: %s%s%s%s%s", prefix,
- mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "",
- mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "",
- mr->flags & MEAS_REP_F_FPC ? "FPC " : "",
- mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ",
- VTY_NEWLINE);
- if (mr->flags & MEAS_REP_F_MS_TO)
- vty_out(vty, "%s MS Timing Offset: %d%s", prefix, mr->ms_timing_offset, VTY_NEWLINE);
- if (mr->flags & MEAS_REP_F_MS_L1)
- vty_out(vty, "%s L1 MS Power: %u dBm, Timing Advance: %u%s",
- prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE);
- if (mr->flags & MEAS_REP_F_DL_VALID)
- meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl");
- meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul");
+ vty_out(vty, " Use count: %s%s", osmo_use_count_to_str_c(OTC_SELECT, &bsub->use_count), VTY_NEWLINE);
}
static inline void print_all_trx_ext(struct vty *vty, bool show_connected)
@@ -1364,88 +612,14 @@ DEFUN(show_trx_con,
return CMD_SUCCESS;
}
-static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
-{
- int idx;
-
- vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s",
- lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
- lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE);
- vty_out_dyn_ts_details(vty, lchan->ts);
- vty_out(vty, " Connection: %u, State: %s%s%s%s",
- lchan->conn ? 1: 0, lchan_state_name(lchan),
- lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? " Error reason: " : "",
- lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? lchan->last_error : "",
- VTY_NEWLINE);
- vty_out(vty, " BS Power: %u dBm, MS Power: %u dBm%s",
- lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red
- - lchan->bs_power*2,
- ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
- VTY_NEWLINE);
- vty_out(vty, " Channel Mode / Codec: %s%s",
- gsm48_chan_mode_name(lchan->tch_mode),
- VTY_NEWLINE);
- if (lchan->conn && lchan->conn->bsub) {
- vty_out(vty, " Subscriber:%s", VTY_NEWLINE);
- bsc_subscr_dump_vty(vty, lchan->conn->bsub);
- } else
- vty_out(vty, " No Subscriber%s", VTY_NEWLINE);
- if (is_ipaccess_bts(lchan->ts->trx->bts)) {
- struct in_addr ia;
- if (lchan->abis_ip.bound_ip) {
- ia.s_addr = htonl(lchan->abis_ip.bound_ip);
- vty_out(vty, " Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s",
- inet_ntoa(ia), lchan->abis_ip.bound_port,
- lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id,
- VTY_NEWLINE);
- }
- if (lchan->abis_ip.connect_ip) {
- ia.s_addr = htonl(lchan->abis_ip.connect_ip);
- vty_out(vty, " Conn. IP: %s Port %u RTP_TYPE=%u SPEECH_MODE=0x%02x%s",
- inet_ntoa(ia), lchan->abis_ip.connect_port,
- lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode,
- VTY_NEWLINE);
- }
-
- }
-
- /* we want to report the last measurement report */
- idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
- lchan->meas_rep_idx, 1);
- meas_rep_dump_vty(vty, &lchan->meas_rep[idx], " ");
-}
-
-static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan)
-{
- struct gsm_meas_rep *mr;
- int idx;
-
- /* we want to report the last measurement report */
- idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
- lchan->meas_rep_idx, 1);
- mr = &lchan->meas_rep[idx];
-
- vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s",
- lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
- gsm_pchan_name(lchan->ts->pchan_on_init));
- vty_out_dyn_ts_status(vty, lchan->ts);
- vty_out(vty, ", Lchan %u, Type %s, State %s - "
- "L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s",
- lchan->nr,
- gsm_lchant_name(lchan->type), lchan_state_name(lchan),
- mr->ms_l1.pwr,
- rxlev2dbm(mr->dl.full.rx_lev),
- rxlev2dbm(mr->ul.full.rx_lev),
- VTY_NEWLINE);
-}
-
static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty,
- void (*dump_cb)(struct vty *, struct gsm_lchan *))
+ void (*dump_cb)(struct vty *, struct gsm_lchan *),
+ bool all)
{
struct gsm_lchan *lchan;
- ts_for_each_lchan(lchan, ts) {
- if (lchan_state_is(lchan, LCHAN_ST_UNUSED))
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
+ if (lchan_state_is(lchan, LCHAN_ST_UNUSED) && all == false)
continue;
dump_cb(vty, lchan);
}
@@ -1454,33 +628,36 @@ static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty,
}
static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty,
- void (*dump_cb)(struct vty *, struct gsm_lchan *))
+ void (*dump_cb)(struct vty *, struct gsm_lchan *),
+ bool all)
{
int ts_nr;
for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
- dump_lchan_trx_ts(ts, vty, dump_cb);
+ dump_lchan_trx_ts(ts, vty, dump_cb, all);
}
return CMD_SUCCESS;
}
static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty,
- void (*dump_cb)(struct vty *, struct gsm_lchan *))
+ void (*dump_cb)(struct vty *, struct gsm_lchan *),
+ bool all)
{
int trx_nr;
for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr);
- dump_lchan_trx(trx, vty, dump_cb);
+ dump_lchan_trx(trx, vty, dump_cb, all);
}
return CMD_SUCCESS;
}
static int lchan_summary(struct vty *vty, int argc, const char **argv,
- void (*dump_cb)(struct vty *, struct gsm_lchan *))
+ void (*dump_cb)(struct vty *, struct gsm_lchan *),
+ bool all)
{
struct gsm_network *net = gsmnet_from_vty(vty);
struct gsm_bts *bts = NULL;
@@ -1500,7 +677,7 @@ static int lchan_summary(struct vty *vty, int argc, const char **argv,
bts = gsm_bts_num(net, bts_nr);
if (argc == 1)
- return dump_lchan_bts(bts, vty, dump_cb);
+ return dump_lchan_bts(bts, vty, dump_cb, all);
}
if (argc >= 2) {
trx_nr = atoi(argv[1]);
@@ -1512,7 +689,7 @@ static int lchan_summary(struct vty *vty, int argc, const char **argv,
trx = gsm_bts_trx_num(bts, trx_nr);
if (argc == 2)
- return dump_lchan_trx(trx, vty, dump_cb);
+ return dump_lchan_trx(trx, vty, dump_cb, all);
}
if (argc >= 3) {
ts_nr = atoi(argv[2]);
@@ -1524,7 +701,7 @@ static int lchan_summary(struct vty *vty, int argc, const char **argv,
ts = &trx->ts[ts_nr];
if (argc == 3)
- return dump_lchan_trx_ts(ts, vty, dump_cb);
+ return dump_lchan_trx_ts(ts, vty, dump_cb, all);
}
if (argc >= 4) {
lchan_nr = atoi(argv[3]);
@@ -1541,7 +718,7 @@ static int lchan_summary(struct vty *vty, int argc, const char **argv,
for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
bts = gsm_bts_num(net, bts_nr);
- dump_lchan_bts(bts, vty, dump_cb);
+ dump_lchan_bts(bts, vty, dump_cb, all);
}
return CMD_SUCCESS;
@@ -1554,24 +731,34 @@ DEFUN(show_lchan,
SHOW_STR "Display information about a logical channel\n"
BTS_TRX_TS_LCHAN_STR)
{
- return lchan_summary(vty, argc, argv, lchan_dump_full_vty);
+ return lchan_summary(vty, argc, argv, lchan_dump_full_vty, true);
}
DEFUN(show_lchan_summary,
show_lchan_summary_cmd,
"show lchan summary [<0-255>] [<0-255>] [<0-7>] [<0-7>]",
SHOW_STR "Display information about a logical channel\n"
- "Short summary\n"
+ "Short summary (used lchans)\n"
BTS_TRX_TS_LCHAN_STR)
{
- return lchan_summary(vty, argc, argv, lchan_dump_short_vty);
+ return lchan_summary(vty, argc, argv, lchan_dump_short_vty, false);
+}
+
+DEFUN(show_lchan_summary_all,
+ show_lchan_summary_all_cmd,
+ "show lchan summary-all [<0-255>] [<0-255>] [<0-7>] [<0-7>]",
+ SHOW_STR "Display information about a logical channel\n"
+ "Short summary (all lchans)\n"
+ BTS_TRX_TS_LCHAN_STR)
+{
+ return lchan_summary(vty, argc, argv, lchan_dump_short_vty, true);
}
static void dump_one_subscr_conn(struct vty *vty, const struct gsm_subscriber_connection *conn)
{
vty_out(vty, "conn ID=%u, MSC=%u, hodec2_fail=%d, mgw_ep=%s%s",
- conn->sccp.conn_id, conn->sccp.msc->nr, conn->hodec2.failures,
- mgw_endpoint_name(conn->user_plane.mgw_endpoint), VTY_NEWLINE);
+ conn->sccp.conn.conn_id, conn->sccp.msc->nr, conn->hodec2.failures,
+ osmo_mgcpc_ep_name(conn->user_plane.mgw_endpoint), VTY_NEWLINE);
if (conn->lcls.global_call_ref_len) {
vty_out(vty, " LCLS GCR: %s%s",
osmo_hexdump_nospc(conn->lcls.global_call_ref, conn->lcls.global_call_ref_len),
@@ -1596,14 +783,12 @@ DEFUN(show_subscr_conn,
struct gsm_subscriber_connection *conn;
struct gsm_network *net = gsmnet_from_vty(vty);
bool no_conns = true;
- unsigned int count = 0;
vty_out(vty, "Active subscriber connections: %s", VTY_NEWLINE);
llist_for_each_entry(conn, &net->subscr_conns, entry) {
dump_one_subscr_conn(vty, conn);
no_conns = false;
- count++;
}
if (no_conns)
@@ -1612,26 +797,38 @@ DEFUN(show_subscr_conn,
return CMD_SUCCESS;
}
-static int trigger_ho_or_as(struct vty *vty, struct gsm_lchan *from_lchan, struct gsm_bts *to_bts)
+static int trigger_as(struct vty *vty, struct gsm_lchan *from_lchan, struct gsm_lchan *to_lchan)
{
- if (!to_bts || from_lchan->ts->trx->bts == to_bts) {
- LOGP(DHO, LOGL_NOTICE, "%s Manually triggering Assignment from VTY\n",
- gsm_lchan_name(from_lchan));
- to_bts = from_lchan->ts->trx->bts;
- } else
- LOGP(DHO, LOGL_NOTICE, "%s (ARFCN %u) --> BTS %u Manually triggering Handover from VTY\n",
- gsm_lchan_name(from_lchan), from_lchan->ts->trx->arfcn, to_bts->nr);
- {
- struct handover_out_req req = {
- .from_hodec_id = HODEC_USER,
- .old_lchan = from_lchan,
- .target_nik = *bts_ident_key(to_bts),
- };
- handover_request(&req);
+ LOG_LCHAN(from_lchan, LOGL_NOTICE, "Manually triggering Assignment from VTY\n");
+ if (!to_lchan) {
+ struct gsm_bts *bts = from_lchan->ts->trx->bts;
+ to_lchan = lchan_select_by_type(bts, from_lchan->type,
+ SELECT_FOR_ASSIGNMENT,
+ from_lchan);
+ vty_out(vty, "Error: cannot find free lchan of type %s%s",
+ gsm_chan_t_name(from_lchan->type), VTY_NEWLINE);
+ }
+ if (reassignment_request_to_lchan(ASSIGN_FOR_VTY, from_lchan, to_lchan, -1, -1)) {
+ vty_out(vty, "Error: not allowed to start assignment for %s%s",
+ gsm_lchan_name(from_lchan), VTY_NEWLINE);
+ return CMD_WARNING;
}
return CMD_SUCCESS;
}
+static int trigger_ho(struct vty *vty, struct gsm_lchan *from_lchan, struct gsm_bts *to_bts)
+{
+ struct handover_out_req req = {
+ .from_hodec_id = HODEC_USER,
+ .old_lchan = from_lchan,
+ };
+ bts_cell_ab(&req.target_cell_ab, to_bts);
+ LOGP(DHO, LOGL_NOTICE, "%s (ARFCN %u) --> BTS %u Manually triggering Handover from VTY\n",
+ gsm_lchan_name(from_lchan), from_lchan->ts->trx->arfcn, to_bts->nr);
+ handover_request(&req);
+ return CMD_SUCCESS;
+}
+
static int ho_or_as(struct vty *vty, const char *argv[], int argc)
{
struct gsm_network *net = gsmnet_from_vty(vty);
@@ -1642,11 +839,10 @@ static int ho_or_as(struct vty *vty, const char *argv[], int argc)
unsigned int trx_nr = atoi(argv[1]);
unsigned int ts_nr = atoi(argv[2]);
unsigned int ss_nr = atoi(argv[3]);
- unsigned int bts_nr_new;
const char *action;
if (argc > 4) {
- bts_nr_new = atoi(argv[4]);
+ unsigned int bts_nr_new = atoi(argv[4]);
/* Lookup the BTS where we want to handover to */
llist_for_each_entry(bts, &net->bts_list, list) {
@@ -1657,7 +853,7 @@ static int ho_or_as(struct vty *vty, const char *argv[], int argc)
}
if (!new_bts) {
- vty_out(vty, "Unable to trigger handover, specified bts #%u does not exist %s",
+ vty_out(vty, "%% Unable to trigger handover, specified bts #%u does not exist %s",
bts_nr_new, VTY_NEWLINE);
return CMD_WARNING;
}
@@ -1667,21 +863,50 @@ static int ho_or_as(struct vty *vty, const char *argv[], int argc)
/* Find the connection/lchan that we want to handover */
llist_for_each_entry(conn, &net->subscr_conns, entry) {
- if (conn_get_bts(conn)->nr == bts_nr &&
+ struct gsm_bts *bts = conn_get_bts(conn);
+ if (!bts)
+ continue;
+ if (bts->nr == bts_nr &&
conn->lchan->ts->trx->nr == trx_nr &&
conn->lchan->ts->nr == ts_nr && conn->lchan->nr == ss_nr) {
vty_out(vty, "starting %s for lchan %s...%s", action, conn->lchan->name, VTY_NEWLINE);
lchan_dump_full_vty(vty, conn->lchan);
- return trigger_ho_or_as(vty, conn->lchan, new_bts);
+ if (new_bts)
+ return trigger_ho(vty, conn->lchan, new_bts);
+ else
+ return trigger_as(vty, conn->lchan, NULL);
}
}
- vty_out(vty, "Unable to trigger %s, specified connection (bts=%u,trx=%u,ts=%u,ss=%u) does not exist%s",
+ vty_out(vty, "%% Unable to trigger %s, specified connection (bts=%u,trx=%u,ts=%u,ss=%u) does not exist%s",
action, bts_nr, trx_nr, ts_nr, ss_nr, VTY_NEWLINE);
return CMD_WARNING;
}
+/* tsc_set and tsc: -1 to automatically determine which TSC Set / which TSC to use. */
+static int trigger_vamos_mode_modify(struct vty *vty, struct gsm_lchan *lchan, bool vamos, int tsc_set, int tsc)
+{
+ struct lchan_modify_info info = {
+ .modify_for = MODIFY_FOR_VTY,
+ .ch_mode_rate = lchan->current_ch_mode_rate,
+ .ch_indctr = lchan->current_ch_indctr,
+ .tsc_set = {
+ .present = (tsc_set >= 0),
+ .val = tsc_set,
+ },
+ .tsc = {
+ .present = (tsc >= 0),
+ .val = tsc,
+ },
+ };
+ if (vamos)
+ info.type_for = LCHAN_TYPE_FOR_VAMOS;
+
+ lchan_mode_modify(lchan, &info);
+ return CMD_SUCCESS;
+}
+
#define MANUAL_HANDOVER_STR "Manually trigger handover (for debugging)\n"
#define MANUAL_ASSIGNMENT_STR "Manually trigger assignment (for debugging)\n"
@@ -1723,7 +948,7 @@ static struct gsm_lchan *find_used_voice_lchan(struct vty *vty, int random_idx)
if (ts->fi->state != TS_ST_IN_USE)
continue;
- ts_for_each_lchan(lchan, ts) {
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
if (lchan_state_is(lchan, LCHAN_ST_ESTABLISHED)
&& (lchan->type == GSM_LCHAN_TCH_F
|| lchan->type == GSM_LCHAN_TCH_H)) {
@@ -1748,7 +973,7 @@ static struct gsm_lchan *find_used_voice_lchan(struct vty *vty, int random_idx)
random_idx %= count;
}
- vty_out(vty, "Cannot find any ongoing voice calls%s", VTY_NEWLINE);
+ vty_out(vty, "%% Cannot find any ongoing voice calls%s", VTY_NEWLINE);
return NULL;
}
@@ -1765,18 +990,20 @@ static struct gsm_bts *find_other_bts_with_free_slots(struct vty *vty, struct gs
continue;
llist_for_each_entry(trx, &bts->trx_list, list) {
- struct gsm_lchan *lchan = lchan_select_by_type(bts, free_type);
+ struct gsm_lchan *lchan = lchan_select_by_type(bts, free_type,
+ SELECT_FOR_HANDOVER,
+ NULL);
if (!lchan)
continue;
vty_out(vty, "Found unused %s slot: %s%s",
- gsm_lchant_name(free_type), gsm_lchan_name(lchan), VTY_NEWLINE);
+ gsm_chan_t_name(free_type), gsm_lchan_name(lchan), VTY_NEWLINE);
lchan_dump_full_vty(vty, lchan);
return bts;
}
}
- vty_out(vty, "Cannot find any BTS (other than BTS %u) with free %s lchan%s",
- not_this_bts? not_this_bts->nr : 255, gsm_lchant_name(free_type), VTY_NEWLINE);
+ vty_out(vty, "%% Cannot find any BTS (other than BTS %u) with free %s lchan%s",
+ not_this_bts ? not_this_bts->nr : 255, gsm_chan_t_name(free_type), VTY_NEWLINE);
return NULL;
}
@@ -1797,7 +1024,7 @@ DEFUN(handover_any, handover_any_cmd,
if (!to_bts)
return CMD_WARNING;
- return trigger_ho_or_as(vty, from_lchan, to_bts);
+ return trigger_ho(vty, from_lchan, to_bts);
}
DEFUN(assignment_any, assignment_any_cmd,
@@ -1812,18 +1039,19 @@ DEFUN(assignment_any, assignment_any_cmd,
if (!from_lchan)
return CMD_WARNING;
- return trigger_ho_or_as(vty, from_lchan, NULL);
+ return trigger_as(vty, from_lchan, NULL);
}
DEFUN(handover_any_to_arfcn_bsic, handover_any_to_arfcn_bsic_cmd,
- "handover any to " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
+ "handover any to " CELL_AB_VTY_PARAMS,
MANUAL_HANDOVER_STR
"Pick any actively used TCH/F or TCH/H lchan to handover to another cell."
" This is likely to fail outside of a lab setup where you are certain that"
" all MS are able to see the target cell.\n"
"'to'\n"
- NEIGHBOR_IDENT_VTY_KEY_DOC)
+ CELL_AB_VTY_DOC)
{
+ struct cell_ab ab = {};
struct handover_out_req req;
struct gsm_lchan *from_lchan;
@@ -1836,12 +1064,8 @@ DEFUN(handover_any_to_arfcn_bsic, handover_any_to_arfcn_bsic_cmd,
.old_lchan = from_lchan,
};
- if (!neighbor_ident_bts_parse_key_params(vty, from_lchan->ts->trx->bts,
- argv, &req.target_nik)) {
- vty_out(vty, "%% BTS %u does not know about this neighbor%s",
- from_lchan->ts->trx->bts->nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
+ neighbor_ident_vty_parse_arfcn_bsic(&ab, argv);
+ req.target_cell_ab = ab;
handover_request(&req);
return CMD_SUCCESS;
@@ -1849,7 +1073,7 @@ DEFUN(handover_any_to_arfcn_bsic, handover_any_to_arfcn_bsic_cmd,
static void paging_dump_vty(struct vty *vty, struct gsm_paging_request *pag)
{
- vty_out(vty, "Paging on BTS %u%s", pag->bts->nr, VTY_NEWLINE);
+ vty_out(vty, "Paging on BTS %u (%u request timeouts)%s", pag->bts->nr, pag->attempts, VTY_NEWLINE);
bsc_subscr_dump_vty(vty, pag->bsub);
}
@@ -1857,10 +1081,9 @@ static void bts_paging_dump_vty(struct vty *vty, struct gsm_bts *bts)
{
struct gsm_paging_request *pag;
- if (!bts->paging.bts)
- return;
-
- llist_for_each_entry(pag, &bts->paging.pending_requests, entry)
+ llist_for_each_entry(pag, &bts->paging.initial_req_list, entry)
+ paging_dump_vty(vty, pag);
+ llist_for_each_entry(pag, &bts->paging.retrans_req_list, entry)
paging_dump_vty(vty, pag);
}
@@ -1919,17 +1142,18 @@ DEFUN(show_paging_group,
page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc,
str_to_imsi(argv[1]));
- vty_out(vty, "%%Paging group for IMSI %" PRIu64 " on BTS #%d is %u%s",
+ vty_out(vty, "%% Paging group for IMSI %" PRIu64 " on BTS #%d is %u%s",
str_to_imsi(argv[1]), bts->nr,
page_group, VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_net_neci,
- cfg_net_neci_cmd,
- "neci (0|1)",
- "New Establish Cause Indication\n"
- "Don't set the NECI bit\n" "Set the NECI bit\n")
+DEFUN_USRATTR(cfg_net_neci,
+ cfg_net_neci_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "neci (0|1)",
+ "New Establish Cause Indication\n"
+ "Don't set the NECI bit\n" "Set the NECI bit\n")
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
@@ -1938,13 +1162,14 @@ DEFUN(cfg_net_neci,
return CMD_SUCCESS;
}
-DEFUN(cfg_net_pag_any_tch,
- cfg_net_pag_any_tch_cmd,
- "paging any use tch (0|1)",
- "Assign a TCH when receiving a Paging Any request\n"
- "Any Channel\n" "Use\n" "TCH\n"
- "Do not use TCH for Paging Request Any\n"
- "Do use TCH for Paging Request Any\n")
+DEFUN_USRATTR(cfg_net_pag_any_tch,
+ cfg_net_pag_any_tch_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "paging any use tch (0|1)",
+ "Assign a TCH when receiving a Paging Any request\n"
+ "Any Channel\n" "Use\n" "TCH\n"
+ "Do not use TCH for Paging Request Any\n"
+ "Do use TCH for Paging Request Any\n")
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
gsmnet->pag_any_tch = atoi(argv[0]);
@@ -1962,2451 +1187,2043 @@ DEFUN_DEPRECATED(cfg_net_dtx,
return CMD_SUCCESS;
}
-/* per-BTS configuration */
-DEFUN(cfg_bts,
- cfg_bts_cmd,
- "bts <0-255>",
- "Select a BTS to configure\n"
- BTS_NR_STR)
+#define NRI_STR "Mapping of Network Resource Indicators, for MSC pooling\n"
+#define NULL_NRI_STR "Define NULL-NRI values that cause re-assignment of an MS to a different MSC, for MSC pooling.\n"
+#define NRI_FIRST_LAST_STR "First value of the NRI value range, should not surpass the configured 'nri bitlen'.\n" \
+ "Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the" \
+ " first value; if omitted, apply only the first value.\n"
+#define NRI_ARGS_TO_STR_FMT "%s%s%s"
+#define NRI_ARGS_TO_STR_ARGS(ARGC, ARGV) ARGV[0], (ARGC>1)? ".." : "", (ARGC>1)? ARGV[1] : ""
+#define NRI_WARN(MSC, FORMAT, args...) do { \
+ vty_out(vty, "%% Warning: msc %d: " FORMAT "%s", MSC->nr, ##args, VTY_NEWLINE); \
+ LOGP(DMSC, LOGL_ERROR, "msc %d: " FORMAT "\n", MSC->nr, ##args); \
+ } while (0)
+
+DEFUN_ATTR(cfg_net_nri_bitlen,
+ cfg_net_nri_bitlen_cmd,
+ "nri bitlen <1-15>",
+ NRI_STR
+ "Set number of bits that an NRI has, to extract from TMSI identities (always starting just after the TMSI's most significant octet).\n"
+ "bit count (default: " OSMO_STRINGIFY_VAL(OSMO_NRI_BITLEN_DEFAULT) ")\n",
+ CMD_ATTR_IMMEDIATE)
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- int bts_nr = atoi(argv[0]);
- struct gsm_bts *bts;
-
- if (bts_nr > gsmnet->num_bts) {
- vty_out(vty, "%% The next unused BTS number is %u%s",
- gsmnet->num_bts, VTY_NEWLINE);
- return CMD_WARNING;
- } else if (bts_nr == gsmnet->num_bts) {
- /* allocate a new one */
- bts = bsc_bts_alloc_register(gsmnet, GSM_BTS_TYPE_UNKNOWN,
- HARDCODED_BSIC);
- /*
- * Initalize bts->acc_ramp here. Else we could segfault while
- * processing a configuration file with ACC ramping settings.
- */
- acc_ramp_init(&bts->acc_ramp, bts);
- } else
- bts = gsm_bts_num(gsmnet, bts_nr);
-
- if (!bts) {
- vty_out(vty, "%% Unable to allocate BTS %u%s",
- gsmnet->num_bts, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- vty->index = bts;
- vty->index_sub = &bts->description;
- vty->node = BTS_NODE;
-
+ gsmnet->nri_bitlen = atoi(argv[0]);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_type,
- cfg_bts_type_cmd,
- "type TYPE", /* dynamically created */
- "Set the BTS type\n" "Type\n")
+DEFUN_ATTR(cfg_net_nri_null_add,
+ cfg_net_nri_null_add_cmd,
+ "nri null add <0-32767> [<0-32767>]",
+ NRI_STR NULL_NRI_STR "Add NULL-NRI value (or range)\n"
+ NRI_FIRST_LAST_STR,
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts *bts = vty->index;
int rc;
-
- rc = gsm_set_bts_type(bts, str2btstype(argv[0]));
+ const char *message;
+ rc = osmo_nri_ranges_vty_add(&message, NULL, bsc_gsmnet->null_nri_ranges, argc, argv,
+ bsc_gsmnet->nri_bitlen);
+ if (message) {
+ vty_out(vty, "%% %s: " NRI_ARGS_TO_STR_FMT, message, NRI_ARGS_TO_STR_ARGS(argc, argv));
+ }
if (rc < 0)
return CMD_WARNING;
-
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_band,
- cfg_bts_band_cmd,
- "band BAND",
- "Set the frequency band of this BTS\n" "Frequency band\n")
+DEFUN_ATTR(cfg_net_nri_null_del,
+ cfg_net_nri_null_del_cmd,
+ "nri null del <0-32767> [<0-32767>]",
+ NRI_STR NULL_NRI_STR "Remove NRI value or range from the NRI mapping\n"
+ NRI_FIRST_LAST_STR,
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts *bts = vty->index;
- int band = gsm_band_parse(argv[0]);
-
- if (band < 0) {
- vty_out(vty, "%% BAND %d is not a valid GSM band%s",
- band, VTY_NEWLINE);
- return CMD_WARNING;
+ int rc;
+ const char *message;
+ rc = osmo_nri_ranges_vty_del(&message, NULL, bsc_gsmnet->null_nri_ranges, argc, argv);
+ if (message) {
+ vty_out(vty, "%% %s: " NRI_ARGS_TO_STR_FMT "%s", message, NRI_ARGS_TO_STR_ARGS(argc, argv),
+ VTY_NEWLINE);
}
-
- bts->band = band;
-
+ if (rc < 0)
+ return CMD_WARNING;
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_dtxu, cfg_bts_dtxu_cmd, "dtx uplink [force]",
- "Configure discontinuous transmission\n"
- "Enable Uplink DTX for this BTS\n"
- "MS 'shall' use DTXu instead of 'may' use (might not be supported by "
- "older phones).\n")
+int print_counter(struct rate_ctr_group *bsc_ctrs, struct rate_ctr *ctr, const struct rate_ctr_desc *desc, void *data)
{
- struct gsm_bts *bts = vty->index;
-
- bts->dtxu = (argc > 0) ? GSM48_DTX_SHALL_BE_USED : GSM48_DTX_MAY_BE_USED;
- if (!is_ipaccess_bts(bts))
- vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration "
- "neither supported nor tested!%s", VTY_NEWLINE);
- return CMD_SUCCESS;
+ struct vty *vty = data;
+ vty_out(vty, "%25s: %10"PRIu64" %s%s", desc->name, ctr->current, desc->description, VTY_NEWLINE);
+ return 0;
}
-DEFUN(cfg_bts_no_dtxu, cfg_bts_no_dtxu_cmd, "no dtx uplink",
- NO_STR
- "Configure discontinuous transmission\n"
- "Disable Uplink DTX for this BTS\n")
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net)
{
- struct gsm_bts *bts = vty->index;
-
- bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED;
-
- return CMD_SUCCESS;
+ rate_ctr_for_each_counter(net->bsc_ctrs, print_counter, vty);
}
-DEFUN(cfg_bts_dtxd, cfg_bts_dtxd_cmd, "dtx downlink",
- "Configure discontinuous transmission\n"
- "Enable Downlink DTX for this BTS\n")
+DEFUN(drop_bts,
+ drop_bts_cmd,
+ "drop bts connection <0-65535> (oml|rsl)",
+ "Debug/Simulation command to drop Abis/IP BTS\n"
+ "Debug/Simulation command to drop Abis/IP BTS\n"
+ "Debug/Simulation command to drop Abis/IP BTS\n"
+ "BTS NR\n" "Drop OML Connection\n" "Drop RSL Connection\n")
{
- struct gsm_bts *bts = vty->index;
+ struct gsm_network *gsmnet;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts *bts;
+ unsigned int bts_nr;
- bts->dtxd = true;
- if (!is_ipaccess_bts(bts))
- vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration "
- "neither supported nor tested!%s", VTY_NEWLINE);
- return CMD_SUCCESS;
-}
+ gsmnet = gsmnet_from_vty(vty);
-DEFUN(cfg_bts_no_dtxd, cfg_bts_no_dtxd_cmd, "no dtx downlink",
- NO_STR
- "Configure discontinuous transmission\n"
- "Disable Downlink DTX for this BTS\n")
-{
- struct gsm_bts *bts = vty->index;
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= gsmnet->num_bts) {
+ vty_out(vty, "%% BTS number must be between 0 and %d. It was %d.%s",
+ gsmnet->num_bts, bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- bts->dtxd = false;
+ bts = gsm_bts_num(gsmnet, bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- return CMD_SUCCESS;
-}
+ if (!is_ipa_abisip_bts(bts)) {
+ vty_out(vty, "%% This command only works for IPA Abis/IP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
-DEFUN(cfg_bts_ci,
- cfg_bts_ci_cmd,
- "cell_identity <0-65535>",
- "Set the Cell identity of this BTS\n" "Cell Identity\n")
-{
- struct gsm_bts *bts = vty->index;
- int ci = atoi(argv[0]);
- if (ci < 0 || ci > 0xffff) {
- vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s",
- ci, VTY_NEWLINE);
+ /* close all connections */
+ if (strcmp(argv[1], "oml") == 0) {
+ ipaccess_drop_oml(bts, "vty");
+ } else if (strcmp(argv[1], "rsl") == 0) {
+ /* close all rsl connections */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ ipaccess_drop_rsl(trx, "vty");
+ }
+ } else {
+ vty_out(vty, "%% Argument must be 'oml' or 'rsl'.%s", VTY_NEWLINE);
return CMD_WARNING;
}
- bts->cell_identity = ci;
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_lac,
- cfg_bts_lac_cmd,
- "location_area_code <0-65535>",
- "Set the Location Area Code (LAC) of this BTS\n" "LAC\n")
+DEFUN(restart_bts, restart_bts_cmd,
+ "restart-bts <0-65535>",
+ "Restart ip.access nanoBTS through OML\n"
+ BTS_NR_STR)
{
- struct gsm_bts *bts = vty->index;
- int lac = atoi(argv[0]);
+ struct gsm_network *gsmnet;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts *bts;
+ unsigned int bts_nr;
+
+ gsmnet = gsmnet_from_vty(vty);
- if (lac < 0 || lac > 0xffff) {
- vty_out(vty, "%% LAC %d is not in the valid range (0-65535)%s",
- lac, VTY_NEWLINE);
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= gsmnet->num_bts) {
+ vty_out(vty, "%% BTS number must be between 0 and %d. It was %d.%s",
+ gsmnet->num_bts, bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
- vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
- lac, VTY_NEWLINE);
+ bts = gsm_bts_num(gsmnet, bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- bts->location_area_code = lac;
-
- return CMD_SUCCESS;
-}
+ if (!is_ipa_abisip_bts(bts)) {
+ vty_out(vty, "%% This command only works for IPA Abis/IP.%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ /* go from last TRX to c0 */
+ llist_for_each_entry_reverse(trx, &bts->trx_list, list)
+ abis_nm_ipaccess_restart(trx);
-/* compatibility wrapper for old config files */
-DEFUN_HIDDEN(cfg_bts_tsc,
- cfg_bts_tsc_cmd,
- "training_sequence_code <0-7>",
- "Set the Training Sequence Code (TSC) of this BTS\n" "TSC\n")
-{
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_bsic,
- cfg_bts_bsic_cmd,
- "base_station_id_code <0-63>",
- "Set the Base Station Identity Code (BSIC) of this BTS\n"
- "BSIC of this BTS\n")
+DEFUN(bts_resend_sysinfo,
+ bts_resend_sysinfo_cmd,
+ "bts <0-255> resend-system-information",
+ "BTS Specific Commands\n" BTS_NR_STR
+ "Re-generate + re-send BCCH SYSTEM INFORMATION\n")
{
- struct gsm_bts *bts = vty->index;
- int bsic = atoi(argv[0]);
+ struct gsm_network *gsmnet;
+ struct gsm_bts *bts;
+ unsigned int bts_nr;
+
+ gsmnet = gsmnet_from_vty(vty);
- if (bsic < 0 || bsic > 0x3f) {
- vty_out(vty, "%% BSIC %d is not in the valid range (0-255)%s",
- bsic, VTY_NEWLINE);
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= gsmnet->num_bts) {
+ vty_out(vty, "%% BTS number must be between 0 and %d. It was %d.%s",
+ gsmnet->num_bts, bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- bts->bsic = bsic;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_unit_id,
- cfg_bts_unit_id_cmd,
- "ip.access unit_id <0-65534> <0-255>",
- "Abis/IP specific options\n"
- "Set the IPA BTS Unit ID\n"
- "Unit ID (Site)\n"
- "Unit ID (BTS)\n")
-{
- struct gsm_bts *bts = vty->index;
- int site_id = atoi(argv[0]);
- int bts_id = atoi(argv[1]);
-
- if (!is_ipaccess_bts(bts)) {
- vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+ bts = gsm_bts_num(gsmnet, bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- bts->ip_access.site_id = site_id;
- bts->ip_access.bts_id = bts_id;
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_rsl_ip,
- cfg_bts_rsl_ip_cmd,
- "ip.access rsl-ip A.B.C.D",
- "Abis/IP specific options\n"
- "Set the IPA RSL IP Address of the BSC\n"
- "Destination IP address for RSL connection\n")
-{
- struct gsm_bts *bts = vty->index;
- struct in_addr ia;
-
- if (!is_ipaccess_bts(bts)) {
- vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+ if (gsm_bts_set_system_infos(bts) != 0) {
+ vty_out(vty, "%% Filed to (re)generate System Information "
+ "messages, check the logs%s", VTY_NEWLINE);
return CMD_WARNING;
}
- inet_aton(argv[0], &ia);
- bts->ip_access.rsl_ip = ntohl(ia.s_addr);
-
return CMD_SUCCESS;
}
-#define NOKIA_STR "Nokia *Site related commands\n"
-
-DEFUN(cfg_bts_nokia_site_skip_reset,
- cfg_bts_nokia_site_skip_reset_cmd,
- "nokia_site skip-reset (0|1)",
- NOKIA_STR
- "Skip the reset step during bootstrap process of this BTS\n"
- "Do NOT skip the reset\n" "Skip the reset\n")
+DEFUN(bts_resend_power_ctrl_params,
+ bts_resend_power_ctrl_params_cmd,
+ "bts <0-255> resend-power-control-defaults",
+ "BTS Specific Commands\n" BTS_NR_STR
+ "Re-generate + re-send default MS/BS Power control parameters\n")
{
- struct gsm_bts *bts = vty->index;
+ const struct gsm_bts_trx *trx;
+ const struct gsm_bts *bts;
+ int bts_nr = atoi(argv[0]);
- if (bts->type != GSM_BTS_TYPE_NOKIA_SITE) {
- vty_out(vty, "%% BTS is not of Nokia *Site type%s", VTY_NEWLINE);
+ bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- bts->nokia.skip_reset = atoi(argv[0]);
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_nokia_site_no_loc_rel_cnf,
- cfg_bts_nokia_site_no_loc_rel_cnf_cmd,
- "nokia_site no-local-rel-conf (0|1)",
- NOKIA_STR
- "Do not wait for RELease CONFirm message when releasing channel locally\n"
- "Wait for RELease CONFirm\n" "Do not wait for RELease CONFirm\n")
-{
- struct gsm_bts *bts = vty->index;
+ if (bts->ms_power_ctrl.mode != GSM_PWR_CTRL_MODE_DYN_BTS) {
+ vty_out(vty, "%% Not Sending default MS/BS Power control parameters "
+ "because BTS%d is not using dyn-bts mode%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- if (!is_nokia_bts(bts)) {
- vty_out(vty, "%% BTS is not of Nokia *Site type%s",
- VTY_NEWLINE);
+ if (bts->model->power_ctrl_send_def_params == NULL) {
+ vty_out(vty, "%% Sending default MS/BS Power control parameters "
+ "for BTS%d is not implemented%s", bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- bts->nokia.no_loc_rel_cnf = atoi(argv[0]);
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (bts->model->power_ctrl_send_def_params(trx) != 0) {
+ vty_out(vty, "%% Failed to send default MS/BS Power control parameters "
+ "to BTS%d/TRX%d%s", bts_nr, trx->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_nokia_site_bts_reset_timer_cnf,
- cfg_bts_nokia_site_bts_reset_timer_cnf_cmd,
- "nokia_site bts-reset-timer <15-100>",
- NOKIA_STR
- "The amount of time (in sec.) between BTS_RESET is sent,\n"
- "and the BTS is being bootstrapped.\n")
+DEFUN(bts_c0_power_red,
+ bts_c0_power_red_cmd,
+ "bts <0-255> c0-power-reduction <0-6>",
+ "BTS Specific Commands\n" BTS_NR_STR
+ "BCCH carrier power reduction operation\n"
+ "Power reduction value (in dB, even numbers only)\n")
{
- struct gsm_bts *bts = vty->index;
+ int bts_nr = atoi(argv[0]);
+ int red = atoi(argv[1]);
+ struct gsm_bts *bts;
+ int rc;
- if (!is_nokia_bts(bts)) {
- vty_out(vty, "%% BTS is not of Nokia *Site type%s",
- VTY_NEWLINE);
+ bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- bts->nokia.bts_reset_timer_cnf = atoi(argv[0]);
+ if (red % 2 != 0) {
+ vty_out(vty, "%% Incorrect BCCH power reduction value, "
+ "an even number is expected%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- return CMD_SUCCESS;
+ rc = gsm_bts_set_c0_power_red(bts, red);
+ switch (rc) {
+ case 0: /* success */
+ return CMD_SUCCESS;
+ case -ENOTCONN:
+ vty_out(vty, "%% BTS%u is offline%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ case -ENOTSUP:
+ vty_out(vty, "%% BCCH carrier power reduction operation mode "
+ "is not supported for BTS%u%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ default:
+ vty_out(vty, "%% Failed to %sable BCCH carrier power reduction "
+ "operation mode for BTS%u%s", red ? "en" : "dis",
+ bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
}
-#define OML_STR "Organization & Maintenance Link\n"
-#define IPA_STR "A-bis/IP Specific Options\n"
-DEFUN(cfg_bts_stream_id,
- cfg_bts_stream_id_cmd,
- "oml ip.access stream_id <0-255> line E1_LINE",
- OML_STR IPA_STR
- "Set the ip.access Stream ID of the OML link of this BTS\n"
- "Stream Identifier\n" "Virtual E1 Line Number\n" "Virtual E1 Line Number\n")
+/* this command is now hidden, as it's a low-level debug hack, and people should
+ * instead use osmo-cbc these days */
+DEFUN_HIDDEN(smscb_cmd, smscb_cmd_cmd,
+ "bts <0-255> smscb-command (normal|schedule|default) <1-4> HEXSTRING",
+ "BTS related commands\n" BTS_NR_STR
+ "SMS Cell Broadcast\n"
+ "Normal (one-shot) SMSCB Message; sent once over Abis+Um\n"
+ "Schedule (one-shot) SMSCB Message; sent once over Abis+Um\n"
+ "Default (repeating) SMSCB Message; sent once over Abis, unlimited ovrer Um\n"
+ "Last Valid Block\n"
+ "Hex Encoded SMSCB message (up to 88 octets)\n")
{
- struct gsm_bts *bts = vty->index;
- int stream_id = atoi(argv[0]), linenr = atoi(argv[1]);
+ struct gsm_bts *bts;
+ int bts_nr = atoi(argv[0]);
+ const char *type_str = argv[1];
+ int last_block = atoi(argv[2]);
+ struct rsl_ie_cb_cmd_type cb_cmd;
+ uint8_t buf[88];
+ int rc;
- if (!is_ipaccess_bts(bts)) {
- vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+ bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!gsm_bts_get_cbch(bts)) {
+ vty_out(vty, "%% BTS %d doesn't have a CBCH%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ rc = osmo_hexparse(argv[3], buf, sizeof(buf));
+ if (rc < 0 || rc > sizeof(buf)) {
+ vty_out(vty, "%% Error parsing HEXSTRING%s", VTY_NEWLINE);
return CMD_WARNING;
}
- bts->oml_tei = stream_id;
- /* This is used by e1inp_bind_ops callback for each BTS model. */
- bts->oml_e1_link.e1_nr = linenr;
-
- return CMD_SUCCESS;
-}
-
-#define OML_E1_STR OML_STR "OML E1/T1 Configuration\n"
+ cb_cmd.spare = 0;
+ cb_cmd.def_bcast = 0;
+ if (!strcmp(type_str, "normal"))
+ cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL;
+ else if (!strcmp(type_str, "schedule"))
+ cb_cmd.command = RSL_CB_CMD_TYPE_SCHEDULE;
+ else if (!strcmp(type_str, "default"))
+ cb_cmd.command = RSL_CB_CMD_TYPE_DEFAULT;
+ else {
+ vty_out(vty, "%% Error parsing type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
-DEFUN(cfg_bts_oml_e1,
- cfg_bts_oml_e1_cmd,
- "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
- OML_E1_STR
- "E1/T1 line number to be used for OML\n"
- "E1/T1 line number to be used for OML\n"
- "E1/T1 timeslot to be used for OML\n"
- "E1/T1 timeslot to be used for OML\n"
- "E1/T1 sub-slot to be used for OML\n"
- "Use E1/T1 sub-slot 0\n"
- "Use E1/T1 sub-slot 1\n"
- "Use E1/T1 sub-slot 2\n"
- "Use E1/T1 sub-slot 3\n"
- "Use full E1 slot 3\n"
- )
-{
- struct gsm_bts *bts = vty->index;
+ switch (last_block) {
+ case 1:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1;
+ break;
+ case 2:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2;
+ break;
+ case 3:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3;
+ break;
+ case 4:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4;
+ break;
+ default:
+ vty_out(vty, "%% Error parsing LASTBLOCK%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- parse_e1_link(&bts->oml_e1_link, argv[0], argv[1], argv[2]);
+ /* SDCCH4 might not be correct here if the CBCH is on a SDCCH8? */
+ rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, false, buf, rc);
return CMD_SUCCESS;
}
-
-DEFUN(cfg_bts_oml_e1_tei,
- cfg_bts_oml_e1_tei_cmd,
- "oml e1 tei <0-63>",
- OML_E1_STR
- "Set the TEI to be used for OML\n"
- "TEI Number\n")
+DEFUN(pdch_act, pdch_act_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> pdch (activate|deactivate)",
+ BTS_NR_TRX_TS_STR2
+ "Packet Data Channel\n"
+ "Activate Dynamic PDCH/TCH (-> PDCH mode)\n"
+ "Deactivate Dynamic PDCH/TCH (-> TCH mode)\n")
{
- struct gsm_bts *bts = vty->index;
+ struct gsm_bts_trx_ts *ts;
+ int activate;
- bts->oml_tei = atoi(argv[0]);
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts || !ts->fi || ts->fi->state == TS_ST_NOT_INITIALIZED || ts->fi->state == TS_ST_BORKEN) {
+ vty_out(vty, "%% Timeslot is not usable%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- return CMD_SUCCESS;
-}
+ if (!is_ipa_abisip_bts(ts->trx->bts)) {
+ vty_out(vty, "%% This command only works for IPA Abis/IP BTS%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
-DEFUN(cfg_bts_challoc, cfg_bts_challoc_cmd,
- "channel allocator (ascending|descending)",
- "Channnel Allocator\n" "Channel Allocator\n"
- "Allocate Timeslots and Transceivers in ascending order\n"
- "Allocate Timeslots and Transceivers in descending order\n")
-{
- struct gsm_bts *bts = vty->index;
+ if (ts->pchan_on_init != GSM_PCHAN_OSMO_DYN
+ && ts->pchan_on_init != GSM_PCHAN_TCH_F_PDCH) {
+ vty_out(vty, "%% Timeslot %u is not dynamic TCH/F_TCH/H_SDCCH8_PDCH or TCH/F_PDCH%s",
+ ts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- if (!strcmp(argv[0], "ascending"))
- bts->chan_alloc_reverse = 0;
+ if (!strcmp(argv[3], "activate"))
+ activate = 1;
else
- bts->chan_alloc_reverse = 1;
+ activate = 0;
- return CMD_SUCCESS;
-}
+ if (activate && ts->fi->state != TS_ST_UNUSED) {
+ vty_out(vty, "%% Timeslot %u is still in use%s",
+ ts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ } else if (!activate && ts->fi->state != TS_ST_PDCH) {
+ vty_out(vty, "%% Timeslot %u is not in PDCH mode%s",
+ ts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
-#define RACH_STR "Random Access Control Channel\n"
+ LOG_TS(ts, LOGL_NOTICE, "telnet VTY user asks to %s\n", activate ? "PDCH ACT" : "PDCH DEACT");
+ ts->pdch_act_allowed = activate;
+ osmo_fsm_inst_state_chg(ts->fi, activate ? TS_ST_WAIT_PDCH_ACT : TS_ST_WAIT_PDCH_DEACT, 4, 0);
-DEFUN(cfg_bts_rach_tx_integer,
- cfg_bts_rach_tx_integer_cmd,
- "rach tx integer <0-15>",
- RACH_STR
- "Set the raw tx integer value in RACH Control parameters IE\n"
- "Set the raw tx integer value in RACH Control parameters IE\n"
- "Raw tx integer value in RACH Control parameters IE\n")
-{
- struct gsm_bts *bts = vty->index;
- bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf;
return CMD_SUCCESS;
-}
-DEFUN(cfg_bts_rach_max_trans,
- cfg_bts_rach_max_trans_cmd,
- "rach max transmission (1|2|4|7)",
- RACH_STR
- "Set the maximum number of RACH burst transmissions\n"
- "Set the maximum number of RACH burst transmissions\n"
- "Maximum number of 1 RACH burst transmissions\n"
- "Maximum number of 2 RACH burst transmissions\n"
- "Maximum number of 4 RACH burst transmissions\n"
- "Maximum number of 7 RACH burst transmissions\n")
-{
- struct gsm_bts *bts = vty->index;
- bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0]));
- return CMD_SUCCESS;
}
-#define CD_STR "Channel Description\n"
-
-DEFUN(cfg_bts_chan_desc_att,
- cfg_bts_chan_desc_att_cmd,
- "channel-descrption attach (0|1)",
- CD_STR
- "Set if attachment is required\n"
- "Attachment is NOT required\n"
- "Attachment is required (standard)\n")
-{
- struct gsm_bts *bts = vty->index;
- bts->si_common.chan_desc.att = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-DEFUN(cfg_bts_chan_desc_bs_pa_mfrms,
- cfg_bts_chan_desc_bs_pa_mfrms_cmd,
- "channel-descrption bs-pa-mfrms <2-9>",
- CD_STR
- "Set number of multiframe periods for paging groups\n"
- "Number of multiframe periods for paging groups\n")
+/* Activate / Deactivate a single lchan with a specific codec mode */
+static int lchan_act_single(struct vty *vty, struct gsm_lchan *lchan, const char *codec_str, int amr_mode, int activate)
{
- struct gsm_bts *bts = vty->index;
- int bs_pa_mfrms = atoi(argv[0]);
+ struct lchan_activate_info info = {0};
+ uint16_t amr_modes[8] =
+ { GSM0808_SC_CFG_AMR_4_75, GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20, GSM0808_SC_CFG_AMR_5_90,
+ GSM0808_SC_CFG_AMR_6_70, GSM0808_SC_CFG_AMR_7_40, GSM0808_SC_CFG_AMR_7_95, GSM0808_SC_CFG_AMR_10_2,
+ GSM0808_SC_CFG_AMR_12_2 };
- bts->si_common.chan_desc.bs_pa_mfrms = bs_pa_mfrms - 2;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_chan_desc_bs_ag_blks_res,
- cfg_bts_chan_desc_bs_ag_blks_res_cmd,
- "channel-descrption bs-ag-blks-res <0-7>",
- CD_STR
- "Set number of blocks reserved for access grant\n"
- "Number of blocks reserved for access grant\n")
-{
- struct gsm_bts *bts = vty->index;
- int bs_ag_blks_res = atoi(argv[0]);
+ if (activate) {
+ if (!codec_str) {
+ vty_out(vty, "%% Error: need a channel type argument to activate%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- bts->si_common.chan_desc.bs_ag_blks_res = bs_ag_blks_res;
- return CMD_SUCCESS;
-}
+ LOG_LCHAN(lchan, LOGL_NOTICE, "attempt from VTY to activate lchan %s with codec %s\n",
+ gsm_lchan_name(lchan), codec_str);
+ if (!lchan->fi) {
+ vty_out(vty, "%% Cannot activate: Channel not initialized%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
-#define NM_STR "Network Management\n"
+ int lchan_t;
+ if (lchan->fi->state != LCHAN_ST_UNUSED) {
+ vty_out(vty, "%% Cannot activate: Channel busy!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
-DEFUN(cfg_bts_rach_nm_b_thresh,
- cfg_bts_rach_nm_b_thresh_cmd,
- "rach nm busy threshold <0-255>",
- RACH_STR NM_STR
- "Set the NM Busy Threshold\n"
- "Set the NM Busy Threshold\n"
- "NM Busy Threshold in dB")
-{
- struct gsm_bts *bts = vty->index;
- bts->rach_b_thresh = atoi(argv[0]);
- return CMD_SUCCESS;
-}
+ /* pick a suitable lchan type */
+ lchan_t = gsm_lchan_type_by_pchan(lchan->ts->pchan_is);
+ if (lchan_t < 0) {
+ if (lchan->ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH && !strcmp(codec_str, "fr"))
+ lchan_t = GSM_LCHAN_TCH_F;
+ else if (lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN && !strcmp(codec_str, "hr"))
+ lchan_t = GSM_LCHAN_TCH_H;
+ else if ((lchan->ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH
+ || lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN)
+ && !strcmp(codec_str, "fr"))
+ lchan_t = GSM_LCHAN_TCH_F;
+ else {
+ vty_out(vty, "%% Cannot activate: Invalid lchan type (%s)!%s",
+ gsm_pchan_name(lchan->ts->pchan_on_init), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
-DEFUN(cfg_bts_rach_nm_ldavg,
- cfg_bts_rach_nm_ldavg_cmd,
- "rach nm load average <0-65535>",
- RACH_STR NM_STR
- "Set the NM Loadaverage Slots value\n"
- "Set the NM Loadaverage Slots value\n"
- "NM Loadaverage Slots value\n")
-{
- struct gsm_bts *bts = vty->index;
- bts->rach_ldavg_slots = atoi(argv[0]);
- return CMD_SUCCESS;
-}
+ /* configure the lchan */
+ lchan_select_set_type(lchan, lchan_t);
+ if (!strcmp(codec_str, "hr") || !strcmp(codec_str, "fr")) {
+ info.ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_V1;
+ } else if (!strcmp(codec_str, "efr")) {
+ info.ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_EFR;
+ } else if (!strcmp(codec_str, "amr")) {
+ if (amr_mode == -1) {
+ vty_out(vty, "%% AMR requires specification of AMR mode%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ info.ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_AMR;
+ info.ch_mode_rate.s15_s0 = amr_modes[amr_mode];
+ } else if (!strcmp(codec_str, "sig")) {
+ info.ch_mode_rate.chan_mode = GSM48_CMODE_SIGN;
+ } else {
+ vty_out(vty, "%% Invalid channel mode specified!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
-DEFUN(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd,
- "cell barred (0|1)",
- "Should this cell be barred from access?\n"
- "Should this cell be barred from access?\n"
- "Cell should NOT be barred\n"
- "Cell should be barred\n")
+ info.activ_for = ACTIVATE_FOR_VTY;
+ info.ch_indctr = GSM0808_CHAN_SIGN;
+ info.ch_mode_rate.chan_rate = chan_t_to_chan_rate(lchan_t);
-{
- struct gsm_bts *bts = vty->index;
+ if (activate == 2 || lchan->vamos.is_secondary) {
+ info.type_for = LCHAN_TYPE_FOR_VAMOS;
+ if (lchan->vamos.is_secondary) {
+ info.tsc_set.present = true;
+ info.tsc_set.val = 1;
+ }
+ info.tsc.present = true;
+ info.tsc.val = 0;
+ info.ch_mode_rate.chan_mode = gsm48_chan_mode_to_vamos(info.ch_mode_rate.chan_mode);
+ }
- bts->si_common.rach_control.cell_bar = atoi(argv[0]);
+ vty_out(vty, "%% activating lchan %s as %s%s", gsm_lchan_name(lchan), gsm_chan_t_name(lchan->type),
+ VTY_NEWLINE);
+ lchan_activate(lchan, &info);
+ } else {
+ LOG_LCHAN(lchan, LOGL_NOTICE, "attempt from VTY to release lchan %s\n", gsm_lchan_name(lchan));
+ if (!lchan->fi) {
+ vty_out(vty, "%% Cannot release: Channel not initialized%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ vty_out(vty, "%% Asking for release of %s in state %s%s", gsm_lchan_name(lchan),
+ osmo_fsm_inst_state_name(lchan->fi), VTY_NEWLINE);
+ lchan_release(lchan, !!(lchan->conn), false, 0,
+ gscon_last_eutran_plmn(lchan->conn));
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_rach_ec_allowed, cfg_bts_rach_ec_allowed_cmd,
- "rach emergency call allowed (0|1)",
- RACH_STR
- "Should this cell allow emergency calls?\n"
- "Should this cell allow emergency calls?\n"
- "Should this cell allow emergency calls?\n"
- "Do NOT allow emergency calls\n"
- "Allow emergency calls\n")
+/* Activate / Deactivate a single lchan with a specific codec mode */
+static int lchan_act_trx(struct vty *vty, struct gsm_bts_trx *trx, int activate)
{
- struct gsm_bts *bts = vty->index;
-
- if (atoi(argv[0]) == 0)
- bts->si_common.rach_control.t2 |= 0x4;
- else
- bts->si_common.rach_control.t2 &= ~0x4;
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_rach_ac_class, cfg_bts_rach_ac_class_cmd,
- "rach access-control-class (0|1|2|3|4|5|6|7|8|9|11|12|13|14|15) (barred|allowed)",
- RACH_STR
- "Set access control class\n"
- "Access control class 0\n"
- "Access control class 1\n"
- "Access control class 2\n"
- "Access control class 3\n"
- "Access control class 4\n"
- "Access control class 5\n"
- "Access control class 6\n"
- "Access control class 7\n"
- "Access control class 8\n"
- "Access control class 9\n"
- "Access control class 11 for PLMN use\n"
- "Access control class 12 for security services\n"
- "Access control class 13 for public utilities (e.g. water/gas suppliers)\n"
- "Access control class 14 for emergency services\n"
- "Access control class 15 for PLMN staff\n"
- "barred to use access control class\n"
- "allowed to use access control class\n")
-{
- struct gsm_bts *bts = vty->index;
-
- uint8_t control_class;
- uint8_t allowed = 0;
-
- if (strcmp(argv[1], "allowed") == 0)
- allowed = 1;
-
- control_class = atoi(argv[0]);
- if (control_class < 8)
- if (allowed)
- bts->si_common.rach_control.t3 &= ~(0x1 << control_class);
- else
- bts->si_common.rach_control.t3 |= (0x1 << control_class);
- else
- if (allowed)
- bts->si_common.rach_control.t2 &= ~(0x1 << (control_class - 8));
- else
- bts->si_common.rach_control.t2 |= (0x1 << (control_class - 8));
+ int ts_nr;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ char *codec_str;
+ bool skip_next = false;
- return CMD_SUCCESS;
-}
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ case GSM_PCHAN_CCCH:
+ case GSM_PCHAN_CCCH_SDCCH4:
+ codec_str = "sig";
+ break;
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_F_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
+ codec_str = "fr";
+ break;
+ case GSM_PCHAN_TCH_H:
+ codec_str = "hr";
+ break;
+ default:
+ codec_str = NULL;
+ }
-DEFUN(cfg_bts_ms_max_power, cfg_bts_ms_max_power_cmd,
- "ms max power <0-40>",
- "MS Options\n"
- "Maximum transmit power of the MS\n"
- "Maximum transmit power of the MS\n"
- "Maximum transmit power of the MS in dBm")
-{
- struct gsm_bts *bts = vty->index;
+ if (codec_str && skip_next == false) {
+ lchan_act_single(vty, lchan, codec_str, -1, activate);
- bts->ms_max_power = atoi(argv[0]);
+ /* We use GSM_PCHAN_OSMO_DYN slots as TCH_F for this test, so we
+ * must not use the TCH_H reserved lchan in subslot 1. */
+ if (ts->pchan_on_init == GSM_PCHAN_OSMO_DYN)
+ skip_next = true;
+ }
+ else {
+ vty_out(vty, "%% omitting lchan %s%s", gsm_lchan_name(lchan), VTY_NEWLINE);
+ skip_next = false;
+ }
+ }
+ }
return CMD_SUCCESS;
}
-#define CELL_STR "Cell Parameters\n"
-
-DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd,
- "cell reselection hysteresis <0-14>",
- CELL_STR "Cell re-selection parameters\n"
- "Cell Re-Selection Hysteresis in dB\n"
- "Cell Re-Selection Hysteresis in dB")
+static int lchan_act_deact(struct vty *vty, const char **argv, int argc)
{
- struct gsm_bts *bts = vty->index;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_bts *bts;
+ struct gsm_lchan *lchan;
+ bool vamos = (strcmp(argv[3], "vamos-sub-slot") == 0);
+ int ss_nr = atoi(argv[4]);
+ const char *act_str = NULL;
+ const char *codec_str = NULL;
+ int activate;
+ int amr_mode = -1;
- bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2;
+ if (argc > 5)
+ act_str = argv[5];
+ if (argc > 6)
+ codec_str = argv[6];
+ if (argc > 7)
+ amr_mode = atoi(argv[7]);
- return CMD_SUCCESS;
-}
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts)
+ return CMD_WARNING;
-DEFUN(cfg_bts_rxlev_acc_min, cfg_bts_rxlev_acc_min_cmd,
- "rxlev access min <0-63>",
- "Minimum RxLev needed for cell access\n"
- "Minimum RxLev needed for cell access\n"
- "Minimum RxLev needed for cell access\n"
- "Minimum RxLev needed for cell access (better than -110dBm)")
-{
- struct gsm_bts *bts = vty->index;
+ if (ss_nr >= ts->max_primary_lchans) {
+ vty_out(vty, "Invalid sub-slot number %d for this timeslot type: %s (%u)%s", ss_nr,
+ gsm_pchan_name(ts->pchan_on_init), ts->max_primary_lchans, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]);
+ bts = ts->trx->bts;
+ if (vamos && bts->features_known && !osmo_bts_has_feature(&bts->features, BTS_FEAT_VAMOS)) {
+ vty_out(vty, "BTS does not support VAMOS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- return CMD_SUCCESS;
-}
+ if (vamos)
+ ss_nr += ts->max_primary_lchans;
-DEFUN(cfg_bts_cell_bar_qualify, cfg_bts_cell_bar_qualify_cmd,
- "cell bar qualify (0|1)",
- CELL_STR "Cell Bar Qualify\n" "Cell Bar Qualify\n"
- "Set CBQ to 0\n" "Set CBQ to 1\n")
-{
- struct gsm_bts *bts = vty->index;
+ lchan = &ts->lchan[ss_nr];
- bts->si_common.cell_ro_sel_par.present = 1;
- bts->si_common.cell_ro_sel_par.cbq = atoi(argv[0]);
+ if (!act_str)
+ activate = 0;
+ else if (!strcmp(act_str, "activate"))
+ activate = 1;
+ else if (!strcmp(act_str, "activate-vamos"))
+ activate = 2;
+ else
+ return CMD_WARNING;
- return CMD_SUCCESS;
+ return lchan_act_single(vty, lchan, codec_str, amr_mode, activate);
}
-DEFUN(cfg_bts_cell_resel_ofs, cfg_bts_cell_resel_ofs_cmd,
- "cell reselection offset <0-126>",
- CELL_STR "Cell Re-Selection Parameters\n"
- "Cell Re-Selection Offset (CRO) in dB\n"
- "Cell Re-Selection Offset (CRO) in dB\n"
- )
+/* Debug/Measurement command to activate a given logical channel
+ * manually in a given mode/codec. This is useful for receiver
+ * performance testing (FER/RBER/...) */
+DEFUN(lchan_act, lchan_act_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> (sub-slot|vamos-sub-slot) <0-7> (activate|activate-vamos) (hr|fr|efr|amr|sig) [<0-7>]",
+ BTS_NR_TRX_TS_STR2
+ "Primary sub-slot\n" "VAMOS secondary shadow subslot, range <0-1>, only valid for TCH type timeslots\n"
+ SS_NR_STR
+ "Manual Channel Activation (e.g. for BER test)\n"
+ "Manual Channel Activation, in VAMOS mode\n"
+ "Half-Rate v1\n" "Full-Rate\n" "Enhanced Full Rate\n" "Adaptive Multi-Rate\n" "Signalling\n" "AMR Mode\n")
{
- struct gsm_bts *bts = vty->index;
-
- bts->si_common.cell_ro_sel_par.present = 1;
- bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(argv[0])/2;
-
- return CMD_SUCCESS;
+ return lchan_act_deact(vty, argv, argc);
}
-DEFUN(cfg_bts_temp_ofs, cfg_bts_temp_ofs_cmd,
- "temporary offset <0-60>",
- "Cell selection temporary negative offset\n"
- "Cell selection temporary negative offset\n"
- "Cell selection temporary negative offset in dB")
+DEFUN(lchan_deact, lchan_deact_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> (sub-slot|vamos-sub-slot) <0-7> deactivate",
+ BTS_NR_TRX_TS_STR2
+ "Primary sub-slot\n" "VAMOS secondary shadow subslot, range <0-1>, only valid for TCH type timeslots\n"
+ SS_NR_STR
+ "Manual Channel Deactivation (e.g. for BER test)\n")
{
- struct gsm_bts *bts = vty->index;
-
- bts->si_common.cell_ro_sel_par.present = 1;
- bts->si_common.cell_ro_sel_par.temp_offs = atoi(argv[0])/10;
-
- return CMD_SUCCESS;
+ return lchan_act_deact(vty, argv, argc);
}
-DEFUN(cfg_bts_temp_ofs_inf, cfg_bts_temp_ofs_inf_cmd,
- "temporary offset infinite",
- "Cell selection temporary negative offset\n"
- "Cell selection temporary negative offset\n"
- "Sets cell selection temporary negative offset to infinity")
-{
- struct gsm_bts *bts = vty->index;
-
- bts->si_common.cell_ro_sel_par.present = 1;
- bts->si_common.cell_ro_sel_par.temp_offs = 7;
+#define ACTIVATE_ALL_LCHANS_STR "Manual Channel Activation of all logical channels (e.g. for BER test)\n"
+#define DEACTIVATE_ALL_LCHANS_STR "Manual Channel Deactivation of all logical channels (e.g. for BER test)\n"
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_penalty_time, cfg_bts_penalty_time_cmd,
- "penalty time <20-620>",
- "Cell selection penalty time\n"
- "Cell selection penalty time\n"
- "Cell selection penalty time in seconds (by 20s increments)\n")
+/* Similar to lchan_act, but activates all lchans on the network at once,
+ * this is intended to perform lab tests / measurements. */
+DEFUN_HIDDEN(lchan_act_bts, lchan_act_all_cmd,
+ "(activate-all-lchan|deactivate-all-lchan)",
+ ACTIVATE_ALL_LCHANS_STR
+ DEACTIVATE_ALL_LCHANS_STR)
{
- struct gsm_bts *bts = vty->index;
-
- bts->si_common.cell_ro_sel_par.present = 1;
- bts->si_common.cell_ro_sel_par.penalty_time = (atoi(argv[0])-20)/20;
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ const char *act_str = argv[0];
+ int activate;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
- return CMD_SUCCESS;
-}
+ if (!strcmp(act_str, "activate-all-lchan"))
+ activate = 1;
+ else
+ activate = 0;
-DEFUN(cfg_bts_penalty_time_rsvd, cfg_bts_penalty_time_rsvd_cmd,
- "penalty time reserved",
- "Cell selection penalty time\n"
- "Cell selection penalty time\n"
- "Set cell selection penalty time to reserved value 31, "
- "(indicate that CELL_RESELECT_OFFSET is subtracted from C2 "
- "and TEMPORARY_OFFSET is ignored)")
-{
- struct gsm_bts *bts = vty->index;
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ lchan_act_trx(vty, trx, activate);
+ }
- bts->si_common.cell_ro_sel_par.present = 1;
- bts->si_common.cell_ro_sel_par.penalty_time = 31;
+ vty_out(vty, "%% All channels have been %s on all BTS/TRX, please "
+ "make sure that the radio link timeout is set to %s%s",
+ activate ? "activated" : "deactivated",
+ activate ? "'infinite'" : "its old value (e.g. 'oml')",
+ VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_radio_link_timeout, cfg_bts_radio_link_timeout_cmd,
- "radio-link-timeout <4-64>",
- "Radio link timeout criterion (BTS side)\n"
- "Radio link timeout value (lost SACCH block)\n")
+/* Similar to lchan_act, but activates all lchans on the specified BTS at once,
+ * this is intended to perform lab tests / measurements. */
+DEFUN_HIDDEN(lchan_act_all_bts, lchan_act_all_bts_cmd,
+ "bts <0-255> (activate-all-lchan|deactivate-all-lchan)",
+ "BTS Specific Commands\n" BTS_NR_STR
+ ACTIVATE_ALL_LCHANS_STR
+ DEACTIVATE_ALL_LCHANS_STR)
{
- struct gsm_bts *bts = vty->index;
-
- gsm_bts_set_radio_link_timeout(bts, atoi(argv[0]));
-
- return CMD_SUCCESS;
-}
+ int bts_nr = atoi(argv[0]);
+ const char *act_str = argv[1];
+ int activate;
+ struct gsm_bts *bts;
+ int trx_nr;
+ struct gsm_bts_trx *trx;
-DEFUN(cfg_bts_radio_link_timeout_inf, cfg_bts_radio_link_timeout_inf_cmd,
- "radio-link-timeout infinite",
- "Radio link timeout criterion (BTS side)\n"
- "Infinite Radio link timeout value (use only for BTS RF testing)\n")
-{
- struct gsm_bts *bts = vty->index;
+ if (!strcmp(act_str, "activate-all-lchan"))
+ activate = 1;
+ else
+ activate = 0;
- if (bts->type != GSM_BTS_TYPE_OSMOBTS) {
- vty_out(vty, "%% infinite radio link timeout not supported by this BTS%s", VTY_NEWLINE);
+ bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- vty_out(vty, "%% INFINITE RADIO LINK TIMEOUT, USE ONLY FOR BTS RF TESTING%s", VTY_NEWLINE);
- gsm_bts_set_radio_link_timeout(bts, -1);
-
- return CMD_SUCCESS;
-}
-
-#define GPRS_TEXT "GPRS Packet Network\n"
-
-DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd,
- "gprs cell bvci <2-65535>",
- GPRS_TEXT
- "GPRS Cell Settings\n"
- "GPRS BSSGP VC Identifier\n"
- "GPRS BSSGP VC Identifier")
-{
- /* ETSI TS 101 343: values 0 and 1 are reserved for signalling and PTM */
- struct gsm_bts *bts = vty->index;
-
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
- return CMD_WARNING;
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ lchan_act_trx(vty, trx, activate);
}
- bts->gprs.cell.bvci = atoi(argv[0]);
+ vty_out(vty, "%% All channels have been %s on all TRX of BTS%d, please "
+ "make sure that the radio link timeout is set to %s%s",
+ activate ? "activated" : "deactivated", bts_nr,
+ activate ? "'infinite'" : "its old value (e.g. 'oml')",
+ VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd,
- "gprs nsei <0-65535>",
- GPRS_TEXT
- "GPRS NS Entity Identifier\n"
- "GPRS NS Entity Identifier")
+/* Similar to lchan_act, but activates all lchans on the specified BTS at once,
+ * this is intended to perform lab tests / measurements. */
+DEFUN_HIDDEN(lchan_act_all_trx, lchan_act_all_trx_cmd,
+ "bts <0-255> trx <0-255> (activate-all-lchan|deactivate-all-lchan)",
+ "BTS for manual command\n" BTS_NR_STR
+ "TRX for manual command\n" TRX_NR_STR
+ ACTIVATE_ALL_LCHANS_STR
+ DEACTIVATE_ALL_LCHANS_STR)
{
- struct gsm_bts *bts = vty->index;
-
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- bts->gprs.nse.nsei = atoi(argv[0]);
-
- return CMD_SUCCESS;
-}
-
-#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \
- "NSVC Logical Number\n"
+ int bts_nr = atoi(argv[0]);
+ int trx_nr = atoi(argv[1]);
+ const char *act_str = argv[2];
+ int activate;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
-DEFUN(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd,
- "gprs nsvc <0-1> nsvci <0-65535>",
- GPRS_TEXT NSVC_TEXT
- "NS Virtual Connection Identifier\n"
- "GPRS NS VC Identifier")
-{
- struct gsm_bts *bts = vty->index;
- int idx = atoi(argv[0]);
+ if (!strcmp(act_str, "activate-all-lchan"))
+ activate = 1;
+ else
+ activate = 0;
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- bts->gprs.nsvc[idx].nsvci = atoi(argv[1]);
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd,
- "gprs nsvc <0-1> local udp port <0-65535>",
- GPRS_TEXT NSVC_TEXT
- "GPRS NS Local UDP Port\n"
- "GPRS NS Local UDP Port\n"
- "GPRS NS Local UDP Port\n"
- "GPRS NS Local UDP Port Number\n")
-{
- struct gsm_bts *bts = vty->index;
- int idx = atoi(argv[0]);
-
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ if (!trx) {
+ vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- bts->gprs.nsvc[idx].local_port = atoi(argv[1]);
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd,
- "gprs nsvc <0-1> remote udp port <0-65535>",
- GPRS_TEXT NSVC_TEXT
- "GPRS NS Remote UDP Port\n"
- "GPRS NS Remote UDP Port\n"
- "GPRS NS Remote UDP Port\n"
- "GPRS NS Remote UDP Port Number\n")
-{
- struct gsm_bts *bts = vty->index;
- int idx = atoi(argv[0]);
-
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
+ lchan_act_trx(vty, trx, activate);
- bts->gprs.nsvc[idx].remote_port = atoi(argv[1]);
+ vty_out(vty, "%% All channels have been %s on BTS%d/TRX%d, please "
+ "make sure that the radio link timeout is set to %s%s",
+ activate ? "activated" : "deactivated", bts_nr, trx_nr,
+ activate ? "'infinite'" : "its old value (e.g. 'oml')",
+ VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd,
- "gprs nsvc <0-1> remote ip A.B.C.D",
- GPRS_TEXT NSVC_TEXT
- "GPRS NS Remote IP Address\n"
- "GPRS NS Remote IP Address\n"
- "GPRS NS Remote IP Address\n")
+DEFUN(lchan_set_mspower, lchan_set_mspower_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> ms-power <0-40> [verify]\n",
+ BTS_NR_TRX_TS_SS_STR2
+ "Manually force MS Uplink Power Level in dBm on the lchan (for testing)\n"
+ "Set transmit power of the MS in dBm\n"
+ "Check requested level against BAND and UE Power Class.\n")
{
- struct gsm_bts *bts = vty->index;
- int idx = atoi(argv[0]);
- struct in_addr ia;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int bts_nr = atoi(argv[0]);
+ int trx_nr = atoi(argv[1]);
+ int ss_nr = atoi(argv[3]);
+ bool verify = (argc > 5);
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- inet_aton(argv[1], &ia);
- bts->gprs.nsvc[idx].remote_ip = ntohl(ia.s_addr);
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_pag_free, cfg_bts_pag_free_cmd,
- "paging free <-1-1024>",
- "Paging options\n"
- "Only page when having a certain amount of free slots\n"
- "amount of required free paging slots. -1 to disable\n")
-{
- struct gsm_bts *bts = vty->index;
-
- bts->paging.free_chans_need = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_gprs_ns_timer, cfg_bts_gprs_ns_timer_cmd,
- "gprs ns timer " NS_TIMERS " <0-255>",
- GPRS_TEXT "Network Service\n"
- "Network Service Timer\n"
- NS_TIMERS_HELP "Timer Value\n")
-{
- struct gsm_bts *bts = vty->index;
- int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
- int val = atoi(argv[1]);
-
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ if (!trx) {
+ vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.nse.timer))
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts) {
+ vty_out(vty, "%% No such TS (%d)%s", atoi(argv[2]), VTY_NEWLINE);
return CMD_WARNING;
-
- bts->gprs.nse.timer[idx] = val;
-
- return CMD_SUCCESS;
-}
-
-#define BSSGP_TIMERS "(blocking-timer|blocking-retries|unblocking-retries|reset-timer|reset-retries|suspend-timer|suspend-retries|resume-timer|resume-retries|capability-update-timer|capability-update-retries)"
-#define BSSGP_TIMERS_HELP \
- "Tbvc-block timeout\n" \
- "Tbvc-block retries\n" \
- "Tbvc-unblock retries\n" \
- "Tbvcc-reset timeout\n" \
- "Tbvc-reset retries\n" \
- "Tbvc-suspend timeout\n" \
- "Tbvc-suspend retries\n" \
- "Tbvc-resume timeout\n" \
- "Tbvc-resume retries\n" \
- "Tbvc-capa-update timeout\n" \
- "Tbvc-capa-update retries\n"
-
-DEFUN(cfg_bts_gprs_cell_timer, cfg_bts_gprs_cell_timer_cmd,
- "gprs cell timer " BSSGP_TIMERS " <0-255>",
- GPRS_TEXT "Cell / BSSGP\n"
- "Cell/BSSGP Timer\n"
- BSSGP_TIMERS_HELP "Timer Value\n")
-{
- struct gsm_bts *bts = vty->index;
- int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]);
- int val = atoi(argv[1]);
-
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ }
+ if (ss_nr >= ts->max_primary_lchans) {
+ vty_out(vty, "%% Invalid sub-slot number for this timeslot type%s", VTY_NEWLINE);
return CMD_WARNING;
}
- if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer))
+ lchan = &ts->lchan[ss_nr];
+ if (!lchan->fi)
return CMD_WARNING;
- bts->gprs.cell.timer[idx] = val;
-
+ if (verify) {
+ lchan_update_ms_power_ctrl_level(lchan, atoi(argv[4]));
+ return CMD_SUCCESS;
+ }
+ lchan->ms_power = ms_pwr_ctl_lvl(ts->trx->bts->band, atoi(argv[4]));
+ rsl_chan_ms_power_ctrl(lchan);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd,
- "gprs routing area <0-255>",
- GPRS_TEXT
- "GPRS Routing Area Code\n"
- "GPRS Routing Area Code\n"
- "GPRS Routing Area Code\n")
+DEFUN(vamos_modify_lchan, vamos_modify_lchan_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> modify (vamos|non-vamos) " TSC_ARGS_OPT,
+ BTS_NR_TRX_TS_SS_STR2
+ "Manually send Channel Mode Modify (for debugging)\n"
+ "Enable VAMOS channel mode\n" "Disable VAMOS channel mode\n"
+ TSC_ARGS_DOC)
{
- struct gsm_bts *bts = vty->index;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_bts *bts;
+ struct gsm_lchan *lchan;
+ int ss_nr = atoi(argv[3]);
+ const char *vamos_str = argv[4];
+ /* argv[5] is the "tsc" string from TSC_ARGS_OPT */
+ int tsc_set = (argc > 6) ? atoi(argv[6]) : -1;
+ int tsc = (argc > 7) ? atoi(argv[7]) : -1;
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts)
return CMD_WARNING;
- }
-
- bts->gprs.rac = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_gprs_ctrl_ack, cfg_bts_gprs_ctrl_ack_cmd,
- "gprs control-ack-type-rach", GPRS_TEXT
- "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to "
- "four access bursts format instead of default RLC/MAC control block\n")
-{
- struct gsm_bts *bts = vty->index;
+ if (ss_nr >= ts->max_primary_lchans) {
+ vty_out(vty, "%% Invalid sub-slot number for this timeslot type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ bts = ts->trx->bts;
+ if (bts->features_known && !osmo_bts_has_feature(&bts->features, BTS_FEAT_VAMOS)) {
+ vty_out(vty, "%% BTS does not support VAMOS%s", VTY_NEWLINE);
return CMD_WARNING;
}
- bts->gprs.ctrl_ack_type_use_block = false;
+ lchan = &ts->lchan[ss_nr];
- return CMD_SUCCESS;
+ return trigger_vamos_mode_modify(vty, lchan, strcmp(vamos_str, "vamos") == 0, tsc_set, tsc);
}
-DEFUN(cfg_no_bts_gprs_ctrl_ack, cfg_no_bts_gprs_ctrl_ack_cmd,
- "no gprs control-ack-type-rach", NO_STR GPRS_TEXT
- "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to "
- "four access bursts format instead of default RLC/MAC control block\n")
+/* Debug command to send lchans from state LCHAN_ST_UNUSED to state
+ * LCHAN_ST_BORKEN and vice versa. */
+DEFUN_HIDDEN(lchan_set_borken, lchan_set_borken_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> (borken|unused)",
+ BTS_NR_TRX_TS_SS_STR2
+ "send lchan to state LCHAN_ST_BORKEN (for debugging)\n"
+ "send lchan to state LCHAN_ST_UNUSED (for debugging)\n")
{
- struct gsm_bts *bts = vty->index;
-
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int ss_nr = atoi(argv[3]);
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts)
return CMD_WARNING;
- }
- bts->gprs.ctrl_ack_type_use_block = true;
+ lchan = &ts->lchan[ss_nr];
+ if (!lchan->fi)
+ return CMD_WARNING;
+
+ if (!strcmp(argv[4], "borken")) {
+ if (lchan->fi->state == LCHAN_ST_UNUSED) {
+ osmo_fsm_inst_state_chg(lchan->fi, LCHAN_ST_BORKEN, 0, 0);
+ } else {
+ vty_out(vty,
+ "%% lchan is in state %s, only lchans that are in state %s may be moved to state %s manually%s",
+ osmo_fsm_state_name(lchan->fi->fsm, lchan->fi->state),
+ osmo_fsm_state_name(lchan->fi->fsm, LCHAN_ST_UNUSED),
+ osmo_fsm_state_name(lchan->fi->fsm, LCHAN_ST_BORKEN), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else {
+ if (lchan->fi->state == LCHAN_ST_BORKEN) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_VTY));
+ osmo_fsm_inst_state_chg(lchan->fi, LCHAN_ST_UNUSED, 0, 0);
+ } else {
+ vty_out(vty,
+ "%% lchan is in state %s, only lchans that are in state %s may be moved to state %s manually%s",
+ osmo_fsm_state_name(lchan->fi->fsm, lchan->fi->state),
+ osmo_fsm_state_name(lchan->fi->fsm, LCHAN_ST_BORKEN),
+ osmo_fsm_state_name(lchan->fi->fsm, LCHAN_ST_UNUSED), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_gprs_net_ctrl_ord, cfg_bts_gprs_net_ctrl_ord_cmd,
- "gprs network-control-order (nc0|nc1|nc2)",
- GPRS_TEXT
- "GPRS Network Control Order\n"
- "MS controlled cell re-selection, no measurement reporting\n"
- "MS controlled cell re-selection, MS sends measurement reports\n"
- "Network controlled cell re-selection, MS sends measurement reports\n")
+DEFUN(lchan_mdcx, lchan_mdcx_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> mdcx A.B.C.D <0-65535>",
+ BTS_NR_TRX_TS_SS_STR2
+ "Modify RTP Connection\n" "MGW IP Address\n" "MGW UDP Port\n")
{
- struct gsm_bts *bts = vty->index;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int ss_nr = atoi(argv[3]);
+ int port = atoi(argv[5]);
+ struct in_addr ia;
+ inet_aton(argv[4], &ia);
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts)
return CMD_WARNING;
- }
-
- bts->gprs.net_ctrl_ord = atoi(argv[0] + 2);
- return CMD_SUCCESS;
-}
+ lchan = &ts->lchan[ss_nr];
-DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd,
- "gprs mode (none|gprs|egprs)",
- GPRS_TEXT
- "GPRS Mode for this BTS\n"
- "GPRS Disabled on this BTS\n"
- "GPRS Enabled on this BTS\n"
- "EGPRS (EDGE) Enabled on this BTS\n")
-{
- struct gsm_bts *bts = vty->index;
- enum bts_gprs_mode mode = bts_gprs_mode_parse(argv[0], NULL);
+ if (!is_ipa_abisip_bts(lchan->ts->trx->bts)) {
+ vty_out(vty, "%% BTS is not of IPA Abis/IP type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- if (!bts_gprs_mode_is_compat(bts, mode)) {
- vty_out(vty, "This BTS type does not support %s%s", argv[0],
+ if (ss_nr >= ts->max_primary_lchans) {
+ vty_out(vty, "%% subslot index %d too large for physical channel %s (%u slots)%s",
+ ss_nr, gsm_pchan_name(ts->pchan_is), ts->max_primary_lchans,
VTY_NEWLINE);
return CMD_WARNING;
}
- bts->gprs.mode = mode;
-
+ vty_out(vty, "%% connecting RTP of %s to %s:%u%s", gsm_lchan_name(lchan),
+ inet_ntoa(ia), port, VTY_NEWLINE);
+ lchan->abis_ip.connect_ip = ia.s_addr;
+ lchan->abis_ip.connect_port = port;
+ rsl_tx_ipacc_mdcx(lchan);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_gprs_11bit_rach_support_for_egprs,
- cfg_bts_gprs_11bit_rach_support_for_egprs_cmd,
- "gprs 11bit_rach_support_for_egprs (0|1)",
- GPRS_TEXT "11 bit RACH options\n"
- "Disable 11 bit RACH for EGPRS\n"
- "Enable 11 bit RACH for EGPRS")
-{
- struct gsm_bts *bts = vty->index;
+DEFUN(lchan_reassign, lchan_reassign_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> (sub-slot|vamos-sub-slot) <0-7> "
+ "reassign-to trx <0-255> timeslot <0-7> (sub-slot|vamos-sub-slot) <0-7> "
+ TSC_ARGS_OPT,
+ BTS_NR_TRX_TS_STR2
+ "Primary sub-slot\n" "VAMOS secondary shadow subslot, range <0-1>, only valid for TCH type timeslots\n"
+ SS_NR_STR
+ "Trigger Assignment to an unused lchan on the same cell\n"
+ "Target TRX\nTRX nr\nTarget timeslot\ntimeslot nr\n"
+ "Primary sub-slot\n" "VAMOS secondary shadow subslot, range <0-1>, only valid for TCH type timeslots\n"
+ SS_NR_STR
+ TSC_ARGS_DOC)
+{
+ const char *bts_str = argv[0];
+ const char *from_trx_str = argv[1];
+ const char *from_ts_str = argv[2];
+ bool from_vamos = (strcmp(argv[3], "vamos-sub-slot") == 0);
+ int from_ss_nr = atoi(argv[4]);
+ const char *to_trx_str = argv[5];
+ const char *to_ts_str = argv[6];
+ bool to_vamos = (strcmp(argv[7], "vamos-sub-slot") == 0);
+ int to_ss_nr = atoi(argv[8]);
+ int tsc_set = (argc > 10) ? atoi(argv[10]) : -1;
+ int tsc = (argc > 11) ? atoi(argv[11]) : -1;
+
+ struct gsm_bts_trx_ts *from_ts;
+ struct gsm_bts_trx_ts *to_ts;
+ struct gsm_lchan *from_lchan;
+ struct gsm_lchan *to_lchan;
- bts->gprs.supports_egprs_11bit_rach = atoi(argv[0]);
+ from_ts = vty_get_ts(vty, bts_str, from_trx_str, from_ts_str);
+ if (!from_ts)
+ return CMD_WARNING;
+ to_ts = vty_get_ts(vty, bts_str, to_trx_str, to_ts_str);
+ if (!to_ts)
+ return CMD_WARNING;
- if (bts->gprs.supports_egprs_11bit_rach > 1) {
- vty_out(vty, "Error in RACH type%s", VTY_NEWLINE);
+ if (!ts_is_capable_of_pchan(to_ts, from_ts->pchan_is)) {
+ vty_out(vty, "cannot re-assign, target timeslot has mismatching physical channel config: %s -> %s%s",
+ gsm_pchan_name(from_ts->pchan_is), gsm_pchan_name(to_ts->pchan_on_init), VTY_NEWLINE);
return CMD_WARNING;
}
- if ((bts->gprs.mode == BTS_GPRS_NONE) &&
- (bts->gprs.supports_egprs_11bit_rach == 1)) {
- vty_out(vty, "Error:gprs mode is none and 11bit rach is"
- " enabled%s", VTY_NEWLINE);
+ if (from_ss_nr >= from_ts->max_primary_lchans) {
+ vty_out(vty, "cannot re-assign, invalid source subslot number: %d%s",
+ from_ss_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- return CMD_SUCCESS;
-}
-
-#define SI_TEXT "System Information Messages\n"
-#define SI_TYPE_TEXT "(1|2|3|4|5|6|7|8|9|10|13|16|17|18|19|20|2bis|2ter|2quater|5bis|5ter)"
-#define SI_TYPE_HELP "System Information Type 1\n" \
- "System Information Type 2\n" \
- "System Information Type 3\n" \
- "System Information Type 4\n" \
- "System Information Type 5\n" \
- "System Information Type 6\n" \
- "System Information Type 7\n" \
- "System Information Type 8\n" \
- "System Information Type 9\n" \
- "System Information Type 10\n" \
- "System Information Type 13\n" \
- "System Information Type 16\n" \
- "System Information Type 17\n" \
- "System Information Type 18\n" \
- "System Information Type 19\n" \
- "System Information Type 20\n" \
- "System Information Type 2bis\n" \
- "System Information Type 2ter\n" \
- "System Information Type 2quater\n" \
- "System Information Type 5bis\n" \
- "System Information Type 5ter\n"
-
-DEFUN(cfg_bts_si_mode, cfg_bts_si_mode_cmd,
- "system-information " SI_TYPE_TEXT " mode (static|computed)",
- SI_TEXT SI_TYPE_HELP
- "System Information Mode\n"
- "Static user-specified\n"
- "Dynamic, BSC-computed\n")
-{
- struct gsm_bts *bts = vty->index;
- int type;
-
- type = get_string_value(osmo_sitype_strs, argv[0]);
- if (type < 0) {
- vty_out(vty, "Error SI Type%s", VTY_NEWLINE);
+ if (to_ss_nr >= to_ts->max_primary_lchans) {
+ vty_out(vty, "cannot re-assign, invalid target subslot number: %d%s",
+ to_ss_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- if (!strcmp(argv[1], "static"))
- bts->si_mode_static |= (1 << type);
- else
- bts->si_mode_static &= ~(1 << type);
+ if (from_vamos)
+ from_ss_nr += from_ts->max_primary_lchans;
+ from_lchan = &from_ts->lchan[from_ss_nr];
- return CMD_SUCCESS;
-}
+ if (to_vamos)
+ to_ss_nr += to_ts->max_primary_lchans;
+ to_lchan = &to_ts->lchan[to_ss_nr];
-DEFUN(cfg_bts_si_static, cfg_bts_si_static_cmd,
- "system-information " SI_TYPE_TEXT " static HEXSTRING",
- SI_TEXT SI_TYPE_HELP
- "Static System Information filling\n"
- "Static user-specified SI content in HEX notation\n")
-{
- struct gsm_bts *bts = vty->index;
- int rc, type;
-
- type = get_string_value(osmo_sitype_strs, argv[0]);
- if (type < 0) {
- vty_out(vty, "Error SI Type%s", VTY_NEWLINE);
+ if (!lchan_state_is(from_lchan, LCHAN_ST_ESTABLISHED)) {
+ vty_out(vty, "cannot re-assign, source lchan is not in ESTABLISHED state%s", VTY_NEWLINE);
return CMD_WARNING;
}
-
- if (!(bts->si_mode_static & (1 << type))) {
- vty_out(vty, "SI Type %s is not configured in static mode%s",
- get_value_string(osmo_sitype_strs, type), VTY_NEWLINE);
+ if (!to_lchan->fi) {
+ vty_out(vty, "cannot re-assign, target lchan is not initialized%s", VTY_NEWLINE);
return CMD_WARNING;
}
-
- /* Fill buffer with padding pattern */
- memset(GSM_BTS_SI(bts, type), 0x2b, GSM_MACBLOCK_LEN);
-
- /* Parse the user-specified SI in hex format, [partially] overwriting padding */
- rc = osmo_hexparse(argv[1], GSM_BTS_SI(bts, type), GSM_MACBLOCK_LEN);
- if (rc < 0 || rc > GSM_MACBLOCK_LEN) {
- vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE);
+ if (!lchan_state_is(to_lchan, LCHAN_ST_UNUSED)) {
+ vty_out(vty, "cannot re-assign, target lchan is already in use%s", VTY_NEWLINE);
return CMD_WARNING;
}
- /* Mark this SI as present */
- bts->si_valid |= (1 << type);
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_si_unused_send_empty, cfg_bts_si_unused_send_empty_cmd,
- "system-information unused-send-empty",
- SI_TEXT
- "Send BCCH Info with empty 'Full BCCH Info' TLV to notify disabled SI. "
- "Some nanoBTS fw versions are known to fail upon receival of these messages.\n")
-{
- struct gsm_bts *bts = vty->index;
-
- bts->si_unused_send_empty = true;
+ /* Set lchan type, so that activation will work out. */
+ lchan_select_set_type(to_lchan, chan_mode_to_chan_type(from_lchan->current_ch_mode_rate.chan_mode,
+ from_lchan->current_ch_mode_rate.chan_rate));
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bts_no_si_unused_send_empty, cfg_bts_no_si_unused_send_empty_cmd,
- "no system-information unused-send-empty",
- NO_STR SI_TEXT
- "Avoid sending BCCH Info with empty 'Full BCCH Info' TLV to notify disabled SI. "
- "Some nanoBTS fw versions are known to fail upon receival of these messages.\n")
-{
- struct gsm_bts *bts = vty->index;
-
- if (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) {
- vty_out(vty, "This command is only intended for ipaccess nanoBTS. See OS#3707.%s",
- VTY_NEWLINE);
+ LOG_LCHAN(from_lchan, LOGL_NOTICE, "VTY requests re-assignment of this lchan to %s%s\n",
+ gsm_lchan_name(to_lchan), to_lchan->vamos.is_secondary ? " (to VAMOS mode)" : "");
+ LOG_LCHAN(to_lchan, LOGL_NOTICE, "VTY requests re-assignment of %s to this lchan%s TSC %d/%d\n",
+ gsm_lchan_name(from_lchan), to_lchan->vamos.is_secondary ? " (to VAMOS mode)" : "",
+ tsc_set, tsc);
+ if (reassignment_request_to_lchan(ASSIGN_FOR_VTY, from_lchan, to_lchan, tsc_set, tsc)) {
+ vty_out(vty, "failed to request re-assignment%s", VTY_NEWLINE);
return CMD_WARNING;
}
-
- bts->si_unused_send_empty = false;
-
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_early_cm, cfg_bts_early_cm_cmd,
- "early-classmark-sending (allowed|forbidden)",
- "Early Classmark Sending\n"
- "Early Classmark Sending is allowed\n"
- "Early Classmark Sending is forbidden\n")
+DEFUN(ctrl_trap, ctrl_trap_cmd,
+ "ctrl-interface generate-trap TRAP VALUE",
+ "Commands related to the CTRL Interface\n"
+ "Generate a TRAP for test purpose\n"
+ "Identity/Name of the TRAP variable\n"
+ "Value of the TRAP variable\n")
{
- struct gsm_bts *bts = vty->index;
-
- if (!strcmp(argv[0], "allowed"))
- bts->early_classmark_allowed = true;
- else
- bts->early_classmark_allowed = false;
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ ctrl_cmd_send_trap(net->ctrl, argv[0], (char *) argv[1]);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_early_cm_3g, cfg_bts_early_cm_3g_cmd,
- "early-classmark-sending-3g (allowed|forbidden)",
- "3G Early Classmark Sending\n"
- "3G Early Classmark Sending is allowed\n"
- "3G Early Classmark Sending is forbidden\n")
-{
- struct gsm_bts *bts = vty->index;
-
- if (!strcmp(argv[0], "allowed"))
- bts->early_classmark_allowed_3g = true;
- else
- bts->early_classmark_allowed_3g = false;
-
- return CMD_SUCCESS;
-}
+#define NETWORK_STR "Configure the GSM network\n"
+#define CODE_CMD_STR "Code commands\n"
+#define NAME_CMD_STR "Name Commands\n"
+#define NAME_STR "Name to use\n"
-DEFUN(cfg_bts_neigh_mode, cfg_bts_neigh_mode_cmd,
- "neighbor-list mode (automatic|manual|manual-si5)",
- "Neighbor List\n" "Mode of Neighbor List generation\n"
- "Automatically from all BTS in this OpenBSC\n" "Manual\n"
- "Manual with different lists for SI2 and SI5\n")
+DEFUN_ATTR(cfg_net,
+ cfg_net_cmd,
+ "network", NETWORK_STR,
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts *bts = vty->index;
- int mode = get_string_value(bts_neigh_mode_strs, argv[0]);
-
- switch (mode) {
- case NL_MODE_MANUAL_SI5SEP:
- case NL_MODE_MANUAL:
- /* make sure we clear the current list when switching to
- * manual mode */
- if (bts->neigh_list_manual_mode == 0)
- memset(&bts->si_common.data.neigh_list, 0,
- sizeof(bts->si_common.data.neigh_list));
- break;
- default:
- break;
- }
-
- bts->neigh_list_manual_mode = mode;
+ vty->index = gsmnet_from_vty(vty);
+ vty->node = GSMNET_NODE;
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_neigh, cfg_bts_neigh_cmd,
- "neighbor-list (add|del) arfcn <0-1023>",
- "Neighbor List\n" "Add to manual neighbor list\n"
- "Delete from manual neighbor list\n" "ARFCN of neighbor\n"
- "ARFCN of neighbor\n")
+DEFUN_USRATTR(cfg_net_ncc,
+ cfg_net_ncc_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "network country code <1-999>",
+ "Set the GSM network country code\n"
+ "Country commands\n"
+ CODE_CMD_STR
+ "Network Country Code to use\n")
{
- struct gsm_bts *bts = vty->index;
- struct bitvec *bv = &bts->si_common.neigh_list;
- uint16_t arfcn = atoi(argv[1]);
- enum gsm_band unused;
-
- if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) {
- vty_out(vty, "%% Cannot configure neighbor list in "
- "automatic mode%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ uint16_t mcc;
- if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
- vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ if (osmo_mcc_from_str(argv[0], &mcc)) {
+ vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
- if (!strcmp(argv[0], "add"))
- bitvec_set_bit_pos(bv, arfcn, 1);
- else
- bitvec_set_bit_pos(bv, arfcn, 0);
+ gsmnet->plmn.mcc = mcc;
return CMD_SUCCESS;
}
-/* help text should be kept in sync with EARFCN_*_INVALID defines */
-DEFUN(cfg_bts_si2quater_neigh_add, cfg_bts_si2quater_neigh_add_cmd,
- "si2quater neighbor-list add earfcn <0-65535> thresh-hi <0-31> "
- "thresh-lo <0-32> prio <0-8> qrxlv <0-32> meas <0-8>",
- "SI2quater Neighbor List\n" "SI2quater Neighbor List\n"
- "Add to manual SI2quater neighbor list\n"
- "EARFCN of neighbor\n" "EARFCN of neighbor\n"
- "threshold high bits\n" "threshold high bits\n"
- "threshold low bits\n" "threshold low bits (32 means NA)\n"
- "priority\n" "priority (8 means NA)\n"
- "QRXLEVMIN\n" "QRXLEVMIN (32 means NA)\n"
- "measurement bandwidth\n" "measurement bandwidth (8 means NA)\n")
-{
- struct gsm_bts *bts = vty->index;
- struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
- uint16_t arfcn = atoi(argv[0]);
- uint8_t thresh_hi = atoi(argv[1]), thresh_lo = atoi(argv[2]),
- prio = atoi(argv[3]), qrx = atoi(argv[4]), meas = atoi(argv[5]);
- int r = bts_earfcn_add(bts, arfcn, thresh_hi, thresh_lo, prio, qrx, meas);
-
- switch (r) {
- case 1:
- vty_out(vty, "Warning: multiple threshold-high are not supported, overriding with %u%s",
- thresh_hi, VTY_NEWLINE);
- break;
- case EARFCN_THRESH_LOW_INVALID:
- vty_out(vty, "Warning: multiple threshold-low are not supported, overriding with %u%s",
- thresh_lo, VTY_NEWLINE);
- break;
- case EARFCN_QRXLV_INVALID + 1:
- vty_out(vty, "Warning: multiple QRXLEVMIN are not supported, overriding with %u%s",
- qrx, VTY_NEWLINE);
- break;
- case EARFCN_PRIO_INVALID:
- vty_out(vty, "Warning: multiple priorities are not supported, overriding with %u%s",
- prio, VTY_NEWLINE);
- break;
- default:
- if (r < 0) {
- vty_out(vty, "Unable to add ARFCN %u: %s%s", arfcn, strerror(-r), VTY_NEWLINE);
- return CMD_WARNING;
- }
- }
-
- if (si2q_num(bts) <= SI2Q_MAX_NUM)
- return CMD_SUCCESS;
-
- vty_out(vty, "Warning: not enough space in SI2quater (%u/%u used) for a given EARFCN %u%s",
- bts->si2q_count, SI2Q_MAX_NUM, arfcn, VTY_NEWLINE);
- osmo_earfcn_del(e, arfcn);
-
- return CMD_WARNING;
-}
+DEFUN_USRATTR(cfg_net_mnc,
+ cfg_net_mnc_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "mobile network code <0-999>",
+ "Set the GSM mobile network code\n"
+ "Network Commands\n"
+ CODE_CMD_STR
+ "Mobile Network Code to use\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ uint16_t mnc;
+ bool mnc_3_digits;
-DEFUN(cfg_bts_si2quater_neigh_del, cfg_bts_si2quater_neigh_del_cmd,
- "si2quater neighbor-list del earfcn <0-65535>",
- "SI2quater Neighbor List\n"
- "SI2quater Neighbor List\n"
- "Delete from SI2quater manual neighbor list\n"
- "EARFCN of neighbor\n"
- "EARFCN\n")
-{
- struct gsm_bts *bts = vty->index;
- struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
- uint16_t arfcn = atoi(argv[0]);
- int r = osmo_earfcn_del(e, arfcn);
- if (r < 0) {
- vty_out(vty, "Unable to delete arfcn %u: %s%s", arfcn,
- strerror(-r), VTY_NEWLINE);
+ if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) {
+ vty_out(vty, "%% Error decoding MNC: %s%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
+ gsmnet->plmn.mnc = mnc;
+ gsmnet->plmn.mnc_3_digits = mnc_3_digits;
+
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_si2quater_uarfcn_add, cfg_bts_si2quater_uarfcn_add_cmd,
- "si2quater neighbor-list add uarfcn <0-16383> <0-511> <0-1>",
- "SI2quater Neighbor List\n"
- "SI2quater Neighbor List\n" "Add to manual SI2quater neighbor list\n"
- "UARFCN of neighbor\n" "UARFCN of neighbor\n" "scrambling code\n"
- "diversity bit\n")
+DEFUN_USRATTR(cfg_net_encryption,
+ cfg_net_encryption_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "encryption a5 <0-4> [<0-4>] [<0-4>] [<0-4>] [<0-4>]",
+ "Encryption options\n"
+ "GSM A5 Air Interface Encryption\n"
+ "A5/n Algorithm Number\n"
+ "A5/n Algorithm Number\n"
+ "A5/n Algorithm Number\n"
+ "A5/n Algorithm Number\n"
+ "A5/n Algorithm Number\n")
{
- struct gsm_bts *bts = vty->index;
- uint16_t arfcn = atoi(argv[0]), scramble = atoi(argv[1]);
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ unsigned int i;
- switch(bts_uarfcn_add(bts, arfcn, scramble, atoi(argv[2]))) {
- case -ENOMEM:
- vty_out(vty, "Unable to add UARFCN: max number of UARFCNs (%u) reached%s", MAX_EARFCN_LIST, VTY_NEWLINE);
- return CMD_WARNING;
- case -ENOSPC:
- vty_out(vty, "Warning: not enough space in SI2quater for a given UARFCN (%u, %u)%s",
- arfcn, scramble, VTY_NEWLINE);
- return CMD_WARNING;
- case -EADDRINUSE:
- vty_out(vty, "Unable to add UARFCN: (%u, %u) is already added%s", arfcn, scramble, VTY_NEWLINE);
- return CMD_WARNING;
- }
+ gsmnet->a5_encryption_mask = 0;
+ for (i = 0; i < argc; i++)
+ gsmnet->a5_encryption_mask |= (1 << atoi(argv[i]));
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_si2quater_uarfcn_del, cfg_bts_si2quater_uarfcn_del_cmd,
- "si2quater neighbor-list del uarfcn <0-16383> <0-511>",
- "SI2quater Neighbor List\n"
- "SI2quater Neighbor List\n"
- "Delete from SI2quater manual neighbor list\n"
- "UARFCN of neighbor\n"
- "UARFCN\n"
- "scrambling code\n")
+DEFUN_DEPRECATED(cfg_net_dyn_ts_allow_tch_f,
+ cfg_net_dyn_ts_allow_tch_f_cmd,
+ "dyn_ts_allow_tch_f (0|1)",
+ "Allow or disallow allocating TCH/F on TCH_F_TCH_H_PDCH timeslots\n"
+ "Disallow TCH/F on TCH_F_TCH_H_PDCH (default)\n"
+ "Allow TCH/F on TCH_F_TCH_H_PDCH\n")
{
- struct gsm_bts *bts = vty->index;
-
- if (bts_uarfcn_del(bts, atoi(argv[0]), atoi(argv[1])) < 0) {
- vty_out(vty, "Unable to delete uarfcn: pair not found%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
-
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->dyn_ts_allow_tch_f = atoi(argv[0]) ? true : false;
+ vty_out(vty, "%% dyn_ts_allow_tch_f is deprecated, rather use msc/codec-list to pick codecs%s",
+ VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd,
- "si5 neighbor-list (add|del) arfcn <0-1023>",
- "SI5 Neighbor List\n"
- "SI5 Neighbor List\n" "Add to manual SI5 neighbor list\n"
- "Delete from SI5 manual neighbor list\n" "ARFCN of neighbor\n"
- "ARFCN of neighbor\n")
+DEFUN_ATTR(cfg_net_timezone,
+ cfg_net_timezone_cmd,
+ "timezone <-19-19> (0|15|30|45)",
+ "Set the Timezone Offset of the network\n"
+ "Timezone offset (hours)\n"
+ "Timezone offset (00 minutes)\n"
+ "Timezone offset (15 minutes)\n"
+ "Timezone offset (30 minutes)\n"
+ "Timezone offset (45 minutes)\n",
+ CMD_ATTR_IMMEDIATE)
{
- enum gsm_band unused;
- struct gsm_bts *bts = vty->index;
- struct bitvec *bv = &bts->si_common.si5_neigh_list;
- uint16_t arfcn = atoi(argv[1]);
-
- if (!bts->neigh_list_manual_mode) {
- vty_out(vty, "%% Cannot configure neighbor list in "
- "automatic mode%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
- vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
- return CMD_WARNING;
- }
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int tzhr = atoi(argv[0]);
+ int tzmn = atoi(argv[1]);
- if (!strcmp(argv[0], "add"))
- bitvec_set_bit_pos(bv, arfcn, 1);
- else
- bitvec_set_bit_pos(bv, arfcn, 0);
+ net->tz.hr = tzhr;
+ net->tz.mn = tzmn;
+ net->tz.dst = 0;
+ net->tz.override = 1;
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd,
- "pcu-socket PATH",
- "PCU Socket Path for using OsmoPCU co-located with BSC (legacy BTS)\n"
- "Path in the file system for the unix-domain PCU socket\n")
+DEFUN_ATTR(cfg_net_timezone_dst,
+ cfg_net_timezone_dst_cmd,
+ "timezone <-19-19> (0|15|30|45) <0-2>",
+ "Set the Timezone Offset of the network\n"
+ "Timezone offset (hours)\n"
+ "Timezone offset (00 minutes)\n"
+ "Timezone offset (15 minutes)\n"
+ "Timezone offset (30 minutes)\n"
+ "Timezone offset (45 minutes)\n"
+ "DST offset (hours)\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts *bts = vty->index;
- int rc;
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int tzhr = atoi(argv[0]);
+ int tzmn = atoi(argv[1]);
+ int tzdst = atoi(argv[2]);
- osmo_talloc_replace_string(bts, &bts->pcu_sock_path, argv[0]);
- pcu_sock_exit(bts);
- rc = pcu_sock_init(bts->pcu_sock_path, bts);
- if (rc < 0) {
- vty_out(vty, "%% Error creating PCU socket `%s' for BTS %u%s",
- bts->pcu_sock_path, bts->nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
+ net->tz.hr = tzhr;
+ net->tz.mn = tzmn;
+ net->tz.dst = tzdst;
+ net->tz.override = 1;
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_acc_ramping,
- cfg_bts_acc_ramping_cmd,
- "access-control-class-ramping",
- "Enable Access Control Class ramping\n")
+DEFUN_ATTR(cfg_net_no_timezone,
+ cfg_net_no_timezone_cmd,
+ "no timezone",
+ NO_STR
+ "Disable network timezone override, use system tz\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts *bts = vty->index;
+ struct gsm_network *net = gsmnet_from_vty(vty);
- if (!acc_ramp_is_enabled(&bts->acc_ramp))
- acc_ramp_set_enabled(&bts->acc_ramp, true);
+ net->tz.override = 0;
- /*
- * ACC ramping takes effect either when the BTS reconnects RSL,
- * or when RF administrative state changes to 'unlocked'.
- */
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_no_acc_ramping, cfg_bts_no_acc_ramping_cmd,
- "no access-control-class-ramping",
- NO_STR
- "Disable Access Control Class ramping\n")
+DEFUN_USRATTR(cfg_net_per_loc_upd,
+ cfg_net_per_loc_upd_cmd,
+ BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK,
+ "periodic location update <6-1530>",
+ "Periodic Location Updating Interval\n"
+ "Periodic Location Updating Interval\n"
+ "Periodic Location Updating Interval\n"
+ "Periodic Location Updating Interval in Minutes\n")
{
- struct gsm_bts *bts = vty->index;
-
- if (acc_ramp_is_enabled(&bts->acc_ramp)) {
- acc_ramp_abort(&bts->acc_ramp);
- acc_ramp_set_enabled(&bts->acc_ramp, false);
- gsm_bts_set_system_infos(bts);
- }
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct osmo_tdef *d = osmo_tdef_get_entry(net->T_defs, 3212);
+ OSMO_ASSERT(d);
+ d->val = atoi(argv[0]) / 6;
+ vty_out(vty, "T%d = %lu %s (%s)%s", d->T, d->val, "* 6min", d->desc, VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_acc_ramping_step_interval,
- cfg_bts_acc_ramping_step_interval_cmd,
- "access-control-class-ramping-step-interval (<"
- OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MIN) "-"
- OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MAX) ">|dynamic)",
- "Configure Access Control Class ramping step interval\n"
- "Set a fixed step interval (in seconds)\n"
- "Use dynamic step interval based on BTS channel load\n")
+DEFUN_USRATTR(cfg_net_no_per_loc_upd,
+ cfg_net_no_per_loc_upd_cmd,
+ BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK,
+ "no periodic location update",
+ NO_STR
+ "Periodic Location Updating Interval\n"
+ "Periodic Location Updating Interval\n"
+ "Periodic Location Updating Interval\n")
{
- struct gsm_bts *bts = vty->index;
- bool dynamic = (strcmp(argv[0], "dynamic") == 0);
- int error;
-
- if (dynamic) {
- acc_ramp_set_step_interval_dynamic(&bts->acc_ramp);
- return CMD_SUCCESS;
- }
-
- error = acc_ramp_set_step_interval(&bts->acc_ramp, atoi(argv[0]));
- if (error != 0) {
- if (error == -ERANGE)
- vty_out(vty, "Unable to set ACC ramp step interval: value out of range%s", VTY_NEWLINE);
- else
- vty_out(vty, "Unable to set ACC ramp step interval: unknown error%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct osmo_tdef *d = osmo_tdef_get_entry(net->T_defs, 3212);
+ OSMO_ASSERT(d);
+ d->val = 0;
+ vty_out(vty, "T%d = %lu %s (%s)%s", d->T, d->val, "* 6min", d->desc, VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_acc_ramping_step_size,
- cfg_bts_acc_ramping_step_size_cmd,
- "access-control-class-ramping-step-size (<"
- OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MIN) "-"
- OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MAX) ">)",
- "Configure Access Control Class ramping step size\n"
- "Set the number of Access Control Classes to enable per ramping step\n")
+#define MEAS_FEED_STR "Measurement Report export\n"
+
+DEFUN_ATTR(cfg_net_meas_feed_dest, cfg_net_meas_feed_dest_cmd,
+ "meas-feed destination ADDR <0-65535>",
+ MEAS_FEED_STR "Where to forward Measurement Report feeds\n" "address or hostname\n" "port number\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts *bts = vty->index;
- int error;
+ int rc;
+ const char *host = argv[0];
+ uint16_t port = atoi(argv[1]);
- error = acc_ramp_set_step_size(&bts->acc_ramp, atoi(argv[0]));
- if (error != 0) {
- if (error == -ERANGE)
- vty_out(vty, "Unable to set ACC ramp step size: value out of range%s", VTY_NEWLINE);
- else
- vty_out(vty, "Unable to set ACC ramp step size: unknown error%s", VTY_NEWLINE);
+ rc = meas_feed_cfg_set(host, port);
+ if (rc < 0)
return CMD_WARNING;
- }
return CMD_SUCCESS;
}
-#define EXCL_RFLOCK_STR "Exclude this BTS from the global RF Lock\n"
-
-DEFUN(cfg_bts_excl_rf_lock,
- cfg_bts_excl_rf_lock_cmd,
- "rf-lock-exclude",
- EXCL_RFLOCK_STR)
+DEFUN_ATTR(cfg_net_meas_feed_scenario, cfg_net_meas_feed_scenario_cmd,
+ "meas-feed scenario NAME",
+ MEAS_FEED_STR "Set a name to include in the Measurement Report feeds\n" "Name string, up to 31 characters\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts *bts = vty->index;
- bts->excl_from_rf_lock = 1;
+ meas_feed_scenario_set(argv[0]);
+
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_no_excl_rf_lock,
- cfg_bts_no_excl_rf_lock_cmd,
- "no rf-lock-exclude",
- NO_STR EXCL_RFLOCK_STR)
+DEFUN_ATTR(cfg_net_meas_feed_wqueue_max_len, cfg_net_meas_feed_wqueue_max_len_cmd,
+ "meas-feed write-queue-max-length <1-65535>",
+ MEAS_FEED_STR "Set the maximum length of the message write queue towards the UDP socket\n"
+ "Maximum number of messages to be queued waiting for transmission\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts *bts = vty->index;
- bts->excl_from_rf_lock = 0;
+ meas_feed_txqueue_max_length_set(atoi(argv[0]));
return CMD_SUCCESS;
}
-#define FORCE_COMB_SI_STR "Force the generation of a single SI (no ter/bis)\n"
-
-DEFUN(cfg_bts_force_comb_si,
- cfg_bts_force_comb_si_cmd,
- "force-combined-si",
- FORCE_COMB_SI_STR)
+static void legacy_timers(struct vty *vty, const char **T_arg)
{
- struct gsm_bts *bts = vty->index;
- bts->force_combined_si = 1;
- bts->force_combined_si_set = true;
- return CMD_SUCCESS;
+ if (!strcmp((*T_arg), "T993111") || !strcmp((*T_arg), "t993111")) {
+ vty_out(vty, "%% Legacy: timer T993111 is now X3111%s", VTY_NEWLINE);
+ (*T_arg) = "X3111";
+ } else if (!strcmp((*T_arg), "T993210") || !strcmp((*T_arg), "t993210")) {
+ vty_out(vty, "%% Legacy: timer T993210 is now X3210%s", VTY_NEWLINE);
+ (*T_arg) = "X3210";
+ } else if (!strcmp((*T_arg), "T999") || !strcmp((*T_arg), "t999")) {
+ vty_out(vty, "%% Legacy: timer T999 is now X4%s", VTY_NEWLINE);
+ (*T_arg) = "X4";
+ }
}
-DEFUN(cfg_bts_no_force_comb_si,
- cfg_bts_no_force_comb_si_cmd,
- "no force-combined-si",
- NO_STR FORCE_COMB_SI_STR)
+/* LEGACY TIMER COMMAND. The proper commands are added by osmo_tdef_vty_groups_init(), using explicit timer group
+ * naming. The old groupless timer command accesses the 'net' group only, but is still available. */
+DEFUN_HIDDEN(show_timer, show_timer_cmd,
+ "show timer " OSMO_TDEF_VTY_ARG_T,
+ SHOW_STR "Show timers\n"
+ OSMO_TDEF_VTY_DOC_T)
{
- struct gsm_bts *bts = vty->index;
- bts->force_combined_si = 0;
- bts->force_combined_si_set = true;
- return CMD_SUCCESS;
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ const char *T_arg = argv[0];
+ if (T_arg)
+ legacy_timers(vty, &T_arg);
+ return osmo_tdef_vty_show_cmd(vty, net->T_defs, T_arg, NULL);
}
-static void _get_codec_from_arg(struct vty *vty, int argc, const char *argv[])
+/* LEGACY TIMER COMMAND. The proper commands are added by osmo_tdef_vty_groups_init(), using explicit timer group
+ * naming. The old groupless timer command accesses the 'net' group only, but is still available. */
+DEFUN_HIDDEN(cfg_net_timer, cfg_net_timer_cmd,
+ "timer " OSMO_TDEF_VTY_ARG_T " " OSMO_TDEF_VTY_ARG_VAL_OPTIONAL,
+ "Configure or show timers\n"
+ OSMO_TDEF_VTY_DOC_SET)
{
- struct gsm_bts *bts = vty->index;
- struct bts_codec_conf *codec = &bts->codec;
- int i;
-
- codec->hr = 0;
- codec->efr = 0;
- codec->amr = 0;
- for (i = 0; i < argc; i++) {
- if (!strcmp(argv[i], "hr"))
- codec->hr = 1;
- if (!strcmp(argv[i], "efr"))
- codec->efr = 1;
- if (!strcmp(argv[i], "amr"))
- codec->amr = 1;
- }
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ const char *mod_argv[argc];
+ memcpy(mod_argv, argv, sizeof(mod_argv));
+ legacy_timers(vty, &mod_argv[0]);
+ /* If any arguments are missing, redirect to 'show' */
+ if (argc < 2)
+ return show_timer(self, vty, argc, mod_argv);
+ return osmo_tdef_vty_set_cmd(vty, net->T_defs, mod_argv);
}
-#define CODEC_PAR_STR " (hr|efr|amr)"
-#define CODEC_HELP_STR "Half Rate\n" \
- "Enhanced Full Rate\nAdaptive Multirate\n"
-
-DEFUN(cfg_bts_codec0, cfg_bts_codec0_cmd,
- "codec-support fr",
- "Codec Support settings\nFullrate\n")
+DEFUN(cfg_net_allow_unusable_timeslots, cfg_net_allow_unusable_timeslots_cmd,
+ "allow-unusable-timeslots",
+ "Don't refuse to start with mutually exclusive codec settings\n")
{
- _get_codec_from_arg(vty, 0, argv);
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ net->allow_unusable_timeslots = true;
+ LOGP(DMSC, LOGL_ERROR, "Configuration contains 'allow-unusable-timeslots'. OsmoBSC will start up even if the"
+ " configuration has unusable codec settings!\n");
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_codec1, cfg_bts_codec1_cmd,
- "codec-support fr" CODEC_PAR_STR,
- "Codec Support settings\nFullrate\n"
- CODEC_HELP_STR)
+DEFUN_ATTR(cfg_bts_pcu_sock_wqueue_len, cfg_bts_pcu_sock_wqueue_len_cmd,
+ "pcu-socket-wqueue-length <1-2147483646>",
+ "Configure the PCU socket queue length\n"
+ "Queue length\n",
+ CMD_ATTR_IMMEDIATE)
{
- _get_codec_from_arg(vty, 1, argv);
+ size_t dropped_msgs = 0;
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ size_t old = net->pcu_sock_wqueue_len_max;
+ net->pcu_sock_wqueue_len_max = atoi(argv[0]);
+ if (net->pcu_state)
+ dropped_msgs = osmo_wqueue_set_maxlen(&net->pcu_state->upqueue, net->pcu_sock_wqueue_len_max);
+ if (dropped_msgs) {
+ LOGP(DPCU, LOGL_INFO, "Have dropped %zu messages due to shortened max. message queue size (from: %zu to %u)\n",
+ dropped_msgs, old, net->pcu_sock_wqueue_len_max);
+ vty_out(vty, "Have dropped %zu messages due to shortened max. message queue size (from: %zu to %u)%s",
+ dropped_msgs, old, net->pcu_sock_wqueue_len_max, VTY_NEWLINE);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_codec2, cfg_bts_codec2_cmd,
- "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR,
- "Codec Support settings\nFullrate\n"
- CODEC_HELP_STR CODEC_HELP_STR)
+DEFUN_ATTR(cfg_net_pcu_sock_path,
+ cfg_net_pcu_sock_path_cmd,
+ "pcu-socket PATH",
+ "PCU Socket Path for using OsmoPCU co-located with BSC\n"
+ "Path in the file system for the unix-domain PCU socket\n",
+ CMD_ATTR_IMMEDIATE)
{
- _get_codec_from_arg(vty, 2, argv);
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int rc;
+
+ osmo_talloc_replace_string(net, &net->pcu_sock_path, argv[0]);
+ pcu_sock_exit(net);
+ rc = pcu_sock_init(net);
+ if (rc < 0) {
+ vty_out(vty, "%% Error creating PCU socket `%s'%s",
+ net->pcu_sock_path, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_codec3, cfg_bts_codec3_cmd,
- "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR,
- "Codec Support settings\nFullrate\n"
- CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR)
+DEFUN_ATTR(cfg_net_no_pcu_sock,
+ cfg_net_no_pcu_sock_cmd,
+ "no pcu-socket",
+ NO_STR "Disable BSC co-located PCU\n",
+ CMD_ATTR_IMMEDIATE)
{
- _get_codec_from_arg(vty, 3, argv);
+ struct gsm_network *net = gsmnet_from_vty(vty);
+
+ pcu_sock_exit(net);
+ talloc_free(net->pcu_sock_path);
+ net->pcu_sock_path = NULL;
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_codec4, cfg_bts_codec4_cmd,
- "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR,
- "Codec Support settings\nFullrate\n"
- CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR)
+static struct bsc_msc_data *bsc_msc_data(struct vty *vty)
{
- _get_codec_from_arg(vty, 4, argv);
- return CMD_SUCCESS;
+ return vty->index;
}
-DEFUN(cfg_bts_depends_on, cfg_bts_depends_on_cmd,
- "depends-on-bts <0-255>",
- "This BTS can only be started if another one is up\n"
- BTS_NR_STR)
-{
- struct gsm_bts *bts = vty->index;
- struct gsm_bts *other_bts;
- int dep = atoi(argv[0]);
+static struct cmd_node bsc_node = {
+ BSC_NODE,
+ "%s(config-bsc)# ",
+ 1,
+};
+static struct cmd_node msc_node = {
+ MSC_NODE,
+ "%s(config-msc)# ",
+ 1,
+};
- if (!is_ipaccess_bts(bts)) {
- vty_out(vty, "This feature is only available for IP systems.%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
+#define MSC_NR_RANGE "<0-1000>"
- other_bts = gsm_bts_num(bts->network, dep);
- if (!other_bts || !is_ipaccess_bts(other_bts)) {
- vty_out(vty, "This feature is only available for IP systems.%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
+DEFUN_ATTR(cfg_net_msc,
+ cfg_net_msc_cmd,
+ "msc [" MSC_NR_RANGE "]", "Configure MSC details\n" "MSC connection to configure\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ int index = argc == 1 ? atoi(argv[0]) : 0;
+ struct bsc_msc_data *msc;
- if (dep >= bts->nr) {
- vty_out(vty, "%%Need to depend on an already declared unit.%s",
- VTY_NEWLINE);
+ msc = osmo_msc_data_alloc(bsc_gsmnet, index);
+ if (!msc) {
+ vty_out(vty, "%% Failed to allocate MSC data.%s", VTY_NEWLINE);
return CMD_WARNING;
}
- bts_depend_mark(bts, dep);
+ vty->index = msc;
+ vty->node = MSC_NODE;
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_no_depends_on, cfg_bts_no_depends_on_cmd,
- "depeneds-on-bts <0-255>",
- NO_STR "This BTS can only be started if another one is up\n"
- BTS_NR_STR)
+DEFUN_ATTR(cfg_net_bsc,
+ cfg_net_bsc_cmd,
+ "bsc", "Configure BSC\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts *bts = vty->index;
- int dep = atoi(argv[0]);
-
- bts_depend_clear(bts, dep);
+ vty->node = BSC_NODE;
return CMD_SUCCESS;
}
-#define AMR_TEXT "Adaptive Multi Rate settings\n"
-#define AMR_MODE_TEXT "Codec modes to use with AMR codec\n"
-#define AMR_START_TEXT "Initial codec to use with AMR\n" \
- "Automatically\nFirst codec\nSecond codec\nThird codec\nFourth codec\n"
-#define AMR_TH_TEXT "AMR threshold between codecs\nMS side\nBTS side\n"
-#define AMR_HY_TEXT "AMR hysteresis between codecs\nMS side\nBTS side\n"
-
-static void get_amr_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+static void write_msc_amr_options(struct vty *vty, struct bsc_msc_data *msc)
{
- struct gsm_bts *bts = vty->index;
- struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
- struct gsm48_multi_rate_conf *mr_conf =
- (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
- int i;
- int mode;
- int mode_prev = -1;
-
- /* Check if mode parameters are in order */
- for (i = 0; i < argc; i++) {
- mode = atoi(argv[i]);
- if (mode_prev > mode) {
- vty_out(vty, "Modes must be listed in order%s",
- VTY_NEWLINE);
- return;
- }
-
- if (mode_prev == mode) {
- vty_out(vty, "Modes must be unique %s", VTY_NEWLINE);
- return;
- }
- mode_prev = mode;
- }
-
- /* Prepare the multirate configuration IE */
- mr->gsm48_ie[1] = 0;
- for (i = 0; i < argc; i++)
- mr->gsm48_ie[1] |= 1 << atoi(argv[i]);
- mr_conf->icmi = 0;
-
- /* Store actual mode identifier values */
- for (i = 0; i < argc; i++) {
- mr->ms_mode[i].mode = atoi(argv[i]);
- mr->bts_mode[i].mode = atoi(argv[i]);
- }
- mr->num_modes = argc;
+#define WRITE_AMR(vty, msc, name, var) \
+ vty_out(vty, " amr-config %s %s%s", \
+ name, msc->amr_conf.var ? "allowed" : "forbidden", \
+ VTY_NEWLINE);
- /* Trim excess threshold and hysteresis values from previous config */
- for (i = argc - 1; i < 4; i++) {
- mr->ms_mode[i].threshold = 0;
- mr->bts_mode[i].threshold = 0;
- mr->ms_mode[i].hysteresis = 0;
- mr->bts_mode[i].hysteresis = 0;
- }
+ WRITE_AMR(vty, msc, "12_2k", m12_2);
+ WRITE_AMR(vty, msc, "10_2k", m10_2);
+ WRITE_AMR(vty, msc, "7_95k", m7_95);
+ WRITE_AMR(vty, msc, "7_40k", m7_40);
+ WRITE_AMR(vty, msc, "6_70k", m6_70);
+ WRITE_AMR(vty, msc, "5_90k", m5_90);
+ WRITE_AMR(vty, msc, "5_15k", m5_15);
+ WRITE_AMR(vty, msc, "4_75k", m4_75);
+#undef WRITE_AMR
+
+ if (msc->amr_octet_aligned)
+ vty_out(vty, " amr-payload octet-aligned%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " amr-payload bandwidth-efficient%s", VTY_NEWLINE);
}
-static void get_amr_th_from_arg(struct vty *vty, int argc, const char *argv[], int full)
-{
- struct gsm_bts *bts = vty->index;
- struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
- struct amr_mode *modes;
- int i;
-
- modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode;
- for (i = 0; i < argc - 1; i++)
- modes[i].threshold = atoi(argv[i + 1]);
-}
+static void msc_write_nri(struct vty *vty, struct bsc_msc_data *msc, bool verbose);
-static void get_amr_hy_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+static void write_msc(struct vty *vty, struct bsc_msc_data *msc)
{
- struct gsm_bts *bts = vty->index;
- struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
- struct amr_mode *modes;
- int i;
+ vty_out(vty, "msc %d%s", msc->nr, VTY_NEWLINE);
+ if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID)
+ vty_out(vty, " core-mobile-network-code %s%s",
+ osmo_mnc_name(msc->core_plmn.mnc, msc->core_plmn.mnc_3_digits), VTY_NEWLINE);
+ if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
+ vty_out(vty, " core-mobile-country-code %s%s",
+ osmo_mcc_name(msc->core_plmn.mcc), VTY_NEWLINE);
- modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode;
- for (i = 0; i < argc - 1; i++)
- modes[i].hysteresis = atoi(argv[i + 1]);
-}
+ if (msc->audio_length != 0) {
+ int i;
-static void get_amr_start_from_arg(struct vty *vty, const char *argv[], int full)
-{
- struct gsm_bts *bts = vty->index;
- struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
- struct gsm48_multi_rate_conf *mr_conf =
- (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
- int num = 0, i;
+ vty_out(vty, " codec-list ");
+ for (i = 0; i < msc->audio_length; ++i) {
+ if (i != 0)
+ vty_out(vty, " ");
- for (i = 0; i < ((full) ? 8 : 6); i++) {
- if ((mr->gsm48_ie[1] & (1 << i))) {
- num++;
+ if (msc->audio_support[i].hr)
+ vty_out(vty, "hr%u", msc->audio_support[i].ver);
+ else
+ vty_out(vty, "fr%u", msc->audio_support[i].ver);
}
- }
+ vty_out(vty, "%s", VTY_NEWLINE);
- if (argv[0][0] == 'a' || num == 0)
- mr_conf->icmi = 0;
- else {
- mr_conf->icmi = 1;
- if (num < atoi(argv[0]))
- mr_conf->smod = num - 1;
- else
- mr_conf->smod = atoi(argv[0]) - 1;
}
-}
-/* Give the current amr configuration a final consistency chack by feeding the
- * the configuration into the gsm48 multirate IE generator function */
-static int check_amr_config(struct vty *vty)
-{
- int rc = 0;
- struct amr_multirate_conf *mr;
- const struct gsm48_multi_rate_conf *mr_conf;
- struct gsm_bts *bts = vty->index;
- int vty_rc = CMD_SUCCESS;
+ vty_out(vty, " allow-emergency %s%s", msc->allow_emerg ?
+ "allow" : "deny", VTY_NEWLINE);
- mr = &bts->mr_full;
- mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie;
- rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes);
- if (rc != 0) {
- vty_out(vty,
- "Invalid AMR multirate configuration (tch-f, ms) - check parameters%s",
- VTY_NEWLINE);
- vty_rc = CMD_WARNING;
- }
+ /* write amr options */
+ write_msc_amr_options(vty, msc);
- rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes);
- if (rc != 0) {
- vty_out(vty,
- "Invalid AMR multirate configuration (tch-f, bts) - check parameters%s",
- VTY_NEWLINE);
- vty_rc = CMD_WARNING;
+ /* write sccp connection configuration */
+ if (msc->a.bsc_addr_name) {
+ vty_out(vty, " bsc-addr %s%s",
+ msc->a.bsc_addr_name, VTY_NEWLINE);
}
-
- mr = &bts->mr_half;
- mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie;
- rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes);
- if (rc != 0) {
- vty_out(vty,
- "Invalid AMR multirate configuration (tch-h, ms) - check parameters%s",
- VTY_NEWLINE);
- vty_rc = CMD_WARNING;
+ if (msc->a.msc_addr_name) {
+ vty_out(vty, " msc-addr %s%s",
+ msc->a.msc_addr_name, VTY_NEWLINE);
}
+ vty_out(vty, " asp-protocol %s%s", osmo_ss7_asp_protocol_name(msc->a.asp_proto), VTY_NEWLINE);
+ vty_out(vty, " lcls-mode %s%s", get_value_string(bsc_lcls_mode_names, msc->lcls_mode),
+ VTY_NEWLINE);
- rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes);
- if (rc != 0) {
- vty_out(vty,
- "Invalid AMR multirate configuration (tch-h, bts) - check parameters%s",
- VTY_NEWLINE);
- vty_rc = CMD_WARNING;
- }
+ if (msc->lcls_codec_mismatch_allow)
+ vty_out(vty, " lcls-codec-mismatch allowed%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " lcls-codec-mismatch forbidden%s", VTY_NEWLINE);
- return vty_rc;
-}
+ /* write MGW configuration */
+ mgcp_client_config_write(vty, " ");
-#define AMR_TCHF_PAR_STR " (0|1|2|3|4|5|6|7)"
-#define AMR_TCHF_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" \
- "10,2k\n12,2k\n"
+ if (msc->x_osmo_ign_configured) {
+ if (!msc->x_osmo_ign)
+ vty_out(vty, " no mgw x-osmo-ign%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " mgw x-osmo-ign call-id%s", VTY_NEWLINE);
+ }
-#define AMR_TCHH_PAR_STR " (0|1|2|3|4|5)"
-#define AMR_TCHH_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n"
+ if (msc->use_osmux != OSMUX_USAGE_OFF) {
+ vty_out(vty, " osmux %s%s", msc->use_osmux == OSMUX_USAGE_ON ? "on" : "only",
+ VTY_NEWLINE);
+ }
-#define AMR_TH_HELP_STR "Threshold between codec 1 and 2\n"
-#define AMR_HY_HELP_STR "Hysteresis between codec 1 and 2\n"
+ msc_write_nri(vty, msc, false);
-DEFUN(cfg_bts_amr_fr_modes1, cfg_bts_amr_fr_modes1_cmd,
- "amr tch-f modes" AMR_TCHF_PAR_STR,
- AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
- AMR_TCHF_HELP_STR)
-{
- get_amr_from_arg(vty, 1, argv, 1);
- return check_amr_config(vty);
+ if (!msc->allow_attach)
+ vty_out(vty, " no allow-attach%s", VTY_NEWLINE);
}
-DEFUN(cfg_bts_amr_fr_modes2, cfg_bts_amr_fr_modes2_cmd,
- "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
- AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
- AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+static int config_write_msc(struct vty *vty)
{
- get_amr_from_arg(vty, 2, argv, 1);
- return check_amr_config(vty);
-}
+ struct bsc_msc_data *msc;
-DEFUN(cfg_bts_amr_fr_modes3, cfg_bts_amr_fr_modes3_cmd,
- "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
- AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
- AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
-{
- get_amr_from_arg(vty, 3, argv, 1);
- return check_amr_config(vty);
-}
+ llist_for_each_entry(msc, &bsc_gsmnet->mscs, entry)
+ write_msc(vty, msc);
-DEFUN(cfg_bts_amr_fr_modes4, cfg_bts_amr_fr_modes4_cmd,
- "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
- AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
- AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
-{
- get_amr_from_arg(vty, 4, argv, 1);
- return check_amr_config(vty);
+ return CMD_SUCCESS;
}
-DEFUN(cfg_bts_amr_fr_start_mode, cfg_bts_amr_fr_start_mode_cmd,
- "amr tch-f start-mode (auto|1|2|3|4)",
- AMR_TEXT "Full Rate\n" AMR_START_TEXT)
+static int config_write_bsc(struct vty *vty)
{
- get_amr_start_from_arg(vty, argv, 1);
- return check_amr_config(vty);
-}
+ vty_out(vty, "bsc%s", VTY_NEWLINE);
+ vty_out(vty, " mid-call-timeout %d%s", bsc_gsmnet->mid_call_timeout, VTY_NEWLINE);
+ if (bsc_gsmnet->rf_ctrl_name)
+ vty_out(vty, " bsc-rf-socket %s%s",
+ bsc_gsmnet->rf_ctrl_name, VTY_NEWLINE);
-DEFUN(cfg_bts_amr_fr_thres1, cfg_bts_amr_fr_thres1_cmd,
- "amr tch-f threshold (ms|bts) <0-63>",
- AMR_TEXT "Full Rate\n" AMR_TH_TEXT
- AMR_TH_HELP_STR)
-{
- get_amr_th_from_arg(vty, 2, argv, 1);
- return check_amr_config(vty);
-}
+ if (bsc_gsmnet->auto_off_timeout != -1)
+ vty_out(vty, " bsc-auto-rf-off %d%s",
+ bsc_gsmnet->auto_off_timeout, VTY_NEWLINE);
-DEFUN(cfg_bts_amr_fr_thres2, cfg_bts_amr_fr_thres2_cmd,
- "amr tch-f threshold (ms|bts) <0-63> <0-63>",
- AMR_TEXT "Full Rate\n" AMR_TH_TEXT
- AMR_TH_HELP_STR AMR_TH_HELP_STR)
-{
- get_amr_th_from_arg(vty, 3, argv, 1);
- return check_amr_config(vty);
-}
+ if (bsc_gsmnet->bts_setup_ramp.enabled)
+ vty_out(vty, " bts-setup-ramping%s", VTY_NEWLINE);
-DEFUN(cfg_bts_amr_fr_thres3, cfg_bts_amr_fr_thres3_cmd,
- "amr tch-f threshold (ms|bts) <0-63> <0-63> <0-63>",
- AMR_TEXT "Full Rate\n" AMR_TH_TEXT
- AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR)
-{
- get_amr_th_from_arg(vty, 4, argv, 1);
- return check_amr_config(vty);
-}
+ if (bsc_gsmnet->bts_setup_ramp.step_size > 0)
+ vty_out(vty, " bts-setup-ramping-step-size %d%s",
+ bsc_gsmnet->bts_setup_ramp.step_size, VTY_NEWLINE);
-DEFUN(cfg_bts_amr_fr_hyst1, cfg_bts_amr_fr_hyst1_cmd,
- "amr tch-f hysteresis (ms|bts) <0-15>",
- AMR_TEXT "Full Rate\n" AMR_HY_TEXT
- AMR_HY_HELP_STR)
-{
- get_amr_hy_from_arg(vty, 2, argv, 1);
- return check_amr_config(vty);
-}
+ if (bsc_gsmnet->bts_setup_ramp.step_interval > 0)
+ vty_out(vty, " bts-setup-ramping-step-interval %d%s",
+ bsc_gsmnet->bts_setup_ramp.step_interval, VTY_NEWLINE);
-DEFUN(cfg_bts_amr_fr_hyst2, cfg_bts_amr_fr_hyst2_cmd,
- "amr tch-f hysteresis (ms|bts) <0-15> <0-15>",
- AMR_TEXT "Full Rate\n" AMR_HY_TEXT
- AMR_HY_HELP_STR AMR_HY_HELP_STR)
-{
- get_amr_hy_from_arg(vty, 3, argv, 1);
return CMD_SUCCESS;
}
-DEFUN(cfg_bts_amr_fr_hyst3, cfg_bts_amr_fr_hyst3_cmd,
- "amr tch-f hysteresis (ms|bts) <0-15> <0-15> <0-15>",
- AMR_TEXT "Full Rate\n" AMR_HY_TEXT
- AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR)
-{
- get_amr_hy_from_arg(vty, 4, argv, 1);
- return check_amr_config(vty);
-}
-
-DEFUN(cfg_bts_amr_hr_modes1, cfg_bts_amr_hr_modes1_cmd,
- "amr tch-h modes" AMR_TCHH_PAR_STR,
- AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
- AMR_TCHH_HELP_STR)
+DEFUN_ATTR(cfg_net_msc_ncc,
+ cfg_net_msc_ncc_cmd,
+ "core-mobile-network-code <1-999>",
+ "Use this network code for the core network\n" "MNC value\n",
+ CMD_ATTR_IMMEDIATE)
{
- get_amr_from_arg(vty, 1, argv, 0);
- return check_amr_config(vty);
-}
-
-DEFUN(cfg_bts_amr_hr_modes2, cfg_bts_amr_hr_modes2_cmd,
- "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
- AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
- AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
-{
- get_amr_from_arg(vty, 2, argv, 0);
- return check_amr_config(vty);
-}
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ uint16_t mnc;
+ bool mnc_3_digits;
-DEFUN(cfg_bts_amr_hr_modes3, cfg_bts_amr_hr_modes3_cmd,
- "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
- AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
- AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
-{
- get_amr_from_arg(vty, 3, argv, 0);
- return check_amr_config(vty);
+ if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) {
+ vty_out(vty, "%% Error decoding MNC: %s%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ data->core_plmn.mnc = mnc;
+ data->core_plmn.mnc_3_digits = mnc_3_digits;
+ return CMD_SUCCESS;
}
-DEFUN(cfg_bts_amr_hr_modes4, cfg_bts_amr_hr_modes4_cmd,
- "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
- AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
- AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+DEFUN_ATTR(cfg_net_msc_mcc,
+ cfg_net_msc_mcc_cmd,
+ "core-mobile-country-code <1-999>",
+ "Use this country code for the core network\n" "MCC value\n",
+ CMD_ATTR_IMMEDIATE)
{
- get_amr_from_arg(vty, 4, argv, 0);
- return check_amr_config(vty);
+ uint16_t mcc;
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ if (osmo_mcc_from_str(argv[0], &mcc)) {
+ vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ data->core_plmn.mcc = mcc;
+ return CMD_SUCCESS;
}
-DEFUN(cfg_bts_amr_hr_start_mode, cfg_bts_amr_hr_start_mode_cmd,
- "amr tch-h start-mode (auto|1|2|3|4)",
- AMR_TEXT "Half Rate\n" AMR_START_TEXT)
+DEFUN_DEPRECATED(cfg_net_msc_lac,
+ cfg_net_msc_lac_cmd,
+ "core-location-area-code <0-65535>",
+ "Legacy configuration that no longer has any effect\n-\n")
{
- get_amr_start_from_arg(vty, argv, 0);
- return check_amr_config(vty);
+ vty_out(vty, "%% Deprecated 'core-location-area-code' config no longer has any effect%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
}
-DEFUN(cfg_bts_amr_hr_thres1, cfg_bts_amr_hr_thres1_cmd,
- "amr tch-h threshold (ms|bts) <0-63>",
- AMR_TEXT "Half Rate\n" AMR_TH_TEXT
- AMR_TH_HELP_STR)
+DEFUN_DEPRECATED(cfg_net_msc_ci,
+ cfg_net_msc_ci_cmd,
+ "core-cell-identity <0-65535>",
+ "Legacy configuration that no longer has any effect\n-\n")
{
- get_amr_th_from_arg(vty, 2, argv, 0);
- return check_amr_config(vty);
+ vty_out(vty, "%% Deprecated 'core-cell-identity' config no longer has any effect%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
}
-DEFUN(cfg_bts_amr_hr_thres2, cfg_bts_amr_hr_thres2_cmd,
- "amr tch-h threshold (ms|bts) <0-63> <0-63>",
- AMR_TEXT "Half Rate\n" AMR_TH_TEXT
- AMR_TH_HELP_STR AMR_TH_HELP_STR)
+DEFUN_DEPRECATED(cfg_net_msc_rtp_base,
+ cfg_net_msc_rtp_base_cmd,
+ "ip.access rtp-base <1-65000>",
+ "deprecated\n" "deprecated, RTP is handled by the MGW\n" "deprecated\n")
{
- get_amr_th_from_arg(vty, 3, argv, 0);
- return check_amr_config(vty);
+ vty_out(vty, "%% deprecated: 'ip.access rtp-base' has no effect, RTP is handled by the MGW%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
}
-DEFUN(cfg_bts_amr_hr_thres3, cfg_bts_amr_hr_thres3_cmd,
- "amr tch-h threshold (ms|bts) <0-63> <0-63> <0-63>",
- AMR_TEXT "Half Rate\n" AMR_TH_TEXT
- AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR)
+DEFUN_USRATTR(cfg_net_msc_codec_list,
+ cfg_net_msc_codec_list_cmd,
+ BSC_VTY_ATTR_NEW_LCHAN,
+ "codec-list .LIST",
+ "Set the allowed audio codecs and their order of preference\n"
+ "List of audio codecs in order of preference, e.g. 'codec-list fr3 fr2 fr1 hr3 hr1'."
+ " (fr3: AMR-FR, hr3: AMR-HR, fr2: GSM-EFR, fr1: GSM-FR, hr1: GSM-HR)\n")
{
- get_amr_th_from_arg(vty, 4, argv, 0);
- return check_amr_config(vty);
-}
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ struct gsm_audio_support tmp[ARRAY_SIZE(data->audio_support)];
+ const char *arg = NULL;
+ int i;
-DEFUN(cfg_bts_amr_hr_hyst1, cfg_bts_amr_hr_hyst1_cmd,
- "amr tch-h hysteresis (ms|bts) <0-15>",
- AMR_TEXT "Half Rate\n" AMR_HY_TEXT
- AMR_HY_HELP_STR)
-{
- get_amr_hy_from_arg(vty, 2, argv, 0);
- return check_amr_config(vty);
-}
+ /* Nr of arguments must fit in the array */
+ if (argc > ARRAY_SIZE(data->audio_support)) {
+ vty_out(vty, "Too many items in 'msc' / 'codec-list': %d. There can be at most %zu entries.%s",
+ argc, ARRAY_SIZE(data->audio_support), VTY_NEWLINE);
+ return CMD_ERR_EXEED_ARGC_MAX;
+ }
-DEFUN(cfg_bts_amr_hr_hyst2, cfg_bts_amr_hr_hyst2_cmd,
- "amr tch-h hysteresis (ms|bts) <0-15> <0-15>",
- AMR_TEXT "Half Rate\n" AMR_HY_TEXT
- AMR_HY_HELP_STR AMR_HY_HELP_STR)
-{
- get_amr_hy_from_arg(vty, 3, argv, 0);
- return check_amr_config(vty);
-}
+ /* check all given arguments first */
+ for (i = 0; i < argc; i++) {
+ int j;
+ int ver;
+ arg = argv[i];
-DEFUN(cfg_bts_amr_hr_hyst3, cfg_bts_amr_hr_hyst3_cmd,
- "amr tch-h hysteresis (ms|bts) <0-15> <0-15> <0-15>",
- AMR_TEXT "Half Rate\n" AMR_HY_TEXT
- AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR)
-{
- get_amr_hy_from_arg(vty, 4, argv, 0);
- return check_amr_config(vty);
-}
+ if (strncmp("hr", arg, 2) == 0)
+ tmp[i].hr = 1;
+ else if (strncmp("fr", arg, 2) == 0)
+ tmp[i].hr = 0;
+ else
+ goto invalid_arg;
-#define TNUM_STR "T-number, optionally preceded by 't' or 'T'\n"
-DEFUN(cfg_bts_t3113_dynamic, cfg_bts_t3113_dynamic_cmd,
- "timer-dynamic TNNNN",
- "Calculate T3113 dynamically based on channel config and load\n"
- TNUM_STR)
-{
- struct T_def *d;
- struct gsm_bts *bts = vty->index;
+ ver = atoi(arg + 2);
+ if (ver < 1 || ver > 7)
+ goto invalid_arg;
+ tmp[i].ver = ver;
- d = parse_T_arg(vty, argv[0]);
- if (!d)
- return CMD_WARNING;
+ if (tmp[i].hr == 1 && ver == 2)
+ goto invalid_arg;
- switch (d->T) {
- case 3113:
- bts->T3113_dynamic = true;
- break;
- default:
- vty_out(vty, "T%d cannot be set to dynamic%s", d->T, VTY_NEWLINE);
- return CMD_WARNING;
+ /* prevent duplicate entries */
+ for (j = 0; j < i; j++) {
+ if (gsm_audio_support_cmp(&tmp[j], &tmp[i]) == 0) {
+ vty_out(vty, "duplicate entry in 'msc' / 'codec-list': %s%s", argv[i], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
}
+ memcpy(data->audio_support, tmp, sizeof(data->audio_support));
+ data->audio_length = argc;
+
return CMD_SUCCESS;
-}
-DEFUN(cfg_bts_no_t3113_dynamic, cfg_bts_no_t3113_dynamic_cmd,
- "no timer-dynamic TNNNN",
- NO_STR
- "Set given timer to non-dynamic and use the default or user provided fixed value\n"
- TNUM_STR)
-{
- struct T_def *d;
- struct gsm_bts *bts = vty->index;
+invalid_arg:
+ vty_out(vty, "%s is not a valid codec version%s", osmo_quote_cstr_c(OTC_SELECT, arg, -1), VTY_NEWLINE);
+ return CMD_WARNING;
+}
- d = parse_T_arg(vty, argv[0]);
- if (!d)
- return CMD_WARNING;
+#define LEGACY_STR "This command has no effect, it is kept to support legacy config files\n"
- switch (d->T) {
- case 3113:
- bts->T3113_dynamic = false;
- break;
- default:
- vty_out(vty, "T%d already is non-dynamic%s", d->T, VTY_NEWLINE);
- return CMD_WARNING;
- }
+DEFUN_DEPRECATED(deprecated_ussd_text,
+ cfg_net_msc_welcome_ussd_cmd,
+ "bsc-welcome-text .TEXT", LEGACY_STR LEGACY_STR)
+{
+ vty_out(vty, "%% osmo-bsc no longer supports USSD notification. These commands have no effect:%s"
+ "%% bsc-welcome-text, bsc-msc-lost-text, mid-call-text, bsc-grace-text, missing-msc-text%s",
+ VTY_NEWLINE, VTY_NEWLINE);
+ return CMD_WARNING;
+}
+DEFUN_DEPRECATED(deprecated_no_ussd_text,
+ cfg_net_msc_no_welcome_ussd_cmd,
+ "no bsc-welcome-text",
+ NO_STR LEGACY_STR)
+{
return CMD_SUCCESS;
}
-#define TRX_TEXT "Radio Transceiver\n"
+ALIAS_DEPRECATED(deprecated_ussd_text,
+ cfg_net_msc_lost_ussd_cmd,
+ "bsc-msc-lost-text .TEXT", LEGACY_STR LEGACY_STR);
-/* per TRX configuration */
-DEFUN(cfg_trx,
- cfg_trx_cmd,
- "trx <0-255>",
- TRX_TEXT
- "Select a TRX to configure")
-{
- int trx_nr = atoi(argv[0]);
- struct gsm_bts *bts = vty->index;
- struct gsm_bts_trx *trx;
+ALIAS_DEPRECATED(deprecated_no_ussd_text,
+ cfg_net_msc_no_lost_ussd_cmd,
+ "no bsc-msc-lost-text", NO_STR LEGACY_STR);
- if (trx_nr > bts->num_trx) {
- vty_out(vty, "%% The next unused TRX number in this BTS is %u%s",
- bts->num_trx, VTY_NEWLINE);
- return CMD_WARNING;
- } else if (trx_nr == bts->num_trx) {
- /* we need to allocate a new one */
- trx = gsm_bts_trx_alloc(bts);
- } else
- trx = gsm_bts_trx_num(bts, trx_nr);
+ALIAS_DEPRECATED(deprecated_ussd_text,
+ cfg_net_msc_grace_ussd_cmd,
+ "bsc-grace-text .TEXT", LEGACY_STR LEGACY_STR);
- if (!trx)
- return CMD_WARNING;
+ALIAS_DEPRECATED(deprecated_no_ussd_text,
+ cfg_net_msc_no_grace_ussd_cmd,
+ "no bsc-grace-text", NO_STR LEGACY_STR);
+
+ALIAS_DEPRECATED(deprecated_ussd_text,
+ cfg_net_bsc_missing_msc_ussd_cmd,
+ "missing-msc-text .TEXT", LEGACY_STR LEGACY_STR);
- vty->index = trx;
- vty->index_sub = &trx->description;
- vty->node = TRX_NODE;
+ALIAS_DEPRECATED(deprecated_no_ussd_text,
+ cfg_net_bsc_no_missing_msc_text_cmd,
+ "no missing-msc-text", NO_STR LEGACY_STR);
+DEFUN_DEPRECATED(cfg_net_msc_type,
+ cfg_net_msc_type_cmd,
+ "type (normal|local)",
+ LEGACY_STR LEGACY_STR)
+{
+ vty_out(vty, "%% 'msc' / 'type' config is deprecated and no longer has any effect%s",
+ VTY_NEWLINE);
return CMD_SUCCESS;
}
-DEFUN(cfg_trx_arfcn,
- cfg_trx_arfcn_cmd,
- "arfcn <0-1023>",
- "Set the ARFCN for this TRX\n"
- "Absolute Radio Frequency Channel Number\n")
+DEFUN_ATTR(cfg_net_msc_emerg,
+ cfg_net_msc_emerg_cmd,
+ "allow-emergency (allow|deny)",
+ "Allow CM ServiceRequests with type emergency\n"
+ "Allow\n" "Deny\n",
+ CMD_ATTR_IMMEDIATE)
{
- enum gsm_band unused;
- struct gsm_bts_trx *trx = vty->index;
- int arfcn = atoi(argv[0]);
-
- if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
- vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- /* FIXME: check if this ARFCN is supported by this TRX */
-
- trx->arfcn = arfcn;
-
- /* FIXME: patch ARFCN into SYSTEM INFORMATION */
- /* FIXME: use OML layer to update the ARFCN */
- /* FIXME: use RSL layer to update SYSTEM INFORMATION */
-
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->allow_emerg = strcmp("allow", argv[0]) == 0;
return CMD_SUCCESS;
}
-DEFUN(cfg_trx_nominal_power,
- cfg_trx_nominal_power_cmd,
- "nominal power <0-100>",
- "Nominal TRX RF Power in dBm\n"
- "Nominal TRX RF Power in dBm\n"
- "Nominal TRX RF Power in dBm\n")
-{
- struct gsm_bts_trx *trx = vty->index;
+#define AMR_CONF_STR "AMR Multirate Configuration\n"
+#define AMR_COMMAND(name) \
+ DEFUN_USRATTR(cfg_net_msc_amr_##name, \
+ cfg_net_msc_amr_##name##_cmd,BSC_VTY_ATTR_NEW_LCHAN, \
+ "amr-config " #name "k (allowed|forbidden)", \
+ AMR_CONF_STR "Bitrate\n" "Allowed\n" "Forbidden\n") \
+{ \
+ struct bsc_msc_data *msc = bsc_msc_data(vty); \
+ \
+ msc->amr_conf.m##name = strcmp(argv[0], "allowed") == 0; \
+ return CMD_SUCCESS; \
+}
- trx->nominal_power = atoi(argv[0]);
+AMR_COMMAND(12_2)
+AMR_COMMAND(10_2)
+AMR_COMMAND(7_95)
+AMR_COMMAND(7_40)
+AMR_COMMAND(6_70)
+AMR_COMMAND(5_90)
+AMR_COMMAND(5_15)
+AMR_COMMAND(4_75)
- return CMD_SUCCESS;
+/* Make sure only standard SSN numbers are used. If no ssn number is
+ * configured, silently apply the default SSN */
+static void enforce_standard_ssn(struct vty *vty, struct osmo_sccp_addr *addr)
+{
+ if (addr->presence & OSMO_SCCP_ADDR_T_SSN) {
+ if (addr->ssn != OSMO_SCCP_SSN_BSSAP)
+ vty_out(vty,
+ "setting an SSN (%u) different from the standard (%u) is not allowed, will use standard SSN for address: %s%s",
+ addr->ssn, OSMO_SCCP_SSN_BSSAP, osmo_sccp_addr_dump(addr), VTY_NEWLINE);
+ }
+
+ addr->presence |= OSMO_SCCP_ADDR_T_SSN;
+ addr->ssn = OSMO_SCCP_SSN_BSSAP;
}
-DEFUN(cfg_trx_max_power_red,
- cfg_trx_max_power_red_cmd,
- "max_power_red <0-100>",
- "Reduction of maximum BS RF Power (relative to nominal power)\n"
- "Reduction of maximum BS RF Power in dB\n")
+DEFUN(cfg_msc_cs7_bsc_addr,
+ cfg_msc_cs7_bsc_addr_cmd,
+ "bsc-addr NAME",
+ "Calling Address (local address of this BSC)\n" "SCCP address name\n")
{
- int maxpwr_r = atoi(argv[0]);
- struct gsm_bts_trx *trx = vty->index;
- int upper_limit = 24; /* default 12.21 max power red. */
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ const char *bsc_addr_name = argv[0];
+ struct osmo_ss7_instance *ss7;
- /* FIXME: check if our BTS type supports more than 12 */
- if (maxpwr_r < 0 || maxpwr_r > upper_limit) {
- vty_out(vty, "%% Power %d dB is not in the valid range%s",
- maxpwr_r, VTY_NEWLINE);
- return CMD_WARNING;
- }
- if (maxpwr_r & 1) {
- vty_out(vty, "%% Power %d dB is not an even value%s",
- maxpwr_r, VTY_NEWLINE);
- return CMD_WARNING;
+ ss7 = osmo_sccp_addr_by_name(&msc->a.bsc_addr, bsc_addr_name);
+ if (!ss7) {
+ vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", bsc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
}
- trx->max_power_red = maxpwr_r;
-
- /* FIXME: make sure we update this using OML */
+ /* Prevent mixing addresses from different CS7/SS7 instances */
+ if (msc->a.cs7_instance_valid) {
+ if (msc->a.cs7_instance != ss7->cfg.id) {
+ vty_out(vty,
+ "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s",
+ bsc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ }
+ msc->a.cs7_instance = ss7->cfg.id;
+ msc->a.cs7_instance_valid = true;
+ enforce_standard_ssn(vty, &msc->a.bsc_addr);
+ msc->a.bsc_addr_name = talloc_strdup(msc, bsc_addr_name);
return CMD_SUCCESS;
}
-DEFUN(cfg_trx_rsl_e1,
- cfg_trx_rsl_e1_cmd,
- "rsl e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
- "RSL Parameters\n"
- "E1/T1 interface to be used for RSL\n"
- "E1/T1 interface to be used for RSL\n"
- "E1/T1 Line Number to be used for RSL\n"
- "E1/T1 Timeslot to be used for RSL\n"
- "E1/T1 Timeslot to be used for RSL\n"
- "E1/T1 Sub-slot to be used for RSL\n"
- "E1/T1 Sub-slot 0 is to be used for RSL\n"
- "E1/T1 Sub-slot 1 is to be used for RSL\n"
- "E1/T1 Sub-slot 2 is to be used for RSL\n"
- "E1/T1 Sub-slot 3 is to be used for RSL\n"
- "E1/T1 full timeslot is to be used for RSL\n")
+DEFUN(cfg_msc_cs7_msc_addr,
+ cfg_msc_cs7_msc_addr_cmd,
+ "msc-addr NAME",
+ "Called Address (remote address of the MSC)\n" "SCCP address name\n")
{
- struct gsm_bts_trx *trx = vty->index;
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ const char *msc_addr_name = argv[0];
+ struct osmo_ss7_instance *ss7;
- parse_e1_link(&trx->rsl_e1_link, argv[0], argv[1], argv[2]);
+ ss7 = osmo_sccp_addr_by_name(&msc->a.msc_addr, msc_addr_name);
+ if (!ss7) {
+ vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", msc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ /* Prevent mixing addresses from different CS7/SS7 instances */
+ if (msc->a.cs7_instance_valid) {
+ if (msc->a.cs7_instance != ss7->cfg.id) {
+ vty_out(vty,
+ "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s",
+ msc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ }
+
+ msc->a.cs7_instance = ss7->cfg.id;
+ msc->a.cs7_instance_valid = true;
+ enforce_standard_ssn(vty, &msc->a.msc_addr);
+ msc->a.msc_addr_name = talloc_strdup(msc, msc_addr_name);
return CMD_SUCCESS;
}
-DEFUN(cfg_trx_rsl_e1_tei,
- cfg_trx_rsl_e1_tei_cmd,
- "rsl e1 tei <0-63>",
- "RSL Parameters\n"
- "Set the TEI to be used for RSL\n"
- "Set the TEI to be used for RSL\n"
- "TEI to be used for RSL\n")
+DEFUN(cfg_msc_cs7_asp_proto,
+ cfg_msc_cs7_asp_proto_cmd,
+ "asp-protocol (m3ua|sua|ipa)",
+ "A interface protocol to use for this MSC)\n"
+ "MTP3 User Adaptation\n"
+ "SCCP User Adaptation\n"
+ "IPA Multiplex (SCCP Lite)\n")
{
- struct gsm_bts_trx *trx = vty->index;
-
- trx->rsl_tei = atoi(argv[0]);
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ msc->a.asp_proto = get_string_value(osmo_ss7_asp_protocol_vals, argv[0]);
return CMD_SUCCESS;
}
-DEFUN(cfg_trx_rf_locked,
- cfg_trx_rf_locked_cmd,
- "rf_locked (0|1)",
- "Set or unset the RF Locking (Turn off RF of the TRX)\n"
- "TRX is NOT RF locked (active)\n"
- "TRX is RF locked (turned off)\n")
+DEFUN_USRATTR(cfg_net_msc_lcls_mode,
+ cfg_net_msc_lcls_mode_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "lcls-mode (disabled|mgw-loop|bts-loop)",
+ "Configure 3GPP LCLS (Local Call, Local Switch)\n"
+ "Disable LCLS for all calls of this MSC\n"
+ "Enable LCLS with looping traffic in MGW\n"
+ "Enable LCLS with looping traffic between BTS\n")
{
- int locked = atoi(argv[0]);
- struct gsm_bts_trx *trx = vty->index;
-
- gsm_trx_lock_rf(trx, locked, "vty");
+ struct bsc_msc_data *data = bsc_msc_data(vty);
+ data->lcls_mode = get_string_value(bsc_lcls_mode_names, argv[0]);
return CMD_SUCCESS;
}
-/* per TS configuration */
-DEFUN(cfg_ts,
- cfg_ts_cmd,
- "timeslot <0-7>",
- "Select a Timeslot to configure\n"
- "Timeslot number\n")
+DEFUN_USRATTR(cfg_net_msc_lcls_mismtch,
+ cfg_net_msc_lcls_mismtch_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "lcls-codec-mismatch (allowed|forbidden)",
+ "Allow 3GPP LCLS (Local Call, Local Switch) when call legs use different codec/rate\n"
+ "Allow LCLS only only for calls that use the same codec/rate on both legs\n"
+ "Do not Allow LCLS for calls that use a different codec/rate on both legs\n")
{
- int ts_nr = atoi(argv[0]);
- struct gsm_bts_trx *trx = vty->index;
- struct gsm_bts_trx_ts *ts;
-
- if (ts_nr >= TRX_NR_TS) {
- vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s",
- TRX_NR_TS, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- ts = &trx->ts[ts_nr];
+ struct bsc_msc_data *data = bsc_msc_data(vty);
- vty->index = ts;
- vty->node = TS_NODE;
+ if (strcmp(argv[0], "allowed") == 0)
+ data->lcls_codec_mismatch_allow = true;
+ else
+ data->lcls_codec_mismatch_allow = false;
return CMD_SUCCESS;
}
-DEFUN(cfg_ts_pchan,
- cfg_ts_pchan_cmd,
- "phys_chan_config PCHAN", /* dynamically generated! */
- "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n")
+DEFUN_USRATTR(cfg_msc_mgw_x_osmo_ign,
+ cfg_msc_mgw_x_osmo_ign_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "mgw x-osmo-ign call-id",
+ MGCP_CLIENT_MGW_STR
+ "Set a (non-standard) X-Osmo-IGN header in all CRCX messages for RTP streams"
+ " associated with this MSC, useful for A/SCCPlite MSCs, since osmo-bsc cannot know"
+ " the MSC's chosen CallID. This is enabled by default for A/SCCPlite connections,"
+ " disabled by default for all others.\n"
+ "Send 'X-Osmo-IGN: C' to ignore CallID mismatches. See OsmoMGW.\n")
{
- struct gsm_bts_trx_ts *ts = vty->index;
- int pchanc;
-
- pchanc = gsm_pchan_parse(argv[0]);
- if (pchanc < 0)
- return CMD_WARNING;
-
- ts->pchan_from_config = pchanc;
-
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ msc->x_osmo_ign |= MGCP_X_OSMO_IGN_CALLID;
+ msc->x_osmo_ign_configured = true;
return CMD_SUCCESS;
}
-/* used for backwards compatibility with old config files that still
- * have uppercase pchan type names */
-DEFUN_HIDDEN(cfg_ts_pchan_compat,
- cfg_ts_pchan_compat_cmd,
- "phys_chan_config PCHAN",
- "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n")
+DEFUN_USRATTR(cfg_msc_no_mgw_x_osmo_ign,
+ cfg_msc_no_mgw_x_osmo_ign_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "no mgw x-osmo-ign",
+ NO_STR
+ MGCP_CLIENT_MGW_STR
+ "Do not send X-Osmo-IGN MGCP header to this MSC\n")
{
- struct gsm_bts_trx_ts *ts = vty->index;
- int pchanc;
-
- pchanc = gsm_pchan_parse(argv[0]);
- if (pchanc < 0) {
- vty_out(vty, "Unknown physical channel name '%s'%s", argv[0], VTY_NEWLINE);
- return CMD_ERR_NO_MATCH;
- }
-
- ts->pchan_from_config = pchanc;
-
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ msc->x_osmo_ign = 0;
+ msc->x_osmo_ign_configured = true;
return CMD_SUCCESS;
}
-
-
-DEFUN(cfg_ts_tsc,
- cfg_ts_tsc_cmd,
- "training_sequence_code <0-7>",
- "Training Sequence Code of the Timeslot\n" "TSC\n")
+#define OSMUX_STR "RTP multiplexing\n"
+DEFUN_USRATTR(cfg_msc_osmux,
+ cfg_msc_osmux_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "osmux (on|off|only)",
+ OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only use OSMUX\n")
{
- struct gsm_bts_trx_ts *ts = vty->index;
-
- if (!osmo_bts_has_feature(&ts->trx->bts->model->features, BTS_FEAT_MULTI_TSC)) {
- vty_out(vty, "%% This BTS does not support a TSC != BCC, "
- "falling back to BCC%s", VTY_NEWLINE);
- ts->tsc = -1;
- return CMD_WARNING;
- }
-
- ts->tsc = atoi(argv[0]);
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ if (strcmp(argv[0], "off") == 0)
+ msc->use_osmux = OSMUX_USAGE_OFF;
+ else if (strcmp(argv[0], "on") == 0)
+ msc->use_osmux = OSMUX_USAGE_ON;
+ else if (strcmp(argv[0], "only") == 0)
+ msc->use_osmux = OSMUX_USAGE_ONLY;
return CMD_SUCCESS;
}
-#define HOPPING_STR "Configure frequency hopping\n"
+ALIAS_DEPRECATED(deprecated_ussd_text,
+ cfg_net_bsc_mid_call_text_cmd,
+ "mid-call-text .TEXT",
+ LEGACY_STR LEGACY_STR);
-DEFUN(cfg_ts_hopping,
- cfg_ts_hopping_cmd,
- "hopping enabled (0|1)",
- HOPPING_STR "Enable or disable frequency hopping\n"
- "Disable frequency hopping\n" "Enable frequency hopping\n")
+DEFUN_ATTR(cfg_net_bsc_mid_call_timeout,
+ cfg_net_bsc_mid_call_timeout_cmd,
+ "mid-call-timeout NR",
+ "Switch from Grace to Off in NR seconds.\n" "Timeout in seconds\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts_trx_ts *ts = vty->index;
- int enabled = atoi(argv[0]);
-
- if (enabled && !osmo_bts_has_feature(&ts->trx->bts->model->features, BTS_FEAT_HOPPING)) {
- vty_out(vty, "BTS model does not support hopping%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- ts->hopping.enabled = enabled;
-
+ bsc_gsmnet->mid_call_timeout = atoi(argv[0]);
return CMD_SUCCESS;
}
-DEFUN(cfg_ts_hsn,
- cfg_ts_hsn_cmd,
- "hopping sequence-number <0-63>",
- HOPPING_STR
- "Which hopping sequence to use for this channel\n"
- "Hopping Sequence Number (HSN)\n")
+DEFUN(cfg_net_rf_socket,
+ cfg_net_rf_socket_cmd,
+ "bsc-rf-socket PATH",
+ "Set the filename for the RF control interface.\n" "RF Control path\n")
{
- struct gsm_bts_trx_ts *ts = vty->index;
-
- ts->hopping.hsn = atoi(argv[0]);
-
+ osmo_talloc_replace_string(bsc_gsmnet, &bsc_gsmnet->rf_ctrl_name, argv[0]);
return CMD_SUCCESS;
}
-DEFUN(cfg_ts_maio,
- cfg_ts_maio_cmd,
- "hopping maio <0-63>",
- HOPPING_STR
- "Which hopping MAIO to use for this channel\n"
- "Mobile Allocation Index Offset (MAIO)\n")
+DEFUN_ATTR(cfg_net_rf_off_time,
+ cfg_net_rf_off_time_cmd,
+ "bsc-auto-rf-off <1-65000>",
+ "Disable RF on MSC Connection\n" "Timeout\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts_trx_ts *ts = vty->index;
-
- ts->hopping.maio = atoi(argv[0]);
-
+ bsc_gsmnet->auto_off_timeout = atoi(argv[0]);
return CMD_SUCCESS;
}
-DEFUN(cfg_ts_arfcn_add,
- cfg_ts_arfcn_add_cmd,
- "hopping arfcn add <0-1023>",
- HOPPING_STR "Configure hopping ARFCN list\n"
- "Add an entry to the hopping ARFCN list\n" "ARFCN\n")
+DEFUN_ATTR(cfg_net_no_rf_off_time,
+ cfg_net_no_rf_off_time_cmd,
+ "no bsc-auto-rf-off",
+ NO_STR "Disable RF on MSC Connection\n",
+ CMD_ATTR_IMMEDIATE)
{
- enum gsm_band unused;
- struct gsm_bts_trx_ts *ts = vty->index;
- int arfcn = atoi(argv[0]);
-
- if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
- vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1);
-
+ bsc_gsmnet->auto_off_timeout = -1;
return CMD_SUCCESS;
}
-DEFUN(cfg_ts_arfcn_del,
- cfg_ts_arfcn_del_cmd,
- "hopping arfcn del <0-1023>",
- HOPPING_STR "Configure hopping ARFCN list\n"
- "Delete an entry to the hopping ARFCN list\n" "ARFCN\n")
+DEFUN_ATTR(cfg_bsc_bts_setup_ramping,
+ cfg_bsc_bts_setup_ramping_cmd,
+ "bts-setup-ramping",
+ "Enable BTS setup ramping to limit the amount of BTS to configure within a time window.\n",
+ CMD_ATTR_IMMEDIATE)
{
- enum gsm_band unused;
- struct gsm_bts_trx_ts *ts = vty->index;
- int arfcn = atoi(argv[0]);
-
- if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
- vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0);
-
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ bts_setup_ramp_enable(net);
return CMD_SUCCESS;
}
-DEFUN(cfg_ts_e1_subslot,
- cfg_ts_e1_subslot_cmd,
- "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
- "E1/T1 channel connected to this on-air timeslot\n"
- "E1/T1 channel connected to this on-air timeslot\n"
- "E1/T1 line connected to this on-air timeslot\n"
- "E1/T1 timeslot connected to this on-air timeslot\n"
- "E1/T1 timeslot connected to this on-air timeslot\n"
- "E1/T1 sub-slot connected to this on-air timeslot\n"
- "E1/T1 sub-slot 0 connected to this on-air timeslot\n"
- "E1/T1 sub-slot 1 connected to this on-air timeslot\n"
- "E1/T1 sub-slot 2 connected to this on-air timeslot\n"
- "E1/T1 sub-slot 3 connected to this on-air timeslot\n"
- "Full E1/T1 timeslot connected to this on-air timeslot\n")
+DEFUN_ATTR(cfg_bsc_no_bts_setup_ramping,
+ cfg_bsc_no_bts_setup_ramping_cmd,
+ "no bts-setup-ramping",
+ NO_STR
+ "Disable BTS ramping and configure all waiting BTS.\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_bts_trx_ts *ts = vty->index;
-
- parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]);
-
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ bts_setup_ramp_disable(net);
return CMD_SUCCESS;
}
-int print_counter(struct rate_ctr_group *bsc_ctrs, struct rate_ctr *ctr, const struct rate_ctr_desc *desc, void *data)
+DEFUN_ATTR(cfg_bsc_bts_ramping_step_interval,
+ cfg_bsc_bts_setup_ramping_step_interval_cmd,
+ "bts-setup-ramping-step-interval <0-65535>",
+ "Configure the BTS setup ramping step interval. The time between ramping steps.\n"
+ "Set a step interval (in seconds)\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct vty *vty = data;
- vty_out(vty, "%25s: %10"PRIu64" %s%s", desc->name, ctr->current, desc->description, VTY_NEWLINE);
- return 0;
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int interval_size = atoi(argv[0]);
+ bts_setup_ramp_set_step_interval(net, interval_size);
+ return CMD_SUCCESS;
}
-void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net)
+DEFUN_ATTR(cfg_bsc_bts_ramping_step_size,
+ cfg_bsc_bts_setup_ramping_step_size_cmd,
+ "bts-setup-ramping-step-size <0-65535>",
+ "Configure the BTS setup ramping step size. The amount of BTS to allow to configure within a ramping interval\n"
+ "Amount of BTS to setup while a step size\n",
+ CMD_ATTR_IMMEDIATE)
{
- rate_ctr_for_each_counter(net->bsc_ctrs, print_counter, vty);
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int seconds = atoi(argv[0]);
+ bts_setup_ramp_set_step_size(net, seconds);
+ return CMD_SUCCESS;
}
-DEFUN(drop_bts,
- drop_bts_cmd,
- "drop bts connection <0-65535> (oml|rsl)",
- "Debug/Simulation command to drop Abis/IP BTS\n"
- "Debug/Simulation command to drop Abis/IP BTS\n"
- "Debug/Simulation command to drop Abis/IP BTS\n"
- "BTS NR\n" "Drop OML Connection\n" "Drop RSL Connection\n")
+DEFUN(bts_unblock_setup_ramping,
+ bts_unblock_setup_ramping_cmd,
+ "bts <0-255> unblock-setup-ramping",
+ "BTS Specific Commands\n" BTS_NR_STR
+ "Unblock and allow to configure a BTS if kept back by BTS ramping\n")
{
struct gsm_network *gsmnet;
- struct gsm_bts_trx *trx;
struct gsm_bts *bts;
unsigned int bts_nr;
@@ -4414,650 +3231,395 @@ DEFUN(drop_bts,
bts_nr = atoi(argv[0]);
if (bts_nr >= gsmnet->num_bts) {
- vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s",
+ vty_out(vty, "%% BTS number must be between 0 and %d. It was %d.%s",
gsmnet->num_bts, bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
bts = gsm_bts_num(gsmnet, bts_nr);
if (!bts) {
- vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
+ vty_out(vty, "%% BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
return CMD_WARNING;
}
- if (!is_ipaccess_bts(bts)) {
- vty_out(vty, "This command only works for ipaccess.%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
-
-
- /* close all connections */
- if (strcmp(argv[1], "oml") == 0) {
- ipaccess_drop_oml(bts);
- } else if (strcmp(argv[1], "rsl") == 0) {
- /* close all rsl connections */
- llist_for_each_entry(trx, &bts->trx_list, list) {
- ipaccess_drop_rsl(trx);
- }
- } else {
- vty_out(vty, "Argument must be 'oml# or 'rsl'.%s", VTY_NEWLINE);
+ if (bts_setup_ramp_unblock_bts(bts)) {
+ vty_out(vty, "%% The BTS is not blocked by BTS ramping.%s", VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
-DEFUN(restart_bts, restart_bts_cmd,
- "restart-bts <0-65535>",
- "Restart ip.access nanoBTS through OML\n"
- BTS_NR_STR)
+DEFUN(show_statistics,
+ show_statistics_cmd,
+ "show statistics",
+ SHOW_STR "Statistics about the BSC\n")
{
- struct gsm_network *gsmnet;
- struct gsm_bts_trx *trx;
- struct gsm_bts *bts;
- unsigned int bts_nr;
-
- gsmnet = gsmnet_from_vty(vty);
-
- bts_nr = atoi(argv[0]);
- if (bts_nr >= gsmnet->num_bts) {
- vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s",
- gsmnet->num_bts, bts_nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- bts = gsm_bts_num(gsmnet, bts_nr);
- if (!bts) {
- vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- if (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) {
- vty_out(vty, "This command only works for ipaccess nanoBTS.%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- /* go from last TRX to c0 */
- llist_for_each_entry_reverse(trx, &bts->trx_list, list)
- abis_nm_ipaccess_restart(trx);
-
+ openbsc_vty_print_statistics(vty, bsc_gsmnet);
return CMD_SUCCESS;
}
-DEFUN(bts_resend, bts_resend_cmd,
- "bts <0-255> resend-system-information",
- "BTS Specific Commands\n" BTS_NR_STR
- "Re-generate + re-send BCCH SYSTEM INFORMATION\n")
+DEFUN(show_mscs,
+ show_mscs_cmd,
+ "show mscs",
+ SHOW_STR "MSC Connections and State\n")
{
- struct gsm_network *gsmnet;
- struct gsm_bts_trx *trx;
- struct gsm_bts *bts;
- unsigned int bts_nr;
-
- gsmnet = gsmnet_from_vty(vty);
-
- bts_nr = atoi(argv[0]);
- if (bts_nr >= gsmnet->num_bts) {
- vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s",
- gsmnet->num_bts, bts_nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
+ struct bsc_msc_data *msc;
+ llist_for_each_entry(msc, &bsc_gsmnet->mscs, entry) {
+ vty_out(vty, "MSC %d: %s <-> ",
+ msc->a.cs7_instance,
+ osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.bsc_addr));
+ vty_out(vty, "%s%s",
+ osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.msc_addr),
+ VTY_NEWLINE);
+ vty_out(vty, " ASP protocol: %s%s",
+ osmo_ss7_asp_protocol_name(msc->a.asp_proto),
+ VTY_NEWLINE);
+ vty_out(vty, " BSSMAP state: %s%s",
+ msc->a.bssmap_reset ? osmo_fsm_inst_state_name(msc->a.bssmap_reset->fi) : "",
+ VTY_NEWLINE);
- bts = gsm_bts_num(gsmnet, bts_nr);
- if (!bts) {
- vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
- return CMD_WARNING;
}
- llist_for_each_entry_reverse(trx, &bts->trx_list, list)
- gsm_bts_trx_set_system_infos(trx);
-
return CMD_SUCCESS;
}
-
-DEFUN(smscb_cmd, smscb_cmd_cmd,
- "bts <0-255> smscb-command <1-4> HEXSTRING",
- "BTS related commands\n" BTS_NR_STR
- "SMS Cell Broadcast\n" "Last Valid Block\n"
- "Hex Encoded SMSCB message (up to 88 octets)\n")
+DEFUN(show_pos,
+ show_pos_cmd,
+ "show position",
+ SHOW_STR "Position information of the BTS\n")
{
struct gsm_bts *bts;
- int bts_nr = atoi(argv[0]);
- int last_block = atoi(argv[1]);
- struct rsl_ie_cb_cmd_type cb_cmd;
- uint8_t buf[88];
- int rc;
+ struct bts_location *curloc;
+ struct tm time;
+ char timestr[50];
- bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
- if (!bts) {
- vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
- if (!gsm_bts_get_cbch(bts)) {
- vty_out(vty, "%% BTS %d doesn't have a CBCH%s", bts_nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
- rc = osmo_hexparse(argv[2], buf, sizeof(buf));
- if (rc < 0 || rc > sizeof(buf)) {
- vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- cb_cmd.spare = 0;
- cb_cmd.def_bcast = 0;
- cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL;
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ if (llist_empty(&bts->loc_list)) {
+ vty_out(vty, "BTS Nr: %d position invalid%s", bts->nr,
+ VTY_NEWLINE);
+ continue;
+ }
+ curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+ if (gmtime_r(&curloc->tstamp, &time) == NULL) {
+ vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
+ VTY_NEWLINE);
+ continue;
+ }
+ if (asctime_r(&time, timestr) == NULL) {
+ vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
+ VTY_NEWLINE);
+ continue;
+ }
+ /* Last character in asctime is \n */
+ timestr[strlen(timestr)-1] = 0;
- switch (last_block) {
- case 1:
- cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1;
- break;
- case 2:
- cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2;
- break;
- case 3:
- cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3;
- break;
- case 4:
- cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4;
- break;
- default:
- vty_out(vty, "Error parsing LASTBLOCK%s", VTY_NEWLINE);
- return CMD_WARNING;
+ vty_out(vty, "BTS Nr: %d position: %s time: %s%s", bts->nr,
+ get_value_string(bts_loc_fix_names, curloc->valid), timestr,
+ VTY_NEWLINE);
+ vty_out(vty, " lat: %f lon: %f height: %f%s", curloc->lat, curloc->lon,
+ curloc->height, VTY_NEWLINE);
}
-
- rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, buf, rc);
-
return CMD_SUCCESS;
}
-/* resolve a gsm_bts_trx_ts basd on the given numeric identifiers */
-static struct gsm_bts_trx_ts *vty_get_ts(struct vty *vty, const char *bts_str, const char *trx_str,
- const char *ts_str)
+DEFUN(gen_position_trap,
+ gen_position_trap_cmd,
+ "generate-location-state-trap <0-255>",
+ "Generate location state report\n"
+ "BTS to report\n")
{
- int bts_nr = atoi(bts_str);
- int trx_nr = atoi(trx_str);
- int ts_nr = atoi(ts_str);
+ int bts_nr;
struct gsm_bts *bts;
- struct gsm_bts_trx *trx;
- struct gsm_bts_trx_ts *ts;
+ struct gsm_network *net = bsc_gsmnet;
- bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
- if (!bts) {
- vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE);
- return NULL;
- }
-
- trx = gsm_bts_trx_num(bts, trx_nr);
- if (!trx) {
- vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE);
- return NULL;
- }
-
- ts = &trx->ts[ts_nr];
-
- return ts;
-}
-
-DEFUN(pdch_act, pdch_act_cmd,
- "bts <0-255> trx <0-255> timeslot <0-7> pdch (activate|deactivate)",
- BTS_NR_TRX_TS_STR2
- "Packet Data Channel\n"
- "Activate Dynamic PDCH/TCH (-> PDCH mode)\n"
- "Deactivate Dynamic PDCH/TCH (-> TCH mode)\n")
-{
- struct gsm_bts_trx_ts *ts;
- int activate;
-
- ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
- if (!ts || !ts->fi || ts->fi->state == TS_ST_NOT_INITIALIZED || ts->fi->state == TS_ST_BORKEN) {
- vty_out(vty, "%% Timeslot is not usable%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- if (!is_ipaccess_bts(ts->trx->bts)) {
- vty_out(vty, "%% This command only works for ipaccess BTS%s",
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
VTY_NEWLINE);
return CMD_WARNING;
}
- if (ts->pchan_on_init != GSM_PCHAN_TCH_F_TCH_H_PDCH
- && ts->pchan_on_init != GSM_PCHAN_TCH_F_PDCH) {
- vty_out(vty, "%% Timeslot %u is not dynamic TCH/F_TCH/H_PDCH or TCH/F_PDCH%s",
- ts->nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- if (!strcmp(argv[3], "activate"))
- activate = 1;
- else
- activate = 0;
-
- if (activate && ts->fi->state != TS_ST_UNUSED) {
- vty_out(vty, "%% Timeslot %u is still in use%s",
- ts->nr, VTY_NEWLINE);
- return CMD_WARNING;
- } else if (!activate && ts->fi->state != TS_ST_PDCH) {
- vty_out(vty, "%% Timeslot %u is not in PDCH mode%s",
- ts->nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- LOG_TS(ts, LOGL_NOTICE, "telnet VTY user asks to %s\n", activate ? "PDCH ACT" : "PDCH DEACT");
- ts->pdch_act_allowed = activate;
- osmo_fsm_inst_state_chg(ts->fi, activate ? TS_ST_WAIT_PDCH_ACT : TS_ST_WAIT_PDCH_DEACT, 4, 0);
-
+ bts = gsm_bts_num(net, bts_nr);
+ bsc_gen_location_state_trap(bts);
return CMD_SUCCESS;
-
}
-/* configure the lchan for a single AMR mode (as specified) */
-static int lchan_set_single_amr_mode(struct vty *vty, struct gsm_lchan *lchan, uint8_t amr_mode)
-{
- struct amr_multirate_conf mr;
- struct gsm48_multi_rate_conf *mr_conf;
- int rc, vty_rc = CMD_SUCCESS;
- mr_conf = (struct gsm48_multi_rate_conf *) &mr.gsm48_ie;
-
- if (amr_mode > 7)
- return -1;
-
- memset(&mr, 0, sizeof(mr));
- mr_conf->ver = 1;
- /* bit-mask of supported modes, only one bit is set. Reflects
- * Figure 10.5.2.47a where there are no thershold and only a
- * single mode */
- mr.gsm48_ie[1] = 1 << amr_mode;
-
- mr.ms_mode[0].mode = amr_mode;
- mr.bts_mode[0].mode = amr_mode;
- mr.num_modes = 1;
-
- /* encode this configuration into the lchan for both uplink and
- * downlink direction */
- rc = gsm48_multirate_config(lchan->mr_ms_lv, mr_conf, mr.ms_mode, mr.num_modes);
- if (rc != 0) {
- vty_out(vty,
- "Invalid AMR multirate configuration (%s, amr mode %d, ms) - check parameters%s",
- gsm_lchant_name(lchan->type), amr_mode, VTY_NEWLINE);
- vty_rc = CMD_WARNING;
- }
- rc = gsm48_multirate_config(lchan->mr_bts_lv, mr_conf, mr.bts_mode, mr.num_modes);
- if (rc != 0) {
- vty_out(vty,
- "Invalid AMR multirate configuration (%s, amr mode %d, bts) - check parameters%s",
- gsm_lchant_name(lchan->type), amr_mode, VTY_NEWLINE);
- vty_rc = CMD_WARNING;
- }
-
- return vty_rc;
-}
-
-/* Debug/Measurement command to activate a given logical channel
- * manually in a given mode/codec. This is useful for receiver
- * performance testing (FER/RBER/...) */
-DEFUN(lchan_act, lchan_act_cmd,
- "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> (activate|deactivate) (hr|fr|efr|amr) [<0-7>]",
- BTS_NR_TRX_TS_SS_STR2
- "Manual Channel Activation (e.g. for BER test)\n"
- "Manual Channel Deactivation (e.g. for BER test)\n"
- "Half-Rate v1\n" "Full-Rate\n" "Enhanced Full Rate\n" "Adaptive Multi-Rate\n" "AMR Mode\n")
+DEFUN(logging_fltr_imsi,
+ logging_fltr_imsi_cmd,
+ "logging filter imsi IMSI",
+ LOGGING_STR FILTER_STR
+ "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
{
- struct gsm_bts_trx_ts *ts;
- struct gsm_lchan *lchan;
- int ss_nr = atoi(argv[3]);
- const char *act_str = argv[4];
- const char *codec_str = argv[5];
- int activate;
+ struct bsc_subscr *bsc_subscr;
+ struct log_target *tgt = osmo_log_vty2tgt(vty);
+ const char *imsi = argv[0];
- ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
- if (!ts)
+ if (!tgt)
return CMD_WARNING;
- lchan = &ts->lchan[ss_nr];
+ bsc_subscr = bsc_subscr_find_or_create_by_imsi(bsc_gsmnet->bsc_subscribers, imsi, __func__);
- if (!strcmp(act_str, "activate"))
- activate = 1;
- else
- activate = 0;
-
- /* FIXME: allow dynamic channels with switchover, lchan_activate(lchan, FOR_VTY) */
- if (ss_nr >= pchan_subslots(ts->pchan_is)) {
- vty_out(vty, "%% subslot index %d too large for physical channel %s (%u slots)%s",
- ss_nr, gsm_pchan_name(ts->pchan_is), pchan_subslots(ts->pchan_is),
- VTY_NEWLINE);
+ if (!bsc_subscr) {
+ vty_out(vty, "%% failed to enable logging for subscriber with IMSI(%s)%s",
+ imsi, VTY_NEWLINE);
return CMD_WARNING;
}
- if (activate) {
- int lchan_t;
- if (lchan->fi->state != LCHAN_ST_UNUSED) {
- vty_out(vty, "%% Cannot activate: Channel busy!%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
- lchan_t = gsm_lchan_type_by_pchan(ts->pchan_is);
- if (lchan_t < 0)
- return CMD_WARNING;
- /* configure the lchan */
- lchan->type = lchan_t;
- lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
- if (!strcmp(codec_str, "hr") || !strcmp(codec_str, "fr"))
- lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
- else if (!strcmp(codec_str, "efr"))
- lchan->tch_mode = GSM48_CMODE_SPEECH_EFR;
- else if (!strcmp(codec_str, "amr")) {
- int amr_mode, vty_rc;
- if (argc < 7) {
- vty_out(vty, "%% AMR requires specification of AMR mode%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
- amr_mode = atoi(argv[6]);
- lchan->tch_mode = GSM48_CMODE_SPEECH_AMR;
- vty_rc = lchan_set_single_amr_mode(vty, lchan, amr_mode);
- if (vty_rc != CMD_SUCCESS)
- return vty_rc;
- }
- vty_out(vty, "%% activating lchan %s%s", gsm_lchan_name(lchan), VTY_NEWLINE);
- rsl_tx_chan_activ(lchan, RSL_ACT_TYPE_INITIAL, 0);
- rsl_tx_ipacc_crcx(lchan);
- } else {
- if (!lchan->fi) {
- vty_out(vty, "%% Cannot release: Channel not initialized%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
- vty_out(vty, "%% Asking for release of %s in state %s%s", gsm_lchan_name(lchan),
- osmo_fsm_inst_state_name(lchan->fi), VTY_NEWLINE);
- lchan_release(lchan, !!(lchan->conn), false, 0);
- }
+ log_set_filter_bsc_subscr(tgt, bsc_subscr);
+ /* log_set_filter has grabbed its own reference */
+ bsc_subscr_put(bsc_subscr, __func__);
return CMD_SUCCESS;
}
-DEFUN(lchan_mdcx, lchan_mdcx_cmd,
- "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> mdcx A.B.C.D <0-65535>",
- BTS_NR_TRX_TS_SS_STR2
- "Modify RTP Connection\n" "MGW IP Address\n" "MGW UDP Port\n")
+static void dump_one_sub(struct vty *vty, struct bsc_subscr *bsub)
{
- struct gsm_bts_trx_ts *ts;
- struct gsm_lchan *lchan;
- int ss_nr = atoi(argv[3]);
- int port = atoi(argv[5]);
- struct in_addr ia;
- inet_aton(argv[4], &ia);
+ vty_out(vty, " %15s %08x %s%s", bsub->imsi, bsub->tmsi, osmo_use_count_to_str_c(OTC_SELECT, &bsub->use_count),
+ VTY_NEWLINE);
+}
- ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
- if (!ts)
- return CMD_WARNING;
+DEFUN(show_subscr_all,
+ show_subscr_all_cmd,
+ "show subscriber all",
+ SHOW_STR "Display information about subscribers\n" "All Subscribers\n")
+{
+ struct bsc_subscr *bsc_subscr;
- lchan = &ts->lchan[ss_nr];
+ vty_out(vty, " IMSI TMSI Use%s", VTY_NEWLINE);
+ /* " 001010123456789 ffffffff 1" */
- if (ss_nr >= pchan_subslots(ts->pchan_is)) {
- vty_out(vty, "%% subslot index %d too large for physical channel %s (%u slots)%s",
- ss_nr, gsm_pchan_name(ts->pchan_is), pchan_subslots(ts->pchan_is),
- VTY_NEWLINE);
- return CMD_WARNING;
- }
+ llist_for_each_entry(bsc_subscr, &bsc_gsmnet->bsc_subscribers->bsub_list, entry)
+ dump_one_sub(vty, bsc_subscr);
- vty_out(vty, "%% connecting RTP of %s to %s:%u%s", gsm_lchan_name(lchan),
- inet_ntoa(ia), port, VTY_NEWLINE);
- lchan->abis_ip.connect_ip = ia.s_addr;
- lchan->abis_ip.connect_port = port;
- rsl_tx_ipacc_mdcx(lchan);
return CMD_SUCCESS;
}
-DEFUN(ctrl_trap, ctrl_trap_cmd,
- "ctrl-interface generate-trap TRAP VALUE",
- "Commands related to the CTRL Interface\n"
- "Generate a TRAP for test purpose\n"
- "Identity/Name of the TRAP variable\n"
- "Value of the TRAP variable\n")
+DEFUN_DEPRECATED(cfg_net_msc_ping_time, cfg_net_msc_ping_time_cmd,
+ "timeout-ping ARG", LEGACY_STR "-\n")
{
- struct gsm_network *net = gsmnet_from_vty(vty);
-
- ctrl_cmd_send_trap(net->ctrl, argv[0], (char *) argv[1]);
- return CMD_SUCCESS;
+ vty_out(vty, "%% timeout-ping / timeout-pong config is deprecated and has no effect%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
-#define NETWORK_STR "Configure the GSM network\n"
-#define CODE_CMD_STR "Code commands\n"
-#define NAME_CMD_STR "Name Commands\n"
-#define NAME_STR "Name to use\n"
+ALIAS_DEPRECATED(cfg_net_msc_ping_time, cfg_net_msc_no_ping_time_cmd,
+ "no timeout-ping [ARG]", NO_STR LEGACY_STR "-\n");
-DEFUN(cfg_net,
- cfg_net_cmd,
- "network", NETWORK_STR)
-{
- vty->index = gsmnet_from_vty(vty);
- vty->node = GSMNET_NODE;
+ALIAS_DEPRECATED(cfg_net_msc_ping_time, cfg_net_msc_pong_time_cmd,
+ "timeout-pong ARG", LEGACY_STR "-\n");
- return CMD_SUCCESS;
+DEFUN_DEPRECATED(cfg_net_msc_dest, cfg_net_msc_dest_cmd,
+ "dest A.B.C.D <1-65000> <0-255>", LEGACY_STR "-\n" "-\n" "-\n")
+{
+ vty_out(vty, "%% dest config is deprecated and has no effect%s", VTY_NEWLINE);
+ return CMD_WARNING;
}
-DEFUN(cfg_net_ncc,
- cfg_net_ncc_cmd,
- "network country code <1-999>",
- "Set the GSM network country code\n"
- "Country commands\n"
- CODE_CMD_STR
- "Network Country Code to use\n")
+ALIAS_DEPRECATED(cfg_net_msc_dest, cfg_net_msc_no_dest_cmd,
+ "no dest A.B.C.D <1-65000> <0-255>", NO_STR LEGACY_STR "-\n" "-\n" "-\n");
+
+DEFUN_USRATTR(cfg_net_msc_amr_octet_align,
+ cfg_net_msc_amr_octet_align_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr-payload (octet-aligned|bandwidth-efficient)",
+ "Set AMR payload framing mode\n"
+ "payload fields aligned on octet boundaries\n"
+ "payload fields packed (AoIP)\n")
{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- uint16_t mcc;
+ struct bsc_msc_data *data = bsc_msc_data(vty);
- if (osmo_mcc_from_str(argv[0], &mcc)) {
- vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE);
- return CMD_WARNING;
+ if (strcmp(argv[0], "octet-aligned") == 0)
+ data->amr_octet_aligned = true;
+ else if (strcmp(argv[0], "bandwidth-efficient") == 0)
+ data->amr_octet_aligned = false;
+ else {
+ data->amr_octet_aligned = false;
+ vty_out(vty, "%% Command 'amr-payload': Option 'bandwith-efficient' "
+ "containing typo is deprecated, use 'bandwidth-efficient' instead!%s",
+ VTY_NEWLINE);
}
- gsmnet->plmn.mcc = mcc;
-
return CMD_SUCCESS;
}
+ALIAS_DEPRECATED(cfg_net_msc_amr_octet_align,
+ cfg_net_msc_amr_octet_align_deprecated_cmd,
+ "amr-payload bandwith-efficient",
+ "Set AMR payload framing mode\n"
+ "payload fields packed (AoIP)\n");
-DEFUN(cfg_net_mnc,
- cfg_net_mnc_cmd,
- "mobile network code <0-999>",
- "Set the GSM mobile network code\n"
- "Network Commands\n"
- CODE_CMD_STR
- "Mobile Network Code to use\n")
+DEFUN_ATTR(cfg_msc_nri_add, cfg_msc_nri_add_cmd,
+ "nri add <0-32767> [<0-32767>]",
+ NRI_STR "Add NRI value or range to the NRI mapping for this MSC\n"
+ NRI_FIRST_LAST_STR,
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- uint16_t mnc;
- bool mnc_3_digits;
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ struct bsc_msc_data *other_msc;
+ bool before;
+ int rc;
+ const char *message;
+ struct osmo_nri_range add_range;
- if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) {
- vty_out(vty, "%% Error decoding MNC: %s%s", argv[0], VTY_NEWLINE);
- return CMD_WARNING;
+ rc = osmo_nri_ranges_vty_add(&message, &add_range, msc->nri_ranges, argc, argv, bsc_gsmnet->nri_bitlen);
+ if (message) {
+ NRI_WARN(msc, "%s: " NRI_ARGS_TO_STR_FMT, message, NRI_ARGS_TO_STR_ARGS(argc, argv));
}
+ if (rc < 0)
+ return CMD_WARNING;
- gsmnet->plmn.mnc = mnc;
- gsmnet->plmn.mnc_3_digits = mnc_3_digits;
-
+ /* Issue a warning about NRI range overlaps (but still allow them).
+ * Overlapping ranges will map to whichever MSC comes fist in the bsc_gsmnet->mscs llist,
+ * which is not necessarily in the order of increasing msc->nr. */
+ before = true;
+ llist_for_each_entry(other_msc, &bsc_gsmnet->mscs, entry) {
+ if (other_msc == msc) {
+ before = false;
+ continue;
+ }
+ if (osmo_nri_range_overlaps_ranges(&add_range, other_msc->nri_ranges)) {
+ NRI_WARN(msc, "NRI range [%d..%d] overlaps between msc %d and msc %d."
+ " For overlaps, msc %d has higher priority than msc %d",
+ add_range.first, add_range.last, msc->nr, other_msc->nr,
+ before ? other_msc->nr : msc->nr, before ? msc->nr : other_msc->nr);
+ }
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_net_encryption,
- cfg_net_encryption_cmd,
- "encryption a5 <0-3> [<0-3>] [<0-3>] [<0-3>]",
- "Encryption options\n"
- "GSM A5 Air Interface Encryption\n"
- "A5/n Algorithm Number\n"
- "A5/n Algorithm Number\n"
- "A5/n Algorithm Number\n"
- "A5/n Algorithm Number\n")
+DEFUN_ATTR(cfg_msc_nri_del, cfg_msc_nri_del_cmd,
+ "nri del <0-32767> [<0-32767>]",
+ NRI_STR "Remove NRI value or range from the NRI mapping for this MSC\n"
+ NRI_FIRST_LAST_STR,
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- unsigned int i;
-
- gsmnet->a5_encryption_mask = 0;
- for (i = 0; i < argc; i++)
- gsmnet->a5_encryption_mask |= (1 << atoi(argv[i]));
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ int rc;
+ const char *message;
+ rc = osmo_nri_ranges_vty_del(&message, NULL, msc->nri_ranges, argc, argv);
+ if (message) {
+ NRI_WARN(msc, "%s: " NRI_ARGS_TO_STR_FMT, message, NRI_ARGS_TO_STR_ARGS(argc, argv));
+ }
+ if (rc < 0)
+ return CMD_WARNING;
return CMD_SUCCESS;
}
-DEFUN_DEPRECATED(cfg_net_dyn_ts_allow_tch_f,
- cfg_net_dyn_ts_allow_tch_f_cmd,
- "dyn_ts_allow_tch_f (0|1)",
- "Allow or disallow allocating TCH/F on TCH_F_TCH_H_PDCH timeslots\n"
- "Disallow TCH/F on TCH_F_TCH_H_PDCH (default)\n"
- "Allow TCH/F on TCH_F_TCH_H_PDCH\n")
+DEFUN_ATTR(cfg_msc_allow_attach, cfg_msc_allow_attach_cmd,
+ "allow-attach",
+ "Allow this MSC to attach new subscribers (default).\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- gsmnet->dyn_ts_allow_tch_f = atoi(argv[0]) ? true : false;
- vty_out(vty, "%% dyn_ts_allow_tch_f is deprecated, rather use msc/codec-list to pick codecs%s",
- VTY_NEWLINE);
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ msc->allow_attach = true;
return CMD_SUCCESS;
}
-DEFUN(cfg_net_timezone,
- cfg_net_timezone_cmd,
- "timezone <-19-19> (0|15|30|45)",
- "Set the Timezone Offset of the network\n"
- "Timezone offset (hours)\n"
- "Timezone offset (00 minutes)\n"
- "Timezone offset (15 minutes)\n"
- "Timezone offset (30 minutes)\n"
- "Timezone offset (45 minutes)\n"
- )
+DEFUN_ATTR(cfg_msc_no_allow_attach, cfg_msc_no_allow_attach_cmd,
+ "no allow-attach",
+ NO_STR
+ "Do not assign new subscribers to this MSC."
+ " Useful if an MSC in an MSC pool is configured to off-load subscribers."
+ " The MSC will still be operational for already IMSI-Attached subscribers,"
+ " but the NAS node selection function will skip this MSC for new subscribers\n",
+ CMD_ATTR_IMMEDIATE)
{
- struct gsm_network *net = vty->index;
- int tzhr = atoi(argv[0]);
- int tzmn = atoi(argv[1]);
-
- net->tz.hr = tzhr;
- net->tz.mn = tzmn;
- net->tz.dst = 0;
- net->tz.override = 1;
-
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ msc->allow_attach = false;
return CMD_SUCCESS;
}
-DEFUN(cfg_net_timezone_dst,
- cfg_net_timezone_dst_cmd,
- "timezone <-19-19> (0|15|30|45) <0-2>",
- "Set the Timezone Offset of the network\n"
- "Timezone offset (hours)\n"
- "Timezone offset (00 minutes)\n"
- "Timezone offset (15 minutes)\n"
- "Timezone offset (30 minutes)\n"
- "Timezone offset (45 minutes)\n"
- "DST offset (hours)\n"
- )
+static void msc_write_nri(struct vty *vty, struct bsc_msc_data *msc, bool verbose)
{
- struct gsm_network *net = vty->index;
- int tzhr = atoi(argv[0]);
- int tzmn = atoi(argv[1]);
- int tzdst = atoi(argv[2]);
+ struct osmo_nri_range *r;
- net->tz.hr = tzhr;
- net->tz.mn = tzmn;
- net->tz.dst = tzdst;
- net->tz.override = 1;
+ if (verbose) {
+ vty_out(vty, "msc %d%s", msc->nr, VTY_NEWLINE);
+ if (llist_empty(&msc->nri_ranges->entries)) {
+ vty_out(vty, " %% no NRI mappings%s", VTY_NEWLINE);
+ return;
+ }
+ }
- return CMD_SUCCESS;
+ llist_for_each_entry(r, &msc->nri_ranges->entries, entry) {
+ if (osmo_nri_range_validate(r, 255))
+ vty_out(vty, " %% INVALID RANGE:");
+ vty_out(vty, " nri add %d", r->first);
+ if (r->first != r->last)
+ vty_out(vty, " %d", r->last);
+ vty_out(vty, "%s", VTY_NEWLINE);
+ }
}
-DEFUN(cfg_net_no_timezone,
- cfg_net_no_timezone_cmd,
- "no timezone",
- NO_STR
- "Disable network timezone override, use system tz\n")
+DEFUN(cfg_msc_show_nri, cfg_msc_show_nri_cmd,
+ "show nri",
+ SHOW_STR NRI_STR)
{
- struct gsm_network *net = vty->index;
-
- net->tz.override = 0;
-
+ struct bsc_msc_data *msc = bsc_msc_data(vty);
+ msc_write_nri(vty, msc, true);
return CMD_SUCCESS;
}
-DEFUN(cfg_net_per_loc_upd, cfg_net_per_loc_upd_cmd,
- "periodic location update <6-1530>",
- "Periodic Location Updating Interval\n"
- "Periodic Location Updating Interval\n"
- "Periodic Location Updating Interval\n"
- "Periodic Location Updating Interval in Minutes\n")
+DEFUN(show_nri, show_nri_cmd,
+ "show nri [" MSC_NR_RANGE "]",
+ SHOW_STR NRI_STR "Optional MSC number to limit to\n")
{
- struct gsm_network *net = vty->index;
- struct T_def *d = T_def_get_entry(net->T_defs, 3212);
+ struct bsc_msc_data *msc;
+ if (argc > 0) {
+ int msc_nr = atoi(argv[0]);
+ msc = osmo_msc_data_find(bsc_gsmnet, msc_nr);
+ if (!msc) {
+ vty_out(vty, "%% No such MSC%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
+ }
+ msc_write_nri(vty, msc, true);
+ return CMD_SUCCESS;
+ }
- OSMO_ASSERT(d);
- d->val = atoi(argv[0]) / 6;
- vty_out(vty, "T%d = %u %s (%s)%s", d->T, d->val, "* 6min", d->desc, VTY_NEWLINE);
+ llist_for_each_entry(msc, &bsc_gsmnet->mscs, entry) {
+ msc_write_nri(vty, msc, true);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_net_no_per_loc_upd, cfg_net_no_per_loc_upd_cmd,
- "no periodic location update",
- NO_STR
- "Periodic Location Updating Interval\n"
- "Periodic Location Updating Interval\n"
- "Periodic Location Updating Interval\n")
+/* Hidden since it exists only for use by ttcn3 tests */
+DEFUN_HIDDEN(mscpool_roundrobin_next, mscpool_roundrobin_next_cmd,
+ "mscpool roundrobin next " MSC_NR_RANGE,
+ "MSC pooling: load balancing across multiple MSCs.\n"
+ "Adjust current state of the MSC round-robin algorithm (for testing).\n"
+ "Set the MSC nr to direct the next new subscriber to (for testing).\n"
+ "MSC number, as in the config file; if the number does not exist,"
+ " the round-robin continues to the next valid number.\n")
{
- struct gsm_network *net = vty->index;
- struct T_def *d = T_def_get_entry(net->T_defs, 3212);
-
- OSMO_ASSERT(d);
- d->val = 0;
- vty_out(vty, "T%d = %u %s (%s)%s", d->T, d->val, "* 6min", d->desc, VTY_NEWLINE);
+ bsc_gsmnet->mscs_round_robin_next_nr = atoi(argv[0]);
return CMD_SUCCESS;
}
-#define MEAS_FEED_STR "Measurement Report export\n"
-
-DEFUN(cfg_net_meas_feed_dest, cfg_net_meas_feed_dest_cmd,
- "meas-feed destination ADDR <0-65535>",
- MEAS_FEED_STR "Where to forward Measurement Report feeds\n" "address or hostname\n" "port number\n")
+DEFUN(msc_bssmap_reset, msc_bssmap_reset_cmd,
+ "msc " MSC_NR_RANGE " bssmap reset",
+ "Query or manipulate a specific A-interface link\n"
+ "MSC nr\n"
+ "Query or manipulate BSSMAP layer of A-interface\n"
+ "Flip this MSC to disconnected state and re-send BSSMAP RESET\n")
{
- int rc;
- const char *host = argv[0];
- uint16_t port = atoi(argv[1]);
-
- rc = meas_feed_cfg_set(host, port);
- if (rc < 0)
- return CMD_WARNING;
+ int msc_nr = atoi(argv[0]);
+ struct bsc_msc_data *msc;
- return CMD_SUCCESS;
-}
+ msc = osmo_msc_data_find(bsc_gsmnet, msc_nr);
-DEFUN(cfg_net_meas_feed_scenario, cfg_net_meas_feed_scenario_cmd,
- "meas-feed scenario NAME",
- MEAS_FEED_STR "Set a name to include in the Measurement Report feeds\n" "Name string, up to 31 characters\n")
-{
- meas_feed_scenario_set(argv[0]);
+ if (!msc) {
+ vty_out(vty, "%% No such MSC: nr %d\n", msc_nr);
+ return CMD_WARNING;
+ }
+ LOGP(DMSC, LOGL_NOTICE, "(msc%d) VTY requests BSSMAP RESET\n", msc_nr);
+ bssmap_reset_resend_reset(msc->a.bssmap_reset);
return CMD_SUCCESS;
}
-extern int bsc_vty_init_extra(void);
-
int bsc_vty_init(struct gsm_network *network)
{
- cfg_ts_pchan_cmd.string =
- vty_cmd_string_from_valstr(tall_bsc_ctx,
- gsm_pchant_names,
- "phys_chan_config (", "|", ")",
- VTY_DO_LOWER);
- cfg_ts_pchan_cmd.doc =
- vty_cmd_string_from_valstr(tall_bsc_ctx,
- gsm_pchant_descs,
- "Physical Channel Combination\n",
- "\n", "", 0);
-
- cfg_bts_type_cmd.string =
- vty_cmd_string_from_valstr(tall_bsc_ctx,
- bts_type_names,
- "type (", "|", ")",
- VTY_DO_LOWER);
- cfg_bts_type_cmd.doc =
- vty_cmd_string_from_valstr(tall_bsc_ctx,
- bts_type_descs,
- "BTS Vendor/Type\n",
- "\n", "", 0);
-
OSMO_ASSERT(vty_global_gsm_network == NULL);
vty_global_gsm_network = network;
@@ -5076,15 +3638,28 @@ int bsc_vty_init(struct gsm_network *network)
install_element(GSMNET_NODE, &cfg_net_dyn_ts_allow_tch_f_cmd);
install_element(GSMNET_NODE, &cfg_net_meas_feed_dest_cmd);
install_element(GSMNET_NODE, &cfg_net_meas_feed_scenario_cmd);
+ install_element(GSMNET_NODE, &cfg_net_meas_feed_wqueue_max_len_cmd);
+ install_element(GSMNET_NODE, &cfg_net_timer_cmd);
+ install_element(GSMNET_NODE, &cfg_net_allow_unusable_timeslots_cmd);
+ install_element(GSMNET_NODE, &cfg_net_pcu_sock_path_cmd);
+ install_element(GSMNET_NODE, &cfg_bts_pcu_sock_wqueue_len_cmd);
+ install_element(GSMNET_NODE, &cfg_net_no_pcu_sock_cmd);
+
+ /* Timer configuration commands (generic osmo_tdef API) */
+ osmo_tdef_vty_groups_init(GSMNET_NODE, bsc_tdef_group);
install_element_ve(&bsc_show_net_cmd);
install_element_ve(&show_bts_cmd);
+ install_element_ve(&show_bts_brief_cmd);
+ install_element_ve(&show_bts_fail_rep_cmd);
install_element_ve(&show_rejected_bts_cmd);
install_element_ve(&show_trx_cmd);
install_element_ve(&show_trx_con_cmd);
install_element_ve(&show_ts_cmd);
install_element_ve(&show_lchan_cmd);
install_element_ve(&show_lchan_summary_cmd);
+ install_element_ve(&show_lchan_summary_all_cmd);
+ install_element_ve(&show_timer_cmd);
install_element_ve(&show_subscr_conn_cmd);
@@ -5094,161 +3669,39 @@ int bsc_vty_init(struct gsm_network *network)
install_element(ENABLE_NODE, &handover_any_cmd);
install_element(ENABLE_NODE, &assignment_any_cmd);
install_element(ENABLE_NODE, &handover_any_to_arfcn_bsic_cmd);
+ /* See also handover commands added on net level from handover_vty.c */
- logging_vty_add_cmds(NULL);
+ logging_vty_add_cmds();
osmo_talloc_vty_add_cmds();
- T_defs_vty_init(network->T_defs, GSMNET_NODE);
-
install_element(GSMNET_NODE, &cfg_net_neci_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_net_nri_bitlen_cmd);
+ install_element(GSMNET_NODE, &cfg_net_nri_null_add_cmd);
+ install_element(GSMNET_NODE, &cfg_net_nri_null_del_cmd);
- install_element(GSMNET_NODE, &cfg_bts_cmd);
- install_node(&bts_node, config_write_bts);
- install_element(BTS_NODE, &cfg_bts_type_cmd);
- install_element(BTS_NODE, &cfg_description_cmd);
- install_element(BTS_NODE, &cfg_no_description_cmd);
- install_element(BTS_NODE, &cfg_bts_band_cmd);
- install_element(BTS_NODE, &cfg_bts_ci_cmd);
- install_element(BTS_NODE, &cfg_bts_dtxu_cmd);
- install_element(BTS_NODE, &cfg_bts_dtxd_cmd);
- install_element(BTS_NODE, &cfg_bts_no_dtxu_cmd);
- install_element(BTS_NODE, &cfg_bts_no_dtxd_cmd);
- install_element(BTS_NODE, &cfg_bts_lac_cmd);
- install_element(BTS_NODE, &cfg_bts_tsc_cmd);
- install_element(BTS_NODE, &cfg_bts_bsic_cmd);
- install_element(BTS_NODE, &cfg_bts_unit_id_cmd);
- install_element(BTS_NODE, &cfg_bts_rsl_ip_cmd);
- install_element(BTS_NODE, &cfg_bts_nokia_site_skip_reset_cmd);
- install_element(BTS_NODE, &cfg_bts_nokia_site_no_loc_rel_cnf_cmd);
- install_element(BTS_NODE, &cfg_bts_nokia_site_bts_reset_timer_cnf_cmd);
- install_element(BTS_NODE, &cfg_bts_stream_id_cmd);
- install_element(BTS_NODE, &cfg_bts_oml_e1_cmd);
- install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd);
- install_element(BTS_NODE, &cfg_bts_challoc_cmd);
- install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd);
- install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd);
- install_element(BTS_NODE, &cfg_bts_chan_desc_att_cmd);
- install_element(BTS_NODE, &cfg_bts_chan_desc_bs_pa_mfrms_cmd);
- install_element(BTS_NODE, &cfg_bts_chan_desc_bs_ag_blks_res_cmd);
- install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd);
- install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd);
- install_element(BTS_NODE, &cfg_bts_cell_barred_cmd);
- install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd);
- install_element(BTS_NODE, &cfg_bts_rach_ac_class_cmd);
- install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd);
- install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd);
- install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd);
- install_element(BTS_NODE, &cfg_bts_cell_bar_qualify_cmd);
- install_element(BTS_NODE, &cfg_bts_cell_resel_ofs_cmd);
- install_element(BTS_NODE, &cfg_bts_temp_ofs_cmd);
- install_element(BTS_NODE, &cfg_bts_temp_ofs_inf_cmd);
- install_element(BTS_NODE, &cfg_bts_penalty_time_cmd);
- install_element(BTS_NODE, &cfg_bts_penalty_time_rsvd_cmd);
- install_element(BTS_NODE, &cfg_bts_radio_link_timeout_cmd);
- install_element(BTS_NODE, &cfg_bts_radio_link_timeout_inf_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_11bit_rach_support_for_egprs_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_net_ctrl_ord_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_ctrl_ack_cmd);
- install_element(BTS_NODE, &cfg_no_bts_gprs_ctrl_ack_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd);
- install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd);
- install_element(BTS_NODE, &cfg_bts_pag_free_cmd);
- install_element(BTS_NODE, &cfg_bts_si_mode_cmd);
- install_element(BTS_NODE, &cfg_bts_si_static_cmd);
- install_element(BTS_NODE, &cfg_bts_si_unused_send_empty_cmd);
- install_element(BTS_NODE, &cfg_bts_no_si_unused_send_empty_cmd);
- install_element(BTS_NODE, &cfg_bts_early_cm_cmd);
- install_element(BTS_NODE, &cfg_bts_early_cm_3g_cmd);
- install_element(BTS_NODE, &cfg_bts_neigh_mode_cmd);
- install_element(BTS_NODE, &cfg_bts_neigh_cmd);
- install_element(BTS_NODE, &cfg_bts_si5_neigh_cmd);
- install_element(BTS_NODE, &cfg_bts_si2quater_neigh_add_cmd);
- install_element(BTS_NODE, &cfg_bts_si2quater_neigh_del_cmd);
- install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_add_cmd);
- install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_del_cmd);
- install_element(BTS_NODE, &cfg_bts_excl_rf_lock_cmd);
- install_element(BTS_NODE, &cfg_bts_no_excl_rf_lock_cmd);
- install_element(BTS_NODE, &cfg_bts_force_comb_si_cmd);
- install_element(BTS_NODE, &cfg_bts_no_force_comb_si_cmd);
- install_element(BTS_NODE, &cfg_bts_codec0_cmd);
- install_element(BTS_NODE, &cfg_bts_codec1_cmd);
- install_element(BTS_NODE, &cfg_bts_codec2_cmd);
- install_element(BTS_NODE, &cfg_bts_codec3_cmd);
- install_element(BTS_NODE, &cfg_bts_codec4_cmd);
- install_element(BTS_NODE, &cfg_bts_depends_on_cmd);
- install_element(BTS_NODE, &cfg_bts_no_depends_on_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_modes1_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_modes2_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_modes3_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_modes4_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_thres1_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_thres2_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_thres3_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_hyst1_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_hyst2_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_hyst3_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_fr_start_mode_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_hr_modes1_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_hr_modes2_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_hr_modes3_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_hr_modes4_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_hr_thres1_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_hr_thres2_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_hr_thres3_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_hr_hyst1_cmd);
- install_element(BTS_NODE, &cfg_bts_amr_hr_hyst2_cmd);
- 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);
- install_element(BTS_NODE, &cfg_bts_acc_ramping_cmd);
- install_element(BTS_NODE, &cfg_bts_no_acc_ramping_cmd);
- install_element(BTS_NODE, &cfg_bts_acc_ramping_step_interval_cmd);
- install_element(BTS_NODE, &cfg_bts_acc_ramping_step_size_cmd);
- install_element(BTS_NODE, &cfg_bts_t3113_dynamic_cmd);
- install_element(BTS_NODE, &cfg_bts_no_t3113_dynamic_cmd);
- neighbor_ident_vty_init(network, network->neighbor_bss_cells);
- /* 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);
- install_element(TRX_NODE, &cfg_trx_arfcn_cmd);
- install_element(TRX_NODE, &cfg_description_cmd);
- install_element(TRX_NODE, &cfg_no_description_cmd);
- install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
- install_element(TRX_NODE, &cfg_trx_max_power_red_cmd);
- install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd);
- install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd);
- install_element(TRX_NODE, &cfg_trx_rf_locked_cmd);
-
- install_element(TRX_NODE, &cfg_ts_cmd);
- install_node(&ts_node, dummy_config_write);
- install_element(TS_NODE, &cfg_ts_pchan_cmd);
- install_element(TS_NODE, &cfg_ts_pchan_compat_cmd);
- install_element(TS_NODE, &cfg_ts_tsc_cmd);
- install_element(TS_NODE, &cfg_ts_hopping_cmd);
- install_element(TS_NODE, &cfg_ts_hsn_cmd);
- install_element(TS_NODE, &cfg_ts_maio_cmd);
- install_element(TS_NODE, &cfg_ts_arfcn_add_cmd);
- install_element(TS_NODE, &cfg_ts_arfcn_del_cmd);
- install_element(TS_NODE, &cfg_ts_e1_subslot_cmd);
+ bts_vty_init();
+ mgcp_client_pool_vty_init(GSMNET_NODE, MGW_NODE, NULL, vty_global_gsm_network->mgw.mgw_pool);
install_element(ENABLE_NODE, &drop_bts_cmd);
install_element(ENABLE_NODE, &restart_bts_cmd);
- install_element(ENABLE_NODE, &bts_resend_cmd);
+ install_element(ENABLE_NODE, &bts_unblock_setup_ramping_cmd);
+ install_element(ENABLE_NODE, &bts_resend_sysinfo_cmd);
+ install_element(ENABLE_NODE, &bts_resend_power_ctrl_params_cmd);
+ install_element(ENABLE_NODE, &bts_c0_power_red_cmd);
install_element(ENABLE_NODE, &pdch_act_cmd);
install_element(ENABLE_NODE, &lchan_act_cmd);
+ install_element(ENABLE_NODE, &lchan_deact_cmd);
+ install_element(ENABLE_NODE, &lchan_act_all_cmd);
+ install_element(ENABLE_NODE, &lchan_act_all_bts_cmd);
+ install_element(ENABLE_NODE, &lchan_act_all_trx_cmd);
+ install_element(ENABLE_NODE, &vamos_modify_lchan_cmd);
install_element(ENABLE_NODE, &lchan_mdcx_cmd);
+ install_element(ENABLE_NODE, &lchan_set_borken_cmd);
+ install_element(ENABLE_NODE, &lchan_reassign_cmd);
+ install_element(ENABLE_NODE, &lchan_set_mspower_cmd);
+
install_element(ENABLE_NODE, &handover_subscr_conn_cmd);
install_element(ENABLE_NODE, &assignment_subscr_conn_cmd);
install_element(ENABLE_NODE, &smscb_cmd_cmd);
@@ -5260,8 +3713,84 @@ int bsc_vty_init(struct gsm_network *network)
osmo_fsm_vty_add_cmds();
ho_vty_init();
-
- bsc_vty_init_extra();
+ cbc_vty_init();
+ smscb_vty_init();
+
+ install_element(CONFIG_NODE, &cfg_net_msc_cmd);
+ install_element(CONFIG_NODE, &cfg_net_bsc_cmd);
+
+ install_node(&bsc_node, config_write_bsc);
+ install_element(BSC_NODE, &cfg_net_bsc_mid_call_text_cmd);
+ install_element(BSC_NODE, &cfg_net_bsc_mid_call_timeout_cmd);
+ install_element(BSC_NODE, &cfg_net_rf_socket_cmd);
+ install_element(BSC_NODE, &cfg_net_rf_off_time_cmd);
+ install_element(BSC_NODE, &cfg_net_no_rf_off_time_cmd);
+ install_element(BSC_NODE, &cfg_net_bsc_missing_msc_ussd_cmd);
+ install_element(BSC_NODE, &cfg_net_bsc_no_missing_msc_text_cmd);
+ install_element(BSC_NODE, &cfg_bsc_bts_setup_ramping_cmd);
+ install_element(BSC_NODE, &cfg_bsc_no_bts_setup_ramping_cmd);
+ install_element(BSC_NODE, &cfg_bsc_bts_setup_ramping_step_size_cmd);
+ install_element(BSC_NODE, &cfg_bsc_bts_setup_ramping_step_interval_cmd);
+
+ install_node(&msc_node, config_write_msc);
+ install_element(MSC_NODE, &cfg_net_msc_ncc_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_mcc_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_lac_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_ci_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_rtp_base_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_codec_list_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_dest_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_dest_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_welcome_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_lost_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_lost_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_grace_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_no_grace_ussd_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_type_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_emerg_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_12_2_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_10_2_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_7_95_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_7_40_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_6_70_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_5_90_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_5_15_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_4_75_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_octet_align_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_amr_octet_align_deprecated_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_lcls_mode_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_lcls_mismtch_cmd);
+ install_element(MSC_NODE, &cfg_msc_cs7_bsc_addr_cmd);
+ install_element(MSC_NODE, &cfg_msc_cs7_msc_addr_cmd);
+ install_element(MSC_NODE, &cfg_msc_cs7_asp_proto_cmd);
+ install_element(MSC_NODE, &cfg_msc_nri_add_cmd);
+ install_element(MSC_NODE, &cfg_msc_nri_del_cmd);
+ install_element(MSC_NODE, &cfg_msc_show_nri_cmd);
+ install_element(MSC_NODE, &cfg_msc_allow_attach_cmd);
+ install_element(MSC_NODE, &cfg_msc_no_allow_attach_cmd);
+ install_element(MSC_NODE, &cfg_msc_mgw_x_osmo_ign_cmd);
+ install_element(MSC_NODE, &cfg_msc_no_mgw_x_osmo_ign_cmd);
+ install_element(MSC_NODE, &cfg_msc_osmux_cmd);
+ /* Deprecated: Old MGCP config without pooling support in MSC node: */
+ mgcp_client_vty_init(network, MSC_NODE, network->mgw.conf);
+ /* Deprecated: ping time config, kept to support legacy config files. */
+ install_element(MSC_NODE, &cfg_net_msc_no_ping_time_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_ping_time_cmd);
+ install_element(MSC_NODE, &cfg_net_msc_pong_time_cmd);
+
+ install_element_ve(&show_statistics_cmd);
+ install_element_ve(&show_mscs_cmd);
+ install_element_ve(&show_pos_cmd);
+ install_element_ve(&logging_fltr_imsi_cmd);
+ install_element_ve(&show_subscr_all_cmd);
+ install_element_ve(&show_nri_cmd);
+
+ install_element(ENABLE_NODE, &gen_position_trap_cmd);
+ install_element(ENABLE_NODE, &mscpool_roundrobin_next_cmd);
+ install_element(ENABLE_NODE, &msc_bssmap_reset_cmd);
+
+ install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
return 0;
}
diff --git a/src/osmo-bsc/bssmap_reset.c b/src/osmo-bsc/bssmap_reset.c
new file mode 100644
index 000000000..018ecbac1
--- /dev/null
+++ b/src/osmo-bsc/bssmap_reset.c
@@ -0,0 +1,263 @@
+/* (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Authors: Philipp Maier, 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bssmap_reset.h>
+#include <osmocom/bsc/gsm_data.h>
+
+static struct osmo_fsm bssmap_reset_fsm;
+
+enum bssmap_reset_fsm_state {
+ BSSMAP_RESET_ST_DISC,
+ BSSMAP_RESET_ST_CONN,
+};
+
+static const struct value_string bssmap_reset_fsm_event_names[] = {
+ OSMO_VALUE_STRING(BSSMAP_RESET_EV_RX_RESET),
+ OSMO_VALUE_STRING(BSSMAP_RESET_EV_RX_RESET_ACK),
+ OSMO_VALUE_STRING(BSSMAP_RESET_EV_CONN_CFM_FAILURE),
+ OSMO_VALUE_STRING(BSSMAP_RESET_EV_CONN_CFM_SUCCESS),
+ {}
+};
+
+static const struct osmo_tdef_state_timeout bssmap_reset_timeouts[32] = {
+ [BSSMAP_RESET_ST_DISC] = { .T = 4 },
+};
+
+#define bssmap_reset_fsm_state_chg(FI, STATE) \
+ osmo_tdef_fsm_inst_state_chg(FI, STATE, \
+ bssmap_reset_timeouts, \
+ (bsc_gsmnet)->T_defs, \
+ -1)
+
+struct bssmap_reset *bssmap_reset_alloc(void *ctx, const char *label, const struct bssmap_reset_cfg *cfg)
+{
+ struct bssmap_reset *bssmap_reset;
+ struct osmo_fsm_inst *fi;
+
+ fi = osmo_fsm_inst_alloc(&bssmap_reset_fsm, ctx, NULL, LOGL_DEBUG, label);
+ OSMO_ASSERT(fi);
+
+ bssmap_reset = talloc_zero(fi, struct bssmap_reset);
+ OSMO_ASSERT(bssmap_reset);
+ *bssmap_reset = (struct bssmap_reset){
+ .fi = fi,
+ .cfg = *cfg,
+ };
+ fi->priv = bssmap_reset;
+
+ /* Immediately (1ms) kick off reset sending mechanism */
+ osmo_fsm_inst_state_chg_ms(fi, BSSMAP_RESET_ST_DISC, 1, 0);
+ return bssmap_reset;
+}
+
+void bssmap_reset_term_and_free(struct bssmap_reset *bssmap_reset)
+{
+ if (!bssmap_reset)
+ return;
+ osmo_fsm_inst_term(bssmap_reset->fi, OSMO_FSM_TERM_REQUEST, NULL);
+ talloc_free(bssmap_reset);
+}
+
+static void link_up(struct bssmap_reset *bssmap_reset)
+{
+ LOGPFSML(bssmap_reset->fi, LOGL_NOTICE, "link up\n");
+ bssmap_reset->conn_cfm_failures = 0;
+ if (bssmap_reset->cfg.ops.link_up)
+ bssmap_reset->cfg.ops.link_up(bssmap_reset->cfg.data);
+}
+
+static void link_lost(struct bssmap_reset *bssmap_reset)
+{
+ LOGPFSML(bssmap_reset->fi, LOGL_NOTICE, "link lost\n");
+ if (bssmap_reset->cfg.ops.link_lost)
+ bssmap_reset->cfg.ops.link_lost(bssmap_reset->cfg.data);
+}
+
+static void tx_reset(struct bssmap_reset *bssmap_reset)
+{
+ if (bssmap_reset->cfg.ops.tx_reset)
+ bssmap_reset->cfg.ops.tx_reset(bssmap_reset->cfg.data);
+}
+
+static void tx_reset_ack(struct bssmap_reset *bssmap_reset)
+{
+ if (bssmap_reset->cfg.ops.tx_reset_ack)
+ bssmap_reset->cfg.ops.tx_reset_ack(bssmap_reset->cfg.data);
+}
+
+static void bssmap_reset_disc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct bssmap_reset *bssmap_reset = (struct bssmap_reset*)fi->priv;
+ if (prev_state == BSSMAP_RESET_ST_CONN)
+ link_lost(bssmap_reset);
+}
+
+static void bssmap_reset_disc_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bssmap_reset *bssmap_reset = (struct bssmap_reset*)fi->priv;
+ switch (event) {
+
+ case BSSMAP_RESET_EV_RX_RESET:
+ tx_reset_ack(bssmap_reset);
+ bssmap_reset_fsm_state_chg(fi, BSSMAP_RESET_ST_CONN);
+ break;
+
+ case BSSMAP_RESET_EV_RX_RESET_ACK:
+ bssmap_reset_fsm_state_chg(fi, BSSMAP_RESET_ST_CONN);
+ break;
+
+ case BSSMAP_RESET_EV_CONN_CFM_FAILURE:
+ /* ignore */
+ break;
+
+ case BSSMAP_RESET_EV_CONN_CFM_SUCCESS:
+ /* A connection succeeded before we managed to do a RESET handshake?
+ * Then the calling code is not taking care to check bssmap_reset_is_conn_ready().
+ */
+ LOGPFSML(fi, LOGL_ERROR, "Connection success confirmed, but we have not seen a RESET-ACK; bug?\n");
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void bssmap_reset_conn_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct bssmap_reset *bssmap_reset = (struct bssmap_reset*)fi->priv;
+ if (prev_state != BSSMAP_RESET_ST_CONN)
+ link_up(bssmap_reset);
+}
+
+static void bssmap_reset_conn_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bssmap_reset *bssmap_reset = (struct bssmap_reset*)fi->priv;
+
+ switch (event) {
+
+ case BSSMAP_RESET_EV_RX_RESET:
+ /* We were connected, but the remote side has restarted. */
+ link_lost(bssmap_reset);
+ tx_reset_ack(bssmap_reset);
+ link_up(bssmap_reset);
+ break;
+
+ case BSSMAP_RESET_EV_RX_RESET_ACK:
+ LOGPFSML(fi, LOGL_INFO, "Link is already up, ignoring RESET ACK\n");
+ break;
+
+ case BSSMAP_RESET_EV_CONN_CFM_FAILURE:
+ bssmap_reset->conn_cfm_failures++;
+ if (bssmap_reset->conn_cfm_failures > bssmap_reset->cfg.conn_cfm_failure_threshold)
+ bssmap_reset_fsm_state_chg(fi, BSSMAP_RESET_ST_DISC);
+ break;
+
+ case BSSMAP_RESET_EV_CONN_CFM_SUCCESS:
+ bssmap_reset->conn_cfm_failures = 0;
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static int bssmap_reset_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct bssmap_reset *bssmap_reset = (struct bssmap_reset*)fi->priv;
+
+ tx_reset(bssmap_reset);
+
+ /* (re-)enter disconnect state to resend RESET after timeout. */
+ bssmap_reset_fsm_state_chg(fi, BSSMAP_RESET_ST_DISC);
+
+ /* Return 0 to not terminate the fsm */
+ return 0;
+}
+
+#define S(x) (1 << (x))
+
+static struct osmo_fsm_state bssmap_reset_fsm_states[] = {
+ [BSSMAP_RESET_ST_DISC] = {
+ .name = "DISCONNECTED",
+ .in_event_mask = 0
+ | S(BSSMAP_RESET_EV_RX_RESET)
+ | S(BSSMAP_RESET_EV_RX_RESET_ACK)
+ | S(BSSMAP_RESET_EV_CONN_CFM_FAILURE)
+ | S(BSSMAP_RESET_EV_CONN_CFM_SUCCESS)
+ ,
+ .out_state_mask = 0
+ | S(BSSMAP_RESET_ST_DISC)
+ | S(BSSMAP_RESET_ST_CONN)
+ ,
+ .onenter = bssmap_reset_disc_onenter,
+ .action = bssmap_reset_disc_action,
+ },
+ [BSSMAP_RESET_ST_CONN] = {
+ .name = "CONNECTED",
+ .in_event_mask = 0
+ | S(BSSMAP_RESET_EV_RX_RESET)
+ | S(BSSMAP_RESET_EV_RX_RESET_ACK)
+ | S(BSSMAP_RESET_EV_CONN_CFM_FAILURE)
+ | S(BSSMAP_RESET_EV_CONN_CFM_SUCCESS)
+ ,
+ .out_state_mask = 0
+ | S(BSSMAP_RESET_ST_DISC)
+ | S(BSSMAP_RESET_ST_CONN)
+ ,
+ .onenter = bssmap_reset_conn_onenter,
+ .action = bssmap_reset_conn_action,
+ },
+};
+
+static struct osmo_fsm bssmap_reset_fsm = {
+ .name = "bssmap_reset",
+ .states = bssmap_reset_fsm_states,
+ .num_states = ARRAY_SIZE(bssmap_reset_fsm_states),
+ .log_subsys = DRESET,
+ .timer_cb = bssmap_reset_fsm_timer_cb,
+ .event_names = bssmap_reset_fsm_event_names,
+};
+
+bool bssmap_reset_is_conn_ready(const struct bssmap_reset *bssmap_reset)
+{
+ return bssmap_reset->fi->state == BSSMAP_RESET_ST_CONN;
+}
+
+void bssmap_reset_resend_reset(struct bssmap_reset *bssmap_reset)
+{
+ OSMO_ASSERT(bssmap_reset);
+
+ /* Immediately (1ms) kick off reset sending mechanism */
+ osmo_fsm_inst_state_chg_ms(bssmap_reset->fi, BSSMAP_RESET_ST_DISC, 1, 0);
+}
+
+void bssmap_reset_set_disconnected(struct bssmap_reset *bssmap_reset)
+{
+ /* Go to disconnected state, with the normal RESET timeout to re-send RESET. */
+ bssmap_reset_fsm_state_chg(bssmap_reset->fi, BSSMAP_RESET_ST_DISC);
+}
+
+static __attribute__((constructor)) void bssmap_reset_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&bssmap_reset_fsm) == 0);
+}
diff --git a/src/osmo-bsc/bts.c b/src/osmo-bsc/bts.c
new file mode 100644
index 000000000..8cc9e9af0
--- /dev/null
+++ b/src/osmo-bsc/bts.c
@@ -0,0 +1,1764 @@
+/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/gsm/abis_nm.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/smscb.h>
+
+const struct value_string bts_attribute_names[] = {
+ OSMO_VALUE_STRING(BTS_TYPE_VARIANT),
+ OSMO_VALUE_STRING(BTS_SUB_MODEL),
+ OSMO_VALUE_STRING(TRX_PHY_VERSION),
+ { 0, NULL }
+};
+
+enum bts_attribute str2btsattr(const char *s)
+{
+ return get_string_value(bts_attribute_names, s);
+}
+
+const char *btsatttr2str(enum bts_attribute v)
+{
+ return get_value_string(bts_attribute_names, v);
+}
+
+const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = {
+ { BTS_UNKNOWN, "unknown" },
+ { BTS_OSMO_LITECELL15, "osmo-bts-lc15" },
+ { BTS_OSMO_OCTPHY, "osmo-bts-octphy" },
+ { BTS_OSMO_SYSMO, "osmo-bts-sysmo" },
+ { BTS_OSMO_TRX, "osmo-bts-trx" },
+ { 0, NULL }
+};
+
+enum gsm_bts_type_variant str2btsvariant(const char *arg)
+{
+ return get_string_value(osmo_bts_variant_names, arg);
+}
+
+const char *btsvariant2str(enum gsm_bts_type_variant v)
+{
+ return get_value_string(osmo_bts_variant_names, v);
+}
+
+const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE + 1] = {
+ { GSM_BTS_TYPE_UNKNOWN, "unknown" },
+ { GSM_BTS_TYPE_BS11, "bs11" },
+ { GSM_BTS_TYPE_NANOBTS, "nanobts" },
+ { GSM_BTS_TYPE_RBS2000, "rbs2000" },
+ { GSM_BTS_TYPE_NOKIA_SITE, "nokia_site" },
+ { GSM_BTS_TYPE_OSMOBTS, "osmo-bts" },
+ { 0, NULL }
+};
+
+const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1] = {
+ { GSM_BTS_TYPE_UNKNOWN, "Unknown BTS Type" },
+ { GSM_BTS_TYPE_BS11, "Siemens BTS (BS-11 or compatible)" },
+ { GSM_BTS_TYPE_NANOBTS, "ip.access nanoBTS or compatible" },
+ { GSM_BTS_TYPE_RBS2000, "Ericsson RBS2000 Series" },
+ { GSM_BTS_TYPE_NOKIA_SITE, "Nokia {Metro,Ultra,In}Site" },
+ { GSM_BTS_TYPE_OSMOBTS, "Osmocom Base Transceiver Station" },
+ { 0, NULL }
+};
+
+enum gsm_bts_type str2btstype(const char *arg)
+{
+ return get_string_value(bts_type_names, arg);
+}
+
+const char *btstype2str(enum gsm_bts_type type)
+{
+ return get_value_string(bts_type_names, type);
+}
+
+static LLIST_HEAD(bts_models);
+
+struct gsm_bts_model *bts_model_find(enum gsm_bts_type type)
+{
+ struct gsm_bts_model *model;
+
+ llist_for_each_entry(model, &bts_models, list) {
+ if (model->type == type)
+ return model;
+ }
+
+ return NULL;
+}
+
+int gsm_bts_model_register(struct gsm_bts_model *model)
+{
+ if (bts_model_find(model->type))
+ return -EEXIST;
+
+ tlv_def_patch(&model->nm_att_tlvdef, &abis_nm_att_tlvdef);
+ llist_add_tail(&model->list, &bts_models);
+ return 0;
+}
+
+static const uint8_t bts_cell_timer_default[11] = {
+ 3, /* blocking timer (T1) */
+ 3, /* blocking retries */
+ 3, /* unblocking retries */
+ 3, /* reset timer (T2) */
+ 3, /* reset retries */
+ 10, /* suspend timer (T3) in 100ms */
+ 3, /* suspend retries */
+ 10, /* resume timer (T4) in 100ms */
+ 3, /* resume retries */
+ 10, /* capability update timer (T5) */
+ 3, /* capability update retries */
+};
+
+static const struct gprs_rlc_cfg rlc_cfg_default = {
+ .parameter = {
+ [RLC_T3142] = 20,
+ [RLC_T3169] = 5,
+ [RLC_T3191] = 5,
+ [RLC_T3193] = 160, /* 10ms */
+ [RLC_T3195] = 5,
+ [RLC_N3101] = 10,
+ [RLC_N3103] = 4,
+ [RLC_N3105] = 8,
+ [CV_COUNTDOWN] = 15,
+ [T_DL_TBF_EXT] = 250 * 10, /* ms */
+ [T_UL_TBF_EXT] = 250 * 10, /* ms */
+ },
+ .paging = {
+ .repeat_time = 5 * 50, /* ms */
+ .repeat_count = 3,
+ },
+ .cs_mask = 0x1fff,
+ .initial_cs = 2,
+ .initial_mcs = 6,
+};
+
+static int gsm_bts_talloc_destructor(struct gsm_bts *bts)
+{
+ paging_destructor(bts);
+ bts_setup_ramp_remove(bts);
+
+ osmo_timer_del(&bts->cbch_timer);
+
+ bts->site_mgr->bts[0] = NULL;
+
+ if (bts->gprs.cell.mo.fi) {
+ osmo_fsm_inst_free(bts->gprs.cell.mo.fi);
+ bts->gprs.cell.mo.fi = NULL;
+ }
+
+ if (bts->mo.fi) {
+ osmo_fsm_inst_free(bts->mo.fi);
+ bts->mo.fi = NULL;
+ }
+
+ osmo_stat_item_group_free(bts->bts_statg);
+ rate_ctr_group_free(bts->bts_ctrs);
+ return 0;
+}
+
+/* Initialize those parts that don't require osmo-bsc specific dependencies.
+ * This part is shared among the thin programs in osmo-bsc/src/utils/.
+ * osmo-bsc requires further initialization that pulls in more dependencies (see
+ * bsc_bts_alloc_register()). */
+struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, struct gsm_bts_sm *bts_sm, uint8_t bts_num)
+{
+ struct gsm_bts *bts = talloc_zero(bts_sm, struct gsm_bts);
+
+ if (!bts)
+ return NULL;
+
+ talloc_set_destructor(bts, gsm_bts_talloc_destructor);
+
+ bts->nr = bts_num;
+ bts->num_trx = 0;
+ INIT_LLIST_HEAD(&bts->trx_list);
+ bts->network = net;
+
+ bts->ms_max_power = 15; /* dBm */
+
+ bts->site_mgr = bts_sm;
+
+ bts->mo.fi = osmo_fsm_inst_alloc(&nm_bts_fsm, bts, bts,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(bts->mo.fi, "bts%d", bts->nr);
+ gsm_mo_init(&bts->mo, bts, NM_OC_BTS, bts->nr, 0xff, 0xff);
+
+ /* 3GPP TS 08.18, chapter 5.4.1: 0 is reserved for signalling */
+ bts->gprs.cell.bvci = 2;
+ memcpy(&bts->gprs.cell.timer, bts_cell_timer_default,
+ sizeof(bts->gprs.cell.timer));
+ memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default,
+ sizeof(bts->gprs.cell.rlc_cfg));
+ bts->gprs.cell.mo.fi = osmo_fsm_inst_alloc(&nm_gprs_cell_fsm, bts,
+ &bts->gprs.cell, LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(bts->gprs.cell.mo.fi, "gprs-cell%d", bts->nr);
+ gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL,
+ bts->nr, 0xff, 0xff);
+
+ /* init statistics */
+ bts->bts_ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr);
+ if (!bts->bts_ctrs) {
+ talloc_free(bts);
+ return NULL;
+ }
+ bts->bts_statg = osmo_stat_item_group_alloc(bts, &bts_statg_desc, bts->nr);
+
+ bts->all_allocated.sdcch = (struct osmo_time_cc){
+ .cfg = {
+ .gran_usec = 1*1000000,
+ .forget_sum_usec = 60*1000000,
+ .rate_ctr = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_ALL_ALLOCATED_SDCCH),
+ .T_gran = -16,
+ .T_round_threshold = -17,
+ .T_forget_sum = -18,
+ .T_defs = net->T_defs,
+ },
+ };
+ bts->all_allocated.static_sdcch = (struct osmo_time_cc){
+ .cfg = {
+ .gran_usec = 1*1000000,
+ .forget_sum_usec = 60*1000000,
+ .rate_ctr = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_ALL_ALLOCATED_STATIC_SDCCH),
+ .T_gran = -16,
+ .T_round_threshold = -17,
+ .T_forget_sum = -18,
+ .T_defs = net->T_defs,
+ },
+ };
+ bts->all_allocated.tch = (struct osmo_time_cc){
+ .cfg = {
+ .gran_usec = 1*1000000,
+ .forget_sum_usec = 60*1000000,
+ .rate_ctr = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_ALL_ALLOCATED_TCH),
+ .T_gran = -16,
+ .T_round_threshold = -17,
+ .T_forget_sum = -18,
+ .T_defs = net->T_defs,
+ },
+ };
+ bts->all_allocated.static_tch = (struct osmo_time_cc){
+ .cfg = {
+ .gran_usec = 1*1000000,
+ .forget_sum_usec = 60*1000000,
+ .rate_ctr = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_ALL_ALLOCATED_STATIC_TCH),
+ .T_gran = -16,
+ .T_round_threshold = -17,
+ .T_forget_sum = -18,
+ .T_defs = net->T_defs,
+ },
+ };
+
+ /* create our primary TRX */
+ bts->c0 = gsm_bts_trx_alloc(bts);
+ if (!bts->c0) {
+ rate_ctr_group_free(bts->bts_ctrs);
+ osmo_stat_item_group_free(bts->bts_statg);
+ talloc_free(bts);
+ return NULL;
+ }
+ bts->c0->ts[0].pchan_from_config = GSM_PCHAN_CCCH_SDCCH4; /* TODO: really?? */
+
+ bts->ccch_load_ind_thresh = 10; /* 10% of Load: Start sending CCCH LOAD IND */
+ bts->ccch_load_ind_period = 1; /* Send CCCH LOAD IND every 1 second */
+ bts->rach_b_thresh = -1;
+ bts->rach_ldavg_slots = -1;
+
+ paging_init(bts);
+
+ bts->features.data = &bts->_features_data[0];
+ bts->features.data_len = sizeof(bts->_features_data);
+
+ /* si handling */
+ bts->bcch_change_mark = 1;
+ bts->chan_load_avg = 0;
+
+ /* timer overrides */
+ bts->T3122 = 0; /* not overridden by default */
+ bts->T3113_dynamic = true; /* dynamic by default */
+
+ bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED;
+ bts->dtxd = false;
+ bts->gprs.ctrl_ack_type_use_block = true; /* use RLC/MAC control block */
+ bts->neigh_list_manual_mode = NL_MODE_AUTOMATIC;
+ bts->early_classmark_allowed_3g = true; /* 3g Early Classmark Sending controlled by bts->early_classmark_allowed param */
+ bts->si_unused_send_empty = true;
+ bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_ALWAYS;
+ bts->chan_alloc_dyn_params.ul_rxlev_thresh = 50; /* >= -60 dBm */
+ bts->chan_alloc_dyn_params.ul_rxlev_avg_num = 2; /* at least 2 samples */
+ bts->chan_alloc_dyn_params.c0_chan_load_thresh = 60; /* >= 60% */
+ bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */
+ bts->si_common.cell_sel_par.rxlev_acc_min = 0;
+ bts->si_common.si2quater_neigh_list.arfcn = bts->si_common.data.earfcn_list;
+ bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list;
+ bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST;
+ bts->si_common.si2quater_neigh_list.thresh_hi = 0;
+ osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
+ bts->si_common.neigh_list.data = bts->si_common.data.neigh_list;
+ bts->si_common.neigh_list.data_len =
+ sizeof(bts->si_common.data.neigh_list);
+ bts->si_common.si5_neigh_list.data = bts->si_common.data.si5_neigh_list;
+ bts->si_common.si5_neigh_list.data_len =
+ sizeof(bts->si_common.data.si5_neigh_list);
+ bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc;
+ bts->si_common.cell_alloc.data_len =
+ sizeof(bts->si_common.data.cell_alloc);
+ bts->si_common.rach_control.re = 1; /* no re-establishment */
+ bts->si_common.rach_control.tx_integer = 9; /* 12 slots spread - 217/115 slots delay */
+ bts->si_common.rach_control.max_trans = 3; /* 7 retransmissions */
+ bts->si_common.rach_control.t2 = 4; /* no emergency calls */
+ bts->si_common.chan_desc.mscr = 1; /* Indicate R99 MSC in SI3 */
+ bts->si_common.chan_desc.att = 1; /* attachment required */
+ bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5; /* paging frames */
+ bts->si_common.chan_desc.bs_ag_blks_res = 1; /* reserved AGCH blocks */
+ bts->si_common.chan_desc.t3212 = osmo_tdef_get(net->T_defs, 3212, OSMO_TDEF_CUSTOM, -1);
+ bts->si_common.cell_options.pwrc = 0; /* PWRC not set */
+ bts->si_common.cell_sel_par.acs = 0;
+ bts->si_common.ncc_permitted = 0xff;
+ gsm_bts_set_radio_link_timeout(bts, 32); /* Use RADIO LINK TIMEOUT of 32 */
+
+ INIT_LLIST_HEAD(&bts->abis_queue);
+ INIT_LLIST_HEAD(&bts->loc_list);
+ INIT_LLIST_HEAD(&bts->neighbors);
+ INIT_LLIST_HEAD(&bts->oml_fail_rep);
+ INIT_LLIST_HEAD(&bts->chan_rqd_queue);
+
+ /* Don't pin the BTS to any MGW by default: */
+ bts->mgw_pool_target = -1;
+
+ /* Enable all codecs by default. These get reset to a more fine grained selection IF a
+ * 'codec-support' config appears in the config file (see bsc_vty.c). */
+ bts->codec = (struct bts_codec_conf){
+ .hr = 1,
+ .efr = 1,
+ .amr = 1,
+ };
+
+ /* Set reasonable defaults for AMR-FR and AMR-HR rate configuration.
+ * The values are taken from 3GPP TS 51.010-1 (version 13.11.0).
+ * See 14.2.19.4.1 and 14.2.20.4.1 for AMR-FR and AMR-HR, respectively. */
+ static const struct gsm48_multi_rate_conf amr_fr_mr_cfg = {
+ .m4_75 = 1,
+ .m5_90 = 1,
+ .m7_95 = 1,
+ .m12_2 = 1
+ };
+ static const struct gsm48_multi_rate_conf amr_hr_mr_cfg = {
+ .m4_75 = 1,
+ .m5_90 = 1,
+ .m6_70 = 1,
+ .m7_95 = 1,
+ };
+
+ memcpy(bts->mr_full.gsm48_ie, &amr_fr_mr_cfg, sizeof(bts->mr_full.gsm48_ie));
+ memcpy(bts->mr_half.gsm48_ie, &amr_hr_mr_cfg, sizeof(bts->mr_half.gsm48_ie));
+
+ /* ^ C/I (dB) | FR / HR |
+ * | |
+ * | |
+ * MODE4 | |
+ * = | ----+---- THR_MX_Up(3) | 20.5 / 18.0 |
+ * | | |
+ * | = ----+---- THR_MX_Dn(4) | 18.5 / 16.0 |
+ * MODE3 | |
+ * | = ----+---- THR_MX_Up(2) | 14.5 / 14.0 |
+ * | | |
+ * = | ----+---- THR_MX_Dn(3) | 12.5 / 12.0 |
+ * MODE2 | |
+ * = | ----+---- THR_MX_Up(1) | 8.5 / 10.0 |
+ * | | |
+ * | = ----+---- THR_MX_Dn(2) | 6.5 / 8.0 |
+ * MODE1 | |
+ * | |
+ * | |
+ */
+ static const struct amr_mode amr_fr_ms_bts_mode[] = {
+ {
+ .mode = 0, /* 4.75k */
+ .threshold = 13, /* THR_MX_Dn(2): 6.5 dB */
+ .hysteresis = 4, /* THR_MX_Up(1): 8.5 dB */
+ },
+ {
+ .mode = 2, /* 5.90k */
+ .threshold = 25, /* THR_MX_Dn(3): 12.5 dB */
+ .hysteresis = 4, /* THR_MX_Up(2): 14.5 dB */
+ },
+ {
+ .mode = 5, /* 7.95k */
+ .threshold = 37, /* THR_MX_Dn(4): 18.5 dB */
+ .hysteresis = 4, /* THR_MX_Up(3): 20.5 dB */
+ },
+ {
+ .mode = 7, /* 12.2k */
+ /* this is the last mode, so no threshold */
+ },
+ };
+ static const struct amr_mode amr_hr_ms_bts_mode[] = {
+ {
+ .mode = 0, /* 4.75k */
+ .threshold = 16, /* THR_MX_Dn(2): 8.0 dB */
+ .hysteresis = 4, /* THR_MX_Up(1): 10.0 dB */
+ },
+ {
+ .mode = 2, /* 5.90k */
+ .threshold = 24, /* THR_MX_Dn(3): 12.0 dB */
+ .hysteresis = 4, /* THR_MX_Up(2): 14.0 dB */
+ },
+ {
+ .mode = 3, /* 6.70k */
+ .threshold = 32, /* THR_MX_Dn(4): 16.0 dB */
+ .hysteresis = 4, /* THR_MX_Up(3): 18.0 dB */
+ },
+ {
+ .mode = 5, /* 7.95k */
+ /* this is the last mode, so no threshold */
+ },
+ };
+
+ memcpy(&bts->mr_full.ms_mode[0], &amr_fr_ms_bts_mode[0], sizeof(amr_fr_ms_bts_mode));
+ memcpy(&bts->mr_full.bts_mode[0], &amr_fr_ms_bts_mode[0], sizeof(amr_fr_ms_bts_mode));
+ bts->mr_full.num_modes = ARRAY_SIZE(amr_fr_ms_bts_mode);
+
+ memcpy(&bts->mr_half.ms_mode[0], &amr_hr_ms_bts_mode[0], sizeof(amr_hr_ms_bts_mode));
+ memcpy(&bts->mr_half.bts_mode[0], &amr_hr_ms_bts_mode[0], sizeof(amr_hr_ms_bts_mode));
+ bts->mr_half.num_modes = ARRAY_SIZE(amr_hr_ms_bts_mode);
+
+ bts->use_osmux = OSMUX_USAGE_OFF;
+
+ bts_cbch_init(bts);
+ bts_etws_init(bts);
+
+ bts_setup_ramp_init_bts(bts);
+ acc_mgr_init(&bts->acc_mgr, bts);
+ acc_ramp_init(&bts->acc_ramp, bts);
+
+ /* Default RxQual threshold for ACCH repetition/overpower */
+ bts->rep_acch_cap.rxqual = 4;
+ bts->top_acch_cap.rxqual = 4;
+
+ /* Permit ACCH overpower only for speech channels using AMR */
+ bts->top_acch_chan_mode = TOP_ACCH_CHAN_MODE_SPEECH_V3;
+
+ /* MS Power Control parameters (defaults) */
+ power_ctrl_params_def_reset(&bts->ms_power_ctrl, GSM_PWR_CTRL_DIR_UL);
+
+ /* BS Power Control parameters (defaults) */
+ power_ctrl_params_def_reset(&bts->bs_power_ctrl, GSM_PWR_CTRL_DIR_DL);
+
+ /* Interference Measurement Parameters (defaults) */
+ bts->interf_meas_params_cfg = interf_meas_params_def;
+
+ bts->rach_max_delay = 63;
+ bts->rach_expiry_timeout = 32;
+
+ /* SRVCC is enabled by default */
+ bts->srvcc_fast_return_allowed = true;
+
+ return bts;
+}
+
+/* Validate BTS configuration (ARFCN settings and physical channel configuration) */
+__attribute__((weak)) int gsm_bts_check_cfg(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ if (!bts->model)
+ return -EFAULT;
+
+ switch (bts->band) {
+ case GSM_BAND_1800:
+ if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) {
+ LOGP(DNM, LOGL_ERROR, "(bts=%u) GSM1800 channel (%u) must be between 512-885.\n",
+ bts->nr, bts->c0->arfcn);
+ return -EINVAL;
+ }
+ break;
+ case GSM_BAND_1900:
+ if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) {
+ LOGP(DNM, LOGL_ERROR, "(bts=%u) GSM1900 channel (%u) must be between 512-810.\n",
+ bts->nr, bts->c0->arfcn);
+ return -EINVAL;
+ }
+ break;
+ case GSM_BAND_900:
+ if ((bts->c0->arfcn > 124 && bts->c0->arfcn < 955) ||
+ bts->c0->arfcn > 1023) {
+ LOGP(DNM, LOGL_ERROR, "(bts=%u) GSM900 channel (%u) must be between 0-124, 955-1023.\n",
+ bts->nr, bts->c0->arfcn);
+ return -EINVAL;
+ }
+ break;
+ case GSM_BAND_850:
+ if (bts->c0->arfcn < 128 || bts->c0->arfcn > 251) {
+ LOGP(DNM, LOGL_ERROR, "(bts=%u) GSM850 channel (%u) must be between 128-251.\n",
+ bts->nr, bts->c0->arfcn);
+ return -EINVAL;
+ }
+ break;
+ default:
+ LOGP(DNM, LOGL_ERROR, "(bts=%u) Unsupported frequency band.\n", bts->nr);
+ return -EINVAL;
+ }
+
+ if (bts->features_known) {
+ if (!bts_gprs_mode_is_compat(bts, bts->gprs.mode)) {
+ LOGP(DNM, LOGL_ERROR, "(bts=%u) GPRS mode set to '%s', but BTS does not support it\n", bts->nr,
+ bts_gprs_mode_name(bts->gprs.mode));
+ return -EINVAL;
+ }
+ if (bts->use_osmux == OSMUX_USAGE_ONLY &&
+ !osmo_bts_has_feature(&bts->features, BTS_FEAT_OSMUX)) {
+ LOGP(DNM, LOGL_ERROR,
+ "(bts=%u) osmux use set to 'only', but BTS does not support Osmux\n",
+ bts->nr);
+ return -EINVAL;
+ }
+ }
+
+ /* Verify the physical channel mapping */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (!trx_has_valid_pchan_config(trx)) {
+ LOGP(DNM, LOGL_ERROR, "TRX %u has invalid timeslot "
+ "configuration\n", trx->nr);
+ return -EINVAL;
+ }
+ }
+
+ if (!gsm_bts_check_ny1(bts))
+ return -EINVAL;
+
+ return 0;
+}
+
+static char ts2str[255];
+
+char *gsm_bts_name(const struct gsm_bts *bts)
+{
+ if (!bts)
+ snprintf(ts2str, sizeof(ts2str), "(bts=NULL)");
+ else
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d)", bts->nr);
+
+ return ts2str;
+}
+
+bool gsm_bts_matches_lai(const struct gsm_bts *bts, const struct osmo_location_area_id *lai)
+{
+ return osmo_plmn_cmp(&lai->plmn, &bts->network->plmn) == 0
+ && lai->lac == bts->location_area_code;
+}
+
+bool gsm_bts_matches_cell_id(const struct gsm_bts *bts, const struct gsm0808_cell_id *cell_id)
+{
+ const union gsm0808_cell_id_u *id = &cell_id->id;
+ if (!bts || !cell_id)
+ return false;
+
+ switch (cell_id->id_discr) {
+ case CELL_IDENT_WHOLE_GLOBAL:
+ return gsm_bts_matches_lai(bts, &id->global.lai)
+ && id->global.cell_identity == bts->cell_identity;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ return gsm_bts_matches_lai(bts, &id->global_ps.rai.lac)
+ && id->global_ps.rai.rac == bts->gprs.rac
+ && id->global_ps.cell_identity == bts->cell_identity;
+ case CELL_IDENT_LAC_AND_CI:
+ return id->lac_and_ci.lac == bts->location_area_code
+ && id->lac_and_ci.ci == bts->cell_identity;
+ case CELL_IDENT_CI:
+ return id->ci == bts->cell_identity;
+ case CELL_IDENT_NO_CELL:
+ return false;
+ case CELL_IDENT_LAI_AND_LAC:
+ return gsm_bts_matches_lai(bts, &id->lai_and_lac);
+ case CELL_IDENT_LAC:
+ return id->lac == bts->location_area_code;
+ case CELL_IDENT_BSS:
+ return true;
+ case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+ case CELL_IDENT_UTRAN_RNC:
+ case CELL_IDENT_UTRAN_LAC_RNC:
+ case CELL_IDENT_SAI:
+ return false;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+/* Return a LAC+CI cell identity for the given BTS.
+ * (For matching a BTS within the local BSS, the PLMN code is not important.) */
+void gsm_bts_cell_id(struct gsm0808_cell_id *cell_id, const struct gsm_bts *bts)
+{
+ *cell_id = (struct gsm0808_cell_id){
+ .id_discr = CELL_IDENT_LAC_AND_CI,
+ .id.lac_and_ci = {
+ .lac = bts->location_area_code,
+ .ci = bts->cell_identity,
+ },
+ };
+}
+
+/* Same as gsm_bts_cell_id(), but return in a single-entry gsm0808_cell_id_list2. Useful for e.g.
+ * gsm0808_cell_id_list_add() and gsm0808_cell_id_lists_same(). */
+void gsm_bts_cell_id_list(struct gsm0808_cell_id_list2 *cell_id_list, const struct gsm_bts *bts)
+{
+ struct gsm0808_cell_id cell_id;
+ struct gsm0808_cell_id_list2 add;
+ int rc;
+ gsm_bts_cell_id(&cell_id, bts);
+ gsm0808_cell_id_to_list(&add, &cell_id);
+ /* Since the target list is empty, this should always succeed. */
+ (*cell_id_list) = (struct gsm0808_cell_id_list2){};
+ rc = gsm0808_cell_id_list_add(cell_id_list, &add);
+ OSMO_ASSERT(rc > 0);
+}
+
+/* return the gsm_lchan for the CBCH (if it exists at all) */
+struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx = bts->c0;
+
+ /* According to 3GPP TS 45.002, table 3, CBCH can be allocated
+ * either on C0/TS0 (CCCH+SDCCH4) or on C0..n/TS0..3 (SDCCH/8). */
+ if (trx->ts[0].pchan_from_config == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ return &trx->ts[0].lchan[2]; /* C0/TS0 */
+
+ llist_for_each_entry(trx, &bts->trx_list, list) { /* C0..n */
+ unsigned int tn;
+ for (tn = 0; tn < 4; tn++) { /* TS0..3 */
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ if (ts->pchan_from_config == GSM_PCHAN_SDCCH8_SACCH8C_CBCH)
+ return &ts->lchan[2];
+ }
+ }
+
+ return NULL;
+}
+
+int gsm_set_bts_model(struct gsm_bts *bts, struct gsm_bts_model *model)
+{
+ bts->model = model;
+
+ /* Copy hardcoded feature list from BTS model, unless the BTS supports
+ * reporting features at runtime (as of writing nanobts, OsmoBTS). */
+ if (!model || model->features_get_reported) {
+ memset(bts->_features_data, 0, sizeof(bts->_features_data));
+ bts->features_known = false;
+ } else {
+ memcpy(bts->_features_data, bts->model->_features_data, sizeof(bts->_features_data));
+ bts->features_known = true;
+ }
+
+ return 0;
+}
+
+int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type)
+{
+ struct gsm_bts_model *model;
+
+ if (bts->type != GSM_BTS_TYPE_UNKNOWN && type != bts->type)
+ return -EBUSY;
+
+ model = bts_model_find(type);
+ if (!model)
+ return -EINVAL;
+
+ bts->type = type;
+ gsm_set_bts_model(bts, model);
+
+ if (model->start && !model->started) {
+ int ret = model->start(bts->network);
+ if (ret < 0)
+ return ret;
+
+ model->started = true;
+ }
+
+ if (model->bts_init) {
+ int rc = model->bts_init(bts);
+ if (rc < 0)
+ return rc;
+ }
+
+ /* handle those TRX which are already allocated at the time we set the type */
+ if (model->trx_init) {
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ model->trx_init(trx);
+ }
+
+ switch (bts->type) {
+ case GSM_BTS_TYPE_OSMOBTS:
+ case GSM_BTS_TYPE_NANOBTS:
+ /* Set the default OML Stream ID to 0xff */
+ bts->oml_tei = 0xff;
+ bts->c0->nominal_power = 23;
+ break;
+ case GSM_BTS_TYPE_RBS2000:
+ INIT_LLIST_HEAD(&bts->rbs2000.is.conn_groups);
+ INIT_LLIST_HEAD(&bts->rbs2000.con.conn_groups);
+ break;
+ case GSM_BTS_TYPE_BS11:
+ case GSM_BTS_TYPE_UNKNOWN:
+ case GSM_BTS_TYPE_NOKIA_SITE:
+ /* Set default BTS reset timer */
+ bts->nokia.bts_reset_timer_cnf = 15;
+ case _NUM_GSM_BTS_TYPE:
+ break;
+ }
+
+ /* Enable dynamic Uplink power control by default (if supported) */
+ if (model->power_ctrl_enc_rsl_params != NULL)
+ bts->ms_power_ctrl.mode = GSM_PWR_CTRL_MODE_DYN_BTS;
+
+ return 0;
+}
+
+int bts_gprs_mode_is_compat(struct gsm_bts *bts, enum bts_gprs_mode mode)
+{
+ if (mode != BTS_GPRS_NONE &&
+ !osmo_bts_has_feature(&bts->features, BTS_FEAT_GPRS)) {
+ return 0;
+ }
+ if (mode == BTS_GPRS_EGPRS &&
+ !osmo_bts_has_feature(&bts->features, BTS_FEAT_EGPRS)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num)
+{
+ struct gsm_bts_trx *trx;
+
+ if (num >= bts->num_trx)
+ return NULL;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->nr == num)
+ return trx;
+ }
+
+ return NULL;
+}
+
+void bts_store_uptime(struct gsm_bts *bts)
+{
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_UPTIME_SECONDS),
+ bts->oml_link ? bts_updowntime(bts) : 0);
+}
+
+void bts_store_lchan_durations(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ int i, j;
+ struct timespec now, elapsed;
+ uint64_t elapsed_ms;
+ uint64_t elapsed_tch_ms = 0;
+ uint64_t elapsed_sdcch_ms = 0;
+
+ /* Ignore BTS that are not in operation. */
+ if (!trx_is_usable(bts->c0))
+ return;
+
+ /* Grab storage time to be used for all lchans. */
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+
+ /* Iterate over all lchans. */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ for (j = 0; j < ARRAY_SIZE(ts->lchan); j++) {
+ struct gsm_lchan *lchan = &ts->lchan[j];
+
+ /* Ignore lchans whose activation timestamps are not yet set. */
+ if (lchan->active_stored.tv_sec == 0 && lchan->active_stored.tv_nsec == 0)
+ continue;
+
+ /* Calculate elapsed time since last storage. */
+ timespecsub(&now, &lchan->active_stored, &elapsed);
+ elapsed_ms = elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000;
+
+ /* Assign elapsed time to appropriate bucket. */
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_H:
+ case GSM_LCHAN_TCH_F:
+ elapsed_tch_ms += elapsed_ms;
+ break;
+ case GSM_LCHAN_SDCCH:
+ elapsed_sdcch_ms += elapsed_ms;
+ break;
+ default:
+ continue;
+ }
+
+ /* Update storage time. */
+ lchan->active_stored = now;
+ }
+ }
+ }
+
+ /* Export to rate counters. */
+ rate_ctr_add(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_TCH_ACTIVE_MILLISECONDS_TOTAL), elapsed_tch_ms);
+ rate_ctr_add(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_SDCCH_ACTIVE_MILLISECONDS_TOTAL), elapsed_sdcch_ms);
+}
+
+unsigned long long bts_updowntime(const struct gsm_bts *bts)
+{
+ struct timespec tp;
+
+ if (!bts->updowntime)
+ return 0;
+
+ if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp) != 0) {
+ LOGP(DNM, LOGL_ERROR, "BTS %u uptime/downtime computation failure: %s\n", bts->nr, strerror(errno));
+ return 0;
+ }
+
+ /* monotonic clock helps to ensure that the conversion is valid */
+ return difftime(tp.tv_sec, bts->updowntime);
+}
+
+char *get_model_oml_status(const struct gsm_bts *bts)
+{
+ if (bts->model->oml_status)
+ return bts->model->oml_status(bts);
+
+ return "unknown";
+}
+
+/* reset the state of all MO in the BTS */
+void gsm_bts_mo_reset(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ unsigned int i;
+
+ gsm_abis_mo_reset(&bts->mo);
+ gsm_abis_mo_reset(&bts->gprs.cell.mo);
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ gsm_abis_mo_reset(&trx->mo);
+ gsm_abis_mo_reset(&trx->bb_transc.mo);
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ gsm_abis_mo_reset(&ts->mo);
+ }
+ }
+}
+
+/* Assume there are only 256 possible bts */
+osmo_static_assert(sizeof(((struct gsm_bts *) 0)->nr) == 1, _bts_nr_is_256);
+static void depends_calc_index_bit(int bts_nr, int *idx, int *bit)
+{
+ *idx = bts_nr / (8 * 4);
+ *bit = bts_nr % (8 * 4);
+}
+
+void bts_depend_mark(struct gsm_bts *bts, int dep)
+{
+ int idx, bit;
+ depends_calc_index_bit(dep, &idx, &bit);
+
+ bts->depends_on[idx] |= 1U << bit;
+}
+
+void bts_depend_clear(struct gsm_bts *bts, int dep)
+{
+ int idx, bit;
+ depends_calc_index_bit(dep, &idx, &bit);
+
+ bts->depends_on[idx] &= ~(1U << bit);
+}
+
+int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other)
+{
+ int idx, bit;
+ depends_calc_index_bit(other->nr, &idx, &bit);
+
+ /* Check if there is a depends bit */
+ return (base->depends_on[idx] & (1U << bit)) > 0;
+}
+
+static bool bts_is_online(const struct gsm_bts *bts)
+{
+ /* TODO: support E1 BTS too */
+ if (!is_ipa_abisip_bts(bts))
+ return true;
+
+ if (!bts->oml_link)
+ return false;
+
+ return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED;
+}
+
+int bts_depend_check(struct gsm_bts *bts)
+{
+ struct gsm_bts *other_bts;
+
+ llist_for_each_entry(other_bts, &bts->network->bts_list, list) {
+ if (!bts_depend_is_depedency(bts, other_bts))
+ continue;
+ if (bts_is_online(other_bts))
+ continue;
+ return 0;
+ }
+ return 1;
+}
+
+/* get the radio link timeout (based on SACCH decode errors, according
+ * to algorithm specified in TS 05.08 section 5.2. A value of -1
+ * indicates we should use an infinitely long timeout, which only works
+ * with OsmoBTS as the BTS implementation */
+int gsm_bts_get_radio_link_timeout(const struct gsm_bts *bts)
+{
+ const struct gsm48_cell_options *cell_options = &bts->si_common.cell_options;
+
+ if (bts->infinite_radio_link_timeout)
+ return -1;
+ else {
+ /* Encoding as per Table 10.5.21 of TS 04.08 */
+ return (cell_options->radio_link_timeout + 1) << 2;
+ }
+}
+
+/* set the radio link timeout (based on SACCH decode errors, according
+ * to algorithm specified in TS 05.08 Section 5.2. A value of -1
+ * indicates we should use an infinitely long timeout, which only works
+ * with OsmoBTS as the BTS implementation */
+void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value)
+{
+ struct gsm48_cell_options *cell_options = &bts->si_common.cell_options;
+
+ if (value < 0)
+ bts->infinite_radio_link_timeout = true;
+ else {
+ bts->infinite_radio_link_timeout = false;
+ /* Encoding as per Table 10.5.21 of TS 04.08 */
+ if (value < 4)
+ value = 4;
+ if (value > 64)
+ value = 64;
+ cell_options->radio_link_timeout = (value >> 2) - 1;
+ }
+}
+
+void gsm_bts_all_ts_dispatch(struct gsm_bts *bts, uint32_t ts_ev, void *data)
+{
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ gsm_trx_all_ts_dispatch(trx, ts_ev, data);
+}
+
+/* set all system information types for a BTS */
+int gsm_bts_set_system_infos(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ /* Generate a new ID */
+ bts->bcch_change_mark += 1;
+ bts->bcch_change_mark %= 0x7;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ int rc;
+
+ rc = gsm_bts_trx_set_system_infos(trx);
+ if (rc != 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+/* Send the given C0 power reduction value to the BTS */
+int gsm_bts_send_c0_power_red(const struct gsm_bts *bts, const uint8_t red)
+{
+ if (!bts_is_online(bts))
+ return -ENOTCONN;
+ if (!osmo_bts_has_feature(&bts->features, BTS_FEAT_BCCH_POWER_RED))
+ return -ENOTSUP;
+ if (bts->model->power_ctrl_send_c0_power_red == NULL)
+ return -ENOTSUP;
+ return bts->model->power_ctrl_send_c0_power_red(bts, red);
+}
+
+/* Send the given C0 power reduction value to the BTS and update the internal state */
+int gsm_bts_set_c0_power_red(struct gsm_bts *bts, const uint8_t red)
+{
+ struct gsm_bts_trx *c0 = bts->c0;
+ unsigned int tn;
+ int rc;
+
+ rc = gsm_bts_send_c0_power_red(bts, red);
+ if (rc != 0)
+ return rc;
+
+ LOG_BTS(bts, DRSL, LOGL_NOTICE, "%sabling BCCH carrier power reduction "
+ "operation mode (maximum %u dB)\n", red ? "En" : "Dis", red);
+
+ /* Timeslot 0 is always transmitting BCCH/CCCH */
+ c0->ts[0].c0_max_power_red_db = 0;
+
+ for (tn = 1; tn < ARRAY_SIZE(c0->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &c0->ts[tn];
+ struct gsm_bts_trx_ts *prev = ts - 1;
+
+ switch (ts->pchan_is) {
+ /* Not allowed on CCCH/BCCH */
+ case GSM_PCHAN_CCCH:
+ /* Preceding timeslot shall not exceed 2 dB */
+ if (prev->c0_max_power_red_db > 0)
+ prev->c0_max_power_red_db = 2;
+ /* fall-through */
+ /* Not recommended on SDCCH/8 */
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ ts->c0_max_power_red_db = 0;
+ break;
+ default:
+ ts->c0_max_power_red_db = red;
+ break;
+ }
+ }
+
+ /* Timeslot 7 is always preceding BCCH/CCCH */
+ if (c0->ts[7].c0_max_power_red_db > 0)
+ c0->ts[7].c0_max_power_red_db = 2;
+
+ bts->c0_max_power_red_db = red;
+
+ return 0;
+}
+
+void gsm_bts_stats_reset(struct gsm_bts *bts)
+{
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_USED), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_TOTAL), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_USED), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_TOTAL), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_H_USED), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_H_TOTAL), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_USED), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_TOTAL), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_PDCH_USED), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_PDCH_TOTAL), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_USED), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_TOTAL), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_CBCH_USED), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_CBCH_TOTAL), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_OSMO_DYN_USED), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_OSMO_DYN_TOTAL), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_T3113), 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_REQ_QUEUE_LENGTH), paging_pending_requests_nr(bts));
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_AVAILABLE_SLOTS), bts->paging.available_slots);
+}
+
+const struct rate_ctr_desc bts_ctr_description[] = {
+ [BTS_CTR_CHREQ_TOTAL] = \
+ { "chreq:total",
+ "Received channel requests" },
+ [BTS_CTR_CHREQ_ATTEMPTED_EMERG] = \
+ { "chreq:attempted_emerg",
+ "Received channel requests EMERG" },
+ [BTS_CTR_CHREQ_ATTEMPTED_CALL] = \
+ { "chreq:attempted_call",
+ "Received channel requests CALL" },
+ [BTS_CTR_CHREQ_ATTEMPTED_LOCATION_UPD] = \
+ { "chreq:attempted_location_upd",
+ "Received channel requests LOCATION_UPD" },
+ [BTS_CTR_CHREQ_ATTEMPTED_PAG] = \
+ { "chreq:attempted_pag",
+ "Received channel requests PAG" },
+ [BTS_CTR_CHREQ_ATTEMPTED_PDCH] = \
+ { "chreq:attempted_pdch",
+ "Received channel requests PDCH" },
+ [BTS_CTR_CHREQ_ATTEMPTED_OTHER] = \
+ { "chreq:attempted_other",
+ "Received channel requests OTHER" },
+ [BTS_CTR_CHREQ_ATTEMPTED_UNKNOWN] = \
+ { "chreq:attempted_unknown",
+ "Received channel requests UNKNOWN" },
+ [BTS_CTR_CHREQ_SUCCESSFUL] = \
+ { "chreq:successful",
+ "Successful channel requests (immediate assign sent)" },
+ [BTS_CTR_CHREQ_SUCCESSFUL_EMERG] = \
+ { "chreq:successful_emerg",
+ "Sent Immediate Assignment for EMERG" },
+ [BTS_CTR_CHREQ_SUCCESSFUL_CALL] = \
+ { "chreq:successful_call",
+ "Sent Immediate Assignment for CALL" },
+ [BTS_CTR_CHREQ_SUCCESSFUL_LOCATION_UPD] = \
+ { "chreq:successful_location_upd",
+ "Sent Immediate Assignment for LOCATION_UPD" },
+ [BTS_CTR_CHREQ_SUCCESSFUL_PAG] = \
+ { "chreq:successful_pag",
+ "Sent Immediate Assignment for PAG" },
+ [BTS_CTR_CHREQ_SUCCESSFUL_PDCH] = \
+ { "chreq:successful_pdch",
+ "Sent Immediate Assignment for PDCH" },
+ [BTS_CTR_CHREQ_SUCCESSFUL_OTHER] = \
+ { "chreq:successful_other",
+ "Sent Immediate Assignment for OTHER" },
+ [BTS_CTR_CHREQ_SUCCESSFUL_UNKNOWN] = \
+ { "chreq:successful_unknown",
+ "Sent Immediate Assignment for UNKNOWN" },
+ [BTS_CTR_CHREQ_NO_CHANNEL] = \
+ { "chreq:no_channel",
+ "Sent to MS no channel available" },
+ [BTS_CTR_CHREQ_MAX_DELAY_EXCEEDED] = \
+ { "chreq:max_delay_exceeded",
+ "Received channel requests with greater than permitted access delay" },
+ [BTS_CTR_CHAN_RF_FAIL] = \
+ { "chan:rf_fail",
+ "Received a RF failure indication from BTS" },
+ [BTS_CTR_CHAN_RF_FAIL_TCH] = \
+ { "chan:rf_fail_tch",
+ "Received a RF failure indication from BTS on a TCH channel" },
+ [BTS_CTR_CHAN_RF_FAIL_SDCCH] = \
+ { "chan:rf_fail_sdcch",
+ "Received a RF failure indication from BTS on an SDCCH channel" },
+ [BTS_CTR_CHAN_RLL_ERR] = \
+ { "chan:rll_err",
+ "Received a RLL failure with T200 cause from BTS" },
+ [BTS_CTR_BTS_OML_FAIL] = \
+ { "oml_fail",
+ "Received a TEI down on a OML link" },
+ [BTS_CTR_BTS_RSL_FAIL] = \
+ { "rsl_fail",
+ "Received a TEI down on a RSL link" },
+ [BTS_CTR_CODEC_AMR_F] = \
+ { "codec:amr_f",
+ "Count the usage of AMR/F codec by channel mode requested" },
+ [BTS_CTR_CODEC_AMR_H] = \
+ { "codec:amr_h",
+ "Count the usage of AMR/H codec by channel mode requested" },
+ [BTS_CTR_CODEC_EFR] = \
+ { "codec:efr",
+ "Count the usage of EFR codec by channel mode requested" },
+ [BTS_CTR_CODEC_V1_FR] = \
+ { "codec:fr",
+ "Count the usage of FR codec by channel mode requested" },
+ [BTS_CTR_CODEC_V1_HR] = \
+ { "codec:hr",
+ "Count the usage of HR codec by channel mode requested" },
+ [BTS_CTR_PAGING_ATTEMPTED] = \
+ { "paging:attempted",
+ "Paging attempts for a subscriber" },
+ [BTS_CTR_PAGING_ALREADY] = \
+ { "paging:already",
+ "Paging attempts ignored as subscriber was already being paged" },
+ [BTS_CTR_PAGING_RESPONDED] = \
+ { "paging:responded",
+ "Paging attempts with successful paging response" },
+ [BTS_CTR_PAGING_EXPIRED] = \
+ { "paging:expired",
+ "Paging Request expired because of timeout T3113" },
+ [BTS_CTR_PAGING_NO_ACTIVE_PAGING] = \
+ { "paging:no_active_paging",
+ "Paging response without an active paging request (arrived after paging expiration?)" },
+ [BTS_CTR_PAGING_MSC_FLUSH] = \
+ { "paging:msc_flush",
+ "Paging flushed due to MSC Reset BSSMAP message" },
+ [BTS_CTR_PAGING_OVERLOAD] = \
+ { "paging:overload",
+ "Paging dropped due to BSC Paging queue overload" },
+ [BTS_CTR_CHAN_ACT_TOTAL] = \
+ { "chan_act:total",
+ "Total number of Channel Activations" },
+ [BTS_CTR_CHAN_ACT_SDCCH] = \
+ { "chan_act:sdcch",
+ "Number of SDCCH Channel Activations" },
+ [BTS_CTR_CHAN_ACT_TCH] = \
+ { "chan_act:tch",
+ "Number of TCH Channel Activations" },
+ [BTS_CTR_CHAN_ACT_NACK] = \
+ { "chan_act:nack",
+ "Number of Channel Activations that the BTS NACKed" },
+ [BTS_CTR_CHAN_TCH_ACTIVE_MILLISECONDS_TOTAL] = \
+ { "chan_tch:active_milliseconds:total",
+ "Cumulative number of milliseconds of TCH channel activity" },
+ [BTS_CTR_CHAN_SDCCH_ACTIVE_MILLISECONDS_TOTAL] = \
+ { "chan_sdcch:active_milliseconds:total",
+ "Cumulative number of milliseconds of SDCCH channel activity" },
+ [BTS_CTR_CHAN_TCH_FULLY_ESTABLISHED] = \
+ { "chan_tch:fully_established",
+ "Number of TCH channels which have reached the fully established state" },
+ [BTS_CTR_CHAN_SDCCH_FULLY_ESTABLISHED] = \
+ { "chan_sdcch:fully_established",
+ "Number of SDCCH channels which have reached the fully established state" },
+ [BTS_CTR_RSL_UNKNOWN] = \
+ { "rsl:unknown",
+ "Number of unknown/unsupported RSL messages received from BTS" },
+ [BTS_CTR_RSL_IPA_NACK] = \
+ { "rsl:ipa_nack",
+ "Number of IPA (RTP/dyn-PDCH) related NACKs received from BTS" },
+ [BTS_CTR_RSL_DELETE_IND] = \
+ { "rsl:delete_ind",
+ "Number of RSL DELETE INDICATION (DL CCCH overload)" },
+ [BTS_CTR_MODE_MODIFY_NACK] = \
+ { "chan:mode_modify_nack",
+ "Number of Channel Mode Modify NACKs received from BTS" },
+
+ /* lchan/TS BORKEN state counters */
+ [BTS_CTR_LCHAN_BORKEN_FROM_UNUSED] = \
+ { "lchan_borken:from_state:unused",
+ "Transitions from lchan UNUSED state to BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_FROM_WAIT_ACTIV_ACK] = \
+ { "lchan_borken:from_state:wait_activ_ack",
+ "Transitions from lchan WAIT_ACTIV_ACK state to BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_FROM_WAIT_RF_RELEASE_ACK] = \
+ { "lchan_borken:from_state:wait_rf_release_ack",
+ "Transitions from lchan WAIT_RF_RELEASE_ACK state to BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_FROM_BORKEN] = \
+ { "lchan_borken:from_state:borken",
+ "Transitions from lchan BORKEN state to BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_FROM_WAIT_RR_CHAN_MODE_MODIFY_ACK] = \
+ { "lchan_borken:from_state:wait_rr_chan_mode_modify_ack",
+ "Transitions from lchan WAIT_RR_CHAN_MODE_MODIFY_ACK state to BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_FROM_WAIT_RSL_CHAN_MODE_MODIFY_ACK] = \
+ { "lchan_borken:from_state:wait_rsl_chan_mode_modify_ack",
+ "Transitions from lchan RSL_CHAN_MODE_MODIFY_ACK state to BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_FROM_UNKNOWN] = \
+ { "lchan_borken:from_state:unknown",
+ "Transitions from an unknown lchan state to BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_ACK] = \
+ { "lchan_borken:event:chan_activ_ack",
+ "CHAN_ACTIV_ACK received in the lchan BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_NACK] = \
+ { "lchan_borken:event:chan_activ_nack",
+ "CHAN_ACTIV_NACK received in the lchan BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_EV_RF_CHAN_REL_ACK] = \
+ { "lchan_borken:event:rf_chan_rel_ack",
+ "RF_CHAN_REL_ACK received in the lchan BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_EV_VTY] = \
+ { "lchan_borken:event:vty",
+ "VTY commands received in the lchan BORKEN state" },
+ [BTS_CTR_LCHAN_BORKEN_EV_TEARDOWN] = \
+ { "lchan_borken:event:teardown",
+ "lchan in a BORKEN state is shutting down (BTS disconnected?)" },
+ [BTS_CTR_LCHAN_BORKEN_EV_TS_ERROR] = \
+ { "lchan_borken:event:ts_error",
+ "LCHAN_EV_TS_ERROR received in a BORKEN state" },
+ [BTS_CTR_TS_BORKEN_FROM_NOT_INITIALIZED] = \
+ { "ts_borken:from_state:not_initialized",
+ "Transitions from TS NOT_INITIALIZED state to BORKEN state" },
+ [BTS_CTR_TS_BORKEN_FROM_UNUSED] = \
+ { "ts_borken:from_state:unused",
+ "Transitions from TS UNUSED state to BORKEN state" },
+ [BTS_CTR_TS_BORKEN_FROM_WAIT_PDCH_ACT] = \
+ { "ts_borken:from_state:wait_pdch_act",
+ "Transitions from TS WAIT_PDCH_ACT state to BORKEN state" },
+ [BTS_CTR_TS_BORKEN_FROM_PDCH] = \
+ { "ts_borken:from_state:pdch",
+ "Transitions from TS PDCH state to BORKEN state" },
+ [BTS_CTR_TS_BORKEN_FROM_WAIT_PDCH_DEACT] = \
+ { "ts_borken:from_state:wait_pdch_deact",
+ "Transitions from TS WAIT_PDCH_DEACT state to BORKEN state" },
+ [BTS_CTR_TS_BORKEN_FROM_IN_USE] = \
+ { "ts_borken:from_state:in_use",
+ "Transitions from TS IN_USE state to BORKEN state" },
+ [BTS_CTR_TS_BORKEN_FROM_BORKEN] = \
+ { "ts_borken:from_state:borken",
+ "Transitions from TS BORKEN state to BORKEN state" },
+ [BTS_CTR_TS_BORKEN_FROM_UNKNOWN] = \
+ { "ts_borken:from_state:unknown",
+ "Transitions from an unknown TS state to BORKEN state" },
+ [BTS_CTR_TS_BORKEN_EV_PDCH_ACT_ACK_NACK] = \
+ { "ts_borken:event:pdch_act_ack_nack",
+ "PDCH_ACT_ACK/NACK received in the TS BORKEN state" },
+ [BTS_CTR_TS_BORKEN_EV_PDCH_DEACT_ACK_NACK] = \
+ { "ts_borken:event:pdch_deact_ack_nack",
+ "PDCH_DEACT_ACK/NACK received in the TS BORKEN state" },
+ [BTS_CTR_TS_BORKEN_EV_TEARDOWN] = \
+ { "ts_borken:event:teardown",
+ "TS in a BORKEN state is shutting down (BTS disconnected?)" },
+ [BTS_CTR_ASSIGNMENT_ATTEMPTED] = \
+ { "assignment:attempted",
+ "Assignment attempts" },
+ [BTS_CTR_ASSIGNMENT_ATTEMPTED_SIGN] = \
+ { "assignment:attempted_sign",
+ "Assignment of signaling lchan attempts" },
+ [BTS_CTR_ASSIGNMENT_ATTEMPTED_SPEECH] = \
+ { "assignment:attempted_speech",
+ "Assignment of speech lchan attempts" },
+ [BTS_CTR_ASSIGNMENT_COMPLETED] = \
+ { "assignment:completed",
+ "Assignment completed" },
+ [BTS_CTR_ASSIGNMENT_COMPLETED_SIGN] = \
+ { "assignment:completed_sign",
+ "Assignment of signaling lchan completed" },
+ [BTS_CTR_ASSIGNMENT_COMPLETED_SPEECH] = \
+ { "assignment:completed_speech",
+ "Assignment if speech lchan completed" },
+ [BTS_CTR_ASSIGNMENT_STOPPED] = \
+ { "assignment:stopped",
+ "Connection ended during Assignment" },
+ [BTS_CTR_ASSIGNMENT_STOPPED_SIGN] = \
+ { "assignment:stopped_sign",
+ "Connection ended during signaling lchan Assignment" },
+ [BTS_CTR_ASSIGNMENT_STOPPED_SPEECH] = \
+ { "assignment:stopped_speech",
+ "Connection ended during speech lchan Assignment" },
+ [BTS_CTR_ASSIGNMENT_NO_CHANNEL] = \
+ { "assignment:no_channel",
+ "Failure to allocate lchan for Assignment" },
+ [BTS_CTR_ASSIGNMENT_NO_CHANNEL_SIGN] = \
+ { "assignment:no_channel_sign",
+ "Failure to allocate signaling lchan for Assignment" },
+ [BTS_CTR_ASSIGNMENT_NO_CHANNEL_SPEECH] = \
+ { "assignment:no_channel_speech",
+ "Failure to allocate speech lchan for Assignment" },
+ [BTS_CTR_ASSIGNMENT_TIMEOUT] = \
+ { "assignment:timeout",
+ "Assignment timed out" },
+ [BTS_CTR_ASSIGNMENT_TIMEOUT_SIGN] = \
+ { "assignment:timeout_sign",
+ "Assignment of signaling lchan timed out" },
+ [BTS_CTR_ASSIGNMENT_TIMEOUT_SPEECH] = \
+ { "assignment:timeout_speech",
+ "Assignment of speech lchan timed out" },
+ [BTS_CTR_ASSIGNMENT_FAILED] = \
+ { "assignment:failed",
+ "Received Assignment Failure message" },
+ [BTS_CTR_ASSIGNMENT_FAILED_SIGN] = \
+ { "assignment:failed_sign",
+ "Received Assignment Failure message on signaling lchan" },
+ [BTS_CTR_ASSIGNMENT_FAILED_SPEECH] = \
+ { "assignment:failed_speech",
+ "Received Assignment Failure message on speech lchan" },
+ [BTS_CTR_ASSIGNMENT_ERROR] = \
+ { "assignment:error",
+ "Assignment failed for other reason" },
+ [BTS_CTR_ASSIGNMENT_ERROR_SIGN] = \
+ { "assignment:error_sign",
+ "Assignment of signaling lchan failed for other reason" },
+ [BTS_CTR_ASSIGNMENT_ERROR_SPEECH] = \
+ { "assignment:error_speech",
+ "Assignment of speech lchan failed for other reason" },
+ [BTS_CTR_LOCATION_UPDATE_ACCEPT] = \
+ { "location_update:accept",
+ "Location Update Accept" },
+ [BTS_CTR_LOCATION_UPDATE_REJECT] = \
+ { "location_update:reject",
+ "Location Update Reject" },
+ [BTS_CTR_LOCATION_UPDATE_DETACH] = \
+ { "location_update:detach",
+ "Location Update Detach" },
+ [BTS_CTR_LOCATION_UPDATE_UNKNOWN] = \
+ { "location_update:unknown",
+ "Location Update UNKNOWN" },
+ [BTS_CTR_HANDOVER_ATTEMPTED] = \
+ { "handover:attempted",
+ "Intra-BSC handover attempts" },
+ [BTS_CTR_HANDOVER_COMPLETED] = \
+ { "handover:completed",
+ "Intra-BSC handover completed" },
+ [BTS_CTR_HANDOVER_STOPPED] = \
+ { "handover:stopped",
+ "Connection ended during HO" },
+ [BTS_CTR_HANDOVER_NO_CHANNEL] = \
+ { "handover:no_channel",
+ "Failure to allocate lchan for HO" },
+ [BTS_CTR_HANDOVER_TIMEOUT] = \
+ { "handover:timeout",
+ "Handover timed out" },
+ [BTS_CTR_HANDOVER_FAILED] = \
+ { "handover:failed",
+ "Received Handover Fail messages" },
+ [BTS_CTR_HANDOVER_ERROR] = \
+ { "handover:error",
+ "Re-assignment failed for other reason" },
+
+ [BTS_CTR_INTRA_CELL_HO_ATTEMPTED] = \
+ { "intra_cell_ho:attempted",
+ "Intra-Cell handover attempts" },
+ [BTS_CTR_INTRA_CELL_HO_COMPLETED] = \
+ { "intra_cell_ho:completed",
+ "Intra-Cell handover completed" },
+ [BTS_CTR_INTRA_CELL_HO_STOPPED] = \
+ { "intra_cell_ho:stopped",
+ "Connection ended during HO" },
+ [BTS_CTR_INTRA_CELL_HO_NO_CHANNEL] = \
+ { "intra_cell_ho:no_channel",
+ "Failure to allocate lchan for HO" },
+ [BTS_CTR_INTRA_CELL_HO_TIMEOUT] = \
+ { "intra_cell_ho:timeout",
+ "Handover timed out" },
+ [BTS_CTR_INTRA_CELL_HO_FAILED] = \
+ { "intra_cell_ho:failed",
+ "Received Handover Fail messages" },
+ [BTS_CTR_INTRA_CELL_HO_ERROR] = \
+ { "intra_cell_ho:error",
+ "Re-assignment failed for other reason" },
+
+ [BTS_CTR_INTRA_BSC_HO_ATTEMPTED] = \
+ { "intra_bsc_ho:attempted",
+ "Intra-BSC inter-cell handover attempts" },
+ [BTS_CTR_INTRA_BSC_HO_COMPLETED] = \
+ { "intra_bsc_ho:completed",
+ "Intra-BSC inter-cell handover completed" },
+ [BTS_CTR_INTRA_BSC_HO_STOPPED] = \
+ { "intra_bsc_ho:stopped",
+ "Connection ended during HO" },
+ [BTS_CTR_INTRA_BSC_HO_NO_CHANNEL] = \
+ { "intra_bsc_ho:no_channel",
+ "Failure to allocate lchan for HO" },
+ [BTS_CTR_INTRA_BSC_HO_TIMEOUT] = \
+ { "intra_bsc_ho:timeout",
+ "Handover timed out" },
+ [BTS_CTR_INTRA_BSC_HO_FAILED] = \
+ { "intra_bsc_ho:failed",
+ "Received Handover Fail messages" },
+ [BTS_CTR_INTRA_BSC_HO_ERROR] = \
+ { "intra_bsc_ho:error",
+ "Intra-BSC inter-cell HO failed for other reason" },
+
+ [BTS_CTR_INCOMING_INTRA_BSC_HO_ATTEMPTED] = \
+ { "incoming_intra_bsc_ho:attempted",
+ "Incoming intra-BSC inter-cell handover attempts" },
+ [BTS_CTR_INCOMING_INTRA_BSC_HO_COMPLETED] = \
+ { "incoming_intra_bsc_ho:completed",
+ "Incoming intra-BSC inter-cell handover completed" },
+ [BTS_CTR_INCOMING_INTRA_BSC_HO_STOPPED] = \
+ { "incoming_intra_bsc_ho:stopped",
+ "Connection ended during HO" },
+ [BTS_CTR_INCOMING_INTRA_BSC_HO_NO_CHANNEL] = \
+ { "incoming_intra_bsc_ho:no_channel",
+ "Failure to allocate lchan for HO" },
+ [BTS_CTR_INCOMING_INTRA_BSC_HO_TIMEOUT] = \
+ { "incoming_intra_bsc_ho:timeout",
+ "Handover timed out" },
+ [BTS_CTR_INCOMING_INTRA_BSC_HO_FAILED] = \
+ { "incoming_intra_bsc_ho:failed",
+ "Received Handover Fail messages" },
+ [BTS_CTR_INCOMING_INTRA_BSC_HO_ERROR] = \
+ { "incoming_intra_bsc_ho:error",
+ "Incoming intra-BSC inter-cell HO failed for other reason" },
+
+ [BTS_CTR_INTER_BSC_HO_OUT_ATTEMPTED] = \
+ { "interbsc_ho_out:attempted",
+ "Attempts to handover to remote BSS" },
+ [BTS_CTR_INTER_BSC_HO_OUT_COMPLETED] = \
+ { "interbsc_ho_out:completed",
+ "Handover to remote BSS completed" },
+ [BTS_CTR_INTER_BSC_HO_OUT_STOPPED] = \
+ { "interbsc_ho_out:stopped",
+ "Connection ended during HO" },
+ [BTS_CTR_INTER_BSC_HO_OUT_TIMEOUT] = \
+ { "interbsc_ho_out:timeout",
+ "Handover timed out" },
+ [BTS_CTR_INTER_BSC_HO_OUT_FAILED] = \
+ { "interbsc_ho_out:failed",
+ "Received Handover Fail message" },
+ [BTS_CTR_INTER_BSC_HO_OUT_ERROR] = \
+ { "interbsc_ho_out:error",
+ "Handover to remote BSS failed for other reason" },
+ [BTS_CTR_INTER_BSC_HO_IN_ATTEMPTED] = \
+ { "interbsc_ho_in:attempted",
+ "Attempts to handover from remote BSS" },
+ [BTS_CTR_INTER_BSC_HO_IN_COMPLETED] = \
+ { "interbsc_ho_in:completed",
+ "Handover from remote BSS completed" },
+ [BTS_CTR_INTER_BSC_HO_IN_STOPPED] = \
+ { "interbsc_ho_in:stopped",
+ "Connection ended during HO" },
+ [BTS_CTR_INTER_BSC_HO_IN_NO_CHANNEL] = \
+ { "interbsc_ho_in:no_channel",
+ "Failure to allocate lchan for HO" },
+ [BTS_CTR_INTER_BSC_HO_IN_TIMEOUT] = \
+ { "interbsc_ho_in:timeout",
+ "Handover from remote BSS timed out" },
+ [BTS_CTR_INTER_BSC_HO_IN_FAILED] = \
+ { "interbsc_ho_in:failed",
+ "Received Handover Fail message" },
+ [BTS_CTR_INTER_BSC_HO_IN_ERROR] = \
+ { "interbsc_ho_in:error",
+ "Handover from remote BSS failed for other reason" },
+
+ [BTS_CTR_SRVCC_ATTEMPTED] = \
+ { "srvcc:attempted",
+ "Intra-BSC handover attempts" },
+ [BTS_CTR_SRVCC_COMPLETED] = \
+ { "srvcc:completed",
+ "Intra-BSC handover completed" },
+ [BTS_CTR_SRVCC_STOPPED] = \
+ { "srvcc:stopped",
+ "Connection ended during HO" },
+ [BTS_CTR_SRVCC_NO_CHANNEL] = \
+ { "srvcc:no_channel",
+ "Failure to allocate lchan for HO" },
+ [BTS_CTR_SRVCC_TIMEOUT] = \
+ { "srvcc:timeout",
+ "Handover timed out" },
+ [BTS_CTR_SRVCC_FAILED] = \
+ { "srvcc:failed",
+ "Received Handover Fail messages" },
+ [BTS_CTR_SRVCC_ERROR] = \
+ { "srvcc:error",
+ "Re-assignment failed for other reason" },
+ [BTS_CTR_ALL_ALLOCATED_SDCCH] = \
+ { "all_allocated:sdcch",
+ "Cumulative counter of seconds where all SDCCH channels were allocated" },
+ [BTS_CTR_ALL_ALLOCATED_STATIC_SDCCH] = \
+ { "all_allocated:static_sdcch",
+ "Cumulative counter of seconds where all non-dynamic SDCCH channels were allocated" },
+ [BTS_CTR_ALL_ALLOCATED_TCH] = \
+ { "all_allocated:tch",
+ "Cumulative counter of seconds where all TCH channels were allocated" },
+ [BTS_CTR_ALL_ALLOCATED_STATIC_TCH] = \
+ { "all_allocated:static_tch",
+ "Cumulative counter of seconds where all non-dynamic TCH channels were allocated" },
+
+ [BTS_CTR_CM_SERV_REJ] = \
+ { "cm_serv_rej", "MSC sent CM Service Reject" },
+ [BTS_CTR_CM_SERV_REJ_IMSI_UNKNOWN_IN_HLR] = \
+ { "cm_serv_rej:imsi_unknown_in_hlr",
+ "MSC sent CM Service Reject with cause IMSI_UNKNOWN_IN_HLR" },
+ [BTS_CTR_CM_SERV_REJ_ILLEGAL_MS] = \
+ { "cm_serv_rej:illegal_ms",
+ "MSC sent CM Service Reject with cause ILLEGAL_MS" },
+ [BTS_CTR_CM_SERV_REJ_IMSI_UNKNOWN_IN_VLR] = \
+ { "cm_serv_rej:imsi_unknown_in_vlr",
+ "MSC sent CM Service Reject with cause IMSI_UNKNOWN_IN_VLR" },
+ [BTS_CTR_CM_SERV_REJ_IMEI_NOT_ACCEPTED] = \
+ { "cm_serv_rej:imei_not_accepted",
+ "MSC sent CM Service Reject with cause IMEI_NOT_ACCEPTED" },
+ [BTS_CTR_CM_SERV_REJ_ILLEGAL_ME] = \
+ { "cm_serv_rej:illegal_me",
+ "MSC sent CM Service Reject with cause ILLEGAL_ME" },
+ [BTS_CTR_CM_SERV_REJ_PLMN_NOT_ALLOWED] = \
+ { "cm_serv_rej:plmn_not_allowed",
+ "MSC sent CM Service Reject with cause PLMN_NOT_ALLOWED" },
+ [BTS_CTR_CM_SERV_REJ_LOC_NOT_ALLOWED] = \
+ { "cm_serv_rej:loc_not_allowed",
+ "MSC sent CM Service Reject with cause LOC_NOT_ALLOWED" },
+ [BTS_CTR_CM_SERV_REJ_ROAMING_NOT_ALLOWED] = \
+ { "cm_serv_rej:roaming_not_allowed",
+ "MSC sent CM Service Reject with cause ROAMING_NOT_ALLOWED" },
+ [BTS_CTR_CM_SERV_REJ_NETWORK_FAILURE] = \
+ { "cm_serv_rej:network_failure",
+ "MSC sent CM Service Reject with cause NETWORK_FAILURE" },
+ [BTS_CTR_CM_SERV_REJ_SYNCH_FAILURE] = \
+ { "cm_serv_rej:synch_failure",
+ "MSC sent CM Service Reject with cause SYNCH_FAILURE" },
+ [BTS_CTR_CM_SERV_REJ_CONGESTION] = \
+ { "cm_serv_rej:congestion",
+ "MSC sent CM Service Reject with cause CONGESTION" },
+ [BTS_CTR_CM_SERV_REJ_SRV_OPT_NOT_SUPPORTED] = \
+ { "cm_serv_rej:srv_opt_not_supported",
+ "MSC sent CM Service Reject with cause SRV_OPT_NOT_SUPPORTED" },
+ [BTS_CTR_CM_SERV_REJ_RQD_SRV_OPT_NOT_SUPPORTED] = \
+ { "cm_serv_rej:rqd_srv_opt_not_supported",
+ "MSC sent CM Service Reject with cause RQD_SRV_OPT_NOT_SUPPORTED" },
+ [BTS_CTR_CM_SERV_REJ_SRV_OPT_TMP_OUT_OF_ORDER] = \
+ { "cm_serv_rej:srv_opt_tmp_out_of_order",
+ "MSC sent CM Service Reject with cause SRV_OPT_TMP_OUT_OF_ORDER" },
+ [BTS_CTR_CM_SERV_REJ_CALL_CAN_NOT_BE_IDENTIFIED] = \
+ { "cm_serv_rej:call_can_not_be_identified",
+ "MSC sent CM Service Reject with cause CALL_CAN_NOT_BE_IDENTIFIED" },
+ [BTS_CTR_CM_SERV_REJ_INCORRECT_MESSAGE] = \
+ { "cm_serv_rej:incorrect_message",
+ "MSC sent CM Service Reject with cause INCORRECT_MESSAGE" },
+ [BTS_CTR_CM_SERV_REJ_INVALID_MANDANTORY_INF] = \
+ { "cm_serv_rej:invalid_mandantory_inf",
+ "MSC sent CM Service Reject with cause INVALID_MANDANTORY_INF" },
+ [BTS_CTR_CM_SERV_REJ_MSG_TYPE_NOT_IMPLEMENTED] = \
+ { "cm_serv_rej:msg_type_not_implemented",
+ "MSC sent CM Service Reject with cause MSG_TYPE_NOT_IMPLEMENTED" },
+ [BTS_CTR_CM_SERV_REJ_MSG_TYPE_NOT_COMPATIBLE] = \
+ { "cm_serv_rej:msg_type_not_compatible",
+ "MSC sent CM Service Reject with cause MSG_TYPE_NOT_COMPATIBLE" },
+ [BTS_CTR_CM_SERV_REJ_INF_ELEME_NOT_IMPLEMENTED] = \
+ { "cm_serv_rej:inf_eleme_not_implemented",
+ "MSC sent CM Service Reject with cause INF_ELEME_NOT_IMPLEMENTED" },
+ [BTS_CTR_CM_SERV_REJ_CONDTIONAL_IE_ERROR] = \
+ { "cm_serv_rej:condtional_ie_error",
+ "MSC sent CM Service Reject with cause CONDTIONAL_IE_ERROR" },
+ [BTS_CTR_CM_SERV_REJ_MSG_NOT_COMPATIBLE] = \
+ { "cm_serv_rej:msg_not_compatible",
+ "MSC sent CM Service Reject with cause MSG_NOT_COMPATIBLE" },
+ [BTS_CTR_CM_SERV_REJ_PROTOCOL_ERROR] = \
+ { "cm_serv_rej:protocol_error",
+ "MSC sent CM Service Reject with cause PROTOCOL_ERROR" },
+ [BTS_CTR_CM_SERV_REJ_RETRY_IN_NEW_CELL] = \
+ { "cm_serv_rej:retry_in_new_cell",
+ "MSC sent CM Service Reject with cause 00110000..00111111, Retry upon entry in a new cell" },
+};
+
+const struct rate_ctr_group_desc bts_ctrg_desc = {
+ "bts",
+ "base transceiver station",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(bts_ctr_description),
+ bts_ctr_description,
+};
+
+const struct osmo_stat_item_desc bts_stat_desc[] = {
+ [BTS_STAT_UPTIME_SECONDS] = \
+ { "uptime:seconds",
+ "Seconds of uptime",
+ "s", 60, 0 },
+ [BTS_STAT_CHAN_LOAD_AVERAGE] = \
+ { "chanloadavg",
+ "Channel load average",
+ "%", 60, 0 },
+ [BTS_STAT_CHAN_CCCH_SDCCH4_USED] = \
+ { "chan_ccch_sdcch4:used",
+ "Number of CCCH+SDCCH4 channels used",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_CCCH_SDCCH4_TOTAL] = \
+ { "chan_ccch_sdcch4:total",
+ "Number of CCCH+SDCCH4 channels total",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_TCH_F_USED] = \
+ { "chan_tch_f:used",
+ "Number of TCH/F channels used",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_TCH_F_TOTAL] = \
+ { "chan_tch_f:total",
+ "Number of TCH/F channels total",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_TCH_H_USED] = \
+ { "chan_tch_h:used",
+ "Number of TCH/H channels used",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_TCH_H_TOTAL] = \
+ { "chan_tch_h:total",
+ "Number of TCH/H channels total",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_SDCCH8_USED] = \
+ { "chan_sdcch8:used",
+ "Number of SDCCH8 channels used",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_SDCCH8_TOTAL] = \
+ { "chan_sdcch8:total",
+ "Number of SDCCH8 channels total",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_TCH_F_PDCH_USED] = \
+ { "chan_dynamic_ipaccess:used",
+ "Number of DYNAMIC/IPACCESS channels used",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_TCH_F_PDCH_TOTAL] = \
+ { "chan_dynamic_ipaccess:total",
+ "Number of DYNAMIC/IPACCESS channels total",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_USED] = \
+ { "chan_ccch_sdcch4_cbch:used",
+ "Number of CCCH+SDCCH4+CBCH channels used",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_TOTAL] = \
+ { "chan_ccch_sdcch4_cbch:total",
+ "Number of CCCH+SDCCH4+CBCH channels total",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_SDCCH8_CBCH_USED] = \
+ { "chan_sdcch8_cbch:used",
+ "Number of SDCCH8+CBCH channels used",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_SDCCH8_CBCH_TOTAL] = \
+ { "chan_sdcch8_cbch:total",
+ "Number of SDCCH8+CBCH channels total",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_OSMO_DYN_USED] = \
+ { "chan_dynamic_osmocom:used",
+ "Number of DYNAMIC/OSMOCOM channels used",
+ "", 60, 0 },
+ [BTS_STAT_CHAN_OSMO_DYN_TOTAL] = \
+ { "chan_dynamic_osmocom:total",
+ "Number of DYNAMIC/OSMOCOM channels total",
+ "", 60, 0 },
+ [BTS_STAT_T3122] = \
+ { "T3122",
+ "T3122 IMMEDIATE ASSIGNMENT REJECT wait indicator",
+ "s", 60, GSM_T3122_DEFAULT },
+ [BTS_STAT_RACH_BUSY] = \
+ { "rach_busy",
+ "RACH slots with signal above threshold",
+ "%", 60, 0 },
+ [BTS_STAT_RACH_ACCESS] = \
+ { "rach_access",
+ "RACH slots with access bursts in them",
+ "%", 60, 0 },
+ [BTS_STAT_OML_CONNECTED] = \
+ { "oml_connected",
+ "Number of OML links connected",
+ "", 16, 0 },
+ [BTS_STAT_RSL_CONNECTED] = \
+ { "rsl_connected",
+ "Number of RSL links connected (same as num_trx:rsl_connected)",
+ "", 16, 0 },
+ [BTS_STAT_LCHAN_BORKEN] = \
+ { "lchan_borken",
+ "Number of lchans in the BORKEN state",
+ "", 16, 0 },
+ [BTS_STAT_TS_BORKEN] = \
+ { "ts_borken",
+ "Number of timeslots in the BORKEN state",
+ "", 16, 0 },
+ [BTS_STAT_NUM_TRX_RSL_CONNECTED] = \
+ { "num_trx:rsl_connected",
+ "Number of TRX in this BTS where RSL is up",
+ "" },
+ [BTS_STAT_NUM_TRX_TOTAL] = \
+ { "num_trx:total",
+ "Number of configured TRX in this BTS",
+ "" },
+ [BTS_STAT_PAGING_REQ_QUEUE_LENGTH] = \
+ { "paging:request_queue_length",
+ "Paging Request queue length",
+ "", 60, 0 },
+ [BTS_STAT_PAGING_AVAILABLE_SLOTS] = \
+ { "paging:available_slots",
+ "Available paging slots in this BTS",
+ "", 60, 0 },
+ [BTS_STAT_PAGING_T3113] = \
+ { "paging:t3113",
+ "T3113 paging timer",
+ "s", 60, 0 },
+};
+
+const struct osmo_stat_item_group_desc bts_statg_desc = {
+ .group_name_prefix = "bts",
+ .group_description = "base transceiver station",
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+ .num_items = ARRAY_SIZE(bts_stat_desc),
+ .item_desc = bts_stat_desc,
+};
+
+/* Return 'true' if and only if Ny1 satisfies network requirements */
+bool gsm_bts_check_ny1(const struct gsm_bts *bts)
+{
+ unsigned long T3105, ny1, ny1_recommended;
+ T3105 = osmo_tdef_get(bts->network->T_defs, 3105, OSMO_TDEF_MS, -1);
+ ny1 = osmo_tdef_get(bts->network->T_defs, -3105, OSMO_TDEF_CUSTOM, -1);
+ if (!(T3105 * ny1 > GSM_T3124_MAX + GSM_NY1_REQ_DELTA)) {
+ /* See comment for GSM_NY1_DEFAULT */
+ ny1_recommended = (GSM_T3124_MAX + GSM_NY1_REQ_DELTA)/T3105 + 1;
+ LOGP(DNM, LOGL_ERROR, "Value of Ny1 should be higher. "
+ "Is: %lu, lowest recommendation: %lu\n",
+ ny1, ny1_recommended);
+ return false;
+ }
+ return true;
+}
diff --git a/src/osmo-bsc/bts_ctrl.c b/src/osmo-bsc/bts_ctrl.c
new file mode 100644
index 000000000..814a17dca
--- /dev/null
+++ b/src/osmo-bsc/bts_ctrl.c
@@ -0,0 +1,1580 @@
+/*
+ * (C) 2013-2015 by Holger Hans Peter Freyther
+ * (C) 2013-2022 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <time.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmocom/bsc/ctrl.h>
+#include <osmocom/bsc/osmo_bsc_rf.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/system_information.h>
+
+#include <osmocom/gsm/sysinfo.h>
+
+static int location_equal(struct bts_location *a, struct bts_location *b)
+{
+ return ((a->tstamp == b->tstamp) && (a->valid == b->valid) && (a->lat == b->lat) &&
+ (a->lon == b->lon) && (a->height == b->height));
+}
+
+static void cleanup_locations(struct llist_head *locations)
+{
+ struct bts_location *myloc, *tmp;
+ int invalpos = 0, i = 0;
+
+ LOGP(DCTRL, LOGL_DEBUG, "Checking position list.\n");
+ llist_for_each_entry_safe(myloc, tmp, locations, list) {
+ i++;
+ if (i > 3) {
+ LOGP(DCTRL, LOGL_DEBUG, "Deleting old position.\n");
+ llist_del(&myloc->list);
+ talloc_free(myloc);
+ } else if (myloc->valid == BTS_LOC_FIX_INVALID) {
+ /* Only capture the newest of subsequent invalid positions */
+ invalpos++;
+ if (invalpos > 1) {
+ LOGP(DCTRL, LOGL_DEBUG, "Deleting subsequent invalid position.\n");
+ invalpos--;
+ i--;
+ llist_del(&myloc->list);
+ talloc_free(myloc);
+ }
+ } else {
+ invalpos = 0;
+ }
+ }
+ LOGP(DCTRL, LOGL_DEBUG, "Found %d positions.\n", i);
+}
+
+static int get_bts_loc(struct ctrl_cmd *cmd, void *data);
+
+void ctrl_generate_bts_location_state_trap(struct gsm_bts *bts, struct bsc_msc_data *msc)
+{
+ struct ctrl_cmd *cmd;
+ const char *oper, *admin, *policy;
+
+ cmd = ctrl_cmd_create(msc, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n");
+ return;
+ }
+
+ cmd->id = "0";
+ cmd->variable = talloc_asprintf(cmd, "bts.%d.location-state", bts->nr);
+
+ /* Prepare the location reply */
+ cmd->node = bts;
+ get_bts_loc(cmd, NULL);
+
+ oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts));
+ admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
+ policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
+
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ ",%s,%s,%s,%s,%s",
+ oper, admin, policy,
+ osmo_mcc_name(bts->network->plmn.mcc),
+ osmo_mnc_name(bts->network->plmn.mnc,
+ bts->network->plmn.mnc_3_digits));
+
+ osmo_bsc_send_trap(cmd, msc);
+ talloc_free(cmd);
+}
+
+void bsc_gen_location_state_trap(struct gsm_bts *bts)
+{
+ struct bsc_msc_data *msc;
+
+ llist_for_each_entry(msc, &bts->network->mscs, entry)
+ ctrl_generate_bts_location_state_trap(bts, msc);
+}
+
+CTRL_CMD_DEFINE(bts_loc, "location");
+static int get_bts_loc(struct ctrl_cmd *cmd, void *data)
+{
+ struct bts_location *curloc;
+ struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
+ if (!bts) {
+ cmd->reply = "bts not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (llist_empty(&bts->loc_list)) {
+ cmd->reply = talloc_asprintf(cmd, "0,invalid,0,0,0");
+ return CTRL_CMD_REPLY;
+ }
+
+ curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+
+ cmd->reply = talloc_asprintf(cmd, "%lu,%s,%f,%f,%f", curloc->tstamp,
+ get_value_string(bts_loc_fix_names, curloc->valid), curloc->lat, curloc->lon, curloc->height);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_loc(struct ctrl_cmd *cmd, void *data)
+{
+ char *saveptr, *lat, *lon, *height, *tstamp, *valid, *tmp;
+ struct bts_location *curloc, *lastloc;
+ int ret;
+ struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
+ if (!bts) {
+ cmd->reply = "bts not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
+
+ tstamp = strtok_r(tmp, ",", &saveptr);
+ valid = strtok_r(NULL, ",", &saveptr);
+ lat = strtok_r(NULL, ",", &saveptr);
+ lon = strtok_r(NULL, ",", &saveptr);
+ height = strtok_r(NULL, "\0", &saveptr);
+
+ /* Check if one of the strtok results was NULL. This will probably never occur since we will only see verified
+ * input in this code path */
+ if ((tstamp == NULL) || (valid == NULL) || (lat == NULL) || (lon == NULL) || (height == NULL)) {
+ talloc_free(tmp);
+ cmd->reply = "parse error";
+ return CTRL_CMD_ERROR;
+ }
+
+ curloc = talloc_zero(tall_bsc_ctx, struct bts_location);
+ if (!curloc) {
+ talloc_free(tmp);
+ goto oom;
+ }
+ INIT_LLIST_HEAD(&curloc->list);
+
+ curloc->tstamp = atol(tstamp);
+ curloc->valid = get_string_value(bts_loc_fix_names, valid);
+ curloc->lat = atof(lat);
+ curloc->lon = atof(lon);
+ curloc->height = atof(height);
+ talloc_free(tmp);
+
+ lastloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+
+ /* Add location to the end of the list */
+ llist_add(&curloc->list, &bts->loc_list);
+
+ ret = get_bts_loc(cmd, data);
+
+ if (!location_equal(curloc, lastloc))
+ bsc_gen_location_state_trap(bts);
+
+ cleanup_locations(&bts->loc_list);
+
+ return ret;
+
+oom:
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+}
+
+static int verify_bts_loc(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ char *saveptr, *latstr, *lonstr, *heightstr, *tstampstr, *validstr, *tmp;
+ time_t tstamp;
+ int valid;
+ double lat, lon, height __attribute__((unused));
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ tstampstr = strtok_r(tmp, ",", &saveptr);
+ validstr = strtok_r(NULL, ",", &saveptr);
+ latstr = strtok_r(NULL, ",", &saveptr);
+ lonstr = strtok_r(NULL, ",", &saveptr);
+ heightstr = strtok_r(NULL, "\0", &saveptr);
+
+ if ((tstampstr == NULL) || (validstr == NULL) || (latstr == NULL) ||
+ (lonstr == NULL) || (heightstr == NULL))
+ goto err;
+
+ tstamp = atol(tstampstr);
+ valid = get_string_value(bts_loc_fix_names, validstr);
+ lat = atof(latstr);
+ lon = atof(lonstr);
+ height = atof(heightstr);
+ talloc_free(tmp);
+ tmp = NULL;
+
+ if (((tstamp == 0) && (valid != BTS_LOC_FIX_INVALID)) || (lat < -90) || (lat > 90) ||
+ (lon < -180) || (lon > 180) || (valid < 0)) {
+ goto err;
+ }
+
+ return 0;
+
+err:
+ talloc_free(tmp);
+ cmd->reply = talloc_strdup(cmd, "The format is <unixtime>,(invalid|fix2d|fix3d),<lat>,<lon>,<height>");
+ return 1;
+}
+
+/* BTS related commands below */
+CTRL_CMD_DEFINE_RANGE(bts_lac, "location-area-code", struct gsm_bts, location_area_code, 0, 65535);
+CTRL_CMD_DEFINE_RANGE(bts_ci, "cell-identity", struct gsm_bts, cell_identity, 0, 65535);
+CTRL_CMD_DEFINE_RANGE(bts_bsic, "bsic", struct gsm_bts, bsic, 0, 63);
+CTRL_CMD_DEFINE_RANGE(bts_rach_max_delay, "rach-max-delay", struct gsm_bts, rach_max_delay, 1, 127);
+CTRL_CMD_DEFINE_RANGE(bts_rach_expiry_timeout, "rach-expiry-timeout", struct gsm_bts, rach_expiry_timeout, 4, 64);
+CTRL_CMD_DEFINE_RANGE(bts_ms_max_power, "ms-max-power", struct gsm_bts, ms_max_power, 0, 40);
+
+static int set_bts_apply_config(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ if (!is_ipa_abisip_bts(bts)) {
+ cmd->reply = "BTS is not IPA Abis/IP based";
+ return CTRL_CMD_ERROR;
+ }
+
+ ipaccess_drop_oml(bts, "ctrl bts.apply-configuration");
+ cmd->reply = "Tried to drop the BTS";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(bts_apply_config, "apply-configuration");
+
+static int set_bts_si(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ rc = gsm_bts_set_system_infos(bts);
+ if (rc != 0) {
+ cmd->reply = "Failed to generate SI";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "Generated new System Information";
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_WO_NOVRF(bts_si, "send-new-system-informations");
+
+static int set_bts_power_ctrl_defs(struct ctrl_cmd *cmd, void *data)
+{
+ const struct gsm_bts *bts = cmd->node;
+ const struct gsm_bts_trx *trx;
+
+ if (bts->ms_power_ctrl.mode != GSM_PWR_CTRL_MODE_DYN_BTS) {
+ cmd->reply = "BTS is not using dyn-bts mode";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (bts->model->power_ctrl_send_def_params == NULL) {
+ cmd->reply = "Not implemented for this BTS model";
+ return CTRL_CMD_ERROR;
+ }
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (bts->model->power_ctrl_send_def_params(trx) != 0) {
+ cmd->reply = "power_ctrl_send_def_params() failed";
+ return CTRL_CMD_ERROR;
+ }
+ }
+
+ cmd->reply = "Default power control parameters have been sent";
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_WO_NOVRF(bts_power_ctrl_defs, "send-power-control-defaults");
+
+static int get_bts_chan_load(struct ctrl_cmd *cmd, void *data)
+{
+ int i;
+ struct pchan_load pl;
+ struct gsm_bts *bts;
+ const char *space = "";
+
+ bts = cmd->node;
+ memset(&pl, 0, sizeof(pl));
+ bts_chan_load(&pl, bts);
+
+ cmd->reply = talloc_strdup(cmd, "");
+
+ for (i = 0; i < ARRAY_SIZE(pl.pchan); ++i) {
+ const struct load_counter *lc = &pl.pchan[i];
+
+ /* These can never have user load */
+ if (i == GSM_PCHAN_NONE)
+ continue;
+ if (i == GSM_PCHAN_CCCH)
+ continue;
+ if (i == GSM_PCHAN_PDCH)
+ continue;
+ if (i == GSM_PCHAN_UNKNOWN)
+ continue;
+
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "%s%s,%u,%u",
+ space, gsm_pchan_name(i), lc->used, lc->total);
+ if (!cmd->reply)
+ goto error;
+ space = " ";
+ }
+
+ return CTRL_CMD_REPLY;
+
+error:
+ cmd->reply = "Memory allocation failure";
+ return CTRL_CMD_ERROR;
+}
+
+CTRL_CMD_DEFINE_RO(bts_chan_load, "channel-load");
+
+static int get_bts_oml_conn(struct ctrl_cmd *cmd, void *data)
+{
+ const struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = get_model_oml_status(bts);
+
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_RO(bts_oml_conn, "oml-connection-state");
+
+static int get_bts_oml_up(struct ctrl_cmd *cmd, void *data)
+{
+ const struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%llu", bts->oml_link ? bts_updowntime(bts) : 0);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_RO(bts_oml_up, "oml-uptime");
+
+static int verify_bts_gprs_mode(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int valid;
+ enum bts_gprs_mode mode;
+ struct gsm_bts *bts = cmd->node;
+
+ mode = bts_gprs_mode_parse(value, &valid);
+ if (!valid) {
+ cmd->reply = "Mode is not known";
+ return 1;
+ }
+
+ if (!bts_gprs_mode_is_compat(bts, mode)) {
+ cmd->reply = "bts does not support this mode";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int get_bts_gprs_mode(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_strdup(cmd, bts_gprs_mode_name(bts->gprs.mode));
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_gprs_mode(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ bts->gprs.mode = bts_gprs_mode_parse(cmd->value, NULL);
+ return get_bts_gprs_mode(cmd, data);
+}
+
+CTRL_CMD_DEFINE(bts_gprs_mode, "gprs-mode");
+
+static int get_bts_rf_state(struct ctrl_cmd *cmd, void *data)
+{
+ const char *oper, *admin, *policy;
+ struct gsm_bts *bts = cmd->node;
+
+ if (!bts) {
+ cmd->reply = "bts not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts));
+ admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
+ policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
+
+ cmd->reply = talloc_asprintf(cmd, "%s,%s,%s", oper, admin, policy);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(bts_rf_state, "rf_state");
+
+/* Return a list of the states of each TRX for a given BTS.
+ * <bts_nr>,<trx_nr>,<opstate>,<adminstate>,<rf_policy>,<rsl_status>;<bts_nr>,<trx_nr>,...;...;
+ * For details on the string, see bsc_rf_states_c();
+ */
+static int get_bts_rf_states(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ if (!bts) {
+ cmd->reply = "bts not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = bsc_rf_states_of_bts_c(cmd, bts);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(bts_rf_states, "rf_states");
+
+static int verify_bts_c0_power_red(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ const int red = atoi(value);
+
+ if (red < 0 || red > 6) {
+ cmd->reply = "Value is out of range";
+ return 1;
+ } else if (red % 2 != 0) {
+ cmd->reply = "Value must be even";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int get_bts_c0_power_red(struct ctrl_cmd *cmd, void *data)
+{
+ const struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%u", bts->c0_max_power_red_db);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_c0_power_red(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ const int red = atoi(cmd->value);
+ int rc;
+
+ rc = gsm_bts_set_c0_power_red(bts, red);
+ switch (rc) {
+ case 0: /* success */
+ return get_bts_c0_power_red(cmd, data);
+ case -ENOTCONN:
+ cmd->reply = "BTS is offline";
+ return CTRL_CMD_ERROR;
+ case -ENOTSUP:
+ cmd->reply = "BCCH carrier power reduction is not supported";
+ return CTRL_CMD_ERROR;
+ default:
+ cmd->reply = "Failed to enable BCCH carrier power reduction";
+ return CTRL_CMD_ERROR;
+ }
+}
+
+CTRL_CMD_DEFINE(bts_c0_power_red, "c0-power-reduction");
+
+static int get_bts_neighbor_list(struct ctrl_cmd *cmd, const struct bitvec *neigh_list)
+{
+ int i;
+ char *pos;
+
+ /* The length of "1 2 3 ... 1023" is 4009, so 4096 is enough */
+ cmd->reply = talloc_size(cmd, 4096);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply[0] = '\0';
+
+ pos = cmd->reply;
+
+ for (i = 0; i < neigh_list->data_len * 8; i++) {
+ if (!bitvec_get_bit_pos(neigh_list, i))
+ continue;
+
+ pos += sprintf(pos, i == 0 ? "%u" : " %u", i);
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int get_bts_neighbor_list_si2(struct ctrl_cmd *cmd, void *data)
+{
+ const struct gsm_bts *bts = cmd->node;
+ return get_bts_neighbor_list(cmd, &bts->si_common.neigh_list);
+}
+
+CTRL_CMD_DEFINE_RO(bts_neighbor_list_si2, "neighbor-list si2");
+
+static int get_bts_neighbor_list_si5(struct ctrl_cmd *cmd, void *data)
+{
+ const struct gsm_bts *bts = cmd->node;
+ return get_bts_neighbor_list(cmd, &bts->si_common.si5_neigh_list);
+}
+
+CTRL_CMD_DEFINE_RO(bts_neighbor_list_si5, "neighbor-list si5");
+
+static int verify_bts_neighbor_list_add_del(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int arfcn;
+
+ if (osmo_str_to_int(&arfcn, value, 10, 0, 1023) < 0) {
+ cmd->reply = "Invalid ARFCN value";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int set_bts_neighbor_list_add_del(struct ctrl_cmd *cmd, void *data, bool add, struct bitvec *neigh_list)
+{
+ int arfcn_int;
+ uint16_t arfcn;
+ enum gsm_band unused;
+
+ if (osmo_str_to_int(&arfcn_int, cmd->value, 10, 0, 1023) < 0) {
+ cmd->reply = "Failed to parse ARFCN value";
+ return CTRL_CMD_ERROR;
+ }
+ arfcn = (uint16_t) arfcn_int;
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ cmd->reply = "Invalid arfcn detected";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (add)
+ bitvec_set_bit_pos(neigh_list, arfcn, 1);
+ else
+ bitvec_set_bit_pos(neigh_list, arfcn, 0);
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+static int verify_bts_neighbor_list_add(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ return verify_bts_neighbor_list_add_del(cmd, value, _data);
+}
+
+static int set_bts_neighbor_list_add(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) {
+ cmd->reply = "Neighbor list not in manual mode";
+ return CTRL_CMD_ERROR;
+ }
+ return set_bts_neighbor_list_add_del(cmd, data, true, &bts->si_common.neigh_list);
+}
+
+CTRL_CMD_DEFINE_WO(bts_neighbor_list_add, "neighbor-list add");
+
+static int verify_bts_neighbor_list_del(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ return verify_bts_neighbor_list_add_del(cmd, value, _data);
+}
+
+static int set_bts_neighbor_list_del(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) {
+ cmd->reply = "Neighbor list not in manual mode";
+ return CTRL_CMD_ERROR;
+ }
+ return set_bts_neighbor_list_add_del(cmd, data, false, &bts->si_common.neigh_list);
+}
+
+CTRL_CMD_DEFINE_WO(bts_neighbor_list_del, "neighbor-list del");
+
+static int verify_bts_neighbor_list_si5_add(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ return verify_bts_neighbor_list_add_del(cmd, value, _data);
+}
+
+static int set_bts_neighbor_list_si5_add(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ if (bts->neigh_list_manual_mode != NL_MODE_MANUAL_SI5SEP) {
+ cmd->reply = "Neighbor list not in manual mode with separate SI5";
+ return CTRL_CMD_ERROR;
+ }
+ return set_bts_neighbor_list_add_del(cmd, data, true, &bts->si_common.si5_neigh_list);
+}
+
+CTRL_CMD_DEFINE_WO(bts_neighbor_list_si5_add, "neighbor-list si5-add");
+
+static int verify_bts_neighbor_list_si5_del(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ return verify_bts_neighbor_list_add_del(cmd, value, _data);
+}
+
+static int set_bts_neighbor_list_si5_del(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ if (bts->neigh_list_manual_mode != NL_MODE_MANUAL_SI5SEP) {
+ cmd->reply = "Neighbor list not in manual mode with separate SI5";
+ return CTRL_CMD_ERROR;
+ }
+ return set_bts_neighbor_list_add_del(cmd, data, false, &bts->si_common.si5_neigh_list);
+}
+
+CTRL_CMD_DEFINE_WO(bts_neighbor_list_si5_del, "neighbor-list si5-del");
+
+static int verify_bts_neighbor_list_mode(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (!strcmp(value, "automatic"))
+ return 0;
+ if (!strcmp(value, "manual"))
+ return 0;
+ if (!strcmp(value, "manual-si5"))
+ return 0;
+
+ cmd->reply = "Invalid mode";
+ return 1;
+}
+
+static int set_bts_neighbor_list_mode(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int mode = NL_MODE_AUTOMATIC;
+
+ if (!strcmp(cmd->value, "automatic"))
+ mode = NL_MODE_AUTOMATIC;
+ else if (!strcmp(cmd->value, "manual"))
+ mode = NL_MODE_MANUAL;
+ else if (!strcmp(cmd->value, "manual-si5"))
+ mode = NL_MODE_MANUAL_SI5SEP;
+
+ switch (mode) {
+ case NL_MODE_MANUAL_SI5SEP:
+ case NL_MODE_MANUAL:
+ /* make sure we clear the current list when switching to
+ * manual mode */
+ if (bts->neigh_list_manual_mode == 0)
+ memset(&bts->si_common.data.neigh_list, 0, sizeof(bts->si_common.data.neigh_list));
+ break;
+ default:
+ break;
+ }
+
+ bts->neigh_list_manual_mode = mode;
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO(bts_neighbor_list_mode, "neighbor-list mode");
+
+/* si2quater neighbor management: delete an EARFCN.
+ * Format: bts.<0-255>.si2quater-neighbor-list.del.earfcn EARFCN
+ * EARFCN is in range 0..65535 */
+static int set_bts_si2quater_neighbor_list_del_earfcn(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)cmd->node;
+ int earfcn;
+
+ if (osmo_str_to_int(&earfcn, cmd->value, 10, 0, 65535) < 0) {
+ cmd->reply = "Failed to parse neighbor EARFCN value";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (bts_earfcn_del(bts, earfcn) < 0) {
+ cmd->reply = "Failed to delete a (not existent?) neighbor EARFCN";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(bts_si2quater_neighbor_list_del_earfcn,
+ "si2quater-neighbor-list del earfcn");
+
+/* si2quater neighbor management: delete an UARFCN
+ * Format: bts.<0-255>.si2quater-neighbor-list.del.uarfcn UARFCN,SCRAMBLE
+ * UARFCN is in range 0..16383, SCRAMBLE is in range 0..511 */
+static int set_bts_si2quater_neighbor_list_del_uarfcn(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)cmd->node;
+ char *uarfcn_str, *scramble_str;
+ char *tmp, *saveptr;
+ int uarfcn, scramble;
+
+ tmp = talloc_strdup(OTC_SELECT, cmd->value);
+ if (!tmp) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ uarfcn_str = strtok_r(tmp, ",", &saveptr);
+ scramble_str = strtok_r(NULL, ",", &saveptr);
+
+ if (!uarfcn_str || osmo_str_to_int(&uarfcn, uarfcn_str, 10, 0, 16383) < 0) {
+ cmd->reply = "Failed to parse neighbor UARFCN value";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!scramble_str || osmo_str_to_int(&scramble, scramble_str, 10, 0, 511) < 0) {
+ cmd->reply = "Failed to parse neighbor scrambling code";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (bts_uarfcn_del(bts, uarfcn, scramble) < 0) {
+ cmd->reply = "Failed to delete a (not existent?) neighbor UARFCN";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(bts_si2quater_neighbor_list_del_uarfcn,
+ "si2quater-neighbor-list del uarfcn");
+
+static int verify_bts_si2quater_neighbor_list_add_earfcn(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ char *earfcn_str, *thresh_hi_str, *thresh_lo_str, *prio_str, *qrxlv_str, *meas_str, *saveptr, *tmp;
+ int earfcn, thresh_hi, thresh_lo, prio, qrxlv, meas;
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ earfcn_str = strtok_r(tmp, ",", &saveptr);
+ thresh_hi_str = strtok_r(NULL, ",", &saveptr);
+ thresh_lo_str = strtok_r(NULL, ",", &saveptr);
+ prio_str = strtok_r(NULL, ",", &saveptr);
+ qrxlv_str = strtok_r(NULL, ",", &saveptr);
+ meas_str = strtok_r(NULL, "\0", &saveptr);
+
+
+ if (!earfcn_str || osmo_str_to_int(&earfcn, earfcn_str, 10, 0, 65535) < 0) {
+ cmd->reply = "Failed to parse neighbor EARFCN value";
+ return 1;
+ }
+
+ if (!thresh_hi_str || osmo_str_to_int(&thresh_hi, thresh_hi_str, 10, 0, 31) < 0) {
+ cmd->reply = "Failed to parse neighbor threshold high bits value";
+ return 1;
+ }
+
+ if (!thresh_lo_str || osmo_str_to_int(&thresh_lo, thresh_lo_str, 10, 0, 32) < 0) {
+ cmd->reply = "Failed to parse neighbor threshold low bits value";
+ return 1;
+ }
+
+ if (!prio_str || osmo_str_to_int(&prio, prio_str, 10, 0, 8) < 0) {
+ cmd->reply = "Failed to parse neighbor priority value";
+ return 1;
+ }
+
+ if (!qrxlv_str || osmo_str_to_int(&qrxlv, qrxlv_str, 10, 0, 32) < 0) {
+ cmd->reply = "Failed to parse neighbor QRXLEVMIN value";
+ return 1;
+ }
+
+ if (!meas_str || osmo_str_to_int(&meas, meas_str, 10, 0, 8) < 0) {
+ cmd->reply = "Failed to parse neighbor measurement bandwidth";
+ return 1;
+ }
+
+ return 0;
+}
+
+/* si2quater neighbor management: add an EARFCN
+ * Format: bts.<0-255>.si2quater-neighbor-list.add.earfcn <EARFCN>,<thresh-hi>,<thresh-lo>,<priority>,<QRXLEVMIN>,<measurement bandwidth>
+ * EARFCN is in range 0..65535, thresh-hi is in range 0..31, thresh-hi is in range 0..32,
+ * priority is in range 0..8, QRXLEVMIN is in range 0..32, measurement bandwidth is in range 0..8 */
+static int set_bts_si2quater_neighbor_list_add_earfcn(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)cmd->node;
+ char *earfcn_str, *thresh_hi_str, *thresh_lo_str, *prio_str, *qrxlv_str, *meas_str, *saveptr, *tmp;
+ int earfcn, thresh_hi, thresh_lo, prio, qrxlv, meas, result;
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ earfcn_str = strtok_r(tmp, ",", &saveptr);
+ thresh_hi_str = strtok_r(NULL, ",", &saveptr);
+ thresh_lo_str = strtok_r(NULL, ",", &saveptr);
+ prio_str = strtok_r(NULL, ",", &saveptr);
+ qrxlv_str = strtok_r(NULL, ",", &saveptr);
+ meas_str = strtok_r(NULL, "\0", &saveptr);
+
+
+ if (!earfcn_str || osmo_str_to_int(&earfcn, earfcn_str, 10, 0, 65535) < 0) {
+ cmd->reply = "Failed to parse neighbor EARFCN value";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!thresh_hi_str || osmo_str_to_int(&thresh_hi, thresh_hi_str, 10, 0, 31) < 0) {
+ cmd->reply = "Failed to parse neighbor threshold high bits value";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!thresh_lo_str || osmo_str_to_int(&thresh_lo, thresh_lo_str, 10, 0, 32) < 0) {
+ cmd->reply = "Failed to parse neighbor threshold low bits value";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!prio_str || osmo_str_to_int(&prio, prio_str, 10, 0, 8) < 0) {
+ cmd->reply = "Failed to parse neighbor priority value";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!qrxlv_str || osmo_str_to_int(&qrxlv, qrxlv_str, 10, 0, 32) < 0) {
+ cmd->reply = "Failed to parse neighbor QRXLEVMIN value";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!meas_str || osmo_str_to_int(&meas, meas_str, 10, 0, 8) < 0) {
+ cmd->reply = "Failed to parse neighbor measurement bandwidth";
+ return CTRL_CMD_ERROR;
+ }
+
+ result = bts_earfcn_add(bts, earfcn, thresh_hi, thresh_lo, prio, qrxlv, meas);
+
+ if ((result == 0) && (si2q_num(bts) <= SI2Q_MAX_NUM)) {
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+ }
+
+ switch (result) {
+ case 0:
+ cmd->reply = talloc_asprintf(cmd, "Not enough space in SI2quater (%u/%u used)", bts->si2q_count, SI2Q_MAX_NUM);
+ if (!cmd->reply)
+ cmd->reply = "OOM";
+ break;
+ case 1:
+ cmd->reply = "Multiple threshold-high are not supported";
+ break;
+ case EARFCN_THRESH_LOW_INVALID:
+ cmd->reply = "Multiple threshold-low are not supported";
+ break;
+ case EARFCN_QRXLV_INVALID + 1:
+ cmd->reply = "Multiple QRXLEVMIN are not supported";
+ break;
+ case EARFCN_PRIO_INVALID:
+ cmd->reply = "Multiple priorities are not supported";
+ break;
+ default:
+ cmd->reply = talloc_asprintf(cmd, "Unable to add EARFCN: %s", strerror(-result));
+ if (!cmd->reply)
+ cmd->reply = "OOM";
+ }
+
+ if (bts_earfcn_del(bts, earfcn) != 0)
+ cmd->reply = "Failed to roll-back adding EARFCN";
+
+ return CTRL_CMD_ERROR;
+}
+
+CTRL_CMD_DEFINE_WO(bts_si2quater_neighbor_list_add_earfcn,
+ "si2quater-neighbor-list add earfcn");
+
+static int verify_bts_si2quater_neighbor_list_add_uarfcn(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ char *uarfcn_str, *scramble_str, *diversity_str, *saveptr, *tmp;
+ int uarfcn, scramble;
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ uarfcn_str = strtok_r(tmp, ",", &saveptr);
+ scramble_str = strtok_r(NULL, ",", &saveptr);
+ diversity_str = strtok_r(NULL, "\0", &saveptr);
+
+ if (!uarfcn_str || osmo_str_to_int(&uarfcn, uarfcn_str, 10, 0, 16383) < 0) {
+ cmd->reply = "Failed to parse neighbor UARFCN value";
+ return 1;
+ }
+
+ if (!scramble_str || osmo_str_to_int(&scramble, scramble_str, 10, 0, 511) < 0) {
+ cmd->reply = "Failed to parse neighbor scrambling code";
+ return 1;
+ }
+
+ if (!diversity_str || ((strcmp(diversity_str, "1") != 0) && (strcmp(diversity_str, "0") != 0))) {
+ cmd->reply = "Failed to parse neighbor diversity bit";
+ return 1;
+ }
+
+ return 0;
+}
+
+/* si2quater neighbor management: add an UARFCN
+ * Format: bts.<0-255>.si2quater-neighbor-list.add.uarfcn <UARFCN>,<scrambling code>,<diversity bit>
+ * UARFCN is in range 0..16383, scrambling code is in range 0..511 */
+static int set_bts_si2quater_neighbor_list_add_uarfcn(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)cmd->node;
+ char *uarfcn_str, *scramble_str, *diversity_str, *saveptr, *tmp;
+ int uarfcn, scramble;
+ bool diversity;
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ uarfcn_str = strtok_r(tmp, ",", &saveptr);
+ scramble_str = strtok_r(NULL, ",", &saveptr);
+ diversity_str = strtok_r(NULL, "\0", &saveptr);
+
+
+ if (!uarfcn_str || osmo_str_to_int(&uarfcn, uarfcn_str, 10, 0, 16383) < 0) {
+ cmd->reply = "Failed to parse neighbor UARFCN value";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!scramble_str || osmo_str_to_int(&scramble, scramble_str, 10, 0, 511) < 0) {
+ cmd->reply = "Failed to parse neighbor scrambling code";
+ return CTRL_CMD_ERROR;
+ }
+
+ diversity = strcmp(diversity_str, "1") == 0;
+
+ switch (bts_uarfcn_add(bts, uarfcn, scramble, diversity)) {
+ case -ENOMEM:
+ cmd->reply = "max number of UARFCNs reached";
+ return CTRL_CMD_ERROR;
+ case -ENOSPC:
+ cmd->reply = "not enough space in SI2quater";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO(bts_si2quater_neighbor_list_add_uarfcn,
+ "si2quater-neighbor-list add uarfcn");
+
+static int verify_bts_cell_reselection_offset(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ const int cell_reselection_offset = atoi(value);
+
+ if (cell_reselection_offset < 0 || cell_reselection_offset > 126) {
+ cmd->reply = "Value is out of range";
+ return 1;
+ } else if (cell_reselection_offset % 2 != 0) {
+ cmd->reply = "Value must be even";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int get_bts_cell_reselection_offset(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ if (!bts->si_common.cell_ro_sel_par.present) {
+ cmd->reply = "0";
+ return CTRL_CMD_REPLY;
+ }
+
+ cmd->reply = talloc_asprintf(cmd, "%u", bts->si_common.cell_ro_sel_par.cell_resel_off * 2);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_cell_reselection_offset(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(cmd->value) / 2;
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(bts_cell_reselection_offset, "cell-reselection-offset");
+
+static int verify_bts_cell_reselection_penalty_time(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int penalty_time;
+
+ if (strcmp(value, "reserved") == 0)
+ return 0;
+
+ penalty_time = atoi(value);
+
+ if (penalty_time < 20 || penalty_time > 620) {
+ cmd->reply = "Value is out of range";
+ return 1;
+ } else if (penalty_time % 20 != 0) {
+ cmd->reply = "Value must be a multiple of 20";
+ return 1;
+ }
+
+ return 0;
+}
+
+/* According to 3GPP TS 45.008, PENALTY_TIME in the Control parameters section */
+static int get_bts_cell_reselection_penalty_time(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ if (!bts->si_common.cell_ro_sel_par.present) {
+ cmd->reply = "0";
+ return CTRL_CMD_REPLY;
+ }
+
+ if (bts->si_common.cell_ro_sel_par.penalty_time == 31) {
+ cmd->reply = "reserved";
+ return CTRL_CMD_REPLY;
+ }
+
+ /* Calculate the penalty time in seconds */
+ cmd->reply = talloc_asprintf(cmd, "%u", (bts->si_common.cell_ro_sel_par.penalty_time * 20) + 20);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_cell_reselection_penalty_time(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ bts->si_common.cell_ro_sel_par.present = 1;
+
+ if (strcmp(cmd->value, "reserved") == 0)
+ bts->si_common.cell_ro_sel_par.penalty_time = 31;
+ else
+ bts->si_common.cell_ro_sel_par.penalty_time = (atoi(cmd->value) - 20) / 20;
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(bts_cell_reselection_penalty_time, "cell-reselection-penalty-time");
+
+static int verify_bts_cell_reselection_hysteresis(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ const int cell_reselection_hysteresis = atoi(value);
+
+ if (cell_reselection_hysteresis < 0 || cell_reselection_hysteresis > 14) {
+ cmd->reply = "Value is out of range";
+ return 1;
+ } else if (cell_reselection_hysteresis % 2 != 0) {
+ cmd->reply = "Value must be even";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int get_bts_cell_reselection_hysteresis(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%u", bts->si_common.cell_sel_par.cell_resel_hyst * 2);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_cell_reselection_hysteresis(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ bts->si_common.cell_sel_par.cell_resel_hyst = atoi(cmd->value) / 2;
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(bts_cell_reselection_hysteresis, "cell-reselection-hysteresis");
+
+
+static int verify_bts_radio_link_timeout(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int radio_link_timeout;
+ struct gsm_bts *bts = cmd->node;
+
+ if (strcmp(value, "infinite") == 0) {
+ if (bts->type != GSM_BTS_TYPE_OSMOBTS) {
+ cmd->reply = "Infinite radio link timeout not supported by BTS";
+ return 1;
+ }
+ return 0;
+ }
+
+ radio_link_timeout = atoi(cmd->value);
+
+ if (radio_link_timeout < 0 || radio_link_timeout > 64) {
+ cmd->reply = "Value is out of range";
+ return 1;
+ } else if (radio_link_timeout % 4 != 0) {
+ cmd->reply = "Value must be a multiple of 4";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int get_bts_radio_link_timeout(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%u", gsm_bts_get_radio_link_timeout(bts));
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_radio_link_timeout(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ gsm_bts_set_radio_link_timeout(bts, atoi(cmd->value));
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(bts_radio_link_timeout, "radio-link-timeout");
+
+static int verify_bts_rxlev_access_min(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int rxlev_access_min = atoi(cmd->value);
+
+ if (rxlev_access_min < 0 || rxlev_access_min > 63) {
+ cmd->reply = "Value is out of range";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int get_bts_rxlev_access_min(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%u", bts->si_common.cell_sel_par.rxlev_acc_min);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_rxlev_access_min(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ bts->si_common.cell_sel_par.rxlev_acc_min = atoi(cmd->value);
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(bts_rxlev_access_min, "rach-rxlev-access-min");
+
+/* Return space concatenated set of pairs <class>,<barred/allowed> */
+static int get_bts_rach_access_control_class(struct ctrl_cmd *cmd, void *data)
+{
+ int i;
+ const struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_strdup(cmd, "");
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ for (i = 0; i < 8; i++) {
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ i == 0 ? "%u,%s" : " %u,%s",
+ i, bts->si_common.rach_control.t3 & (0x1 << i) ? "barred" : "allowed");
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+ }
+
+ for (i = 0; i < 8; i++) {
+ if (i != 2)
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ " %u,%s",
+ i + 8, bts->si_common.rach_control.t2 & (0x1 << i) ? "barred" : "allowed");
+ else
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ " emergency,%s",
+ bts->si_common.rach_control.t2 & (0x1 << i) ? "barred" : "allowed");
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_RO(bts_rach_access_control_class, "rach-access-control-classes");
+
+static int verify_access_control_class(struct ctrl_cmd *cmd, const char *value)
+{
+ int acc;
+
+ if (strcmp(value, "emergency") == 0)
+ return 0;
+
+ acc = atoi(value);
+
+ if (acc < 0 || acc > 15) {
+ cmd->reply = "Value is out of range";
+ return 1;
+ } else if (acc == 10) {
+ cmd->reply = "Access control class 10 does not exist, consider using \"emergency\" instead";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int set_access_control_class(struct ctrl_cmd *cmd, bool allow)
+{
+ int acc;
+ struct gsm_bts *bts = cmd->node;
+
+ if (strcmp(cmd->value, "emergency") == 0) {
+ if (allow)
+ bts->si_common.rach_control.t2 &= ~0x4;
+ else
+ bts->si_common.rach_control.t2 |= 0x4;
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+ }
+
+ acc = atoi(cmd->value);
+ if (acc < 8)
+ if (allow)
+ bts->si_common.rach_control.t3 &= ~(0x1 << acc);
+ else
+ bts->si_common.rach_control.t3 |= (0x1 << acc);
+ else
+ if (allow)
+ bts->si_common.rach_control.t2 &= ~(0x1 << (acc - 8));
+ else
+ bts->si_common.rach_control.t2 |= (0x1 << (acc - 8));
+
+ if (acc < 10)
+ acc_mgr_perm_subset_changed(&bts->acc_mgr, &bts->si_common.rach_control);
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+static int verify_bts_rach_access_control_class_bar(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ return verify_access_control_class(cmd, value);
+}
+
+static int set_bts_rach_access_control_class_bar(struct ctrl_cmd *cmd, void *data)
+{
+ return set_access_control_class(cmd, false);
+}
+
+CTRL_CMD_DEFINE_WO(bts_rach_access_control_class_bar, "rach-access-control-class bar");
+
+static int verify_bts_rach_access_control_class_allow(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ return verify_access_control_class(cmd, value);
+}
+
+static int set_bts_rach_access_control_class_allow(struct ctrl_cmd *cmd, void *data)
+{
+ return set_access_control_class(cmd, true);
+}
+
+CTRL_CMD_DEFINE_WO(bts_rach_access_control_class_allow, "rach-access-control-class allow");
+
+static int verify_bts_rach_cell_barred(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int bar = atoi(cmd->value);
+
+ if ((bar != 0) && (bar != 1))
+ return 1;
+
+ return 0;
+}
+
+static int get_bts_rach_cell_barred(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%u", bts->si_common.rach_control.cell_bar);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_rach_cell_barred(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ bts->si_common.rach_control.cell_bar = atoi(cmd->value);
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(bts_rach_cell_barred, "rach-cell-barred");
+
+static int verify_bts_rach_max_trans(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int max_trans = atoi(cmd->value);
+
+ if ((max_trans != 1) && (max_trans != 2) && (max_trans != 4) && (max_trans != 7))
+ return 1;
+
+ return 0;
+}
+
+static int get_bts_rach_max_trans(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%u", rach_max_trans_raw2val(bts->si_common.rach_control.max_trans));
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_rach_max_trans(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(cmd->value));
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(bts_rach_max_trans, "rach-max-transmission");
+
+/* Return space concatenated set of tuples <UARFCN>,<scrambling code>,<diversity bit> */
+static int get_bts_neighbor_list_si2quater_uarfcn(struct ctrl_cmd *cmd, void *data)
+{
+ int i;
+ const struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_strdup(cmd, "");
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ for (i = 0; i < bts->si_common.uarfcn_length; i++) {
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ i == 0 ? "%u,%u,%u" : " %u,%u,%u",
+ bts->si_common.data.uarfcn_list[i],
+ bts->si_common.data.scramble_list[i] & ~(1 << 9),
+ (bts->si_common.data.scramble_list[i] >> 9) & 1);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_RO(bts_neighbor_list_si2quater_uarfcn, "neighbor-list si2quater uarfcns");
+
+/* Return space concatenated set of tuples <EARFCN>,<thresh-hi>,<thresh-lo>,<prio>,<qrxlv>,<meas> */
+static int get_bts_neighbor_list_si2quater_earfcn(struct ctrl_cmd *cmd, void *data)
+{
+ int i;
+ bool first_earfcn = true;
+ const struct gsm_bts *bts = cmd->node;
+ const struct osmo_earfcn_si2q *neighbors = &bts->si_common.si2quater_neigh_list;
+
+ cmd->reply = talloc_strdup(cmd, "");
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ for (i = 0; i < MAX_EARFCN_LIST; i++) {
+ if (neighbors->arfcn[i] == OSMO_EARFCN_INVALID)
+ continue;
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ first_earfcn ? "%u,%u,%u,%u,%u,%u" : " %u,%u,%u,%u,%u,%u",
+ neighbors->arfcn[i],
+ neighbors->thresh_hi,
+ neighbors->thresh_lo_valid ? neighbors->thresh_lo : 32,
+ neighbors->prio_valid ? neighbors->prio : 8,
+ neighbors->qrxlm_valid ? neighbors->qrxlm : 32,
+ (neighbors->meas_bw[i] != OSMO_EARFCN_MEAS_INVALID) ? neighbors->meas_bw[i] : 8);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+ first_earfcn = false;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_RO(bts_neighbor_list_si2quater_earfcn, "neighbor-list si2quater earfcns");
+
+char *bts_lchan_dump_full_ctrl(const void *t, struct gsm_bts *bts)
+{
+ int trx_nr;
+ bool first_trx = true;
+ char *trx_dump, *dump;
+ struct gsm_bts_trx *trx;
+
+ dump = talloc_strdup(t, "");
+ if (!dump)
+ return NULL;
+
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ trx_dump = trx_lchan_dump_full_ctrl(t, trx);
+ if (!trx_dump)
+ return NULL;
+ if (!strlen(trx_dump))
+ continue;
+ dump = talloc_asprintf_append(dump, first_trx ? "%s" : "\n%s", trx_dump);
+ if (!dump)
+ return NULL;
+ first_trx = false;
+ }
+
+ return dump;
+}
+
+/* Return full information about all logical channels in a BTS.
+ * format: bts.<0-255>.show-lchan.full
+ * result format: New line delimited list of <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>,
+ * <ms power>,<interference dbm>, <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>,
+ * <ipa bound conn id>,<ipa conn ip>,<ipa conn port>,<ipa conn speech mode>
+ */
+static int get_bts_show_lchan_full(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = bts_lchan_dump_full_ctrl(cmd, bts);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(bts_show_lchan_full, "show-lchan full");
+
+int bsc_bts_ctrl_cmds_install(void)
+{
+ int rc = 0;
+
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_loc);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_lac);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_ci);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_bsic);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_max_delay);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_expiry_timeout);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_apply_config);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_power_ctrl_defs);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_chan_load);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_conn);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_up);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_gprs_mode);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rf_state);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rf_states);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_c0_power_red);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si2);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si5);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_add);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_del);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si5_add);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si5_del);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_mode);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si2quater_neighbor_list_del_earfcn);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si2quater_neighbor_list_del_uarfcn);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si2quater_neighbor_list_add_earfcn);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si2quater_neighbor_list_add_uarfcn);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_cell_reselection_offset);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_cell_reselection_penalty_time);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_cell_reselection_hysteresis);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_ms_max_power);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_radio_link_timeout);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rxlev_access_min);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_access_control_class);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_access_control_class_bar);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_access_control_class_allow);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_cell_barred);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rach_max_trans);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si2quater_uarfcn);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_neighbor_list_si2quater_earfcn);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_show_lchan_full);
+
+ rc |= neighbor_ident_ctrl_init();
+
+ rc = bsc_bts_trx_ctrl_cmds_install();
+
+ return rc;
+}
diff --git a/src/osmo-bsc/bts_ericsson_rbs2000.c b/src/osmo-bsc/bts_ericsson_rbs2000.c
index 4d1e91b0f..36b378318 100644
--- a/src/osmo-bsc/bts_ericsson_rbs2000.c
+++ b/src/osmo-bsc/bts_ericsson_rbs2000.c
@@ -29,6 +29,7 @@
#include <osmocom/abis/e1_input.h>
#include <osmocom/bsc/signal.h>
#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/abis/lapd.h>
@@ -36,10 +37,6 @@ static void bootstrap_om_bts(struct gsm_bts *bts)
{
LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
- /* FIXME: this is global init, not bootstrapping */
- abis_om2k_bts_init(bts);
- abis_om2k_trx_init(bts->c0);
-
/* TODO: Should we wait for a Failure report? */
om2k_bts_fsm_start(bts);
}
@@ -48,12 +45,14 @@ static void bootstrap_om_trx(struct gsm_bts_trx *trx)
{
LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for TRX %u/%u\n",
trx->bts->nr, trx->nr);
- /* FIXME */
+
+ om2k_trx_fsm_start(trx);
}
static int shutdown_om(struct gsm_bts *bts)
{
gsm_bts_all_ts_dispatch(bts, TS_EV_OML_DOWN, NULL);
+ gsm_bts_stats_reset(bts);
/* FIXME */
return 0;
@@ -140,6 +139,10 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
LOGP(DNM, LOGL_NOTICE, "Line-%u TS-%u TEI-%u SAPI-%u: Link "
"Lost for Ericsson RBS2000. Re-starting DL Establishment\n",
isd->line->num, isd->ts_nr, isd->tei, isd->sapi);
+ if (isd->tei == isd->trx->bts->oml_tei)
+ om2k_bts_fsm_reset(isd->trx->bts);
+ else
+ om2k_trx_fsm_reset(isd->trx);
/* Some datalink for a given TEI/SAPI went down, try to re-start it */
e1i_ts = &isd->line->ts[isd->ts_nr-1];
OSMO_ASSERT(e1i_ts->type == E1INP_TS_TYPE_SIGN);
@@ -149,14 +152,16 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
case S_L_INP_LINE_NOALARM:
if (strcasecmp(isd->line->driver->name, "DAHDI")
&& strcasecmp(isd->line->driver->name, "MISDN_LAPD")
- && strcasecmp(isd->line->driver->name, "UNIXSOCKET"))
+ && strcasecmp(isd->line->driver->name, "UNIXSOCKET")
+ && strcasecmp(isd->line->driver->name, "E1D"))
break;
start_sabm_in_line(isd->line, 1);
break;
case S_L_INP_LINE_ALARM:
if (strcasecmp(isd->line->driver->name, "DAHDI")
&& strcasecmp(isd->line->driver->name, "MISDN_LAPD")
- && strcasecmp(isd->line->driver->name, "UNIXSOCKET"))
+ && strcasecmp(isd->line->driver->name, "UNIXSOCKET")
+ && strcasecmp(isd->line->driver->name, "E1D"))
break;
start_sabm_in_line(isd->line, 0);
break;
@@ -170,6 +175,11 @@ static void config_write_bts(struct vty *vty, struct gsm_bts *bts)
abis_om2k_config_write_bts(vty, bts);
}
+static void config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ abis_om2k_config_write_trx(vty, trx);
+}
+
static int bts_model_rbs2k_start(struct gsm_network *net);
static void bts_model_rbs2k_e1line_bind_ops(struct e1inp_line *line)
@@ -177,17 +187,40 @@ static void bts_model_rbs2k_e1line_bind_ops(struct e1inp_line *line)
e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops);
}
+static int bts_model_rbs2k_bts_init(struct gsm_bts *bts)
+{
+ abis_om2k_bts_init(bts);
+ return 0;
+}
+
+static int bts_model_rbs2k_trx_init(struct gsm_bts_trx *trx)
+{
+ abis_om2k_trx_init(trx);
+ return 0;
+}
+
static struct gsm_bts_model model_rbs2k = {
.type = GSM_BTS_TYPE_RBS2000,
.name = "rbs2000",
.start = bts_model_rbs2k_start,
+ .bts_init = bts_model_rbs2k_bts_init,
+ .trx_init = bts_model_rbs2k_trx_init,
.oml_rcvmsg = &abis_om2k_rcvmsg,
.config_write_bts = &config_write_bts,
+ .config_write_trx = &config_write_trx,
.e1line_bind_ops = &bts_model_rbs2k_e1line_bind_ops,
};
static int bts_model_rbs2k_start(struct gsm_network *net)
{
+ osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
+ osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL);
+
+ return 0;
+}
+
+int bts_model_rbs2k_init(void)
+{
model_rbs2k.features.data = &model_rbs2k._features_data[0];
model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data);
@@ -197,13 +230,5 @@ static int bts_model_rbs2k_start(struct gsm_network *net)
osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_HSCSD);
osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_MULTI_TSC);
- osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
- osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL);
-
- return 0;
-}
-
-int bts_model_rbs2k_init(void)
-{
return gsm_bts_model_register(&model_rbs2k);
}
diff --git a/src/osmo-bsc/bts_init.c b/src/osmo-bsc/bts_init.c
index 18f1ed4c8..0e3debcf4 100644
--- a/src/osmo-bsc/bts_init.c
+++ b/src/osmo-bsc/bts_init.c
@@ -24,7 +24,7 @@ int bts_init(void)
bts_model_rbs2k_init();
bts_model_nanobts_init();
bts_model_nokia_site_init();
- bts_model_sysmobts_init();
+ bts_model_osmobts_init();
/* Your new BTS here. */
return 0;
}
diff --git a/src/osmo-bsc/bts_ipaccess_nanobts.c b/src/osmo-bsc/bts_ipaccess_nanobts.c
index cf81a22c6..b0532e5d9 100644
--- a/src/osmo-bsc/bts_ipaccess_nanobts.c
+++ b/src/osmo-bsc/bts_ipaccess_nanobts.c
@@ -1,6 +1,7 @@
/* ip.access nanoBTS specific code */
/* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
@@ -31,11 +32,13 @@
#include <osmocom/gsm/tlv.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
+#include <osmocom/core/stat_item.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/abis_nm.h>
#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/abis_osmo.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/abis/subchan_demux.h>
#include <osmocom/gsm/ipa.h>
@@ -45,10 +48,17 @@
#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bts_sm.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/bsc_stats.h>
static int bts_model_nanobts_start(struct gsm_network *net);
static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line);
+static int power_ctrl_send_def_params(const struct gsm_bts_trx *trx);
+static int power_ctrl_enc_rsl_params(struct msgb *msg, const struct gsm_power_ctrl_params *cp);
+
static char *get_oml_status(const struct gsm_bts *bts)
{
if (bts->oml_link)
@@ -64,6 +74,11 @@ struct gsm_bts_model bts_model_nanobts = {
.oml_rcvmsg = &abis_nm_rcvmsg,
.oml_status = &get_oml_status,
.e1line_bind_ops = bts_model_nanobts_e1line_bind_ops,
+
+ /* MS/BS Power control specific API */
+ .power_ctrl_send_def_params = &power_ctrl_send_def_params,
+ .power_ctrl_enc_rsl_params = &power_ctrl_enc_rsl_params,
+
/* Some nanoBTS firmwares (if not all) don't support SI2ter and cause
* problems on some MS if it is enabled, see OS#3063. Disable it by
* default, can still be enabled through VTY cmd with same name.
@@ -116,6 +131,7 @@ struct gsm_bts_model bts_model_nanobts = {
[NM_ATT_IPACC_REVOC_DATE] = { TLV_TYPE_TL16V },
},
},
+ .features_get_reported = false,
};
@@ -124,125 +140,52 @@ static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd)
{
uint8_t obj_class = nsd->obj_class;
void *obj = nsd->obj;
- struct gsm_nm_state *new_state = nsd->new_state;
+ struct gsm_bts_sm *bts_sm;
struct gsm_bts *bts;
struct gsm_bts_trx *trx;
+ struct gsm_bts_bb_trx *bb_transc;
struct gsm_bts_trx_ts *ts;
- struct gsm_bts_gprs_nsvc *nsvc;
-
- struct msgb *msgb;
-
- if (!is_ipaccess_bts(nsd->bts))
- return 0;
-
- /* This event-driven BTS setup is currently only required on nanoBTS */
+ struct gsm_gprs_nsvc *nsvc;
+ struct gsm_gprs_nse *nse;
+ struct gsm_gprs_cell *cell;
- /* S_NM_STATECHG_ADM is called after we call chg_adm_state() and would create
- * endless loop */
- if (evt != S_NM_STATECHG_OPER)
+ if (!is_ipa_abisip_bts(nsd->bts))
return 0;
switch (obj_class) {
case NM_OC_SITE_MANAGER:
- bts = container_of(obj, struct gsm_bts, site_mgr);
- if ((new_state->operational == NM_OPSTATE_ENABLED &&
- new_state->availability == NM_AVSTATE_OK) ||
- (new_state->operational == NM_OPSTATE_DISABLED &&
- new_state->availability == NM_AVSTATE_OFF_LINE))
- abis_nm_opstart(bts, obj_class, 0xff, 0xff, 0xff);
+ bts_sm = obj;
+ osmo_fsm_inst_dispatch(bts_sm->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_BTS:
bts = obj;
- if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
- msgb = nanobts_attr_bts_get(bts);
- abis_nm_set_bts_attr(bts, msgb->data, msgb->len);
- msgb_free(msgb);
- abis_nm_chg_adm_state(bts, obj_class,
- bts->bts_nr, 0xff, 0xff,
- NM_STATE_UNLOCKED);
- abis_nm_opstart(bts, obj_class,
- bts->bts_nr, 0xff, 0xff);
- }
+ osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_STATE_CHG_REP, nsd);
+ break;
+ case NM_OC_BASEB_TRANSC:
+ bb_transc = obj;
+ osmo_fsm_inst_dispatch(bb_transc->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_CHANNEL:
ts = obj;
- trx = ts->trx;
- if (new_state->operational == NM_OPSTATE_DISABLED &&
- new_state->availability == NM_AVSTATE_DEPENDENCY) {
- enum abis_nm_chan_comb ccomb =
- abis_nm_chcomb4pchan(ts->pchan_from_config);
- if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL) {
- ipaccess_drop_oml_deferred(trx->bts);
- return -1;
- }
- abis_nm_chg_adm_state(trx->bts, obj_class,
- trx->bts->bts_nr, trx->nr, ts->nr,
- NM_STATE_UNLOCKED);
- abis_nm_opstart(trx->bts, obj_class,
- trx->bts->bts_nr, trx->nr, ts->nr);
- }
+ osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_RADIO_CARRIER:
trx = obj;
- if (new_state->operational == NM_OPSTATE_DISABLED &&
- new_state->availability == NM_AVSTATE_OK)
- abis_nm_opstart(trx->bts, obj_class, trx->bts->bts_nr,
- trx->nr, 0xff);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_GPRS_NSE:
- bts = container_of(obj, struct gsm_bts, gprs.nse);
- if (bts->gprs.mode == BTS_GPRS_NONE)
- break;
- if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
- msgb = nanobts_attr_nse_get(bts);
- abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
- 0xff, 0xff, msgb->data,
- msgb->len);
- msgb_free(msgb);
- abis_nm_opstart(bts, obj_class, bts->bts_nr,
- 0xff, 0xff);
- }
+ nse = obj;
+ osmo_fsm_inst_dispatch(nse->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_GPRS_CELL:
- bts = container_of(obj, struct gsm_bts, gprs.cell);
- if (bts->gprs.mode == BTS_GPRS_NONE)
- break;
- if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
- msgb = nanobts_attr_cell_get(bts);
- abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
- 0, 0xff, msgb->data,
- msgb->len);
- msgb_free(msgb);
- abis_nm_opstart(bts, obj_class, bts->bts_nr,
- 0, 0xff);
- abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
- 0, 0xff, NM_STATE_UNLOCKED);
- abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE, bts->bts_nr,
- 0xff, 0xff, NM_STATE_UNLOCKED);
- }
+ cell = obj;
+ osmo_fsm_inst_dispatch(cell->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_GPRS_NSVC:
nsvc = obj;
- bts = nsvc->bts;
- if (bts->gprs.mode == BTS_GPRS_NONE)
- break;
- /* We skip NSVC1 since we only use NSVC0 */
- if (nsvc->id == 1)
- break;
- if ((new_state->availability == NM_AVSTATE_OFF_LINE) ||
- (new_state->availability == NM_AVSTATE_DEPENDENCY)) {
- msgb = nanobts_attr_nscv_get(bts);
- abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
- nsvc->id, 0xff,
- msgb->data, msgb->len);
- msgb_free(msgb);
- abis_nm_opstart(bts, obj_class, bts->bts_nr,
- nsvc->id, 0xff);
- abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
- nsvc->id, 0xff,
- NM_STATE_UNLOCKED);
- }
+ osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_STATE_CHG_REP, nsd);
+ break;
default:
break;
}
@@ -252,75 +195,229 @@ static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd)
/* Callback function to be called every time we receive a 12.21 SW activated report */
static int sw_activ_rep(struct msgb *mb)
{
- struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ const struct abis_om_fom_hdr *foh = msgb_l3(mb);
struct e1inp_sign_link *sign_link = mb->dst;
struct gsm_bts *bts = sign_link->trx->bts;
- struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ struct gsm_abis_mo *mo;
+ struct tlv_parsed tp;
- if (!trx)
+ if (!is_ipa_abisip_bts(bts))
+ return 0;
+
+ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
+ if (mo == NULL) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Rx SW activated report for non-existent MO\n");
+ return -ENOENT;
+ }
+
+ if (abis_nm_tlv_parse(&tp, bts, &foh->data[0], msgb_l3len(mb) - sizeof(*foh)) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
return -EINVAL;
+ }
- if (!is_ipaccess_bts(trx->bts))
- return 0;
+ mo->ipaccess.obj_version = 0; /* implicit default */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_IPACC_OBJ_VERSION, 1)) {
+ const uint8_t *versions = TLVP_VAL(&tp, NM_ATT_IPACC_OBJ_VERSION);
+ char buf[256];
+ struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
- switch (foh->obj_class) {
- case NM_OC_BASEB_TRANSC:
- abis_nm_chg_adm_state(trx->bts, foh->obj_class,
- trx->bts->bts_nr, trx->nr, 0xff,
- NM_STATE_UNLOCKED);
- abis_nm_opstart(trx->bts, foh->obj_class,
- trx->bts->bts_nr, trx->nr, 0xff);
- /* TRX software is active, tell it to initiate RSL Link */
- abis_nm_ipaccess_rsl_connect(trx, trx->bts->ip_access.rsl_ip,
- 3003, trx->rsl_tei);
- break;
- case NM_OC_RADIO_CARRIER: {
- /*
- * Locking the radio carrier will make it go
- * offline again and we would come here. The
- * framework should determine that there was
- * no change and avoid recursion.
- *
- * This code is here to make sure that on start
- * a TRX remains locked.
- */
- int rc_state = trx->mo.nm_state.administrative;
- /* Patch ARFCN into radio attribute */
- struct msgb *msgb = nanobts_attr_radio_get(trx->bts, trx);
- abis_nm_set_radio_attr(trx, msgb->data, msgb->len);
- msgb_free(msgb);
- abis_nm_chg_adm_state(trx->bts, foh->obj_class,
- trx->bts->bts_nr, trx->nr, 0xff,
- rc_state);
- abis_nm_opstart(trx->bts, foh->obj_class, trx->bts->bts_nr,
- trx->nr, 0xff);
- break;
- }
+ /* nanoBTS may report several Object Versions; the first one will
+ * be used by default unless requested explicitly before OPSTARTing. */
+ mo->ipaccess.obj_version = versions[0];
+
+ OSMO_STRBUF_PRINTF(sb, "%u (default)", versions[0]);
+ for (uint16_t i = 1; i < TLVP_LEN(&tp, NM_ATT_IPACC_OBJ_VERSION); i++)
+ OSMO_STRBUF_PRINTF(sb, ", %u", versions[i]);
+ LOGPFOH(DNM, LOGL_INFO, foh, "IPA Object Versions supported: %s\n", buf);
}
+
+ osmo_fsm_inst_dispatch(mo->fi, NM_EV_SW_ACT_REP, NULL);
return 0;
}
-static void nm_rx_opstart_ack_chan(struct msgb *oml_msg)
+static void nm_rx_opstart_ack(struct msgb *oml_msg)
{
- struct gsm_bts_trx_ts *ts;
- ts = abis_nm_get_ts(oml_msg);
- if (!ts)
- /* error already logged in abis_nm_get_ts() */
+ const struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
+ struct e1inp_sign_link *sign_link = oml_msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct gsm_abis_mo *mo;
+
+ if (!is_ipa_abisip_bts(bts))
return;
- if (!ts->fi) {
- LOG_TS(ts, LOGL_ERROR, "Channel OPSTART ACK for uninitialized TS\n");
+
+ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
+ if (mo == NULL)
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Rx OPSTART ACK for non-existent MO\n");
+ else
+ osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
+}
+
+static void nm_rx_opstart_nack(struct msgb *oml_msg)
+{
+ const struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
+ struct e1inp_sign_link *sign_link = oml_msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct gsm_abis_mo *mo;
+
+ if (!is_ipa_abisip_bts(bts))
+ return;
+
+ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
+ if (mo == NULL)
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Rx OPSTART NACK for non-existent MO\n");
+ else
+ osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_NACK, NULL);
+}
+
+static void nm_rx_get_attr_rep(struct msgb *oml_msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
+ struct e1inp_sign_link *sign_link = oml_msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct gsm_abis_mo *mo;
+
+ if (!is_ipa_abisip_bts(bts))
+ return;
+
+ mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
+ if (mo == NULL)
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Rx Get Attribute Report for non-existent MO\n");
+ else
+ osmo_fsm_inst_dispatch(mo->fi, NM_EV_GET_ATTR_REP, NULL);
+}
+
+static void nm_rx_set_bts_attr_ack(struct msgb *oml_msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
+ struct e1inp_sign_link *sign_link = oml_msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+
+ if (!is_ipa_abisip_bts(bts))
+ return;
+
+ if (foh->obj_class != NM_OC_BTS) {
+ LOG_BTS(bts, DNM, LOGL_ERROR, "Set BTS Attr Ack received on non BTS object!\n");
return;
}
+ osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
+}
+
+
+static void nm_rx_set_radio_attr_ack(struct msgb *oml_msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
+ struct e1inp_sign_link *sign_link = oml_msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct gsm_bts_trx *trx;
+
+ if (!is_ipa_abisip_bts(bts))
+ return;
- osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL);
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (!trx || foh->obj_class != NM_OC_RADIO_CARRIER) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Set Radio Carrier Attr Ack received on non Radio Carrier object!\n");
+ return;
+ }
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
}
-static void nm_rx_opstart_ack(struct msgb *oml_msg)
+static void nm_rx_set_chan_attr_ack(struct msgb *oml_msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
+ struct e1inp_sign_link *sign_link = oml_msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct gsm_bts_trx_ts *ts;
+
+ if (!is_ipa_abisip_bts(bts))
+ return;
+
+ ts = abis_nm_get_ts(oml_msg);
+ if (!ts || foh->obj_class != NM_OC_CHANNEL) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "Set Channel Attr Ack received on non Radio Channel object!\n");
+ return;
+ }
+ osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
+}
+
+static void nm_rx_ipacc_set_attr_ack(struct ipacc_ack_signal_data *sig_data)
+{
+ struct gsm_bts *bts = sig_data->bts;
+ struct abis_om_fom_hdr *foh = sig_data->foh;
+ void *obj;
+ struct gsm_gprs_nse *nse;
+ struct gsm_gprs_cell *cell;
+ struct gsm_gprs_nsvc *nsvc;
+
+ obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst);
+
switch (foh->obj_class) {
- case NM_OC_CHANNEL:
- nm_rx_opstart_ack_chan(oml_msg);
+ case NM_OC_GPRS_NSE:
+ nse = obj;
+ osmo_fsm_inst_dispatch(nse->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
+ break;
+ case NM_OC_GPRS_CELL:
+ cell = obj;
+ osmo_fsm_inst_dispatch(cell->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
+ break;
+ case NM_OC_GPRS_NSVC:
+ if (!(nsvc = gsm_bts_sm_nsvc_num(bts->site_mgr, foh->obj_inst.trx_nr)))
+ return;
+ osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
+ break;
+ default:
+ LOGPFOH(DNM, LOGL_ERROR, foh, "IPACC Set Attr Ack received on incorrect object class %d!\n", foh->obj_class);
+ }
+}
+
+static void nm_rx_ipacc_rsl_connect_ack(struct ipacc_ack_signal_data *sig_data)
+{
+ struct gsm_bts *bts = sig_data->bts;
+ struct abis_om_fom_hdr *foh = sig_data->foh;
+ struct gsm_bts_trx *trx;
+
+ if (foh->obj_class != NM_OC_BASEB_TRANSC) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "IPACC RSL Connect ACK received on incorrect object class %d!\n", foh->obj_class);
+ return;
+ }
+
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_RSL_CONNECT_ACK, NULL);
+}
+
+static void nm_rx_ipacc_ack(struct ipacc_ack_signal_data *sig_data)
+{
+ switch (sig_data->foh->msg_type) {
+ case NM_MT_IPACC_SET_ATTR_ACK:
+ nm_rx_ipacc_set_attr_ack(sig_data);
+ break;
+ case NM_MT_IPACC_RSL_CONNECT_ACK:
+ nm_rx_ipacc_rsl_connect_ack(sig_data);
+ break;
+ default:
+ break;
+ }
+}
+
+static void nm_rx_ipacc_rsl_connect_nack(struct ipacc_ack_signal_data *sig_data)
+{
+ struct gsm_bts *bts = sig_data->bts;
+ struct abis_om_fom_hdr *foh = sig_data->foh;
+ struct gsm_bts_trx *trx;
+
+ if (foh->obj_class != NM_OC_BASEB_TRANSC) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "IPACC RSL Connect NACK received on incorrect object class %d!\n", foh->obj_class);
+ return;
+ }
+
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_RSL_CONNECT_NACK, NULL);
+}
+
+static void nm_rx_ipacc_nack(struct ipacc_ack_signal_data *sig_data)
+{
+ switch (sig_data->foh->msg_type) {
+ case NM_MT_IPACC_RSL_CONNECT_ACK:
+ nm_rx_ipacc_rsl_connect_nack(sig_data);
break;
default:
break;
@@ -337,12 +434,32 @@ static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal,
switch (signal) {
case S_NM_SW_ACTIV_REP:
return sw_activ_rep(signal_data);
- case S_NM_STATECHG_OPER:
- case S_NM_STATECHG_ADM:
+ case S_NM_STATECHG:
return nm_statechg_event(signal, signal_data);
case S_NM_OPSTART_ACK:
nm_rx_opstart_ack(signal_data);
return 0;
+ case S_NM_OPSTART_NACK:
+ nm_rx_opstart_nack(signal_data);
+ return 0;
+ case S_NM_GET_ATTR_REP:
+ nm_rx_get_attr_rep(signal_data);
+ return 0;
+ case S_NM_SET_BTS_ATTR_ACK:
+ nm_rx_set_bts_attr_ack(signal_data);
+ return 0;
+ case S_NM_SET_RADIO_ATTR_ACK:
+ nm_rx_set_radio_attr_ack(signal_data);
+ return 0;
+ case S_NM_SET_CHAN_ATTR_ACK:
+ nm_rx_set_chan_attr_ack(signal_data);
+ return 0;
+ case S_NM_IPACC_ACK:
+ nm_rx_ipacc_ack(signal_data);
+ return 0;
+ case S_NM_IPACC_NACK:
+ nm_rx_ipacc_nack(signal_data);
+ return 0;
default:
break;
}
@@ -378,7 +495,7 @@ find_bts_by_unitid(struct gsm_network *net, uint16_t site_id, uint16_t bts_id)
struct gsm_bts *bts;
llist_for_each_entry(bts, &net->bts_list, list) {
- if (!is_ipaccess_bts(bts))
+ if (!is_ipa_abisip_bts(bts))
continue;
if (bts->ip_access.site_id == site_id &&
@@ -389,23 +506,29 @@ find_bts_by_unitid(struct gsm_network *net, uint16_t site_id, uint16_t bts_id)
}
/* These are exported because they are used by the VTY interface. */
-void ipaccess_drop_rsl(struct gsm_bts_trx *trx)
+void ipaccess_drop_rsl(struct gsm_bts_trx *trx, const char *reason)
{
- if (!trx->rsl_link)
+ if (!trx->rsl_link_primary)
return;
- LOGP(DLINP, LOGL_NOTICE, "(bts=%d,trx=%d) Dropping RSL link.\n", trx->bts->nr, trx->nr);
- e1inp_sign_link_destroy(trx->rsl_link);
- trx->rsl_link = NULL;
+ LOG_TRX(trx, DLINP, LOGL_NOTICE, "Dropping RSL link: %s\n", reason);
+ e1inp_sign_link_destroy(trx->rsl_link_primary);
+ trx->rsl_link_primary = NULL;
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(trx->bts->bts_statg, BTS_STAT_RSL_CONNECTED), 1);
if (trx->bts->c0 == trx)
paging_flush_bts(trx->bts, NULL);
}
-void ipaccess_drop_oml(struct gsm_bts *bts)
+void ipaccess_drop_oml(struct gsm_bts *bts, const char *reason)
{
struct gsm_bts *rdep_bts;
struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts ;
+ uint8_t tn;
+ uint8_t i;
+ struct timespec tp;
+ int rc;
/* First of all, remove deferred drop if enabled */
osmo_timer_del(&bts->oml_drop_link_timer);
@@ -413,19 +536,48 @@ void ipaccess_drop_oml(struct gsm_bts *bts)
if (!bts->oml_link)
return;
- LOGP(DLINP, LOGL_NOTICE, "(bts=%d) Dropping OML link.\n", bts->nr);
+ LOG_BTS(bts, DLINP, LOGL_NOTICE, "Dropping OML link: %s\n", reason);
e1inp_sign_link_destroy(bts->oml_link);
bts->oml_link = NULL;
- bts->uptime = 0;
+ rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
+ bts->updowntime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for downtime */
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_OML_CONNECTED), 1);
+ gsm_bts_stats_reset(bts);
+
+ /* Also drop the associated OSMO link */
+ OSMO_ASSERT(bts->osmo_link);
+ e1inp_sign_link_destroy(bts->osmo_link);
+ bts->osmo_link = NULL;
+
+ bts_setup_ramp_remove(bts);
/* we have issues reconnecting RSL, drop everything. */
- llist_for_each_entry(trx, &bts->trx_list, list)
- ipaccess_drop_rsl(trx);
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ ipaccess_drop_rsl(trx, "OML link drop");
+ osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_OML_DOWN, NULL);
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OML_DOWN, NULL);
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ ts = &trx->ts[tn];
+ osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_OML_DOWN, NULL);
+ }
+ }
- gsm_bts_all_ts_dispatch(bts, TS_EV_OML_DOWN, NULL);
+ osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OML_DOWN, NULL);
+ osmo_fsm_inst_dispatch(bts->gprs.cell.mo.fi, NM_EV_OML_DOWN, NULL);
+
+ osmo_fsm_inst_dispatch(bts->site_mgr->mo.fi, NM_EV_OML_DOWN, NULL);
+ osmo_fsm_inst_dispatch(bts->site_mgr->gprs.nse.mo.fi, NM_EV_OML_DOWN, NULL);
+ for (i = 0; i < ARRAY_SIZE(bts->site_mgr->gprs.nsvc); i++)
+ osmo_fsm_inst_dispatch(bts->site_mgr->gprs.nsvc[i].mo.fi, NM_EV_OML_DOWN, NULL);
bts->ip_access.flags = 0;
+ if (bts->model->features_get_reported) {
+ /* Reset the feature vector */
+ memset(bts->_features_data, 0, sizeof(bts->_features_data));
+ bts->features_known = false;
+ }
+
/*
* Go through the list and see if we are the depndency of a BTS
* and then drop the BTS. This can lead to some recursion but it
@@ -438,7 +590,7 @@ void ipaccess_drop_oml(struct gsm_bts *bts)
continue;
LOGP(DLINP, LOGL_NOTICE, "Dropping BTS(%u) due BTS(%u).\n",
rdep_bts->nr, bts->nr);
- ipaccess_drop_oml(rdep_bts);
+ ipaccess_drop_oml(rdep_bts, "Dependency link drop");
}
}
@@ -447,7 +599,7 @@ void ipaccess_drop_oml(struct gsm_bts *bts)
static void ipaccess_drop_oml_deferred_cb(void *data)
{
struct gsm_bts *bts = (struct gsm_bts *) data;
- ipaccess_drop_oml(bts);
+ ipaccess_drop_oml(bts, "Deferred link drop");
}
/*! Deferr \ref ipacces_drop_oml through a timer to avoid dropping structures in
* current code context. This may be needed if we want to destroy the OML link
@@ -459,7 +611,7 @@ static void ipaccess_drop_oml_deferred_cb(void *data)
void ipaccess_drop_oml_deferred(struct gsm_bts *bts)
{
if (!osmo_timer_pending(&bts->oml_drop_link_timer) && bts->oml_link) {
- LOGP(DLINP, LOGL_NOTICE, "(bts=%d) Deferring Drop of OML link.\n", bts->nr);
+ LOG_BTS(bts, DLINP, LOGL_NOTICE, "Deferring Drop of OML link.\n");
osmo_timer_setup(&bts->oml_drop_link_timer, ipaccess_drop_oml_deferred_cb, bts);
osmo_timer_schedule(&bts->oml_drop_link_timer, 0, 0);
}
@@ -478,7 +630,7 @@ static void ipaccess_sign_link_reject(const struct ipaccess_unit *dev, const str
/* Write to log and increase counter */
LOGP(DLINP, LOGL_ERROR, "Unable to find BTS configuration for %u/%u/%u, disconnecting\n", site_id, bts_id,
trx_id);
- rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_UNKNOWN_UNIT_ID]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_UNKNOWN_UNIT_ID));
/* Get remote IP */
if (osmo_sock_get_remote_ip(ts->driver.ipaccess.fd.fd, ip, sizeof(ip)))
@@ -525,19 +677,28 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line,
struct e1inp_sign_link *sign_link = NULL;
struct timespec tp;
int rc;
+ struct e1inp_ts *sign_ts = e1inp_line_ipa_oml_ts(line);
bts = find_bts_by_unitid(bsc_gsmnet, dev->site_id, dev->bts_id);
if (!bts) {
- ipaccess_sign_link_reject(dev, &line->ts[E1INP_SIGN_OML - 1]);
+ ipaccess_sign_link_reject(dev, sign_ts);
return NULL;
}
- DEBUGP(DLINP, "Identified BTS %u/%u/%u\n",
+ DEBUGP(DLINP, "%s: Identified BTS %u/%u/%u\n", e1inp_signtype_name(type),
dev->site_id, dev->bts_id, dev->trx_id);
+ /* Check if this BTS has a valid configuration. If not we will drop it
+ * immediately. */
+ if (gsm_bts_check_cfg(bts) != 0) {
+ LOGP(DLINP, LOGL_NOTICE, "(bts=%u) BTS config invalid, dropping BTS!\n", bts->nr);
+ ipaccess_drop_oml_deferred(bts);
+ return NULL;
+ }
+
switch(type) {
case E1INP_SIGN_OML:
/* remove old OML signal link for this BTS. */
- ipaccess_drop_oml(bts);
+ ipaccess_drop_oml(bts, "new OML link");
if (!bts_depend_check(bts)) {
LOGP(DLINP, LOGL_NOTICE,
@@ -548,16 +709,21 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line,
/* create new OML link. */
sign_link = bts->oml_link =
- e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML - 1],
+ e1inp_sign_link_create(sign_ts,
E1INP_SIGN_OML, bts->c0,
bts->oml_tei, 0);
- rc = clock_gettime(CLOCK_MONOTONIC, &tp);
- bts->uptime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */
+ rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
+ bts->updowntime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */
if (!(sign_link->trx->bts->ip_access.flags & OML_UP)) {
e1inp_event(sign_link->ts, S_L_INP_TEI_UP,
sign_link->tei, sign_link->sapi);
sign_link->trx->bts->ip_access.flags |= OML_UP;
}
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_OML_CONNECTED), 1);
+
+ /* Create link for E1INP_SIGN_OSMO */
+ //SAPI must be 0, no IPAC_PROTO_EXT_PCU, see ipaccess_bts_read_cb
+ bts->osmo_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OSMO, bts->c0, IPAC_PROTO_OSMO, 0);
break;
case E1INP_SIGN_RSL: {
struct e1inp_ts *ts;
@@ -568,16 +734,16 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line,
return NULL;
/* remove old RSL link for this TRX. */
- ipaccess_drop_rsl(trx);
+ ipaccess_drop_rsl(trx, "new RSL link");
/* set new RSL link for this TRX. */
line = bts->oml_link->ts->line;
- ts = &line->ts[E1INP_SIGN_RSL + dev->trx_id - 1];
+ ts = e1inp_line_ipa_rsl_ts(line, dev->trx_id);
e1inp_ts_config_sign(ts, line);
- sign_link = trx->rsl_link =
+ sign_link = trx->rsl_link_primary =
e1inp_sign_link_create(ts, E1INP_SIGN_RSL,
- trx, trx->rsl_tei, 0);
- trx->rsl_link->ts->sign.delay = 0;
+ trx, trx->rsl_tei_primary, 0);
+ trx->rsl_link_primary->ts->sign.delay = 0;
if (!(sign_link->trx->bts->ip_access.flags &
(RSL_UP << sign_link->trx->nr))) {
e1inp_event(sign_link->ts, S_L_INP_TEI_UP,
@@ -585,6 +751,7 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line,
sign_link->trx->bts->ip_access.flags |=
(RSL_UP << sign_link->trx->nr);
}
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_RSL_CONNECTED), 1);
break;
}
default:
@@ -596,10 +763,12 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line,
static void ipaccess_sign_link_down(struct e1inp_line *line)
{
/* No matter what link went down, we close both signal links. */
- struct e1inp_ts *ts = &line->ts[E1INP_SIGN_OML-1];
+ struct e1inp_ts *ts = e1inp_line_ipa_oml_ts(line);
struct gsm_bts *bts = NULL;
struct e1inp_sign_link *link;
+ LOGPIL(line, DLINP, LOGL_NOTICE, "Signalling link down\n");
+
llist_for_each_entry(link, &ts->sign.sign_links, list) {
/* Get bts pointer from the first element of the list. */
if (bts == NULL)
@@ -609,7 +778,9 @@ static void ipaccess_sign_link_down(struct e1inp_line *line)
osmo_timer_del(&link->trx->rsl_connect_timeout);
}
if (bts != NULL)
- ipaccess_drop_oml(bts);
+ ipaccess_drop_oml(bts, "link down");
+ else
+ LOGPIL(line, DLINP, LOGL_NOTICE, "Signalling link down for unknown BTS\n");
}
/* This function is called if we receive one OML/RSL message. */
@@ -625,6 +796,9 @@ static int ipaccess_sign_link(struct msgb *msg)
case E1INP_SIGN_OML:
ret = abis_nm_rcvmsg(msg);
break;
+ case E1INP_SIGN_OSMO:
+ ret = abis_osmo_rcvmsg(msg);
+ break;
default:
LOGP(DLINP, LOGL_ERROR, "Unknown signal link type %d\n",
link->type);
@@ -651,3 +825,153 @@ static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line)
{
e1inp_line_bind_ops(line, &ipaccess_e1inp_line_ops);
}
+
+static void enc_meas_proc_params(struct msgb *msg, uint8_t ptype,
+ const struct gsm_power_ctrl_meas_params *mp)
+{
+ struct ipac_preproc_ave_cfg *ave_cfg;
+ uint8_t *ie_len;
+
+ /* No averaging => no Measurement Averaging parameters */
+ if (mp->algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE)
+ return;
+
+ /* (TLV) Measurement Averaging parameters for RxLev/RxQual */
+ ie_len = msgb_tl_put(msg, RSL_IPAC_EIE_MEAS_AVG_CFG);
+
+ ave_cfg = (struct ipac_preproc_ave_cfg *) msgb_put(msg, sizeof(*ave_cfg));
+ ave_cfg->param_id = ptype & 0x03;
+
+ /* H_REQAVE and H_REQT */
+ ave_cfg->h_reqave = mp->h_reqave & 0x1f;
+ ave_cfg->h_reqt = mp->h_reqt & 0x1f;
+
+ /* Averaging method and parameters */
+ ave_cfg->ave_method = (mp->algo - 1) & 0x07;
+ switch (mp->algo) {
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA:
+ msgb_v_put(msg, mp->ewma.alpha);
+ break;
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED:
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN:
+ /* FIXME: unknown format */
+ break;
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED:
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE:
+ /* No parameters here */
+ break;
+ }
+
+ /* Update length part of the containing IE */
+ *ie_len = msg->tail - (ie_len + 1);
+}
+
+static void enc_power_params(struct msgb *msg, const struct gsm_power_ctrl_params *cp)
+{
+ struct ipac_preproc_pc_comp *thresh_comp;
+ struct ipac_preproc_pc_thresh *thresh;
+
+ /* These parameters are valid for dynamic mode only */
+ OSMO_ASSERT(cp->mode == GSM_PWR_CTRL_MODE_DYN_BTS);
+
+ /* (TLV) Measurement Averaging Configure */
+ enc_meas_proc_params(msg, IPAC_RXQUAL_AVE, &cp->rxqual_meas);
+ enc_meas_proc_params(msg, IPAC_RXLEV_AVE, &cp->rxlev_meas);
+
+ /* (TV) Thresholds: {L,U}_RXLEV_XX_P and {L,U}_RXQUAL_XX_P */
+ if (cp->dir == GSM_PWR_CTRL_DIR_UL)
+ msgb_v_put(msg, RSL_IPAC_EIE_MS_PWR_CTL);
+ else
+ msgb_v_put(msg, RSL_IPAC_EIE_BS_PWR_CTL);
+
+ thresh = (struct ipac_preproc_pc_thresh *) msgb_put(msg, sizeof(*thresh));
+
+ /* {L,U}_RXLEV_XX_P (see 3GPP TS 45.008, A.3.2.1, a & b) */
+ thresh->l_rxlev = cp->rxlev_meas.lower_thresh & 0x3f;
+ thresh->u_rxlev = cp->rxlev_meas.upper_thresh & 0x3f;
+
+ /* {L,U}_RXQUAL_XX_P (see 3GPP TS 45.008, A.3.2.1, c & d) */
+ thresh->l_rxqual = cp->rxqual_meas.lower_thresh & 0x07;
+ thresh->u_rxqual = cp->rxqual_meas.upper_thresh & 0x07;
+
+ /* (TV) PC Threshold Comparators */
+ msgb_v_put(msg, RSL_IPAC_EIE_PC_THRESH_COMP);
+
+ thresh_comp = (struct ipac_preproc_pc_comp *) msgb_put(msg, sizeof(*thresh_comp));
+
+ /* RxLev: P1, N1, P2, N2 (see 3GPP TS 45.008, A.3.2.1, a & b) */
+ thresh_comp->p1 = cp->rxlev_meas.lower_cmp_p & 0x1f;
+ thresh_comp->n1 = cp->rxlev_meas.lower_cmp_n & 0x1f;
+ thresh_comp->p2 = cp->rxlev_meas.upper_cmp_p & 0x1f;
+ thresh_comp->n2 = cp->rxlev_meas.upper_cmp_n & 0x1f;
+
+ /* RxQual: P3, N3, P4, N4 (see 3GPP TS 45.008, A.3.2.1, c & d) */
+ thresh_comp->p3 = cp->rxqual_meas.lower_cmp_p & 0x1f;
+ thresh_comp->n3 = cp->rxqual_meas.lower_cmp_n & 0x1f;
+ thresh_comp->p4 = cp->rxqual_meas.upper_cmp_p & 0x1f;
+ thresh_comp->n4 = cp->rxqual_meas.upper_cmp_n & 0x1f;
+
+ /* Minimum interval between power level changes (P_CON_INTERVAL) */
+ thresh_comp->pc_interval = cp->ctrl_interval;
+
+ /* Change step limitations: POWER_{INC,RED}_STEP_SIZE */
+ thresh_comp->inc_step_size = cp->inc_step_size_db & 0x0f;
+ thresh_comp->red_step_size = cp->red_step_size_db & 0x0f;
+}
+
+void osmobts_enc_power_params_osmo_ext(struct msgb *msg, const struct gsm_power_ctrl_params *cp);
+static void add_power_params_ie(struct msgb *msg, enum abis_rsl_ie iei,
+ const struct gsm_bts_trx *trx,
+ const struct gsm_power_ctrl_params *cp)
+{
+ uint8_t *ie_len = msgb_tl_put(msg, iei);
+ uint8_t msg_len = msgb_length(msg);
+
+ enc_power_params(msg, cp);
+ if (iei == RSL_IE_MS_POWER_PARAM && is_osmobts(trx->bts))
+ osmobts_enc_power_params_osmo_ext(msg, cp);
+
+ *ie_len = msgb_length(msg) - msg_len;
+}
+
+static int power_ctrl_send_def_params(const struct gsm_bts_trx *trx)
+{
+ const struct gsm_power_ctrl_params *ms_power_ctrl = &trx->bts->ms_power_ctrl;
+ const struct gsm_power_ctrl_params *bs_power_ctrl = &trx->bts->bs_power_ctrl;
+ struct abis_rsl_common_hdr *ch;
+ struct msgb *msg;
+
+ /* Sending this message does not make sense if neither MS Power control
+ * nor BS Power control is to be performed by the BTS itself ('dyn-bts'). */
+ if (ms_power_ctrl->mode != GSM_PWR_CTRL_MODE_DYN_BTS &&
+ bs_power_ctrl->mode != GSM_PWR_CTRL_MODE_DYN_BTS)
+ return 0;
+
+ msg = rsl_msgb_alloc();
+ if (msg == NULL)
+ return -ENOMEM;
+
+ ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+ ch->msg_discr = ABIS_RSL_MDISC_TRX;
+ ch->msg_type = RSL_MT_IPAC_MEAS_PREPROC_DFT;
+
+ /* BS/MS Power IEs (to be re-defined in channel specific messages) */
+ msgb_tv_put(msg, RSL_IE_MS_POWER, 0); /* dummy value */
+ msgb_tv_put(msg, RSL_IE_BS_POWER, 0); /* dummy value */
+
+ /* MS/BS Power Parameters IEs */
+ if (ms_power_ctrl->mode == GSM_PWR_CTRL_MODE_DYN_BTS)
+ add_power_params_ie(msg, RSL_IE_MS_POWER_PARAM, trx, ms_power_ctrl);
+ if (bs_power_ctrl->mode == GSM_PWR_CTRL_MODE_DYN_BTS)
+ add_power_params_ie(msg, RSL_IE_BS_POWER_PARAM, trx, bs_power_ctrl);
+
+ msg->dst = trx->rsl_link_primary;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+static int power_ctrl_enc_rsl_params(struct msgb *msg, const struct gsm_power_ctrl_params *cp)
+{
+ /* We send everything in "Measurement Pre-processing Defaults" */
+ return 0;
+}
diff --git a/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c b/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c
index d674c1891..23196fcee 100644
--- a/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c
+++ b/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c
@@ -1,6 +1,6 @@
/* ip.access nanoBTS specific code, OML attribute table generator */
-/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+/* (C) 2016-2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* Author: Philipp Maier
@@ -23,31 +23,95 @@
#include <osmocom/core/msgb.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/abis_nm.h>
-#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/gsm/bts_features.h>
+
+const struct tlv_definition ipacc_eie_tlv_def = {
+ .def = {
+ /* TODO: add more values from enum ipac_eie */
+ [NM_IPAC_EIE_FREQ_BANDS] = { TLV_TYPE_TL16V },
+ [NM_IPAC_EIE_MAX_TA] = { TLV_TYPE_TL16V },
+ [NM_IPAC_EIE_CIPH_ALGOS] = { TLV_TYPE_TL16V },
+ [NM_IPAC_EIE_CHAN_TYPES] = { TLV_TYPE_TL16V },
+ [NM_IPAC_EIE_CHAN_MODES] = { TLV_TYPE_TL16V },
+ [NM_IPAC_EIE_GPRS_CODING] = { TLV_TYPE_TL16V },
+ [NM_IPAC_EIE_RTP_FEATURES] = { TLV_TYPE_TL16V },
+ [NM_IPAC_EIE_RSL_FEATURES] = { TLV_TYPE_TL16V },
+ }
+};
-static void patch_16(uint8_t *data, const uint16_t val)
+static inline uint32_t ipacc_parse_supp_flags(const struct abis_om_fom_hdr *foh,
+ const struct value_string *flags,
+ const struct tlv_p_entry *e,
+ const char *text)
{
- memcpy(data, &val, sizeof(val));
+ uint32_t u32 = 0;
+
+ for (unsigned int i = 0; i < OSMO_MAX(e->len, 4); i++)
+ u32 |= e->val[i] << (i * 8);
+ for (const struct value_string *vs = flags; vs->value && vs->str; vs++) {
+ if (u32 & vs->value)
+ LOGPFOH(DNM, LOGL_INFO, foh, "%s '%s' is supported\n", text, vs->str);
+ }
+
+ return u32;
}
-static void patch_32(uint8_t *data, const uint32_t val)
+/* Parse ip.access Supported Features IE */
+int ipacc_parse_supp_features(const struct gsm_bts *bts,
+ const struct abis_om_fom_hdr *foh,
+ const uint8_t *data, uint16_t data_len)
{
- memcpy(data, &val, sizeof(val));
+ const struct tlv_p_entry *e;
+ struct tlv_parsed tp;
+
+ if (tlv_parse(&tp, &ipacc_eie_tlv_def, data, data_len, 0, 0) < 0) {
+ LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
+ return -EINVAL;
+ }
+
+ /* TODO: store the flags in the respective MO state */
+ if ((e = TLVP_GET(&tp, NM_IPAC_EIE_FREQ_BANDS)) != NULL)
+ ipacc_parse_supp_flags(foh, abis_nm_ipacc_freq_band_desc, e, "Freq. band");
+ if ((e = TLVP_GET(&tp, NM_IPAC_EIE_CIPH_ALGOS)) != NULL)
+ ipacc_parse_supp_flags(foh, abis_nm_ipacc_ciph_algo_desc, e, "Ciphering algorithm");
+ if ((e = TLVP_GET(&tp, NM_IPAC_EIE_CHAN_TYPES)) != NULL)
+ ipacc_parse_supp_flags(foh, abis_nm_ipacc_chant_desc, e, "Channel type");
+ if ((e = TLVP_GET(&tp, NM_IPAC_EIE_CHAN_MODES)) != NULL)
+ ipacc_parse_supp_flags(foh, abis_nm_ipacc_chanm_desc, e, "Channel mode");
+ if ((e = TLVP_GET(&tp, NM_IPAC_EIE_GPRS_CODING)) != NULL)
+ ipacc_parse_supp_flags(foh, abis_nm_ipacc_gprs_coding_desc, e, "GPRS Coding Scheme");
+ if ((e = TLVP_GET(&tp, NM_IPAC_EIE_RTP_FEATURES)) != NULL)
+ ipacc_parse_supp_flags(foh, abis_nm_ipacc_rtp_feat_desc, e, "RTP Feature");
+ if ((e = TLVP_GET(&tp, NM_IPAC_EIE_RSL_FEATURES)) != NULL)
+ ipacc_parse_supp_flags(foh, abis_nm_ipacc_rsl_feat_desc, e, "RSL Feature");
+ if (TLVP_PRES_LEN(&tp, NM_IPAC_EIE_MAX_TA, 1)) {
+ uint8_t u8 = *TLVP_VAL(&tp, NM_IPAC_EIE_MAX_TA);
+ LOGPFOH(DNM, LOGL_DEBUG, foh, "Max Timing Advance %u\n", u8);
+ }
+
+ return 0;
}
-struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts)
+/* 3GPP TS 52.021 section 8.6.1 Set BTS Attributes */
+struct msgb *nanobts_gen_set_bts_attr(struct gsm_bts *bts)
{
struct msgb *msgb;
uint8_t buf[256];
int rlt;
- msgb = msgb_alloc(1024, "nanobts_attr_bts");
- memcpy(buf, "\x55\x5b\x61\x67\x6d\x73", 6);
- msgb_tv_fixed_put(msgb, NM_ATT_INTERF_BOUND, 6, buf);
+ msgb = msgb_alloc(1024, __func__);
+ if (!msgb)
+ return NULL;
- /* interference avg. period in numbers of SACCH multifr */
- msgb_tv_put(msgb, NM_ATT_INTAVE_PARAM, 0x06);
+ /* Interference level Boundaries: 0 .. X5 (3GPP TS 52.021 sec 9.4.25) */
+ msgb_tv_fixed_put(msgb, NM_ATT_INTERF_BOUND,
+ sizeof(bts->interf_meas_params_cfg.bounds_dbm),
+ &bts->interf_meas_params_cfg.bounds_dbm[0]);
+ /* Intave: Interference Averaging period (3GPP TS 52.021 sec 9.4.24) */
+ msgb_tv_put(msgb, NM_ATT_INTAVE_PARAM, bts->interf_meas_params_cfg.avg_period);
+ /* Connection Failure Criterion (3GPP TS 52.021 sec 9.4.14) */
rlt = gsm_bts_get_radio_link_timeout(bts);
if (rlt == -1) {
/* Osmocom extension: Use infinite radio link timeout */
@@ -60,28 +124,30 @@ struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts)
}
msgb_tl16v_put(msgb, NM_ATT_CONN_FAIL_CRIT, 2, buf);
+ /* T200 (3GPP TS 52.021 sec 9.4.53) */
memcpy(buf, "\x1e\x24\x24\xa8\x34\x21\xa8", 7);
msgb_tv_fixed_put(msgb, NM_ATT_T200, 7, buf);
+ /* Max Timing Advance (3GPP TS 52.021 sec 9.4.31) */
msgb_tv_put(msgb, NM_ATT_MAX_TA, 0x3f);
- /* seconds */
+ /* Overload Period (3GPP TS 52.021 sec 9.4.39), seconds */
memcpy(buf, "\x00\x01\x0a", 3);
msgb_tv_fixed_put(msgb, NM_ATT_OVERL_PERIOD, 3, buf);
- /* percent */
- msgb_tv_put(msgb, NM_ATT_CCCH_L_T, 10);
+ /* CCCH Load Threshold (3GPP TS 12.21 sec 9.4.12), percent */
+ msgb_tv_put(msgb, NM_ATT_CCCH_L_T, bts->ccch_load_ind_thresh);
- /* seconds */
- msgb_tv_put(msgb, NM_ATT_CCCH_L_I_P, 1);
+ /* CCCH Load Indication Period (3GPP TS 12.21 sec 9.4.11), seconds */
+ msgb_tv_put(msgb, NM_ATT_CCCH_L_I_P, bts->ccch_load_ind_period);
- /* busy threshold in - dBm */
+ /* RACH Busy Threshold (3GPP TS 12.21 sec 9.4.44), -dBm */
buf[0] = 90; /* -90 dBm as default "busy" threshold */
if (bts->rach_b_thresh != -1)
buf[0] = bts->rach_b_thresh & 0xff;
msgb_tv_put(msgb, NM_ATT_RACH_B_THRESH, buf[0]);
- /* rach load averaging 1000 slots */
+ /* RACH Load Averaging Slots (3GPP TS 12.21 sec 9.4.45), 1000 slots */
buf[0] = 0x03;
buf[1] = 0xe8;
if (bts->rach_ldavg_slots != -1) {
@@ -90,16 +156,19 @@ struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts)
}
msgb_tv_fixed_put(msgb, NM_ATT_LDAVG_SLOTS, 2, buf);
- /* 10 milliseconds */
- msgb_tv_put(msgb, NM_ATT_BTS_AIR_TIMER, T_def_get(bts->network->T_defs, 3105, T_MS, -1));
+ /* BTS Air Timer (3GPP TS 12.21 sec 9.4.10), 10 milliseconds */
+ msgb_tv_put(msgb, NM_ATT_BTS_AIR_TIMER, osmo_tdef_get(bts->network->T_defs, 3105, OSMO_TDEF_MS, -1)/10);
- /* 10 retransmissions of physical config */
- msgb_tv_put(msgb, NM_ATT_NY1, 10);
+ /* NY1 (3GPP TS 12.21 sec 9.4.37), number of retransmissions of physical config */
+ gsm_bts_check_ny1(bts);
+ msgb_tv_put(msgb, NM_ATT_NY1, osmo_tdef_get(bts->network->T_defs, -3105, OSMO_TDEF_CUSTOM, -1));
+ /* BCCH ARFCN (3GPP TS 12.21 sec 9.4.8) */
buf[0] = (bts->c0->arfcn >> 8) & 0x0f;
buf[1] = bts->c0->arfcn & 0xff;
msgb_tv_fixed_put(msgb, NM_ATT_BCCH_ARFCN, 2, buf);
+ /* BSIC (3GPP TS 12.21 sec 9.4.9) */
msgb_tv_put(msgb, NM_ATT_BSIC, bts->bsic);
abis_nm_ipaccess_cgi(buf, bts);
@@ -108,127 +177,209 @@ struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts)
return msgb;
}
-struct msgb *nanobts_attr_nse_get(struct gsm_bts *bts)
+struct msgb *nanobts_gen_set_nse_attr(struct gsm_bts_sm *bts_sm)
{
struct msgb *msgb;
- uint8_t buf[256];
- msgb = msgb_alloc(1024, "nanobts_attr_bts");
+ uint8_t buf[2];
+ struct abis_nm_ipacc_att_ns_cfg ns_cfg;
+ struct abis_nm_ipacc_att_bssgp_cfg bssgp_cfg;
+ struct gsm_bts *bts = gsm_bts_sm_get_bts(bts_sm);
+
+ msgb = msgb_alloc(1024, __func__);
+ if (!msgb)
+ return NULL;
/* NSEI 925 */
- buf[0] = bts->gprs.nse.nsei >> 8;
- buf[1] = bts->gprs.nse.nsei & 0xff;
+ buf[0] = bts_sm->gprs.nse.nsei >> 8;
+ buf[1] = bts_sm->gprs.nse.nsei & 0xff;
msgb_tl16v_put(msgb, NM_ATT_IPACC_NSEI, 2, buf);
- /* all timers in seconds */
- OSMO_ASSERT(ARRAY_SIZE(bts->gprs.nse.timer) < sizeof(buf));
- memcpy(buf, bts->gprs.nse.timer, ARRAY_SIZE(bts->gprs.nse.timer));
- msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_CFG, 7, buf);
-
- /* all timers in seconds */
- buf[0] = 3; /* blockimg timer (T1) */
- buf[1] = 3; /* blocking retries */
- buf[2] = 3; /* unblocking retries */
- buf[3] = 3; /* reset timer (T2) */
- buf[4] = 3; /* reset retries */
- buf[5] = 10; /* suspend timer (T3) in 100ms */
- buf[6] = 3; /* suspend retries */
- buf[7] = 10; /* resume timer (T4) in 100ms */
- buf[8] = 3; /* resume retries */
- buf[9] = 10; /* capability update timer (T5) */
- buf[10] = 3; /* capability update retries */
-
- OSMO_ASSERT(ARRAY_SIZE(bts->gprs.cell.timer) < sizeof(buf));
- memcpy(buf, bts->gprs.cell.timer, ARRAY_SIZE(bts->gprs.cell.timer));
- msgb_tl16v_put(msgb, NM_ATT_IPACC_BSSGP_CFG, 11, buf);
+ osmo_static_assert(ARRAY_SIZE(bts_sm->gprs.nse.timer) == 7, nse_timer_array_wrong_size);
+ ns_cfg = (struct abis_nm_ipacc_att_ns_cfg){
+ .un_blocking_timer = bts_sm->gprs.nse.timer[0],
+ .un_blocking_retries = bts_sm->gprs.nse.timer[1],
+ .reset_timer = bts_sm->gprs.nse.timer[2],
+ .reset_retries = bts_sm->gprs.nse.timer[3],
+ .test_timer = bts_sm->gprs.nse.timer[4],
+ .alive_timer = bts_sm->gprs.nse.timer[5],
+ .alive_retries = bts_sm->gprs.nse.timer[6],
+ };
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_CFG, sizeof(ns_cfg), (const uint8_t *)&ns_cfg);
+
+ osmo_static_assert(ARRAY_SIZE(bts->gprs.cell.timer) == 11, cell_timer_array_wrong_size);
+ bssgp_cfg = (struct abis_nm_ipacc_att_bssgp_cfg){
+ .t1_s = bts->gprs.cell.timer[0],
+ .t1_blocking_retries = bts->gprs.cell.timer[1],
+ .t1_unblocking_retries = bts->gprs.cell.timer[2],
+ .t2_s = bts->gprs.cell.timer[3],
+ .t2_retries = bts->gprs.cell.timer[4],
+ .t3_100ms = bts->gprs.cell.timer[5],
+ .t3_retries = bts->gprs.cell.timer[6],
+ .t4_100ms = bts->gprs.cell.timer[7],
+ .t4_retries = bts->gprs.cell.timer[8],
+ .t5_s = bts->gprs.cell.timer[9],
+ .t5_retries = bts->gprs.cell.timer[10],
+ };
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_BSSGP_CFG, sizeof(bssgp_cfg), (const uint8_t *)&bssgp_cfg);
return msgb;
}
-struct msgb *nanobts_attr_cell_get(struct gsm_bts *bts)
+struct msgb *nanobts_gen_set_cell_attr(struct gsm_bts *bts)
{
+ const struct gsm_gprs_cell *cell = &bts->gprs.cell;
+ const struct gprs_rlc_cfg *rlcc = &cell->rlc_cfg;
struct msgb *msgb;
- uint8_t buf[256];
- msgb = msgb_alloc(1024, "nanobts_attr_bts");
+ uint8_t buf[2];
+
+ msgb = msgb_alloc(1024, __func__);
+ if (!msgb)
+ return NULL;
/* routing area code */
buf[0] = bts->gprs.rac;
msgb_tl16v_put(msgb, NM_ATT_IPACC_RAC, 1, buf);
- buf[0] = 5; /* repeat time (50ms) */
- buf[1] = 3; /* repeat count */
+ buf[0] = rlcc->paging.repeat_time / 50; /* units of 50ms */
+ buf[1] = rlcc->paging.repeat_count;
msgb_tl16v_put(msgb, NM_ATT_IPACC_GPRS_PAGING_CFG, 2, buf);
/* BVCI 925 */
- buf[0] = bts->gprs.cell.bvci >> 8;
- buf[1] = bts->gprs.cell.bvci & 0xff;
+ buf[0] = cell->bvci >> 8;
+ buf[1] = cell->bvci & 0xff;
msgb_tl16v_put(msgb, NM_ATT_IPACC_BVCI, 2, buf);
/* all timers in seconds, unless otherwise stated */
- buf[0] = 20; /* T3142 */
- buf[1] = 5; /* T3169 */
- buf[2] = 5; /* T3191 */
- buf[3] = 160; /* T3193 (units of 10ms) */
- buf[4] = 5; /* T3195 */
- buf[5] = 10; /* N3101 */
- buf[6] = 4; /* N3103 */
- buf[7] = 8; /* N3105 */
- buf[8] = 15; /* RLC CV countdown */
- msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG, 9, buf);
-
- if (bts->gprs.mode == BTS_GPRS_EGPRS) {
- buf[0] = 0x8f;
- buf[1] = 0xff;
- } else {
- buf[0] = 0x0f;
+ const struct abis_nm_ipacc_att_rlc_cfg rlc_cfg = {
+ .t3142 = rlcc->parameter[RLC_T3142],
+ .t3169 = rlcc->parameter[RLC_T3169],
+ .t3191 = rlcc->parameter[RLC_T3191],
+ .t3193_10ms = rlcc->parameter[RLC_T3193],
+ .t3195 = rlcc->parameter[RLC_T3195],
+ .n3101 = rlcc->parameter[RLC_N3101],
+ .n3103 = rlcc->parameter[RLC_N3103],
+ .n3105 = rlcc->parameter[RLC_N3105],
+ .rlc_cv_countdown = rlcc->parameter[CV_COUNTDOWN],
+ };
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG, sizeof(rlc_cfg), (const uint8_t *)&rlc_cfg);
+
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ if (cell->mo.ipaccess.obj_version < 4)
+ break;
+ /* fall-through */
+ case GSM_BTS_TYPE_OSMOBTS:
+ /* CS1..CS4 flags encoded in the first octet */
+ buf[0] = rlcc->cs_mask & 0x0f;
+ /* MCS1..MSC8 flags encoded in the second octet */
buf[1] = 0x00;
+ if (bts->gprs.mode == BTS_GPRS_EGPRS) {
+ /* MSC9 is special and also goes to the first octet */
+ if (rlcc->cs_mask & (1 << GPRS_MCS9))
+ buf[0] |= (1 << 7);
+ buf[1] = (rlcc->cs_mask >> 4) & 0xff;
+ }
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_CODING_SCHEMES, 2, buf);
+ break;
+ default:
+ break;
+ }
+
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ if (cell->mo.ipaccess.obj_version < 20)
+ break;
+ /* fall-through */
+ case GSM_BTS_TYPE_OSMOBTS:
+ {
+ const struct abis_nm_ipacc_att_rlc_cfg_2 rlc_cfg_2 = {
+ .t_dl_tbf_ext_10ms = htons(rlcc->parameter[T_DL_TBF_EXT] / 10),
+ .t_ul_tbf_ext_10ms = htons(rlcc->parameter[T_UL_TBF_EXT] / 10),
+ .initial_cs = rlcc->initial_cs,
+ };
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_2,
+ sizeof(rlc_cfg_2), (const uint8_t *)&rlc_cfg_2);
+ break;
+ }
+ default:
+ break;
+ }
+
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ if (cell->mo.ipaccess.obj_version < 30)
+ break;
+ /* fall-through */
+ case GSM_BTS_TYPE_OSMOBTS:
+ {
+ const struct abis_nm_ipacc_att_rlc_cfg_3 rlc_cfg_3 = {
+ .initial_mcs = rlcc->initial_mcs,
+ };
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_3,
+ sizeof(rlc_cfg_3), (const uint8_t *)&rlc_cfg_3);
+ break;
+ }
+ default:
+ break;
}
- msgb_tl16v_put(msgb, NM_ATT_IPACC_CODING_SCHEMES, 2, buf);
-
- buf[0] = 0; /* T downlink TBF extension (0..500, high byte) */
- buf[1] = 250; /* T downlink TBF extension (0..500, low byte) */
- buf[2] = 0; /* T uplink TBF extension (0..500, high byte) */
- buf[3] = 250; /* T uplink TBF extension (0..500, low byte) */
- buf[4] = 2; /* CS2 */
- msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_2, 5, buf);
-
-#if 0
- /* EDGE model only, breaks older models.
- * Should inquire the BTS capabilities */
- buf[0] = 2; /* MCS2 */
- msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_3, 1, buf);
-#endif
return msgb;
}
-struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts)
+struct msgb *nanobts_gen_set_nsvc_attr(struct gsm_gprs_nsvc *nsvc)
{
struct msgb *msgb;
uint8_t buf[256];
- msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+ msgb = msgb_alloc(1024, __func__);
+ if (!msgb)
+ return NULL;
/* 925 */
- buf[0] = bts->gprs.nsvc[0].nsvci >> 8;
- buf[1] = bts->gprs.nsvc[0].nsvci & 0xff;
+ buf[0] = nsvc->nsvci >> 8;
+ buf[1] = nsvc->nsvci & 0xff;
msgb_tl16v_put(msgb, NM_ATT_IPACC_NSVCI, 2, buf);
- /* remote udp port */
- patch_16(&buf[0], htons(bts->gprs.nsvc[0].remote_port));
- /* remote ip address */
- patch_32(&buf[2], htonl(bts->gprs.nsvc[0].remote_ip));
- /* local udp port */
- patch_16(&buf[6], htons(bts->gprs.nsvc[0].local_port));
- msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_LINK_CFG, 8, buf);
+ switch (nsvc->remote.u.sa.sa_family) {
+ case AF_INET6:
+ /* all fields are encoded in network byte order */
+ /* protocol family */
+ buf[0] = OSMO_NSVC_ADDR_IPV6;
+ /* padding */
+ buf[1] = 0x00;
+ /* local udp port */
+ osmo_store16be(nsvc->local_port, &buf[2]);
+ /* remote udp port */
+ memcpy(&buf[4], &nsvc->remote.u.sin6.sin6_port, sizeof(uint16_t));
+ /* remote ip address */
+ memcpy(&buf[6], &nsvc->remote.u.sin6.sin6_addr, sizeof(struct in6_addr));
+ msgb_tl16v_put(msgb, NM_ATT_OSMO_NS_LINK_CFG, 6 + sizeof(struct in6_addr), buf);
+ break;
+ case AF_INET:
+ /* remote udp port */
+ memcpy(&buf[0], &nsvc->remote.u.sin.sin_port, sizeof(uint16_t));
+ /* remote ip address */
+ memcpy(&buf[2], &nsvc->remote.u.sin.sin_addr, sizeof(struct in_addr));
+ /* local udp port */
+ osmo_store16be(nsvc->local_port, &buf[6]);
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_LINK_CFG, 8, buf);
+ break;
+ default:
+ break;
+ }
return msgb;
}
-struct msgb *nanobts_attr_radio_get(struct gsm_bts *bts,
+struct msgb *nanobts_gen_set_radio_attr(struct gsm_bts *bts,
struct gsm_bts_trx *trx)
{
struct msgb *msgb;
uint8_t buf[256];
- msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+ msgb = msgb_alloc(1024, __func__);
+ if (!msgb)
+ return NULL;
/* number of -2dB reduction steps / Pn */
msgb_tv_put(msgb, NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2);
diff --git a/src/osmo-bsc/bts_nokia_site.c b/src/osmo-bsc/bts_nokia_site.c
index 66972c2b5..dc8ff1495 100644
--- a/src/osmo-bsc/bts_nokia_site.c
+++ b/src/osmo-bsc/bts_nokia_site.c
@@ -36,16 +36,22 @@
#include <osmocom/abis/e1_input.h>
#include <osmocom/bsc/signal.h>
#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/core/timer.h>
#include <osmocom/abis/lapd.h>
+enum reset_timer_state {
+ RESET_T_NONE = 0,
+ RESET_T_STOP_LAPD = 1, /* first timer expiration: stop LAPD SAP */
+ RESET_T_RESTART_LAPD = 2, /* second timer expiration: restart LAPD SAP */
+};
+
/* TODO: put in a separate file ? */
extern int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg);
-/* was static in system_information.c */
-extern int generate_cell_chan_list(uint8_t * chan_list, struct gsm_bts *bts);
static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts);
static void reset_timer_cb(void *_bts);
@@ -54,7 +60,7 @@ static int dump_elements(uint8_t * data, int len) __attribute__((unused));
static void bootstrap_om_bts(struct gsm_bts *bts)
{
- LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+ LOG_BTS(bts, DNM, LOGL_NOTICE, "bootstrapping OML\n");
if (!bts->nokia.skip_reset) {
if (!bts->nokia.did_reset)
@@ -67,14 +73,14 @@ static void bootstrap_om_bts(struct gsm_bts *bts)
static void bootstrap_om_trx(struct gsm_bts_trx *trx)
{
- LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for TRX %u/%u\n",
- trx->bts->nr, trx->nr);
+ LOG_TRX(trx, DNM, LOGL_NOTICE, "bootstrapping OML\n");
gsm_trx_all_ts_dispatch(trx, TS_EV_OML_READY, NULL);
}
static int shutdown_om(struct gsm_bts *bts)
{
+ gsm_bts_stats_reset(bts);
/* TODO !? */
return 0;
}
@@ -198,8 +204,7 @@ static int nm_sig_cb(unsigned int subsys, unsigned int signal,
return 0;
switch (signal) {
- case S_NM_STATECHG_OPER:
- case S_NM_STATECHG_ADM:
+ case S_NM_STATECHG:
nm_statechg_evt(signal, signal_data);
break;
default:
@@ -757,7 +762,7 @@ static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id,
/* set CA */
if (generate_cell_chan_list(&fu_config[38], trx->bts) != 0) {
- fprintf(stderr, "generate_cell_chan_list failed\n");
+ LOG_TRX(trx, DNM, LOGL_ERROR, "generate_cell_chan_list failed\n");
return 0;
}
@@ -792,6 +797,9 @@ static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id,
case GSM_PCHAN_CCCH_SDCCH4:
chan_config = 1;
break;
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ chan_config = 3;
+ break;
case GSM_PCHAN_TCH_F:
chan_config = 6; /* 9 should work too */
break;
@@ -801,12 +809,14 @@ static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id,
case GSM_PCHAN_SDCCH8_SACCH8C:
chan_config = 4;
break;
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ chan_config = 5;
+ break;
case GSM_PCHAN_PDCH:
chan_config = 11;
break;
default:
- fprintf(stderr,
- "unsupported channel config %s for timeslot %d\n",
+ LOG_TRX(trx, DNM, LOGL_ERROR, "unsupported channel config %s for timeslot %d\n",
gsm_pchan_name(ts->pchan_from_config), i);
return 0;
}
@@ -995,17 +1005,17 @@ void set_real_time(uint8_t * real_time)
build the configuration data
*/
-static int make_bts_config(uint8_t bts_type, int n_trx, uint8_t * fu_config,
+static int make_bts_config(struct gsm_bts *bts, uint8_t bts_type, int n_trx, uint8_t * fu_config,
int need_hopping)
{
/* is it an InSite BTS ? */
if (bts_type == 0x0E || bts_type == 0x0F || bts_type == 0x10) { /* TODO */
if (n_trx != 1) {
- fprintf(stderr, "InSite has only one TRX\n");
+ LOG_BTS(bts, DNM, LOGL_ERROR, "InSite has only one TRX\n");
return 0;
}
if (need_hopping != 0) {
- fprintf(stderr, "InSite does not support hopping\n");
+ LOG_BTS(bts, DNM, LOGL_ERROR, "InSite does not support hopping\n");
return 0;
}
memcpy(fu_config, bts_config_insite, sizeof(bts_config_insite));
@@ -1085,7 +1095,7 @@ static int abis_nm_send(struct gsm_bts *bts, uint8_t msg_type, uint16_t ref,
noh->reference = htons(ref);
memcpy(noh->data, data, len_data);
- DEBUGPC(DNM, "Sending %s\n", get_msg_type_name_string(msg_type));
+ LOG_BTS(bts, DNM, LOGL_DEBUG, "Sending %s\n", get_msg_type_name_string(msg_type));
return abis_nm_sendmsg(bts, msg);
}
@@ -1160,7 +1170,7 @@ static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref)
{
uint8_t *data = reset;
int len_data = sizeof(reset);
- LOGP(DLINP, LOGL_INFO, "Nokia BTS reset timer: %d\n", bts->nokia.bts_reset_timer_cnf);
+ LOG_BTS(bts, DLINP, LOGL_INFO, "Nokia BTS reset timer: %d\n", bts->nokia.bts_reset_timer_cnf);
return abis_nm_send(bts, NOKIA_MSG_RESET_REQ, ref, data, len_data);
}
@@ -1247,7 +1257,7 @@ static int abis_nm_send_multi_segments(struct gsm_bts *bts, uint8_t msg_type,
memcpy(oh->data, data, len_to_send);
}
- DEBUGPC(DNM, "Sending multi-segment %d\n", seq);
+ LOG_BTS(bts, DNM, LOGL_DEBUG, "Sending multi-segment %d\n", seq);
ret = abis_nm_sendmsg(bts, msg);
if (ret < 0)
@@ -1301,7 +1311,7 @@ static int abis_nm_send_config(struct gsm_bts *bts, uint8_t bts_type)
idx++;
}
- ret = make_bts_config(bts_type, idx, config + len, need_hopping);
+ ret = make_bts_config(bts, bts_type, idx, config + len, need_hopping);
len += ret;
#if 0 /* debugging */
@@ -1328,7 +1338,7 @@ static int find_element(uint8_t * data, int len, uint16_t id, uint8_t * value,
GET_NEXT_BYTE;
- /* encoding bit, construced means that other elements are contained */
+ /* encoding bit, constructed means that other elements are contained */
constructed = ((ub & 0x20) ? 1 : 0);
if ((ub & 0x1F) == 0x1F) {
@@ -1387,7 +1397,7 @@ static int dump_elements(uint8_t * data, int len)
GET_NEXT_BYTE;
- /* encoding bit, construced means that other elements are contained */
+ /* encoding bit, constructed means that other elements are contained */
constructed = ((ub & 0x20) ? 1 : 0);
if ((ub & 0x1F) == 0x1F) {
@@ -1430,6 +1440,39 @@ static int dump_elements(uint8_t * data, int len)
return 0;
}
+static void mo_ok(struct gsm_abis_mo *mo)
+{
+ mo->nm_state.operational = NM_OPSTATE_ENABLED;
+ mo->nm_state.administrative = NM_STATE_UNLOCKED;
+ mo->nm_state.availability = NM_AVSTATE_OK;
+}
+
+static void nokia_abis_nm_fake_1221_ok(struct gsm_bts *bts)
+{
+ /*
+ * The Nokia BTS don't follow the 12.21 model and we don't have OM objects
+ * for the various elements. However some of the BSC code depends on seeing
+ * those object "up & running", so when the Nokia init is done, we fake
+ * a "good" state
+ */
+ struct gsm_bts_trx *trx;
+
+ mo_ok(&bts->mo);
+ mo_ok(&bts->site_mgr->mo);
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ int i;
+
+ mo_ok(&trx->mo);
+ mo_ok(&trx->bb_transc.mo);
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ mo_ok(&ts->mo);
+ }
+ }
+}
+
/* TODO: put in a separate file ? */
/* taken from abis_nm.c */
@@ -1461,18 +1504,28 @@ static void reset_timer_cb(void *_bts)
struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
struct e1inp_line *line;
- bts->nokia.wait_reset = 0;
-
/* OML link */
line = e1inp_line_find(e1_link->e1_nr);
if (!line) {
- LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to "
- "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr);
+ LOG_BTS(bts, DLINP, LOGL_ERROR, "BTS OML link referring to non-existing E1 line %u\n",
+ e1_link->e1_nr);
return;
}
- start_sabm_in_line(line, 0, -1); /* stop all first */
- start_sabm_in_line(line, 1, SAPI_OML); /* start only OML */
+ switch (bts->nokia.wait_reset) {
+ case RESET_T_NONE: /* shouldn't happen */
+ break;
+ case RESET_T_STOP_LAPD:
+ start_sabm_in_line(line, 0, -1); /* stop all first */
+ bts->nokia.wait_reset = RESET_T_RESTART_LAPD;
+ osmo_timer_schedule(&bts->nokia.reset_timer, bts->nokia.bts_reset_timer_cnf, 0);
+ break;
+ case RESET_T_RESTART_LAPD:
+ bts->nokia.wait_reset = 0;
+ start_sabm_in_line(line, 0, -1); /* stop all first */
+ start_sabm_in_line(line, 1, SAPI_OML); /* start only OML */
+ break;
+ }
}
/* TODO: put in a separate file ? */
@@ -1487,7 +1540,7 @@ static void reset_timer_cb(void *_bts)
- receive ACK, start RSL link(s)
ACK some other messages received from the BTS.
- Probably its also possible to configure the BTS without a reset, this
+ Probably its also possible to configure the BTS without a reset, this
has not been tested yet.
*/
@@ -1507,18 +1560,17 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
int len_data;
if (bts->nokia.wait_reset) {
- LOGP(DNM, LOGL_INFO,
- "Ignore message while waiting for reset\n");
+ LOG_BTS(bts, DNM, LOGL_INFO, "Ignoring message while waiting for reset: %s\n", msgb_hexdump(mb));
return ret;
}
if (oh->length < sizeof(struct abis_om_nokia_hdr)) {
- LOGP(DNM, LOGL_ERROR, "Message too short\n");
+ LOG_BTS(bts, DNM, LOGL_ERROR, "Message too short: %s\n", msgb_hexdump(mb));
return -EINVAL;
}
len_data = oh->length - sizeof(struct abis_om_nokia_hdr);
- LOGP(DNM, LOGL_INFO, "(0x%02X) %s\n", mt, get_msg_type_name_string(mt));
+ LOG_BTS(bts, DNM, LOGL_INFO, "Rx (0x%02X) %s\n", mt, get_msg_type_name_string(mt));
#if 0 /* debugging */
dump_elements(noh->data, len_data);
#endif
@@ -1528,11 +1580,10 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
if (find_element(noh->data, len_data,
NOKIA_EI_BTS_TYPE, &bts->nokia.bts_type,
sizeof(uint8_t)) == sizeof(uint8_t))
- LOGP(DNM, LOGL_INFO, "BTS type = %d (%s)\n",
- bts->nokia.bts_type,
- get_bts_type_string(bts->nokia.bts_type));
+ LOG_BTS(bts, DNM, LOGL_INFO, "Rx BTS type = %d (%s)\n", bts->nokia.bts_type,
+ get_bts_type_string(bts->nokia.bts_type));
else
- LOGP(DNM, LOGL_ERROR, "BTS type not found\n");
+ LOG_BTS(bts, DNM, LOGL_ERROR, "BTS type not found in NOKIA_MSG_OMU_STARTED\n");
/* send START_DOWNLOAD_REQ */
abis_nm_download_req(bts, ref);
break;
@@ -1550,14 +1601,13 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
if (find_element
(noh->data, len_data, NOKIA_EI_ACK, &ack,
sizeof(uint8_t)) == sizeof(uint8_t)) {
- LOGP(DNM, LOGL_INFO, "ACK = %d\n", ack);
+ LOG_BTS(bts, DNM, LOGL_INFO, "Rx ACK = %u\n", ack);
if (ack != 1) {
- LOGP(DNM, LOGL_ERROR, "No ACK received (%d)\n",
- ack);
+ LOG_BTS(bts, DNM, LOGL_ERROR, "Rx No ACK (%u): don't know how to proceed\n", ack);
/* TODO: properly handle failures (NACK) */
}
} else
- LOGP(DNM, LOGL_ERROR, "ACK not found\n");
+ LOG_BTS(bts, DNM, LOGL_ERROR, "Rx MSG_ACK but no EI_ACK found: %s\n", msgb_hexdump(mb));
/* TODO: the assumption for the following is that no NACK was received */
@@ -1565,8 +1615,8 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
if (!bts->nokia.did_reset) {
bts->nokia.did_reset = 1;
- /*
- TODO: For the InSite processing the received data is
+ /*
+ TODO: For the InSite processing the received data is
blocked in the driver during reset.
Otherwise the LAPD module might assert because the InSite
sends garbage on the E1 line during reset.
@@ -1574,25 +1624,16 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
(function handle_ts1_read()) and ignoring the received data.
It seems to be necessary for the MetroSite too.
*/
- bts->nokia.wait_reset = 1;
-
- osmo_timer_setup(&bts->nokia.reset_timer,
- reset_timer_cb, bts);
- osmo_timer_schedule(&bts->nokia.reset_timer, bts->nokia.bts_reset_timer_cnf, 0);
-
- struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
- struct e1inp_line *line;
- /* OML link */
- line = e1inp_line_find(e1_link->e1_nr);
- if (!line) {
- LOGP(DLINP, LOGL_ERROR,
- "BTS %u OML link referring to "
- "non-existing E1 line %u\n", bts->nr,
- e1_link->e1_nr);
- return -ENOMEM;
- }
- start_sabm_in_line(line, 0, -1); /* stop all first */
+ /* we cannot delete / stop the OML LAPD SAP right here, as we are in
+ * the middle of processing an LAPD I frame and are subsequently returning
+ * back to the LAPD I frame processing code that assumes the SAP is still
+ * active. So we first schedule the timer at 0ms in the future, where we
+ * kill all LAPD SAP and re-arm the timer for the reset duration, after which
+ * we re-create them */
+ bts->nokia.wait_reset = RESET_T_STOP_LAPD;
+ osmo_timer_setup(&bts->nokia.reset_timer, reset_timer_cb, bts);
+ osmo_timer_schedule(&bts->nokia.reset_timer, 0, 0);
}
/* ACK for CONF DATA message ? */
@@ -1608,11 +1649,8 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
/* RSL Link */
line = e1inp_line_find(e1_link->e1_nr);
if (!line) {
- LOGP(DLINP, LOGL_ERROR,
- "TRX (%u/%u) RSL link referring "
- "to non-existing E1 line %u\n",
- sign_link->trx->bts->nr, sign_link->trx->nr,
- e1_link->e1_nr);
+ LOG_BTS(bts, DLINP, LOGL_ERROR, "RSL link referring to "
+ "non-existing E1 line %u\n", e1_link->e1_nr);
return -ENOMEM;
}
/* start TRX */
@@ -1626,6 +1664,8 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
case NOKIA_MSG_CONF_COMPLETE:
/* send ACK */
abis_nm_ack(bts, ref);
+ /* fake 12.21 OM */
+ nokia_abis_nm_fake_1221_ok(bts);
break;
case NOKIA_MSG_BLOCK_CTRL_REQ: /* seems to be send when something goes wrong !? */
/* send ACK (do we have to send an ACK ?) */
@@ -1640,21 +1680,17 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
sizeof(info));
if (str_len > 0) {
info[str_len] = 0;
- LOGP(DNM, LOGL_INFO, "ALARM Severity %s (%d) : %s\n",
- get_severity_string(severity), severity, info);
+ LOG_BTS(bts, DNM, LOGL_NOTICE, "Rx ALARM Severity %s (%d) : %s\n",
+ get_severity_string(severity), severity, info);
} else { /* nothing found, try details */
str_len =
- find_element(noh->data, len_data,
- NOKIA_EI_ALARM_DETAIL, info,
- sizeof(info));
+ find_element(noh->data, len_data, NOKIA_EI_ALARM_DETAIL, info, sizeof(info));
if (str_len > 0) {
uint16_t code;
info[str_len] = 0;
code = (info[0] << 8) + info[1];
- LOGP(DNM, LOGL_INFO,
- "ALARM Severity %s (%d), code 0x%X : %s\n",
- get_severity_string(severity), severity,
- code, info + 2);
+ LOG_BTS(bts, DNM, LOGL_NOTICE, "Rx ALARM Severity %s (%d), code 0x%X : %s\n",
+ get_severity_string(severity), severity, code, info + 2);
}
}
/* send ACK */
@@ -1671,41 +1707,37 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
int abis_nokia_rcvmsg(struct msgb *msg)
{
+ struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
struct abis_om_hdr *oh = msgb_l2(msg);
int rc = 0;
/* Various consistency checks */
if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
- LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
- oh->placement);
+ LOG_BTS(bts, DNM, LOGL_ERROR, "Rx ABIS OML placement 0x%x not supported\n", oh->placement);
if (oh->placement != ABIS_OM_PLACEMENT_FIRST)
return -EINVAL;
}
if (oh->sequence != 0) {
- LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
- oh->sequence);
+ LOG_BTS(bts, DNM, LOGL_ERROR, "Rx ABIS OML sequence 0x%x != 0x00\n", oh->sequence);
return -EINVAL;
}
msg->l3h = (unsigned char *)oh + sizeof(*oh);
switch (oh->mdisc) {
case ABIS_OM_MDISC_FOM:
- LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_FOM\n");
+ LOG_BTS(bts, DNM, LOGL_INFO, "Rx ABIS_OM_MDISC_FOM\n");
rc = abis_nm_rcvmsg_fom(msg);
break;
case ABIS_OM_MDISC_MANUF:
- LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_MANUF\n");
+ LOG_BTS(bts, DNM, LOGL_INFO, "Rx ABIS_OM_MDISC_MANUF: ignoring\n");
break;
case ABIS_OM_MDISC_MMI:
case ABIS_OM_MDISC_TRAU:
- LOGP(DNM, LOGL_ERROR,
- "unimplemented ABIS OML message discriminator 0x%x\n",
- oh->mdisc);
+ LOG_BTS(bts, DNM, LOGL_ERROR, "Rx unimplemented ABIS OML message discriminator 0x%x\n", oh->mdisc);
break;
default:
- LOGP(DNM, LOGL_ERROR,
- "unknown ABIS OML message discriminator 0x%x\n",
- oh->mdisc);
+ LOG_BTS(bts, DNM, LOGL_ERROR, "Rx unknown ABIS OML message discriminator 0x%x\n", oh->mdisc);
return -EINVAL;
}
@@ -1732,14 +1764,6 @@ static struct gsm_network *my_net;
static int bts_model_nokia_site_start(struct gsm_network *net)
{
- model_nokia_site.features.data = &model_nokia_site._features_data[0];
- model_nokia_site.features.data_len =
- sizeof(model_nokia_site._features_data);
-
- osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HOPPING);
- osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HSCSD);
- osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_MULTI_TSC);
-
osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL);
osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
@@ -1751,5 +1775,12 @@ static int bts_model_nokia_site_start(struct gsm_network *net)
int bts_model_nokia_site_init(void)
{
+ model_nokia_site.features.data = &model_nokia_site._features_data[0];
+ model_nokia_site.features.data_len = sizeof(model_nokia_site._features_data);
+
+ osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HOPPING);
+ osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HSCSD);
+ osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_MULTI_TSC);
+
return gsm_bts_model_register(&model_nokia_site);
}
diff --git a/src/osmo-bsc/bts_osmobts.c b/src/osmo-bsc/bts_osmobts.c
new file mode 100644
index 000000000..9312a2a27
--- /dev/null
+++ b/src/osmo-bsc/bts_osmobts.c
@@ -0,0 +1,210 @@
+/* Osmocom OsmoBTS specific code */
+
+/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <arpa/inet.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/abis/subchan_demux.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/core/logging.h>
+
+extern struct gsm_bts_model bts_model_nanobts;
+
+static struct gsm_bts_model model_osmobts;
+
+static void enc_osmo_meas_proc_params(struct msgb *msg, const struct gsm_power_ctrl_params *mp)
+{
+ struct osmo_preproc_ave_cfg *ave_cfg;
+ uint8_t *ie_len;
+
+ /* No averaging => no Measurement Averaging parameters */
+ if (mp->ci_fr_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE &&
+ mp->ci_hr_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE &&
+ mp->ci_amr_fr_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE &&
+ mp->ci_amr_hr_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE &&
+ mp->ci_sdcch_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE &&
+ mp->ci_gprs_meas.algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE)
+ return;
+
+ /* (TLV) Measurement Averaging parameters for RxLev/RxQual */
+ ie_len = msgb_tl_put(msg, RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG);
+
+ ave_cfg = (struct osmo_preproc_ave_cfg *) msgb_put(msg, sizeof(*ave_cfg));
+
+#define ENC_PROC(PARAMS, TO, TYPE) do { \
+ (TO)->TYPE.ave_enabled = (PARAMS)->TYPE##_meas.algo != GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE; \
+ if ((TO)->TYPE.ave_enabled) { \
+ /* H_REQAVE and H_REQT */ \
+ (TO)->TYPE.h_reqave = (PARAMS)->TYPE##_meas.h_reqave & 0x1f; \
+ (TO)->TYPE.h_reqt = (PARAMS)->TYPE##_meas.h_reqt & 0x1f; \
+ /* Averaging method and parameters */ \
+ (TO)->TYPE.ave_method = ((PARAMS)->TYPE##_meas.algo - 1) & 0x07; \
+ switch ((PARAMS)->TYPE##_meas.algo) { \
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA: \
+ msgb_v_put(msg, (PARAMS)->TYPE##_meas.ewma.alpha); \
+ break; \
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED: \
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN: \
+ /* FIXME: unknown format */ \
+ break; \
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED: \
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE: \
+ /* No parameters here */ \
+ break; \
+ } \
+ } \
+ } while (0)
+ ENC_PROC(mp, ave_cfg, ci_fr);
+ ENC_PROC(mp, ave_cfg, ci_hr);
+ ENC_PROC(mp, ave_cfg, ci_amr_fr);
+ ENC_PROC(mp, ave_cfg, ci_amr_hr);
+ ENC_PROC(mp, ave_cfg, ci_sdcch);
+ ENC_PROC(mp, ave_cfg, ci_gprs);
+#undef ENC_PROC
+
+ /* Update length part of the containing IE */
+ *ie_len = msg->tail - (ie_len + 1);
+}
+
+/* Appends Osmocom specific extension IEs into RSL_IE_MS_POWER_PARAM */
+void osmobts_enc_power_params_osmo_ext(struct msgb *msg, const struct gsm_power_ctrl_params *cp)
+{
+ struct osmo_preproc_pc_thresh *osmo_thresh;
+ struct osmo_preproc_pc_comp *osmo_thresh_comp;
+ uint8_t *ie_len;
+
+ /* (TLV) Measurement Averaging Configure (C/I) */
+ enc_osmo_meas_proc_params(msg, cp);
+
+ /* (TLV) Thresholds (C/I) */
+ ie_len = msgb_tl_put(msg, RSL_IPAC_EIE_OSMO_MS_PWR_CTL);
+ osmo_thresh = (struct osmo_preproc_pc_thresh *) msgb_put(msg, sizeof(*osmo_thresh));
+ #define ENC_THRESH_CI(TYPE) \
+ do { \
+ if (cp->TYPE##_meas.enabled) { \
+ osmo_thresh->l_##TYPE = cp->TYPE##_meas.lower_thresh; \
+ osmo_thresh->u_##TYPE = cp->TYPE##_meas.upper_thresh; \
+ } else { \
+ osmo_thresh->l_##TYPE = 0; \
+ osmo_thresh->u_##TYPE = 0; \
+ } \
+ } while (0)
+ ENC_THRESH_CI(ci_fr);
+ ENC_THRESH_CI(ci_hr);
+ ENC_THRESH_CI(ci_amr_fr);
+ ENC_THRESH_CI(ci_amr_hr);
+ ENC_THRESH_CI(ci_sdcch);
+ ENC_THRESH_CI(ci_gprs);
+ #undef ENC_THRESH_CI
+ /* Update length part of the containing IE */
+ *ie_len = msg->tail - (ie_len + 1);
+
+ /* (TLV) PC Threshold Comparators (C/I) */
+ ie_len = msgb_tl_put(msg, RSL_IPAC_EIE_OSMO_PC_THRESH_COMP);
+ osmo_thresh_comp = (struct osmo_preproc_pc_comp *) msgb_put(msg, sizeof(*osmo_thresh_comp));
+ #define ENC_THRESH_CI(TYPE) \
+ do { \
+ if (cp->TYPE##_meas.enabled) { \
+ osmo_thresh_comp->TYPE.lower_p = cp->TYPE##_meas.lower_cmp_p & 0x1f; \
+ osmo_thresh_comp->TYPE.lower_n = cp->TYPE##_meas.lower_cmp_n & 0x1f; \
+ osmo_thresh_comp->TYPE.upper_p = cp->TYPE##_meas.upper_cmp_p & 0x1f; \
+ osmo_thresh_comp->TYPE.upper_n = cp->TYPE##_meas.upper_cmp_n & 0x1f; \
+ } else { \
+ osmo_thresh_comp->TYPE.lower_p = 0; \
+ osmo_thresh_comp->TYPE.lower_n = 0; \
+ osmo_thresh_comp->TYPE.upper_p = 0; \
+ osmo_thresh_comp->TYPE.upper_n = 0; \
+ } \
+ } while (0)
+ ENC_THRESH_CI(ci_fr);
+ ENC_THRESH_CI(ci_hr);
+ ENC_THRESH_CI(ci_amr_fr);
+ ENC_THRESH_CI(ci_amr_hr);
+ ENC_THRESH_CI(ci_sdcch);
+ ENC_THRESH_CI(ci_gprs);
+ #undef ENC_THRESH_CI
+ /* Update length part of the containing IE */
+ *ie_len = msg->tail - (ie_len + 1);
+}
+
+static int power_ctrl_send_c0_power_red(const struct gsm_bts *bts, const uint8_t red)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *msg;
+
+ msg = rsl_msgb_alloc();
+ if (msg == NULL)
+ return -ENOMEM;
+
+ /* Abuse the standard BS POWER CONTROL message by specifying 'Common Channel'
+ * in the Protocol Discriminator field and 'BCCH' in the Channel Number IE. */
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ dh->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN;
+ dh->c.msg_type = RSL_MT_BS_POWER_CONTROL;
+ dh->ie_chan = RSL_IE_CHAN_NR;
+ dh->chan_nr = RSL_CHAN_BCCH;
+
+ msgb_tv_put(msg, RSL_IE_BS_POWER, red / 2);
+
+ msg->dst = bts->c0->rsl_link_primary;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+int bts_model_osmobts_init(void)
+{
+ model_osmobts = bts_model_nanobts;
+ model_osmobts.name = "osmo-bts";
+ model_osmobts.type = GSM_BTS_TYPE_OSMOBTS;
+ model_osmobts.features_get_reported = true;
+
+ /* Unlike nanoBTS, osmo-bts does support SI2bis and SI2ter fine */
+ model_osmobts.force_combined_si = false;
+
+ /* Power control API */
+ model_osmobts.power_ctrl_send_c0_power_red = &power_ctrl_send_c0_power_red;
+
+ model_osmobts.features.data = &model_osmobts._features_data[0];
+ model_osmobts.features.data_len =
+ sizeof(model_osmobts._features_data);
+ memset(model_osmobts.features.data, 0, model_osmobts.features.data_len);
+
+ /* Adjust bts_init/bts_model_init in OsmoBTS to report new features.
+ * See also: doc/bts-features.txt */
+
+ model_osmobts.nm_att_tlvdef.def[NM_ATT_OSMO_NS_LINK_CFG].type = TLV_TYPE_TL16V;
+
+ return gsm_bts_model_register(&model_osmobts);
+}
diff --git a/src/osmo-bsc/bts_setup_ramp.c b/src/osmo-bsc/bts_setup_ramp.c
new file mode 100644
index 000000000..247f77654
--- /dev/null
+++ b/src/osmo-bsc/bts_setup_ramp.c
@@ -0,0 +1,249 @@
+/* (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Alexander Couzens <acouzens@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <osmocom/core/fsm.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bts_sm.h>
+#include <osmocom/bsc/bts_setup_ramp.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+
+
+static void _bts_setup_ramp_unblock_bts(struct gsm_bts *bts)
+{
+ llist_del_init(&bts->bts_setup_ramp.list);
+ bts->bts_setup_ramp.state = BTS_SETUP_RAMP_READY;
+
+ nm_fsm_dispatch_all_configuring(bts, NM_EV_SETUP_RAMP_READY, NULL);
+}
+
+/*!
+ * Unblock a BTS from BTS setup ramping to continue setup and configure.
+ *
+ * \param bts pointer to the bts
+ * \return 0 on success, -EINVAL when the BTS is not waiting.
+ */
+int bts_setup_ramp_unblock_bts(struct gsm_bts *bts)
+{
+ if (bts->bts_setup_ramp.state != BTS_SETUP_RAMP_WAIT)
+ return -EINVAL;
+
+ if (llist_empty(&bts->bts_setup_ramp.list))
+ return -EINVAL;
+
+ _bts_setup_ramp_unblock_bts(bts);
+ return 0;
+}
+
+/*!
+ * Timer callback and called by bts_setup_ramp_deactivate
+ * \param _net pointer to struct gsm_network
+ */
+static void bts_setup_ramp_timer_cb(void *_net)
+{
+ struct gsm_network *net = (struct gsm_network *) _net;
+ struct gsm_bts *bts, *n;
+ net->bts_setup_ramp.count = 0;
+
+ llist_for_each_entry_safe(bts, n, &net->bts_setup_ramp.head, bts_setup_ramp.list) {
+ net->bts_setup_ramp.count++;
+ _bts_setup_ramp_unblock_bts(bts);
+ LOG_BTS(bts, DNM, LOGL_INFO, "Unblock BTS %d from BTS ramping.\n", bts->nr);
+ if (bts_setup_ramp_active(net) && net->bts_setup_ramp.count >= net->bts_setup_ramp.step_size)
+ break;
+ }
+
+ if (bts_setup_ramp_active(net))
+ osmo_timer_schedule(&net->bts_setup_ramp.timer, net->bts_setup_ramp.step_interval, 0);
+}
+
+const struct value_string bts_setup_ramp_state_values[] = {
+ { BTS_SETUP_RAMP_INIT, "Initial" },
+ { BTS_SETUP_RAMP_WAIT, "Waiting" },
+ { BTS_SETUP_RAMP_READY, "Ready" },
+ { 0, NULL },
+};
+
+const char *bts_setup_ramp_get_state_str(struct gsm_bts *bts)
+{
+ return get_value_string_or_null(bts_setup_ramp_state_values, bts->bts_setup_ramp.state);
+}
+
+/* return true when state has been changed. */
+static bool check_config(struct gsm_network *net)
+{
+ bool new_state = (net->bts_setup_ramp.enabled
+ && net->bts_setup_ramp.step_size > 0
+ && net->bts_setup_ramp.step_interval > 0);
+
+ if (!new_state && bts_setup_ramp_active(net)) {
+ net->bts_setup_ramp.active = false;
+ osmo_timer_del(&net->bts_setup_ramp.timer);
+ /* clear bts list */
+ bts_setup_ramp_timer_cb(net);
+ return true;
+ } else if (new_state && !bts_setup_ramp_active(net)) {
+ net->bts_setup_ramp.active = true;
+ osmo_timer_schedule(&net->bts_setup_ramp.timer, net->bts_setup_ramp.step_interval, 0);
+ return true;
+ }
+
+ return false;
+}
+
+/*!
+ * Enable the bts setup ramping feature
+ *
+ * The BTS setup ramping prevents BSC overload when too many BTS tries to setup and
+ * configure at the same time. E.g. this might happen if there is a major network outage
+ * between all BTS and the BSC.
+ *
+ * \param[in] net a pointer to the gsm network
+ */
+void bts_setup_ramp_enable(struct gsm_network *net)
+{
+ net->bts_setup_ramp.enabled = true;
+ check_config(net);
+}
+
+/*!
+ * Disable the bts setup ramping feature
+ *
+ * \param[in] net a pointer to the gsm network
+ */
+void bts_setup_ramp_disable(struct gsm_network *net)
+{
+ net->bts_setup_ramp.enabled = false;
+ check_config(net);
+}
+
+/*! Checks if the bts setup ramp correct configured and active
+ *
+ * \param[in] net a pointer to the gsm network
+ * \return true if the bts setup ramp is active
+ */
+bool bts_setup_ramp_active(struct gsm_network *net)
+{
+ return net->bts_setup_ramp.active;
+}
+
+/*!
+ * Check if the BTS should wait to setup.
+ *
+ * Can be called multiple times by the same BTS.
+ *
+ * \param bts pointer to the bts
+ * \return true if the bts should wait
+ */
+bool bts_setup_ramp_wait(struct gsm_bts *bts)
+{
+ struct gsm_network *net = bts->network;
+
+ if (!bts_setup_ramp_active(net)) {
+ bts->bts_setup_ramp.state = BTS_SETUP_RAMP_READY;
+ return false;
+ }
+
+ switch (bts->bts_setup_ramp.state) {
+ case BTS_SETUP_RAMP_INIT:
+ break;
+ case BTS_SETUP_RAMP_WAIT:
+ return true;
+ case BTS_SETUP_RAMP_READY:
+ return false;
+ }
+
+ if (net->bts_setup_ramp.count < net->bts_setup_ramp.step_size) {
+ LOG_BTS(bts, DNM, LOGL_INFO,
+ "BTS %d can configure without waiting for BTS ramping.\n", bts->nr);
+
+ net->bts_setup_ramp.count++;
+ bts->bts_setup_ramp.state = BTS_SETUP_RAMP_READY;
+ return false;
+ }
+
+ bts->bts_setup_ramp.state = BTS_SETUP_RAMP_WAIT;
+ llist_add_tail(&bts->bts_setup_ramp.list, &net->bts_setup_ramp.head);
+ LOGP(DNM, LOGL_INFO, "BTS %d will wait for BTS ramping.\n", bts->nr);
+
+ return true;
+}
+
+void bts_setup_ramp_init_network(struct gsm_network *net)
+{
+ INIT_LLIST_HEAD(&net->bts_setup_ramp.head);
+ osmo_timer_setup(&net->bts_setup_ramp.timer, bts_setup_ramp_timer_cb, net);
+}
+
+void bts_setup_ramp_init_bts(struct gsm_bts *bts)
+{
+ /* Initialize bts_setup_ramp.list (llist_entry) to have llist_empty() available */
+ INIT_LLIST_HEAD(&bts->bts_setup_ramp.list);
+ bts->bts_setup_ramp.state = BTS_SETUP_RAMP_INIT;
+}
+
+/*!
+ * Remove the bts from the bts setup ramp waiting list and resets the BTS setup ramping state.
+ * Should be called when removing the BTS
+ *
+ * \param bts pointer to the bts
+ */
+void bts_setup_ramp_remove(struct gsm_bts *bts)
+{
+ if (!llist_empty(&bts->bts_setup_ramp.list))
+ llist_del_init(&bts->bts_setup_ramp.list);
+ bts->bts_setup_ramp.state = BTS_SETUP_RAMP_INIT;
+}
+
+/*!
+ * Set the BTS setup ramping step interval.
+ *
+ * Within the time window of \param step_interval only a limited amount (see step_size)
+ * of BTS will be configured.
+ *
+ * \param[in] net a pointer to the gsm network
+ * \param step_interval in seconds
+ */
+void bts_setup_ramp_set_step_interval(struct gsm_network *net, unsigned int step_interval)
+{
+ net->bts_setup_ramp.step_interval = step_interval;
+ check_config(net);
+}
+
+/*!
+ * Set the BTS setup ramping step_size
+ *
+ * Within the time window of step_interval only a limited amount of BTS (\param step_size)
+ * will be configured.
+ *
+ * \param[in] net a pointer to the gsm network
+ * \param step_size the step size
+ */
+void bts_setup_ramp_set_step_size(struct gsm_network *net, unsigned int step_size)
+{
+ net->bts_setup_ramp.step_size = step_size;
+ check_config(net);
+}
diff --git a/src/osmo-bsc/bts_siemens_bs11.c b/src/osmo-bsc/bts_siemens_bs11.c
index 2cb676c93..9e4f09103 100644
--- a/src/osmo-bsc/bts_siemens_bs11.c
+++ b/src/osmo-bsc/bts_siemens_bs11.c
@@ -20,6 +20,8 @@
*/
+#include <osmocom/core/tdef.h>
+
#include <osmocom/gsm/tlv.h>
#include <osmocom/bsc/debug.h>
@@ -27,8 +29,8 @@
#include <osmocom/bsc/abis_nm.h>
#include <osmocom/abis/e1_input.h>
#include <osmocom/bsc/signal.h>
-#include <osmocom/bsc/gsm_timers.h>
#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/bts.h>
static int bts_model_bs11_start(struct gsm_network *net);
@@ -60,7 +62,7 @@ static struct gsm_bts_model model_bs11 = {
[NM_ATT_BS11_L1_PROT_TYPE] = { TLV_TYPE_TV },
[NM_ATT_BS11_BIT_ERR_THESH] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_BS11_DIVERSITY] = { TLV_TYPE_TLV },
- [NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV },
+ [NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV },
[NM_ATT_BS11_LMT_LOGIN_TIME] = { TLV_TYPE_TLV },
[NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV },
[NM_ATT_BS11_LMT_USER_NAME] = { TLV_TYPE_TLV },
@@ -329,7 +331,7 @@ unsigned char msg_4[] =
/*
Object Class: Transceiver
BTS relat. Number: 0
- Tranceiver number: 0
+ Transceiver number: 0
Instance 3: FF
SET TRX ATTRIBUTES
aRFCNList (HEX): 0001
@@ -360,7 +362,7 @@ static void patch_nm_tables(struct gsm_bts *bts)
uint8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f;
/* T3105 attribute in units of 10ms */
- bs11_attr_bts[2] = T_def_get(bts->network->T_defs, 3105, T_MS, -1) / 10;
+ bs11_attr_bts[2] = osmo_tdef_get(bts->network->T_defs, 3105, OSMO_TDEF_MS, -1) / 10;
/* patch ARFCN into BTS Attributes */
bs11_attr_bts[69] &= 0xf0;
@@ -399,12 +401,19 @@ static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts)
abis_nm_set_channel_attr(ts, ccomb);
- if (is_ipaccess_bts(ts->trx->bts))
+ if (is_ipa_abisip_bts(ts->trx->bts))
return;
- if (ts_is_tch(ts))
- abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts,
- e1l->e1_ts_ss);
+ switch (ts->pchan_from_config) {
+ case GSM_PCHAN_TCH_F:
+ abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts, e1l->e1_ts_ss);
+ break;
+ case GSM_PCHAN_TCH_H:
+ LOG_TS(ts, LOGL_ERROR, "No support for half-rate over E1 yet!\n");
+ break;
+ default:
+ break;
+ }
}
static void nm_reconfig_trx(struct gsm_bts_trx *trx)
@@ -414,57 +423,35 @@ static void nm_reconfig_trx(struct gsm_bts_trx *trx)
patch_nm_tables(trx->bts);
- switch (trx->bts->type) {
- case GSM_BTS_TYPE_BS11:
- /* FIXME: discover this by fetching an attribute */
+ /* FIXME: discover this by fetching an attribute */
#if 0
- trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */
+ trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */
#else
- trx->nominal_power = 24; /* 24dBm == 250mW PA configuration */
+ trx->nominal_power = 24; /* 24dBm == 250mW PA configuration */
#endif
- abis_nm_conn_terr_sign(trx, e1l->e1_nr, e1l->e1_ts,
- e1l->e1_ts_ss);
- abis_nm_establish_tei(trx->bts, trx->nr, e1l->e1_nr,
- e1l->e1_ts, e1l->e1_ts_ss, trx->rsl_tei);
-
- /* Set Radio Attributes */
- if (trx == trx->bts->c0)
- abis_nm_set_radio_attr(trx, bs11_attr_radio,
- sizeof(bs11_attr_radio));
- else {
- uint8_t trx1_attr_radio[sizeof(bs11_attr_radio)];
- uint8_t arfcn_low = trx->arfcn & 0xff;
- uint8_t arfcn_high = (trx->arfcn >> 8) & 0x0f;
- memcpy(trx1_attr_radio, bs11_attr_radio,
- sizeof(trx1_attr_radio));
-
- /* patch ARFCN into TRX Attributes */
- trx1_attr_radio[2] &= 0xf0;
- trx1_attr_radio[2] |= arfcn_high;
- trx1_attr_radio[3] = arfcn_low;
-
- abis_nm_set_radio_attr(trx, trx1_attr_radio,
- sizeof(trx1_attr_radio));
- }
- break;
- case GSM_BTS_TYPE_NANOBTS:
- switch (trx->bts->band) {
- case GSM_BAND_850:
- case GSM_BAND_900:
- trx->nominal_power = 20;
- break;
- case GSM_BAND_1800:
- case GSM_BAND_1900:
- trx->nominal_power = 23;
- break;
- default:
- LOGP(DNM, LOGL_ERROR, "Unsupported nanoBTS GSM band %s\n",
- gsm_band_name(trx->bts->band));
- break;
- }
- break;
- default:
- break;
+ abis_nm_conn_terr_sign(trx, e1l->e1_nr, e1l->e1_ts,
+ e1l->e1_ts_ss);
+ abis_nm_establish_tei(trx->bts, trx->nr, e1l->e1_nr,
+ e1l->e1_ts, e1l->e1_ts_ss, trx->rsl_tei_primary);
+
+ /* Set Radio Attributes */
+ if (trx == trx->bts->c0)
+ abis_nm_set_radio_attr(trx, bs11_attr_radio,
+ sizeof(bs11_attr_radio));
+ else {
+ uint8_t trx1_attr_radio[sizeof(bs11_attr_radio)];
+ uint8_t arfcn_low = trx->arfcn & 0xff;
+ uint8_t arfcn_high = (trx->arfcn >> 8) & 0x0f;
+ memcpy(trx1_attr_radio, bs11_attr_radio,
+ sizeof(trx1_attr_radio));
+
+ /* patch ARFCN into TRX Attributes */
+ trx1_attr_radio[2] &= 0xf0;
+ trx1_attr_radio[2] |= arfcn_high;
+ trx1_attr_radio[3] = arfcn_low;
+
+ abis_nm_set_radio_attr(trx, trx1_attr_radio,
+ sizeof(trx1_attr_radio));
}
for (i = 0; i < TRX_NR_TS; i++)
@@ -475,17 +462,11 @@ static void nm_reconfig_bts(struct gsm_bts *bts)
{
struct gsm_bts_trx *trx;
- switch (bts->type) {
- case GSM_BTS_TYPE_BS11:
- patch_nm_tables(bts);
- abis_nm_raw_msg(bts, sizeof(msg_1), msg_1); /* set BTS SiteMgr attr*/
- abis_nm_set_bts_attr(bts, bs11_attr_bts, sizeof(bs11_attr_bts));
- abis_nm_raw_msg(bts, sizeof(msg_3), msg_3); /* set BTS handover attr */
- abis_nm_raw_msg(bts, sizeof(msg_4), msg_4); /* set BTS power control attr */
- break;
- default:
- break;
- }
+ patch_nm_tables(bts);
+ abis_nm_raw_msg(bts, sizeof(msg_1), msg_1); /* set BTS SiteMgr attr*/
+ abis_nm_set_bts_attr(bts, bs11_attr_bts, sizeof(bs11_attr_bts));
+ abis_nm_raw_msg(bts, sizeof(msg_3), msg_3); /* set BTS handover attr */
+ abis_nm_raw_msg(bts, sizeof(msg_4), msg_4); /* set BTS power control attr */
llist_for_each_entry(trx, &bts->trx_list, list)
nm_reconfig_trx(trx);
@@ -522,6 +503,11 @@ static void bootstrap_om_bs11(struct gsm_bts *bts)
/* restart sending event reports */
abis_nm_event_reports(bts, 1);
+
+ /* make the timeslot FSM happy. Siemens doesn't send us
+ * OML state changes for individual timeslots, so we
+ * bring all of them up here */
+ gsm_bts_all_ts_dispatch(bts, TS_EV_OML_READY, NULL);
}
static int shutdown_om(struct gsm_bts *bts)
@@ -538,6 +524,7 @@ static int shutdown_om(struct gsm_bts *bts)
/* Reset BTS Site manager resource */
abis_nm_bs11_reset_resource(bts);
+ gsm_bts_stats_reset(bts);
gsm_bts_all_ts_dispatch(bts, TS_EV_OML_DOWN, NULL);
return 0;
@@ -589,13 +576,6 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
static int bts_model_bs11_start(struct gsm_network *net)
{
- model_bs11.features.data = &model_bs11._features_data[0];
- model_bs11.features.data_len = sizeof(model_bs11._features_data);
-
- osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HOPPING);
- osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HSCSD);
- osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_MULTI_TSC);
-
osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL);
@@ -604,5 +584,12 @@ static int bts_model_bs11_start(struct gsm_network *net)
int bts_model_bs11_init(void)
{
+ model_bs11.features.data = &model_bs11._features_data[0];
+ model_bs11.features.data_len = sizeof(model_bs11._features_data);
+
+ osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HOPPING);
+ osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HSCSD);
+ osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_MULTI_TSC);
+
return gsm_bts_model_register(&model_bs11);
}
diff --git a/src/osmo-bsc/bts_sm.c b/src/osmo-bsc/bts_sm.c
new file mode 100644
index 000000000..ca572f146
--- /dev/null
+++ b/src/osmo-bsc/bts_sm.c
@@ -0,0 +1,112 @@
+/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/gsm/abis_nm.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bts_sm.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+
+static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 };
+
+static int gsm_bts_sm_talloc_destructor(struct gsm_bts_sm *bts_sm)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nsvc); i++) {
+ if (bts_sm->gprs.nsvc[i].mo.fi) {
+ osmo_fsm_inst_free(bts_sm->gprs.nsvc[i].mo.fi);
+ bts_sm->gprs.nsvc[i].mo.fi = NULL;
+ }
+ }
+ if (bts_sm->gprs.nse.mo.fi) {
+ osmo_fsm_inst_free(bts_sm->gprs.nse.mo.fi);
+ bts_sm->gprs.nse.mo.fi = NULL;
+ }
+
+ if (bts_sm->mo.fi) {
+ osmo_fsm_inst_free(bts_sm->mo.fi);
+ bts_sm->mo.fi = NULL;
+ }
+ return 0;
+}
+
+struct gsm_bts_sm *gsm_bts_sm_alloc(struct gsm_network *net, uint8_t bts_num)
+{
+ struct gsm_bts_sm *bts_sm = talloc_zero(net, struct gsm_bts_sm);
+ struct gsm_bts *bts;
+ int i;
+ if (!bts_sm)
+ return NULL;
+
+ talloc_set_destructor(bts_sm, gsm_bts_sm_talloc_destructor);
+ bts_sm->mo.fi = osmo_fsm_inst_alloc(&nm_bts_sm_fsm, bts_sm, bts_sm,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(bts_sm->mo.fi, "bts_sm");
+
+ bts = gsm_bts_alloc(net, bts_sm, bts_num);
+ if (!bts) {
+ talloc_free(bts_sm);
+ return NULL;
+ }
+ bts_sm->bts[0] = bts;
+
+ gsm_mo_init(&bts_sm->mo, bts, NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff);
+
+
+ bts_sm->gprs.nse.mo.fi = osmo_fsm_inst_alloc(&nm_gprs_nse_fsm, bts_sm, &bts_sm->gprs.nse,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(bts_sm->gprs.nse.mo.fi, "nse%d", bts_num);
+ gsm_mo_init(&bts_sm->gprs.nse.mo, bts, NM_OC_GPRS_NSE, bts->nr, 0xff, 0xff);
+ memcpy(&bts_sm->gprs.nse.timer, bts_nse_timer_default,
+ sizeof(bts_sm->gprs.nse.timer));
+
+ for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nsvc); i++) {
+ bts_sm->gprs.nsvc[i].bts = bts;
+ bts_sm->gprs.nsvc[i].id = i;
+ bts_sm->gprs.nsvc[i].mo.fi = osmo_fsm_inst_alloc(
+ &nm_gprs_nsvc_fsm, bts_sm,
+ &bts_sm->gprs.nsvc[i],
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(bts_sm->gprs.nsvc[i].mo.fi,
+ "nsvc%d", i);
+ gsm_mo_init(&bts_sm->gprs.nsvc[i].mo, bts, NM_OC_GPRS_NSVC,
+ bts->nr, i, 0xff);
+ }
+ memcpy(&bts_sm->gprs.nse.timer, bts_nse_timer_default,
+ sizeof(bts_sm->gprs.nse.timer));
+ gsm_mo_init(&bts_sm->gprs.nse.mo, bts, NM_OC_GPRS_NSE,
+ bts->nr, 0xff, 0xff);
+
+ return bts_sm;
+}
+
+void gsm_bts_sm_mo_reset(struct gsm_bts_sm *bts_sm)
+{
+ int i;
+ gsm_abis_mo_reset(&bts_sm->mo);
+
+ gsm_abis_mo_reset(&bts_sm->gprs.nse.mo);
+ for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nsvc); i++)
+ gsm_abis_mo_reset(&bts_sm->gprs.nsvc[i].mo);
+
+ gsm_bts_mo_reset(bts_sm->bts[0]);
+}
diff --git a/src/osmo-bsc/bts_sysmobts.c b/src/osmo-bsc/bts_sysmobts.c
deleted file mode 100644
index 253786428..000000000
--- a/src/osmo-bsc/bts_sysmobts.c
+++ /dev/null
@@ -1,63 +0,0 @@
-/* sysmocom sysmoBTS specific code */
-
-/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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 <arpa/inet.h>
-
-#include <osmocom/gsm/tlv.h>
-
-#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/signal.h>
-#include <osmocom/bsc/abis_nm.h>
-#include <osmocom/abis/e1_input.h>
-#include <osmocom/gsm/tlv.h>
-#include <osmocom/core/msgb.h>
-#include <osmocom/core/talloc.h>
-#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/abis_nm.h>
-#include <osmocom/bsc/abis_rsl.h>
-#include <osmocom/bsc/debug.h>
-#include <osmocom/abis/subchan_demux.h>
-#include <osmocom/abis/ipaccess.h>
-#include <osmocom/core/logging.h>
-
-extern struct gsm_bts_model bts_model_nanobts;
-
-static struct gsm_bts_model model_sysmobts;
-
-int bts_model_sysmobts_init(void)
-{
- model_sysmobts = bts_model_nanobts;
- model_sysmobts.name = "sysmobts";
- model_sysmobts.type = GSM_BTS_TYPE_OSMOBTS;
-
- /* Unlike nanoBTS, sysmoBTS supports SI2bis and SI2ter fine */
- model_sysmobts.force_combined_si = false;
-
- model_sysmobts.features.data = &model_sysmobts._features_data[0];
- model_sysmobts.features.data_len =
- sizeof(model_sysmobts._features_data);
- memset(model_sysmobts.features.data, 0, sizeof(model_sysmobts.features.data_len));
-
- osmo_bts_set_feature(&model_sysmobts.features, BTS_FEAT_GPRS);
- osmo_bts_set_feature(&model_sysmobts.features, BTS_FEAT_EGPRS);
-
- return gsm_bts_model_register(&model_sysmobts);
-}
diff --git a/src/osmo-bsc/bts_trx.c b/src/osmo-bsc/bts_trx.c
new file mode 100644
index 000000000..4d2588d5b
--- /dev/null
+++ b/src/osmo-bsc/bts_trx.c
@@ -0,0 +1,509 @@
+/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/gsm/abis_nm.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bts_trx.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/pcu_if.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/lchan.h>
+
+static int gsm_bts_trx_talloc_destructor(struct gsm_bts_trx *trx)
+{
+ unsigned int i;
+
+ if (trx->bb_transc.mo.fi) {
+ osmo_fsm_inst_free(trx->bb_transc.mo.fi);
+ trx->bb_transc.mo.fi = NULL;
+ }
+ if (trx->mo.fi) {
+ osmo_fsm_inst_free(trx->mo.fi);
+ trx->mo.fi = NULL;
+ }
+ for (i = 0; i < TRX_NR_TS; i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ if (ts->mo.fi) {
+ osmo_fsm_inst_free(ts->mo.fi);
+ ts->mo.fi = NULL;
+ }
+ ts_fsm_free(ts);
+ }
+ return 0;
+}
+
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx);
+ int k;
+
+ if (!trx)
+ return NULL;
+
+ talloc_set_destructor(trx, gsm_bts_trx_talloc_destructor);
+
+ trx->bts = bts;
+ trx->nr = bts->num_trx++;
+
+ trx->rsl_tei_primary = trx->nr;
+
+ trx->mo.fi = osmo_fsm_inst_alloc(&nm_rcarrier_fsm, trx, trx,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(trx->mo.fi, "bts%d-trx%d", bts->nr, trx->nr);
+ gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER,
+ bts->nr, trx->nr, 0xff);
+
+ trx->bb_transc.mo.fi = osmo_fsm_inst_alloc(&nm_bb_transc_fsm, trx, &trx->bb_transc,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(trx->bb_transc.mo.fi, "bts%d-trx%d", bts->nr, trx->nr);
+ gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC,
+ bts->nr, trx->nr, 0xff);
+
+ for (k = 0; k < TRX_NR_TS; k++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[k];
+ int l;
+
+
+ ts->trx = trx;
+ ts->nr = k;
+ ts->pchan_from_config = ts->pchan_on_init = ts->pchan_is = GSM_PCHAN_NONE;
+ ts->tsc = -1;
+
+ ts_fsm_alloc(ts);
+
+ ts->mo.fi = osmo_fsm_inst_alloc(&nm_chan_fsm, trx, ts,
+ LOGL_INFO, NULL);
+ osmo_fsm_inst_update_id_f(ts->mo.fi, "bts%d-trx%d-ts%d",
+ bts->nr, trx->nr, ts->nr);
+ gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL,
+ bts->nr, trx->nr, ts->nr);
+
+ ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data);
+ ts->hopping.arfcns.data = ts->hopping.arfcns_data;
+ ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data);
+ ts->hopping.ma.data = ts->hopping.ma_data;
+
+ for (l = 0; l < TS_MAX_LCHAN; l++) {
+ struct gsm_lchan *lchan = &ts->lchan[l];
+ lchan_init(lchan, ts, l);
+ }
+ }
+
+ if (trx->nr != 0)
+ trx->nominal_power = bts->c0->nominal_power;
+
+ if (bts->model && bts->model->trx_init) {
+ if (bts->model->trx_init(trx) < 0) {
+ talloc_free(trx);
+ return NULL;
+ }
+ }
+
+ llist_add_tail(&trx->list, &bts->trx_list);
+
+ return trx;
+}
+
+static char ts2str[255];
+
+char *gsm_trx_name(const struct gsm_bts_trx *trx)
+{
+ if (!trx)
+ snprintf(ts2str, sizeof(ts2str), "(trx=NULL)");
+ else
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)",
+ trx->bts->nr, trx->nr);
+
+ return ts2str;
+}
+
+/* determine logical channel based on TRX and channel number IE */
+struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ int *rc)
+{
+ uint8_t ts_nr = chan_nr & 0x07;
+ uint8_t cbits = chan_nr >> 3;
+ uint8_t lch_idx;
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ bool vamos = false;
+ bool ok;
+
+ if (rc)
+ *rc = -EINVAL;
+
+ /* Why call ts_is_capable_of_pchan() here? Dynamic timeslots may receive RSL Channel Activation ACK on a
+ * timeslot that is in transition between pchan modes. That ACK actually confirms the pchan switch, so instead
+ * of checking the current pchan mode, we must allow any pchans that a dyn TS is capable of. */
+
+ /* Interpret Osmocom specific cbits only for OsmoBTS type */
+ if (trx->bts->model->type == GSM_BTS_TYPE_OSMOBTS) {
+ /* For VAMOS cbits, set vamos = true and handle cbits as their equivalent non-VAMOS cbits below. */
+ switch (cbits) {
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Bm_ACCHs:
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(0):
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(1):
+ cbits = (chan_nr & ~RSL_CHAN_OSMO_VAMOS_MASK) >> 3;
+ vamos = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ switch (cbits) {
+ case ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs:
+ lch_idx = 0; /* TCH/F */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_F)
+ || ts->pchan_on_init == GSM_PCHAN_PDCH; /* PDCH? really? */
+ if (!ok)
+ LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr %x cbits %x: ts %s is not capable of GSM_PCHAN_TCH_F\n",
+ chan_nr, cbits, gsm_ts_and_pchan_name(ts));
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0):
+ case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(1):
+ lch_idx = cbits & 0x1; /* TCH/H */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_H);
+ if (!ok)
+ LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_TCH_H\n",
+ chan_nr, cbits, gsm_ts_and_pchan_name(ts));
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(1):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(2):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(3):
+ lch_idx = cbits & 0x3; /* SDCCH/4 */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH_SDCCH4);
+ if (!ok)
+ LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_CCCH_SDCCH4\n",
+ chan_nr, cbits, gsm_ts_and_pchan_name(ts));
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(1):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(2):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(3):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(4):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(5):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(6):
+ case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(7):
+ lch_idx = cbits & 0x7; /* SDCCH/8 */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_SDCCH8_SACCH8C);
+ if (!ok)
+ LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_SDCCH8_SACCH8C\n",
+ chan_nr, cbits, gsm_ts_and_pchan_name(ts));
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_BCCH:
+ case ABIS_RSL_CHAN_NR_CBITS_RACH:
+ case ABIS_RSL_CHAN_NR_CBITS_PCH_AGCH:
+ lch_idx = 0; /* CCCH? */
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH);
+ if (!ok)
+ LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_CCCH\n",
+ chan_nr, cbits, gsm_ts_and_pchan_name(ts));
+ /* FIXME: we should not return first sdcch4 !!! */
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH:
+ lch_idx = 0;
+ ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_PDCH);
+ if (!ok)
+ LOG_TRX(trx, DRSL, LOGL_ERROR, "chan_nr 0x%x cbits 0x%x: %s is not capable of GSM_PCHAN_PDCH\n",
+ chan_nr, cbits, gsm_ts_and_pchan_name(ts));
+ break;
+ default:
+ return NULL;
+ }
+
+ if (rc && ok)
+ *rc = 0;
+
+ if (vamos)
+ lch_idx += ts->max_primary_lchans;
+ return &ts->lchan[lch_idx];
+}
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason)
+{
+ uint8_t new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+
+ /* State will be sent when BTS connects. */
+ if (!trx->bts || !trx->bts->oml_link) {
+ trx->mo.force_rf_lock = locked;
+ return;
+ }
+
+ LOG_TRX(trx, DNM, LOGL_NOTICE, "Requesting administrative state change %s -> %s [%s]\n",
+ get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative),
+ get_value_string(abis_nm_adm_state_names, new_state), reason);
+
+ osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_FORCE_LOCK, (void*)(intptr_t)locked);
+}
+
+bool trx_is_usable(const struct gsm_bts_trx *trx)
+{
+ /* FIXME: How does this behave for BS-11 ? */
+ if (is_ipa_abisip_bts(trx->bts)) {
+ if (!nm_is_running(&trx->mo.nm_state) ||
+ !nm_is_running(&trx->bb_transc.mo.nm_state))
+ return false;
+ } else if (is_ericsson_bts(trx->bts)) {
+ /* The OM2000 -> 12.21 mapping we do doesn't have separate bb_transc MO */
+ if (!nm_is_running(&trx->mo.nm_state))
+ return false;
+ }
+
+ return true;
+}
+
+
+void gsm_trx_all_ts_dispatch(struct gsm_bts_trx *trx, uint32_t ts_ev, void *data)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ osmo_fsm_inst_dispatch(ts->fi, ts_ev, data);
+ }
+}
+
+bool trx_has_valid_pchan_config(const struct gsm_bts_trx *trx)
+{
+ bool combined = false;
+ bool result = true;
+ unsigned int i;
+
+ /* Iterate over all timeslots */
+ for (i = 0; i < 8; i++) {
+ const struct gsm_bts_trx_ts *ts = &trx->ts[i];
+
+ switch (ts->pchan_from_config) {
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ case GSM_PCHAN_CCCH_SDCCH4:
+ /* CCCH+SDCCH4 can only be configured on TS0 */
+ if (i > 0) {
+ LOGP(DNM, LOGL_ERROR, "Combined CCCH is not allowed "
+ "on TS%u > 0\n", i);
+ result = false;
+ }
+ if (i == 0)
+ combined = true;
+ /* fall-through */
+ case GSM_PCHAN_CCCH:
+ /* 3GPP TS 45.002, Table 3, CCCH: TS (0, 2, 4, 6) */
+ if (i % 2 != 0) {
+ LOGP(DNM, LOGL_ERROR, "%s is not allowed on odd TS%u\n",
+ gsm_pchan_name(ts->pchan_from_config), i);
+ result = false;
+ }
+
+ /* There can be no more CCCHs if TS0/C0 is combined */
+ if (i > 0 && combined) {
+ LOGP(DNM, LOGL_ERROR, "%s is not allowed on TS%u, "
+ "because TS0 is using combined channel configuration\n",
+ gsm_pchan_name(ts->pchan_from_config), i);
+ result = false;
+ }
+ break;
+
+ case GSM_PCHAN_PDCH:
+ if (is_ericsson_bts(trx->bts)) {
+ /* NOTE: A static PDCH is usually handled by the BTS/PCU internally, the BSC
+ * will not actively manage this channel. It will just keep the timeslot
+ * unused so that it is free for the BTS/PCU to use it as PDCH. Not all BTSs
+ * work well in this scheme. Ericsson RBS BTSs support dynamic channels natively
+ * and require a channel activation on RSL level before the PDCH can be used.
+ * One could work around this by activating the PDCH once on startup and
+ * leave it on indefinetly but we decided not to do so. Users of Ericsson RBS
+ * BTSs must configure a dynamic PDCH channel. */
+ LOGP(DNM, LOGL_ERROR, "%s is not allowed, because Ericsson RBS does"
+ "not support static PDCH (use TCH/F_TCH/H_SDCCH8_PDCH)\n",
+ gsm_pchan_name(ts->pchan_from_config));
+ result = false;
+ }
+ break;
+
+ default:
+ /* CCCH on TS0 is mandatory for C0 */
+ if (trx->bts->c0 == trx && i == 0) {
+ LOGP(DNM, LOGL_ERROR, "TS0 on C0 must be CCCH/BCCH\n");
+ result = false;
+ }
+ }
+
+ if (trx->bts->features_known) {
+ const struct bitvec *ft = &trx->bts->features;
+
+ if (ts->hopping.enabled && !osmo_bts_has_feature(ft, BTS_FEAT_HOPPING)) {
+ LOGP(DNM, LOGL_ERROR, "TS%d has freq. hopping enabled, but BTS does not support it\n", i);
+ result = false;
+ }
+
+ if (ts->tsc != -1 && !osmo_bts_has_feature(ft, BTS_FEAT_MULTI_TSC)) {
+ LOGP(DNM, LOGL_ERROR, "TS%d has TSC != BCC, but BTS does not support it\n", i);
+ result = false;
+ }
+ }
+ }
+
+ return result;
+}
+
+static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len)
+{
+ struct gsm_bts *bts = trx->bts;
+ int rc, j;
+
+ if (si_len) {
+ DEBUGP(DRR, "SI%s: %s\n", get_value_string(osmo_sitype_strs, i),
+ osmo_hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN));
+ } else
+ DEBUGP(DRR, "SI%s: OFF\n", get_value_string(osmo_sitype_strs, i));
+
+ switch (i) {
+ case SYSINFO_TYPE_5:
+ case SYSINFO_TYPE_5bis:
+ case SYSINFO_TYPE_5ter:
+ case SYSINFO_TYPE_6:
+ rc = rsl_sacch_filling(trx, osmo_sitype2rsl(i),
+ si_len ? GSM_BTS_SI(bts, i) : NULL, si_len);
+ break;
+ case SYSINFO_TYPE_2quater:
+ if (si_len == 0) {
+ rc = rsl_bcch_info(trx, i, NULL, 0);
+ break;
+ }
+ rc = 0;
+ for (j = 0; j <= bts->si2q_count; j++)
+ rc = rsl_bcch_info(trx, i, (const uint8_t *)GSM_BTS_SI2Q(bts, j), GSM_MACBLOCK_LEN);
+ break;
+ default:
+ rc = rsl_bcch_info(trx, i, si_len ? GSM_BTS_SI(bts, i) : NULL, si_len);
+ break;
+ }
+
+ return rc;
+}
+
+/* set all system information types for a TRX */
+int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx)
+{
+ int rc;
+ struct gsm_bts *bts = trx->bts;
+ uint8_t gen_si[_MAX_SYSINFO_TYPE], n_si = 0, n;
+ int si_len[_MAX_SYSINFO_TYPE];
+
+ bts->si_common.cell_sel_par.ms_txpwr_max_ccch =
+ ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+ bts->si_common.cell_sel_par.neci = bts->network->neci;
+
+ /* Zero/forget the state of the dynamically computed SIs, leeping the static ones */
+ bts->si_valid = bts->si_mode_static;
+
+ /* First, we determine which of the SI messages we actually need */
+
+ if (trx == bts->c0) {
+ /* 1...4 are always present on a C0 TRX */
+ gen_si[n_si++] = SYSINFO_TYPE_1;
+ gen_si[n_si++] = SYSINFO_TYPE_2;
+ gen_si[n_si++] = SYSINFO_TYPE_2bis;
+ gen_si[n_si++] = SYSINFO_TYPE_2ter;
+ gen_si[n_si++] = SYSINFO_TYPE_2quater;
+ gen_si[n_si++] = SYSINFO_TYPE_3;
+ gen_si[n_si++] = SYSINFO_TYPE_4;
+
+ /* 13 is always present on a C0 TRX of a GPRS BTS */
+ if (bts->gprs.mode != BTS_GPRS_NONE)
+ gen_si[n_si++] = SYSINFO_TYPE_13;
+ }
+
+ /* 5 and 6 are always present on every TRX */
+ gen_si[n_si++] = SYSINFO_TYPE_5;
+ gen_si[n_si++] = SYSINFO_TYPE_5bis;
+ gen_si[n_si++] = SYSINFO_TYPE_5ter;
+ gen_si[n_si++] = SYSINFO_TYPE_6;
+
+ /* Second, we generate the selected SI via RSL */
+
+ for (n = 0; n < n_si; n++) {
+ const enum osmo_sysinfo_type si_type = gen_si[n];
+
+ /* Only generate SI if this SI is not in "static" (user-defined) mode */
+ if (!(bts->si_mode_static & (1 << si_type))) {
+ /* Set SI as being valid. gsm_generate_si() might unset
+ * it, if SI is not required. */
+ bts->si_valid |= (1 << si_type);
+ rc = gsm_generate_si(bts, si_type);
+ if (rc < 0)
+ goto err_out;
+ si_len[si_type] = rc;
+ } else {
+ switch (si_type) {
+ case SYSINFO_TYPE_5:
+ case SYSINFO_TYPE_5bis:
+ case SYSINFO_TYPE_5ter:
+ si_len[si_type] = 18;
+ break;
+ case SYSINFO_TYPE_6:
+ si_len[si_type] = 11;
+ break;
+ case SYSINFO_TYPE_10:
+ si_len[si_type] = 21;
+ break;
+ default:
+ si_len[si_type] = GSM_MACBLOCK_LEN;
+ }
+ }
+ }
+
+ /* Third, we send the selected SI via RSL */
+
+ for (n = 0; n < n_si; n++) {
+ const enum osmo_sysinfo_type si_type = gen_si[n];
+
+ /* 3GPP TS 08.58 ยง8.5.1 BCCH INFORMATION. If we don't currently
+ * have this SI, we send a zero-length RSL BCCH FILLING /
+ * SACCH FILLING in order to deactivate the SI, in case it
+ * might have previously been active */
+ if (!GSM_BTS_HAS_SI(bts, si_type)) {
+ if (bts->si_unused_send_empty)
+ rc = rsl_si(trx, si_type, 0);
+ else
+ rc = 0; /* some nanoBTS fw don't like receiving empty unsupported SI */
+ } else
+ rc = rsl_si(trx, si_type, si_len[si_type]);
+ if (rc < 0)
+ return rc;
+ }
+
+ /* Make sure the PCU is aware (in case anything GPRS related has
+ * changed in SI */
+ pcu_info_update(bts);
+
+ return 0;
+err_out:
+ LOGP(DRR, LOGL_ERROR, "Cannot generate SI%s for BTS %u: error <%s>, "
+ "most likely a problem with neighbor cell list generation\n",
+ get_value_string(osmo_sitype_strs, gen_si[n]), bts->nr, strerror(-rc));
+ return rc;
+}
diff --git a/src/osmo-bsc/bts_trx_ctrl.c b/src/osmo-bsc/bts_trx_ctrl.c
new file mode 100644
index 000000000..1e30e8518
--- /dev/null
+++ b/src/osmo-bsc/bts_trx_ctrl.c
@@ -0,0 +1,157 @@
+/*
+ * (C) 2013-2015 by Holger Hans Peter Freyther
+ * (C) 2013-2022 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/ctrl/control_cmd.h>
+
+#include <osmocom/bsc/ctrl.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/abis_nm.h>
+
+static int get_trx_rf_locked(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ /* Return rf_locked = 1 only if it is explicitly locked. If it is in shutdown or null state, do not "trick" the
+ * caller into thinking that sending "rf_locked 0" is necessary to bring the TRX up. */
+ cmd->reply = (trx->mo.nm_state.administrative == NM_STATE_LOCKED) ? "1" : "0";
+ return CTRL_CMD_REPLY;
+}
+
+static int set_trx_rf_locked(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ int locked;
+ if (osmo_str_to_int(&locked, cmd->value, 10, 0, 1)) {
+ cmd->reply = "Invalid value";
+ return CTRL_CMD_ERROR;
+ }
+
+ gsm_trx_lock_rf(trx, locked, "ctrl");
+
+ /* Let's not assume the nm FSM has already switched its state, just return the intended rf_locked value. */
+ cmd->reply = locked ? "1" : "0";
+ return CTRL_CMD_REPLY;
+}
+
+static int verify_trx_rf_locked(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ return osmo_str_to_int(NULL, value, 10, 0, 1);
+}
+CTRL_CMD_DEFINE(trx_rf_locked, "rf_locked");
+
+/* TRX related commands below here */
+CTRL_HELPER_GET_INT(trx_max_power, struct gsm_bts_trx, max_power_red);
+static int verify_trx_max_power(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int tmp = atoi(value);
+
+ if (tmp < 0 || tmp > 22) {
+ cmd->reply = "Value must be between 0 and 22";
+ return -1;
+ }
+
+ if (tmp & 1) {
+ cmd->reply = "Value must be even";
+ return -1;
+ }
+
+ return 0;
+}
+CTRL_CMD_DEFINE_RANGE(trx_arfcn, "arfcn", struct gsm_bts_trx, arfcn, 0, 1023);
+
+static int set_trx_max_power(struct ctrl_cmd *cmd, void *_data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ int old_power;
+
+ /* remember the old value, set the new one */
+ old_power = trx->max_power_red;
+ trx->max_power_red = atoi(cmd->value);
+
+ /* Maybe update the value */
+ if (old_power != trx->max_power_red) {
+ LOGP(DCTRL, LOGL_NOTICE,
+ "%s updating max_pwr_red(%d)\n",
+ gsm_trx_name(trx), trx->max_power_red);
+ abis_nm_update_max_power_red(trx);
+ }
+
+ return get_trx_max_power(cmd, _data);
+}
+CTRL_CMD_DEFINE(trx_max_power, "max-power-reduction");
+
+char *trx_lchan_dump_full_ctrl(const void *t, struct gsm_bts_trx *trx)
+{
+ int ts_nr;
+ bool first_ts = true;
+ char *ts_dump, *dump;
+
+ dump = talloc_strdup(t, "");
+ if (!dump)
+ return NULL;
+
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts_dump = ts_lchan_dump_full_ctrl(t, &trx->ts[ts_nr]);
+ if (!ts_dump)
+ return NULL;
+ if (!strlen(ts_dump))
+ continue;
+ dump = talloc_asprintf_append(dump, first_ts ? "%s" : "\n%s", ts_dump);
+ if (!dump)
+ return NULL;
+ first_ts = false;
+ }
+
+ return dump;
+}
+
+/* Return full information about all logical channels in a TRX.
+ * format: bts.<0-255>.trx.<0-255>.show-lchan.full
+ * result format: New line delimited list of <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>,
+ * <ms power>,<interference dbm>, <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>,
+ * <ipa bound conn id>,<ipa conn ip>,<ipa conn port>,<ipa conn speech mode>
+ */
+static int get_trx_show_lchan_full(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+
+ cmd->reply = trx_lchan_dump_full_ctrl(cmd, trx);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(trx_show_lchan_full, "show-lchan full");
+
+int bsc_bts_trx_ctrl_cmds_install(void)
+{
+ int rc = 0;
+
+ rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_max_power);
+ rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_arfcn);
+ rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_rf_locked);
+ rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_show_lchan_full);
+
+ rc |= bsc_bts_trx_ts_ctrl_cmds_install();
+
+ return rc;
+}
diff --git a/src/osmo-bsc/bts_trx_ts_ctrl.c b/src/osmo-bsc/bts_trx_ts_ctrl.c
new file mode 100644
index 000000000..a1a17f0d2
--- /dev/null
+++ b/src/osmo-bsc/bts_trx_ts_ctrl.c
@@ -0,0 +1,151 @@
+/*
+ * (C) 2013-2015 by Holger Hans Peter Freyther
+ * (C) 2013-2022 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/ctrl/control_cmd.h>
+
+#include <osmocom/bsc/ctrl.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/system_information.h>
+
+static int verify_ts_hopping_arfcn_add(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int64_t arfcn;
+ enum gsm_band unused;
+ if (osmo_str_to_int64(&arfcn, value, 10, 0, 1024) < 0)
+ return 1;
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0)
+ return 1;
+ return 0;
+}
+static int set_ts_hopping_arfcn_add(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx_ts *ts = cmd->node;
+ int arfcn = atoi(cmd->value);
+
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ONE);
+
+ /* Update Cell Allocation (list of all the frequencies allocated to a cell) */
+ if (generate_cell_chan_alloc(ts->trx->bts) != 0) {
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ZERO); /* roll-back */
+ cmd->reply = "Failed to re-generate Cell Allocation";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+/* Parameter format: "<arfcn>" */
+CTRL_CMD_DEFINE_WO(ts_hopping_arfcn_add, "hopping-arfcn-add");
+
+static int verify_ts_hopping_arfcn_del(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int64_t arfcn;
+ enum gsm_band unused;
+ if (strcmp(value, "all") == 0)
+ return 0;
+ if (osmo_str_to_int64(&arfcn, value, 10, 0, 1024) < 0)
+ return 1;
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0)
+ return 1;
+ return 0;
+}
+static int set_ts_hopping_arfcn_del(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx_ts *ts = cmd->node;
+ bool all = (strcmp(cmd->value, "all") == 0);
+ int arfcn;
+
+ if (all) {
+ bitvec_zero(&ts->hopping.arfcns);
+ } else {
+ arfcn = atoi(cmd->value);
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ZERO);
+ }
+
+ /* Update Cell Allocation (list of all the frequencies allocated to a cell) */
+ if (generate_cell_chan_alloc(ts->trx->bts) != 0) {
+ if (!all)
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ONE); /* roll-back */
+ cmd->reply = "Failed to re-generate Cell Allocation";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+/* Parameter format: "(<arfcn>|all)" */
+CTRL_CMD_DEFINE_WO(ts_hopping_arfcn_del, "hopping-arfcn-del");
+
+char *ts_lchan_dump_full_ctrl(const void *t, struct gsm_bts_trx_ts *ts)
+{
+ bool first_lchan = true;
+ char *lchan_dump, *dump;
+ struct gsm_lchan *lchan;
+
+ dump = talloc_strdup(t, "");
+ if (!dump)
+ return NULL;
+
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
+ lchan_dump = lchan_dump_full_ctrl(t, lchan);
+ if (!lchan_dump)
+ return NULL;
+ dump = talloc_asprintf_append(dump, first_lchan ? "%s" : "\n%s", lchan_dump);
+ if (!dump)
+ return NULL;
+ first_lchan = false;
+ }
+
+ return dump;
+}
+
+/* Return full information about all logical channels in a timeslot.
+ * format: bts.<0-255>.trx.<0-255>.ts.<0-8>.show-lchan.full
+ * result format: New line delimited list of <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>,
+ * <ms power>,<interference dbm>, <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>,
+ * <ipa bound conn id>,<ipa conn ip>,<ipa conn port>,<ipa conn speech mode>
+ */
+static int get_ts_show_lchan_full(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx_ts *ts = cmd->node;
+
+ cmd->reply = ts_lchan_dump_full_ctrl(cmd, ts);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(ts_show_lchan_full, "show-lchan full");
+
+int bsc_bts_trx_ts_ctrl_cmds_install(void)
+{
+ int rc = 0;
+
+ rc |= ctrl_cmd_install(CTRL_NODE_TS, &cmd_ts_hopping_arfcn_add);
+ rc |= ctrl_cmd_install(CTRL_NODE_TS, &cmd_ts_hopping_arfcn_del);
+ rc |= ctrl_cmd_install(CTRL_NODE_TS, &cmd_ts_show_lchan_full);
+
+ rc |= bsc_bts_trx_ts_lchan_ctrl_cmds_install();
+
+ return rc;
+}
diff --git a/src/osmo-bsc/bts_trx_ts_lchan_ctrl.c b/src/osmo-bsc/bts_trx_ts_lchan_ctrl.c
new file mode 100644
index 000000000..be5e755cb
--- /dev/null
+++ b/src/osmo-bsc/bts_trx_ts_lchan_ctrl.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 by sysmocom s.f.m.c. GmbH
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation; either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmocom/bsc/ctrl.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/lchan_fsm.h>
+
+static int verify_lchan_ms_power(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int ms_power = atoi(cmd->value);
+
+ if (ms_power < 0 || ms_power > 40) {
+ cmd->reply = "Value is out of range";
+ return 1;
+ }
+
+ return 0;
+}
+
+/* power control management: Get lchan's ms power in dBm
+ * format: bts.<0-255>.trx.<0-255>.ts.<0-8>.lchan.<0-8>.ms-power */
+static int get_lchan_ms_power(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_lchan *lchan = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%u", ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power));
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+/* power control management: Set lchan's ms power in dBm.
+ * For static ms power control it will change the ms tx power.
+ * For dynamic ms power control it will limit the maximum power level.
+ * format: bts.<0-255>.trx.<0-255>.ts.<0-8>.lchan.<0-8>.ms-power <ms power>
+ * ms power is in range 0..40 */
+static int set_lchan_ms_power(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_lchan *lchan = cmd->node;
+
+ lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, atoi(cmd->value));
+ rsl_chan_ms_power_ctrl(lchan);
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(lchan_ms_power, "ms-power");
+
+
+char *lchan_dump_full_ctrl(const void *t, struct gsm_lchan *lchan)
+{
+ struct in_addr ia;
+ char *interference = ",", *tmsi = "", *ipa_bound = ",,", *ipa_conn = ",,";
+
+ if (lchan->interf_dbm != INTERF_DBM_UNKNOWN) {
+ interference = talloc_asprintf(t, "%d,%u", lchan->interf_dbm, lchan->interf_band);
+ if (!interference)
+ return NULL;
+ }
+
+ if (lchan->conn && lchan->conn->bsub && lchan->conn->bsub->tmsi != GSM_RESERVED_TMSI) {
+ tmsi = talloc_asprintf(t, "0x%08x", lchan->conn->bsub->tmsi);
+ if (!tmsi)
+ return NULL;
+ }
+
+ if (is_ipa_abisip_bts(lchan->ts->trx->bts) && lchan->abis_ip.bound_ip) {
+ ia.s_addr = htonl(lchan->abis_ip.bound_ip);
+ ipa_bound = talloc_asprintf(t, "%s,%u,%u", inet_ntoa(ia), lchan->abis_ip.bound_port,
+ lchan->abis_ip.conn_id);
+ if (!ipa_bound)
+ return NULL;
+ }
+
+ if (is_ipa_abisip_bts(lchan->ts->trx->bts) && lchan->abis_ip.connect_ip) {
+ ia.s_addr = htonl(lchan->abis_ip.connect_ip);
+ ipa_conn = talloc_asprintf(t, "%s,%u,0x%02x", inet_ntoa(ia), lchan->abis_ip.connect_port,
+ lchan->abis_ip.speech_mode);
+ if (!ipa_conn)
+ return NULL;
+ }
+
+ return talloc_asprintf(t, "%u,%u,%u,%u,%s,%u,%s,%s,%u,%u,%s,%s,%s,%s,%s,%s",
+ lchan->ts->trx->bts->nr,
+ lchan->ts->trx->nr,
+ lchan->ts->nr,
+ lchan->nr,
+ gsm_chan_t_name(lchan->type),
+ lchan->conn ? 1 : 0, lchan_state_name(lchan),
+ lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? lchan->last_error : "",
+ lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red - lchan->bs_power_db,
+ ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
+ interference,
+ gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode),
+ lchan->conn && lchan->conn->bsub && strlen(lchan->conn->bsub->imsi) ? lchan->conn->bsub->imsi : "",
+ tmsi,
+ ipa_bound,
+ ipa_conn
+ );
+}
+
+/* Return full information about a logical channel.
+ * format: bts.<0-255>.trx.<0-255>.ts.<0-8>.lchan.<0-8>.show.full
+ * result format: <bts>,<trx>,<ts>,<lchan>,<type>,<connection>,<state>,<last error>,<bs power>,<ms power>,<interference dbm>,
+ * <interference band>,<channel mode>,<imsi>,<tmsi>,<ipa bound ip>,<ipa bound port>,<ipa bound conn id>,<ipa conn ip>,
+ * <ipa conn port>,<ipa conn speech mode>
+ */
+static int get_lchan_show_full(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_lchan *lchan = cmd->node;
+ cmd->reply = lchan_dump_full_ctrl(cmd, lchan);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(lchan_show_full, "show full");
+
+
+int bsc_bts_trx_ts_lchan_ctrl_cmds_install(void)
+{
+ int rc = 0;
+
+ rc |= ctrl_cmd_install(CTRL_NODE_LCHAN, &cmd_lchan_ms_power);
+ rc |= ctrl_cmd_install(CTRL_NODE_LCHAN, &cmd_lchan_show_full);
+
+ return rc;
+}
diff --git a/src/osmo-bsc/bts_trx_vty.c b/src/osmo-bsc/bts_trx_vty.c
new file mode 100644
index 000000000..9a58089a4
--- /dev/null
+++ b/src/osmo-bsc/bts_trx_vty.c
@@ -0,0 +1,881 @@
+/* OsmoBSC interface to quagga VTY, TRX (and TS) node */
+/* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/lchan.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/bts.h>
+
+#include <inttypes.h>
+
+#include "../../bscconfig.h"
+
+#define X(x) (1 << x)
+
+static struct cmd_node trx_node = {
+ TRX_NODE,
+ "%s(config-net-bts-trx)# ",
+ 1,
+};
+
+static struct cmd_node ts_node = {
+ TS_NODE,
+ "%s(config-net-bts-trx-ts)# ",
+ 1,
+};
+
+/* utility functions */
+void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line,
+ const char *ts, const char *ss)
+{
+ e1_link->e1_nr = atoi(line);
+ e1_link->e1_ts = atoi(ts);
+ if (!strcmp(ss, "full"))
+ e1_link->e1_ts_ss = E1_SUBSLOT_FULL;
+ else
+ e1_link->e1_ts_ss = atoi(ss);
+}
+
+#define TRX_TEXT "Radio Transceiver\n"
+
+/* per TRX configuration */
+DEFUN_ATTR(cfg_trx,
+ cfg_trx_cmd,
+ "trx <0-255>",
+ TRX_TEXT
+ "Select a TRX to configure\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_trx *trx;
+
+ if (trx_nr > bts->num_trx) {
+ vty_out(vty, "%% The next unused TRX number in this BTS is %u%s",
+ bts->num_trx, VTY_NEWLINE);
+ return CMD_WARNING;
+ } else if (trx_nr == bts->num_trx) {
+ /* we need to allocate a new one */
+ trx = gsm_bts_trx_alloc(bts);
+ } else
+ trx = gsm_bts_trx_num(bts, trx_nr);
+
+ if (!trx)
+ return CMD_WARNING;
+
+ vty->index = trx;
+ vty->node = TRX_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_trx_arfcn,
+ cfg_trx_arfcn_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "arfcn <0-1023>",
+ "Set the ARFCN for this TRX\n"
+ "Absolute Radio Frequency Channel Number\n")
+{
+ enum gsm_band unused;
+ struct gsm_bts_trx *trx = vty->index;
+ int arfcn = atoi(argv[0]);
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* FIXME: check if this ARFCN is supported by this TRX */
+
+ trx->arfcn = arfcn;
+
+ /* Update Cell Allocation (list of all the frequencies allocated to a cell) */
+ if (generate_cell_chan_alloc(trx->bts) != 0) {
+ vty_out(vty, "%% Failed to re-generate Cell Allocation%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* FIXME: patch ARFCN into SYSTEM INFORMATION */
+ /* FIXME: use OML layer to update the ARFCN */
+ /* FIXME: use RSL layer to update SYSTEM INFORMATION */
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_trx_nominal_power,
+ cfg_trx_nominal_power_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "nominal power <-20-100>",
+ "Nominal TRX RF Power in dBm\n"
+ "Nominal TRX RF Power in dBm\n"
+ "Nominal TRX RF Power in dBm\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->nominal_power = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_trx_max_power_red,
+ cfg_trx_max_power_red_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "max_power_red <0-100>",
+ "Reduction of maximum BS RF Power (relative to nominal power)\n"
+ "Reduction of maximum BS RF Power in dB\n")
+{
+ int maxpwr_r = atoi(argv[0]);
+ struct gsm_bts_trx *trx = vty->index;
+ int upper_limit = 24; /* default 12.21 max power red. */
+
+ /* FIXME: check if our BTS type supports more than 12 */
+ if (maxpwr_r < 0 || maxpwr_r > upper_limit) {
+ vty_out(vty, "%% Power %d dB is not in the valid range%s",
+ maxpwr_r, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (maxpwr_r & 1) {
+ vty_out(vty, "%% Power %d dB is not an even value%s",
+ maxpwr_r, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ trx->max_power_red = maxpwr_r;
+
+ /* FIXME: make sure we update this using OML */
+
+ return CMD_SUCCESS;
+}
+
+/* NOTE: This requires a full restart as bsc_network_configure() is executed
+ * only once on startup from osmo_bsc_main.c */
+DEFUN(cfg_trx_rsl_e1,
+ cfg_trx_rsl_e1_cmd,
+ "rsl e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+ "RSL Parameters\n"
+ "E1/T1 interface to be used for RSL\n"
+ "E1/T1 interface to be used for RSL\n"
+ "E1/T1 Line Number to be used for RSL\n"
+ "E1/T1 Timeslot to be used for RSL\n"
+ "E1/T1 Timeslot to be used for RSL\n"
+ "E1/T1 Sub-slot to be used for RSL\n"
+ "E1/T1 Sub-slot 0 is to be used for RSL\n"
+ "E1/T1 Sub-slot 1 is to be used for RSL\n"
+ "E1/T1 Sub-slot 2 is to be used for RSL\n"
+ "E1/T1 Sub-slot 3 is to be used for RSL\n"
+ "E1/T1 full timeslot is to be used for RSL\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ parse_e1_link(&trx->rsl_e1_link, argv[0], argv[1], argv[2]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_trx_rsl_e1_tei,
+ cfg_trx_rsl_e1_tei_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "rsl e1 tei <0-63>",
+ "RSL Parameters\n"
+ "Set the TEI to be used for RSL\n"
+ "Set the TEI to be used for RSL\n"
+ "TEI to be used for RSL\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->rsl_tei_primary = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_trx_rf_locked,
+ cfg_trx_rf_locked_cmd,
+ "rf_locked (0|1)",
+ "Set or unset the RF Locking (Turn off RF of the TRX)\n"
+ "TRX is NOT RF locked (active)\n"
+ "TRX is RF locked (turned off)\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ int locked = atoi(argv[0]);
+ struct gsm_bts_trx *trx = vty->index;
+
+ gsm_trx_lock_rf(trx, locked, "vty");
+ return CMD_SUCCESS;
+}
+
+/* per TS configuration */
+DEFUN_ATTR(cfg_ts,
+ cfg_ts_cmd,
+ "timeslot <0-7>",
+ "Select a Timeslot to configure\n"
+ "Timeslot number\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ int ts_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = vty->index;
+ struct gsm_bts_trx_ts *ts;
+
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s",
+ TRX_NR_TS, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ts = &trx->ts[ts_nr];
+
+ vty->index = ts;
+ vty->node = TS_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_ts_pchan,
+ cfg_ts_pchan_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "phys_chan_config PCHAN", /* dynamically generated! */
+ "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int pchanc;
+
+ pchanc = gsm_pchan_parse(argv[0]);
+ if (pchanc < 0)
+ return CMD_WARNING;
+
+ ts->pchan_from_config = pchanc;
+
+ return CMD_SUCCESS;
+}
+
+/* used for backwards compatibility with old config files that still
+ * have uppercase pchan type names. Also match older names for existing types. */
+DEFUN_HIDDEN(cfg_ts_pchan_compat,
+ cfg_ts_pchan_compat_cmd,
+ "phys_chan_config PCHAN",
+ "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int pchanc;
+
+ pchanc = gsm_pchan_parse(argv[0]);
+ if (pchanc < 0) {
+ if (strcasecmp(argv[0], "tch/f_tch/h_pdch") == 0) {
+ pchanc = GSM_PCHAN_OSMO_DYN;
+ } else {
+ vty_out(vty, "Unknown physical channel name '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_ERR_NO_MATCH;
+ }
+ }
+
+ ts->pchan_from_config = pchanc;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_ts_tsc,
+ cfg_ts_tsc_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "training_sequence_code <0-7>",
+ "Training Sequence Code of the Timeslot\n" "TSC\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+ const struct gsm_bts *bts = ts->trx->bts;
+
+ if (bts->features_known && !osmo_bts_has_feature(&bts->features, BTS_FEAT_MULTI_TSC)) {
+ vty_out(vty, "%% This BTS does not support a TSC != BCC, "
+ "falling back to BCC%s", VTY_NEWLINE);
+ ts->tsc = -1;
+ return CMD_WARNING;
+ }
+
+ ts->tsc = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define HOPPING_STR "Configure frequency hopping\n"
+
+DEFUN_USRATTR(cfg_ts_hopping,
+ cfg_ts_hopping_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "hopping enabled (0|1)",
+ HOPPING_STR "Enable or disable frequency hopping\n"
+ "Disable frequency hopping\n" "Enable frequency hopping\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+ const struct gsm_bts *bts = ts->trx->bts;
+ int enabled = atoi(argv[0]);
+
+ if (enabled && bts->features_known && !osmo_bts_has_feature(&bts->features, BTS_FEAT_HOPPING)) {
+ vty_out(vty, "%% BTS does not support freq. hopping%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (enabled && bts->imm_ass_time != IMM_ASS_TIME_POST_CHAN_ACK) {
+ vty_out(vty,
+ "%% ERROR: 'hopping enabled 1' works only with 'immediate-assignment post-chan-ack'%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ts->hopping.enabled = enabled;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_ts_hsn,
+ cfg_ts_hsn_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "hopping sequence-number <0-63>",
+ HOPPING_STR
+ "Which hopping sequence to use for this channel\n"
+ "Hopping Sequence Number (HSN)\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+
+ ts->hopping.hsn = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_ts_maio,
+ cfg_ts_maio_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "hopping maio <0-63>",
+ HOPPING_STR
+ "Which hopping MAIO to use for this channel\n"
+ "Mobile Allocation Index Offset (MAIO)\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+
+ ts->hopping.maio = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_ts_arfcn_add,
+ cfg_ts_arfcn_add_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "hopping arfcn add <0-1023>",
+ HOPPING_STR "Configure hopping ARFCN list\n"
+ "Add an entry to the hopping ARFCN list\n" "ARFCN\n")
+{
+ enum gsm_band unused;
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int arfcn = atoi(argv[0]);
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bitvec_get_bit_pos(&ts->hopping.arfcns, arfcn) == ONE) {
+ vty_out(vty, "%% ARFCN %" PRIu16 " is already set%s", arfcn, VTY_NEWLINE);
+ return CMD_SUCCESS;
+ }
+
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1);
+
+ /* Update Cell Allocation (list of all the frequencies allocated to a cell) */
+ if (generate_cell_chan_alloc(ts->trx->bts) != 0) {
+ vty_out(vty, "%% Failed to re-generate Cell Allocation%s", VTY_NEWLINE);
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, ZERO); /* roll-back */
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_ts_arfcn_del,
+ cfg_ts_arfcn_del_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "hopping arfcn del <0-1023>",
+ HOPPING_STR "Configure hopping ARFCN list\n"
+ "Delete an entry to the hopping ARFCN list\n" "ARFCN\n")
+{
+ enum gsm_band unused;
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int arfcn = atoi(argv[0]);
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bitvec_get_bit_pos(&ts->hopping.arfcns, arfcn) != ONE) {
+ vty_out(vty, "%% ARFCN %" PRIu16 " is not set%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0);
+
+ /* Update Cell Allocation (list of all the frequencies allocated to a cell) */
+ if (generate_cell_chan_alloc(ts->trx->bts) != 0) {
+ vty_out(vty, "%% Failed to re-generate Cell Allocation%s", VTY_NEWLINE);
+ /* It's unlikely to happen on removal, so we don't roll-back */
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_ts_arfcn_del_all,
+ cfg_ts_arfcn_del_all_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "hopping arfcn del-all",
+ HOPPING_STR "Configure hopping ARFCN list\n"
+ "Delete all previously configured entries\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+
+ bitvec_zero(&ts->hopping.arfcns);
+
+ /* Update Cell Allocation (list of all the frequencies allocated to a cell) */
+ if (generate_cell_chan_alloc(ts->trx->bts) != 0) {
+ vty_out(vty, "%% Failed to re-generate Cell Allocation%s", VTY_NEWLINE);
+ /* It's unlikely to happen on removal, so we don't roll-back */
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* NOTE: This will have an effect on newly created voice lchans since the E1
+ * voice channels are handled by osmo-mgw and the information put in e1_link
+ * here is only used to generate the MGCP messages for the mgw. */
+DEFUN_ATTR(cfg_ts_e1_subslot,
+ cfg_ts_e1_subslot_cmd,
+ "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+ "E1/T1 channel connected to this on-air timeslot\n"
+ "E1/T1 channel connected to this on-air timeslot\n"
+ "E1/T1 line connected to this on-air timeslot\n"
+ "E1/T1 timeslot connected to this on-air timeslot\n"
+ "E1/T1 timeslot connected to this on-air timeslot\n"
+ "E1/T1 sub-slot connected to this on-air timeslot\n"
+ "E1/T1 sub-slot 0 connected to this on-air timeslot\n"
+ "E1/T1 sub-slot 1 connected to this on-air timeslot\n"
+ "E1/T1 sub-slot 2 connected to this on-air timeslot\n"
+ "E1/T1 sub-slot 3 connected to this on-air timeslot\n"
+ "Full E1/T1 timeslot connected to this on-air timeslot\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+
+ parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]);
+
+ return CMD_SUCCESS;
+}
+
+/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots.
+ * Don't do anything if the ts is not dynamic. */
+static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config target;
+ if (ts_is_pchan_switching(ts, &target)) {
+ vty_out(vty, " switching %s -> %s", gsm_pchan_name(ts->pchan_is),
+ gsm_pchan_name(target));
+ } else if (ts->pchan_is != ts->pchan_on_init) {
+ vty_out(vty, " as %s", gsm_pchan_name(ts->pchan_is));
+ }
+}
+
+static void vty_out_dyn_ts_details(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ /* show dyn TS details, if applicable */
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_OSMO_DYN:
+ vty_out(vty, " Osmocom Dyn TS:");
+ vty_out_dyn_ts_status(vty, ts);
+ vty_out(vty, VTY_NEWLINE);
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ vty_out(vty, " IPACC Dyn PDCH TS:");
+ vty_out_dyn_ts_status(vty, ts);
+ vty_out(vty, VTY_NEWLINE);
+ break;
+ default:
+ /* no dyn ts */
+ break;
+ }
+}
+
+static void meas_rep_dump_uni_vty(struct vty *vty,
+ struct gsm_meas_rep_unidir *mru,
+ const char *prefix,
+ const char *dir)
+{
+ vty_out(vty, "%s RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ",
+ prefix, dir, rxlev2dbm(mru->full.rx_lev),
+ dir, rxlev2dbm(mru->sub.rx_lev));
+ vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s",
+ dir, mru->full.rx_qual, dir, mru->sub.rx_qual,
+ VTY_NEWLINE);
+}
+
+static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr,
+ const char *prefix)
+{
+ vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE);
+ vty_out(vty, "%s Flags: %s%s%s%s%s", prefix,
+ mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "",
+ mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "",
+ mr->flags & MEAS_REP_F_FPC ? "FPC " : "",
+ mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ",
+ VTY_NEWLINE);
+ if (mr->flags & MEAS_REP_F_MS_TO)
+ vty_out(vty, "%s MS Timing Offset: %d%s", prefix, mr->ms_timing_offset, VTY_NEWLINE);
+ if (mr->flags & MEAS_REP_F_MS_L1)
+ vty_out(vty, "%s L1 MS Power: %u dBm, Timing Advance: %u%s",
+ prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE);
+ if (mr->flags & MEAS_REP_F_DL_VALID)
+ meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl");
+ meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul");
+}
+
+void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+ int idx;
+
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ lchan->nr, gsm_chan_t_name(lchan->type), VTY_NEWLINE);
+
+ if (lchan->activate.concluded) {
+ vty_out(vty, " Active for: %s seconds%s",
+ osmo_int_to_float_str_c(OTC_SELECT, gsm_lchan_active_duration_ms(lchan), 3),
+ VTY_NEWLINE);
+ }
+
+ vty_out_dyn_ts_details(vty, lchan->ts);
+ vty_out(vty, " Connection: %u, State: %s%s%s%s",
+ lchan->conn ? 1: 0, lchan_state_name(lchan),
+ lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? " Error reason: " : "",
+ lchan->fi && lchan->fi->state == LCHAN_ST_BORKEN ? lchan->last_error : "",
+ VTY_NEWLINE);
+ vty_out(vty, " BS Power: %u dBm, MS Power: %u dBm%s",
+ lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red
+ - lchan->bs_power_db,
+ ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
+ VTY_NEWLINE);
+
+ vty_out(vty, " Interference Level: ");
+ if (lchan->interf_dbm == INTERF_DBM_UNKNOWN)
+ vty_out(vty, "unknown");
+ else
+ vty_out(vty, "%d dBm (%u)", lchan->interf_dbm, lchan->interf_band);
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ vty_out(vty, " Channel Mode / Codec: %s%s",
+ gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode),
+ VTY_NEWLINE);
+ if (!lchan_state_is(lchan, LCHAN_ST_UNUSED))
+ vty_out(vty, " Training Sequence: Set %d Code %u%s", (lchan->tsc_set > 0 ? lchan->tsc_set : 1), lchan->tsc, VTY_NEWLINE);
+ if (lchan->vamos.enabled)
+ vty_out(vty, " VAMOS: enabled%s", VTY_NEWLINE);
+ if (lchan->conn && lchan->conn->bsub) {
+ vty_out(vty, " Subscriber:%s", VTY_NEWLINE);
+ bsc_subscr_dump_vty(vty, lchan->conn->bsub);
+ } else {
+ vty_out(vty, " No Subscriber%s", VTY_NEWLINE);
+ }
+ if (is_ipa_abisip_bts(lchan->ts->trx->bts)) {
+ struct in_addr ia;
+ if (lchan->abis_ip.bound_ip) {
+ ia.s_addr = htonl(lchan->abis_ip.bound_ip);
+ vty_out(vty, " Bound IP: %s Port %u CONN_ID=%u",
+ inet_ntoa(ia), lchan->abis_ip.bound_port,
+ lchan->abis_ip.conn_id);
+ if (lchan->abis_ip.osmux.use)
+ vty_out(vty, " Osmux_CID=%u%s", lchan->abis_ip.osmux.local_cid, VTY_NEWLINE);
+ else
+ vty_out(vty, " RTP_TYPE2=%u%s", lchan->abis_ip.rtp_payload2, VTY_NEWLINE);
+ }
+ if (lchan->abis_ip.connect_ip) {
+ ia.s_addr = htonl(lchan->abis_ip.connect_ip);
+ vty_out(vty, " Conn. IP: %s Port %u SPEECH_MODE=0x%02x",
+ inet_ntoa(ia), lchan->abis_ip.connect_port,
+ lchan->abis_ip.speech_mode);
+ if (lchan->abis_ip.osmux.use) {
+ if (lchan->abis_ip.osmux.remote_cid_present)
+ vty_out(vty, " Osmux_CID=%u%s", lchan->abis_ip.osmux.remote_cid, VTY_NEWLINE);
+ else
+ vty_out(vty, " Osmux_CID=?%s", VTY_NEWLINE);
+ } else {
+ vty_out(vty, " RTP_TYPE=%u%s", lchan->abis_ip.rtp_payload, VTY_NEWLINE);
+ }
+ }
+ }
+
+ /* we want to report the last measurement report */
+ idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+ lchan->meas_rep_idx, 1);
+ meas_rep_dump_vty(vty, &lchan->meas_rep[idx], " ");
+}
+
+void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+ struct gsm_meas_rep *mr;
+ int idx;
+
+ /* we want to report the last measurement report */
+ idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+ lchan->meas_rep_idx, 1);
+ mr = &lchan->meas_rep[idx];
+
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ gsm_pchan_name(lchan->ts->pchan_on_init));
+ vty_out_dyn_ts_status(vty, lchan->ts);
+ vty_out(vty, ", Lchan %u", lchan->nr);
+
+ if (lchan_state_is(lchan, LCHAN_ST_UNUSED)) {
+ vty_out(vty, ", Type %s, State %s - Interference Level: ",
+ gsm_pchan_name(lchan->ts->pchan_is),
+ lchan_state_name(lchan));
+ if (lchan->interf_dbm == INTERF_DBM_UNKNOWN)
+ vty_out(vty, "unknown");
+ else
+ vty_out(vty, "%d dBm (%u)", lchan->interf_dbm, lchan->interf_band);
+ vty_out(vty, "%s", VTY_NEWLINE);
+ return;
+ }
+
+ vty_out(vty, ", Type %s%s TSC-s%dc%u, State %s - L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s",
+ gsm_chan_t_name(lchan->type),
+ lchan->vamos.enabled ? " (VAMOS)" : "",
+ lchan->tsc_set > 0 ? lchan->tsc_set : 1,
+ lchan->tsc,
+ lchan_state_name(lchan),
+ mr->ms_l1.pwr,
+ rxlev2dbm(mr->dl.full.rx_lev),
+ rxlev2dbm(mr->ul.full.rx_lev),
+ VTY_NEWLINE);
+}
+
+void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s (active %s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan_on_init),
+ gsm_pchan_name(ts->pchan_is));
+ if (ts->pchan_is != ts->pchan_on_init)
+ vty_out(vty, " (%s mode)", gsm_pchan_name(ts->pchan_is));
+ vty_out(vty, ", TSC %u%s NM State: ", gsm_ts_tsc(ts), VTY_NEWLINE);
+ vty_out_dyn_ts_details(vty, ts);
+ net_dump_nmstate(vty, &ts->mo.nm_state);
+ if (!is_ipa_abisip_bts(ts->trx->bts))
+ vty_out(vty, " E1 Line %u, Timeslot %u, Subslot %u%s",
+ ts->e1_link.e1_nr, ts->e1_link.e1_ts,
+ ts->e1_link.e1_ts_ss, VTY_NEWLINE);
+}
+
+void e1isl_dump_vty(struct vty *vty, struct e1inp_sign_link *e1l)
+{
+ struct e1inp_line *line;
+
+ if (!e1l) {
+ vty_out(vty, " None%s", VTY_NEWLINE);
+ return;
+ }
+
+ line = e1l->ts->line;
+
+ vty_out(vty, " E1 Line %u, Type %s: Timeslot %u, Mode %s%s",
+ line->num, line->driver->name, e1l->ts->num,
+ e1inp_signtype_name(e1l->type), VTY_NEWLINE);
+ vty_out(vty, " E1 TEI %u, SAPI %u%s",
+ e1l->tei, e1l->sapi, VTY_NEWLINE);
+}
+
+/*! Dump the IP addresses and ports of the input signal link's timeslot.
+ * This only makes sense for links connected with ipaccess.
+ * Example output: "(r=10.1.42.1:55416<->l=10.1.42.123:3003)" */
+void e1isl_dump_vty_tcp(struct vty *vty, const struct e1inp_sign_link *e1l)
+{
+ if (e1l) {
+ char *name = osmo_sock_get_name(NULL, e1l->ts->driver.ipaccess.fd.fd);
+ vty_out(vty, "%s", name);
+ talloc_free(name);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx, bool print_rsl, bool show_connected)
+{
+ if (show_connected && !trx->rsl_link_primary)
+ return;
+
+ if (!show_connected && trx->rsl_link_primary)
+ return;
+
+ vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s",
+ trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE);
+ vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, "
+ "resulting BS power: %d dBm%s",
+ trx->nominal_power, trx->max_power_red,
+ trx->nominal_power - trx->max_power_red, VTY_NEWLINE);
+ vty_out(vty, " Radio Carrier NM State: ");
+ net_dump_nmstate(vty, &trx->mo.nm_state);
+ if (print_rsl)
+ vty_out(vty, " RSL State: %s%s", trx->rsl_link_primary? "connected" : "disconnected", VTY_NEWLINE);
+
+ vty_out(vty, " %sBaseband Transceiver NM State: ", is_ericsson_bts(trx->bts) ? "[Virtual] " : "");
+ net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state);
+
+ if (is_ipa_abisip_bts(trx->bts)) {
+ vty_out(vty, " IPA Abis/IP stream ID: 0x%02x ", trx->rsl_tei_primary);
+ e1isl_dump_vty_tcp(vty, trx->rsl_link_primary);
+ } else {
+ vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE);
+ e1isl_dump_vty(vty, trx->rsl_link_primary);
+ }
+
+ const struct load_counter *ll = &trx->lchan_load;
+ vty_out(vty, " Channel load: %u%%%s",
+ ll->total ? ll->used * 100 / ll->total : 0,
+ VTY_NEWLINE);
+}
+
+void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link,
+ const char *prefix)
+{
+ if (!e1_link->e1_ts)
+ return;
+
+ if (e1_link->e1_ts_ss == E1_SUBSLOT_FULL)
+ vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s",
+ prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE);
+ else
+ vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s",
+ prefix, e1_link->e1_nr, e1_link->e1_ts,
+ e1_link->e1_ts_ss, VTY_NEWLINE);
+}
+
+
+static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ vty_out(vty, " timeslot %u%s", ts->nr, VTY_NEWLINE);
+ if (ts->tsc != -1)
+ vty_out(vty, " training_sequence_code %u%s", ts->tsc, VTY_NEWLINE);
+ if (ts->pchan_from_config != GSM_PCHAN_NONE)
+ vty_out(vty, " phys_chan_config %s%s",
+ gsm_pchan_name(ts->pchan_from_config), VTY_NEWLINE);
+ vty_out(vty, " hopping enabled %u%s",
+ ts->hopping.enabled, VTY_NEWLINE);
+ if (ts->hopping.enabled) {
+ unsigned int i;
+ vty_out(vty, " hopping sequence-number %u%s",
+ ts->hopping.hsn, VTY_NEWLINE);
+ vty_out(vty, " hopping maio %u%s",
+ ts->hopping.maio, VTY_NEWLINE);
+ for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+ if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i))
+ continue;
+ vty_out(vty, " hopping arfcn add %u%s",
+ i, VTY_NEWLINE);
+ }
+ }
+ config_write_e1_link(vty, &ts->e1_link, " ");
+
+ if (ts->trx->bts->model->config_write_ts)
+ ts->trx->bts->model->config_write_ts(vty, ts);
+}
+
+void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ int i;
+
+ vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE);
+ vty_out(vty, " rf_locked %u%s",
+ trx->mo.force_rf_lock ? 1 : 0,
+ VTY_NEWLINE);
+ vty_out(vty, " arfcn %u%s", trx->arfcn, VTY_NEWLINE);
+ vty_out(vty, " nominal power %u%s", trx->nominal_power, VTY_NEWLINE);
+ vty_out(vty, " max_power_red %u%s", trx->max_power_red, VTY_NEWLINE);
+ config_write_e1_link(vty, &trx->rsl_e1_link, " rsl ");
+ vty_out(vty, " rsl e1 tei %u%s", trx->rsl_tei_primary, VTY_NEWLINE);
+
+ if (trx->bts->model->config_write_trx)
+ trx->bts->model->config_write_trx(vty, trx);
+
+ for (i = 0; i < TRX_NR_TS; i++)
+ config_write_ts_single(vty, &trx->ts[i]);
+}
+
+int bts_trx_vty_init(void)
+{
+ cfg_ts_pchan_cmd.string =
+ vty_cmd_string_from_valstr(tall_bsc_ctx,
+ gsm_pchant_names,
+ "phys_chan_config (", "|", ")",
+ VTY_DO_LOWER);
+ cfg_ts_pchan_cmd.doc =
+ vty_cmd_string_from_valstr(tall_bsc_ctx,
+ gsm_pchant_descs,
+ "Physical Channel Combination\n",
+ "\n", "", 0);
+
+ install_element(BTS_NODE, &cfg_trx_cmd);
+ install_node(&trx_node, dummy_config_write);
+ install_element(TRX_NODE, &cfg_trx_arfcn_cmd);
+ install_element(TRX_NODE, &cfg_description_cmd);
+ install_element(TRX_NODE, &cfg_no_description_cmd);
+ install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+ install_element(TRX_NODE, &cfg_trx_max_power_red_cmd);
+ install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd);
+ install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd);
+ install_element(TRX_NODE, &cfg_trx_rf_locked_cmd);
+
+ install_element(TRX_NODE, &cfg_ts_cmd);
+ install_node(&ts_node, dummy_config_write);
+ install_element(TS_NODE, &cfg_ts_pchan_cmd);
+ install_element(TS_NODE, &cfg_ts_pchan_compat_cmd);
+ install_element(TS_NODE, &cfg_ts_tsc_cmd);
+ install_element(TS_NODE, &cfg_ts_hopping_cmd);
+ install_element(TS_NODE, &cfg_ts_hsn_cmd);
+ install_element(TS_NODE, &cfg_ts_maio_cmd);
+ install_element(TS_NODE, &cfg_ts_arfcn_add_cmd);
+ install_element(TS_NODE, &cfg_ts_arfcn_del_cmd);
+ install_element(TS_NODE, &cfg_ts_arfcn_del_all_cmd);
+ install_element(TS_NODE, &cfg_ts_e1_subslot_cmd);
+
+ return 0;
+}
diff --git a/src/osmo-bsc/bts_unknown.c b/src/osmo-bsc/bts_unknown.c
index 5ecf875c1..b5471ce0c 100644
--- a/src/osmo-bsc/bts_unknown.c
+++ b/src/osmo-bsc/bts_unknown.c
@@ -21,6 +21,7 @@
#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/bsc/abis_nm.h>
@@ -36,5 +37,11 @@ static struct gsm_bts_model model_unknown = {
int bts_model_unknown_init(void)
{
+ /* NOTE: the buffer is zero-initialized by compiler */
+ model_unknown.features = (struct bitvec) {
+ .data_len = sizeof(model_unknown._features_data),
+ .data = &model_unknown._features_data[0],
+ };
+
return gsm_bts_model_register(&model_unknown);
}
diff --git a/src/osmo-bsc/bts_vty.c b/src/osmo-bsc/bts_vty.c
new file mode 100644
index 000000000..24224f64f
--- /dev/null
+++ b/src/osmo-bsc/bts_vty.c
@@ -0,0 +1,5090 @@
+/* OsmoBSC interface to quagga VTY, BTS node */
+/* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/vty/tdef_vty.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/gprs/gprs_ns.h>
+
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/meas_rep.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/pcu_if.h>
+#include <osmocom/bsc/handover_vty.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bsc_stats.h>
+
+#include <inttypes.h>
+
+#include "../../bscconfig.h"
+
+#define X(x) (1 << x)
+
+/* FIXME: this should go to some common file */
+static const struct value_string gprs_ns_timer_strs[] = {
+ { 0, "tns-block" },
+ { 1, "tns-block-retries" },
+ { 2, "tns-reset" },
+ { 3, "tns-reset-retries" },
+ { 4, "tns-test" },
+ { 5, "tns-alive" },
+ { 6, "tns-alive-retries" },
+ { 0, NULL }
+};
+
+static const struct value_string gprs_bssgp_cfg_strs[] = {
+ { 0, "blocking-timer" },
+ { 1, "blocking-retries" },
+ { 2, "unblocking-retries" },
+ { 3, "reset-timer" },
+ { 4, "reset-retries" },
+ { 5, "suspend-timer" },
+ { 6, "suspend-retries" },
+ { 7, "resume-timer" },
+ { 8, "resume-retries" },
+ { 9, "capability-update-timer" },
+ { 10, "capability-update-retries" },
+ { 0, NULL }
+};
+
+static const struct value_string bts_neigh_mode_strs[] = {
+ { NL_MODE_AUTOMATIC, "automatic" },
+ { NL_MODE_MANUAL, "manual" },
+ { NL_MODE_MANUAL_SI5SEP, "manual-si5" },
+ { 0, NULL }
+};
+
+static struct cmd_node bts_node = {
+ BTS_NODE,
+ "%s(config-net-bts)# ",
+ 1,
+};
+
+static struct cmd_node power_ctrl_node = {
+ POWER_CTRL_NODE,
+ "%s(config-power-ctrl)# ",
+ 1,
+};
+
+/* per-BTS configuration */
+DEFUN_ATTR(cfg_bts,
+ cfg_bts_cmd,
+ "bts <0-255>",
+ "Select a BTS to configure\n"
+ BTS_NR_STR,
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ int bts_nr = atoi(argv[0]);
+ struct gsm_bts *bts;
+
+ if (bts_nr > gsmnet->num_bts) {
+ vty_out(vty, "%% BTS number %d not valid (next BTS number must be %u)%s",
+ bts_nr, gsmnet->num_bts, VTY_NEWLINE);
+ return CMD_WARNING;
+ } else if (bts_nr == gsmnet->num_bts) {
+ /* allocate a new one */
+ bts = bsc_bts_alloc_register(gsmnet, GSM_BTS_TYPE_UNKNOWN,
+ HARDCODED_BSIC);
+ } else
+ bts = gsm_bts_num(gsmnet, bts_nr);
+
+ if (!bts) {
+ vty_out(vty, "%% Unable to allocate BTS %u%s",
+ gsmnet->num_bts, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty->index = bts;
+ vty->index_sub = &bts->description;
+ vty->node = BTS_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_type,
+ cfg_bts_type_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "type TYPE", /* dynamically created */
+ "Set the BTS type\n" "Type\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int rc;
+
+ rc = gsm_set_bts_type(bts, str2btstype(argv[0]));
+ if (rc == -EBUSY)
+ vty_out(vty, "%% Changing the type of an existing BTS is not supported.%s",
+ VTY_NEWLINE);
+ if (rc < 0)
+ return CMD_WARNING;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_bts_type_sysmobts,
+ cfg_bts_type_sysmobts_cmd,
+ "type sysmobts",
+ "Set the BTS type\n"
+ "Deprecated alias for 'osmo-bts'\n")
+{
+ const char *args[] = { "osmo-bts" };
+
+ vty_out(vty, "%% BTS type 'sysmobts' is deprecated, "
+ "use 'type osmo-bts' instead.%s", VTY_NEWLINE);
+
+ return cfg_bts_type(self, vty, 1, args);
+}
+
+DEFUN_USRATTR(cfg_bts_band,
+ cfg_bts_band_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "band BAND",
+ "Set the frequency band of this BTS\n" "Frequency band\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int band = gsm_band_parse(argv[0]);
+
+ if (band < 0) {
+ vty_out(vty, "%% BAND %d is not a valid GSM band%s",
+ band, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->band = band;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_dtxu,
+ cfg_bts_dtxu_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "dtx uplink [force]",
+ "Configure discontinuous transmission\n"
+ "Enable Uplink DTX for this BTS\n"
+ "MS 'shall' use DTXu instead of 'may' use (might not be supported by "
+ "older phones).\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->dtxu = (argc > 0) ? GSM48_DTX_SHALL_BE_USED : GSM48_DTX_MAY_BE_USED;
+ if (!is_ipa_abisip_bts(bts))
+ vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration "
+ "neither supported nor tested!%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_no_dtxu,
+ cfg_bts_no_dtxu_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "no dtx uplink",
+ NO_STR "Configure discontinuous transmission\n"
+ "Disable Uplink DTX for this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_dtxd,
+ cfg_bts_dtxd_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "dtx downlink",
+ "Configure discontinuous transmission\n"
+ "Enable Downlink DTX for this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->dtxd = true;
+ if (!is_ipa_abisip_bts(bts))
+ vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration "
+ "neither supported nor tested!%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_no_dtxd,
+ cfg_bts_no_dtxd_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "no dtx downlink",
+ NO_STR "Configure discontinuous transmission\n"
+ "Disable Downlink DTX for this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->dtxd = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_ci,
+ cfg_bts_ci_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "cell_identity <0-65535>",
+ "Set the Cell identity of this BTS\n" "Cell Identity\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int ci = atoi(argv[0]);
+
+ if (ci < 0 || ci > 0xffff) {
+ vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s",
+ ci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts->cell_identity = ci;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_lac,
+ cfg_bts_lac_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "location_area_code (<0-65535>|<0x0000-0xffff>)",
+ "Set the Location Area Code (LAC) of this BTS\n"
+ "LAC in decimal format\n"
+ "LAC in hexadecimal format\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int lac;
+ if (osmo_str_to_int(&lac, argv[0], 0, 0, 0xffff) < 0)
+ return CMD_WARNING;
+
+ if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) {
+ vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s",
+ lac, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->location_area_code = lac;
+
+ return CMD_SUCCESS;
+}
+
+
+/* compatibility wrapper for old config files */
+DEFUN_HIDDEN(cfg_bts_tsc,
+ cfg_bts_tsc_cmd,
+ "training_sequence_code <0-7>",
+ "Set the Training Sequence Code (TSC) of this BTS\n" "TSC\n")
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_bsic,
+ cfg_bts_bsic_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "base_station_id_code <0-63>",
+ "Set the Base Station Identity Code (BSIC) of this BTS\n"
+ "BSIC of this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int bsic = atoi(argv[0]);
+
+ if (bsic < 0 || bsic > 0x3f) {
+ vty_out(vty, "%% BSIC %d is not in the valid range (0-255)%s",
+ bsic, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts->bsic = bsic;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_unit_id,
+ cfg_bts_unit_id_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "ipa unit-id <0-65534> <0-255>",
+ "Abis/IP specific options\n"
+ "Set the IPA BTS Unit ID\n"
+ "Unit ID (Site)\n"
+ "Unit ID (BTS)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int site_id = atoi(argv[0]);
+ int bts_id = atoi(argv[1]);
+
+ if (!is_ipa_abisip_bts(bts)) {
+ vty_out(vty, "%% BTS is not of IPA Abis/IP type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->ip_access.site_id = site_id;
+ bts->ip_access.bts_id = bts_id;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_bts_unit_id,
+ cfg_bts_deprecated_unit_id_cmd,
+ "ip.access unit_id <0-65534> <0-255>",
+ "Abis/IP specific options\n"
+ "Set the IPA BTS Unit ID\n"
+ "Unit ID (Site)\n"
+ "Unit ID (BTS)\n");
+
+DEFUN_USRATTR(cfg_bts_rsl_ip,
+ cfg_bts_rsl_ip_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "ipa rsl-ip A.B.C.D",
+ "Abis/IP specific options\n"
+ "Set the IPA RSL IP Address of the BSC\n"
+ "Destination IP address for RSL connection\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct in_addr ia;
+
+ if (!is_ipa_abisip_bts(bts)) {
+ vty_out(vty, "%% BTS is not of IPA Abis/IP type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ inet_aton(argv[0], &ia);
+ bts->ip_access.rsl_ip = ntohl(ia.s_addr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_bts_rsl_ip,
+ cfg_bts_deprecated_rsl_ip_cmd,
+ "ip.access rsl-ip A.B.C.D",
+ "Abis/IP specific options\n"
+ "Set the IPA RSL IP Address of the BSC\n"
+ "Destination IP address for RSL connection\n");
+
+#define NOKIA_STR "Nokia *Site related commands\n"
+
+DEFUN_USRATTR(cfg_bts_nokia_site_skip_reset,
+ cfg_bts_nokia_site_skip_reset_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "nokia_site skip-reset (0|1)",
+ NOKIA_STR
+ "Skip the reset step during bootstrap process of this BTS\n"
+ "Do NOT skip the reset\n" "Skip the reset\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->type != GSM_BTS_TYPE_NOKIA_SITE) {
+ vty_out(vty, "%% BTS is not of Nokia *Site type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->nokia.skip_reset = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_nokia_site_no_loc_rel_cnf,
+ cfg_bts_nokia_site_no_loc_rel_cnf_cmd,
+ "nokia_site no-local-rel-conf (0|1)",
+ NOKIA_STR
+ "Do not wait for RELease CONFirm message when releasing channel locally\n"
+ "Wait for RELease CONFirm\n" "Do not wait for RELease CONFirm\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (!is_nokia_bts(bts)) {
+ vty_out(vty, "%% BTS is not of Nokia *Site type%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->nokia.no_loc_rel_cnf = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_nokia_site_bts_reset_timer_cnf,
+ cfg_bts_nokia_site_bts_reset_timer_cnf_cmd,
+ "nokia_site bts-reset-timer <15-100>",
+ NOKIA_STR
+ "The amount of time between BTS_RESET is sent "
+ "and the BTS is being bootstrapped\n"
+ "Timer value (in seconds, default 15)\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (!is_nokia_bts(bts)) {
+ vty_out(vty, "%% BTS is not of Nokia *Site type%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->nokia.bts_reset_timer_cnf = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+#define OML_STR "Organization & Maintenance Link\n"
+#define IPA_STR "A-bis/IP Specific Options\n"
+
+DEFUN_USRATTR(cfg_bts_stream_id,
+ cfg_bts_stream_id_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "oml ipa stream-id <0-255> line E1_LINE",
+ OML_STR IPA_STR
+ "Set the ipa Stream ID of the OML link of this BTS\n" "Stream Identifier\n"
+ "Virtual E1 Line Number\n" "Virtual E1 Line Number\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int stream_id = atoi(argv[0]), linenr = atoi(argv[1]);
+
+ if (!is_ipa_abisip_bts(bts)) {
+ vty_out(vty, "%% BTS is not of IPA Abis/IP type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->oml_tei = stream_id;
+ /* This is used by e1inp_bind_ops callback for each BTS model. */
+ bts->oml_e1_link.e1_nr = linenr;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_bts_stream_id,
+ cfg_bts_deprecated_stream_id_cmd,
+ "oml ip.access stream_id <0-255> line E1_LINE",
+ OML_STR IPA_STR
+ "Set the ip.access Stream ID of the OML link of this BTS\n"
+ "Stream Identifier\n" "Virtual E1 Line Number\n" "Virtual E1 Line Number\n");
+
+#define OML_E1_STR OML_STR "OML E1/T1 Configuration\n"
+
+/* NOTE: This requires a full restart as bsc_network_configure() is executed
+ * only once on startup from osmo_bsc_main.c */
+DEFUN(cfg_bts_oml_e1,
+ cfg_bts_oml_e1_cmd,
+ "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+ OML_E1_STR
+ "E1/T1 line number to be used for OML\n"
+ "E1/T1 line number to be used for OML\n"
+ "E1/T1 timeslot to be used for OML\n"
+ "E1/T1 timeslot to be used for OML\n"
+ "E1/T1 sub-slot to be used for OML\n"
+ "Use E1/T1 sub-slot 0\n"
+ "Use E1/T1 sub-slot 1\n"
+ "Use E1/T1 sub-slot 2\n"
+ "Use E1/T1 sub-slot 3\n"
+ "Use full E1 slot 3\n"
+ )
+{
+ struct gsm_bts *bts = vty->index;
+
+ parse_e1_link(&bts->oml_e1_link, argv[0], argv[1], argv[2]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_oml_e1_tei,
+ cfg_bts_oml_e1_tei_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "oml e1 tei <0-63>",
+ OML_E1_STR
+ "Set the TEI to be used for OML\n"
+ "TEI Number\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->oml_tei = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define CHAN_ALLOC_CMD "channel allocator"
+#define CHAN_ALLOC_DESC \
+ "Channel Allocator\n" \
+ "Channel Allocator\n"
+
+#define CHAN_ALLOC_ASC_DSC "(ascending|descending)"
+#define CHAN_ALLOC_ASC_DSC_DESC \
+ "Allocate Timeslots and Transceivers in ascending order\n" \
+ "Allocate Timeslots and Transceivers in descending order\n"
+
+DEFUN_ATTR(cfg_bts_challoc_mode_all,
+ cfg_bts_challoc_mode_all_cmd,
+ CHAN_ALLOC_CMD " " CHAN_ALLOC_ASC_DSC,
+ CHAN_ALLOC_DESC CHAN_ALLOC_ASC_DSC_DESC,
+ CMD_ATTR_IMMEDIATE | CMD_ATTR_DEPRECATED)
+{
+ bool reverse = !strcmp(argv[0], "descending");
+ struct gsm_bts *bts = vty->index;
+
+ bts->chan_alloc_chan_req_reverse = reverse;
+ bts->chan_alloc_assignment_reverse = reverse;
+ bts->chan_alloc_handover_reverse = reverse;
+ bts->chan_alloc_vgcs_reverse = reverse;
+ bts->chan_alloc_assignment_dynamic = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_challoc_mode,
+ cfg_bts_challoc_mode_cmd,
+ CHAN_ALLOC_CMD
+ " mode (set-all|chan-req|assignment|handover|vgcs-vbs) "
+ CHAN_ALLOC_ASC_DSC,
+ CHAN_ALLOC_DESC
+ "Channel allocation mode\n"
+ "Set a single mode for all variants\n"
+ "Channel allocation for CHANNEL REQUEST (RACH)\n"
+ "Channel allocation for assignment\n"
+ "Channel allocation for handover\n"
+ "Channel allocation for VGCS/VBS\n"
+ CHAN_ALLOC_ASC_DSC_DESC,
+ CMD_ATTR_IMMEDIATE)
+{
+ bool reverse = !strcmp(argv[1], "descending");
+ bool set_all = !strcmp(argv[0], "set-all");
+ struct gsm_bts *bts = vty->index;
+
+ if (set_all || !strcmp(argv[0], "chan-req"))
+ bts->chan_alloc_chan_req_reverse = reverse;
+ if (set_all || !strcmp(argv[0], "assignment")) {
+ bts->chan_alloc_assignment_reverse = reverse;
+ bts->chan_alloc_assignment_dynamic = false;
+ }
+ if (set_all || !strcmp(argv[0], "handover"))
+ bts->chan_alloc_handover_reverse = reverse;
+ if (set_all || !strcmp(argv[0], "vgcs-vbs"))
+ bts->chan_alloc_vgcs_reverse = reverse;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_challoc_mode_ass_dynamic,
+ cfg_bts_challoc_mode_ass_dynamic_cmd,
+ CHAN_ALLOC_CMD " mode assignment dynamic",
+ CHAN_ALLOC_DESC
+ "Channel allocation mode\n"
+ "Channel allocation for assignment\n"
+ "Dynamic lchan selection based on configured parameters\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->chan_alloc_assignment_dynamic = true;
+
+ return CMD_SUCCESS;
+}
+
+#define CHAN_ALLOC_DYN_PARAM_CMD \
+ CHAN_ALLOC_CMD " dynamic-param"
+#define CHAN_ALLOC_DYN_PARAM_DESC \
+ CHAN_ALLOC_DESC \
+ "Parameters for dynamic channel allocation mode\n"
+
+DEFUN_ATTR(cfg_bts_challoc_dynamic_param_sort_by_trx_power,
+ cfg_bts_challoc_dynamic_param_sort_by_trx_power_cmd,
+ CHAN_ALLOC_DYN_PARAM_CMD " sort-by-trx-power (0|1)",
+ CHAN_ALLOC_DYN_PARAM_DESC
+ "Whether to sort TRX instances by their respective power levels\n"
+ "Do not sort, use the same order as in the configuration file\n"
+ "Sort TRX instances by their power levels in descending order\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->chan_alloc_dyn_params.sort_by_trx_power = (argv[0][0] == '1');
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_challoc_dynamic_param_ul_rxlev,
+ cfg_bts_challoc_dynamic_param_ul_rxlev_cmd,
+ CHAN_ALLOC_DYN_PARAM_CMD " ul-rxlev thresh <0-63> avg-num <1-10>",
+ CHAN_ALLOC_DYN_PARAM_DESC
+ "Uplink RxLev\n"
+ "Uplink RxLev threshold\n"
+ "Uplink RxLev threshold\n"
+ "Minimum number of RxLev samples for averaging\n"
+ "Minimum number of RxLev samples for averaging\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->chan_alloc_dyn_params.ul_rxlev_thresh = atoi(argv[0]);
+ bts->chan_alloc_dyn_params.ul_rxlev_avg_num = atoi(argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_challoc_dynamic_param_c0_chan_load,
+ cfg_bts_challoc_dynamic_param_c0_chan_load_cmd,
+ CHAN_ALLOC_DYN_PARAM_CMD " c0-chan-load thresh <0-100>",
+ CHAN_ALLOC_DYN_PARAM_DESC
+ "C0 (BCCH carrier) channel load\n"
+ "Channel load threshold\n"
+ "Channel load threshold (in %)\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->chan_alloc_dyn_params.c0_chan_load_thresh = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_chan_alloc_interf,
+ cfg_bts_chan_alloc_interf_cmd,
+ CHAN_ALLOC_CMD " avoid-interference (0|1)",
+ CHAN_ALLOC_DESC
+ "Configure whether reported interference levels from RES IND are used in channel allocation\n"
+ "Ignore interference levels (default). Always assign lchans in a deterministic order.\n"
+ "In channel allocation, prefer lchans with less interference.\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (!strcmp(argv[0], "0"))
+ bts->chan_alloc_avoid_interf = false;
+ else
+ bts->chan_alloc_avoid_interf = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_chan_alloc_tch_signalling_policy,
+ cfg_bts_chan_alloc_tch_signalling_policy_cmd,
+ CHAN_ALLOC_CMD " tch-signalling-policy (never|emergency|voice|always)",
+ CHAN_ALLOC_DESC
+ "Configure when TCH/H or TCH/F channels can be used to serve signalling if SDCCHs are exhausted\n"
+ "Never allow TCH for signalling purposes\n"
+ "Only allow TCH for signalling purposes when establishing an emergency call\n"
+ "Allow TCH for signalling purposes when establishing any voice call\n"
+ "Always allow TCH for signalling purposes (default)\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (!strcmp(argv[0], "never"))
+ bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_NEVER;
+ else if (!strcmp(argv[0], "emergency"))
+ bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_EMERG;
+ else if (!strcmp(argv[0], "voice"))
+ bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_VOICE;
+ else
+ bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_ALWAYS;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_chan_alloc_allow_tch_for_signalling,
+ cfg_bts_chan_alloc_allow_tch_for_signalling_cmd,
+ CHAN_ALLOC_CMD " allow-tch-for-signalling (0|1)",
+ CHAN_ALLOC_DESC
+ "Configure whether TCH/H or TCH/F channels can be used to serve non-call-related signalling if SDCCHs are exhausted\n"
+ "Forbid use of TCH for non-call-related signalling purposes\n"
+ "Allow use of TCH for non-call-related signalling purposes (default)\n",
+ CMD_ATTR_IMMEDIATE|CMD_ATTR_DEPRECATED)
+{
+ struct gsm_bts *bts = vty->index;
+
+ vty_out(vty, "%% 'allow-tch-for-signalling' is deprecated, use 'tch-signalling-policy' instead.%s", VTY_NEWLINE);
+
+ if (!strcmp(argv[0], "0"))
+ bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_VOICE;
+ else
+ bts->chan_alloc_tch_signalling_policy = BTS_TCH_SIGNALLING_ALWAYS;
+
+ return CMD_SUCCESS;
+}
+
+#define RACH_STR "Random Access Control Channel\n"
+
+DEFUN_USRATTR(cfg_bts_rach_tx_integer,
+ cfg_bts_rach_tx_integer_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "rach tx integer <0-15>",
+ RACH_STR
+ "Set the raw tx integer value in RACH Control parameters IE\n"
+ "Set the raw tx integer value in RACH Control parameters IE\n"
+ "Raw tx integer value in RACH Control parameters IE\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf;
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rach_max_trans,
+ cfg_bts_rach_max_trans_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "rach max transmission (1|2|4|7)",
+ RACH_STR
+ "Set the maximum number of RACH burst transmissions\n"
+ "Set the maximum number of RACH burst transmissions\n"
+ "Maximum number of 1 RACH burst transmissions\n"
+ "Maximum number of 2 RACH burst transmissions\n"
+ "Maximum number of 4 RACH burst transmissions\n"
+ "Maximum number of 7 RACH burst transmissions\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0]));
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_rach_max_delay,
+ cfg_bts_rach_max_delay_cmd,
+ "rach max-delay <1-127>",
+ RACH_STR
+ "Set the max Access Delay IE value to accept in CHANnel ReQuireD\n"
+ "Maximum Access Delay IE value to accept in CHANnel ReQuireD\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ bts->rach_max_delay = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_rach_expiry_timeout,
+ cfg_bts_rach_expiry_timeout_cmd,
+ "rach expiry-timeout <4-64>",
+ RACH_STR
+ "Set the timeout for channel requests expiry\n"
+ "Maximum timeout before dropping channel requests\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ bts->rach_expiry_timeout = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+#define REP_ACCH_STR "FACCH/SACCH repetition\n"
+
+DEFUN_USRATTR(cfg_bts_rep_dl_facch,
+ cfg_bts_rep_dl_facch_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "repeat dl-facch (command|all)",
+ REP_ACCH_STR
+ "Enable DL-FACCH repetition for this BTS\n"
+ "command LAPDm frames only\n"
+ "all LAPDm frames\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) {
+ vty_out(vty, "%% repeated ACCH not supported by BTS %u%s",
+ bts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "command")) {
+ bts->rep_acch_cap.dl_facch_cmd = true;
+ bts->rep_acch_cap.dl_facch_all = false;
+ } else {
+ bts->rep_acch_cap.dl_facch_cmd = true;
+ bts->rep_acch_cap.dl_facch_all = true;
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rep_no_dl_facch,
+ cfg_bts_rep_no_dl_facch_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "no repeat dl-facch",
+ NO_STR REP_ACCH_STR
+ "Disable DL-FACCH repetition for this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->rep_acch_cap.dl_facch_cmd = false;
+ bts->rep_acch_cap.dl_facch_all = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rep_ul_dl_sacch,
+ cfg_bts_rep_ul_dl_sacch_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "repeat (ul-sacch|dl-sacch)",
+ REP_ACCH_STR
+ "Enable UL-SACCH repetition for this BTS\n"
+ "Enable DL-SACCH repetition for this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) {
+ vty_out(vty, "%% repeated ACCH not supported by BTS %u%s",
+ bts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (strcmp(argv[0], "ul-sacch") == 0)
+ bts->rep_acch_cap.ul_sacch = true;
+ else
+ bts->rep_acch_cap.dl_sacch = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rep_no_ul_dl_sacch,
+ cfg_bts_rep_no_ul_dl_sacch_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "no repeat (ul-sacch|dl-sacch)",
+ NO_STR REP_ACCH_STR
+ "Disable UL-SACCH repetition for this BTS\n"
+ "Disable DL-SACCH repetition for this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (strcmp(argv[0], "ul-sacch") == 0)
+ bts->rep_acch_cap.ul_sacch = false;
+ else
+ bts->rep_acch_cap.dl_sacch = false;
+
+ return CMD_SUCCESS;
+}
+
+/* See 3GPP TS 45.008, section 8.2.4 */
+#define RXQUAL_THRESH_CMD \
+ "rxqual (0|1|2|3|4|5|6|7)"
+#define RXQUAL_THRESH_CMD_DESC \
+ "Set RxQual (BER) threshold (default 4)\n" \
+ "BER >= 0% (always on)\n" \
+ "BER >= 0.2%\n" \
+ "BER >= 0.4%\n" \
+ "BER >= 0.8%\n" \
+ "BER >= 1.6% (default)\n" \
+ "BER >= 3.2%\n" \
+ "BER >= 6.4%\n" \
+ "BER >= 12.8%\n"
+
+DEFUN_USRATTR(cfg_bts_rep_rxqual,
+ cfg_bts_rep_rxqual_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "repeat " RXQUAL_THRESH_CMD,
+ REP_ACCH_STR RXQUAL_THRESH_CMD_DESC)
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) {
+ vty_out(vty, "%% repeated ACCH not supported by BTS %u%s",
+ bts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* See also: GSM 05.08, section 8.2.4 */
+ bts->rep_acch_cap.rxqual = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define TOP_ACCH_STR "Temporary ACCH overpower\n"
+
+DEFUN_USRATTR(cfg_bts_top_dl_acch,
+ cfg_bts_top_dl_acch_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "overpower (dl-acch|dl-sacch|dl-facch) <1-4>",
+ TOP_ACCH_STR
+ "Enable overpower for both SACCH and FACCH\n"
+ "Enable overpower for SACCH only\n"
+ "Enable overpower for FACCH only\n"
+ "Overpower value in dB\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) {
+ vty_out(vty, "%% ACCH overpower is not supported by BTS %u%s",
+ bts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->top_acch_cap.sacch_enable = 0;
+ bts->top_acch_cap.facch_enable = 0;
+
+ if (!strcmp(argv[0], "dl-acch") || !strcmp(argv[0], "dl-sacch"))
+ bts->top_acch_cap.sacch_enable = 1;
+ if (!strcmp(argv[0], "dl-acch") || !strcmp(argv[0], "dl-facch"))
+ bts->top_acch_cap.facch_enable = 1;
+
+ bts->top_acch_cap.overpower_db = atoi(argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_top_no_dl_acch,
+ cfg_bts_top_no_dl_acch_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "no overpower dl-acch",
+ NO_STR TOP_ACCH_STR
+ "Disable ACCH overpower for this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->top_acch_cap.overpower_db = 0;
+ bts->top_acch_cap.sacch_enable = 0;
+ bts->top_acch_cap.facch_enable = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_top_dl_acch_rxqual,
+ cfg_bts_top_dl_acch_rxqual_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "overpower " RXQUAL_THRESH_CMD,
+ TOP_ACCH_STR RXQUAL_THRESH_CMD_DESC)
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) {
+ vty_out(vty, "%% ACCH overpower is not supported by BTS %u%s",
+ bts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->top_acch_cap.rxqual = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+static const struct value_string top_acch_chan_mode_name[] = {
+ { TOP_ACCH_CHAN_MODE_ANY, "any" },
+ { TOP_ACCH_CHAN_MODE_SPEECH_V3, "speech-amr" },
+ { 0, NULL }
+};
+
+DEFUN_USRATTR(cfg_bts_top_dl_acch_chan_mode,
+ cfg_bts_top_dl_acch_chan_mode_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "overpower chan-mode (speech-amr|any)",
+ TOP_ACCH_STR
+ "Allow temporary overpower for specific Channel mode(s)\n"
+ "Speech channels using AMR codec (default)\n"
+ "Any kind of channel mode\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->model->type != GSM_BTS_TYPE_OSMOBTS) {
+ vty_out(vty, "%% ACCH overpower is not supported by BTS %u%s",
+ bts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->top_acch_chan_mode = get_string_value(top_acch_chan_mode_name, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define CD_STR "Channel Description\n"
+
+DEFUN_USRATTR(cfg_bts_chan_desc_att,
+ cfg_bts_chan_desc_att_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "channel-description attach (0|1)",
+ CD_STR
+ "Set if attachment is required\n"
+ "Attachment is NOT required\n"
+ "Attachment is required (standard)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->si_common.chan_desc.att = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+ALIAS_DEPRECATED(cfg_bts_chan_desc_att,
+ cfg_bts_chan_dscr_att_cmd,
+ "channel-descrption attach (0|1)",
+ CD_STR
+ "Set if attachment is required\n"
+ "Attachment is NOT required\n"
+ "Attachment is required (standard)\n");
+
+DEFUN_USRATTR(cfg_bts_chan_desc_bs_pa_mfrms,
+ cfg_bts_chan_desc_bs_pa_mfrms_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "channel-description bs-pa-mfrms <2-9>",
+ CD_STR
+ "Set number of multiframe periods for paging groups\n"
+ "Number of multiframe periods for paging groups\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int bs_pa_mfrms = atoi(argv[0]);
+
+ bts->si_common.chan_desc.bs_pa_mfrms = bs_pa_mfrms - 2;
+ return CMD_SUCCESS;
+}
+ALIAS_DEPRECATED(cfg_bts_chan_desc_bs_pa_mfrms,
+ cfg_bts_chan_dscr_bs_pa_mfrms_cmd,
+ "channel-descrption bs-pa-mfrms <2-9>",
+ CD_STR
+ "Set number of multiframe periods for paging groups\n"
+ "Number of multiframe periods for paging groups\n");
+
+DEFUN_USRATTR(cfg_bts_chan_desc_bs_ag_blks_res,
+ cfg_bts_chan_desc_bs_ag_blks_res_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "channel-description bs-ag-blks-res <0-7>",
+ CD_STR
+ "Set number of blocks reserved for access grant\n"
+ "Number of blocks reserved for access grant\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int bs_ag_blks_res = atoi(argv[0]);
+
+ bts->si_common.chan_desc.bs_ag_blks_res = bs_ag_blks_res;
+ return CMD_SUCCESS;
+}
+ALIAS_DEPRECATED(cfg_bts_chan_desc_bs_ag_blks_res,
+ cfg_bts_chan_dscr_bs_ag_blks_res_cmd,
+ "channel-descrption bs-ag-blks-res <0-7>",
+ CD_STR
+ "Set number of blocks reserved for access grant\n"
+ "Number of blocks reserved for access grant\n");
+
+#define CCCH_STR "Common Control Channel\n"
+
+DEFUN_USRATTR(cfg_bts_ccch_load_ind_thresh,
+ cfg_bts_ccch_load_ind_thresh_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "ccch load-indication-threshold <0-100>",
+ CCCH_STR
+ "Percentage of CCCH load at which BTS sends RSL CCCH LOAD IND\n"
+ "CCCH Load Threshold in percent (Default: 10)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->ccch_load_ind_thresh = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_ccch_load_ind_period,
+ cfg_bts_ccch_load_ind_period_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "ccch load-indication-period <0-255>",
+ CCCH_STR
+ "Period of time at which BTS sends RSL CCCH LOAD IND\n"
+ "CCCH Load Indication Period in seconds (Default: 1)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->ccch_load_ind_period = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+#define NM_STR "Network Management\n"
+
+DEFUN_USRATTR(cfg_bts_rach_nm_b_thresh,
+ cfg_bts_rach_nm_b_thresh_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "rach nm busy threshold <0-255>",
+ RACH_STR NM_STR
+ "Set the NM Busy Threshold\n"
+ "Set the NM Busy Threshold\n"
+ "NM Busy Threshold in dB\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->rach_b_thresh = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rach_nm_ldavg,
+ cfg_bts_rach_nm_ldavg_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "rach nm load average <0-65535>",
+ RACH_STR NM_STR
+ "Set the NM Loadaverage Slots value\n"
+ "Set the NM Loadaverage Slots value\n"
+ "NM Loadaverage Slots value\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->rach_ldavg_slots = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_nch_position,
+ cfg_bts_nch_position_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "nch-position num-blocks <1-7> first-block <0-6>",
+ "NCH (Notification Channel) position within CCCH\n"
+ "Number of blocks reserved for NCH\n"
+ "Number of blocks reserved for NCH\n"
+ "First block reserved for NCH\n"
+ "First block reserved for NCH\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int num_blocks = atoi(argv[0]);
+ int first_block = atoi(argv[1]);
+
+ if (osmo_gsm48_si1ro_nch_pos_encode(num_blocks, first_block)) {
+ vty_out(vty, "num-blocks %u first-block %u is not permitted by 3GPP TS 44.010 Table 10.5.2.32.1b%s",
+ num_blocks, first_block, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->nch.num_blocks = num_blocks;
+ bts->nch.first_block = first_block;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_no_nch_position,
+ cfg_bts_no_nch_position_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "no nch-position",
+ NO_STR "Disable NCH in this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->nch.num_blocks = 0;
+ bts->nch.first_block = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_cell_barred,
+ cfg_bts_cell_barred_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "cell barred (0|1)",
+ "Should this cell be barred from access?\n"
+ "Should this cell be barred from access?\n"
+ "Cell should NOT be barred\n"
+ "Cell should be barred\n")
+
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.rach_control.cell_bar = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rach_ec_allowed,
+ cfg_bts_rach_ec_allowed_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "rach emergency call allowed (0|1)",
+ RACH_STR
+ "Should this cell allow emergency calls?\n"
+ "Should this cell allow emergency calls?\n"
+ "Should this cell allow emergency calls?\n"
+ "Do NOT allow emergency calls\n"
+ "Allow emergency calls\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (atoi(argv[0]) == 0)
+ bts->si_common.rach_control.t2 |= 0x4;
+ else
+ bts->si_common.rach_control.t2 &= ~0x4;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rach_re_allowed,
+ cfg_bts_rach_re_allowed_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "rach call-reestablishment allowed (0|1)",
+ RACH_STR
+ "Resume calls after radio link failure\n"
+ "Resume calls after radio link failure\n"
+ "Forbid MS to reestablish calls\n"
+ "Allow MS to try to reestablish calls\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (atoi(argv[0]) == 0)
+ bts->si_common.rach_control.re = 1;
+ else
+ bts->si_common.rach_control.re = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rach_ac_class,
+ cfg_bts_rach_ac_class_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "rach access-control-class (0|1|2|3|4|5|6|7|8|9|11|12|13|14|15) (barred|allowed)",
+ RACH_STR
+ "Set access control class\n"
+ "Access control class 0\n"
+ "Access control class 1\n"
+ "Access control class 2\n"
+ "Access control class 3\n"
+ "Access control class 4\n"
+ "Access control class 5\n"
+ "Access control class 6\n"
+ "Access control class 7\n"
+ "Access control class 8\n"
+ "Access control class 9\n"
+ "Access control class 11 for PLMN use\n"
+ "Access control class 12 for security services\n"
+ "Access control class 13 for public utilities (e.g. water/gas suppliers)\n"
+ "Access control class 14 for emergency services\n"
+ "Access control class 15 for PLMN staff\n"
+ "barred to use access control class\n"
+ "allowed to use access control class\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ uint8_t control_class;
+ uint8_t allowed = 0;
+
+ if (strcmp(argv[1], "allowed") == 0)
+ allowed = 1;
+
+ control_class = atoi(argv[0]);
+ if (control_class < 8)
+ if (allowed)
+ bts->si_common.rach_control.t3 &= ~(0x1 << control_class);
+ else
+ bts->si_common.rach_control.t3 |= (0x1 << control_class);
+ else
+ if (allowed)
+ bts->si_common.rach_control.t2 &= ~(0x1 << (control_class - 8));
+ else
+ bts->si_common.rach_control.t2 |= (0x1 << (control_class - 8));
+
+ if (control_class < 10)
+ acc_mgr_perm_subset_changed(&bts->acc_mgr, &bts->si_common.rach_control);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_ms_max_power,
+ cfg_bts_ms_max_power_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "ms max power <0-40>",
+ "MS Options\n"
+ "Maximum transmit power of the MS\n"
+ "Maximum transmit power of the MS\n"
+ "Maximum transmit power of the MS in dBm\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->ms_max_power = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define CELL_STR "Cell Parameters\n"
+
+DEFUN_USRATTR(cfg_bts_cell_resel_hyst,
+ cfg_bts_cell_resel_hyst_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "cell reselection hysteresis <0-14>",
+ CELL_STR "Cell re-selection parameters\n"
+ "Cell Re-Selection Hysteresis in dB\n"
+ "Cell Re-Selection Hysteresis in dB\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_rxlev_acc_min,
+ cfg_bts_rxlev_acc_min_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "rxlev access min <0-63>",
+ "Minimum RxLev needed for cell access\n"
+ "Minimum RxLev needed for cell access\n"
+ "Minimum RxLev needed for cell access\n"
+ "Minimum RxLev needed for cell access (better than -110dBm)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_cell_bar_qualify,
+ cfg_bts_cell_bar_qualify_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "cell bar qualify (0|1)",
+ CELL_STR "Cell Bar Qualify\n" "Cell Bar Qualify\n"
+ "Set CBQ to 0\n" "Set CBQ to 1\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.cbq = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_cell_resel_ofs,
+ cfg_bts_cell_resel_ofs_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "cell reselection offset <0-126>",
+ CELL_STR "Cell Re-Selection Parameters\n"
+ "Cell Re-Selection Offset (CRO) in dB\n"
+ "Cell Re-Selection Offset (CRO) in dB\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(argv[0])/2;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_temp_ofs,
+ cfg_bts_temp_ofs_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "temporary offset <0-60>",
+ "Cell selection temporary negative offset\n"
+ "Cell selection temporary negative offset\n"
+ "Cell selection temporary negative offset in dB\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.temp_offs = atoi(argv[0])/10;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_temp_ofs_inf,
+ cfg_bts_temp_ofs_inf_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "temporary offset infinite",
+ "Cell selection temporary negative offset\n"
+ "Cell selection temporary negative offset\n"
+ "Sets cell selection temporary negative offset to infinity\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.temp_offs = 7;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_penalty_time,
+ cfg_bts_penalty_time_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "penalty time <20-620>",
+ "Cell selection penalty time\n"
+ "Cell selection penalty time\n"
+ "Cell selection penalty time in seconds (by 20s increments)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.penalty_time = (atoi(argv[0])-20)/20;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_penalty_time_rsvd,
+ cfg_bts_penalty_time_rsvd_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "penalty time reserved",
+ "Cell selection penalty time\n"
+ "Cell selection penalty time\n"
+ "Set cell selection penalty time to reserved value 31, "
+ "(indicate that CELL_RESELECT_OFFSET is subtracted from C2 "
+ "and TEMPORARY_OFFSET is ignored)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.penalty_time = 31;
+
+ return CMD_SUCCESS;
+}
+
+#define NCC_STR "Network Colour Code\n"
+#define NCC_PERMITTED_STR "Set permitted NCCs\n"
+
+DEFUN_USRATTR(cfg_bts_ncc_permitted_all,
+ cfg_bts_ncc_permitted_all_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "ncc-permitted all\n",
+ NCC_PERMITTED_STR
+ "Permit all NCCs (default)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.ncc_permitted = 0xff;
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_ncc_permitted,
+ cfg_bts_ncc_permitted_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "ncc-permitted <1-8> [<1-8>] [<1-8>] [<1-8>] [<1-8>] [<1-8>] [<1-8>]\n",
+ NCC_PERMITTED_STR
+ NCC_STR NCC_STR NCC_STR NCC_STR NCC_STR NCC_STR NCC_STR)
+{
+ struct gsm_bts *bts = vty->index;
+ int i;
+ int ncc_prev = -1;
+
+ if (argc == 1 && !strcmp(argv[0], "all")) {
+ bts->si_common.ncc_permitted = 0xff;
+ return CMD_SUCCESS;
+ }
+
+ bts->si_common.ncc_permitted = 0x00;
+
+ /* Check if NCCs are in order (like get_amr_from_arg) */
+ for (i = 0; i < argc; i++) {
+ int ncc = atoi(argv[i]);
+ if (ncc_prev > ncc) {
+ vty_out(vty, "%% NCCs must be listed in order%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (ncc_prev == ncc) {
+ vty_out(vty, "%% NCCs must be unique%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ ncc_prev = ncc;
+ }
+
+ for (i = 0; i < argc; i++)
+ bts->si_common.ncc_permitted |= 1 << (atoi(argv[i]) - 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_radio_link_timeout,
+ cfg_bts_radio_link_timeout_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "radio-link-timeout <4-64>",
+ "Radio link timeout criterion (BTS side)\n"
+ "Radio link timeout value (lost SACCH block)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ unsigned int radio_link_timeout = atoi(argv[0]);
+
+ /* According to Table 10.5.2.3.1 in TS 144.018 */
+ if (radio_link_timeout % 4 != 0) {
+ vty_out(vty, "%% Radio link timeout must be a multiple of 4%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gsm_bts_set_radio_link_timeout(bts, radio_link_timeout);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_radio_link_timeout_inf,
+ cfg_bts_radio_link_timeout_inf_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "radio-link-timeout infinite",
+ "Radio link timeout criterion (BTS side)\n"
+ "Infinite Radio link timeout value (use only for BTS RF testing)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->type != GSM_BTS_TYPE_OSMOBTS) {
+ vty_out(vty, "%% infinite radio link timeout not supported by BTS %u%s", bts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% INFINITE RADIO LINK TIMEOUT, USE ONLY FOR BTS RF TESTING%s", VTY_NEWLINE);
+ gsm_bts_set_radio_link_timeout(bts, -1);
+
+ return CMD_SUCCESS;
+}
+
+#define GPRS_TEXT "GPRS Packet Network\n"
+
+#define GPRS_CHECK_ENABLED(bts) \
+ do { \
+ if (bts->gprs.mode == BTS_GPRS_NONE) { \
+ vty_out(vty, "%% GPRS is not enabled on BTS %u%s", \
+ bts->nr, VTY_NEWLINE); \
+ return CMD_WARNING; \
+ } \
+ } while (0)
+
+DEFUN_USRATTR(cfg_bts_prs_bvci,
+ cfg_bts_gprs_bvci_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs cell bvci <2-65535>",
+ GPRS_TEXT
+ "GPRS Cell Settings\n"
+ "GPRS BSSGP VC Identifier\n"
+ "GPRS BSSGP VC Identifier\n")
+{
+ /* ETSI TS 101 343: values 0 and 1 are reserved for signalling and PTM */
+ struct gsm_bts *bts = vty->index;
+
+ GPRS_CHECK_ENABLED(bts);
+
+ bts->gprs.cell.bvci = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_nsei,
+ cfg_bts_gprs_nsei_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs nsei <0-65535>",
+ GPRS_TEXT
+ "GPRS NS Entity Identifier\n"
+ "GPRS NS Entity Identifier\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ GPRS_CHECK_ENABLED(bts);
+
+ bts->site_mgr->gprs.nse.nsei = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \
+ "NSVC Logical Number\n"
+
+DEFUN_USRATTR(cfg_no_bts_gprs_nsvc,
+ cfg_no_bts_gprs_nsvc_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "no gprs nsvc <0-1>",
+ NO_STR GPRS_TEXT NSVC_TEXT)
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = atoi(argv[0]);
+
+ GPRS_CHECK_ENABLED(bts);
+
+ bts->site_mgr->gprs.nsvc[idx].enabled = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_nsvci,
+ cfg_bts_gprs_nsvci_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs nsvc <0-1> nsvci <0-65535>",
+ GPRS_TEXT NSVC_TEXT
+ "NS Virtual Connection Identifier\n"
+ "GPRS NS VC Identifier\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = atoi(argv[0]);
+
+ GPRS_CHECK_ENABLED(bts);
+
+ bts->site_mgr->gprs.nsvc[idx].nsvci = atoi(argv[1]);
+ bts->site_mgr->gprs.nsvc[idx].enabled = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_nsvc_lport,
+ cfg_bts_gprs_nsvc_lport_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs nsvc <0-1> local udp port <0-65535>",
+ GPRS_TEXT NSVC_TEXT
+ "GPRS NS Local UDP Port\n"
+ "GPRS NS Local UDP Port\n"
+ "GPRS NS Local UDP Port\n"
+ "GPRS NS Local UDP Port Number\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = atoi(argv[0]);
+
+ GPRS_CHECK_ENABLED(bts);
+
+ bts->site_mgr->gprs.nsvc[idx].local_port = atoi(argv[1]);
+ bts->site_mgr->gprs.nsvc[idx].enabled = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_nsvc_rport,
+ cfg_bts_gprs_nsvc_rport_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs nsvc <0-1> remote udp port <0-65535>",
+ GPRS_TEXT NSVC_TEXT
+ "GPRS NS Remote UDP Port\n"
+ "GPRS NS Remote UDP Port\n"
+ "GPRS NS Remote UDP Port\n"
+ "GPRS NS Remote UDP Port Number\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = atoi(argv[0]);
+
+ GPRS_CHECK_ENABLED(bts);
+
+ /* sockaddr_in and sockaddr_in6 have the port at the same position */
+ bts->site_mgr->gprs.nsvc[idx].remote.u.sin.sin_port = htons(atoi(argv[1]));
+ bts->site_mgr->gprs.nsvc[idx].enabled = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_nsvc_rip,
+ cfg_bts_gprs_nsvc_rip_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs nsvc <0-1> remote ip " VTY_IPV46_CMD,
+ GPRS_TEXT NSVC_TEXT
+ "GPRS NS Remote IP Address\n"
+ "GPRS NS Remote IP Address\n"
+ "GPRS NS Remote IPv4 Address\n"
+ "GPRS NS Remote IPv6 Address\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct osmo_sockaddr_str remote;
+ int idx = atoi(argv[0]);
+ int ret;
+
+ GPRS_CHECK_ENABLED(bts);
+
+ ret = osmo_sockaddr_str_from_str2(&remote, argv[1]);
+ if (ret) {
+ vty_out(vty, "%% Invalid IP address %s%s", argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Can't use osmo_sockaddr_str_to_sockaddr() because the port would be overridden */
+ bts->site_mgr->gprs.nsvc[idx].remote.u.sas.ss_family = remote.af;
+ switch (remote.af) {
+ case AF_INET:
+ osmo_sockaddr_str_to_in_addr(&remote, &bts->site_mgr->gprs.nsvc[idx].remote.u.sin.sin_addr);
+ bts->site_mgr->gprs.nsvc[idx].enabled = true;
+ break;
+ case AF_INET6:
+ osmo_sockaddr_str_to_in6_addr(&remote, &bts->site_mgr->gprs.nsvc[idx].remote.u.sin6.sin6_addr);
+ bts->site_mgr->gprs.nsvc[idx].enabled = true;
+ break;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_pag_free, cfg_bts_pag_free_cmd,
+ "paging free <-1-1024>",
+ "Paging options\n"
+ "Only page when having a certain amount of free slots\n"
+ "amount of required free paging slots. -1 to disable\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->paging.free_chans_need = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_ns_timer,
+ cfg_bts_gprs_ns_timer_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs ns timer " NS_TIMERS " <0-255>",
+ GPRS_TEXT "Network Service\n"
+ "Network Service Timer\n"
+ NS_TIMERS_HELP "Timer Value\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+ int val = atoi(argv[1]);
+
+ GPRS_CHECK_ENABLED(bts);
+
+ if (idx < 0 || idx >= ARRAY_SIZE(bts->site_mgr->gprs.nse.timer))
+ return CMD_WARNING;
+
+ bts->site_mgr->gprs.nse.timer[idx] = val;
+
+ return CMD_SUCCESS;
+}
+
+#define BSSGP_TIMERS "(blocking-timer|blocking-retries|unblocking-retries|reset-timer|reset-retries|suspend-timer|suspend-retries|resume-timer|resume-retries|capability-update-timer|capability-update-retries)"
+#define BSSGP_TIMERS_HELP \
+ "Tbvc-block timeout\n" \
+ "Tbvc-block retries\n" \
+ "Tbvc-unblock retries\n" \
+ "Tbvc-reset timeout\n" \
+ "Tbvc-reset retries\n" \
+ "Tbvc-suspend timeout\n" \
+ "Tbvc-suspend retries\n" \
+ "Tbvc-resume timeout\n" \
+ "Tbvc-resume retries\n" \
+ "Tbvc-capa-update timeout\n" \
+ "Tbvc-capa-update retries\n"
+
+DEFUN_USRATTR(cfg_bts_gprs_cell_timer,
+ cfg_bts_gprs_cell_timer_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs cell timer " BSSGP_TIMERS " <0-255>",
+ GPRS_TEXT "Cell / BSSGP\n"
+ "Cell/BSSGP Timer\n"
+ BSSGP_TIMERS_HELP "Timer Value\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]);
+ int val = atoi(argv[1]);
+
+ GPRS_CHECK_ENABLED(bts);
+
+ if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer))
+ return CMD_WARNING;
+
+ bts->gprs.cell.timer[idx] = val;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_rac,
+ cfg_bts_gprs_rac_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs routing area <0-255>",
+ GPRS_TEXT
+ "GPRS Routing Area Code\n"
+ "GPRS Routing Area Code\n"
+ "GPRS Routing Area Code\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ GPRS_CHECK_ENABLED(bts);
+
+ bts->gprs.rac = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_ctrl_ack,
+ cfg_bts_gprs_ctrl_ack_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "gprs control-ack-type-rach",
+ GPRS_TEXT
+ "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to "
+ "four access bursts format instead of default RLC/MAC control block\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ GPRS_CHECK_ENABLED(bts);
+
+ bts->gprs.ctrl_ack_type_use_block = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_ccn_active,
+ cfg_bts_gprs_ccn_active_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "gprs ccn-active (0|1|default)",
+ GPRS_TEXT
+ "Set CCN_ACTIVE in the GPRS Cell Options IE on the BCCH (SI13)\n"
+ "Disable\n" "Enable\n" "Default based on BTS type support\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->gprs.ccn.forced_vty = strcmp(argv[0], "default") != 0;
+
+ if (bts->gprs.ccn.forced_vty)
+ bts->gprs.ccn.active = argv[0][0] == '1';
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_pwr_ctrl_alpha,
+ cfg_bts_gprs_pwr_ctrl_alpha_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "gprs power-control alpha <0-10>",
+ GPRS_TEXT
+ "GPRS Global Power Control Parameters IE (SI13)\n"
+ "Set alpha\n"
+ "alpha for MS output power control in units of 0.1 (defaults to 0)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->gprs.pwr_ctrl.alpha = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_no_bts_gprs_ctrl_ack,
+ cfg_no_bts_gprs_ctrl_ack_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "no gprs control-ack-type-rach",
+ NO_STR GPRS_TEXT
+ "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to "
+ "default RLC/MAC control block\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ GPRS_CHECK_ENABLED(bts);
+
+ bts->gprs.ctrl_ack_type_use_block = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_net_ctrl_ord,
+ cfg_bts_gprs_net_ctrl_ord_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "gprs network-control-order (nc0|nc1|nc2)",
+ GPRS_TEXT
+ "GPRS Network Control Order\n"
+ "MS controlled cell re-selection, no measurement reporting\n"
+ "MS controlled cell re-selection, MS sends measurement reports\n"
+ "Network controlled cell re-selection, MS sends measurement reports\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ GPRS_CHECK_ENABLED(bts);
+
+ bts->gprs.net_ctrl_ord = atoi(argv[0] + 2);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_mode,
+ cfg_bts_gprs_mode_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "gprs mode (none|gprs|egprs)",
+ GPRS_TEXT
+ "GPRS Mode for this BTS\n"
+ "GPRS Disabled on this BTS\n"
+ "GPRS Enabled on this BTS\n"
+ "EGPRS (EDGE) Enabled on this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+ enum bts_gprs_mode mode = bts_gprs_mode_parse(argv[0], NULL);
+
+ if (bts->features_known && !bts_gprs_mode_is_compat(bts, mode)) {
+ vty_out(vty, "%% This BTS type does not support %s%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.mode = mode;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_bts_gprs_11bit_rach_support_for_egprs,
+ cfg_bts_gprs_11bit_rach_support_for_egprs_cmd,
+ "gprs 11bit_rach_support_for_egprs (0|1)",
+ GPRS_TEXT "EGPRS Packet Channel Request support\n"
+ "Disable EGPRS Packet Channel Request support\n"
+ "Enable EGPRS Packet Channel Request support\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ vty_out(vty, "%% 'gprs 11bit_rach_support_for_egprs' is now deprecated: "
+ "use '[no] gprs egprs-packet-channel-request' instead%s", VTY_NEWLINE);
+
+ bts->gprs.egprs_pkt_chan_request = (argv[0][0] == '1');
+
+ if (bts->gprs.mode == BTS_GPRS_NONE && bts->gprs.egprs_pkt_chan_request) {
+ vty_out(vty, "%% (E)GPRS is not enabled (see 'gprs mode')%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bts->gprs.mode != BTS_GPRS_EGPRS) {
+ vty_out(vty, "%% EGPRS Packet Channel Request support requires "
+ "EGPRS mode to be enabled (see 'gprs mode')%s", VTY_NEWLINE);
+ /* Do not return here, keep the old behaviour. */
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_gprs_egprs_pkt_chan_req,
+ cfg_bts_gprs_egprs_pkt_chan_req_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "gprs egprs-packet-channel-request",
+ GPRS_TEXT "EGPRS Packet Channel Request support")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gprs.mode != BTS_GPRS_EGPRS) {
+ vty_out(vty, "%% EGPRS Packet Channel Request support requires "
+ "EGPRS mode to be enabled (see 'gprs mode')%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.egprs_pkt_chan_request = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_no_gprs_egprs_pkt_chan_req,
+ cfg_bts_no_gprs_egprs_pkt_chan_req_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "no gprs egprs-packet-channel-request",
+ NO_STR GPRS_TEXT "EGPRS Packet Channel Request support")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gprs.mode != BTS_GPRS_EGPRS) {
+ vty_out(vty, "%% EGPRS Packet Channel Request support requires "
+ "EGPRS mode to be enabled (see 'gprs mode')%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.egprs_pkt_chan_request = false;
+ return CMD_SUCCESS;
+}
+
+#define SI_TEXT "System Information Messages\n"
+#define SI_TYPE_TEXT "(1|2|3|4|5|6|7|8|9|10|13|16|17|18|19|20|2bis|2ter|2quater|5bis|5ter)"
+#define SI_TYPE_HELP "System Information Type 1\n" \
+ "System Information Type 2\n" \
+ "System Information Type 3\n" \
+ "System Information Type 4\n" \
+ "System Information Type 5\n" \
+ "System Information Type 6\n" \
+ "System Information Type 7\n" \
+ "System Information Type 8\n" \
+ "System Information Type 9\n" \
+ "System Information Type 10\n" \
+ "System Information Type 13\n" \
+ "System Information Type 16\n" \
+ "System Information Type 17\n" \
+ "System Information Type 18\n" \
+ "System Information Type 19\n" \
+ "System Information Type 20\n" \
+ "System Information Type 2bis\n" \
+ "System Information Type 2ter\n" \
+ "System Information Type 2quater\n" \
+ "System Information Type 5bis\n" \
+ "System Information Type 5ter\n"
+
+DEFUN_USRATTR(cfg_bts_si_mode,
+ cfg_bts_si_mode_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "system-information " SI_TYPE_TEXT " mode (static|computed)",
+ SI_TEXT SI_TYPE_HELP
+ "System Information Mode\n"
+ "Static user-specified\n"
+ "Dynamic, BSC-computed\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int type;
+
+ type = get_string_value(osmo_sitype_strs, argv[0]);
+ if (type < 0) {
+ vty_out(vty, "%% Error SI Type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[1], "static"))
+ bts->si_mode_static |= (1 << type);
+ else
+ bts->si_mode_static &= ~(1 << type);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_si_static,
+ cfg_bts_si_static_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "system-information " SI_TYPE_TEXT " static HEXSTRING",
+ SI_TEXT SI_TYPE_HELP
+ "Static System Information filling\n"
+ "Static user-specified SI content in HEX notation\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int rc, type;
+
+ type = get_string_value(osmo_sitype_strs, argv[0]);
+ if (type < 0) {
+ vty_out(vty, "%% Error SI Type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!(bts->si_mode_static & (1 << type))) {
+ vty_out(vty, "%% SI Type %s is not configured in static mode%s",
+ get_value_string(osmo_sitype_strs, type), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Fill buffer with padding pattern */
+ memset(GSM_BTS_SI(bts, type), 0x2b, GSM_MACBLOCK_LEN);
+
+ /* Parse the user-specified SI in hex format, [partially] overwriting padding */
+ rc = osmo_hexparse(argv[1], GSM_BTS_SI(bts, type), GSM_MACBLOCK_LEN);
+ if (rc < 0 || rc > GSM_MACBLOCK_LEN) {
+ vty_out(vty, "%% Error parsing HEXSTRING%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Mark this SI as present */
+ bts->si_valid |= (1 << type);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_si_unused_send_empty,
+ cfg_bts_si_unused_send_empty_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "system-information unused-send-empty",
+ SI_TEXT
+ "Send BCCH Info with empty 'Full BCCH Info' TLV to notify disabled SI. "
+ "Some nanoBTS fw versions are known to fail upon receival of these messages.\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_unused_send_empty = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_no_si_unused_send_empty,
+ cfg_bts_no_si_unused_send_empty_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "no system-information unused-send-empty",
+ NO_STR SI_TEXT
+ "Avoid sending BCCH Info with empty 'Full BCCH Info' TLV to notify disabled SI. "
+ "Some nanoBTS fw versions are known to fail upon receival of these messages.\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (!is_ipa_abisip_bts(bts)) {
+ vty_out(vty, "%% This command is only intended for IPA Abis/IP BTS. See OS#3707.%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->si_unused_send_empty = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_early_cm,
+ cfg_bts_early_cm_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "early-classmark-sending (allowed|forbidden)",
+ "Early Classmark Sending\n"
+ "Early Classmark Sending is allowed\n"
+ "Early Classmark Sending is forbidden\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (!strcmp(argv[0], "allowed"))
+ bts->early_classmark_allowed = true;
+ else
+ bts->early_classmark_allowed = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_early_cm_3g,
+ cfg_bts_early_cm_3g_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "early-classmark-sending-3g (allowed|forbidden)",
+ "3G Early Classmark Sending\n"
+ "3G Early Classmark Sending is allowed\n"
+ "3G Early Classmark Sending is forbidden\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (!strcmp(argv[0], "allowed"))
+ bts->early_classmark_allowed_3g = true;
+ else
+ bts->early_classmark_allowed_3g = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_neigh_mode,
+ cfg_bts_neigh_mode_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "neighbor-list mode (automatic|manual|manual-si5)",
+ "Neighbor List\n" "Mode of Neighbor List generation\n"
+ "Automatically from all BTS in this BSC\n" "Manual\n"
+ "Manual with different lists for SI2 and SI5\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int mode = get_string_value(bts_neigh_mode_strs, argv[0]);
+
+ switch (mode) {
+ case NL_MODE_MANUAL_SI5SEP:
+ case NL_MODE_MANUAL:
+ /* make sure we clear the current list when switching to
+ * manual mode */
+ if (bts->neigh_list_manual_mode == 0)
+ memset(&bts->si_common.data.neigh_list, 0,
+ sizeof(bts->si_common.data.neigh_list));
+ break;
+ default:
+ break;
+ }
+
+ bts->neigh_list_manual_mode = mode;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_neigh,
+ cfg_bts_neigh_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "neighbor-list (add|del) arfcn <0-1023>",
+ "Neighbor List\n" "Add to manual neighbor list\n"
+ "Delete from manual neighbor list\n" "ARFCN of neighbor\n"
+ "ARFCN of neighbor\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct bitvec *bv = &bts->si_common.neigh_list;
+ uint16_t arfcn = atoi(argv[1]);
+ enum gsm_band unused;
+
+ if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) {
+ vty_out(vty, "%% Cannot configure neighbor list in "
+ "automatic mode%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "add"))
+ bitvec_set_bit_pos(bv, arfcn, 1);
+ else
+ bitvec_set_bit_pos(bv, arfcn, 0);
+
+ return CMD_SUCCESS;
+}
+
+/* help text should be kept in sync with EARFCN_*_INVALID defines */
+DEFUN_USRATTR(cfg_bts_si2quater_neigh_add,
+ cfg_bts_si2quater_neigh_add_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "si2quater neighbor-list add earfcn <0-65535> thresh-hi <0-31> "
+ "thresh-lo <0-32> prio <0-8> qrxlv <0-32> meas <0-8>",
+ "SI2quater Neighbor List\n" "SI2quater Neighbor List\n"
+ "Add to manual SI2quater neighbor list\n"
+ "EARFCN of neighbor\n" "EARFCN of neighbor\n"
+ "threshold high bits\n" "threshold high bits\n"
+ "threshold low bits\n" "threshold low bits (32 means NA)\n"
+ "priority\n" "priority (8 means NA)\n"
+ "QRXLEVMIN\n" "QRXLEVMIN (32 means NA)\n"
+ "measurement bandwidth\n" "measurement bandwidth (8 means NA)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ uint16_t arfcn = atoi(argv[0]);
+ uint8_t thresh_hi = atoi(argv[1]), thresh_lo = atoi(argv[2]),
+ prio = atoi(argv[3]), qrx = atoi(argv[4]), meas = atoi(argv[5]);
+ int r = bts_earfcn_add(bts, arfcn, thresh_hi, thresh_lo, prio, qrx, meas);
+
+ switch (r) {
+ case 1:
+ vty_out(vty, "%% Warning: multiple threshold-high are not supported, overriding with %u%s",
+ thresh_hi, VTY_NEWLINE);
+ break;
+ case EARFCN_THRESH_LOW_INVALID:
+ vty_out(vty, "%% Warning: multiple threshold-low are not supported, overriding with %u%s",
+ thresh_lo, VTY_NEWLINE);
+ break;
+ case EARFCN_QRXLV_INVALID + 1:
+ vty_out(vty, "%% Warning: multiple QRXLEVMIN are not supported, overriding with %u%s",
+ qrx, VTY_NEWLINE);
+ break;
+ case EARFCN_PRIO_INVALID:
+ vty_out(vty, "%% Warning: multiple priorities are not supported, overriding with %u%s",
+ prio, VTY_NEWLINE);
+ break;
+ default:
+ if (r < 0) {
+ vty_out(vty, "%% Unable to add ARFCN %u: %s%s", arfcn, strerror(-r), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ if (si2q_num(bts) <= SI2Q_MAX_NUM)
+ return CMD_SUCCESS;
+
+ vty_out(vty, "%% Warning: not enough space in SI2quater (%u/%u used) for a given EARFCN %u%s",
+ bts->si2q_count, SI2Q_MAX_NUM, arfcn, VTY_NEWLINE);
+
+ if (bts_earfcn_del(bts, arfcn) != 0)
+ vty_out(vty, "%% Failed to roll-back adding EARFCN %u%s", arfcn, VTY_NEWLINE);
+
+ return CMD_WARNING;
+}
+
+DEFUN_USRATTR(cfg_bts_si2quater_neigh_del,
+ cfg_bts_si2quater_neigh_del_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "si2quater neighbor-list del earfcn <0-65535>",
+ "SI2quater Neighbor List\n"
+ "SI2quater Neighbor List\n"
+ "Delete from SI2quater manual neighbor list\n"
+ "EARFCN of neighbor\n"
+ "EARFCN\n")
+{
+ struct gsm_bts *bts = vty->index;
+ uint16_t arfcn = atoi(argv[0]);
+ int r = bts_earfcn_del(bts, arfcn);
+ if (r < 0) {
+ vty_out(vty, "%% Unable to delete arfcn %u: %s%s", arfcn,
+ strerror(-r), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_si2quater_uarfcn_add,
+ cfg_bts_si2quater_uarfcn_add_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "si2quater neighbor-list add uarfcn <0-16383> <0-511> <0-1>",
+ "SI2quater Neighbor List\n"
+ "SI2quater Neighbor List\n" "Add to manual SI2quater neighbor list\n"
+ "UARFCN of neighbor\n" "UARFCN of neighbor\n" "scrambling code\n"
+ "diversity bit\n")
+{
+ struct gsm_bts *bts = vty->index;
+ uint16_t arfcn = atoi(argv[0]), scramble = atoi(argv[1]);
+
+ switch (bts_uarfcn_add(bts, arfcn, scramble, atoi(argv[2]))) {
+ case -ENOMEM:
+ vty_out(vty, "%% Unable to add UARFCN: max number of UARFCNs (%u) reached%s",
+ MAX_EARFCN_LIST, VTY_NEWLINE);
+ return CMD_WARNING;
+ case -ENOSPC:
+ vty_out(vty, "%% Warning: not enough space in SI2quater for a given UARFCN (%u, %u)%s",
+ arfcn, scramble, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_si2quater_uarfcn_del,
+ cfg_bts_si2quater_uarfcn_del_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "si2quater neighbor-list del uarfcn <0-16383> <0-511>",
+ "SI2quater Neighbor List\n"
+ "SI2quater Neighbor List\n"
+ "Delete from SI2quater manual neighbor list\n"
+ "UARFCN of neighbor\n"
+ "UARFCN\n"
+ "scrambling code\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts_uarfcn_del(bts, atoi(argv[0]), atoi(argv[1])) < 0) {
+ vty_out(vty, "%% Unable to delete uarfcn: pair not found%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_si5_neigh,
+ cfg_bts_si5_neigh_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK),
+ "si5 neighbor-list (add|del) arfcn <0-1023>",
+ "SI5 Neighbor List\n"
+ "SI5 Neighbor List\n" "Add to manual SI5 neighbor list\n"
+ "Delete from SI5 manual neighbor list\n" "ARFCN of neighbor\n"
+ "ARFCN of neighbor\n")
+{
+ enum gsm_band unused;
+ struct gsm_bts *bts = vty->index;
+ struct bitvec *bv = &bts->si_common.si5_neigh_list;
+ uint16_t arfcn = atoi(argv[1]);
+
+ if (!bts->neigh_list_manual_mode) {
+ vty_out(vty, "%% Cannot configure neighbor list in "
+ "automatic mode%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "add"))
+ bitvec_set_bit_pos(bv, arfcn, 1);
+ else
+ bitvec_set_bit_pos(bv, arfcn, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_acc_rotate,
+ cfg_bts_acc_rotate_cmd,
+ "access-control-class-rotate <0-10>",
+ "Enable Access Control Class allowed subset rotation\n"
+ "Size of the rotating allowed ACC 0-9 subset (default=10, no subset)\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ int len_allowed_adm = atoi(argv[0]);
+ acc_mgr_set_len_allowed_adm(&bts->acc_mgr, len_allowed_adm);
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_acc_rotate_quantum,
+ cfg_bts_acc_rotate_quantum_cmd,
+ "access-control-class-rotate-quantum <1-65535>",
+ "Time between rotation of ACC 0-9 generated subsets\n"
+ "Time in seconds (default=" OSMO_STRINGIFY_VAL(ACC_MGR_QUANTUM_DEFAULT) ")\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ uint32_t rotation_time_sec = (uint32_t)atoi(argv[0]);
+ acc_mgr_set_rotation_time(&bts->acc_mgr, rotation_time_sec);
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_acc_ramping,
+ cfg_bts_acc_ramping_cmd,
+ "access-control-class-ramping",
+ "Enable Access Control Class ramping\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_trx *trx;
+
+ if (!acc_ramp_is_enabled(&bts->acc_ramp)) {
+ acc_ramp_set_enabled(&bts->acc_ramp, true);
+ /* Start ramping if at least one TRX is usable */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx_is_usable(trx)) {
+ acc_ramp_trigger(&bts->acc_ramp);
+ break;
+ }
+ }
+ }
+
+ /*
+ * ACC ramping takes effect either when the BTS reconnects RSL,
+ * or when RF administrative state changes to 'unlocked'.
+ */
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_no_acc_ramping,
+ cfg_bts_no_acc_ramping_cmd,
+ "no access-control-class-ramping",
+ NO_STR
+ "Disable Access Control Class ramping\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (acc_ramp_is_enabled(&bts->acc_ramp)) {
+ acc_ramp_abort(&bts->acc_ramp);
+ acc_ramp_set_enabled(&bts->acc_ramp, false);
+ if (gsm_bts_set_system_infos(bts) != 0) {
+ vty_out(vty, "%% Filed to (re)generate System Information "
+ "messages, check the logs%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_acc_ramping_step_interval,
+ cfg_bts_acc_ramping_step_interval_cmd,
+ "access-control-class-ramping-step-interval (<"
+ OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MIN) "-"
+ OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MAX) ">|dynamic)",
+ "Configure Access Control Class ramping step interval\n"
+ "Set a fixed step interval (in seconds)\n"
+ "Use dynamic step interval based on BTS channel load (deprecated, don't use, ignored)\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ bool dynamic = (strcmp(argv[0], "dynamic") == 0);
+ int error;
+
+ if (dynamic) {
+ vty_out(vty, "%% access-control-class-ramping-step-interval 'dynamic' value is deprecated, ignoring it%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
+ }
+
+ error = acc_ramp_set_step_interval(&bts->acc_ramp, atoi(argv[0]));
+ if (error != 0) {
+ if (error == -ERANGE)
+ vty_out(vty, "%% Unable to set ACC ramp step interval: value out of range%s", VTY_NEWLINE);
+ else
+ vty_out(vty, "%% Unable to set ACC ramp step interval: unknown error%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_acc_ramping_step_size,
+ cfg_bts_acc_ramping_step_size_cmd,
+ "access-control-class-ramping-step-size (<"
+ OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MIN) "-"
+ OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MAX) ">)",
+ "Configure Access Control Class ramping step size\n"
+ "Set the number of Access Control Classes to enable per ramping step\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ int error;
+
+ error = acc_ramp_set_step_size(&bts->acc_ramp, atoi(argv[0]));
+ if (error != 0) {
+ if (error == -ERANGE)
+ vty_out(vty, "%% Unable to set ACC ramp step size: value out of range%s", VTY_NEWLINE);
+ else
+ vty_out(vty, "%% Unable to set ACC ramp step size: unknown error%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_acc_ramping_chan_load,
+ cfg_bts_acc_ramping_chan_load_cmd,
+ "access-control-class-ramping-chan-load <0-100> <0-100>",
+ "Configure Access Control Class ramping channel load thresholds\n"
+ "Lower Channel load threshold (%) below which subset size of allowed broadcast ACCs can be increased\n"
+ "Upper channel load threshold (%) above which subset size of allowed broadcast ACCs can be decreased\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ int rc;
+
+ rc = acc_ramp_set_chan_load_thresholds(&bts->acc_ramp, atoi(argv[0]), atoi(argv[1]));
+ if (rc < 0) {
+ vty_out(vty, "%% Unable to set ACC channel load thresholds%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+#define EXCL_RFLOCK_STR "Exclude this BTS from the global RF Lock\n"
+
+DEFUN_ATTR(cfg_bts_excl_rf_lock,
+ cfg_bts_excl_rf_lock_cmd,
+ "rf-lock-exclude",
+ EXCL_RFLOCK_STR,
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ bts->excl_from_rf_lock = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_no_excl_rf_lock,
+ cfg_bts_no_excl_rf_lock_cmd,
+ "no rf-lock-exclude",
+ NO_STR EXCL_RFLOCK_STR,
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ bts->excl_from_rf_lock = 0;
+ return CMD_SUCCESS;
+}
+
+#define FORCE_COMB_SI_STR "Force the generation of a single SI (no ter/bis)\n"
+
+DEFUN_USRATTR(cfg_bts_force_comb_si,
+ cfg_bts_force_comb_si_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "force-combined-si",
+ FORCE_COMB_SI_STR)
+{
+ struct gsm_bts *bts = vty->index;
+ bts->force_combined_si = 1;
+ bts->force_combined_si_set = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_no_force_comb_si,
+ cfg_bts_no_force_comb_si_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "no force-combined-si",
+ NO_STR FORCE_COMB_SI_STR)
+{
+ struct gsm_bts *bts = vty->index;
+ bts->force_combined_si = 0;
+ bts->force_combined_si_set = true;
+ return CMD_SUCCESS;
+}
+
+static void _get_codec_from_arg(struct vty *vty, int argc, const char *argv[])
+{
+ struct gsm_bts *bts = vty->index;
+ struct bts_codec_conf *codec = &bts->codec;
+ int i;
+
+ codec->hr = 0;
+ codec->efr = 0;
+ codec->amr = 0;
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "hr"))
+ codec->hr = 1;
+ if (!strcmp(argv[i], "efr"))
+ codec->efr = 1;
+ if (!strcmp(argv[i], "amr"))
+ codec->amr = 1;
+ }
+}
+
+#define CODEC_PAR_STR " (hr|efr|amr)"
+#define CODEC_HELP_STR "Half Rate\n" \
+ "Enhanced Full Rate\nAdaptive Multirate\n"
+
+DEFUN_USRATTR(cfg_bts_codec0,
+ cfg_bts_codec0_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "codec-support fr",
+ "Codec Support settings\nFullrate\n")
+{
+ _get_codec_from_arg(vty, 0, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_codec1,
+ cfg_bts_codec1_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "codec-support fr" CODEC_PAR_STR,
+ "Codec Support settings\nFullrate\n"
+ CODEC_HELP_STR)
+{
+ _get_codec_from_arg(vty, 1, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_codec2,
+ cfg_bts_codec2_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR,
+ "Codec Support settings\nFullrate\n"
+ CODEC_HELP_STR CODEC_HELP_STR)
+{
+ _get_codec_from_arg(vty, 2, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_codec3,
+ cfg_bts_codec3_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR,
+ "Codec Support settings\nFullrate\n"
+ CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR)
+{
+ _get_codec_from_arg(vty, 3, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_codec4,
+ cfg_bts_codec4_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR,
+ "Codec Support settings\nFullrate\n"
+ CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR)
+{
+ _get_codec_from_arg(vty, 4, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_depends_on, cfg_bts_depends_on_cmd,
+ "depends-on-bts <0-255>",
+ "This BTS can only be started if another one is up\n"
+ BTS_NR_STR, CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts *other_bts;
+ int dep = atoi(argv[0]);
+
+
+ if (!is_ipa_abisip_bts(bts)) {
+ vty_out(vty, "%% This feature is only available for IP systems.%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ other_bts = gsm_bts_num(bts->network, dep);
+ if (!other_bts || !is_ipa_abisip_bts(other_bts)) {
+ vty_out(vty, "%% This feature is only available for IP systems.%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (dep >= bts->nr) {
+ vty_out(vty, "%% Need to depend on an already declared unit.%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts_depend_mark(bts, dep);
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_no_depends_on, cfg_bts_no_depends_on_cmd,
+ "no depends-on-bts <0-255>",
+ NO_STR "This BTS can only be started if another one is up\n"
+ BTS_NR_STR, CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ int dep = atoi(argv[0]);
+
+ bts_depend_clear(bts, dep);
+ return CMD_SUCCESS;
+}
+
+#define AMR_TEXT "Adaptive Multi Rate settings\n"
+#define AMR_MODE_TEXT "Codec modes to use with AMR codec\n"
+#define AMR_START_TEXT "Initial codec mode to use with AMR\n" \
+ "Automatically\nFirst mode\nSecond mode\nThird mode\nFourth mode\n"
+#define AMR_MS_BTS_TEXT "MS side\nBTS side\n"
+#define AMR_TH_TEXT "Lower threshold(s) for switching between codec modes\n" AMR_MS_BTS_TEXT
+#define AMR_HY_TEXT "Hysteresis value(s) to obtain the higher threshold(s) " \
+ "for switching between codec modes\n" AMR_MS_BTS_TEXT
+
+static int get_amr_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+{
+ struct gsm_bts *bts = vty->index;
+ struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+ int i;
+ int mode;
+ int mode_prev = -1;
+
+ /* Check if mode parameters are in order */
+ for (i = 0; i < argc; i++) {
+ mode = atoi(argv[i]);
+ if (mode_prev > mode) {
+ vty_out(vty, "%% Modes must be listed in order%s",
+ VTY_NEWLINE);
+ return -1;
+ }
+
+ if (mode_prev == mode) {
+ vty_out(vty, "%% Modes must be unique %s", VTY_NEWLINE);
+ return -2;
+ }
+ mode_prev = mode;
+ }
+
+ /* Prepare the multirate configuration IE */
+ mr->gsm48_ie[1] = 0;
+ for (i = 0; i < argc; i++)
+ mr->gsm48_ie[1] |= 1 << atoi(argv[i]);
+ mr_conf->icmi = 0;
+
+ /* Store actual mode identifier values */
+ for (i = 0; i < argc; i++) {
+ mr->ms_mode[i].mode = atoi(argv[i]);
+ mr->bts_mode[i].mode = atoi(argv[i]);
+ }
+ mr->num_modes = argc;
+
+ /* Trim excess threshold and hysteresis values from previous config */
+ for (i = argc - 1; i < 4; i++) {
+ mr->ms_mode[i].threshold = 0;
+ mr->bts_mode[i].threshold = 0;
+ mr->ms_mode[i].hysteresis = 0;
+ mr->bts_mode[i].hysteresis = 0;
+ }
+ return 0;
+}
+
+static void get_amr_th_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+{
+ struct gsm_bts *bts = vty->index;
+ struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+ struct amr_mode *modes;
+ int i;
+
+ modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode;
+ for (i = 0; i < argc - 1; i++)
+ modes[i].threshold = atoi(argv[i + 1]);
+}
+
+static void get_amr_hy_from_arg(struct vty *vty, int argc, const char *argv[], int full)
+{
+ struct gsm_bts *bts = vty->index;
+ struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+ struct amr_mode *modes;
+ int i;
+
+ modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode;
+ for (i = 0; i < argc - 1; i++)
+ modes[i].hysteresis = atoi(argv[i + 1]);
+}
+
+static void get_amr_start_from_arg(struct vty *vty, const char *argv[], int full)
+{
+ struct gsm_bts *bts = vty->index;
+ struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+ int num = 0, i;
+
+ for (i = 0; i < ((full) ? 8 : 6); i++) {
+ if ((mr->gsm48_ie[1] & (1 << i))) {
+ num++;
+ }
+ }
+
+ if (argv[0][0] == 'a' || num == 0) {
+ mr_conf->icmi = 0;
+ mr_conf->smod = 0;
+ } else {
+ mr_conf->icmi = 1;
+ if (num < atoi(argv[0]))
+ mr_conf->smod = num - 1;
+ else
+ mr_conf->smod = atoi(argv[0]) - 1;
+ }
+}
+
+/* Give the current amr configuration a final consistency check by feeding the
+ * the configuration into the gsm48 multirate IE generator function */
+static int check_amr_config(struct vty *vty)
+{
+ int rc = 0;
+ struct amr_multirate_conf *mr;
+ const struct gsm48_multi_rate_conf *mr_conf;
+ struct gsm_bts *bts = vty->index;
+ int vty_rc = CMD_SUCCESS;
+
+ mr = &bts->mr_full;
+ mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie;
+ rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "%% Invalid AMR multirate configuration (tch-f, ms) - check parameters%s",
+ VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+
+ rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "%% Invalid AMR multirate configuration (tch-f, bts) - check parameters%s",
+ VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+
+ mr = &bts->mr_half;
+ mr_conf = (struct gsm48_multi_rate_conf*) mr->gsm48_ie;
+ rc = gsm48_multirate_config(NULL, mr_conf, mr->ms_mode, mr->num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "%% Invalid AMR multirate configuration (tch-h, ms) - check parameters%s",
+ VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+
+ rc = gsm48_multirate_config(NULL, mr_conf, mr->bts_mode, mr->num_modes);
+ if (rc != 0) {
+ vty_out(vty,
+ "%% Invalid AMR multirate configuration (tch-h, bts) - check parameters%s",
+ VTY_NEWLINE);
+ vty_rc = CMD_WARNING;
+ }
+
+ return vty_rc;
+}
+
+#define AMR_TCHF_PAR_STR " (0|1|2|3|4|5|6|7)"
+#define AMR_TCHF_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" \
+ "10,2k\n12,2k\n"
+
+#define AMR_TCHH_PAR_STR " (0|1|2|3|4|5)"
+#define AMR_TCHH_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n"
+
+#define AMR_TH_HELP_STR(a, b) \
+ "Threshold between codec mode " a " and " b " (in 0.5 dB steps)\n"
+#define AMR_HY_HELP_STR(a, b) \
+ "Hysteresis between codec mode " a " and " b " (in 0.5 dB steps)\n"
+
+DEFUN_USRATTR(cfg_bts_amr_fr_modes1,
+ cfg_bts_amr_fr_modes1_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f modes" AMR_TCHF_PAR_STR,
+ AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+ AMR_TCHF_HELP_STR)
+{
+ if (get_amr_from_arg(vty, 1, argv, 1))
+ return CMD_WARNING;
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_modes2,
+ cfg_bts_amr_fr_modes2_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
+ AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+ AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+{
+ if (get_amr_from_arg(vty, 2, argv, 1))
+ return CMD_WARNING;
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_modes3,
+ cfg_bts_amr_fr_modes3_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
+ AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+ AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+{
+ if (get_amr_from_arg(vty, 3, argv, 1))
+ return CMD_WARNING;
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_modes4,
+ cfg_bts_amr_fr_modes4_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
+ AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+ AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+{
+ if (get_amr_from_arg(vty, 4, argv, 1))
+ return CMD_WARNING;
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_start_mode,
+ cfg_bts_amr_fr_start_mode_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f start-mode (auto|1|2|3|4)",
+ AMR_TEXT "Full Rate\n" AMR_START_TEXT)
+{
+ get_amr_start_from_arg(vty, argv, 1);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_thres1,
+ cfg_bts_amr_fr_thres1_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f threshold (ms|bts) <0-63>",
+ AMR_TEXT "Full Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR("1", "2"))
+{
+ get_amr_th_from_arg(vty, 2, argv, 1);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_thres2,
+ cfg_bts_amr_fr_thres2_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f threshold (ms|bts) <0-63> <0-63>",
+ AMR_TEXT "Full Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR("1", "2")
+ AMR_TH_HELP_STR("2", "3"))
+{
+ get_amr_th_from_arg(vty, 3, argv, 1);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_thres3,
+ cfg_bts_amr_fr_thres3_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f threshold (ms|bts) <0-63> <0-63> <0-63>",
+ AMR_TEXT "Full Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR("1", "2")
+ AMR_TH_HELP_STR("2", "3")
+ AMR_TH_HELP_STR("3", "4"))
+{
+ get_amr_th_from_arg(vty, 4, argv, 1);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_hyst1,
+ cfg_bts_amr_fr_hyst1_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f hysteresis (ms|bts) <0-15>",
+ AMR_TEXT "Full Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR("1", "2"))
+{
+ get_amr_hy_from_arg(vty, 2, argv, 1);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_hyst2,
+ cfg_bts_amr_fr_hyst2_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f hysteresis (ms|bts) <0-15> <0-15>",
+ AMR_TEXT "Full Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR("1", "2")
+ AMR_HY_HELP_STR("2", "3"))
+{
+ get_amr_hy_from_arg(vty, 3, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_amr_fr_hyst3,
+ cfg_bts_amr_fr_hyst3_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-f hysteresis (ms|bts) <0-15> <0-15> <0-15>",
+ AMR_TEXT "Full Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR("1", "2")
+ AMR_HY_HELP_STR("2", "3")
+ AMR_HY_HELP_STR("3", "4"))
+{
+ get_amr_hy_from_arg(vty, 4, argv, 1);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_modes1,
+ cfg_bts_amr_hr_modes1_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h modes" AMR_TCHH_PAR_STR,
+ AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+ AMR_TCHH_HELP_STR)
+{
+ if (get_amr_from_arg(vty, 1, argv, 0))
+ return CMD_WARNING;
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_modes2,
+ cfg_bts_amr_hr_modes2_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
+ AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+ AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+{
+ if (get_amr_from_arg(vty, 2, argv, 0))
+ return CMD_WARNING;
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_modes3,
+ cfg_bts_amr_hr_modes3_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
+ AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+ AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+{
+ if (get_amr_from_arg(vty, 3, argv, 0))
+ return CMD_WARNING;
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_modes4,
+ cfg_bts_amr_hr_modes4_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
+ AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+ AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+{
+ if (get_amr_from_arg(vty, 4, argv, 0))
+ return CMD_WARNING;
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_start_mode,
+ cfg_bts_amr_hr_start_mode_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h start-mode (auto|1|2|3|4)",
+ AMR_TEXT "Half Rate\n" AMR_START_TEXT)
+{
+ get_amr_start_from_arg(vty, argv, 0);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_thres1,
+ cfg_bts_amr_hr_thres1_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h threshold (ms|bts) <0-63>",
+ AMR_TEXT "Half Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR("1", "2"))
+{
+ get_amr_th_from_arg(vty, 2, argv, 0);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_thres2,
+ cfg_bts_amr_hr_thres2_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h threshold (ms|bts) <0-63> <0-63>",
+ AMR_TEXT "Half Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR("1", "2")
+ AMR_TH_HELP_STR("2", "3"))
+{
+ get_amr_th_from_arg(vty, 3, argv, 0);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_thres3,
+ cfg_bts_amr_hr_thres3_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h threshold (ms|bts) <0-63> <0-63> <0-63>",
+ AMR_TEXT "Half Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR("1", "2")
+ AMR_TH_HELP_STR("2", "3")
+ AMR_TH_HELP_STR("3", "4"))
+{
+ get_amr_th_from_arg(vty, 4, argv, 0);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_hyst1,
+ cfg_bts_amr_hr_hyst1_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h hysteresis (ms|bts) <0-15>",
+ AMR_TEXT "Half Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR("1", "2"))
+{
+ get_amr_hy_from_arg(vty, 2, argv, 0);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_hyst2,
+ cfg_bts_amr_hr_hyst2_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h hysteresis (ms|bts) <0-15> <0-15>",
+ AMR_TEXT "Half Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR("1", "2")
+ AMR_HY_HELP_STR("2", "3"))
+{
+ get_amr_hy_from_arg(vty, 3, argv, 0);
+ return check_amr_config(vty);
+}
+
+DEFUN_USRATTR(cfg_bts_amr_hr_hyst3,
+ cfg_bts_amr_hr_hyst3_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "amr tch-h hysteresis (ms|bts) <0-15> <0-15> <0-15>",
+ AMR_TEXT "Half Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR("1", "2")
+ AMR_HY_HELP_STR("2", "3")
+ AMR_HY_HELP_STR("3", "4"))
+{
+ get_amr_hy_from_arg(vty, 4, argv, 0);
+ return check_amr_config(vty);
+}
+
+#define OSMUX_STR "RTP multiplexing\n"
+DEFUN_USRATTR(cfg_bts_osmux,
+ cfg_bts_osmux_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "osmux (on|off|only)",
+ OSMUX_STR "Enable OSMUX\n" "Disable OSMUX\n" "Only use OSMUX\n")
+{
+ struct gsm_bts *bts = vty->index;
+ enum osmux_usage use;
+
+ if (strcmp(argv[0], "off") == 0)
+ use = OSMUX_USAGE_OFF;
+ else if (strcmp(argv[0], "on") == 0)
+ use = OSMUX_USAGE_ON;
+ else if (strcmp(argv[0], "only") == 0)
+ use = OSMUX_USAGE_ONLY;
+ else
+ goto err;
+
+ if (!is_osmobts(bts))
+ goto err;
+
+ if (bts->features_known && use != OSMUX_USAGE_OFF &&
+ !osmo_bts_has_feature(&bts->features, BTS_FEAT_OSMUX))
+ goto err;
+
+ bts->use_osmux = use;
+ return CMD_SUCCESS;
+
+err:
+ LOGP(DNM, LOGL_ERROR,
+ "(bts=%u) Unable to set 'osmux %s', BTS does not support Osmux\n",
+ bts->nr, argv[0]);
+ return CMD_WARNING;
+}
+
+DEFUN_USRATTR(cfg_bts_mgw_pool_target,
+ cfg_bts_mgw_pool_target_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "mgw pool-target <0-255> [strict]",
+ "MGW configuration for this specific BTS\n"
+ "Pin BTS to use a single MGW in the pool\n"
+ "Reference Number of the MGW (in the config) to pin to\n"
+ "Strictly prohibit use of other MGWs if the pinned one is not available\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int mgw_nr = atoi(argv[0]);
+ bool strict = argc > 1;
+ bts->mgw_pool_target = mgw_nr;
+ bts->mgw_pool_target_strict = strict;
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_no_mgw_pool_target,
+ cfg_bts_no_mgw_pool_target_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "no mgw pool-target",
+ NO_STR "MGW configuration for this specific BTS\n"
+ "Avoid pinning the BTS to any specific MGW (default)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->mgw_pool_target = -1;
+ bts->mgw_pool_target_strict = false;
+ return CMD_SUCCESS;
+}
+
+#define TNUM_STR "T-number, optionally preceded by 't' or 'T'\n"
+DEFUN_ATTR(cfg_bts_t3113_dynamic, cfg_bts_t3113_dynamic_cmd,
+ "timer-dynamic TNNNN",
+ "Calculate T3113 dynamically based on channel config and load (default)\n"
+ TNUM_STR,
+ CMD_ATTR_IMMEDIATE)
+{
+ struct osmo_tdef *d;
+ struct gsm_bts *bts = vty->index;
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+ d = osmo_tdef_vty_parse_T_arg(vty, gsmnet->T_defs, argv[0]);
+ if (!d)
+ return CMD_WARNING;
+
+ switch (d->T) {
+ case 3113:
+ bts->T3113_dynamic = true;
+ break;
+ default:
+ vty_out(vty, "%% T%d cannot be set to dynamic%s", d->T, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_no_t3113_dynamic, cfg_bts_no_t3113_dynamic_cmd,
+ "no timer-dynamic TNNNN",
+ NO_STR
+ "Set given timer to non-dynamic and use the default or user provided fixed value\n"
+ TNUM_STR,
+ CMD_ATTR_IMMEDIATE)
+{
+ struct osmo_tdef *d;
+ struct gsm_bts *bts = vty->index;
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+ d = osmo_tdef_vty_parse_T_arg(vty, gsmnet->T_defs, argv[0]);
+ if (!d)
+ return CMD_WARNING;
+
+ switch (d->T) {
+ case 3113:
+ bts->T3113_dynamic = false;
+ break;
+ default:
+ vty_out(vty, "%% T%d already is non-dynamic%s", d->T, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_interf_meas_avg_period,
+ cfg_bts_interf_meas_avg_period_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "interference-meas avg-period <1-31>",
+ "Interference measurement parameters\n"
+ "Averaging period (Intave)\n"
+ "Number of SACCH multiframes\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->interf_meas_params_cfg.avg_period = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_bts_interf_meas_level_bounds,
+ cfg_bts_interf_meas_level_bounds_cmd,
+ X(BSC_VTY_ATTR_RESTART_ABIS_OML_LINK),
+ "interference-meas level-bounds "
+ "<-120-0> <-120-0> <-120-0> <-120-0> <-120-0> <-120-0>",
+ "Interference measurement parameters\n"
+ "Interference level Boundaries. 3GPP do not specify whether these should be in ascending or descending"
+ " order (3GPP TS 48.058 9.3.21 / 3GPP TS 52.021 9.4.25). OsmoBSC supports either ordering, but possibly"
+ " some BTS models only return meaningful interference levels with one specific ordering.\n"
+ "Interference boundary 0 (dBm)\n"
+ "Interference boundary X1 (dBm)\n"
+ "Interference boundary X2 (dBm)\n"
+ "Interference boundary X3 (dBm)\n"
+ "Interference boundary X4 (dBm)\n"
+ "Interference boundary X5 (dBm)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(bts->interf_meas_params_cfg.bounds_dbm); i++) {
+ bts->interf_meas_params_cfg.bounds_dbm[i] = abs(atoi(argv[i]));
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_srvcc_fast_return, cfg_bts_srvcc_fast_return_cmd,
+ "srvcc fast-return (allow|forbid)",
+ "SRVCC Configuration\n"
+ "Allow or forbid Fast Return to 4G on Channel Release in this BTS\n"
+ "Allow\n"
+ "Forbid\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->srvcc_fast_return_allowed = strcmp(argv[0], "allow") == 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_bts_immediate_assignment, cfg_bts_immediate_assignment_cmd,
+ "immediate-assignment (post-chan-ack|pre-chan-ack|pre-ts-ack)",
+ "Configure time of Immediate Assignment after ChanRqd RACH (Abis optimization)\n"
+ "Send the Immediate Assignment after the Channel Activation ACK (normal sequence)\n"
+ "Send the Immediate Assignment directly after Channel Activation (early), without waiting for the ACK;"
+ " This may help with double allocations on high latency Abis links\n"
+ "EXPERIMENTAL: If a dynamic timeslot switch is necessary, send the Immediate Assignment even before the"
+ " timeslot is switched, i.e. even before the Channel Activation is sent (very early)\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ struct gsm_bts *bts = vty->index;
+ enum imm_ass_time imm_ass_time;
+
+ if (!strcmp(argv[0], "pre-ts-ack"))
+ imm_ass_time = IMM_ASS_TIME_PRE_TS_ACK;
+ else if (!strcmp(argv[0], "pre-chan-ack"))
+ imm_ass_time = IMM_ASS_TIME_PRE_CHAN_ACK;
+ else
+ imm_ass_time = IMM_ASS_TIME_POST_CHAN_ACK;
+
+ if (imm_ass_time != IMM_ASS_TIME_POST_CHAN_ACK) {
+ struct gsm_bts_trx *trx;
+
+ /* Early-IA does not work with frequency hopping, because the IMM ASS does not convey an ARFCN when
+ * frequency hopping is in use. Make sure the user knows that. */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ int ts_nr;
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ if (ts->hopping.enabled) {
+ vty_out(vty, "%% ERROR: 'hopping enabled 1' works only with"
+ " 'immediate-assignment post-chan-ack', see timeslot %s%s",
+ ts->fi->id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+ }
+ }
+
+ bts->imm_ass_time = imm_ass_time;
+ return CMD_SUCCESS;
+}
+
+#define BS_POWER_CONTROL_CMD \
+ "bs-power-control"
+#define MS_POWER_CONTROL_CMD \
+ "ms-power-control"
+#define POWER_CONTROL_CMD \
+ "(" BS_POWER_CONTROL_CMD "|" MS_POWER_CONTROL_CMD ")"
+#define POWER_CONTROL_DESC \
+ "BS (Downlink) power control parameters\n" \
+ "MS (Uplink) power control parameters\n"
+
+#define BTS_POWER_CTRL_PARAMS(bts) \
+ (strcmp(argv[0], BS_POWER_CONTROL_CMD) == 0) ? \
+ &bts->bs_power_ctrl : &bts->ms_power_ctrl
+
+DEFUN_USRATTR(cfg_bts_no_power_ctrl,
+ cfg_bts_no_power_ctrl_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "no " POWER_CONTROL_CMD,
+ NO_STR POWER_CONTROL_DESC)
+{
+ struct gsm_power_ctrl_params *params;
+ struct gsm_bts *bts = vty->index;
+
+ params = BTS_POWER_CTRL_PARAMS(bts);
+ params->mode = GSM_PWR_CTRL_MODE_NONE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_power_ctrl,
+ cfg_bts_power_ctrl_cmd,
+ POWER_CONTROL_CMD,
+ POWER_CONTROL_DESC)
+{
+ struct gsm_power_ctrl_params *params;
+ struct gsm_bts *bts = vty->index;
+
+ params = BTS_POWER_CTRL_PARAMS(bts);
+ vty->node = POWER_CTRL_NODE;
+ vty->index = params;
+
+ /* Change the prefix to reflect MS/BS difference */
+ if (params->dir == GSM_PWR_CTRL_DIR_UL)
+ power_ctrl_node.prompt = "%s(config-ms-power-ctrl)# ";
+ else
+ power_ctrl_node.prompt = "%s(config-bs-power-ctrl)# ";
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_mode,
+ cfg_power_ctrl_mode_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "mode (static|dyn-bts|dyn-bsc) [reset]",
+ "Power control mode\n"
+ "Instruct the MS/BTS to use a static power level\n"
+ "Power control to be performed dynamically by the BTS itself\n"
+ "Power control to be performed dynamically at this BSC\n"
+ "Reset to default parameters for the given mode\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+
+ /* Do we need to reset? */
+ if (argc > 1) {
+ vty_out(vty, "%% Reset to default parameters%s", VTY_NEWLINE);
+ power_ctrl_params_def_reset(params, params->dir);
+ }
+
+ if (strcmp(argv[0], "static") == 0)
+ params->mode = GSM_PWR_CTRL_MODE_STATIC;
+ else if (strcmp(argv[0], "dyn-bts") == 0)
+ params->mode = GSM_PWR_CTRL_MODE_DYN_BTS;
+ else if (strcmp(argv[0], "dyn-bsc") == 0) {
+ if (params->dir == GSM_PWR_CTRL_DIR_DL) {
+ vty_out(vty, "%% mode dyn-bsc not supported for Downlink.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ params->mode = GSM_PWR_CTRL_MODE_DYN_BSC;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_bs_power,
+ cfg_power_ctrl_bs_power_cmd,
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "bs-power (static|dyn-max) <0-30>",
+ "BS Power IE value to be sent to the BTS\n"
+ "Fixed BS Power reduction value (for static mode)\n"
+ "Maximum BS Power reduction value (for dynamic mode)\n"
+ "BS Power reduction value (in dB, even numbers only)\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ bool dynamic = !strcmp(argv[0], "dyn-max");
+ int value = atoi(argv[1]);
+
+ if (params->dir != GSM_PWR_CTRL_DIR_DL) {
+ vty_out(vty, "%% This command is only valid for "
+ "'bs-power-control' node%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (value % 2 != 0) {
+ vty_out(vty, "%% Incorrect BS Power reduction value, "
+ "an even number is expected%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (dynamic) /* maximum value */
+ params->bs_power_max_db = value;
+ else /* static (fixed) value */
+ params->bs_power_val_db = value;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_ctrl_interval,
+ cfg_power_ctrl_ctrl_interval_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "ctrl-interval <0-31>",
+ "Set power control interval (for dynamic mode)\n"
+ "P_CON_INTERVAL, in units of 2 SACCH periods (0.96 seconds)(default=1)\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+
+ params->ctrl_interval = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_step_size,
+ cfg_power_ctrl_step_size_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "step-size inc <2-6> red <2-4>",
+ "Set power change step size (for dynamic mode)\n"
+ "Increase step size (default is 4 dB)\n"
+ "Step size (2, 4, or 6 dB)\n"
+ "Reduce step size (default is 2 dB)\n"
+ "Step size (2 or 4 dB)\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ int inc_step_size_db = atoi(argv[0]);
+ int red_step_size_db = atoi(argv[1]);
+
+ if (inc_step_size_db % 2 || red_step_size_db % 2) {
+ vty_out(vty, "%% Power change step size must be "
+ "an even number%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Recommendation: POW_RED_STEP_SIZE <= POW_INCR_STEP_SIZE */
+ if (red_step_size_db > inc_step_size_db) {
+ vty_out(vty, "%% Increase step size (%d) should be greater "
+ "than reduce step size (%d), consider changing it%s",
+ inc_step_size_db, red_step_size_db, VTY_NEWLINE);
+ }
+
+ /* Recommendation: POW_INCR_STEP_SIZE <= (U_RXLEV_XX_P - L_RXLEV_XX_P) */
+ const struct gsm_power_ctrl_meas_params *mp = &params->rxlev_meas;
+ if (inc_step_size_db > (mp->upper_thresh - mp->lower_thresh)) {
+ vty_out(vty, "%% Increase step size (%d) should be less or equal "
+ "than/to the RxLev threshold window (%d, upper - lower), "
+ "consider changing it%s", inc_step_size_db,
+ mp->upper_thresh - mp->lower_thresh, VTY_NEWLINE);
+ }
+
+ params->inc_step_size_db = inc_step_size_db;
+ params->red_step_size_db = red_step_size_db;
+
+ return CMD_SUCCESS;
+}
+
+#define POWER_CONTROL_MEAS_RXLEV_DESC \
+ "RxLev value (signal strength, 0 is worst, 63 is best)\n"
+#define POWER_CONTROL_MEAS_RXQUAL_DESC \
+ "RxQual value (signal quality, 0 is best, 7 is worst)\n"
+#define POWER_CONTROL_MEAS_CI_DESC \
+ "C/I value (Carrier-to-Interference (dB), 0 is worst, 30 is best)\n"
+
+DEFUN_USRATTR(cfg_power_ctrl_rxlev_thresh,
+ cfg_power_ctrl_rxlev_thresh_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "rxlev-thresh lower <0-63> upper <0-63>",
+ "Set target RxLev thresholds (for dynamic mode)\n"
+ "Lower RxLev value (default is 32, i.e. -78 dBm)\n"
+ "Lower " POWER_CONTROL_MEAS_RXLEV_DESC
+ "Upper RxLev value (default is 38, i.e. -72 dBm)\n"
+ "Upper " POWER_CONTROL_MEAS_RXLEV_DESC)
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ int lower = atoi(argv[0]);
+ int upper = atoi(argv[1]);
+
+ if (lower > upper) {
+ vty_out(vty, "%% Lower 'rxlev-thresh' (%d) must be less than upper (%d)%s",
+ lower, upper, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ params->rxlev_meas.lower_thresh = lower;
+ params->rxlev_meas.upper_thresh = upper;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_rxqual_thresh,
+ cfg_power_ctrl_rxqual_thresh_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "rxqual-thresh lower <0-7> upper <0-7>",
+ "Set target RxQual thresholds (for dynamic mode)\n"
+ "Lower RxQual value (default is 3, i.e. 0.8% <= BER < 1.6%)\n"
+ "Lower " POWER_CONTROL_MEAS_RXQUAL_DESC
+ "Upper RxQual value (default is 0, i.e. BER < 0.2%)\n"
+ "Upper " POWER_CONTROL_MEAS_RXQUAL_DESC)
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ int lower = atoi(argv[0]);
+ int upper = atoi(argv[1]);
+
+ /* RxQual: 0 is best, 7 is worst, so upper must be less */
+ if (upper > lower) {
+ vty_out(vty, "%% Upper 'rxqual-rxqual' (%d) must be less than lower (%d)%s",
+ upper, lower, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ params->rxqual_meas.lower_thresh = lower;
+ params->rxqual_meas.upper_thresh = upper;
+
+ return CMD_SUCCESS;
+}
+
+#define VTY_CMD_CI_TYPE "(fr-efr|hr|amr-fr|amr-hr|sdcch|gprs)"
+#define VTY_CMD_CI_OR_ALL_TYPE "(fr-efr|hr|amr-fr|amr-hr|sdcch|gprs|all)"
+#define VTY_DESC_CI_TYPE \
+ "Channel Type FR/EFR\n" \
+ "Channel Type HR\n" \
+ "Channel Type AMR FR\n" \
+ "Channel Type AMR HR\n" \
+ "Channel Type SDCCH\n" \
+ "Channel Type (E)GPRS\n"
+#define VTY_DESC_CI_OR_ALL_TYPE VTY_DESC_CI_TYPE "All Channel Types\n"
+
+static struct gsm_power_ctrl_meas_params *ci_thresh_by_conn_type(struct gsm_power_ctrl_params *params, const char *type)
+{
+ if (!strcmp(type, "fr-efr"))
+ return &params->ci_fr_meas;
+ if (!strcmp(type, "hr"))
+ return &params->ci_hr_meas;
+ if (!strcmp(type, "amr-fr"))
+ return &params->ci_amr_fr_meas;
+ if (!strcmp(type, "amr-hr"))
+ return &params->ci_amr_hr_meas;
+ if (!strcmp(type, "sdcch"))
+ return &params->ci_sdcch_meas;
+ if (!strcmp(type, "gprs"))
+ return &params->ci_gprs_meas;
+ OSMO_ASSERT(false);
+ return NULL;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_ci_thresh,
+ cfg_power_ctrl_ci_thresh_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "ci-thresh " VTY_CMD_CI_TYPE " lower <0-30> upper <0-30>",
+ "Set target C/I thresholds (for dynamic mode), only available in ms-power-control\n"
+ VTY_DESC_CI_TYPE
+ "Lower C/I value\n"
+ "Lower " POWER_CONTROL_MEAS_CI_DESC
+ "Upper C/I value\n"
+ "Upper " POWER_CONTROL_MEAS_CI_DESC)
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ const char *type = argv[0];
+ int lower = atoi(argv[1]);
+ int upper = atoi(argv[2]);
+ struct gsm_power_ctrl_meas_params *meas_params;
+
+ if (params->mode == GSM_PWR_CTRL_MODE_DYN_BSC) {
+ vty_out(vty, "%% C/I based power loop not possible in dyn-bsc mode!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (params->dir != GSM_PWR_CTRL_DIR_UL) {
+ vty_out(vty, "%% C/I based power loop only possible in Uplink!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (lower > upper) {
+ vty_out(vty, "%% Lower 'rxqual-rxqual' (%d) must be less than upper (%d)%s",
+ upper, lower, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ meas_params = ci_thresh_by_conn_type(params, type);
+
+ meas_params->lower_thresh = lower;
+ meas_params->upper_thresh = upper;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_ci_thresh_disable,
+ cfg_power_ctrl_ci_thresh_disable_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "ci-thresh " VTY_CMD_CI_OR_ALL_TYPE " (enable|disable)",
+ "Set target C/I thresholds (for dynamic mode), only available in ms-power-control\n"
+ VTY_DESC_CI_OR_ALL_TYPE
+ "Enable C/I comparison in control loop\n"
+ "Disable C/I comparison in control loop\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+
+ bool enable = strcmp(argv[1], "enable") == 0;
+
+ if (strcmp(argv[0], "all") == 0) {
+ params->ci_fr_meas.enabled = enable;
+ params->ci_hr_meas.enabled = enable;
+ params->ci_amr_fr_meas.enabled = enable;
+ params->ci_amr_hr_meas.enabled = enable;
+ params->ci_sdcch_meas.enabled = enable;
+ params->ci_gprs_meas.enabled = enable;
+ } else {
+ struct gsm_power_ctrl_meas_params *meas_params = ci_thresh_by_conn_type(params, argv[0]);
+ meas_params->enabled = enable;
+ }
+
+ return CMD_SUCCESS;
+}
+
+#define POWER_CONTROL_MEAS_THRESH_COMP_CMD(meas) \
+ meas " lower <0-31> <0-31> upper <0-31> <0-31>"
+#define POWER_CONTROL_MEAS_THRESH_COMP_DESC(meas, opt_param, lp, ln, up, un) \
+ "Set " meas " threshold comparators (for dynamic mode)\n" \
+ opt_param \
+ "Lower " meas " threshold comparators (see 3GPP TS 45.008, A.3.2.1)\n" lp ln \
+ "Upper " meas " threshold comparators (see 3GPP TS 45.008, A.3.2.1)\n" up un
+
+DEFUN_USRATTR(cfg_power_ctrl_rxlev_thresh_comp,
+ cfg_power_ctrl_rxlev_thresh_comp_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ POWER_CONTROL_MEAS_THRESH_COMP_CMD("rxlev-thresh-comp"),
+ POWER_CONTROL_MEAS_THRESH_COMP_DESC("RxLev", /*empty*/,
+ "P1 (default 10)\n", "N1 (default 12)\n",
+ "P2 (default 10)\n", "N2 (default 12)\n"))
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ int lower_cmp_p = atoi(argv[0]);
+ int lower_cmp_n = atoi(argv[1]);
+ int upper_cmp_p = atoi(argv[2]);
+ int upper_cmp_n = atoi(argv[3]);
+
+ if (lower_cmp_p > lower_cmp_n) {
+ vty_out(vty, "%% Lower RxLev P1 %d must be less than N1 %d%s",
+ lower_cmp_p, lower_cmp_n, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (upper_cmp_p > upper_cmp_n) {
+ vty_out(vty, "%% Upper RxLev P2 %d must be less than N2 %d%s",
+ upper_cmp_p, upper_cmp_n, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ params->rxlev_meas.lower_cmp_p = lower_cmp_p;
+ params->rxlev_meas.lower_cmp_n = lower_cmp_n;
+ params->rxlev_meas.upper_cmp_p = upper_cmp_p;
+ params->rxlev_meas.upper_cmp_n = upper_cmp_n;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_rxqual_thresh_comp,
+ cfg_power_ctrl_rxqual_thresh_comp_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ POWER_CONTROL_MEAS_THRESH_COMP_CMD("rxqual-thresh-comp"),
+ POWER_CONTROL_MEAS_THRESH_COMP_DESC("RxQual", /*empty*/,
+ "P3 (default 5)\n", "N3 (default 7)\n",
+ "P4 (default 15)\n", "N4 (default 18)\n"))
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ int lower_cmp_p = atoi(argv[0]);
+ int lower_cmp_n = atoi(argv[1]);
+ int upper_cmp_p = atoi(argv[2]);
+ int upper_cmp_n = atoi(argv[3]);
+
+ if (lower_cmp_p > lower_cmp_n) {
+ vty_out(vty, "%% Lower RxQual P3 %d must be less than N3 %d%s",
+ lower_cmp_p, lower_cmp_n, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (upper_cmp_p > upper_cmp_n) {
+ vty_out(vty, "%% Upper RxQual P4 %d must be less than N4 %d%s",
+ upper_cmp_p, upper_cmp_n, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ params->rxqual_meas.lower_cmp_p = lower_cmp_p;
+ params->rxqual_meas.lower_cmp_n = lower_cmp_n;
+ params->rxqual_meas.upper_cmp_p = upper_cmp_p;
+ params->rxqual_meas.upper_cmp_n = upper_cmp_n;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_ci_thresh_comp,
+ cfg_power_ctrl_ci_thresh_comp_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ POWER_CONTROL_MEAS_THRESH_COMP_CMD("ci-thresh-comp " VTY_CMD_CI_TYPE),
+ POWER_CONTROL_MEAS_THRESH_COMP_DESC("Carrier-to_interference (C/I)",
+ VTY_DESC_CI_TYPE,
+ "Lower P (default 5)\n", "Lower N (default 7)\n",
+ "Upper P (default 15)\n", "Upper N (default 18)\n"))
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ struct gsm_power_ctrl_meas_params *meas_params;
+ int lower_cmp_p = atoi(argv[1]);
+ int lower_cmp_n = atoi(argv[2]);
+ int upper_cmp_p = atoi(argv[3]);
+ int upper_cmp_n = atoi(argv[4]);
+
+ if (lower_cmp_p > lower_cmp_n) {
+ vty_out(vty, "%% Lower C/I P %d must be less than N %d%s",
+ lower_cmp_p, lower_cmp_n, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (upper_cmp_p > upper_cmp_n) {
+ vty_out(vty, "%% Upper C/I P %d must be less than N %d%s",
+ upper_cmp_p, upper_cmp_n, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ meas_params = ci_thresh_by_conn_type(params, argv[0]);
+
+ meas_params->lower_cmp_p = lower_cmp_p;
+ meas_params->lower_cmp_n = lower_cmp_n;
+ meas_params->upper_cmp_p = upper_cmp_p;
+ meas_params->upper_cmp_n = upper_cmp_n;
+
+ return CMD_SUCCESS;
+}
+
+#define POWER_CONTROL_MEAS_AVG_CMD \
+ "(rxlev-avg|rxqual-avg)"
+#define POWER_CONTROL_MEAS_AVG_DESC \
+ "RxLev (signal strength) measurement averaging (for dynamic mode)\n" \
+ "RxQual (signal quality) measurement averaging (for dynamic mode)\n"
+
+#define POWER_CONTROL_MEAS_AVG_PARAMS(params) \
+ (strncmp(argv[0], "rxlev", 5) == 0) ? \
+ &params->rxlev_meas : &params->rxqual_meas
+
+DEFUN_USRATTR(cfg_power_ctrl_no_avg,
+ cfg_power_ctrl_no_avg_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "no " POWER_CONTROL_MEAS_AVG_CMD,
+ NO_STR POWER_CONTROL_MEAS_AVG_DESC)
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ struct gsm_power_ctrl_meas_params *avg_params;
+
+ avg_params = POWER_CONTROL_MEAS_AVG_PARAMS(params);
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_avg_params,
+ cfg_power_ctrl_avg_params_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ POWER_CONTROL_MEAS_AVG_CMD " params hreqave <1-31> hreqt <1-31>",
+ POWER_CONTROL_MEAS_AVG_DESC "Configure general averaging parameters\n"
+ "Hreqave: the period over which an average is produced\n"
+ "Hreqave value (so that Hreqave * Hreqt < 32)\n"
+ "Hreqt: the number of averaged results that are maintained\n"
+ "Hreqt value (so that Hreqave * Hreqt < 32)\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ struct gsm_power_ctrl_meas_params *avg_params;
+ int h_reqave = atoi(argv[1]);
+ int h_reqt = atoi(argv[2]);
+
+ if (h_reqave * h_reqt > 31) {
+ vty_out(vty, "%% Hreqave (%d) * Hreqt (%d) = %d must be < 32%s",
+ h_reqave, h_reqt, h_reqave * h_reqt, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ avg_params = POWER_CONTROL_MEAS_AVG_PARAMS(params);
+ avg_params->h_reqave = h_reqave;
+ avg_params->h_reqt = h_reqt;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_avg_algo,
+ cfg_power_ctrl_avg_algo_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ /* FIXME: add algorithm specific parameters */
+ POWER_CONTROL_MEAS_AVG_CMD " algo (unweighted|weighted|mod-median)",
+ POWER_CONTROL_MEAS_AVG_DESC "Select the averaging algorithm\n"
+ "Un-weighted average\n" "Weighted average\n"
+ "Modified median calculation\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ struct gsm_power_ctrl_meas_params *avg_params;
+
+ avg_params = POWER_CONTROL_MEAS_AVG_PARAMS(params);
+ if (strcmp(argv[1], "unweighted") == 0)
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED;
+ else if (strcmp(argv[1], "weighted") == 0)
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED;
+ else if (strcmp(argv[1], "mod-median") == 0)
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_avg_osmo_ewma,
+ cfg_power_ctrl_avg_osmo_ewma_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ POWER_CONTROL_MEAS_AVG_CMD " algo osmo-ewma beta <1-99>",
+ POWER_CONTROL_MEAS_AVG_DESC "Select the averaging algorithm\n"
+ "Exponentially Weighted Moving Average (EWMA)\n"
+ "Smoothing factor (in %): beta = (100 - alpha)\n"
+ "1% - lowest smoothing, 99% - highest smoothing\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ struct gsm_power_ctrl_meas_params *avg_params;
+ const struct gsm_bts *bts;
+
+ if (params->dir == GSM_PWR_CTRL_DIR_UL)
+ bts = container_of(params, struct gsm_bts, ms_power_ctrl);
+ else
+ bts = container_of(params, struct gsm_bts, bs_power_ctrl);
+
+ if (bts->type != GSM_BTS_TYPE_OSMOBTS) {
+ vty_out(vty, "%% EWMA is an OsmoBTS specific algorithm, "
+ "it's not usable for other BTS types%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ avg_params = POWER_CONTROL_MEAS_AVG_PARAMS(params);
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA;
+ avg_params->ewma.alpha = 100 - atoi(argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+/* C/I related power control measurements */
+#define POWER_CONTROL_CI_MEAS_AVG_DESC \
+ "C/I (Carrier-to-Interference) measurement averaging (for dynamic mode)\n"
+
+DEFUN_USRATTR(cfg_power_ctrl_no_ci_avg,
+ cfg_power_ctrl_no_ci_avg_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "no ci-avg " VTY_CMD_CI_TYPE,
+ NO_STR POWER_CONTROL_CI_MEAS_AVG_DESC VTY_DESC_CI_TYPE)
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ struct gsm_power_ctrl_meas_params *avg_params;
+
+ avg_params = ci_thresh_by_conn_type(params, argv[0]);
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_ci_avg_params,
+ cfg_power_ctrl_ci_avg_params_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "ci-avg " VTY_CMD_CI_TYPE " params hreqave <1-31> hreqt <1-31>",
+ POWER_CONTROL_CI_MEAS_AVG_DESC VTY_DESC_CI_TYPE
+ "Configure general averaging parameters\n"
+ "Hreqave: the period over which an average is produced\n"
+ "Hreqave value (so that Hreqave * Hreqt < 32)\n"
+ "Hreqt: the number of averaged results that are maintained\n"
+ "Hreqt value (so that Hreqave * Hreqt < 32)\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ struct gsm_power_ctrl_meas_params *avg_params;
+ int h_reqave = atoi(argv[1]);
+ int h_reqt = atoi(argv[2]);
+
+ if (h_reqave * h_reqt > 31) {
+ vty_out(vty, "%% Hreqave (%d) * Hreqt (%d) = %d must be < 32%s",
+ h_reqave, h_reqt, h_reqave * h_reqt, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ avg_params = ci_thresh_by_conn_type(params, argv[0]);
+ avg_params->h_reqave = h_reqave;
+ avg_params->h_reqt = h_reqt;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_ci_avg_algo,
+ cfg_power_ctrl_ci_avg_algo_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ /* FIXME: add algorithm specific parameters */
+ "ci-avg " VTY_CMD_CI_TYPE " algo (unweighted|weighted|mod-median)",
+ POWER_CONTROL_CI_MEAS_AVG_DESC VTY_DESC_CI_TYPE
+ "Select the averaging algorithm\n"
+ "Un-weighted average\n" "Weighted average\n"
+ "Modified median calculation\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ struct gsm_power_ctrl_meas_params *avg_params;
+
+ avg_params = ci_thresh_by_conn_type(params, argv[0]);
+ if (strcmp(argv[1], "unweighted") == 0)
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED;
+ else if (strcmp(argv[1], "weighted") == 0)
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED;
+ else if (strcmp(argv[1], "mod-median") == 0)
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_power_ctrl_ci_avg_osmo_ewma,
+ cfg_power_ctrl_ci_avg_osmo_ewma_cmd,
+ X(BSC_VTY_ATTR_VENDOR_SPECIFIC) |
+ X(BSC_VTY_ATTR_NEW_LCHAN),
+ "ci-avg " VTY_CMD_CI_TYPE " algo osmo-ewma beta <1-99>",
+ POWER_CONTROL_CI_MEAS_AVG_DESC VTY_DESC_CI_TYPE
+ "Select the averaging algorithm\n"
+ "Exponentially Weighted Moving Average (EWMA)\n"
+ "Smoothing factor (in %): beta = (100 - alpha)\n"
+ "1% - lowest smoothing, 99% - highest smoothing\n")
+{
+ struct gsm_power_ctrl_params *params = vty->index;
+ struct gsm_power_ctrl_meas_params *avg_params;
+ const struct gsm_bts *bts;
+
+ if (params->dir == GSM_PWR_CTRL_DIR_UL)
+ bts = container_of(params, struct gsm_bts, ms_power_ctrl);
+ else
+ bts = container_of(params, struct gsm_bts, bs_power_ctrl);
+
+ if (bts->type != GSM_BTS_TYPE_OSMOBTS) {
+ vty_out(vty, "%% EWMA is an OsmoBTS specific algorithm, "
+ "it's not usable for other BTS types%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ avg_params = ci_thresh_by_conn_type(params, argv[0]);
+ avg_params->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA;
+ avg_params->ewma.alpha = 100 - atoi(argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv)
+{
+ int count = 0;
+ int i;
+ for (i = 0; i < 1024; i++) {
+ if (!bitvec_get_bit_pos(bv, i))
+ continue;
+ vty_out(vty, " %u", i);
+ count ++;
+ }
+ if (!count)
+ vty_out(vty, " (none)");
+ else
+ vty_out(vty, " (%d)", count);
+}
+
+static void bts_dump_vty_cbch(struct vty *vty, const struct bts_smscb_chan_state *cstate)
+{
+ vty_out(vty, " CBCH %s: %u messages, %u pages, %zu-entry sched_arr, %u%% load%s",
+ bts_smscb_chan_state_name(cstate), llist_count(&cstate->messages),
+ bts_smscb_chan_page_count(cstate), cstate->sched_arr_size,
+ bts_smscb_chan_load_percent(cstate), VTY_NEWLINE);
+}
+
+
+static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts)
+{
+ unsigned int i;
+ bool no_features = true;
+ vty_out(vty, " Features:%s", VTY_NEWLINE);
+
+ for (i = 0; i < _NUM_BTS_FEAT; i++) {
+ if (osmo_bts_has_feature(&bts->features, i)) {
+ vty_out(vty, " %03u ", i);
+ vty_out(vty, "%-40s%s", osmo_bts_features_desc(i), VTY_NEWLINE);
+ no_features = false;
+ }
+ }
+
+ if (no_features)
+ vty_out(vty, " (not available)%s", VTY_NEWLINE);
+}
+
+void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+ struct pchan_load pl;
+ struct gsm_bts_trx *trx;
+ int ts_hopping_total;
+ int ts_non_hopping_total;
+ const struct rate_ctr *activations_tch;
+ const struct rate_ctr *activations_sdcch;
+
+ vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, "
+ "BSIC %u (NCC=%u, BCC=%u) and %u TRX%s",
+ bts->nr, btstype2str(bts->type), gsm_band_name(bts->band),
+ bts->cell_identity,
+ bts->location_area_code, bts->bsic,
+ bts->bsic >> 3, bts->bsic & 7,
+ bts->num_trx, VTY_NEWLINE);
+ vty_out(vty, " Description: %s%s",
+ bts->description ? bts->description : "(null)", VTY_NEWLINE);
+
+ vty_out(vty, " ARFCNs:");
+ ts_hopping_total = 0;
+ ts_non_hopping_total = 0;
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ int ts_nr;
+ int ts_hopping = 0;
+ int ts_non_hopping = 0;
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ if (ts->hopping.enabled)
+ ts_hopping++;
+ else
+ ts_non_hopping++;
+ }
+
+ if (ts_non_hopping)
+ vty_out(vty, " %u", trx->arfcn);
+ ts_hopping_total += ts_hopping;
+ ts_non_hopping_total += ts_non_hopping;
+ }
+ if (ts_hopping_total) {
+ if (ts_non_hopping_total)
+ vty_out(vty, " / Hopping on %d of %d timeslots",
+ ts_hopping_total, ts_hopping_total + ts_non_hopping_total);
+ else
+ vty_out(vty, " Hopping on all %d timeslots", ts_hopping_total);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH))
+ vty_out(vty, " PCU version %s connected%s", bts->pcu_version,
+ VTY_NEWLINE);
+ vty_out(vty, " BCCH carrier power reduction (maximum): %u dB%s",
+ bts->c0_max_power_red_db, VTY_NEWLINE);
+ vty_out(vty, " MS Max power: %u dBm%s", bts->ms_max_power, VTY_NEWLINE);
+ vty_out(vty, " Minimum Rx Level for Access: %i dBm%s",
+ rxlev2dbm(bts->si_common.cell_sel_par.rxlev_acc_min),
+ VTY_NEWLINE);
+ vty_out(vty, " Cell Reselection Hysteresis: %u dBm%s",
+ bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
+ vty_out(vty, " Access Control Class rotation allow mask: 0x%" PRIx16 "%s",
+ bts->acc_mgr.allowed_subset_mask, VTY_NEWLINE);
+ vty_out(vty, " Access Control Class ramping: %senabled%s",
+ acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "not ", VTY_NEWLINE);
+ if (acc_ramp_is_enabled(&bts->acc_ramp)) {
+ vty_out(vty, " Access Control Class ramping step interval: %u seconds%s",
+ acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE);
+ vty_out(vty, " Access Control Class channel load thresholds: (%" PRIu8 ", %" PRIu8 ")%s",
+ bts->acc_ramp.chan_load_lower_threshold,
+ bts->acc_ramp.chan_load_upper_threshold, VTY_NEWLINE);
+ vty_out(vty, " enabling %u Access Control Class%s per ramping step%s",
+ acc_ramp_get_step_size(&bts->acc_ramp),
+ acc_ramp_get_step_size(&bts->acc_ramp) > 1 ? "es" : "", VTY_NEWLINE);
+ }
+ vty_out(vty, " RACH TX-Integer: %u%s", bts->si_common.rach_control.tx_integer,
+ VTY_NEWLINE);
+ vty_out(vty, " RACH Max transmissions: %u%s",
+ rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
+ VTY_NEWLINE);
+ vty_out(vty, " RACH Max Delay (Max Access Delay IE in CHANnel ReQuireD): %u%s",
+ bts->rach_max_delay, VTY_NEWLINE);
+ if (bts->si_common.rach_control.cell_bar)
+ vty_out(vty, " CELL IS BARRED%s", VTY_NEWLINE);
+ if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED)
+ vty_out(vty, " Uplink DTX: %s%s",
+ (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ?
+ "enabled" : "forced", VTY_NEWLINE);
+ else
+ vty_out(vty, " Uplink DTX: not enabled%s", VTY_NEWLINE);
+ vty_out(vty, " Downlink DTX: %senabled%s", bts->dtxd ? "" : "not ",
+ VTY_NEWLINE);
+ vty_out(vty, " Channel Description Attachment: %s%s",
+ (bts->si_common.chan_desc.att) ? "yes" : "no", VTY_NEWLINE);
+ vty_out(vty, " Channel Description BS-PA-MFRMS: %u%s",
+ bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE);
+ vty_out(vty, " Channel Description BS-AG_BLKS-RES: %u%s",
+ bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE);
+ vty_out(vty, " System Information present: 0x%08x, static: 0x%08x%s",
+ bts->si_valid, bts->si_mode_static, VTY_NEWLINE);
+ vty_out(vty, " Early Classmark Sending: 2G %s, 3G %s%s%s",
+ bts->early_classmark_allowed ? "allowed" : "forbidden",
+ bts->early_classmark_allowed_3g ? "allowed" : "forbidden",
+ bts->early_classmark_allowed_3g && !bts->early_classmark_allowed ?
+ " (forbidden by 2G bit)" : "",
+ VTY_NEWLINE);
+ if (is_ipa_abisip_bts(bts))
+ vty_out(vty, " Unit ID: %u/%u/0, OML Stream ID 0x%02x%s",
+ bts->ip_access.site_id, bts->ip_access.bts_id,
+ bts->oml_tei, VTY_NEWLINE);
+ else if (bts->type == GSM_BTS_TYPE_NOKIA_SITE)
+ vty_out(vty, " Skip Reset: %d%s",
+ bts->nokia.skip_reset, VTY_NEWLINE);
+ vty_out(vty, " NM State: ");
+ net_dump_nmstate(vty, &bts->mo.nm_state);
+ vty_out(vty, " Site Mgr NM State: ");
+ net_dump_nmstate(vty, &bts->site_mgr->mo.nm_state);
+
+ if (bts->gprs.mode != BTS_GPRS_NONE) {
+ vty_out(vty, " GPRS NSE: ");
+ net_dump_nmstate(vty, &bts->site_mgr->gprs.nse.mo.nm_state);
+ vty_out(vty, " GPRS CELL: ");
+ net_dump_nmstate(vty, &bts->gprs.cell.mo.nm_state);
+ vty_out(vty, " GPRS NSVC0: ");
+ net_dump_nmstate(vty, &bts->site_mgr->gprs.nsvc[0].mo.nm_state);
+ vty_out(vty, " GPRS NSVC1: ");
+ net_dump_nmstate(vty, &bts->site_mgr->gprs.nsvc[1].mo.nm_state);
+ } else
+ vty_out(vty, " GPRS: not configured%s", VTY_NEWLINE);
+
+ vty_out(vty, " Paging: %u pending requests, %u free slots%s",
+ paging_pending_requests_nr(bts),
+ bts->paging.available_slots, VTY_NEWLINE);
+ if (is_ipa_abisip_bts(bts)) {
+ vty_out(vty, " OML Link: ");
+ e1isl_dump_vty_tcp(vty, bts->oml_link);
+ bts_dump_vty_oml_link_state(vty, bts);
+ } else {
+ vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE);
+ e1isl_dump_vty(vty, bts->oml_link);
+ }
+
+ vty_out(vty, " Neighbor Cells: ");
+ switch (bts->neigh_list_manual_mode) {
+ default:
+ case NL_MODE_AUTOMATIC:
+ vty_out(vty, "Automatic");
+ /* generate_bcch_chan_list() should populate si_common.neigh_list */
+ break;
+ case NL_MODE_MANUAL:
+ vty_out(vty, "Manual");
+ break;
+ case NL_MODE_MANUAL_SI5SEP:
+ vty_out(vty, "Manual/separate SI5");
+ break;
+ }
+ vty_out(vty, ", ARFCNs:");
+ vty_out_neigh_list(vty, &bts->si_common.neigh_list);
+ if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) {
+ vty_out(vty, " SI5:");
+ vty_out_neigh_list(vty, &bts->si_common.si5_neigh_list);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ /* FIXME: chan_desc */
+ memset(&pl, 0, sizeof(pl));
+ bts_chan_load(&pl, bts);
+ vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE);
+ dump_pchan_load_vty(vty, " ", &pl);
+
+ bts_dump_vty_cbch(vty, &bts->cbch_basic);
+ bts_dump_vty_cbch(vty, &bts->cbch_extended);
+
+ vty_out(vty, " Channel Requests : %"PRIu64" total, %"PRIu64" no channel%s",
+ rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_TOTAL)->current,
+ rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHREQ_NO_CHANNEL)->current,
+ VTY_NEWLINE);
+
+ vty_out(vty, " Channel Activations :%s", VTY_NEWLINE);
+ activations_tch = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_TCH);
+ vty_out(vty, " TCH %"PRIu64"", activations_tch->current);
+ if (activations_tch->intv[RATE_CTR_INTV_HOUR].rate > 0) {
+ const struct rate_ctr *active_time_tch_ms = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_TCH_ACTIVE_MILLISECONDS_TOTAL);
+ vty_out(vty, " (avg lifespan %s seconds in last hour)",
+ osmo_int_to_float_str_c(OTC_SELECT,
+ active_time_tch_ms->intv[RATE_CTR_INTV_HOUR].rate
+ / activations_tch->intv[RATE_CTR_INTV_HOUR].rate, 3));
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+ activations_sdcch = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_ACT_SDCCH);
+ vty_out(vty, " SDCCH %"PRIu64"", activations_sdcch->current);
+ if (activations_sdcch->intv[RATE_CTR_INTV_HOUR].rate > 0) {
+ const struct rate_ctr *active_time_sdcch_ms = rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_SDCCH_ACTIVE_MILLISECONDS_TOTAL);
+ vty_out(vty, " (avg lifespan %s seconds in last hour)",
+ osmo_int_to_float_str_c(OTC_SELECT,
+ active_time_sdcch_ms->intv[RATE_CTR_INTV_HOUR].rate
+ / activations_sdcch->intv[RATE_CTR_INTV_HOUR].rate, 3));
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ vty_out(vty, " Channel Failures : %"PRIu64" rf_failures, %"PRIu64" rll failures%s",
+ rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_RF_FAIL)->current,
+ rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_CHAN_RLL_ERR)->current,
+ VTY_NEWLINE);
+ vty_out(vty, " BTS failures : %"PRIu64" OML, %"PRIu64" RSL%s",
+ rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_BTS_OML_FAIL)->current,
+ rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_BTS_RSL_FAIL)->current,
+ VTY_NEWLINE);
+
+ vty_out_stat_item_group(vty, " ", bts->bts_statg);
+
+ bts_dump_vty_features(vty, bts);
+}
+
+void bts_dump_vty_oml_link_state(struct vty *vty, struct gsm_bts *bts)
+{
+ unsigned long long sec;
+
+ vty_out(vty, " OML Link state: %s", get_model_oml_status(bts));
+ if (bts_setup_ramp_active(bts->network))
+ vty_out(vty, " BTS Ramping: %s", bts_setup_ramp_get_state_str(bts));
+ sec = bts_updowntime(bts);
+ if (sec)
+ vty_out(vty, " %llu days %llu hours %llu min. %llu sec.",
+ OSMO_SEC2DAY(sec), OSMO_SEC2HRS(sec), OSMO_SEC2MIN(sec), sec % 60);
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts)
+{
+ unsigned int i;
+ struct gsm_bts_sm *bts_sm = bts->site_mgr;
+ vty_out(vty, " gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode),
+ VTY_NEWLINE);
+ if (bts->gprs.mode == BTS_GPRS_NONE)
+ return;
+
+ vty_out(vty, " gprs routing area %u%s", bts->gprs.rac,
+ VTY_NEWLINE);
+ vty_out(vty, " gprs network-control-order nc%u%s",
+ bts->gprs.net_ctrl_ord, VTY_NEWLINE);
+ if (!bts->gprs.ctrl_ack_type_use_block)
+ vty_out(vty, " gprs control-ack-type-rach%s", VTY_NEWLINE);
+ if (bts->gprs.ccn.forced_vty)
+ vty_out(vty, " gprs ccn-active %d%s",
+ bts->gprs.ccn.active ? 1 : 0, VTY_NEWLINE);
+ vty_out(vty, " gprs power-control alpha %u%s",
+ bts->gprs.pwr_ctrl.alpha, VTY_NEWLINE);
+ vty_out(vty, " gprs cell bvci %u%s", bts->gprs.cell.bvci,
+ VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++)
+ vty_out(vty, " gprs cell timer %s %u%s",
+ get_value_string(gprs_bssgp_cfg_strs, i),
+ bts->gprs.cell.timer[i], VTY_NEWLINE);
+ vty_out(vty, " gprs nsei %u%s", bts_sm->gprs.nse.nsei,
+ VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nse.timer); i++)
+ vty_out(vty, " gprs ns timer %s %u%s",
+ get_value_string(gprs_ns_timer_strs, i),
+ bts_sm->gprs.nse.timer[i], VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(bts_sm->gprs.nsvc); i++) {
+ const struct gsm_gprs_nsvc *nsvc = &bts_sm->gprs.nsvc[i];
+ struct osmo_sockaddr_str remote;
+
+ if (!nsvc->enabled) {
+ vty_out(vty, " no gprs nsvc %u%s", i, VTY_NEWLINE);
+ continue;
+ }
+
+ vty_out(vty, " gprs nsvc %u nsvci %u%s", i,
+ nsvc->nsvci, VTY_NEWLINE);
+
+ vty_out(vty, " gprs nsvc %u local udp port %u%s", i,
+ nsvc->local_port, VTY_NEWLINE);
+
+ /* Most likely, the remote address is not configured (AF_UNSPEC).
+ * Printing the port alone makes no sense, so let's just skip both. */
+ if (osmo_sockaddr_str_from_sockaddr(&remote, &nsvc->remote.u.sas) != 0)
+ continue;
+
+ vty_out(vty, " gprs nsvc %u remote ip %s%s",
+ i, remote.ip, VTY_NEWLINE);
+ vty_out(vty, " gprs nsvc %u remote udp port %u%s",
+ i, remote.port, VTY_NEWLINE);
+ }
+
+ /* EGPRS specific parameters */
+ if (bts->gprs.mode == BTS_GPRS_EGPRS) {
+ if (bts->gprs.egprs_pkt_chan_request)
+ vty_out(vty, " gprs egprs-packet-channel-request%s", VTY_NEWLINE);
+ }
+}
+
+/* Write the model data if there is one */
+static void config_write_bts_model(struct vty *vty, struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ if (!bts->model)
+ return;
+
+ if (bts->model->config_write_bts)
+ bts->model->config_write_bts(vty, bts);
+
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ config_write_trx_single(vty, trx);
+}
+
+static void write_amr_modes(struct vty *vty, const char *prefix,
+ const char *name, struct amr_mode *modes, int num)
+{
+ int i;
+
+ vty_out(vty, " %s threshold %s", prefix, name);
+ for (i = 0; i < num - 1; i++)
+ vty_out(vty, " %d", modes[i].threshold);
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, " %s hysteresis %s", prefix, name);
+ for (i = 0; i < num - 1; i++)
+ vty_out(vty, " %d", modes[i].hysteresis);
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+static void config_write_bts_amr(struct vty *vty, struct gsm_bts *bts,
+ struct amr_multirate_conf *mr, int full)
+{
+ struct gsm48_multi_rate_conf *mr_conf;
+ const char *prefix = (full) ? "amr tch-f" : "amr tch-h";
+ int i, num;
+
+ if (!(mr->gsm48_ie[1]))
+ return;
+
+ mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+
+ num = 0;
+ vty_out(vty, " %s modes", prefix);
+ for (i = 0; i < ((full) ? 8 : 6); i++) {
+ if ((mr->gsm48_ie[1] & (1 << i))) {
+ vty_out(vty, " %d", i);
+ num++;
+ }
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+ if (num > 4)
+ num = 4;
+ if (num > 1) {
+ write_amr_modes(vty, prefix, "ms", mr->ms_mode, num);
+ write_amr_modes(vty, prefix, "bts", mr->bts_mode, num);
+ }
+ vty_out(vty, " %s start-mode ", prefix);
+ if (mr_conf->icmi) {
+ num = 0;
+ for (i = 0; i < ((full) ? 8 : 6) && num < 4; i++) {
+ if ((mr->gsm48_ie[1] & (1 << i)))
+ num++;
+ if (mr_conf->smod == num - 1) {
+ vty_out(vty, "%d%s", num, VTY_NEWLINE);
+ break;
+ }
+ }
+ } else
+ vty_out(vty, "auto%s", VTY_NEWLINE);
+}
+
+/* TODO: generalize and move indentation handling to libosmocore */
+#define cfg_out(fmt, args...) \
+ vty_out(vty, "%*s" fmt, indent, "", ##args);
+
+static void config_write_power_ctrl_meas(struct vty *vty, unsigned int indent,
+ const struct gsm_power_ctrl_meas_params *mp,
+ const char *param, const char *param2)
+{
+ if (strcmp(param, "ci") == 0) {
+ cfg_out("%s-thresh%s %s%s",
+ param, param2, mp->enabled ? "enable" : "disable",
+ VTY_NEWLINE);
+ }
+
+ cfg_out("%s-thresh%s lower %u upper %u%s",
+ param, param2, mp->lower_thresh, mp->upper_thresh,
+ VTY_NEWLINE);
+ cfg_out("%s-thresh-comp%s lower %u %u upper %u %u%s",
+ param, param2, mp->lower_cmp_p, mp->lower_cmp_n,
+ mp->upper_cmp_p, mp->upper_cmp_n,
+ VTY_NEWLINE);
+
+ switch (mp->algo) {
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE:
+ /* Do not print any averaging parameters */
+ return; /* we're done */
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED:
+ cfg_out("%s-avg%s algo unweighted%s", param, param2, VTY_NEWLINE);
+ break;
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED:
+ cfg_out("%s-avg%s algo weighted%s", param, param2, VTY_NEWLINE);
+ break;
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN:
+ cfg_out("%s-avg%s algo mod-median%s", param, param2, VTY_NEWLINE);
+ break;
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA:
+ cfg_out("%s-avg%s algo osmo-ewma beta %u%s",
+ param, param2, 100 - mp->ewma.alpha,
+ VTY_NEWLINE);
+ break;
+ }
+
+ cfg_out("%s-avg%s params hreqave %u hreqt %u%s",
+ param, param2, mp->h_reqave, mp->h_reqt,
+ VTY_NEWLINE);
+}
+
+static void config_write_power_ctrl(struct vty *vty, unsigned int indent,
+ const struct gsm_bts *bts,
+ const struct gsm_power_ctrl_params *cp)
+{
+ const char *node_name;
+
+ if (cp->dir == GSM_PWR_CTRL_DIR_UL)
+ node_name = "ms-power-control";
+ else
+ node_name = "bs-power-control";
+
+ switch (cp->mode) {
+ case GSM_PWR_CTRL_MODE_NONE:
+ cfg_out("no %s%s", node_name, VTY_NEWLINE);
+ break;
+ case GSM_PWR_CTRL_MODE_STATIC:
+ cfg_out("%s%s", node_name, VTY_NEWLINE);
+ cfg_out(" mode static%s", VTY_NEWLINE);
+ if (cp->dir == GSM_PWR_CTRL_DIR_DL && cp->bs_power_val_db != 0)
+ cfg_out(" bs-power static %u%s", cp->bs_power_val_db, VTY_NEWLINE);
+ break;
+ case GSM_PWR_CTRL_MODE_DYN_BTS:
+ case GSM_PWR_CTRL_MODE_DYN_BSC:
+ cfg_out("%s%s", node_name, VTY_NEWLINE);
+ cfg_out(" mode %s%s",
+ cp->mode == GSM_PWR_CTRL_MODE_DYN_BTS ? "dyn-bts" : "dyn-bsc", VTY_NEWLINE);
+ if (cp->dir == GSM_PWR_CTRL_DIR_DL)
+ cfg_out(" bs-power dyn-max %u%s", cp->bs_power_max_db, VTY_NEWLINE);
+
+ cfg_out(" ctrl-interval %u%s", cp->ctrl_interval, VTY_NEWLINE);
+ cfg_out(" step-size inc %u red %u%s",
+ cp->inc_step_size_db, cp->red_step_size_db,
+ VTY_NEWLINE);
+
+ /* Measurement processing / averaging parameters */
+ config_write_power_ctrl_meas(vty, indent + 1, &cp->rxlev_meas, "rxlev", "");
+ config_write_power_ctrl_meas(vty, indent + 1, &cp->rxqual_meas, "rxqual", "");
+ if (cp->dir == GSM_PWR_CTRL_DIR_UL && is_osmobts(bts)
+ && cp->mode == GSM_PWR_CTRL_MODE_DYN_BTS) {
+ config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_fr_meas, "ci", " fr-efr");
+ config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_hr_meas, "ci", " hr");
+ config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_amr_fr_meas, "ci", " amr-fr");
+ config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_amr_hr_meas, "ci", " amr-hr");
+ config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_sdcch_meas, "ci", " sdcch");
+ config_write_power_ctrl_meas(vty, indent + 1, &cp->ci_gprs_meas, "ci", " gprs");
+ }
+ break;
+ }
+}
+
+#undef cfg_out
+
+static void config_write_bts_ncc_permitted(struct vty *vty, const char *prefix, const struct gsm_bts *bts)
+{
+ int i;
+ uint8_t ncc_permitted = bts->si_common.ncc_permitted;
+
+ if (ncc_permitted == 0xff)
+ return;
+
+ vty_out(vty, "%sncc-permitted", prefix);
+
+ for (i = 0; i < 8; i++) {
+ if ((ncc_permitted & (1 << i)))
+ vty_out(vty, " %d", i + 1);
+ }
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
+{
+ int i;
+ uint8_t tmp;
+
+ vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE);
+ vty_out(vty, " type %s%s", btstype2str(bts->type), VTY_NEWLINE);
+ if (bts->description)
+ vty_out(vty, " description %s%s", bts->description, VTY_NEWLINE);
+ vty_out(vty, " band %s%s", gsm_band_name(bts->band), VTY_NEWLINE);
+ vty_out(vty, " cell_identity %u%s", bts->cell_identity, VTY_NEWLINE);
+ vty_out(vty, " location_area_code 0x%04x%s", bts->location_area_code,
+ VTY_NEWLINE);
+ if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED)
+ vty_out(vty, " dtx uplink%s%s",
+ (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ? "" : " force",
+ VTY_NEWLINE);
+ if (bts->dtxd)
+ vty_out(vty, " dtx downlink%s", VTY_NEWLINE);
+ vty_out(vty, " base_station_id_code %u%s", bts->bsic, VTY_NEWLINE);
+ vty_out(vty, " ms max power %u%s", bts->ms_max_power, VTY_NEWLINE);
+ vty_out(vty, " cell reselection hysteresis %u%s",
+ bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
+ vty_out(vty, " rxlev access min %u%s",
+ bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE);
+
+ if (bts->si_common.cell_ro_sel_par.present) {
+ struct osmo_gsm48_si_selection_params *sp;
+ sp = &bts->si_common.cell_ro_sel_par;
+
+ if (sp->cbq)
+ vty_out(vty, " cell bar qualify %u%s",
+ sp->cbq, VTY_NEWLINE);
+
+ if (sp->cell_resel_off)
+ vty_out(vty, " cell reselection offset %u%s",
+ sp->cell_resel_off*2, VTY_NEWLINE);
+
+ if (sp->temp_offs == 7)
+ vty_out(vty, " temporary offset infinite%s",
+ VTY_NEWLINE);
+ else if (sp->temp_offs)
+ vty_out(vty, " temporary offset %u%s",
+ sp->temp_offs*10, VTY_NEWLINE);
+
+ if (sp->penalty_time == 31)
+ vty_out(vty, " penalty time reserved%s",
+ VTY_NEWLINE);
+ else if (sp->penalty_time)
+ vty_out(vty, " penalty time %u%s",
+ (sp->penalty_time*20)+20, VTY_NEWLINE);
+ }
+
+ if (gsm_bts_get_radio_link_timeout(bts) < 0)
+ vty_out(vty, " radio-link-timeout infinite%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " radio-link-timeout %d%s",
+ gsm_bts_get_radio_link_timeout(bts), VTY_NEWLINE);
+
+ vty_out(vty, " channel allocator mode chan-req %s%s",
+ bts->chan_alloc_chan_req_reverse ? "descending" : "ascending",
+ VTY_NEWLINE);
+ if (bts->chan_alloc_assignment_dynamic) {
+ vty_out(vty, " channel allocator mode assignment dynamic%s",
+ VTY_NEWLINE);
+ vty_out(vty, " channel allocator dynamic-param sort-by-trx-power %c%s",
+ bts->chan_alloc_dyn_params.sort_by_trx_power ? '1' : '0',
+ VTY_NEWLINE);
+ vty_out(vty, " channel allocator dynamic-param ul-rxlev thresh %u avg-num %u%s",
+ bts->chan_alloc_dyn_params.ul_rxlev_thresh,
+ bts->chan_alloc_dyn_params.ul_rxlev_avg_num,
+ VTY_NEWLINE);
+ vty_out(vty, " channel allocator dynamic-param c0-chan-load thresh %u%s",
+ bts->chan_alloc_dyn_params.c0_chan_load_thresh,
+ VTY_NEWLINE);
+ } else {
+ vty_out(vty, " channel allocator mode assignment %s%s",
+ bts->chan_alloc_assignment_reverse ? "descending" : "ascending",
+ VTY_NEWLINE);
+ }
+ vty_out(vty, " channel allocator mode handover %s%s",
+ bts->chan_alloc_handover_reverse ? "descending" : "ascending",
+ VTY_NEWLINE);
+ vty_out(vty, " channel allocator mode vgcs-vbs %s%s",
+ bts->chan_alloc_vgcs_reverse ? "descending" : "ascending",
+ VTY_NEWLINE);
+ if (bts->chan_alloc_avoid_interf)
+ vty_out(vty, " channel allocator avoid-interference 1%s", VTY_NEWLINE);
+ if (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_NEVER)
+ vty_out(vty, " channel allocator tch-signalling-policy never%s", VTY_NEWLINE);
+ else if (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_EMERG)
+ vty_out(vty, " channel allocator tch-signalling-policy emergency%s", VTY_NEWLINE);
+ else if (bts->chan_alloc_tch_signalling_policy == BTS_TCH_SIGNALLING_VOICE)
+ vty_out(vty, " channel allocator tch-signalling-policy voice%s", VTY_NEWLINE);
+ vty_out(vty, " rach tx integer %u%s",
+ bts->si_common.rach_control.tx_integer, VTY_NEWLINE);
+ vty_out(vty, " rach max transmission %u%s",
+ rach_max_trans_raw2val(bts->si_common.rach_control.max_trans),
+ VTY_NEWLINE);
+ vty_out(vty, " rach max-delay %u%s", bts->rach_max_delay, VTY_NEWLINE);
+ vty_out(vty, " rach expiry-timeout %u%s", bts->rach_expiry_timeout, VTY_NEWLINE);
+
+ vty_out(vty, " channel-description attach %u%s",
+ bts->si_common.chan_desc.att, VTY_NEWLINE);
+ vty_out(vty, " channel-description bs-pa-mfrms %u%s",
+ bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE);
+ vty_out(vty, " channel-description bs-ag-blks-res %u%s",
+ bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE);
+ if (bts->nch.num_blocks) {
+ vty_out(vty, " nch-position num-blocks %u first-block %u%s",
+ bts->nch.num_blocks, bts->nch.first_block, VTY_NEWLINE);
+ } else {
+ vty_out(vty, " no nch-position%s", VTY_NEWLINE);
+ }
+
+ if (bts->ccch_load_ind_thresh != 10)
+ vty_out(vty, " ccch load-indication-threshold %u%s",
+ bts->ccch_load_ind_thresh, VTY_NEWLINE);
+ if (bts->ccch_load_ind_period != 1)
+ vty_out(vty, " ccch load-indication-period %u%s",
+ bts->ccch_load_ind_period, VTY_NEWLINE);
+ if (bts->rach_b_thresh != -1)
+ vty_out(vty, " rach nm busy threshold %u%s",
+ bts->rach_b_thresh, VTY_NEWLINE);
+ if (bts->rach_ldavg_slots != -1)
+ vty_out(vty, " rach nm load average %u%s",
+ bts->rach_ldavg_slots, VTY_NEWLINE);
+ if (bts->si_common.rach_control.cell_bar)
+ vty_out(vty, " cell barred 1%s", VTY_NEWLINE);
+ if ((bts->si_common.rach_control.t2 & 0x4) == 0)
+ vty_out(vty, " rach emergency call allowed 1%s", VTY_NEWLINE);
+ if (bts->si_common.rach_control.re == 0)
+ vty_out(vty, " rach call-reestablishment allowed 1%s", VTY_NEWLINE);
+ if ((bts->si_common.rach_control.t3) != 0)
+ for (i = 0; i < 8; i++)
+ if (bts->si_common.rach_control.t3 & (0x1 << i))
+ vty_out(vty, " rach access-control-class %d barred%s", i, VTY_NEWLINE);
+ if ((bts->si_common.rach_control.t2 & 0xfb) != 0)
+ for (i = 0; i < 8; i++)
+ if ((i != 2) && (bts->si_common.rach_control.t2 & (0x1 << i)))
+ vty_out(vty, " rach access-control-class %d barred%s", i+8, VTY_NEWLINE);
+ if (bts->acc_mgr.len_allowed_adm < 10)
+ vty_out(vty, " access-control-class-rotate %" PRIu8 "%s", bts->acc_mgr.len_allowed_adm, VTY_NEWLINE);
+ if (bts->acc_mgr.rotation_time_sec != ACC_MGR_QUANTUM_DEFAULT)
+ vty_out(vty, " access-control-class-rotate-quantum %" PRIu32 "%s", bts->acc_mgr.rotation_time_sec, VTY_NEWLINE);
+ vty_out(vty, " %saccess-control-class-ramping%s", acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "no ", VTY_NEWLINE);
+ if (acc_ramp_is_enabled(&bts->acc_ramp)) {
+ vty_out(vty, " access-control-class-ramping-step-interval %u%s",
+ acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE);
+ vty_out(vty, " access-control-class-ramping-step-size %u%s", acc_ramp_get_step_size(&bts->acc_ramp),
+ VTY_NEWLINE);
+ vty_out(vty, " access-control-class-ramping-chan-load %u %u%s",
+ bts->acc_ramp.chan_load_lower_threshold, bts->acc_ramp.chan_load_upper_threshold, VTY_NEWLINE);
+ }
+ if (!bts->si_unused_send_empty)
+ vty_out(vty, " no system-information unused-send-empty%s", VTY_NEWLINE);
+ for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) {
+ if (bts->si_mode_static & (1 << i)) {
+ vty_out(vty, " system-information %s mode static%s",
+ get_value_string(osmo_sitype_strs, i), VTY_NEWLINE);
+ vty_out(vty, " system-information %s static %s%s",
+ get_value_string(osmo_sitype_strs, i),
+ osmo_hexdump_nospc(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN),
+ VTY_NEWLINE);
+ }
+ }
+ vty_out(vty, " early-classmark-sending %s%s",
+ bts->early_classmark_allowed ? "allowed" : "forbidden", VTY_NEWLINE);
+ vty_out(vty, " early-classmark-sending-3g %s%s",
+ bts->early_classmark_allowed_3g ? "allowed" : "forbidden", VTY_NEWLINE);
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ case GSM_BTS_TYPE_OSMOBTS:
+ vty_out(vty, " ipa unit-id %u %u%s",
+ bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE);
+ if (bts->ip_access.rsl_ip) {
+ struct in_addr ia;
+ ia.s_addr = htonl(bts->ip_access.rsl_ip);
+ vty_out(vty, " ipa rsl-ip %s%s", inet_ntoa(ia),
+ VTY_NEWLINE);
+ }
+ vty_out(vty, " oml ipa stream-id %u line %u%s",
+ bts->oml_tei, bts->oml_e1_link.e1_nr, VTY_NEWLINE);
+ break;
+ case GSM_BTS_TYPE_NOKIA_SITE:
+ vty_out(vty, " nokia_site skip-reset %d%s", bts->nokia.skip_reset, VTY_NEWLINE);
+ vty_out(vty, " nokia_site no-local-rel-conf %d%s",
+ bts->nokia.no_loc_rel_cnf, VTY_NEWLINE);
+ vty_out(vty, " nokia_site bts-reset-timer %d%s", bts->nokia.bts_reset_timer_cnf, VTY_NEWLINE);
+ /* fall through: Nokia requires "oml e1" parameters also */
+ default:
+ config_write_e1_link(vty, &bts->oml_e1_link, " oml ");
+ vty_out(vty, " oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE);
+ break;
+ }
+
+ /* if we have a limit, write it */
+ if (bts->paging.free_chans_need >= 0)
+ vty_out(vty, " paging free %d%s", bts->paging.free_chans_need, VTY_NEWLINE);
+ if (!bts->T3113_dynamic)
+ vty_out(vty, " no timer-dynamic T3113%s", VTY_NEWLINE);
+
+ vty_out(vty, " neighbor-list mode %s%s",
+ get_value_string(bts_neigh_mode_strs, bts->neigh_list_manual_mode), VTY_NEWLINE);
+ if (bts->neigh_list_manual_mode != NL_MODE_AUTOMATIC) {
+ for (i = 0; i < 1024; i++) {
+ if (bitvec_get_bit_pos(&bts->si_common.neigh_list, i))
+ vty_out(vty, " neighbor-list add arfcn %u%s",
+ i, VTY_NEWLINE);
+ }
+ }
+ if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) {
+ for (i = 0; i < 1024; i++) {
+ if (bitvec_get_bit_pos(&bts->si_common.si5_neigh_list, i))
+ vty_out(vty, " si5 neighbor-list add arfcn %u%s",
+ i, VTY_NEWLINE);
+ }
+ }
+
+ for (i = 0; i < MAX_EARFCN_LIST; i++) {
+ struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+ if (e->arfcn[i] != OSMO_EARFCN_INVALID) {
+ vty_out(vty, " si2quater neighbor-list add earfcn %u "
+ "thresh-hi %u", e->arfcn[i], e->thresh_hi);
+
+ vty_out(vty, " thresh-lo %u",
+ e->thresh_lo_valid ? e->thresh_lo : 32);
+
+ vty_out(vty, " prio %u",
+ e->prio_valid ? e->prio : 8);
+
+ vty_out(vty, " qrxlv %u",
+ e->qrxlm_valid ? e->qrxlm : 32);
+
+ tmp = e->meas_bw[i];
+ vty_out(vty, " meas %u",
+ (tmp != OSMO_EARFCN_MEAS_INVALID) ? tmp : 8);
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+ }
+ }
+
+ for (i = 0; i < bts->si_common.uarfcn_length; i++) {
+ vty_out(vty, " si2quater neighbor-list add uarfcn %u %u %u%s",
+ bts->si_common.data.uarfcn_list[i],
+ bts->si_common.data.scramble_list[i] & ~(1 << 9),
+ (bts->si_common.data.scramble_list[i] >> 9) & 1,
+ VTY_NEWLINE);
+ }
+
+ neighbor_ident_vty_write_bts(vty, " ", bts);
+
+ vty_out(vty, " codec-support fr");
+ if (bts->codec.hr)
+ vty_out(vty, " hr");
+ if (bts->codec.efr)
+ vty_out(vty, " efr");
+ if (bts->codec.amr)
+ vty_out(vty, " amr");
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ config_write_bts_amr(vty, bts, &bts->mr_full, 1);
+ config_write_bts_amr(vty, bts, &bts->mr_half, 0);
+
+ if (bts->use_osmux != OSMUX_USAGE_OFF) {
+ vty_out(vty, " osmux %s%s", bts->use_osmux == OSMUX_USAGE_ON ? "on" : "only",
+ VTY_NEWLINE);
+ }
+
+ if (bts->mgw_pool_target > -1) {
+ vty_out(vty, " mgw pool-target %u%s%s",
+ bts->mgw_pool_target,
+ bts->mgw_pool_target_strict ? " strict" : "",
+ VTY_NEWLINE);
+ }
+
+ config_write_bts_gprs(vty, bts);
+
+ if (bts->excl_from_rf_lock)
+ vty_out(vty, " rf-lock-exclude%s", VTY_NEWLINE);
+
+ if (bts->force_combined_si_set)
+ vty_out(vty, " %sforce-combined-si%s",
+ bts->force_combined_si ? "" : "no ", VTY_NEWLINE);
+
+ for (i = 0; i < ARRAY_SIZE(bts->depends_on); ++i) {
+ int j;
+
+ if (bts->depends_on[i] == 0)
+ continue;
+
+ for (j = 0; j < sizeof(bts->depends_on[i]) * 8; ++j) {
+ int bts_nr;
+
+ if ((bts->depends_on[i] & (1<<j)) == 0)
+ continue;
+
+ bts_nr = (i * sizeof(bts->depends_on[i]) * 8) + j;
+ vty_out(vty, " depends-on-bts %d%s", bts_nr, VTY_NEWLINE);
+ }
+ }
+
+ ho_vty_write_bts(vty, bts);
+
+ if (bts->top_acch_cap.overpower_db > 0) {
+ const struct abis_rsl_osmo_temp_ovp_acch_cap *top = \
+ &bts->top_acch_cap;
+ const char *mode = NULL;
+
+ if (top->sacch_enable && top->facch_enable)
+ mode = "dl-acch";
+ else if (top->sacch_enable)
+ mode = "dl-sacch";
+ else if (top->facch_enable)
+ mode = "dl-facch";
+ else /* shall not happen */
+ OSMO_ASSERT(0);
+
+ vty_out(vty, " overpower %s %u%s",
+ mode, top->overpower_db, VTY_NEWLINE);
+ vty_out(vty, " overpower rxqual %u%s",
+ top->rxqual, VTY_NEWLINE);
+ vty_out(vty, " overpower chan-mode %s%s",
+ get_value_string(top_acch_chan_mode_name,
+ bts->top_acch_chan_mode),
+ VTY_NEWLINE);
+ }
+
+ if (bts->rep_acch_cap.dl_facch_all)
+ vty_out(vty, " repeat dl-facch all%s", VTY_NEWLINE);
+ else if (bts->rep_acch_cap.dl_facch_cmd)
+ vty_out(vty, " repeat dl-facch command%s", VTY_NEWLINE);
+ if (bts->rep_acch_cap.dl_sacch)
+ vty_out(vty, " repeat dl-sacch%s", VTY_NEWLINE);
+ if (bts->rep_acch_cap.ul_sacch)
+ vty_out(vty, " repeat ul-sacch%s", VTY_NEWLINE);
+ if (bts->rep_acch_cap.ul_sacch
+ || bts->rep_acch_cap.dl_facch_cmd
+ || bts->rep_acch_cap.dl_facch_cmd)
+ vty_out(vty, " repeat rxqual %u%s", bts->rep_acch_cap.rxqual, VTY_NEWLINE);
+
+ if (bts->interf_meas_params_cfg.avg_period != interf_meas_params_def.avg_period) {
+ vty_out(vty, " interference-meas avg-period %u%s",
+ bts->interf_meas_params_cfg.avg_period,
+ VTY_NEWLINE);
+ }
+ if (memcmp(bts->interf_meas_params_cfg.bounds_dbm,
+ interf_meas_params_def.bounds_dbm,
+ sizeof(interf_meas_params_def.bounds_dbm))) {
+ vty_out(vty, " interference-meas level-bounds "
+ "%d %d %d %d %d %d%s",
+ -1 * bts->interf_meas_params_cfg.bounds_dbm[0],
+ -1 * bts->interf_meas_params_cfg.bounds_dbm[1],
+ -1 * bts->interf_meas_params_cfg.bounds_dbm[2],
+ -1 * bts->interf_meas_params_cfg.bounds_dbm[3],
+ -1 * bts->interf_meas_params_cfg.bounds_dbm[4],
+ -1 * bts->interf_meas_params_cfg.bounds_dbm[5],
+ VTY_NEWLINE);
+ }
+
+ if (!bts->srvcc_fast_return_allowed)
+ vty_out(vty, " srvcc fast-return forbid%s", VTY_NEWLINE);
+
+ switch (bts->imm_ass_time) {
+ default:
+ case IMM_ASS_TIME_POST_CHAN_ACK:
+ /* default value */
+ break;
+ case IMM_ASS_TIME_PRE_CHAN_ACK:
+ vty_out(vty, " immediate-assignment pre-chan-ack%s", VTY_NEWLINE);
+ break;
+ case IMM_ASS_TIME_PRE_TS_ACK:
+ vty_out(vty, " immediate-assignment pre-ts-ack%s", VTY_NEWLINE);
+ break;
+ }
+
+ /* BS/MS Power Control parameters */
+ config_write_power_ctrl(vty, 2, bts, &bts->bs_power_ctrl);
+ config_write_power_ctrl(vty, 2, bts, &bts->ms_power_ctrl);
+
+ config_write_bts_ncc_permitted(vty, " ", bts);
+
+ config_write_bts_model(vty, bts);
+}
+
+int config_write_bts(struct vty *v)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(v);
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &gsmnet->bts_list, list)
+ config_write_bts_single(v, bts);
+
+ return CMD_SUCCESS;
+}
+
+int bts_vty_init(void)
+{
+ cfg_bts_type_cmd.string =
+ vty_cmd_string_from_valstr(tall_bsc_ctx,
+ bts_type_names,
+ "type (", "|", ")",
+ VTY_DO_LOWER);
+ cfg_bts_type_cmd.doc =
+ vty_cmd_string_from_valstr(tall_bsc_ctx,
+ bts_type_descs,
+ "BTS Vendor/Type\n",
+ "\n", "", 0);
+
+ install_element(GSMNET_NODE, &cfg_bts_cmd);
+ install_node(&bts_node, config_write_bts);
+ install_element(BTS_NODE, &cfg_bts_type_cmd);
+ install_element(BTS_NODE, &cfg_bts_type_sysmobts_cmd);
+ install_element(BTS_NODE, &cfg_description_cmd);
+ install_element(BTS_NODE, &cfg_no_description_cmd);
+ install_element(BTS_NODE, &cfg_bts_band_cmd);
+ install_element(BTS_NODE, &cfg_bts_ci_cmd);
+ install_element(BTS_NODE, &cfg_bts_dtxu_cmd);
+ install_element(BTS_NODE, &cfg_bts_dtxd_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_dtxu_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_dtxd_cmd);
+ install_element(BTS_NODE, &cfg_bts_lac_cmd);
+ install_element(BTS_NODE, &cfg_bts_tsc_cmd);
+ install_element(BTS_NODE, &cfg_bts_bsic_cmd);
+ install_element(BTS_NODE, &cfg_bts_unit_id_cmd);
+ install_element(BTS_NODE, &cfg_bts_deprecated_unit_id_cmd);
+ install_element(BTS_NODE, &cfg_bts_rsl_ip_cmd);
+ install_element(BTS_NODE, &cfg_bts_deprecated_rsl_ip_cmd);
+ install_element(BTS_NODE, &cfg_bts_nokia_site_skip_reset_cmd);
+ install_element(BTS_NODE, &cfg_bts_nokia_site_no_loc_rel_cnf_cmd);
+ install_element(BTS_NODE, &cfg_bts_nokia_site_bts_reset_timer_cnf_cmd);
+ install_element(BTS_NODE, &cfg_bts_stream_id_cmd);
+ install_element(BTS_NODE, &cfg_bts_deprecated_stream_id_cmd);
+ install_element(BTS_NODE, &cfg_bts_oml_e1_cmd);
+ install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd);
+ install_element(BTS_NODE, &cfg_bts_challoc_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_challoc_mode_all_cmd);
+ install_element(BTS_NODE, &cfg_bts_challoc_mode_ass_dynamic_cmd);
+ install_element(BTS_NODE, &cfg_bts_challoc_dynamic_param_sort_by_trx_power_cmd);
+ install_element(BTS_NODE, &cfg_bts_challoc_dynamic_param_ul_rxlev_cmd);
+ install_element(BTS_NODE, &cfg_bts_challoc_dynamic_param_c0_chan_load_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_alloc_interf_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_alloc_tch_signalling_policy_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_alloc_allow_tch_for_signalling_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_max_delay_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_expiry_timeout_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_desc_att_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_dscr_att_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_desc_bs_pa_mfrms_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_dscr_bs_pa_mfrms_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_desc_bs_ag_blks_res_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_dscr_bs_ag_blks_res_cmd);
+ install_element(BTS_NODE, &cfg_bts_ccch_load_ind_thresh_cmd);
+ install_element(BTS_NODE, &cfg_bts_ccch_load_ind_period_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd);
+ install_element(BTS_NODE, &cfg_bts_cell_barred_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_re_allowed_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_ac_class_cmd);
+ install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd);
+ install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd);
+ install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd);
+ install_element(BTS_NODE, &cfg_bts_cell_bar_qualify_cmd);
+ install_element(BTS_NODE, &cfg_bts_cell_resel_ofs_cmd);
+ install_element(BTS_NODE, &cfg_bts_temp_ofs_cmd);
+ install_element(BTS_NODE, &cfg_bts_temp_ofs_inf_cmd);
+ install_element(BTS_NODE, &cfg_bts_penalty_time_cmd);
+ install_element(BTS_NODE, &cfg_bts_penalty_time_rsvd_cmd);
+ install_element(BTS_NODE, &cfg_bts_radio_link_timeout_cmd);
+ install_element(BTS_NODE, &cfg_bts_radio_link_timeout_inf_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_11bit_rach_support_for_egprs_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_gprs_egprs_pkt_chan_req_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_egprs_pkt_chan_req_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_net_ctrl_ord_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_ctrl_ack_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_ccn_active_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_pwr_ctrl_alpha_cmd);
+ install_element(BTS_NODE, &cfg_no_bts_gprs_ctrl_ack_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd);
+ install_element(BTS_NODE, &cfg_no_bts_gprs_nsvc_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd);
+ install_element(BTS_NODE, &cfg_bts_pag_free_cmd);
+ install_element(BTS_NODE, &cfg_bts_si_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_si_static_cmd);
+ install_element(BTS_NODE, &cfg_bts_si_unused_send_empty_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_si_unused_send_empty_cmd);
+ install_element(BTS_NODE, &cfg_bts_early_cm_cmd);
+ install_element(BTS_NODE, &cfg_bts_early_cm_3g_cmd);
+ install_element(BTS_NODE, &cfg_bts_neigh_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_neigh_cmd);
+ install_element(BTS_NODE, &cfg_bts_si5_neigh_cmd);
+ install_element(BTS_NODE, &cfg_bts_si2quater_neigh_add_cmd);
+ install_element(BTS_NODE, &cfg_bts_si2quater_neigh_del_cmd);
+ install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_add_cmd);
+ install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_del_cmd);
+ install_element(BTS_NODE, &cfg_bts_excl_rf_lock_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_excl_rf_lock_cmd);
+ install_element(BTS_NODE, &cfg_bts_force_comb_si_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_force_comb_si_cmd);
+ install_element(BTS_NODE, &cfg_bts_codec0_cmd);
+ install_element(BTS_NODE, &cfg_bts_codec1_cmd);
+ install_element(BTS_NODE, &cfg_bts_codec2_cmd);
+ install_element(BTS_NODE, &cfg_bts_codec3_cmd);
+ install_element(BTS_NODE, &cfg_bts_codec4_cmd);
+ install_element(BTS_NODE, &cfg_bts_depends_on_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_depends_on_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_modes1_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_modes2_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_modes3_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_modes4_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_thres1_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_thres2_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_thres3_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_hyst1_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_hyst2_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_hyst3_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_fr_start_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_hr_modes1_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_hr_modes2_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_hr_modes3_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_hr_modes4_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_hr_thres1_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_hr_thres2_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_hr_thres3_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_hr_hyst1_cmd);
+ install_element(BTS_NODE, &cfg_bts_amr_hr_hyst2_cmd);
+ 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_osmux_cmd);
+ install_element(BTS_NODE, &cfg_bts_mgw_pool_target_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_mgw_pool_target_cmd);
+ install_element(BTS_NODE, &cfg_bts_acc_rotate_cmd);
+ install_element(BTS_NODE, &cfg_bts_acc_rotate_quantum_cmd);
+ install_element(BTS_NODE, &cfg_bts_acc_ramping_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_acc_ramping_cmd);
+ install_element(BTS_NODE, &cfg_bts_acc_ramping_step_interval_cmd);
+ install_element(BTS_NODE, &cfg_bts_acc_ramping_step_size_cmd);
+ install_element(BTS_NODE, &cfg_bts_acc_ramping_chan_load_cmd);
+ install_element(BTS_NODE, &cfg_bts_t3113_dynamic_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_t3113_dynamic_cmd);
+ install_element(BTS_NODE, &cfg_bts_rep_dl_facch_cmd);
+ install_element(BTS_NODE, &cfg_bts_rep_no_dl_facch_cmd);
+ install_element(BTS_NODE, &cfg_bts_rep_ul_dl_sacch_cmd);
+ install_element(BTS_NODE, &cfg_bts_rep_no_ul_dl_sacch_cmd);
+ install_element(BTS_NODE, &cfg_bts_rep_rxqual_cmd);
+ install_element(BTS_NODE, &cfg_bts_top_dl_acch_cmd);
+ install_element(BTS_NODE, &cfg_bts_top_no_dl_acch_cmd);
+ install_element(BTS_NODE, &cfg_bts_top_dl_acch_rxqual_cmd);
+ install_element(BTS_NODE, &cfg_bts_top_dl_acch_chan_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_interf_meas_avg_period_cmd);
+ install_element(BTS_NODE, &cfg_bts_interf_meas_level_bounds_cmd);
+ install_element(BTS_NODE, &cfg_bts_srvcc_fast_return_cmd);
+ install_element(BTS_NODE, &cfg_bts_immediate_assignment_cmd);
+ install_element(BTS_NODE, &cfg_bts_nch_position_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_nch_position_cmd);
+ install_element(BTS_NODE, &cfg_bts_ncc_permitted_all_cmd);
+ install_element(BTS_NODE, &cfg_bts_ncc_permitted_cmd);
+
+ neighbor_ident_vty_init();
+ /* See also handover commands added on bts level from handover_vty.c */
+
+ install_element(BTS_NODE, &cfg_bts_power_ctrl_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_power_ctrl_cmd);
+ install_node(&power_ctrl_node, dummy_config_write);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_mode_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_bs_power_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ctrl_interval_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_step_size_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_rxlev_thresh_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_rxqual_thresh_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_thresh_disable_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_thresh_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_rxlev_thresh_comp_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_rxqual_thresh_comp_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_thresh_comp_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_no_avg_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_avg_params_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_avg_algo_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_avg_osmo_ewma_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_no_ci_avg_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_avg_params_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_avg_algo_cmd);
+ install_element(POWER_CTRL_NODE, &cfg_power_ctrl_ci_avg_osmo_ewma_cmd);
+
+
+ return bts_trx_vty_init();
+}
diff --git a/src/osmo-bsc/cbch_scheduler.c b/src/osmo-bsc/cbch_scheduler.c
new file mode 100644
index 000000000..d699e7ede
--- /dev/null
+++ b/src/osmo-bsc/cbch_scheduler.c
@@ -0,0 +1,290 @@
+/* CBCH (Cell Broadcast Channel) Scheduler for OsmoBSC */
+/*
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/core/stats.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/bts.h>
+
+/* add all pages of given SMSCB so they appear as soon as possible *after* (included) base_idx. */
+static int bts_smscb_sched_add_after(struct bts_smscb_page **sched_arr, int sched_arr_size,
+ int base_idx, struct bts_smscb_message *smscb)
+{
+ int arr_idx = base_idx;
+ int i;
+
+ OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
+ for (i = 0; i < smscb->num_pages; i++) {
+ while (sched_arr[arr_idx]) {
+ arr_idx++;
+ if (arr_idx >= sched_arr_size)
+ return -ENOSPC;
+ }
+ sched_arr[arr_idx] = &smscb->page[i];
+ }
+ return arr_idx;
+}
+
+/* add all pages of given smscb so they appear *before* (included) last_idx. */
+static int bts_smscb_sched_add_before(struct bts_smscb_page **sched_arr, int sched_arr_size,
+ int last_idx, struct bts_smscb_message *smscb)
+{
+ int arr_idx = last_idx;
+ int last_used_idx = 0;
+ int i;
+
+ OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page));
+ OSMO_ASSERT(smscb->num_pages >= 1);
+
+ if (last_idx >= sched_arr_size)
+ return -ERANGE;
+
+ for (i = smscb->num_pages - 1; i >= 0; i--) {
+ while (sched_arr[arr_idx]) {
+ arr_idx--;
+ if (arr_idx < 0)
+ return -ENOSPC;
+ }
+ sched_arr[arr_idx] = &smscb->page[i];
+ if (i == smscb->num_pages)
+ last_used_idx = i;
+ }
+ return last_used_idx;
+}
+
+/* obtain the least frequently scheduled SMSCB for given SMSCB channel */
+static struct bts_smscb_message *
+bts_smscb_chan_get_least_frequent_smscb(struct bts_smscb_chan_state *cstate)
+{
+ if (llist_empty(&cstate->messages))
+ return NULL;
+ /* messages are expected to be ordered with increasing period, so we're
+ * able to return the last message in the list */
+ return llist_entry(cstate->messages.prev, struct bts_smscb_message, list);
+}
+
+/*! Generate per-BTS SMSCB scheduling array
+ * \param[in] cstate BTS CBCH channel state
+ * \param[out] arr_out return argument for allocated + generated scheduling array
+ * \return size of returned scheduling array arr_out in number of entries; negative on error */
+int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out)
+{
+ struct bts_smscb_message *smscb, *least_freq;
+ struct bts_smscb_page **arr;
+ int arr_size;
+ int rc;
+
+ /* start with one instance of the least frequent message at position 0, as we
+ * need to transmit it exactly once during the duration of the scheduling array */
+ least_freq = bts_smscb_chan_get_least_frequent_smscb(cstate);
+ if (!least_freq) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_DEBUG, "No SMSCB; cannot create schedule array\n");
+ *arr_out = NULL;
+ return 0;
+ }
+ arr_size = least_freq->input.rep_period;
+ arr = talloc_zero_array(cstate->bts, struct bts_smscb_page *, arr_size);
+ OSMO_ASSERT(arr);
+ rc = bts_smscb_sched_add_after(arr, arr_size, 0, least_freq);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
+ "very first SMSCB %s ?!?\n", bts_smscb_msg2str(least_freq));
+ talloc_free(arr);
+ return rc;
+ }
+
+ /* continue filling with repetitions of the more frequent messages, starting from
+ * the most frequent message to the least frequent one, repeating them as needed
+ * throughout the duration of the array */
+ llist_for_each_entry(smscb, &cstate->messages, list) {
+ int last_page;
+ if (smscb == least_freq)
+ continue;
+ /* messages are expected to be ordered with increasing period, so we're
+ * starting with the most frequent / shortest period first */
+ rc = bts_smscb_sched_add_after(arr, arr_size, 0, smscb);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of "
+ "SMSCB %s\n", bts_smscb_msg2str(smscb));
+ talloc_free(arr);
+ return rc;
+ }
+ last_page = rc;
+
+ while (last_page + smscb->input.rep_period < cstate->sched_arr_size) {
+ /* store further instances in a way that the last block of the N+1th instance
+ * happens no later than "interval" after the last block of the Nth instance */
+ rc = bts_smscb_sched_add_before(arr, arr_size,
+ last_page + smscb->input.rep_period, smscb);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule further "
+ "SMSCB %s\n", bts_smscb_msg2str(smscb));
+ talloc_free(arr);
+ return rc;
+ }
+ last_page = rc;
+ }
+ }
+ *arr_out = arr;
+ return arr_size;
+}
+
+/*! Pull the next to-be-transmitted SMSCB page out of the scheduler for the given channel */
+struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate)
+{
+ struct bts_smscb_page *page;
+
+ /* if there are no messages to schedule, there is no array */
+ if (!cstate->sched_arr)
+ return NULL;
+
+ /* obtain the page from the scheduler array */
+ page = cstate->sched_arr[cstate->next_idx];
+
+ /* increment the index for the next call to this function */
+ cstate->next_idx = (cstate->next_idx + 1) % cstate->sched_arr_size;
+
+ /* the array can have gaps in between where there is nothing scheduled */
+ if (!page)
+ return NULL;
+
+ return page;
+}
+
+/*! To be called after bts_smscb_pull_page() in order to update transmission count and
+ * check if SMSCB is complete.
+ * \param[in] cstate BTS CBC channel state
+ * \param[in] page SMSCB Page which had been returned by bts_smscb_pull_page() and which
+ * is no longer needed now */
+void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page)
+{
+ struct bts_smscb_message *smscb = page->msg;
+
+ /* If this is the last page of a SMSCB, increment the SMSCB number-of-xmit counter */
+ if (page->nr == smscb->num_pages) {
+ smscb->bcast_count++;
+ /* Check if the SMSCB transmission duration is now over */
+ if (smscb->bcast_count >= smscb->input.num_bcast_req)
+ bts_smscb_del(smscb, cstate, "COMPLETE");
+ }
+}
+
+
+/***********************************************************************
+ * BTS / RSL side
+ ***********************************************************************/
+
+static void bts_cbch_send_one(struct bts_smscb_chan_state *cstate)
+{
+ struct bts_smscb_page *page;
+ struct gsm_bts *bts = cstate->bts;
+ struct rsl_ie_cb_cmd_type cb_cmd;
+ bool is_extended = false;
+
+ if (cstate == &bts->cbch_extended)
+ is_extended = true;
+
+ if (cstate->overflow) {
+ LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB due to overflow (%u)\n",
+ cstate->overflow);
+ cstate->overflow--;
+ return;
+ }
+
+ page = bts_smscb_pull_page(cstate);
+ if (!page) {
+ LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB: No page available\n");
+ return;
+ }
+
+ cb_cmd.spare = 0;
+ cb_cmd.def_bcast = 0;
+ cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL;
+ switch (page->num_blocks) {
+ case 1:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1;
+ break;
+ case 2:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2;
+ break;
+ case 3:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3;
+ break;
+ case 4:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4;
+ break;
+ default:
+ osmo_panic("SMSCB Page must have 1..4 blocks, not %d\n", page->num_blocks);
+ }
+ rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, is_extended,
+ page->data, sizeof(page->data));
+
+ bts_smscb_page_done(cstate, page);
+}
+
+void bts_cbch_timer_cb(void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)data;
+
+ bts_cbch_send_one(&bts->cbch_basic);
+ bts_cbch_send_one(&bts->cbch_extended);
+
+ bts_cbch_timer_schedule(bts);
+}
+
+/* There is one SMSCB message (page) per eight 51-multiframes, i.e. 1.882 seconds */
+void bts_cbch_timer_schedule(struct gsm_bts *bts)
+{
+ osmo_timer_schedule(&bts->cbch_timer, 1, 882920);
+}
+
+/*! Receive a (decoded) incoming CBCH LOAD IND from given bts. See TS 48.058 8.5.9
+ * \param[in] bts The BTS for which the load indication was received
+ * \param[in] cbch_extended Is this report for extended (true) or basic CBCH
+ * \param[in] is_overflow Is this report and overflow (true) or underflow report
+ * \param[in] slot_count amount of SMSCB messages needed / delay needed */
+int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow,
+ uint8_t slot_count)
+{
+ struct bts_smscb_chan_state *cstate = bts_get_smscb_chan(bts, cbch_extended);
+ int i;
+
+ if (!gsm_bts_get_cbch(bts))
+ return -ENODEV;
+
+ if (is_overflow) {
+ /* halt/delay transmission of further CBCH messages */
+ cstate->overflow = slot_count;
+ } else {
+ for (i = 0; i < slot_count; i++)
+ bts_cbch_send_one(cstate);
+ /* re-schedule the timer to count from now on */
+ bts_cbch_timer_schedule(bts);
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bsc/cbsp_link.c b/src/osmo-bsc/cbsp_link.c
new file mode 100644
index 000000000..9e17ef373
--- /dev/null
+++ b/src/osmo-bsc/cbsp_link.c
@@ -0,0 +1,324 @@
+/* CBSP (Cell Broadcast Service Protocol) Handling for OsmoBSC */
+/*
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/debug.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/cbsp.h>
+
+/* if a CBC IP/port has been configured, we continuously try to re-establish the TCP
+ * connection (as a client) to the CBC. If none has been configured, and we have a listen
+ * TCP port, we expect the CBC to connect to us. If neither of the two is configured,
+ * CBSP is effectively disabled */
+
+const struct value_string bsc_cbc_link_mode_names[] = {
+ { BSC_CBC_LINK_MODE_DISABLED, "disabled" },
+ { BSC_CBC_LINK_MODE_SERVER, "server" },
+ { BSC_CBC_LINK_MODE_CLIENT, "client" },
+ {}
+};
+
+const struct osmo_sockaddr_str bsc_cbc_default_server_local_addr = {
+ .af = AF_INET,
+ .ip = "127.0.0.1",
+ .port = CBSP_TCP_PORT,
+};
+
+/*********************************************************************************
+ * CBSP Server (inbound TCP connection from CBC)
+ *********************************************************************************/
+
+static int cbsp_srv_closed_cb(struct osmo_stream_srv *conn)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn);
+
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Server lost connection from %s\n", cbc->server.sock_name);
+ talloc_free(cbc->server.sock_name);
+ cbc->server.sock_name = NULL;
+ cbc->server.srv = NULL;
+ return 0;
+}
+
+static int cbsp_srv_read_cb(struct osmo_stream_srv *conn, int res, struct msgb *msg)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn);
+ struct osmo_cbsp_decoded *decoded;
+
+ if (res <= 0) {
+ if (res == -EAGAIN || res == -EINTR) {
+ msgb_free(msg);
+ return 0;
+ }
+ /*
+ if (rc == -EPIPE || rc == -ECONNRESET) {
+ // lost connection
+ } else if (rc == 0) {
+ // connection closed
+ } */
+ msgb_free(msg);
+ osmo_stream_srv_destroy(conn);
+ cbc->server.srv = NULL;
+ return -EBADF;
+ }
+
+ OSMO_ASSERT(msg);
+ decoded = osmo_cbsp_decode(conn, msg);
+ if (decoded) {
+ LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n",
+ get_value_string(cbsp_msg_type_names, decoded->msg_type));
+ cbsp_rx_decoded(cbc, decoded);
+ talloc_free(decoded);
+ } else {
+ LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n",
+ msgb_hexdump(msg), osmo_cbsp_errstr);
+ }
+ msgb_free(msg);
+ return 0;
+
+}
+
+static int cbsp_srv_link_accept_cb(struct osmo_stream_srv_link *link, int fd)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_srv_link_get_data(link);
+ struct osmo_stream_srv *srv;
+
+ LOGP(DCBS, LOGL_INFO, "CBSP Server received inbound connection from CBC: %s\n",
+ osmo_sock_get_name2(fd));
+
+ if (cbc->server.srv) {
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Server refusing further connection (%s) "
+ "while we already have another connection (%s)\n",
+ osmo_sock_get_name2(fd), cbc->server.sock_name);
+ return -1;
+ }
+
+ srv = osmo_stream_srv_create2(cbc, link, fd, cbc);
+ if (!srv) {
+ LOGP(DCBS, LOGL_ERROR, "Unable to create stream server for %s\n",
+ osmo_sock_get_name2(fd));
+ return -1;
+ }
+ osmo_stream_srv_set_read_cb(srv, cbsp_srv_read_cb);
+ osmo_stream_srv_set_closed_cb(srv, cbsp_srv_closed_cb);
+ osmo_stream_srv_set_segmentation_cb(srv, osmo_cbsp_segmentation_cb);
+
+ cbc->server.srv = srv;
+ if (cbc->server.sock_name)
+ talloc_free(cbc->server.sock_name);
+ cbc->server.sock_name = osmo_sock_get_name(cbc, fd);
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Server link established from CBC %s\n", cbc->server.sock_name);
+ /* TODO: introduce ourselves to the peer using some osmcoom extensions */
+ cbsp_tx_restart(cbc, false);
+ return 0;
+}
+
+/*********************************************************************************
+ * CBSP Client (outbound TCP connection to CBC)
+ *********************************************************************************/
+
+static int cbsp_client_connect_cb(struct osmo_stream_cli *cli)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
+
+ if (cbc->client.sock_name)
+ talloc_free(cbc->client.sock_name);
+ cbc->client.sock_name = osmo_sock_get_name(cbc, osmo_stream_cli_get_fd(cli));
+
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Client connected to CBC: %s\n", cbc->client.sock_name);
+
+ /* TODO: introduce ourselves to the peer using some osmcoom extensions */
+ cbsp_tx_restart(cbc, false);
+
+ return 0;
+}
+
+static int cbsp_client_disconnect_cb(struct osmo_stream_cli *cli)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
+
+ LOGP(DCBS, LOGL_NOTICE, "CBSP Client lost connection to %s\n", cbc->client.sock_name);
+ talloc_free(cbc->client.sock_name);
+ cbc->client.sock_name = NULL;
+ return 0;
+}
+
+static int cbsp_client_read_cb(struct osmo_stream_cli *cli, int res, struct msgb *msg)
+{
+ struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
+ struct osmo_cbsp_decoded *decoded;
+
+ if (res <= 0) {
+ if (res == -EAGAIN || res == -EINTR) {
+ msgb_free(msg);
+ return 0;
+ }
+ /*
+ if (rc == -EPIPE || rc == -ECONNRESET) {
+ // lost connection
+ } else if (rc == 0) {
+ // connection closed
+ } */
+ msgb_free(msg);
+ osmo_stream_cli_reconnect(cli);
+ return -EBADF;
+ }
+
+ OSMO_ASSERT(msg);
+ decoded = osmo_cbsp_decode(cli, msg);
+ if (decoded) {
+ LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n",
+ get_value_string(cbsp_msg_type_names, decoded->msg_type));
+ cbsp_rx_decoded(cbc, decoded);
+ talloc_free(decoded);
+ } else {
+ LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n",
+ msgb_hexdump(msg), osmo_cbsp_errstr);
+ }
+ msgb_free(msg);
+ return 0;
+}
+
+int bsc_cbc_link_restart(void)
+{
+ struct bsc_cbc_link *cbc = bsc_gsmnet->cbc;
+
+ /* shut down client, if no longer configured */
+ if (cbc->client.cli && cbc->mode != BSC_CBC_LINK_MODE_CLIENT) {
+ LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP client\n");
+ osmo_stream_cli_close(cbc->client.cli);
+ osmo_stream_cli_destroy(cbc->client.cli);
+ cbc->client.cli = NULL;
+ }
+
+ /* shut down server, if no longer configured */
+ if (cbc->mode != BSC_CBC_LINK_MODE_SERVER) {
+ if (cbc->server.srv || cbc->server.link)
+ LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP server\n");
+ if (cbc->server.srv) {
+ osmo_stream_srv_destroy(cbc->server.srv);
+ cbc->server.srv = NULL;
+ }
+ if (cbc->server.link) {
+ osmo_stream_srv_link_close(cbc->server.link);
+ osmo_stream_srv_link_destroy(cbc->server.link);
+ cbc->server.link = NULL;
+ }
+ }
+
+ switch (cbc->mode) {
+ case BSC_CBC_LINK_MODE_CLIENT:
+ if (!osmo_sockaddr_str_is_nonzero(&cbc->client.remote_addr)) {
+ LOGP(DCBS, LOGL_ERROR,
+ "Cannot start CBSP in client mode: invalid remote-ip or -port in 'cbc' / 'client')\n");
+ return -1;
+ }
+
+ LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Client (to CBC at " OSMO_SOCKADDR_STR_FMT ")\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->client.remote_addr));
+ if (!cbc->client.cli) {
+ cbc->client.cli = osmo_stream_cli_create(cbc);
+ osmo_stream_cli_set_data(cbc->client.cli, cbc);
+ osmo_stream_cli_set_connect_cb(cbc->client.cli, cbsp_client_connect_cb);
+ osmo_stream_cli_set_disconnect_cb(cbc->client.cli, cbsp_client_disconnect_cb);
+ osmo_stream_cli_set_read_cb2(cbc->client.cli, cbsp_client_read_cb);
+ osmo_stream_cli_set_segmentation_cb(cbc->client.cli, osmo_cbsp_segmentation_cb);
+ }
+ /* CBC side */
+ osmo_stream_cli_set_addr(cbc->client.cli, cbc->client.remote_addr.ip);
+ osmo_stream_cli_set_port(cbc->client.cli, cbc->client.remote_addr.port);
+ /* local side */
+ if (osmo_sockaddr_str_is_set(&cbc->client.local_addr)) {
+ osmo_stream_cli_set_local_addr(cbc->client.cli, cbc->client.local_addr.ip);
+ osmo_stream_cli_set_local_port(cbc->client.cli, cbc->client.local_addr.port);
+ }
+ /* Close/Reconnect? */
+ if (osmo_stream_cli_open(cbc->client.cli) < 0) {
+ LOGP(DCBS, LOGL_ERROR, "Cannot open CBSP client link to " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->client.remote_addr));
+ return -1;
+ }
+ return 0;
+
+ case BSC_CBC_LINK_MODE_SERVER:
+ if (!osmo_sockaddr_str_is_set(&cbc->server.local_addr)) {
+ LOGP(DCBS, LOGL_ERROR,
+ "Cannot start CBSP in server mode: invalid local-ip or -port in 'cbc' / 'server')\n");
+ return -1;
+ }
+ LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Server (listening at " OSMO_SOCKADDR_STR_FMT ")\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->server.local_addr));
+ if (!cbc->server.link) {
+ LOGP(DCBS, LOGL_NOTICE, "Creating CBSP Server\n");
+ cbc->server.link = osmo_stream_srv_link_create(cbc);
+ osmo_stream_srv_link_set_data(cbc->server.link, cbc);
+ osmo_stream_srv_link_set_accept_cb(cbc->server.link, cbsp_srv_link_accept_cb);
+
+ osmo_stream_srv_link_set_addr(cbc->server.link, cbc->server.local_addr.ip);
+ osmo_stream_srv_link_set_port(cbc->server.link, cbc->server.local_addr.port);
+
+ if (osmo_stream_srv_link_open(cbc->server.link) < 0) {
+ LOGP(DCBS, LOGL_ERROR, "Cannot open CBSP Server link at " OSMO_SOCKADDR_STR_FMT ")\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->server.local_addr));
+ return -1;
+ }
+ }
+ return 0;
+
+ default:
+ return 0;
+ }
+}
+
+/*! Encode + Transmit a 'decoded' CBSP message over given CBC link
+ * \param[in] cbc Data structure representing the BSCs link to the CBC
+ * \param[in] cbsp Decoded CBSP message to be transmitted. Ownership is transferred.
+ * \return 0 on success, negative otherwise */
+int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *cbsp)
+{
+ struct msgb *msg;
+
+ if (!cbc->client.cli && !cbc->server.srv) {
+ LOGP(DCBS, LOGL_INFO, "Discarding Tx CBSP Message Type %s, link is down\n",
+ get_value_string(cbsp_msg_type_names, cbsp->msg_type));
+ talloc_free(cbsp);
+ return 0;
+ }
+
+ msg = osmo_cbsp_encode(cbc, cbsp);
+ if (!msg) {
+ LOGP(DCBS, LOGL_ERROR, "Unable to encode CBSP Message Type %s: %s\n",
+ get_value_string(cbsp_msg_type_names, cbsp->msg_type), osmo_cbsp_errstr);
+ talloc_free(cbsp);
+ return -1;
+ }
+ if (cbc->client.cli)
+ osmo_stream_cli_send(cbc->client.cli, msg);
+ else if (cbc->server.srv)
+ osmo_stream_srv_send(cbc->server.srv, msg);
+
+ talloc_free(cbsp);
+ return 0;
+}
diff --git a/src/osmo-bsc/chan_alloc.c b/src/osmo-bsc/chan_alloc.c
index 9c434145f..cc8da3daf 100644
--- a/src/osmo-bsc/chan_alloc.c
+++ b/src/osmo-bsc/chan_alloc.c
@@ -34,19 +34,24 @@
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/core/talloc.h>
+/* Update channel load calculation for the given BTS */
void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
{
struct gsm_bts_trx *trx;
llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct load_counter *ll = &trx->lchan_load;
int i;
- /* skip administratively deactivated tranxsceivers */
- if (!nm_is_running(&trx->mo.nm_state) ||
- !nm_is_running(&trx->bb_transc.mo.nm_state))
+ /* init per-TRX load counters */
+ memset(ll, 0, sizeof(*ll));
+
+ /* skip administratively deactivated transceivers */
+ if (!trx_is_usable(trx))
continue;
for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
@@ -58,13 +63,41 @@ void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
if (!nm_is_running(&ts->mo.nm_state))
continue;
- ts_for_each_lchan(lchan, ts) {
+ /* A dynamic timeslot currently in PDCH mode are available as TCH or SDCCH8, because they can be switched
+ * to TCH or SDCCH mode at any moment. Count TCH/F_TCH/H_SDCCH8_PDCH as one total timeslot, even though it may
+ * be switched to TCH/H and would then count as two -- hence opt for pessimistic load. */
+ if ((ts->pchan_on_init == GSM_PCHAN_OSMO_DYN ||
+ ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH) &&
+ (ts->pchan_is == GSM_PCHAN_NONE ||
+ ts->pchan_is == GSM_PCHAN_PDCH)) {
+ ll->total++;
+ pl->total++;
+ /* Below loop would not count this timeslot, since in PDCH mode it has no usable
+ * timeslots. But let's make it clear that the timeslot must not be counted again: */
+ continue;
+ }
+
+ ts_for_n_lchans(lchan, ts, ts->max_primary_lchans) {
+ /* don't even count CBCH slots in total */
+ if (lchan->type == GSM_LCHAN_CBCH)
+ continue;
+
+ ll->total++;
pl->total++;
+ /* lchans under a BORKEN TS should be counted
+ * as used just as BORKEN lchans under a normal TS */
+ if (ts->fi->state == TS_ST_BORKEN) {
+ ll->used++;
+ pl->used++;
+ continue;
+ }
+
switch (lchan->fi->state) {
case LCHAN_ST_UNUSED:
break;
default:
+ ll->used++;
pl->used++;
break;
}
@@ -73,6 +106,7 @@ void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
}
}
+/* Update channel load calculation for all BTS in the BSC */
void network_chan_load(struct pchan_load *pl, struct gsm_network *net)
{
struct gsm_bts *bts;
@@ -83,6 +117,53 @@ void network_chan_load(struct pchan_load *pl, struct gsm_network *net)
bts_chan_load(pl, bts);
}
+static void chan_load_stat_set(enum gsm_phys_chan_config pchan,
+ struct gsm_bts *bts,
+ struct load_counter *lc)
+{
+ switch (pchan) {
+ case GSM_PCHAN_NONE:
+ case GSM_PCHAN_CCCH:
+ case GSM_PCHAN_PDCH:
+ case GSM_PCHAN_UNKNOWN:
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_USED), lc->used);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_TOTAL), lc->total);
+ break;
+ case GSM_PCHAN_TCH_F:
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_USED), lc->used);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_TOTAL), lc->total);
+ break;
+ case GSM_PCHAN_TCH_H:
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_H_USED), lc->used);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_H_TOTAL), lc->total);
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_USED), lc->used);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_TOTAL), lc->total);
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_PDCH_USED), lc->used);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_TCH_F_PDCH_TOTAL), lc->total);
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_USED), lc->used);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_TOTAL), lc->total);
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_CBCH_USED), lc->used);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_SDCCH8_CBCH_TOTAL), lc->total);
+ break;
+ case GSM_PCHAN_OSMO_DYN:
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_OSMO_DYN_USED), lc->used);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_OSMO_DYN_TOTAL), lc->total);
+ break;
+ default:
+ LOG_BTS(bts, DRLL, LOGL_NOTICE, "Unknown channel type %d\n", pchan);
+ }
+}
+
/* Update T3122 wait indicator based on samples of BTS channel load. */
void
bts_update_t3122_chan_load(struct gsm_bts *bts)
@@ -107,10 +188,13 @@ bts_update_t3122_chan_load(struct gsm_bts *bts)
for (i = 0; i < ARRAY_SIZE(pl.pchan); i++) {
struct load_counter *lc = &pl.pchan[i];
+ /* Export channel load to stats gauges */
+ chan_load_stat_set(i, bts, lc);
+
/* Ignore samples too large for fixed-point calculations (shouldn't happen). */
if (lc->used > UINT16_MAX || lc->total > UINT16_MAX) {
- LOGP(DRLL, LOGL_NOTICE, "(bts=%d) numbers in channel load sample "
- "too large (used=%u / total=%u)\n", bts->nr, lc->used, lc->total);
+ LOG_BTS(bts, DRLL, LOGL_NOTICE, "numbers in channel load sample "
+ "too large (used=%u / total=%u)\n", lc->used, lc->total);
continue;
}
@@ -119,9 +203,11 @@ bts_update_t3122_chan_load(struct gsm_bts *bts)
}
/* Check for invalid samples (shouldn't happen). */
+ if (used > total) {
+ LOG_BTS(bts, DRLL, LOGL_NOTICE, "bogus channel load sample (used=%"PRIu64" / total=%"PRIu32")\n",
+ used, total);
+ }
if (total == 0 || used > total) {
- LOGP(DRLL, LOGL_NOTICE, "(bts=%d) bogus channel load sample (used=%"PRIu64" / total=%"PRIu32")\n",
- bts->nr, used, total);
bts->T3122 = 0; /* disable override of network-wide default value */
bts->chan_load_samples_idx = 0; /* invalidate other samples collected so far */
return;
@@ -149,11 +235,11 @@ bts_update_t3122_chan_load(struct gsm_bts *bts)
/* Log channel load average. */
load = ((used / total) * 100);
- LOGP(DRLL, LOGL_DEBUG, "(bts=%d) channel load average is %"PRIu64".%.2"PRIu64"%%\n",
- bts->nr, (load & 0xffffff00) >> 8, (load & 0xff) / 10);
+ LOG_BTS(bts, DRLL, LOGL_DEBUG, "channel load average is %"PRIu64".%.2"PRIu64"%%\n",
+ (load & 0xffffff00) >> 8, (load & 0xff) / 10);
bts->chan_load_avg = ((load & 0xffffff00) >> 8);
OSMO_ASSERT(bts->chan_load_avg <= 100);
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_LOAD_AVERAGE], bts->chan_load_avg);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_CHAN_LOAD_AVERAGE), bts->chan_load_avg);
/* Calculate new T3122 wait indicator. */
wait_ind = ((used / total) * max_wait_ind);
@@ -163,7 +249,7 @@ bts_update_t3122_chan_load(struct gsm_bts *bts)
else if (wait_ind > max_wait_ind)
wait_ind = max_wait_ind;
- LOGP(DRLL, LOGL_DEBUG, "(bts=%d) T3122 wait indicator set to %"PRIu64" seconds\n", bts->nr, wait_ind);
+ LOG_BTS(bts, DRLL, LOGL_DEBUG, "T3122 wait indicator set to %"PRIu64" seconds\n", wait_ind);
bts->T3122 = (uint8_t)wait_ind;
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_T3122], wait_ind);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_T3122), wait_ind);
}
diff --git a/src/osmo-bsc/chan_counts.c b/src/osmo-bsc/chan_counts.c
new file mode 100644
index 000000000..bf863688d
--- /dev/null
+++ b/src/osmo-bsc/chan_counts.c
@@ -0,0 +1,310 @@
+/* count total, allocated and free channels of all types.
+ *
+ * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * 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/gsm/gsm_utils.h>
+
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bts_trx.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/chan_counts.h>
+#include <osmocom/bsc/bsc_stats.h>
+#include <osmocom/bsc/signal.h>
+
+static const unsigned int lchans_per_pchan[_GSM_PCHAN_MAX][_GSM_LCHAN_MAX] = {
+ [GSM_PCHAN_NONE] = {0},
+ [GSM_PCHAN_CCCH] = { [GSM_LCHAN_CCCH] = 1, },
+ [GSM_PCHAN_PDCH] = { [GSM_LCHAN_PDTCH] = 1, },
+ [GSM_PCHAN_CCCH_SDCCH4] = {
+ [GSM_LCHAN_CCCH] = 1,
+ [GSM_LCHAN_SDCCH] = 3,
+ },
+ [GSM_PCHAN_TCH_F] = { [GSM_LCHAN_TCH_F] = 1, },
+ [GSM_PCHAN_TCH_H] = { [GSM_LCHAN_TCH_H] = 2, },
+ [GSM_PCHAN_SDCCH8_SACCH8C] = { [GSM_LCHAN_SDCCH] = 8, },
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = {
+ [GSM_LCHAN_CCCH] = 1,
+ [GSM_LCHAN_SDCCH] = 3,
+ [GSM_LCHAN_CBCH] = 1,
+ },
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = {
+ [GSM_LCHAN_SDCCH] = 8,
+ [GSM_LCHAN_CBCH] = 1,
+ },
+ [GSM_PCHAN_OSMO_DYN] = {
+ [GSM_LCHAN_TCH_F] = 1,
+ [GSM_LCHAN_TCH_H] = 2,
+ [GSM_LCHAN_SDCCH] = 8,
+ [GSM_LCHAN_PDTCH] = 1,
+ },
+ [GSM_PCHAN_TCH_F_PDCH] = {
+ [GSM_LCHAN_TCH_F] = 1,
+ [GSM_LCHAN_PDTCH] = 1,
+ },
+};
+
+static inline void chan_counts_per_pchan_add(struct chan_counts *dst,
+ enum chan_counts_dim1 dim1, enum chan_counts_dim2 dim2,
+ enum gsm_phys_chan_config pchan)
+{
+ int i;
+ for (i = 0; i < _GSM_LCHAN_MAX; i++)
+ dst->val[dim1][dim2][i] += lchans_per_pchan[pchan][i];
+}
+
+static const char *chan_counts_dim1_name[_CHAN_COUNTS1_NUM] = {
+ [CHAN_COUNTS1_ALL] = "all",
+ [CHAN_COUNTS1_STATIC] = "static",
+ [CHAN_COUNTS1_DYNAMIC] = "dynamic",
+};
+
+static const char *chan_counts_dim2_name[_CHAN_COUNTS2_NUM] = {
+ [CHAN_COUNTS2_MAX_TOTAL] = "max",
+ [CHAN_COUNTS2_CURRENT_TOTAL] = "current",
+ [CHAN_COUNTS2_ALLOCATED] = "alloc",
+ [CHAN_COUNTS2_FREE] = "free",
+};
+
+int chan_counts_to_str_buf(char *buf, size_t buflen, const struct chan_counts *c)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ int i1, i2, i3;
+ OSMO_STRBUF_PRINTF(sb, "{");
+ for (i1 = 0; i1 < _CHAN_COUNTS1_NUM; i1++) {
+ for (i2 = 0; i2 < _CHAN_COUNTS2_NUM; i2++) {
+ bool p12 = false;
+
+ for (i3 = 0; i3 < _GSM_LCHAN_MAX; i3++) {
+
+ int v = c->val[i1][i2][i3];
+ if (v) {
+ if (!p12) {
+ p12 = true;
+ OSMO_STRBUF_PRINTF(sb, " %s.%s{", chan_counts_dim1_name[i1],
+ chan_counts_dim2_name[i2]);
+ }
+ OSMO_STRBUF_PRINTF(sb, " %s=%d", gsm_chan_t_name(i3), v);
+ }
+ }
+
+ if (p12)
+ OSMO_STRBUF_PRINTF(sb, " }");
+ }
+ }
+ OSMO_STRBUF_PRINTF(sb, " }");
+ return sb.chars_needed;
+}
+
+char *chan_counts_to_str_c(void *ctx, const struct chan_counts *c)
+{
+ OSMO_NAME_C_IMPL(ctx, 64, "ERROR", chan_counts_to_str_buf, c)
+}
+
+void chan_counts_for_ts(struct chan_counts *ts_counts, const struct gsm_bts_trx_ts *ts)
+{
+ const struct gsm_lchan *lchan;
+ bool ts_is_dynamic;
+
+ chan_counts_zero(ts_counts);
+
+ if (!ts_is_usable(ts))
+ return;
+
+ /* Count the full potential nr of lchans for dynamic TS */
+ chan_counts_per_pchan_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_MAX_TOTAL, ts->pchan_on_init);
+
+ switch (ts->pchan_on_init) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
+ ts_is_dynamic = true;
+ break;
+ default:
+ ts_is_dynamic = false;
+ break;
+ }
+
+ if (ts_is_dynamic && ts->pchan_is == GSM_PCHAN_PDCH) {
+ /* Dynamic timeslots in PDCH mode can become TCH or SDCCH immediately,
+ * so set CURRENT_TOTAL = MAX_TOTAL. */
+ chan_counts_dim3_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_CURRENT_TOTAL,
+ ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_MAX_TOTAL);
+ } else {
+ /* Static TS, or dyn TS that are currently fixed on a specific pchan: count lchans for the
+ * current pchan mode. */
+ chan_counts_per_pchan_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_CURRENT_TOTAL, ts->pchan_is);
+ }
+
+ /* Count currently allocated lchans */
+ ts_for_n_lchans(lchan, ts, ts->max_primary_lchans) {
+ if (!lchan_state_is(lchan, LCHAN_ST_UNUSED))
+ ts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_ALLOCATED][lchan->type]++;
+ }
+
+ chan_counts_dim3_add(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_FREE,
+ ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_CURRENT_TOTAL);
+ chan_counts_dim3_sub(ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_FREE,
+ ts_counts, CHAN_COUNTS1_ALL, CHAN_COUNTS2_ALLOCATED);
+
+ if (ts_is_dynamic)
+ chan_counts_dim2_add(ts_counts, CHAN_COUNTS1_DYNAMIC, ts_counts, CHAN_COUNTS1_ALL);
+ else
+ chan_counts_dim2_add(ts_counts, CHAN_COUNTS1_STATIC, ts_counts, CHAN_COUNTS1_ALL);
+}
+
+static void chan_counts_diff(struct chan_counts *diff, const struct chan_counts *left, const struct chan_counts *right)
+{
+ chan_counts_zero(diff);
+ chan_counts_add(diff, right);
+ chan_counts_sub(diff, left);
+}
+
+static void _chan_counts_ts_update(struct gsm_bts_trx_ts *ts, const struct chan_counts *ts_new_counts)
+{
+ struct chan_counts diff;
+
+ chan_counts_diff(&diff, &ts->chan_counts, ts_new_counts);
+ if (chan_counts_is_zero(&diff))
+ return;
+
+ ts->chan_counts = *ts_new_counts;
+ chan_counts_add(&ts->trx->chan_counts, &diff);
+ chan_counts_add(&ts->trx->bts->chan_counts, &diff);
+ chan_counts_add(&bsc_gsmnet->chan_counts, &diff);
+
+ all_allocated_update_bts(ts->trx->bts);
+ all_allocated_update_bsc();
+
+ LOGP(DLGLOBAL, LOGL_DEBUG, "change in channel counts: ts %u-%u-%u: %s\n",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr, chan_counts_to_str_c(OTC_SELECT, &diff));
+ LOGP(DLGLOBAL, LOGL_DEBUG, "bsc channel counts: %s\n",
+ chan_counts_to_str_c(OTC_SELECT, &bsc_gsmnet->chan_counts));
+}
+
+/* Re-count this TS, and update ts->chan_counts. If the new ts->chan_counts differ, propagate the difference to
+ * trx->chan_counts, bts->chan_counts and gsm_network->chan_counts. */
+void chan_counts_ts_update(struct gsm_bts_trx_ts *ts)
+{
+ struct chan_counts ts_new_counts;
+ chan_counts_for_ts(&ts_new_counts, ts);
+ _chan_counts_ts_update(ts, &ts_new_counts);
+}
+
+void chan_counts_ts_clear(struct gsm_bts_trx_ts *ts)
+{
+ struct chan_counts ts_new_counts = {0};
+ _chan_counts_ts_update(ts, &ts_new_counts);
+}
+
+void chan_counts_trx_update(struct gsm_bts_trx *trx)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ chan_counts_ts_update(ts);
+ }
+}
+
+static int chan_counts_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+{
+ struct nm_running_chg_signal_data *nsd;
+ struct gsm_bts_trx *trx;
+ if (signal != S_NM_RUNNING_CHG)
+ return 0;
+ nsd = signal_data;
+ switch (nsd->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ trx = (struct gsm_bts_trx *)nsd->obj;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ trx = gsm_bts_bb_trx_get_trx((struct gsm_bts_bb_trx *)nsd->obj);
+ break;
+ default:
+ return 0;
+ }
+ chan_counts_trx_update(trx);
+ return 0;
+}
+
+void chan_counts_sig_init(void)
+{
+ osmo_signal_register_handler(SS_NM, chan_counts_sig_cb, NULL);
+}
+
+void chan_counts_bsc_verify(void)
+{
+ struct gsm_bts *bts;
+ struct chan_counts bsc_counts = {0};
+ struct chan_counts diff;
+
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ struct gsm_bts_trx *trx;
+ struct chan_counts bts_counts = {0};
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct chan_counts trx_counts = {0};
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct chan_counts ts_counts;
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ chan_counts_for_ts(&ts_counts, ts);
+
+ chan_counts_diff(&diff, &ts->chan_counts, &ts_counts);
+ if (!chan_counts_is_zero(&diff)) {
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "internal error in channel counts, on bts-trx-ts %u-%u-%u, fixing."
+ " diff: %s\n",
+ bts->nr, trx->nr, ts->nr,
+ chan_counts_to_str_c(OTC_SELECT, &diff));
+ ts->chan_counts = ts_counts;
+ }
+
+ chan_counts_add(&trx_counts, &ts_counts);
+ }
+
+ chan_counts_diff(&diff, &trx->chan_counts, &trx_counts);
+ if (!chan_counts_is_zero(&diff)) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "internal error in channel counts, on bts-trx %u-%u, fixing."
+ " diff: %s\n",
+ bts->nr, trx->nr, chan_counts_to_str_c(OTC_SELECT, &diff));
+ trx->chan_counts = trx_counts;
+ }
+
+ chan_counts_add(&bts_counts, &trx_counts);
+ }
+
+ chan_counts_diff(&diff, &bts->chan_counts, &bts_counts);
+ if (!chan_counts_is_zero(&diff)) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "internal error in channel counts, on bts %u, fixing. diff: %s\n",
+ bts->nr, chan_counts_to_str_c(OTC_SELECT, &diff));
+ bts->chan_counts = bts_counts;
+ }
+
+ chan_counts_add(&bsc_counts, &bts_counts);
+ }
+
+ chan_counts_diff(&diff, &bsc_gsmnet->chan_counts, &bsc_counts);
+ if (!chan_counts_is_zero(&diff)) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "internal error in overall channel counts, fixing. diff: %s\n",
+ chan_counts_to_str_c(OTC_SELECT, &diff));
+ bsc_gsmnet->chan_counts = bsc_counts;
+ }
+}
diff --git a/src/osmo-bsc/codec_pref.c b/src/osmo-bsc/codec_pref.c
index 8bdfa43aa..454b00bea 100644
--- a/src/osmo-bsc/codec_pref.c
+++ b/src/osmo-bsc/codec_pref.c
@@ -20,11 +20,14 @@
*/
#include <osmocom/core/msgb.h>
+#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+#include <osmocom/netif/rtp.h>
#include <osmocom/gsm/protocol/gsm_08_08.h>
#include <osmocom/gsm/gsm0808_utils.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/codec_pref.h>
#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
/* Determine whether a permitted speech value is specifies a half rate or full
* rate codec */
@@ -58,8 +61,7 @@ static int full_rate_from_perm_spch(bool * full_rate,
return 0;
}
-/* Helper function for match_codec_pref(), looks up a matching chan mode for
- * a given permitted speech value */
+/* Look up a matching chan mode for a given permitted speech value */
static enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech speech)
{
switch (speech) {
@@ -85,8 +87,7 @@ static enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech spe
}
}
-/* Helper function for match_codec_pref(), looks up a matching permitted speech
- * value for a given msc audio codec pref */
+/* Look up a matching permitted speech value for a given msc audio codec pref */
static enum gsm0808_permitted_speech audio_support_to_gsm88(const struct gsm_audio_support *audio)
{
if (audio->hr) {
@@ -122,10 +123,9 @@ static enum gsm0808_permitted_speech audio_support_to_gsm88(const struct gsm_aud
}
}
-/* Helper function for match_codec_pref(), tests if a given audio support
- * matches one of the permitted speech settings of the channel type element.
- * The matched permitted speech value is then also compared against the
- * speech codec list. (optional, only relevant for AoIP) */
+/* Test if a given audio support matches one of the permitted speech settings
+ * of the channel type element. The matched permitted speech value is then also
+ * compared against the speech codec list. (optional, only relevant for AoIP) */
static bool test_codec_pref(const struct gsm0808_speech_codec **sc_match,
const struct gsm0808_speech_codec_list *scl,
const struct gsm0808_channel_type *ct,
@@ -142,8 +142,6 @@ static bool test_codec_pref(const struct gsm0808_speech_codec **sc_match,
* codec list of the channel type element */
for (i = 0; i < ct->perm_spch_len; i++) {
if (ct->perm_spch[i] == perm_spch) {
- LOGP(DCHAN, LOGL_DEBUG, "%s: %s matches gsm0808_channel_type->perm_spch[%d]\n",
- __func__, gsm0808_permitted_speech_name(perm_spch), i);
match = true;
break;
}
@@ -151,35 +149,24 @@ static bool test_codec_pref(const struct gsm0808_speech_codec **sc_match,
/* If we do not have a speech codec list to test against,
* we just exit early (will be always the case in non-AoIP networks) */
- if (!scl || !scl->len) {
- LOGP(DCHAN, LOGL_DEBUG, "%s: there is no gsm0808_speech_codec_list\n",
- __func__);
+ if (!scl || !scl->len)
return match;
- }
/* If we failed to match until here, there is no
* point in testing further */
- if (match == false) {
- LOGP(DCHAN, LOGL_DEBUG, "%s: no match for %s\n",
- __func__, gsm0808_permitted_speech_name(perm_spch));
+ if (match == false)
return false;
- }
/* Extrapolate speech codec data */
rc = gsm0808_speech_codec_from_chan_type(&sc, perm_spch);
if (rc < 0)
return false;
- LOGP(DCHAN, LOGL_DEBUG, "%s: permitted speech = %s corresponds to speech codec type = %s\n",
- __func__, gsm0808_permitted_speech_name(perm_spch), gsm0808_speech_codec_type_name(sc.type));
-
/* Try to find extrapolated speech codec data in
* the speech codec list */
for (i = 0; i < scl->len; i++) {
if (sc.type == scl->codec[i].type) {
*sc_match = &scl->codec[i];
- LOGP(DCHAN, LOGL_DEBUG, "%s: speech_codec_list[%d] matches speech codec type %s\n",
- __func__, i, gsm0808_speech_codec_type_name(sc.type));
return true;
}
}
@@ -187,37 +174,48 @@ static bool test_codec_pref(const struct gsm0808_speech_codec **sc_match,
return false;
}
-/* Helper function to check if the given permitted speech value is supported
- * by the BTS. (vty option bts->codec-support). */
-static bool test_codec_support_bts(const struct gsm_bts *bts, uint8_t perm_spch)
+static bool test_codec_support_bts_rate(const struct gsm_bts *bts, const bool full_rate)
{
+ unsigned int i;
struct gsm_bts_trx *trx;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (i = 0; i < TRX_NR_TS; i++) {
+ switch (trx->ts[i].pchan_from_config) {
+ case GSM_PCHAN_OSMO_DYN:
+ return true;
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (full_rate)
+ return true;
+ break;
+ case GSM_PCHAN_TCH_H:
+ if (!full_rate)
+ return true;
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+
+ return false;
+}
+
+/* Check if the given permitted speech value is supported by the BTS
+ * (vty option bts->codec-support). */
+static bool test_codec_support_bts(const struct gsm_bts *bts, uint8_t perm_spch)
+{
const struct bts_codec_conf *bts_codec = &bts->codec;
- unsigned int i;
bool full_rate;
int rc;
- enum gsm_phys_chan_config pchan;
- bool rate_match = false;
/* Check if the BTS provides a physical channel that matches the
- * bandwith of the desired codec. */
+ * bandwidth of the desired codec. */
rc = full_rate_from_perm_spch(&full_rate, perm_spch);
if (rc < 0)
return false;
- llist_for_each_entry(trx, &bts->trx_list, list) {
- for (i = 0; i < TRX_NR_TS; i++) {
- pchan = trx->ts[i].pchan_from_config;
- if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
- rate_match = true;
- else if (full_rate && pchan == GSM_PCHAN_TCH_F)
- rate_match = true;
- else if (full_rate && pchan == GSM_PCHAN_TCH_F_PDCH)
- rate_match = true;
- else if (!full_rate && pchan == GSM_PCHAN_TCH_H)
- rate_match = true;
- }
- }
- if (!rate_match)
+ if (!test_codec_support_bts_rate(bts, full_rate))
return false;
/* Check codec support */
@@ -227,21 +225,12 @@ static bool test_codec_support_bts(const struct gsm_bts *bts, uint8_t perm_spch)
* selectively disable GSM-RF per BTS via VTY. */
return true;
case GSM0808_PERM_FR2:
- if (bts_codec->efr)
- return true;
- break;
+ return (bool)bts_codec->efr;
case GSM0808_PERM_FR3:
- if (bts_codec->amr)
- return true;
- break;
- case GSM0808_PERM_HR1:
- if (bts_codec->hr)
- return true;
- break;
case GSM0808_PERM_HR3:
- if (bts_codec->amr)
- return true;
- break;
+ return (bool)bts_codec->amr;
+ case GSM0808_PERM_HR1:
+ return (bool)bts_codec->hr;
default:
return false;
}
@@ -263,8 +252,6 @@ static uint16_t gen_bss_supported_amr_s15_s0(const struct bsc_msc_data *msc, con
if (hr) {
amr_cfg_bts = (struct gsm48_multi_rate_conf *)&bts->mr_half.gsm48_ie;
amr_s15_s0_bts = gsm0808_sc_cfg_from_gsm48_mr_cfg(amr_cfg_bts, false);
- LOGP(DCHAN, LOGL_DEBUG, "%s: HR: bts->mr_half.gsm48_ie[1] = 0x%02x ==> amr_cfg_bts=0x%04x\n",
- __func__, bts->mr_half.gsm48_ie[1], amr_s15_s0_bts);
} else {
amr_cfg_bts = (struct gsm48_multi_rate_conf *)&bts->mr_full.gsm48_ie;
amr_s15_s0_bts = gsm0808_sc_cfg_from_gsm48_mr_cfg(amr_cfg_bts, true);
@@ -279,29 +266,67 @@ static uint16_t gen_bss_supported_amr_s15_s0(const struct bsc_msc_data *msc, con
return amr_s15_s0_bts & amr_s15_s0_msc;
}
-/*! Match the codec preferences from local config with a received codec preferences IEs received from the
- * MSC and the BTS' codec configuration.
- * \param[out] chan_mode GSM 04.08 channel mode.
- * \param[out] full_rate true if full-rate.
- * \param[out] s15_s0 codec configuration bits S15-S0 (AMR)
+/* Special handling for AMR rate configuration bits (S15-S0) */
+static int match_amr_s15_s0(struct channel_mode_and_rate *ch_mode_rate, const struct bsc_msc_data *msc, const struct gsm_bts *bts, const struct gsm0808_speech_codec *sc_match, uint8_t perm_spch)
+{
+ uint16_t amr_s15_s0_supported;
+
+ /* Normally the MSC should never try to advertise an AMR codec
+ * configuration that we did not previously advertised as supported.
+ * However, to ensure that no unsupported AMR codec configuration
+ * enters the further processing steps we again lookup what we support
+ * and generate an intersection. All further processing is then done
+ * with this intersection result. At the same time we will make sure
+ * that the intersection contains at least one rate setting. */
+
+ amr_s15_s0_supported = gen_bss_supported_amr_s15_s0(msc, bts, (perm_spch == GSM0808_PERM_HR3));
+
+ /* NOTE: The sc_match pointer points to a speech codec from the speech
+ * codec list that has been communicated with the ASSIGNMENT COMMAND.
+ * However, only AoIP based networks will include a speech codec list
+ * into the ASSIGNMENT COMMAND. For non AoIP based networks, no speech
+ * codec (sc_match) will be available, so we will fully rely on the
+ * local configuration for those cases. */
+ if (sc_match)
+ ch_mode_rate->s15_s0 = sc_match->cfg & amr_s15_s0_supported;
+ else
+ ch_mode_rate->s15_s0 = amr_s15_s0_supported;
+
+ /* Prefer "Config-NB-Code = 1" (S1) over all other AMR rates settings.
+ * When S1 is set, the active set will automatically include 12.2k, 7.4k,
+ * 5.9k, 4.75k, in case of HR 12,2k is left out. */
+ if (ch_mode_rate->s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20) {
+ ch_mode_rate->s15_s0 &= 0xff00;
+ ch_mode_rate->s15_s0 |= GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20;
+ }
+
+ /* Make sure at least one rate is set. */
+ if ((ch_mode_rate->s15_s0 & 0x00ff) == 0x0000)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*! Match the codec preferences from local config with codec preference IEs
+ * received from the MSC and the BTS' codec configuration.
+ * \param[out] ch_mode_rate resulting codec and rate information
* \param[in] ct GSM 08.08 channel type received from MSC.
* \param[in] scl GSM 08.08 speech codec list received from MSC (optional).
* \param[in] msc associated msc (current codec settings).
* \param[in] bts associated bts (current codec settings).
+ * \param[in] pref selected rate preference (full, half or none).
* \returns 0 on success, -1 in case no match was found */
-int match_codec_pref(enum gsm48_chan_mode *chan_mode,
- bool *full_rate,
- uint16_t *s15_s0,
+int match_codec_pref(struct channel_mode_and_rate *ch_mode_rate,
const struct gsm0808_channel_type *ct,
const struct gsm0808_speech_codec_list *scl,
const struct bsc_msc_data *msc,
- const struct gsm_bts *bts)
+ const struct gsm_bts *bts, enum rate_pref rate_pref)
{
unsigned int i;
uint8_t perm_spch;
+ bool full_rate;
bool match = false;
const struct gsm0808_speech_codec *sc_match = NULL;
- uint16_t amr_s15_s0_supported;
int rc;
/* Note: Normally the MSC should never try to advertise a codec that
@@ -310,84 +335,56 @@ int match_codec_pref(enum gsm48_chan_mode *chan_mode,
* indeed available with the current BTS and MSC configuration */
for (i = 0; i < msc->audio_length; i++) {
/* Pick a permitted speech value from the global codec configuration list */
- perm_spch = audio_support_to_gsm88(msc->audio_support[i]);
+ perm_spch = audio_support_to_gsm88(&msc->audio_support[i]);
+
+ /* Determine if the result is a half or full rate codec */
+ rc = full_rate_from_perm_spch(&full_rate, perm_spch);
+ if (rc < 0)
+ return -EINVAL;
+ ch_mode_rate->chan_rate = full_rate ? CH_RATE_FULL : CH_RATE_HALF;
+
+ /* If we have a preference for FR or HR in our request, we
+ * discard the potential match */
+ if (rate_pref == RATE_PREF_HR && ch_mode_rate->chan_rate == CH_RATE_FULL)
+ continue;
+ if (rate_pref == RATE_PREF_FR && ch_mode_rate->chan_rate == CH_RATE_HALF)
+ continue;
/* Check this permitted speech value against the BTS specific parameters.
* if the BTS does not support the codec, try the next one */
- if (!test_codec_support_bts(bts, perm_spch)) {
- LOGP(DCHAN, LOGL_DEBUG, "%s: msc->audio_support[%d] = %s: not supported by bts%u\n",
- __func__, i, gsm0808_permitted_speech_name(perm_spch),
- bts->nr);
+ if (!test_codec_support_bts(bts, perm_spch))
continue;
- }
/* Match the permitted speech value against the codec lists that were
* advertised by the MS and the MSC */
- if (test_codec_pref(&sc_match, scl, ct, perm_spch)) {
- LOGP(DCHAN, LOGL_DEBUG, "%s: msc->audio_support[%d] = %s:"
- " matches channel type and speech codec list\n",
- __func__, i, gsm0808_permitted_speech_name(perm_spch));
- match = true;
- break;
+ if (!test_codec_pref(&sc_match, scl, ct, perm_spch)) {
+ continue;
}
- LOGP(DCHAN, LOGL_DEBUG, "%s: msc->audio_support[%d] = %s:"
- " does not matches channel type and/or speech codec list\n",
- __func__, i, gsm0808_permitted_speech_name(perm_spch));
+
+ /* Special handling for AMR */
+ if (perm_spch == GSM0808_PERM_HR3 || perm_spch == GSM0808_PERM_FR3) {
+ rc = match_amr_s15_s0(ch_mode_rate, msc, bts, sc_match, perm_spch);
+ if (rc < 0) {
+ ch_mode_rate->s15_s0 = 0;
+ continue;
+ }
+ } else
+ ch_mode_rate->s15_s0 = 0;
+
+ match = true;
+ break;
}
- /* Exit without result, in case no match can be deteched */
+ /* Exit without result, in case no match can be detected */
if (!match) {
- *full_rate = false;
- *chan_mode = GSM48_CMODE_SIGN;
- *s15_s0 = 0;
+ ch_mode_rate->chan_mode = GSM48_CMODE_SIGN;
+ ch_mode_rate->chan_rate = CH_RATE_SDCCH;
+ ch_mode_rate->s15_s0 = 0;
return -1;
}
- /* Determine if the result is a half or full rate codec */
- rc = full_rate_from_perm_spch(full_rate, perm_spch);
- if (rc < 0)
- return -EINVAL;
-
/* Lookup a channel mode for the selected codec */
- *chan_mode = gsm88_to_chan_mode(perm_spch);
-
- LOGP(DCHAN, LOGL_DEBUG, "%s: perm_spch=%s corresponds to chan_mode=%s\n",
- __func__,
- gsm0808_permitted_speech_name(perm_spch),
- gsm48_chan_mode_name(*chan_mode));
-
- /* Special handling for AMR */
- if (perm_spch == GSM0808_PERM_HR3 || perm_spch == GSM0808_PERM_FR3) {
- LOGP(DCHAN, LOGL_DEBUG, "%s: perm_spch=%s is AMR\n",
- __func__,
- gsm0808_permitted_speech_name(perm_spch));
- /* Normally the MSC should never try to advertise an AMR codec
- * configuration that we did not previously advertise as
- * supported. However, to ensure that no unsupported AMR codec
- * configuration enters the further processing steps we again
- * lookup what we support and generate an intersection. All
- * further processing is then done with this intersection
- * result */
- amr_s15_s0_supported = gen_bss_supported_amr_s15_s0(msc, bts, (perm_spch == GSM0808_PERM_HR3));
- if (sc_match) {
- *s15_s0 = sc_match->cfg & amr_s15_s0_supported;
- LOGP(DCHAN, LOGL_DEBUG, "%s: sc_match=%s & amr_s15_s0_supported=0x%04x = 0x%04x\n",
- __func__,
- gsm0808_speech_codec_type_name(sc_match->type), amr_s15_s0_supported, *s15_s0);
- } else {
- *s15_s0 = amr_s15_s0_supported;
- LOGP(DCHAN, LOGL_DEBUG, "%s: amr_s15_s0_supported=0x%04x\n", __func__, *s15_s0);
- }
-
- /* NOTE: The function test_codec_pref() will populate the
- * sc_match pointer from the searched speech codec list. For
- * AoIP based networks, no speech codec list will be present
- * and therefore no sc_match will be populated. For those
- * cases only the local configuration will influence s15_s0.
- * However s15_s0 is always populated with a meaningful value,
- * regardless if AoIP is in use or not. */
- } else
- *s15_s0 = 0;
+ ch_mode_rate->chan_mode = gsm88_to_chan_mode(perm_spch);
return 0;
}
@@ -409,7 +406,7 @@ void gen_bss_supported_codec_list(struct gsm0808_speech_codec_list *scl,
for (i = 0; i < msc->audio_length; i++) {
/* Pick a permitted speech value from the global codec configuration list */
- perm_spch = audio_support_to_gsm88(msc->audio_support[i]);
+ perm_spch = audio_support_to_gsm88(&msc->audio_support[i]);
/* Check this permitted speech value against the BTS specific parameters.
* if the BTS does not support the codec, try the next one */
@@ -424,8 +421,8 @@ void gen_bss_supported_codec_list(struct gsm0808_speech_codec_list *scl,
/* AMR (HR/FR version 3) is the only codec that requires a codec
* configuration (S0-S15). Determine the current configuration and update
* the cfg flag. */
- if (msc->audio_support[i]->ver == 3)
- scl->codec[scl->len].cfg = gen_bss_supported_amr_s15_s0(msc, bts, msc->audio_support[i]->hr);
+ if (msc->audio_support[i].ver == 3)
+ scl->codec[scl->len].cfg = gen_bss_supported_amr_s15_s0(msc, bts, msc->audio_support[i].hr);
scl->len++;
}
@@ -484,22 +481,28 @@ int check_codec_pref(struct llist_head *mscs)
rc = -1;
}
- bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_full.gsm48_ie;
- rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie);
- if (rc_rate < 0) {
- LOGP(DMSC, LOGL_FATAL,
- "network amr tch-f mode config of BTS %u does not intersect with amr-config of MSC %u\n",
- bts->nr, msc->nr);
- rc = -1;
+ /* Full rate codec check, only if any full rate TS is configured. */
+ if (test_codec_support_bts_rate(bts, true)) {
+ bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_full.gsm48_ie;
+ rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie);
+ if (rc_rate < 0) {
+ LOGP(DMSC, LOGL_FATAL,
+ "network amr tch-f mode config of BTS %u does not intersect with amr-config of MSC %u\n",
+ bts->nr, msc->nr);
+ rc = -1;
+ }
}
- bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_half.gsm48_ie;
- rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie);
- if (rc_rate < 0) {
- LOGP(DMSC, LOGL_FATAL,
- "network amr tch-h mode config of BTS %u does not intersect with amr-config of MSC %u\n",
- bts->nr, msc->nr);
- rc = -1;
+ /* Half rate codec check, only if any half rate TS is configured. */
+ if (test_codec_support_bts_rate(bts, false)) {
+ bts_gsm48_ie = (struct gsm48_multi_rate_conf *)&bts->mr_half.gsm48_ie;
+ rc_rate = calc_amr_rate_intersection(NULL, &msc->amr_conf, bts_gsm48_ie);
+ if (rc_rate < 0) {
+ LOGP(DMSC, LOGL_FATAL,
+ "network amr tch-h mode config of BTS %u does not intersect with amr-config of MSC %u\n",
+ bts->nr, msc->nr);
+ rc = -1;
+ }
}
}
}
diff --git a/src/osmo-bsc/data_rate_pref.c b/src/osmo-bsc/data_rate_pref.c
new file mode 100644
index 000000000..44a733fb2
--- /dev/null
+++ b/src/osmo-bsc/data_rate_pref.c
@@ -0,0 +1,165 @@
+/*
+ * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Oliver Smith
+ *
+ * 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/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/bsc/data_rate_pref.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/lchan.h>
+
+static int gsm0808_data_rate_transp_to_gsm0858(enum gsm0808_data_rate_transp rate)
+{
+ switch (rate) {
+ case GSM0808_DATA_RATE_TRANSP_32k0:
+ return RSL_CMOD_CSD_T_32k0;
+ case GSM0808_DATA_RATE_TRANSP_28k8:
+ return RSL_CMOD_CSD_T_29k0;
+ case GSM0808_DATA_RATE_TRANSP_14k4:
+ return RSL_CMOD_CSD_T_14k4;
+ case GSM0808_DATA_RATE_TRANSP_9k6:
+ return RSL_CMOD_CSD_T_9k6;
+ case GSM0808_DATA_RATE_TRANSP_4k8:
+ return RSL_CMOD_CSD_T_4k8;
+ case GSM0808_DATA_RATE_TRANSP_2k4:
+ return RSL_CMOD_CSD_T_2k4;
+ case GSM0808_DATA_RATE_TRANSP_1k2:
+ return RSL_CMOD_CSD_T_1k2;
+ case GSM0808_DATA_RATE_TRANSP_600:
+ return RSL_CMOD_CSD_T_600;
+ case GSM0808_DATA_RATE_TRANSP_1200_75:
+ return RSL_CMOD_CSD_T_1200_75;
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Unsupported transparent data rate 0x%x\n", rate);
+ return -1;
+ }
+}
+
+static int gsm0808_data_rate_transp_to_gsm0408(enum gsm0808_data_rate_transp rate)
+{
+ switch (rate) {
+ case GSM0808_DATA_RATE_TRANSP_14k4:
+ return GSM48_CMODE_DATA_14k5;
+ case GSM0808_DATA_RATE_TRANSP_9k6:
+ return GSM48_CMODE_DATA_12k0;
+ case GSM0808_DATA_RATE_TRANSP_4k8:
+ return GSM48_CMODE_DATA_6k0;
+ case GSM0808_DATA_RATE_TRANSP_2k4:
+ case GSM0808_DATA_RATE_TRANSP_1k2:
+ case GSM0808_DATA_RATE_TRANSP_600:
+ case GSM0808_DATA_RATE_TRANSP_1200_75:
+ return GSM48_CMODE_DATA_3k6;
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Unsupported transparent data rate 0x%x\n", rate);
+ return -1;
+ }
+}
+
+static int gsm0808_data_rate_non_transp_to_gsm0408(enum gsm0808_data_rate_non_transp rate, bool full_rate)
+{
+ switch (rate) {
+ case GSM0808_DATA_RATE_NON_TRANSP_12000_6000:
+ if (full_rate)
+ return GSM48_CMODE_DATA_12k0;
+ return GSM48_CMODE_DATA_6k0;
+ case GSM0808_DATA_RATE_NON_TRANSP_14k5:
+ return GSM48_CMODE_DATA_14k5;
+ case GSM0808_DATA_RATE_NON_TRANSP_12k0:
+ return GSM48_CMODE_DATA_12k0;
+ case GSM0808_DATA_RATE_NON_TRANSP_6k0:
+ return GSM48_CMODE_DATA_6k0;
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Unsupported non-transparent data rate 0x%x\n", rate);
+ return -1;
+ }
+}
+
+static int gsm0808_data_rate_non_transp_to_gsm0858(enum gsm0808_data_rate_non_transp rate, bool full_rate)
+{
+ switch (rate) {
+ case GSM0808_DATA_RATE_NON_TRANSP_12000_6000:
+ if (full_rate)
+ return RSL_CMOD_CSD_NT_12k0;
+ return RSL_CMOD_CSD_NT_6k0;
+ case GSM0808_DATA_RATE_NON_TRANSP_14k5:
+ return RSL_CMOD_CSD_NT_14k5;
+ case GSM0808_DATA_RATE_NON_TRANSP_12k0:
+ return RSL_CMOD_CSD_NT_12k0;
+ case GSM0808_DATA_RATE_NON_TRANSP_6k0:
+ return RSL_CMOD_CSD_NT_6k0;
+ case GSM0808_DATA_RATE_NON_TRANSP_43k5:
+ return RSL_CMOD_CSD_NT_43k5;
+ case GSM0808_DATA_RATE_NON_TRANSP_29k0:
+ return RSL_CMOD_CSD_NT_28k8;
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Unsupported non-transparent data rate 0x%x\n", rate);
+ return -1;
+ }
+}
+
+static enum gsm48_chan_mode match_non_transp_data_rate(const struct gsm0808_channel_type *ct, bool full_rate)
+{
+ /* FIXME: Handle ct->data_rate_allowed too if it is set. Find the best
+ * match by comparing the preferred ct->data_rate + all allowed
+ * ct->data_rate_allowed against what's most suitable for the BTS. */
+
+ return gsm0808_data_rate_non_transp_to_gsm0858(ct->data_rate, full_rate);
+}
+
+/*! Match the GSM 08.08 channel type received from the MSC to suitable data for
+ * the BTS, the GSM 04.08 channel mode, channel rate (FR/HR) and GSM 08.58
+ * data rate.
+ * \param[out] ch_mode_rate resulting channel rate, channel mode and data rate
+ * \param[in] ct GSM 08.08 channel type received from MSC.
+ * \param[in] full_rate true means FR is preferred, false means HR
+ * \returns 0 on success, -1 in case no match was found */
+int match_data_rate_pref(struct channel_mode_and_rate *ch_mode_rate,
+ const struct gsm0808_channel_type *ct,
+ const bool full_rate)
+{
+ int rc;
+ *ch_mode_rate = (struct channel_mode_and_rate){};
+ ch_mode_rate->chan_rate = full_rate ? CH_RATE_FULL : CH_RATE_HALF;
+ ch_mode_rate->data_transparent = ct->data_transparent;
+
+ if (ct->data_transparent) {
+ rc = gsm0808_data_rate_transp_to_gsm0858(ct->data_rate);
+ if (rc == -1)
+ return -1;
+ ch_mode_rate->data_rate.t = rc;
+
+ rc = gsm0808_data_rate_transp_to_gsm0408(ct->data_rate);
+ if (rc == -1)
+ return -1;
+ ch_mode_rate->chan_mode = rc;
+ } else {
+ rc = match_non_transp_data_rate(ct, full_rate);
+ if (rc == -1)
+ return -1;
+ ch_mode_rate->data_rate.nt = rc;
+
+ rc = gsm0808_data_rate_non_transp_to_gsm0408(ct->data_rate, full_rate);
+ if (rc == -1)
+ return -1;
+ ch_mode_rate->chan_mode = rc;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bsc/e1_config.c b/src/osmo-bsc/e1_config.c
index e7398ed9c..dbea3e9e0 100644
--- a/src/osmo-bsc/e1_config.c
+++ b/src/osmo-bsc/e1_config.c
@@ -30,6 +30,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/bts.h>
#define SAPI_L2ML 0
#define SAPI_OML 62
@@ -72,17 +73,15 @@ int e1_reconfig_trx(struct gsm_bts_trx *trx)
int i;
if (!e1_link->e1_ts) {
- LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link without "
- "timeslot?\n", trx->bts->nr, trx->nr);
+ LOG_TRX(trx, DLINP, LOGL_ERROR, "RSL link without timeslot?\n");
return -EINVAL;
}
/* RSL Link */
line = e1inp_line_find(e1_link->e1_nr);
if (!line) {
- LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link referring "
- "to non-existing E1 line %u\n", trx->bts->nr,
- trx->nr, e1_link->e1_nr);
+ LOG_TRX(trx, DLINP, LOGL_ERROR, "TRX RSL link referring to non-existing E1 line %u\n",
+ e1_link->e1_nr);
return -ENOMEM;
}
sign_ts = &line->ts[e1_link->e1_ts-1];
@@ -91,10 +90,9 @@ int e1_reconfig_trx(struct gsm_bts_trx *trx)
if (trx->bts->type == GSM_BTS_TYPE_RBS2000) {
struct e1inp_sign_link *oml_link;
oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, trx,
- trx->rsl_tei, SAPI_OML);
+ trx->rsl_tei_primary, SAPI_OML);
if (!oml_link) {
- LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) OML link creation "
- "failed\n", trx->bts->nr, trx->nr);
+ LOG_TRX(trx, DLINP, LOGL_ERROR, "TRX OML link creation failed\n");
return -ENOMEM;
}
if (trx->oml_link)
@@ -102,15 +100,14 @@ int e1_reconfig_trx(struct gsm_bts_trx *trx)
trx->oml_link = oml_link;
}
rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
- trx, trx->rsl_tei, SAPI_RSL);
+ trx, trx->rsl_tei_primary, SAPI_RSL);
if (!rsl_link) {
- LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link creation "
- "failed\n", trx->bts->nr, trx->nr);
+ LOG_TRX(trx, DLINP, LOGL_ERROR, "TRX RSL link creation failed\n");
return -ENOMEM;
}
- if (trx->rsl_link)
- e1inp_sign_link_destroy(trx->rsl_link);
- trx->rsl_link = rsl_link;
+ if (trx->rsl_link_primary)
+ e1inp_sign_link_destroy(trx->rsl_link_primary);
+ trx->rsl_link_primary = rsl_link;
for (i = 0; i < TRX_NR_TS; i++)
e1_reconfig_ts(&trx->ts[i]);
@@ -132,8 +129,8 @@ static int bts_isdn_sign_link(struct msgb *msg)
break;
case E1INP_SIGN_RSL:
if (link->trx->mo.nm_state.administrative == NM_STATE_LOCKED) {
- LOGP(DLMI, LOGL_ERROR, "(bts=%d/trx=%d) discarding RSL message received "
- "in locked administrative state\n", link->trx->bts->nr, link->trx->nr);
+ LOG_TRX(link->trx, DLMI, LOGL_ERROR, "discarding RSL message received "
+ "in locked administrative state\n");
msgb_free(msg);
break;
}
@@ -161,17 +158,17 @@ int e1_reconfig_bts(struct gsm_bts *bts)
struct timespec tp;
int rc;
- DEBUGP(DLMI, "e1_reconfig_bts(%u)\n", bts->nr);
+ LOG_BTS(bts, DLMI, LOGL_DEBUG, "e1_reconfig_bts\n");
line = e1inp_line_find(e1_link->e1_nr);
if (!line) {
- LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to "
- "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr);
+ LOG_BTS(bts, DLINP, LOGL_ERROR, "BTS OML link referring to "
+ "non-existing E1 line %u\n", e1_link->e1_nr);
return -ENOMEM;
}
if (!bts->model->e1line_bind_ops) {
- LOGP(DLINP, LOGL_ERROR, "no callback to bind E1 line operations\n");
+ LOG_BTS(bts, DLINP, LOGL_ERROR, "no callback to bind E1 line operations\n");
return -EINVAL;
}
if (!line->ops)
@@ -184,8 +181,7 @@ int e1_reconfig_bts(struct gsm_bts *bts)
/* OML link */
if (!e1_link->e1_ts) {
- LOGP(DLINP, LOGL_ERROR, "BTS %u OML link without timeslot?\n",
- bts->nr);
+ LOG_BTS(bts, DLINP, LOGL_ERROR, "BTS OML link without timeslot?\n");
return -EINVAL;
}
@@ -194,15 +190,14 @@ int e1_reconfig_bts(struct gsm_bts *bts)
oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
bts->c0, bts->oml_tei, SAPI_OML);
if (!oml_link) {
- LOGP(DLINP, LOGL_ERROR, "BTS %u OML link creation failed\n",
- bts->nr);
+ LOG_BTS(bts, DLINP, LOGL_ERROR, "BTS OML link creation failed\n");
return -ENOMEM;
}
if (bts->oml_link)
e1inp_sign_link_destroy(bts->oml_link);
bts->oml_link = oml_link;
- rc = clock_gettime(CLOCK_MONOTONIC, &tp);
- bts->uptime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */
+ rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
+ bts->updowntime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */
llist_for_each_entry(trx, &bts->trx_list, list)
e1_reconfig_trx(trx);
diff --git a/src/osmo-bsc/gsm_04_08_rr.c b/src/osmo-bsc/gsm_04_08_rr.c
index f1061ef4d..194432125 100644
--- a/src/osmo-bsc/gsm_04_08_rr.c
+++ b/src/osmo-bsc/gsm_04_08_rr.c
@@ -28,7 +28,9 @@
#include <netinet/in.h>
#include <osmocom/core/msgb.h>
+#include <osmocom/core/bitvec.h>
#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/sysinfo.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/debug.h>
@@ -40,20 +42,30 @@
#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/gsm_08_08.h>
-
-/* should ip.access BTS use direct RTP streams between each other (1),
- * or should OpenBSC always act as RTP relay/proxy in between (0) ? */
-int ipacc_rtp_direct = 1;
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/lchan.h>
int gsm48_sendmsg(struct msgb *msg)
{
if (msg->lchan)
- msg->dst = msg->lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(msg->lchan);
msg->l3h = msg->data;
return rsl_data_request(msg, 0);
}
+int gsm48_sendmsg_unit(struct msgb *msg)
+{
+ if (msg->lchan)
+ msg->dst = rsl_chan_link(msg->lchan);
+
+ msg->l3h = msg->data;
+ return rsl_unit_data_request(msg, 0);
+}
+
/* Section 9.1.8 / Table 9.9 */
struct chreq {
uint8_t val;
@@ -229,32 +241,117 @@ int get_reason_by_chreq(uint8_t ra, int neci)
return GSM_CHREQ_REASON_OTHER;
}
-static void mr_config_for_ms(struct gsm_lchan *lchan, struct msgb *msg)
+static int put_mr_config_for_ms(struct msgb *msg, const struct gsm48_multi_rate_conf *mr_conf_filtered,
+ const struct amr_multirate_conf *mr_modes)
{
- if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
- msgb_tlv_put(msg, GSM48_IE_MUL_RATE_CFG, lchan->mr_ms_lv[0],
- lchan->mr_ms_lv + 1);
+ msgb_put_u8(msg, GSM48_IE_MUL_RATE_CFG);
+ return gsm48_multirate_config(msg, mr_conf_filtered, mr_modes->ms_mode, mr_modes->num_modes);
}
+#define CELL_SEL_IND_AFTER_REL_EARCFN_ENTRY (1+16+4+1+1)
+#define CELL_SEL_IND_AFTER_REL_MAX_BITS (3+MAX_EARFCN_LIST*CELL_SEL_IND_AFTER_REL_EARCFN_ENTRY+1)
+#define CELL_SEL_IND_AFTER_REL_MAX_BYTES OSMO_BYTES_FOR_BITS(CELL_SEL_IND_AFTER_REL_MAX_BITS)
+
+/* Generate a CSN.1 encoded "Cell Selection Indicator after release of all TCH and SDCCH"
+ * as per TS 44.018 version 15.3.0 Table 10.5.2.1e.1. This only generates the "value"
+ * part of the IE, not the tag+length wrapper */
+static int generate_cell_sel_ind_after_rel(uint8_t *out, unsigned int out_len, const struct gsm_bts *bts)
+{
+ struct bitvec bv;
+ unsigned int i, rc;
+
+ bv.data = out;
+ bv.data_len = out_len;
+ bitvec_zero(&bv);
+
+ /* E-UTRAN Description */
+ bitvec_set_uint(&bv, 3, 3);
+
+ for (i = 0; i < MAX_EARFCN_LIST; i++) {
+ const struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+ if (e->arfcn[i] == OSMO_EARFCN_INVALID)
+ continue;
+
+ /* tailroom must fit one more EARFCN plus the final list term bit. */
+ if (bitvec_tailroom_bits(&bv) < CELL_SEL_IND_AFTER_REL_EARCFN_ENTRY + 1) {
+ LOGP(DRR, LOGL_NOTICE, "%s: Not enough room to store EARFCN %u in the "
+ "Cell Selection Indicator IE\n", gsm_bts_name(bts), e->arfcn[i]);
+ } else {
+ bitvec_set_bit(&bv, 1);
+ bitvec_set_uint(&bv, e->arfcn[i], 16);
+
+ /* Measurement Bandwidth: 9.1.54 */
+ if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i])
+ bitvec_set_bit(&bv, 0);
+ else {
+ bitvec_set_bit(&bv, 1);
+ bitvec_set_uint(&bv, e->meas_bw[i], 3);
+ }
+ /* No "Not Allowed Cells" */
+ bitvec_set_bit(&bv, 0);
+ /* No "TARGET_PCID" */
+ bitvec_set_bit(&bv, 0);
+ }
+ }
+
+ /* list term */
+ bitvec_set_bit(&bv, 0);
+
+ rc = bitvec_used_bytes(&bv);
+
+ if (rc == 1) {
+ /* only the header was written to the bitvec, no actual EARFCNs were present */
+ return 0;
+ } else {
+ /* return the number of bytes used */
+ return rc;
+ }
+}
+
+#define REPEAT_RR_RELEASE_UI 3
+
/* 7.1.7 and 9.1.7: RR CHANnel RELease */
-int gsm48_send_rr_release(struct gsm_lchan *lchan)
+int gsm48_send_rr_release(struct gsm_lchan *lchan, bool ui)
{
- struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RR REL");
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RR REL"), *msgc;
struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
uint8_t *cause;
+ int n;
msg->lchan = lchan;
gh->proto_discr = GSM48_PDISC_RR;
gh->msg_type = GSM48_MT_RR_CHAN_REL;
cause = msgb_put(msg, 1);
- cause[0] = GSM48_RR_CAUSE_NORMAL;
+ cause[0] = lchan->release.rr_cause;
+
+ if (lchan->release.last_eutran_plmn_valid) {
+ uint8_t buf[CELL_SEL_IND_AFTER_REL_MAX_BYTES];
+ int len;
+ /* FIXME: so far we assume all configured neigbhors match last_eutran_plmn */
+ len = generate_cell_sel_ind_after_rel(buf, sizeof(buf), lchan->ts->trx->bts);
+ if (len == 0) {
+ LOGPLCHAN(lchan, DRR, LOGL_NOTICE, "MSC indicated CSFB Fast Return, but "
+ "BTS has no EARFCN configured!\n");
+ } else
+ msgb_tlv_put(msg, GSM48_IE_CELL_SEL_IND_AFTER_REL, len, buf);
+ }
- DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d\n",
- lchan->nr, lchan->type);
+ DEBUGP(DRR, "%s Tx Channel Release (cause=0x%02x '%s')\n",
+ gsm_lchan_name(lchan), lchan->release.rr_cause,
+ rr_cause_name(lchan->release.rr_cause));
- /* Send actual release request to MS */
- return gsm48_sendmsg(msg);
+ /* Send actual release request to MS (dedicated channel) */
+ if (!ui)
+ return gsm48_sendmsg(msg);
+
+ /* Send actual release request to MS (VGCS channel) */
+ for (n = 1; n < REPEAT_RR_RELEASE_UI; n++) {
+ msgc = msgb_copy(msg, "CHAN RELEASE copy");
+ msgc->lchan = lchan;
+ gsm48_sendmsg_unit(msgc);
+ }
+ return gsm48_sendmsg_unit(msg);
}
int send_siemens_mrpci(struct gsm_lchan *lchan,
@@ -283,7 +380,8 @@ int gsm48_send_rr_classmark_enquiry(struct gsm_lchan *lchan)
gh->proto_discr = GSM48_PDISC_RR;
gh->msg_type = GSM48_MT_RR_CLSM_ENQ;
- DEBUGP(DRR, "%s TX CLASSMARK ENQUIRY %u\n", gsm_lchan_name(lchan), msgb_length(msg));
+ DEBUGP(DRR, "%s Tx CLASSMARK ENQUIRY (len=%u)\n",
+ gsm_lchan_name(lchan), msgb_length(msg));
return gsm48_sendmsg(msg);
}
@@ -293,16 +391,14 @@ int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv)
{
struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CIPH");
struct gsm48_hdr *gh;
- uint8_t ciph_mod_set;
+ uint8_t ciph_mod_set = 0x00;
msg->lchan = lchan;
- DEBUGP(DRR, "TX CIPHERING MODE CMD\n");
+ DEBUGP(DRR, "%s Tx CIPHERING MODE CMD\n", gsm_lchan_name(lchan));
- if (lchan->encr.alg_id <= RSL_ENC_ALG_A5(0))
- ciph_mod_set = 0;
- else
- ciph_mod_set = (lchan->encr.alg_id-2)<<1 | 1;
+ if (lchan->encr.alg_a5_n > 0)
+ ciph_mod_set = (lchan->encr.alg_a5_n - 1) << 1 | 0x01;
gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
gh->proto_discr = GSM48_PDISC_RR;
@@ -322,12 +418,12 @@ static void gsm48_cell_desc(struct gsm48_cell_desc *cd,
}
/*! \brief Encode a TS 04.08 multirate config LV according to 10.5.2.21aa.
- * \param[out] lv caller-allocated buffer of 7 bytes. First octet is is length.
+ * \param[out] msg msgb to append to.
* \param[in] mr_conf multi-rate configuration to encode (selected modes).
* \param[in] modes array describing the AMR modes.
* \param[in] num_modes length of the modes array.
* \returns 0 on success, -EINVAL on failure. */
-int gsm48_multirate_config(uint8_t *lv,
+int gsm48_multirate_config(struct msgb *msg,
const struct gsm48_multi_rate_conf *mr_conf,
const struct amr_mode *modes, unsigned int num_modes)
{
@@ -338,17 +434,19 @@ int gsm48_multirate_config(uint8_t *lv,
bool mode_valid;
uint8_t *gsm48_ie = (uint8_t *) mr_conf;
const struct amr_mode *modes_selected[4];
+ uint8_t *len;
+ uint8_t *data;
/* Check if modes for consistency (order and duplicates) */
- for (i = 0; i < num_modes; i++) {
- if (i > 0 && modes[i - 1].mode > modes[i].mode) {
+ for (i = 1; i < num_modes; i++) {
+ if (modes[i - 1].mode > modes[i].mode) {
LOGP(DRR, LOGL_ERROR,
- "BUG: Multirate codec with inconsistant config (mode order).\n");
+ "BUG: Multirate codec with inconsistent config (mode order).\n");
return -EINVAL;
}
- if (i > 0 && modes[i - 1].mode == modes[i].mode) {
+ if (modes[i - 1].mode == modes[i].mode) {
LOGP(DRR, LOGL_ERROR,
- "BUG: Multirate codec with inconsistant config (duplicate modes).\n");
+ "BUG: Multirate codec with inconsistent config (duplicate modes).\n");
return -EINVAL;
}
}
@@ -398,7 +496,7 @@ int gsm48_multirate_config(uint8_t *lv,
}
if (!mode_valid) {
LOGP(DRR, LOGL_ERROR,
- "BUG: Multirate codec with inconsistant config (no mode defined).\n");
+ "BUG: Multirate codec with inconsistent config (no mode defined).\n");
return -EINVAL;
}
}
@@ -407,99 +505,148 @@ int gsm48_multirate_config(uint8_t *lv,
/* When the caller is not interested in any result, skip the actual
* composition of the IE (dry run) */
- if (!lv)
+ if (!msg)
return 0;
/* Compose output buffer */
- lv[0] = (num == 1) ? 2 : (num + 2);
- memcpy(lv + 1, gsm48_ie, 2);
+ /* length */
+ len = msgb_put(msg, 1);
+
+ /* Write octet 3 (Multirate speech version, NSCB, ICMI, spare, Start mode)
+ * and octet 4 (Set of AMR codec modes) */
+ data = msgb_put(msg, 2);
+ memcpy(data, gsm48_ie, 2);
if (num == 1)
- return 0;
+ goto return_msg;
- lv[3] = modes_selected[0]->threshold & 0x3f;
- lv[4] = modes_selected[0]->hysteresis << 4;
+ /* more than 1 mode: write octet 5 and 6: threshold 1 and hysteresis 1 */
+ data = msgb_put(msg, 2);
+ data[0] = modes_selected[0]->threshold & 0x3f;
+ data[1] = modes_selected[0]->hysteresis << 4;
if (num == 2)
- return 0;
- lv[4] |= (modes_selected[1]->threshold & 0x3f) >> 2;
- lv[5] = modes_selected[1]->threshold << 6;
- lv[5] |= (modes_selected[1]->hysteresis & 0x0f) << 2;
+ goto return_msg;
+
+ /* more than 2 modes: complete octet 6 and add octet 7: threshold 2 and hysteresis 2.
+ * Threshold 2 starts in octet 6. */
+ data[1] |= (modes_selected[1]->threshold & 0x3f) >> 2;
+ /* octet 7 */
+ data = msgb_put(msg, 1);
+ data[0] = modes_selected[1]->threshold << 6;
+ data[0] |= (modes_selected[1]->hysteresis & 0x0f) << 2;
if (num == 3)
- return 0;
- lv[5] |= (modes_selected[2]->threshold & 0x3f) >> 4;
- lv[6] = modes_selected[2]->threshold << 4;
- lv[6] |= modes_selected[2]->hysteresis & 0x0f;
-
+ goto return_msg;
+
+ /* four modes: complete octet 7 and add octet 8: threshold 3 and hysteresis 3.
+ * Threshold 3 starts in octet 7. */
+ data[0] |= (modes_selected[2]->threshold & 0x3f) >> 4;
+ /* octet 8 */
+ data = msgb_put(msg, 1);
+ data[0] = modes_selected[2]->threshold << 4;
+ data[0] |= modes_selected[2]->hysteresis & 0x0f;
+
+return_msg:
+ /* Place written len in the IE length field. msg->tail points one byte after the last data octet, len points at
+ * the L octet of the TLV. */
+ *len = (msg->tail - 1) - len;
return 0;
}
#define GSM48_HOCMD_CCHDESC_LEN 16
/* Chapter 9.1.15: Handover Command */
-struct msgb *gsm48_make_ho_cmd(struct gsm_lchan *new_lchan, uint8_t power_command, uint8_t ho_ref)
+struct msgb *gsm48_make_ho_cmd(const struct gsm_lchan *new_lchan,
+ enum handover_scope ho_scope, bool async,
+ uint8_t power_command, uint8_t ho_ref)
{
struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 HO CMD");
struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
struct gsm48_ho_cmd *ho =
(struct gsm48_ho_cmd *) msgb_put(msg, sizeof(*ho));
+ const struct gsm_bts *bts = new_lchan->ts->trx->bts;
gh->proto_discr = GSM48_PDISC_RR;
gh->msg_type = GSM48_MT_RR_HANDO_CMD;
/* mandatory bits */
- gsm48_cell_desc(&ho->cell_desc, new_lchan->ts->trx->bts);
- gsm48_lchan2chan_desc(&ho->chan_desc, new_lchan);
+ gsm48_cell_desc(&ho->cell_desc, bts);
+ if (gsm48_lchan2chan_desc(&ho->chan_desc, new_lchan, gsm_ts_tsc(new_lchan->ts), false)) {
+ msgb_free(msg);
+ return NULL;
+ }
ho->ho_ref = ho_ref;
ho->power_command = power_command;
+ /* Synchronization Indication, TV (see 3GPP TS 44.018, 9.1.15.1).
+ * In the case of inter-RAT handover, always include this IE for the sake of
+ * explicitness. In the case of intra-RAT handover, include this IE only for
+ * the synchronized handover. If omitted, non-synchronized handover is assumed. */
+ if (!async || (ho_scope & HO_INTER_BSC_IN)) {
+ /* Only the SI field (Non-synchronized/Synchronized) is present.
+ * TODO: ROT (Report Observed Time Difference), currently 0.
+ * TODO: NCI (Normal cell indication), currently 0. */
+ const uint8_t sync_ind = async ? 0x00 : 0x01;
+ /* T (4 bit) + V (4 bit), see 3GPP TS 44.018, 10.5.2.39 */
+ msgb_v_put(msg, (GSM48_IE_SYNC_IND_HO << 4) | (sync_ind & 0x0f));
+ }
+
if (new_lchan->ts->hopping.enabled) {
- struct gsm_bts *bts = new_lchan->ts->trx->bts;
struct gsm48_system_information_type_1 *si1;
- uint8_t *cur;
si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1);
/* Copy the Cell Chan Desc (ARFCNS in this cell) */
- msgb_put_u8(msg, GSM48_IE_CELL_CH_DESC);
- cur = msgb_put(msg, GSM48_HOCMD_CCHDESC_LEN);
- memcpy(cur, si1->cell_channel_description,
- GSM48_HOCMD_CCHDESC_LEN);
- /* Copy the Mobile Allocation */
- msgb_tlv_put(msg, GSM48_IE_MA_BEFORE,
+ msgb_tv_fixed_put(msg, GSM48_IE_CELL_CH_DESC,
+ GSM48_HOCMD_CCHDESC_LEN,
+ si1->cell_channel_description);
+ }
+
+ msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->current_ch_mode_rate.chan_mode);
+
+ /* Mobile Allocation (after time), TLV (see 3GPP TS 44.018, 10.5.2.21) */
+ if (new_lchan->ts->hopping.enabled) {
+ msgb_tlv_put(msg, GSM48_IE_MA_AFTER,
new_lchan->ts->hopping.ma_len,
new_lchan->ts->hopping.ma_data);
}
- /* FIXME: optional bits for type of synchronization? */
- msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->tch_mode);
+ /* (O) Cipher Mode Setting, TV (see 3GPP TS 44.018, 9.1.15.10).
+ * Omitted in the case of intra-RAT (GERAN-to-GERAN) handover.
+ * Shall be included in the case of inter-RAT handover. */
+ if (ho_scope & HO_INTER_BSC_IN) {
+ uint8_t cms = 0x00;
+ if (new_lchan->encr.alg_a5_n > 0)
+ cms = (new_lchan->encr.alg_a5_n - 1) << 1 | 1;
+ /* T (4 bit) + V (4 bit), see 3GPP TS 44.018, 10.5.2.9 */
+ msgb_v_put(msg, (GSM48_IE_CIP_MODE_SET_HO << 4) | (cms & 0x0f));
+ }
/* in case of multi rate we need to attach a config */
- if (new_lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
- msgb_tlv_put(msg, GSM48_IE_MUL_RATE_CFG, new_lchan->mr_ms_lv[0],
- new_lchan->mr_ms_lv + 1);
+ if (gsm48_chan_mode_to_non_vamos(new_lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) {
+ if (put_mr_config_for_ms(msg, &new_lchan->current_mr_conf,
+ (new_lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half)) {
+ LOG_LCHAN(new_lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n");
+ msgb_free(msg);
+ return NULL;
+ }
+ }
return msg;
}
-int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan,
- uint8_t power_command, uint8_t ho_ref)
-{
- struct msgb *msg = gsm48_make_ho_cmd(new_lchan, power_command, ho_ref);
- if (!msg)
- return -EINVAL;
- msg->lchan = old_lchan;
- return gsm48_sendmsg(msg);
-}
-
/* Chapter 9.1.2: Assignment Command */
-int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, uint8_t power_command)
+int gsm48_send_rr_ass_cmd(struct gsm_lchan *current_lchan, struct gsm_lchan *new_lchan, uint8_t power_command)
{
+ int rc;
struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ASS CMD");
struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
struct gsm48_ass_cmd *ass =
(struct gsm48_ass_cmd *) msgb_put(msg, sizeof(*ass));
+ struct gsm_bts *bts = new_lchan->ts->trx->bts;
- DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode);
+ DEBUGP(DRR, "%s Tx ASSIGNMENT COMMAND (tch_mode=0x%02x)\n",
+ gsm_lchan_name(current_lchan),
+ new_lchan->current_ch_mode_rate.chan_mode);
- msg->lchan = dest_lchan;
+ msg->lchan = current_lchan;
gh->proto_discr = GSM48_PDISC_RR;
gh->msg_type = GSM48_MT_RR_ASS_CMD;
@@ -511,21 +658,67 @@ int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan,
* the chan_desc. But as long as multi-slot configurations
* are not used we seem to be fine.
*/
- gsm48_lchan2chan_desc(&ass->chan_desc, lchan);
+ rc = gsm48_lchan2chan_desc(&ass->chan_desc, new_lchan, new_lchan->tsc, false);
+ if (rc) {
+ msgb_free(msg);
+ return rc;
+ }
ass->power_command = power_command;
- /* optional: cell channel description */
+ /* Cell Channel Description (freq. hopping), TV (see 3GPP TS 44.018, 10.5.2.1b) */
+ if (new_lchan->ts->hopping.enabled) {
+ const struct gsm48_system_information_type_1 *si1 = GSM_BTS_SI(bts, 1);
+ msgb_tv_fixed_put(msg, GSM48_IE_CELL_CH_DESC,
+ sizeof(si1->cell_channel_description),
+ si1->cell_channel_description);
+ }
- msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode);
+ msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->current_ch_mode_rate.chan_mode);
- /* mobile allocation in case of hopping */
- if (lchan->ts->hopping.enabled) {
- msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, lchan->ts->hopping.ma_len,
- lchan->ts->hopping.ma_data);
+ /* Mobile Allocation (freq. hopping), TLV (see 3GPP TS 44.018, 10.5.2.21) */
+ if (new_lchan->ts->hopping.enabled) {
+ msgb_tlv_put(msg, GSM48_IE_MA_AFTER, new_lchan->ts->hopping.ma_len,
+ new_lchan->ts->hopping.ma_data);
}
/* in case of multi rate we need to attach a config */
- mr_config_for_ms(lchan, msg);
+ if (gsm48_chan_mode_to_non_vamos(new_lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) {
+ int rc = put_mr_config_for_ms(msg, &new_lchan->current_mr_conf,
+ (new_lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half);
+ if (rc) {
+ LOG_LCHAN(current_lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n");
+ msgb_free(msg);
+ return rc;
+ }
+ }
+
+ /* For VAMOS, include the TSC Set number in the Extended TSC Set IE.
+ * We don't put any PS domain related values, only the lowest two CS domain bits.
+ * Convert from spec conforming "human readable" TSC Set 1-4 to 0-3 on the wire. */
+ if (new_lchan->vamos.enabled && new_lchan->tsc_set > 0)
+ msgb_tv_put(msg, GSM48_IE_EXTENDED_TSC_SET, new_lchan->tsc_set - 1);
+
+ return gsm48_sendmsg(msg);
+}
+
+/* TS 44.018 section 9.1.53 */
+int gsm48_send_rr_app_info(struct gsm_lchan *lchan, uint8_t apdu_id, uint8_t apdu_flags,
+ const uint8_t *apdu_data, ssize_t apdu_data_len)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 APP INFO");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ if ((apdu_id & 0xF0) || (apdu_flags & 0xF0)) {
+ msgb_free(msg);
+ return -EINVAL;
+ }
+
+ msg->lchan = lchan;
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_APP_INFO;
+
+ msgb_put_u8(msg, (apdu_flags << 4) | apdu_id);
+ msgb_lv_put(msg, apdu_data_len, apdu_data);
return gsm48_sendmsg(msg);
}
@@ -533,32 +726,109 @@ int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan,
/* 9.1.5 Channel mode modify: Modify the mode on the MS side */
int gsm48_lchan_modify(struct gsm_lchan *lchan, uint8_t mode)
{
+ int rc;
struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CHN MOD");
struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
struct gsm48_chan_mode_modify *cmm =
(struct gsm48_chan_mode_modify *) msgb_put(msg, sizeof(*cmm));
+ struct gsm_bts *bts = lchan->ts->trx->bts;
- DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", mode);
+ DEBUGP(DRR, "%s Tx CHANNEL MODE MODIFY (mode=0x%02x)\n",
+ gsm_lchan_name(lchan), mode);
- lchan->tch_mode = mode;
msg->lchan = lchan;
gh->proto_discr = GSM48_PDISC_RR;
gh->msg_type = GSM48_MT_RR_CHAN_MODE_MODIF;
- /* fill the channel information element, this code
- * should probably be shared with rsl_rx_chan_rqd() */
- gsm48_lchan2chan_desc(&cmm->chan_desc, lchan);
+ rc = gsm48_lchan2chan_desc(&cmm->chan_desc, lchan, lchan->modify.tsc, false);
+ if (rc) {
+ msgb_free(msg);
+ return rc;
+ }
cmm->mode = mode;
/* in case of multi rate we need to attach a config */
- mr_config_for_ms(lchan, msg);
+ if (gsm48_chan_mode_to_non_vamos(lchan->modify.ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) {
+ int rc = put_mr_config_for_ms(msg, &lchan->modify.mr_conf_filtered,
+ (lchan->type == GSM_LCHAN_TCH_F) ? &bts->mr_full : &bts->mr_half);
+ if (rc) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Cannot encode MultiRate Configuration IE\n");
+ msgb_free(msg);
+ return rc;
+ }
+ }
+
+ if (lchan->modify.info.type_for == LCHAN_TYPE_FOR_VAMOS && lchan->modify.tsc_set > 0) {
+ /* Add the Extended TSC Set IE. So far we only need a TSC Set sent for VAMOS.
+ * Convert from spec conforming "human readable" TSC Set 1-4 to 0-3 on the wire */
+ msgb_tv_put(msg, GSM48_IE_EXTENDED_TSC_SET, (lchan->modify.tsc_set - 1) & 0x3);
+ }
return gsm48_sendmsg(msg);
}
+/* TS 44.018 section 9.1.48 */
+int gsm48_send_uplink_release(struct gsm_lchan *lchan, uint8_t cause)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 UL RELEASE");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ msg->lchan = lchan;
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_UPLINK_RELEASE;
+
+ msgb_put_u8(msg, cause);
+
+ return gsm48_sendmsg(msg);
+}
+
+/* TS 44.018 section 9.1.46 */
+int gsm48_send_uplink_busy(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 UL BUSY");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+ msg->lchan = lchan;
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_UPLINK_BUSY;
+
+ return gsm48_sendmsg_unit(msg);
+}
+
+/* TS 44.018 section 9.1.47 */
+int gsm48_send_uplink_free(struct gsm_lchan *lchan, uint8_t acc_bit, uint8_t *uic)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 UL FREE");
+ struct gsm48_hdr_sh *sh = (struct gsm48_hdr_sh *) msgb_put(msg, sizeof(*sh) + 22);
+ struct bitvec *bv = bitvec_alloc(22, NULL);
+
+ msg->lchan = lchan;
+ sh->rr_short_pd = GSM48_PDISC_SH_RR;
+ sh->msg_type = GSM48_MT_RR_SH_UL_FREE;
+ sh->l2_header = 0;
+
+ /* < Uplink Access Request bit > */
+ bitvec_set_bit(bv, (acc_bit) ? H : L);
+
+ /* { L | H <Uplink Identity Code bit(6) > } */
+ if (uic) {
+ bitvec_set_bit(bv, H);
+ sh->data[0] = 0x40 | *uic;
+ } else
+ bitvec_set_bit(bv, L);
+
+ /* Emergency mode not supported */
+
+ /* Padding the rest with 0x2B and apply to msg. */
+ bitvec_spare_padding(bv, 175);
+ bitvec_pack(bv, sh->data);
+ bitvec_free(bv);
+
+ return gsm48_sendmsg_unit(msg);
+}
+
int gsm48_rx_rr_modif_ack(struct msgb *msg)
{
- int rc;
struct gsm48_hdr *gh = msgb_l3(msg);
struct gsm48_chan_mode_modify *mod =
(struct gsm48_chan_mode_modify *) gh->data;
@@ -566,41 +836,64 @@ int gsm48_rx_rr_modif_ack(struct msgb *msg)
LOG_LCHAN(msg->lchan, LOGL_DEBUG, "CHANNEL MODE MODIFY ACK for %s\n",
gsm48_chan_mode_name(mod->mode));
- if (mod->mode != msg->lchan->tch_mode) {
+ if (mod->mode != msg->lchan->modify.ch_mode_rate.chan_mode) {
LOG_LCHAN(msg->lchan, LOGL_ERROR,
"CHANNEL MODE MODIFY ACK has wrong mode: Wanted: %s Got: %s\n",
- gsm48_chan_mode_name(msg->lchan->tch_mode),
+ gsm48_chan_mode_name(msg->lchan->modify.ch_mode_rate.chan_mode),
gsm48_chan_mode_name(mod->mode));
return -1;
}
- /* update the channel type */
- switch (mod->mode) {
- case GSM48_CMODE_SIGN:
- msg->lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
- break;
- case GSM48_CMODE_SPEECH_V1:
- case GSM48_CMODE_SPEECH_EFR:
- case GSM48_CMODE_SPEECH_AMR:
- msg->lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
- break;
- case GSM48_CMODE_DATA_14k5:
- case GSM48_CMODE_DATA_12k0:
- case GSM48_CMODE_DATA_6k0:
- case GSM48_CMODE_DATA_3k6:
- msg->lchan->rsl_cmode = RSL_CMOD_SPD_DATA;
- break;
+ return 0;
+}
+
+/* Get the ARFCN from the BCCH channel list by the index "BCCH-FREQ-NCELL i"
+ * (idx) as described in 3GPP TS 144.018 ยง 10.5.2.20. The BCCH channel list is
+ * split into two sub lists, each ascendingly ordered by ARFCN > 0:
+ * 1) SI* and SI*bis entries
+ * 2) SI*ter entries (if available)
+ * ARFCN 0 is at the end of each sub list.
+ * Both sub lists are stored in one bitvec (nbv), iterate twice through it. */
+int neigh_list_get_arfcn(struct gsm_bts *bts, const struct bitvec *nbv, unsigned int idx)
+{
+ unsigned int arfcn, i = 0;
+
+ /* First sub list, ARFCN > 0 */
+ for (arfcn = 1; arfcn < nbv->data_len * 8; arfcn++) {
+ if (bitvec_get_bit_pos(nbv, arfcn) == ZERO)
+ continue;
+ /* Skip SI*ter */
+ if (!band_compatible(bts, arfcn))
+ continue;
+ if (i == idx)
+ return arfcn;
+ i++;
}
- /* We've successfully modified the MS side of the channel,
- * now go on to modify the BTS side of the channel */
- rc = rsl_chan_mode_modify_req(msg->lchan);
+ /* First sub list, ARFCN == 0 (last position) */
+ if (bitvec_get_bit_pos(nbv, 0) == ONE && band_compatible(bts, 0)) {
+ if (i == idx)
+ return 0;
+ i++;
+ }
- /* FIXME: we not only need to do this after mode modify, but
- * also after channel activation */
- if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && mod->mode != GSM48_CMODE_SIGN)
- rsl_tx_ipacc_crcx(msg->lchan);
- return rc;
+ /* Second sub list, ARFCN > 0 */
+ for (arfcn = 1; arfcn < nbv->data_len * 8; arfcn++) {
+ if (bitvec_get_bit_pos(nbv, arfcn) == ZERO)
+ continue;
+ /* Require SI*ter */
+ if (band_compatible(bts, arfcn))
+ continue;
+ if (i == idx)
+ return arfcn;
+ i++;
+ }
+
+ /* Second sub list, ARFCN == 0 (last position) */
+ if (bitvec_get_bit_pos(nbv, 0) == ONE && !band_compatible(bts, 0) && i == idx)
+ return 0;
+
+ return -EINVAL;
}
int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
@@ -608,8 +901,9 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
struct gsm48_hdr *gh = msgb_l3(msg);
uint8_t *data = gh->data;
struct gsm_bts *bts = msg->lchan->ts->trx->bts;
- struct bitvec *nbv = &bts->si_common.neigh_list;
+ struct bitvec *nbv;
struct gsm_meas_rep_cell *mrc;
+ int rc;
if (gh->msg_type != GSM48_MT_RR_MEAS_REP)
return -EINVAL;
@@ -633,11 +927,21 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
return 0;
}
+ /* If the phone reports BA-IND 1 this is a report for the SI5* set.
+ * If we have generated SI5* with manual SI5 neighbor list, the measurements refer to it. */
+ if ((rep->flags & MEAS_REP_F_BA1) && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP)
+ nbv = &bts->si_common.si5_neigh_list;
+ else
+ nbv = &bts->si_common.neigh_list;
+
/* an encoding nightmare in perfection */
mrc = &rep->cell[0];
mrc->rxlev = data[3] & 0x3f;
mrc->neigh_idx = data[4] >> 3;
- mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+ rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx);
+ if (rc < 0)
+ goto error;
+ mrc->arfcn = rc;
mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5);
if (rep->num_cell < 2)
return 0;
@@ -645,7 +949,10 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
mrc = &rep->cell[1];
mrc->rxlev = ((data[5] & 0x1f) << 1) | (data[6] >> 7);
mrc->neigh_idx = (data[6] >> 2) & 0x1f;
- mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+ rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx);
+ if (rc < 0)
+ goto error;
+ mrc->arfcn = rc;
mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4);
if (rep->num_cell < 3)
return 0;
@@ -653,7 +960,10 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
mrc = &rep->cell[2];
mrc->rxlev = ((data[7] & 0x0f) << 2) | (data[8] >> 6);
mrc->neigh_idx = (data[8] >> 1) & 0x1f;
- mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+ rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx);
+ if (rc < 0)
+ goto error;
+ mrc->arfcn = rc;
mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3);
if (rep->num_cell < 4)
return 0;
@@ -661,7 +971,10 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
mrc = &rep->cell[3];
mrc->rxlev = ((data[9] & 0x07) << 3) | (data[10] >> 5);
mrc->neigh_idx = data[10] & 0x1f;
- mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+ rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx);
+ if (rc < 0)
+ goto error;
+ mrc->arfcn = rc;
mrc->bsic = data[11] >> 2;
if (rep->num_cell < 5)
return 0;
@@ -669,7 +982,10 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
mrc = &rep->cell[4];
mrc->rxlev = ((data[11] & 0x03) << 4) | (data[12] >> 4);
mrc->neigh_idx = ((data[12] & 0xf) << 1) | (data[13] >> 7);
- mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+ rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx);
+ if (rc < 0)
+ goto error;
+ mrc->arfcn = rc;
mrc->bsic = (data[13] >> 1) & 0x3f;
if (rep->num_cell < 6)
return 0;
@@ -677,10 +993,18 @@ int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg)
mrc = &rep->cell[5];
mrc->rxlev = ((data[13] & 0x01) << 5) | (data[14] >> 3);
mrc->neigh_idx = ((data[14] & 0x07) << 2) | (data[15] >> 6);
- mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1);
+ rc = neigh_list_get_arfcn(bts, nbv, mrc->neigh_idx);
+ if (rc < 0)
+ goto error;
+ mrc->arfcn = rc;
mrc->bsic = data[15] & 0x3f;
return 0;
+
+error:
+ LOGP(DRR, LOGL_ERROR, "Invalid BCCH channel list index %d in measurement report\n", mrc->neigh_idx);
+ rep->num_cell = 0;
+ return 0;
}
/* 9.1.29 RR Status */
@@ -744,30 +1068,6 @@ struct msgb *gsm48_create_loc_upd_rej(uint8_t cause)
return msg;
}
-int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type)
-{
- /* Check the size for the classmark */
- if (length < 1 + *classmark2_lv)
- return -1;
-
- uint8_t *mi_lv = classmark2_lv + *classmark2_lv + 1;
- if (length < 2 + *classmark2_lv + mi_lv[0])
- return -2;
-
- *mi_type = mi_lv[1] & GSM_MI_TYPE_MASK;
- return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv);
-}
-
-int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length,
- char *mi_string, uint8_t *mi_type)
-{
- static const uint32_t classmark_offset =
- offsetof(struct gsm48_pag_resp, classmark2);
- uint8_t *classmark2_lv = (uint8_t *) &resp->classmark2;
- return gsm48_extract_mi(classmark2_lv, length - classmark_offset,
- mi_string, mi_type);
-}
-
/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */
uint64_t str_to_imsi(const char *imsi_str)
{
@@ -876,7 +1176,7 @@ static void dispatch_dtap(struct gsm_subscriber_connection *conn,
osmo_fsm_inst_dispatch(conn->ho.fi, HO_EV_RR_HO_FAIL, msg);
break;
case GSM48_MT_RR_CIPH_M_COMPL:
- bsc_cipher_mode_compl(conn, msg, conn->lchan->encr.alg_id);
+ bsc_cipher_mode_compl(conn, msg, conn->lchan->encr.alg_a5_n);
break;
case GSM48_MT_RR_ASS_COMPL:
if (conn->assignment.fi)
@@ -897,18 +1197,27 @@ static void dispatch_dtap(struct gsm_subscriber_connection *conn,
case GSM48_MT_RR_CHAN_MODE_MODIF_ACK:
rc = gsm48_rx_rr_modif_ack(msg);
if (rc < 0)
- osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_CHAN_MODE_MODIF_ERROR, &rc);
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR, &rc);
else
- osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_CHAN_MODE_MODIF_ACK, msg);
+ osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_RR_CHAN_MODE_MODIFY_ACK, msg);
break;
case GSM48_MT_RR_CLSM_CHG:
handle_classmark_chg(conn, msg);
break;
+ case GSM48_MT_RR_UTRAN_CLSM_CHG:
+ /* TODO: forward to the MSC? */
+ break;
case GSM48_MT_RR_APP_INFO:
/* Passing RR APP INFO to MSC, not quite
* according to spec */
bsc_dtap(conn, link_id, msg);
break;
+ case GSM48_MT_RR_UPLINK_RELEASE:
+ /* When the calling phone releases the uplink before it has been assigned to the group
+ * channel, it will send an UPLINK RELEASE message on the dedicated channel. The MSC
+ * has to take care of it. (assigning the phone to the group channel) */
+ bsc_dtap(conn, link_id, msg);
+ break;
default:
/* Drop unknown RR message */
LOG_LCHAN(msg->lchan, LOGL_NOTICE, "Unknown RR message: %s\n",
@@ -917,6 +1226,27 @@ static void dispatch_dtap(struct gsm_subscriber_connection *conn,
break;
}
break;
+ case GSM48_PDISC_CC:
+ /* Make sure that EMERGENCY CALLS can not be made if the
+ * VTY configuration does not permit. */
+ if (msg_type == GSM48_MT_CC_EMERG_SETUP) {
+ if (msg->lchan->ts->trx->bts->si_common.rach_control.t2 & 0x4) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "MS attempts EMERGENCY SETUP although EMERGENCY CALLS"
+ " are not allowed in sysinfo (cfg: network / bts / rach emergency call allowed 0)\n");
+ lchan_release(msg->lchan, true, true, GSM48_RR_CAUSE_PROT_ERROR_UNSPC,
+ gscon_last_eutran_plmn(msg->lchan->conn));
+ break;
+ }
+ if (!conn->sccp.msc->allow_emerg) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR, "MS attempts EMERGENCY SETUP, but EMERGENCY CALLS are"
+ " denied on MSC %d (cfg: msc %d / allow-emergency deny)\n",
+ conn->sccp.msc->nr, conn->sccp.msc->nr);
+ lchan_release(msg->lchan, true, true, GSM48_RR_CAUSE_PROT_ERROR_UNSPC,
+ gscon_last_eutran_plmn(msg->lchan->conn));
+ break;
+ }
+ }
+ /* fall through */
default:
bsc_dtap(conn, link_id, msg);
break;
@@ -927,12 +1257,11 @@ static void dispatch_dtap(struct gsm_subscriber_connection *conn,
int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id)
{
struct gsm_lchan *lchan;
- int rc;
lchan = msg->lchan;
if (!lchan_may_receive_data(lchan)) {
LOG_LCHAN(msg->lchan, LOGL_INFO, "Got data in non active state, discarding.\n");
- return -1;
+ return 0;
}
if (lchan->conn) {
@@ -940,21 +1269,7 @@ int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id)
* MSC */
dispatch_dtap(lchan->conn, link_id, msg);
} else {
- /* allocate a new connection */
- lchan->conn = bsc_subscr_con_allocate(msg->lchan->ts->trx->bts->network);
- if (!lchan->conn) {
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
- return -1;
- }
- lchan->conn->lchan = lchan;
-
- /* fwd via bsc_api to send COMPLETE L3 INFO to MSC */
- rc = bsc_compl_l3(lchan->conn, msg, 0);
- if (rc < 0) {
- osmo_fsm_inst_dispatch(lchan->conn->fi, GSCON_EV_A_DISC_IND, NULL);
- return rc;
- }
- /* conn shall release lchan on teardown, also if this Layer 3 Complete is rejected. */
+ return bsc_compl_l3(lchan, msg, 0);
}
return 0;
diff --git a/src/osmo-bsc/gsm_04_80_utils.c b/src/osmo-bsc/gsm_04_80_utils.c
deleted file mode 100644
index 8de1262e9..000000000
--- a/src/osmo-bsc/gsm_04_80_utils.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/* OpenBSC utility functions for 3GPP TS 04.80 */
-
-/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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/gsm/gsm0480.h>
-#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
-
-int bsc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level,
- const char *text)
-{
- struct msgb *msg = gsm0480_create_ussd_notify(level, text);
- if (!msg)
- return -1;
- gscon_submit_rsl_dtap(conn, msg, 0, 0);
- return 0;
-}
-
-int bsc_send_ussd_release_complete(struct gsm_subscriber_connection *conn)
-{
- struct msgb *msg = gsm0480_create_ussd_release_complete();
- if (!msg)
- return -1;
- gscon_submit_rsl_dtap(conn, msg, 0, 0);
- return 0;
-}
diff --git a/src/osmo-bsc/gsm_08_08.c b/src/osmo-bsc/gsm_08_08.c
index 2c6a6892a..692e35cea 100644
--- a/src/osmo-bsc/gsm_08_08.c
+++ b/src/osmo-bsc/gsm_08_08.c
@@ -25,17 +25,27 @@
#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/gsm_08_08.h>
#include <osmocom/bsc/codec_pref.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/bsc_stats.h>
-#include <osmocom/bsc/gsm_04_80.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/a_reset.h>
+#include <osmocom/bsc/lcs_ta_req.h>
+#include <osmocom/bsc/lcs_loc_req.h>
+
#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/gsm0808.h>
#include <osmocom/gsm/mncc.h>
#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm23236.h>
#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/bts.h>
+
+#define LOG_COMPL_L3(pdisc, mtype, loglevel, format, args...) \
+ LOGP(DRSL, loglevel, "%s %s: " format, gsm48_pdisc_name(pdisc), gsm48_pdisc_msgtype_name(pdisc, mtype), ##args)
/* Check if we have a proper connection to the MSC */
static bool msc_connected(struct gsm_subscriber_connection *conn)
@@ -55,91 +65,29 @@ static bool msc_connected(struct gsm_subscriber_connection *conn)
return true;
}
-static bool complete_layer3(struct gsm_subscriber_connection *conn,
- struct msgb *msg, struct bsc_msc_data *msc);
-
-static void bsc_maybe_lu_reject(struct gsm_subscriber_connection *conn, int con_type, int cause)
-{
- struct msgb *msg;
-
- /* ignore cm service request or such */
- if (con_type != FLT_CON_TYPE_LU)
- return;
-
- msg = gsm48_create_loc_upd_rej(cause);
- if (!msg) {
- LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
- return;
- }
-
- msg->lchan = conn->lchan;
- gscon_submit_rsl_dtap(conn, msg, 0, 0);
-}
-
-static int bsc_filter_initial(struct osmo_bsc_data *bsc,
- struct bsc_msc_data *msc,
- struct gsm_subscriber_connection *conn,
- struct msgb *msg, char **imsi, int *con_type,
- int *lu_cause)
-{
- struct bsc_filter_request req;
- struct bsc_filter_reject_cause cause;
- struct gsm48_hdr *gh = msgb_l3(msg);
- int rc;
-
- req.ctx = conn;
- req.black_list = NULL;
- req.access_lists = bsc_access_lists();
- req.local_lst_name = msc->acc_lst_name;
- req.global_lst_name = conn_get_bts(conn)->network->bsc_data->acc_lst_name;
- req.bsc_nr = 0;
-
- rc = bsc_msg_filter_initial(gh, msgb_l3len(msg), &req,
- con_type, imsi, &cause);
- *lu_cause = cause.lu_reject_cause;
- return rc;
-}
-
-static int bsc_filter_data(struct gsm_subscriber_connection *conn,
- struct msgb *msg, int *lu_cause)
-{
- struct bsc_filter_request req;
- struct gsm48_hdr *gh = msgb_l3(msg);
- struct bsc_filter_reject_cause cause;
- int rc;
-
- req.ctx = conn;
- req.black_list = NULL;
- req.access_lists = bsc_access_lists();
- req.local_lst_name = conn->sccp.msc->acc_lst_name;
- req.global_lst_name = conn_get_bts(conn)->network->bsc_data->acc_lst_name;
- req.bsc_nr = 0;
-
- rc = bsc_msg_filter_data(gh, msgb_l3len(msg), &req,
- &conn->filter_state,
- &cause);
- *lu_cause = cause.lu_reject_cause;
- return rc;
-}
-
/*! BTS->MSC: tell MSC a SAPI was not established. */
-void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn,
+ uint8_t dlci, enum gsm0808_cause cause)
{
int rc;
struct msgb *resp;
+ struct gsm_bts *bts;
- if (!conn || !msc_connected(conn))
+ if (!msc_connected(conn))
return;
- LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT DLCI=0x%02x\n", dlci);
- resp = gsm0808_create_sapi_reject(dlci);
+ bts = conn_get_bts(conn);
+ LOG_BTS(bts, DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT (dlci=0x%02x, cause='%s')\n",
+ dlci, gsm0808_cause_name(cause));
+ resp = gsm0808_create_sapi_reject_cause(dlci, cause);
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_SAPI_N_REJECT));
rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
if (rc != 0)
msgb_free(resp);
}
/*! MS->MSC: Tell MSC that ciphering has been enabled. */
-void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_encr)
+void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_a5_n)
{
int rc;
struct msgb *resp;
@@ -148,460 +96,467 @@ void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *
return;
LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n");
- resp = gsm0808_create_cipher_complete(msg, chosen_encr);
+ resp = gsm0808_create_cipher_complete(msg, ALG_A5_NR_TO_BSSAP(chosen_a5_n));
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CIPHER_COMPLETE));
rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
if (rc != 0)
msgb_free(resp);
}
-/* 9.2.5 CM service accept */
-int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn)
-{
- struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERV ACK");
- struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
-
- msg->lchan = conn->lchan;
-
- gh->proto_discr = GSM48_PDISC_MM;
- gh->msg_type = GSM48_MT_MM_CM_SERV_ACC;
-
- DEBUGP(DMM, "-> CM SERVICE ACK\n");
-
- gscon_submit_rsl_dtap(conn, msg, 0, 0);
- return 0;
-}
-
-static void bsc_send_ussd_no_srv(struct gsm_subscriber_connection *conn,
- struct msgb *msg, const char *text)
-{
- struct gsm48_hdr *gh;
- int8_t pdisc;
- uint8_t mtype;
- int drop_message = 1;
-
- if (!text)
- return;
-
- if (!msg || msgb_l3len(msg) < sizeof(*gh))
- return;
-
- gh = msgb_l3(msg);
- pdisc = gsm48_hdr_pdisc(gh);
- mtype = gsm48_hdr_msg_type(gh);
-
- /* Is CM service request? */
- if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) {
- struct gsm48_service_request *cm;
-
- cm = (struct gsm48_service_request *) &gh->data[0];
-
- /* Is type SMS or call? */
- if (cm->cm_service_type == GSM48_CMSERV_SMS)
- drop_message = 0;
- else if (cm->cm_service_type == GSM48_CMSERV_MO_CALL_PACKET)
- drop_message = 0;
- }
-
- if (drop_message) {
- LOGP(DMSC, LOGL_DEBUG, "Skipping (not sending) USSD message: '%s'\n", text);
- return;
- }
-
- LOGP(DMSC, LOGL_INFO, "Sending CM Service Accept\n");
- gsm48_tx_mm_serv_ack(conn);
-
- LOGP(DMSC, LOGL_INFO, "Sending USSD message: '%s'\n", text);
- bsc_send_ussd_notify(conn, 1, text);
- bsc_send_ussd_release_complete(conn);
-}
-
-static int is_cm_service_for_emerg(struct msgb *msg)
+static bool is_cm_service_for_emerg(struct msgb *msg)
{
struct gsm48_service_request *cm;
struct gsm48_hdr *gh = msgb_l3(msg);
if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*cm)) {
LOGP(DMSC, LOGL_ERROR, "CM ServiceRequest does not fit.\n");
- return 0;
+ return false;
}
cm = (struct gsm48_service_request *) &gh->data[0];
return cm->cm_service_type == GSM48_CMSERV_EMERGENCY;
}
-/* extract a subscriber from the paging response */
-static struct bsc_subscr *extract_sub(struct gsm_subscriber_connection *conn,
- struct msgb *msg)
+static bool is_lu_from_other_plmn(struct msgb *msg)
{
- uint8_t mi_type;
- char mi_string[GSM48_MI_SIZE];
- struct gsm48_hdr *gh;
- struct gsm48_pag_resp *resp;
- struct bsc_subscr *subscr;
+ const struct gsm48_hdr *gh;
+ int8_t pdisc;
+ uint8_t mtype;
+ const struct gsm48_loc_upd_req *lu;
+ struct osmo_location_area_id old_lai;
- if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*resp)) {
- LOGP(DMSC, LOGL_ERROR, "PagingResponse too small: %u\n", msgb_l3len(msg));
- return NULL;
- }
+ if (msgb_l3len(msg) < sizeof(*gh))
+ return false;
gh = msgb_l3(msg);
- resp = (struct gsm48_pag_resp *) &gh->data[0];
+ pdisc = gsm48_hdr_pdisc(gh);
+ mtype = gsm48_hdr_msg_type(gh);
- gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
- mi_string, &mi_type);
- DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n",
- gsm48_mi_type_name(mi_type), mi_string);
+ switch (pdisc) {
+ case GSM48_PDISC_MM:
- switch (mi_type) {
- case GSM_MI_TYPE_TMSI:
- subscr = bsc_subscr_find_by_tmsi(conn->network->bsc_subscribers,
- tmsi_from_string(mi_string));
- break;
- case GSM_MI_TYPE_IMSI:
- subscr = bsc_subscr_find_by_imsi(conn->network->bsc_subscribers,
- mi_string);
+ switch (mtype) {
+ case GSM48_MT_MM_LOC_UPD_REQUEST:
+ /* First make sure that lu-> can be dereferenced */
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu))
+ return false;
+
+ lu = (struct gsm48_loc_upd_req*)gh->data;
+ gsm48_decode_lai2(&lu->lai, &old_lai);
+
+ if (osmo_plmn_cmp(&old_lai.plmn, &bsc_gsmnet->plmn) != 0)
+ return true;
+ break;
+
+ default:
+ break;
+ }
break;
default:
- subscr = NULL;
break;
}
- return subscr;
+ return false;
}
-struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn,
- struct msgb *msg)
+static bool is_msc_usable(struct bsc_msc_data *msc, bool is_emerg)
{
- struct gsm48_hdr *gh;
- int8_t pdisc;
- uint8_t mtype;
- struct osmo_bsc_data *bsc;
- struct bsc_msc_data *msc, *pag_msc;
- struct bsc_subscr *subscr;
- int is_emerg = 0;
-
- bsc = conn->network->bsc_data;
+ if (is_emerg && !msc->allow_emerg)
+ return false;
+ if (!a_reset_conn_ready(msc))
+ return false;
+ return true;
+}
- if (msgb_l3len(msg) < sizeof(*gh)) {
- LOGP(DMSC, LOGL_ERROR, "There is no GSM48 header here.\n");
- return NULL;
+/* Decide which MSC to forward this Complete Layer 3 request to.
+ * a) If the subscriber was previously paged from a particular MSC, that MSC shall receive the Paging Response.
+ * b) If the message contains an NRI indicating a particular MSC and the MSC is connected, that MSC shall handle this
+ * conn.
+ * c) All other cases distribute the messages across connected MSCs in a round-robin fashion.
+ */
+static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn, const struct osmo_mobile_identity *mi,
+ bool is_emerg, bool from_other_plmn)
+{
+ struct gsm_network *net = conn->network;
+ struct bsc_msc_data *msc;
+ struct bsc_msc_data *msc_target = NULL;
+ struct bsc_msc_data *msc_round_robin_next = NULL;
+ struct bsc_msc_data *msc_round_robin_first = NULL;
+ unsigned int round_robin_next_nr;
+ int16_t nri_v = -1;
+ bool is_null_nri = false;
+
+#define LOG_NRI(LOGLEVEL, FORMAT, ARGS...) \
+ LOGP(DMSC, LOGLEVEL, "%s NRI(%d)=0x%x=%d: " FORMAT, osmo_mobile_identity_to_str_c(OTC_SELECT, mi), \
+ net->nri_bitlen, nri_v, nri_v, ##ARGS)
+
+ /* Extract NRI bits from TMSI, possibly indicating which MSC is responsible */
+ if (mi->type == GSM_MI_TYPE_TMSI) {
+ if (osmo_tmsi_nri_v_get(&nri_v, mi->tmsi, net->nri_bitlen)) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to retrieve NRI from TMSI, nri_bitlen == %u\n", net->nri_bitlen);
+ nri_v = -1;
+ } else if (from_other_plmn) {
+ /* If a subscriber was previously attached to a different PLMN, it might still send the other
+ * PLMN's TMSI identity in an IMSI Attach. The LU sends a LAI indicating the previous PLMN. If
+ * it mismatches our PLMN, ignore the NRI. */
+ LOG_NRI(LOGL_DEBUG,
+ "This LU Request indicates a switch from another PLMN. Ignoring the TMSI's NRI.\n");
+ nri_v = -1;
+ } else {
+ is_null_nri = osmo_nri_v_matches_ranges(nri_v, net->null_nri_ranges);
+ if (is_null_nri)
+ LOG_NRI(LOGL_DEBUG, "this is a NULL-NRI\n");
+ }
}
- gh = msgb_l3(msg);
- pdisc = gsm48_hdr_pdisc(gh);
- mtype = gsm48_hdr_msg_type(gh);
-
- /*
- * We are asked to select a MSC here but they are not equal. We
- * want to respond to a paging request on the MSC where we got the
- * request from. This is where we need to decide where this connection
- * will go.
- */
- if (pdisc == GSM48_PDISC_RR && mtype == GSM48_MT_RR_PAG_RESP)
- goto paging;
- else if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) {
- is_emerg = is_cm_service_for_emerg(msg);
- goto round_robin;
- } else
- goto round_robin;
-
-round_robin:
- llist_for_each_entry(msc, &bsc->mscs, entry) {
- if (!msc->is_authenticated)
- continue;
- if (!is_emerg && msc->type != MSC_CON_TYPE_NORMAL)
- continue;
- if (is_emerg && !msc->allow_emerg)
+ /* Iterate MSCs to find one that matches the extracted NRI, and the next round-robin target for the case no NRI
+ * match is found. */
+ round_robin_next_nr = (is_emerg ? net->mscs_round_robin_next_emerg_nr : net->mscs_round_robin_next_nr);
+ llist_for_each_entry(msc, &net->mscs, entry) {
+ bool nri_matches_msc = (nri_v >= 0 && osmo_nri_v_matches_ranges(nri_v, msc->nri_ranges));
+
+ if (!is_msc_usable(msc, is_emerg)) {
+ if (nri_matches_msc) {
+ LOG_NRI(LOGL_DEBUG, "matches msc %d, but this MSC is currently not connected\n",
+ msc->nr);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_ATTACH_LOST));
+ }
continue;
+ }
- /* force round robin by moving it to the end */
- llist_move_tail(&msc->entry, &bsc->mscs);
- return msc;
- }
-
- return NULL;
+ /* Return MSC if it matches this NRI, with some debug logging. */
+ if (nri_matches_msc) {
+ if (is_null_nri) {
+ LOG_NRI(LOGL_DEBUG, "matches msc %d, but this NRI is also configured as NULL-NRI\n",
+ msc->nr);
+ } else {
+ LOG_NRI(LOGL_DEBUG, "matches msc %d\n", msc->nr);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_KNOWN));
+ if (is_emerg) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_MSCPOOL_EMERG_FORWARDED));
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_EMERG_FORWARDED));
+ }
+ return msc;
+ }
+ }
-paging:
- subscr = extract_sub(conn, msg);
+ /* Figure out the next round-robin MSC. The MSCs may appear unsorted in net->mscs. Make sure to linearly
+ * round robin the MSCs by number: pick the lowest msc->nr >= round_robin_next_nr, and also remember the
+ * lowest available msc->nr to wrap back to that in case no next MSC is left.
+ *
+ * MSCs configured with `no allow-attach` do not accept new subscribers and hence must not be picked by
+ * round-robin. Such an MSC still provides service for already attached subscribers: those that
+ * successfully performed IMSI-Attach and have a TMSI with an NRI pointing at that MSC. We only avoid
+ * adding IMSI-Attach of new subscribers. The idea is that the MSC is in a mode of off-loading
+ * subscribers, and the MSC decides when each subscriber is off-loaded, by assigning the NULL-NRI in a
+ * new TMSI (at the next periodical LU). So until the MSC decides to offload, an attached subscriber
+ * remains attached to that MSC and is free to use its services.
+ */
+ if (!msc->allow_attach)
+ continue;
+ if (!msc_round_robin_first || msc->nr < msc_round_robin_first->nr)
+ msc_round_robin_first = msc;
+ if (msc->nr >= round_robin_next_nr
+ && (!msc_round_robin_next || msc->nr < msc_round_robin_next->nr))
+ msc_round_robin_next = msc;
+ }
- if (!subscr) {
- LOGP(DMSC, LOGL_ERROR, "Got paged but no subscriber found.\n");
+ if (nri_v >= 0 && !is_null_nri)
+ LOG_NRI(LOGL_DEBUG, "No MSC found for this NRI, doing round-robin\n");
+
+ /* No dedicated MSC found. Choose by round-robin.
+ * If msc_round_robin_next is NULL, there are either no more MSCs at/after mscs_round_robin_next_nr, or none of
+ * them are usable -- wrap to the start. */
+ msc_target = msc_round_robin_next ? : msc_round_robin_first;
+ if (!msc_target) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_SUBSCR_NO_MSC));
+ if (is_emerg)
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_EMERG_LOST));
return NULL;
}
- pag_msc = paging_get_msc(conn_get_bts(conn), subscr);
- bsc_subscr_put(subscr);
-
- llist_for_each_entry(msc, &bsc->mscs, entry) {
- if (msc != pag_msc)
- continue;
+ LOGP(DMSC, LOGL_DEBUG, "New subscriber %s: MSC round-robin selects msc %d\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, mi), msc_target->nr);
- /*
- * We don't check if the MSC is connected. In case it
- * is not the connection will be dropped.
- */
+ if (is_null_nri)
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc_target->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_REATTACH));
+ else
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc_target->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_NEW));
- /* force round robin by moving it to the end */
- llist_move_tail(&msc->entry, &bsc->mscs);
- return msc;
+ if (is_emerg) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc_target->msc_ctrs, MSC_CTR_MSCPOOL_EMERG_FORWARDED));
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_EMERG_FORWARDED));
}
- LOGP(DMSC, LOGL_ERROR, "Got paged but no request found.\n");
- return NULL;
+ /* An MSC was picked by round-robin, so update the next round-robin nr to pick */
+ if (is_emerg)
+ net->mscs_round_robin_next_emerg_nr = msc_target->nr + 1;
+ else
+ net->mscs_round_robin_next_nr = msc_target->nr + 1;
+ return msc_target;
+#undef LOG_NRI
}
-/*! MS->MSC: New MM context with L3 payload. */
-int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, uint16_t chosen_channel)
+static void parse_powercap(struct gsm_subscriber_connection *conn, struct msgb *msg)
{
- struct bsc_msc_data *msc;
-
- LOGP(DMSC, LOGL_INFO, "Tx MSC COMPL L3\n");
-
- /* find the MSC link we want to use */
- msc = bsc_find_msc(conn, msg);
- if (!msc) {
- LOGP(DMSC, LOGL_ERROR, "Failed to find a MSC for a connection.\n");
- bsc_send_ussd_no_srv(conn, msg,
- conn_get_bts(conn)->network->bsc_data->ussd_no_msc_txt);
- return -1;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t pdisc = gsm48_hdr_pdisc(gh);
+ uint8_t mtype = gsm48_hdr_msg_type(gh);
+ struct gsm48_loc_upd_req *lu;
+ struct gsm48_service_request *serv_req;
+ uint8_t pwr_lev;
+ struct gsm_bts *bts;
+ int8_t rc8;
+
+ switch (pdisc) {
+ case GSM48_PDISC_MM:
+ switch (mtype) {
+ case GSM48_MT_MM_LOC_UPD_REQUEST:
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "rx Location Updating message too short: %u\n", msgb_l3len(msg));
+ return;
+ }
+ lu = (struct gsm48_loc_upd_req *) gh->data;
+ pwr_lev = lu->classmark1.pwr_lev;
+ break;
+
+ case GSM48_MT_MM_CM_SERV_REQ:
+ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*serv_req)) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "rx CM Service Request message too short: %u\n", msgb_l3len(msg));
+ return;
+ }
+ serv_req = (struct gsm48_service_request *) gh->data;
+ pwr_lev = serv_req->classmark2.pwr_lev;
+ break;
+
+ default:
+ /* No power cap in other messages */
+ return;
+ }
+ break;
+ /* FIXME: pwr_lev in Paging Response? */
+ default:
+ /* No power cap in other messages */
+ return;
}
- return complete_layer3(conn, msg, msc) ? 0 : -2;
+ bts = conn_get_bts(conn);
+ OSMO_ASSERT(bts);
+ rc8 = osmo_gsm48_rfpowercap2powerclass(bts->band, pwr_lev);
+ if (rc8 < 0) {
+ LOGPFSML(conn->fi, LOGL_NOTICE, "%s %s: Unable to decode RF power capability 0x%x\n",
+ gsm48_pdisc_name(pdisc), gsm48_pdisc_msgtype_name(pdisc, mtype), pwr_lev);
+ rc8 = 0;
+ }
+ conn_update_ms_power_class(conn, rc8);
}
-static int handle_page_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+static struct gsm_subscriber_connection *bsc_conn_by_bsub(const struct bsc_subscr *bsub)
{
- struct bsc_subscr *subscr = extract_sub(conn, msg);
+ struct gsm_subscriber_connection *conn;
+ if (!bsub)
+ return NULL;
- if (!subscr) {
- LOGP(DMSC, LOGL_ERROR, "Non active subscriber got paged.\n");
- return -1;
+ llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) {
+ if (conn->bsub == bsub)
+ return conn;
}
-
- paging_request_stop(&conn->network->bts_list, conn_get_bts(conn), subscr, conn,
- msg);
- bsc_subscr_put(subscr);
- return 0;
+ return NULL;
}
-static void handle_lu_request(struct gsm_subscriber_connection *conn,
- struct msgb *msg)
+/*! MS->MSC: New MM context with L3 payload. */
+int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_channel)
{
+ struct gsm_subscriber_connection *conn = NULL;
+ struct bsc_subscr *bsub = NULL;
+ struct bsc_msc_data *paged_from_msc;
+ enum bsc_paging_reason paging_reasons;
+ struct bsc_msc_data *msc;
+ struct msgb *create_l3;
+ struct gsm0808_speech_codec_list scl;
+ struct gsm0808_speech_codec_list *use_scl;
+ int rc = -2;
+ struct gsm_bts *bts;
+ struct osmo_cell_global_id *cgi;
+ struct osmo_mobile_identity mi;
struct gsm48_hdr *gh;
- struct gsm48_loc_upd_req *lu;
- struct gsm48_loc_area_id lai;
+ uint8_t pdisc, mtype;
+ bool is_emerg;
+ bool release_lchan = true;
- if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) {
- LOGP(DMSC, LOGL_ERROR, "LU too small to look at: %u\n", msgb_l3len(msg));
- return;
+ if (msgb_l3len(msg) < sizeof(*gh)) {
+ LOGP(DRSL, LOGL_ERROR, "There is no GSM48 header here.\n");
+ goto early_exit;
}
gh = msgb_l3(msg);
- lu = (struct gsm48_loc_upd_req *) gh->data;
-
- gsm48_generate_lai2(&lai, bts_lai(conn_get_bts(conn)));
+ pdisc = gsm48_hdr_pdisc(gh);
+ mtype = gsm48_hdr_msg_type(gh);
- if (memcmp(&lai, &lu->lai, sizeof(lai)) != 0) {
- LOGP(DMSC, LOGL_DEBUG, "Marking con for welcome USSD.\n");
- conn->new_subscriber = 1;
+ bts = lchan->ts->trx->bts;
+ OSMO_ASSERT(bts);
+
+ /* Normally, if an lchan has no conn yet, it is an all new Complete Layer 3, and we allocate a new conn on the
+ * A-interface. But there are cases where a conn on A already exists for this subscriber (e.g. Perform Location
+ * Request on IDLE MS). The Mobile Identity tells us whether that is the case. */
+ if (osmo_mobile_identity_decode_from_l3(&mi, msg, false)) {
+ LOG_COMPL_L3(pdisc, mtype, LOGL_ERROR, "Cannot extract Mobile Identity: %s\n",
+ msgb_hexdump_c(OTC_SELECT, msg));
+ /* Likely this is an invalid Complete Layer 3 message that deserves to be rejected. However, the BSC is
+ * not expected to look at this layer so it's duty of the MSC to reject it.
+ * Hence, keep on going with the conn without an assigned bsc_subscr, forwarding the L3 to the MSC and
+ * letting it take decision on the matter.
+ */
+ } else {
+ bsub = bsc_subscr_find_or_create_by_mi(bsc_gsmnet->bsc_subscribers, &mi, __func__);
}
-}
-
-int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
-{
- struct gsm48_hdr *gh = msgb_l3(msg);
- uint8_t pdisc = gsm48_hdr_pdisc(gh);
- uint8_t mtype = gsm48_hdr_msg_type(gh);
- if (pdisc == GSM48_PDISC_MM) {
- if (mtype == GSM48_MT_MM_LOC_UPD_REQUEST)
- handle_lu_request(conn, msg);
- } else if (pdisc == GSM48_PDISC_RR) {
- if (mtype == GSM48_MT_RR_PAG_RESP)
- handle_page_resp(conn, msg);
+ /* If this Mobile Identity already has an active bsc_subscr, look whether there also is an active A-interface
+ * conn for this subscriber. This may be the case during a Perform Location Request (LCS) from the MSC that
+ * started on an IDLE MS, and now the MS is becoming active. Associate with the existing conn.
+ *
+ * However, for a CM Re-Establishment Request, we must *not* re-use the existing conn, but allocate a second
+ * conn for the same bsub. The previous conn will be Clear'ed by the MSC as soon as it receives the L3 Complete
+ * message == the CM Re-Establishment Request.
+ */
+ if (bsub && !(pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_REEST_REQ))
+ conn = bsc_conn_by_bsub(bsub);
+
+ if (!conn) {
+ /* Typical Complete Layer 3 with a new conn being established. */
+ conn = bsc_subscr_con_allocate(bsc_gsmnet);
+ if (!conn) {
+ LOG_COMPL_L3(pdisc, mtype, LOGL_ERROR, "Failed to allocate conn\n");
+ goto early_exit;
+ }
}
-
- return 0;
-}
-
-static bool complete_layer3(struct gsm_subscriber_connection *conn,
- struct msgb *msg, struct bsc_msc_data *msc)
-{
- int con_type, rc, lu_cause;
- char *imsi = NULL;
- struct msgb *resp;
- enum bsc_con ret;
- struct gsm0808_speech_codec_list scl;
-
- log_set_context(LOG_CTX_BSC_SUBSCR, conn->bsub);
-
- /* Check the filter */
- rc = bsc_filter_initial(msc->network->bsc_data, msc, conn, msg,
- &imsi, &con_type, &lu_cause);
- if (rc < 0) {
- bsc_maybe_lu_reject(conn, con_type, lu_cause);
- goto early_fail;
+ if (bsub) {
+ /* We got the conn either from new allocation, or by searching for it by bsub. So: */
+ OSMO_ASSERT((!conn->bsub) || (conn->bsub == bsub));
+ if (!conn->bsub) {
+ conn->bsub = bsub;
+ bsc_subscr_get(conn->bsub, BSUB_USE_CONN);
+ }
+ bsc_subscr_put(bsub, __func__);
}
+ /* Associate lchan with the conn, and set the id string for logging */
+ gscon_change_primary_lchan(conn, lchan);
+ gscon_update_id(conn);
- /* allocate resource for a new connection */
- ret = osmo_bsc_sigtran_new_conn(conn, msc);
-
- if (ret != BSC_CON_SUCCESS) {
- /* allocation has failed */
- if (ret == BSC_CON_REJECT_NO_LINK)
- bsc_send_ussd_no_srv(conn, msg, msc->ussd_msc_lost_txt);
- else if (ret == BSC_CON_REJECT_RF_GRACE)
- bsc_send_ussd_no_srv(conn, msg, msc->ussd_grace_txt);
- goto early_fail;
- }
+ log_set_context(LOG_CTX_BSC_SUBSCR, conn->bsub);
- /* TODO: also extract TMSI. We get an IMSI only when an initial L3 Complete comes in that
- * contains an IMSI. We filter by IMSI. A TMSI identity is never returned here, see e.g.
- * _cr_check_loc_upd() and other similar functions called from bsc_msg_filter_initial(). */
- if (imsi) {
- conn->filter_state.imsi = talloc_steal(conn, imsi);
- if (conn->bsub) {
- log_set_context(LOG_CTX_BSC_SUBSCR, conn->bsub);
- /* Already a subscriber on L3 Complete? Should never happen... */
- if (conn->bsub->imsi[0]
- && strcmp(conn->bsub->imsi, imsi))
- LOGP(DMSC, LOGL_ERROR, "Subscriber's IMSI changes from %s to %s\n",
- conn->bsub->imsi, imsi);
- bsc_subscr_set_imsi(conn->bsub, imsi);
+ is_emerg = (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) && is_cm_service_for_emerg(msg);
+
+ /* When receiving a Paging Response, stop Paging for this subscriber on all cells, and figure out which MSC
+ * sent the Paging Request, if any. */
+ paged_from_msc = NULL;
+ paging_reasons = BSC_PAGING_NONE;
+ if (pdisc == GSM48_PDISC_RR && mtype == GSM48_MT_RR_PAG_RESP) {
+ /* It only makes sense to attempt to find a pending paging request if the subscriber from the
+ * Paging Response can be identified (bsub != NULL). */
+ if (conn->bsub)
+ paging_request_stop(&paged_from_msc, &paging_reasons, bts, conn->bsub);
+ if (!paged_from_msc) {
+ /* This looks like an unsolicited Paging Response. It is required to pick any MSC, because any
+ * MT-CSFB calls were Paged by the MSC via SGs, and hence are not listed in the BSC. */
+ LOG_COMPL_L3(pdisc, mtype, LOGL_DEBUG,
+ "%s Unsolicited Paging Response, possibly an MT-CSFB call.\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, &mi));
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_NO_ACTIVE_PAGING));
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_PAGING_NO_ACTIVE_PAGING));
+ } else if (is_msc_usable(paged_from_msc, is_emerg)) {
+ LOG_COMPL_L3(pdisc, mtype, LOGL_DEBUG, "%s matches earlier Paging from msc %d\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, &mi), paged_from_msc->nr);
+ rate_ctr_inc(rate_ctr_group_get_ctr(paged_from_msc->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_PAGED));
} else {
- conn->bsub = bsc_subscr_find_or_create_by_imsi(msc->network->bsc_subscribers,
- imsi);
- log_set_context(LOG_CTX_BSC_SUBSCR, conn->bsub);
+ LOG_COMPL_L3(pdisc, mtype, LOGL_DEBUG,
+ "%s matches earlier Paging from msc %d, but this MSC is not connected\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, &mi), paged_from_msc->nr);
+ paged_from_msc = NULL;
}
- gscon_update_id(conn);
}
- conn->filter_state.con_type = con_type;
- /* check return value, if failed check msg for and send USSD */
+ if (!conn->sccp.msc) {
+ /* The conn was just allocated, and no target MSC has been picked for it yet. */
+ if (paged_from_msc)
+ msc = paged_from_msc;
+ else
+ msc = bsc_find_msc(conn, &mi, is_emerg, is_lu_from_other_plmn(msg));
+ if (!msc) {
+ LOG_COMPL_L3(pdisc, mtype, LOGL_ERROR,
+ "%s%s: No suitable MSC for this Complete Layer 3 request found\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, &mi),
+ is_emerg ? " FOR EMERGENCY CALL" : "");
+ goto early_exit;
+ }
- bsc_scan_bts_msg(conn, msg);
+ /* allocate resource for a new connection */
+ if (osmo_bsc_sigtran_new_conn(conn, msc) != BSC_CON_SUCCESS)
+ goto early_exit;
+ } else if (paged_from_msc && conn->sccp.msc != paged_from_msc) {
+ LOG_COMPL_L3(pdisc, mtype, LOGL_ERROR,
+ "%s%s: there is a conn to MSC %u, but there is a pending Paging request from MSC %u\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, &mi),
+ is_emerg ? " FOR EMERGENCY CALL" : "",
+ conn->sccp.msc->nr, paged_from_msc->nr);
+ }
+ OSMO_ASSERT(conn->sccp.msc);
+
+ parse_powercap(conn, msg);
+
+ /* If a BSSLAP TA Request from the SMLC is waiting for a TA value, we have one now. */
+ if (conn->lcs.loc_req && conn->lcs.loc_req->ta_req)
+ osmo_fsm_inst_dispatch(conn->lcs.loc_req->ta_req->fi, LCS_TA_REQ_EV_GOT_TA, NULL);
+
+ /* If the Paging was issued only by OsmoBSC for LCS, don't bother to establish Layer 3 to the MSC. */
+ if (paged_from_msc && !(paging_reasons & BSC_PAGING_FROM_CN)) {
+ LOG_COMPL_L3(pdisc, mtype, LOGL_DEBUG,
+ "%s%s: Paging was for Perform Location Request only, not establishing Layer 3\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, &mi),
+ is_emerg ? " FOR EMERGENCY CALL" : "");
+ rc = 0;
+ goto early_exit;
+ }
+ /* Send the Create Layer 3. */
+ use_scl = NULL;
if (gscon_is_aoip(conn)) {
- gen_bss_supported_codec_list(&scl, msc, conn_get_bts(conn));
+ gen_bss_supported_codec_list(&scl, conn->sccp.msc, bts);
if (scl.len > 0)
- resp = gsm0808_create_layer3_2(msg, cgi_for_msc(conn->sccp.msc, conn_get_bts(conn)), &scl);
- else {
- /* Note: 3GPP TS 48.008 3.2.1.32, COMPLETE LAYER 3 INFORMATION clearly states that
- * Codec List (BSS Supported) shall be included, if the radio access network
- * supports an IP based user plane interface. It may be intentional that the
- * current configuration does not support any voice codecs, in those cases the
- * network does not support an IP based user plane interface, and therefore the
- * Codec List (BSS Supported) IE can be left out in those situations. */
- resp = gsm0808_create_layer3_2(msg, cgi_for_msc(conn->sccp.msc, conn_get_bts(conn)), NULL);
- }
- } else
- resp = gsm0808_create_layer3_2(msg, cgi_for_msc(conn->sccp.msc, conn_get_bts(conn)), NULL);
-
- if (resp)
- osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_REQ, resp);
- else
- LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n");
-
- log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
- return !!resp;
-early_fail:
- log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
- return false;
-}
-
-/*
- * Plastic surgery... we want to give up the current connection
- */
-static int move_to_msc(struct gsm_subscriber_connection *_conn,
- struct msgb *msg, struct bsc_msc_data *msc)
-{
- /*
- * 1. Give up the old connection.
- * This happens by sending a clear request to the MSC,
- * it should end with the MSC releasing the connection.
- */
- bsc_clear_request(_conn, 0);
-
- /*
- * 2. Attempt to create a new connection to the local
- * MSC. If it fails the caller will need to handle this
- * properly.
- */
- if (!complete_layer3(_conn, msg, msc)) {
- /* FIXME: I have not the slightest idea what move_to_msc() intends to do; during lchan
- * FSM introduction, I changed this and hope it is the appropriate action. I actually
- * assume this is unused legacy code for osmo-bsc_nat?? */
- gscon_release_lchans(_conn, false);
- return 1;
+ use_scl = &scl;
+ /* For AoIP, we should always pass a Codec List (BSS Supported). But osmo-bsc may be configured to
+ * support no voice codecs -- then omit the Codec List. */
}
-
- return 2;
-}
-
-static int handle_cc_setup(struct gsm_subscriber_connection *conn,
- struct msgb *msg)
-{
- struct gsm48_hdr *gh = msgb_l3(msg);
- uint8_t pdisc = gsm48_hdr_pdisc(gh);
- uint8_t mtype = gsm48_hdr_msg_type(gh);
-
- struct bsc_msc_data *msc;
- struct gsm_mncc_number called;
- struct tlv_parsed tp;
- unsigned payload_len;
-
- char _dest_nr[35];
-
- /*
- * Do we have a setup message here? if not return fast.
- */
- if (pdisc != GSM48_PDISC_CC || mtype != GSM48_MT_CC_SETUP)
- return 0;
-
- payload_len = msgb_l3len(msg) - sizeof(*gh);
-
- tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
- if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
- LOGP(DMSC, LOGL_ERROR, "Called BCD not present in setup.\n");
- return -1;
+ cgi = cgi_for_msc(conn->sccp.msc, bts);
+ if (!cgi) {
+ /* should never happen */
+ LOG_COMPL_L3(pdisc, mtype, LOGL_ERROR, "%s: internal error: BTS without identity\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, &mi));
+ goto early_exit;
}
-
- memset(&called, 0, sizeof(called));
- gsm48_decode_called(&called,
- TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1);
-
- if (called.plan != 1 && called.plan != 0)
- return 0;
-
- if (called.plan == 1 && called.type == 1) {
- _dest_nr[0] = _dest_nr[1] = '0';
- memcpy(_dest_nr + 2, called.number, sizeof(called.number));
- } else
- memcpy(_dest_nr, called.number, sizeof(called.number));
-
- /*
- * Check if the connection should be moved...
- */
- llist_for_each_entry(msc, &conn_get_bts(conn)->network->bsc_data->mscs, entry) {
- if (msc->type != MSC_CON_TYPE_LOCAL)
- continue;
- if (!msc->local_pref)
- continue;
- if (regexec(&msc->local_pref_reg, _dest_nr, 0, NULL, 0) != 0)
- continue;
-
- return move_to_msc(conn, msg, msc);
+ create_l3 = gsm0808_create_layer3_2(msg, cgi, use_scl);
+ if (!create_l3) {
+ LOG_COMPL_L3(pdisc, mtype, LOGL_ERROR, "%s: Failed to compose Create Layer 3 message\n",
+ osmo_mobile_identity_to_str_c(OTC_SELECT, &mi));
+ goto early_exit;
}
-
- return 0;
+ rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MO_COMPL_L3, create_l3);
+ if (!rc)
+ release_lchan = false;
+
+early_exit:
+ if (release_lchan)
+ lchan_release(lchan, true, true, RSL_ERR_EQUIPMENT_FAIL,
+ gscon_last_eutran_plmn(conn));
+ log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
+ return rc;
}
/*! MS->BSC/MSC: Um L3 message. */
void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
{
- int lu_cause;
-
log_set_context(LOG_CTX_BSC_SUBSCR, conn->bsub);
if (!msc_connected(conn))
@@ -609,69 +564,70 @@ void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct ms
LOGP(DMSC, LOGL_INFO, "Tx MSC DTAP LINK_ID=0x%02x\n", link_id);
- /*
- * We might want to move this connection to a new MSC. Ask someone
- * to handle it. If it was handled we will return.
- */
- if (handle_cc_setup(conn, msg) >= 1)
- goto done;
-
- /* Check the filter */
- if (bsc_filter_data(conn, msg, &lu_cause) < 0) {
- bsc_maybe_lu_reject(conn,
- conn->filter_state.con_type,
- lu_cause);
- bsc_clear_request(conn, 0);
- goto done;
- }
+ parse_powercap(conn, msg);
- bsc_scan_bts_msg(conn, msg);
+ /* convert RSL link ID to DLCI, store in msg->cb */
+ OBSC_LINKID_CB(msg) = RSL_LINK_ID2DLCI(link_id);
- /* Store link_id in msg->cb */
- OBSC_LINKID_CB(msg) = link_id;
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MO_DTAP, msg);
done:
log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
return;
}
-/*! BSC->MSC: RR conn has been cleared. */
-int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
-{
- int rc;
- struct msgb *resp;
-
- if (!msc_connected(conn))
- return 1;
-
- LOGP(DMSC, LOGL_INFO, "Tx MSC CLEAR REQUEST\n");
-
- resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE);
- if (!resp) {
- LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n");
- return 1;
- }
-
- rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
- if (rc != 0)
- msgb_free(resp);
-
- return 1;
-}
-
/*! BSC->MSC: Classmark Update. */
void bsc_cm_update(struct gsm_subscriber_connection *conn,
const uint8_t *cm2, uint8_t cm2_len,
const uint8_t *cm3, uint8_t cm3_len)
{
+ struct gsm48_classmark2 *cm2_parsed = (struct gsm48_classmark2 *)cm2;
+ int8_t rc8;
int rc;
struct msgb *resp;
+ struct gsm_bts *bts = conn_get_bts(conn);
+
+ if (!bts) {
+ /* should never happen */
+ LOGP(DMSC, LOGL_ERROR, "Classmark Update without lchan\n");
+ return;
+ }
+
+ rc8 = osmo_gsm48_rfpowercap2powerclass(bts->band, cm2_parsed->pwr_lev);
+ if (rc8 < 0) {
+ LOGP(DMSC, LOGL_NOTICE,
+ "Unable to decode RF power capability %x from classmark1 during CM Update.\n",
+ cm2_parsed->pwr_lev);
+ rc8 = 0;
+ }
+ conn_update_ms_power_class(conn, rc8);
+
+ if (cm3 != NULL && cm3_len > 0) {
+ rc = gsm48_decode_classmark3(&conn->cm3, cm3, cm3_len);
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_NOTICE, "Unable to decode classmark3 during CM Update.\n");
+ memset(&conn->cm3, 0, sizeof(conn->cm3));
+ conn->cm3_valid = false;
+ } else
+ conn->cm3_valid = true;
+ }
if (!msc_connected(conn))
return;
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CLASSMARK_UPDATE));
resp = gsm0808_create_classmark_update(cm2, cm2_len, cm3, cm3_len);
rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
if (rc != 0)
msgb_free(resp);
}
+
+bool bsc_chan_ind_requires_rtp_stream(enum gsm0808_chan_indicator ch_indctr)
+{
+ switch (ch_indctr) {
+ case GSM0808_CHAN_SPEECH:
+ case GSM0808_CHAN_DATA:
+ return true;
+ default:
+ return false;
+ }
+}
diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c
index 7eee918b9..b11607e3f 100644
--- a/src/osmo-bsc/gsm_data.c
+++ b/src/osmo-bsc/gsm_data.c
@@ -24,6 +24,7 @@
#include <errno.h>
#include <ctype.h>
#include <stdbool.h>
+#include <inttypes.h>
#include <netinet/in.h>
#include <talloc.h>
@@ -37,25 +38,17 @@
#include <osmocom/gsm/gsm0808_utils.h>
#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/osmo_bsc_lcls.h>
+#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/abis_nm.h>
#include <osmocom/bsc/handover_cfg.h>
-#include <osmocom/bsc/gsm_timers.h>
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bsc_msc_data.h>
void *tall_bsc_ctx = NULL;
-const struct value_string bsc_lcls_mode_names[] = {
- { BSC_LCLS_MODE_DISABLED, "disabled" },
- { BSC_LCLS_MODE_MGW_LOOP, "mgw-loop" },
- { BSC_LCLS_MODE_BTS_LOOP, "bts-loop" },
- { 0, NULL }
-};
-
-static LLIST_HEAD(bts_models);
-
void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr,
uint8_t e1_ts, uint8_t e1_ts_ss)
{
@@ -64,64 +57,18 @@ void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr,
ts->e1_link.e1_ts_ss = e1_ts_ss;
}
-static struct gsm_bts_model *bts_model_find(enum gsm_bts_type type)
-{
- struct gsm_bts_model *model;
-
- llist_for_each_entry(model, &bts_models, list) {
- if (model->type == type)
- return model;
- }
-
- return NULL;
-}
-
-int gsm_bts_model_register(struct gsm_bts_model *model)
-{
- if (bts_model_find(model->type))
- return -EEXIST;
-
- tlv_def_patch(&model->nm_att_tlvdef, &abis_nm_att_tlvdef);
- llist_add_tail(&model->list, &bts_models);
- return 0;
-}
-
-const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1] = {
- { GSM_BTS_TYPE_UNKNOWN, "Unknown BTS Type" },
- { GSM_BTS_TYPE_BS11, "Siemens BTS (BS-11 or compatible)" },
- { GSM_BTS_TYPE_NANOBTS, "ip.access nanoBTS or compatible" },
- { GSM_BTS_TYPE_RBS2000, "Ericsson RBS2000 Series" },
- { GSM_BTS_TYPE_NOKIA_SITE, "Nokia {Metro,Ultra,In}Site" },
- { GSM_BTS_TYPE_OSMOBTS, "sysmocom sysmoBTS" },
- { 0, NULL }
-};
-
-struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr)
-{
- struct gsm_bts_trx *trx;
-
- llist_for_each_entry(trx, &bts->trx_list, list) {
- if (trx->nr == nr)
- return trx;
- }
- return NULL;
-}
-
/* Search for a BTS in the given Location Area; optionally start searching
* with start_bts (for continuing to search after the first result) */
struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac,
struct gsm_bts *start_bts)
{
- int i;
struct gsm_bts *bts;
int skip = 0;
if (start_bts)
skip = 1;
- for (i = 0; i < net->num_bts; i++) {
- bts = gsm_bts_num(net, i);
-
+ llist_for_each_entry(bts, &net->bts_list, list) {
if (skip) {
if (start_bts == bts)
skip = 0;
@@ -156,79 +103,25 @@ const char *bts_gprs_mode_name(enum bts_gprs_mode mode)
return get_value_string(bts_gprs_mode_names, mode);
}
-int bts_gprs_mode_is_compat(struct gsm_bts *bts, enum bts_gprs_mode mode)
-{
- if (mode != BTS_GPRS_NONE &&
- !osmo_bts_has_feature(&bts->model->features, BTS_FEAT_GPRS)) {
- return 0;
- }
- if (mode == BTS_GPRS_EGPRS &&
- !osmo_bts_has_feature(&bts->model->features, BTS_FEAT_EGPRS)) {
- return 0;
- }
-
- return 1;
-}
-
-int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type)
-{
- struct gsm_bts_model *model;
-
- model = bts_model_find(type);
- if (!model)
- return -EINVAL;
-
- bts->type = type;
- bts->model = model;
-
- if (model->start && !model->started) {
- int ret = model->start(bts->network);
- if (ret < 0)
- return ret;
-
- model->started = true;
- }
-
- switch (bts->type) {
- case GSM_BTS_TYPE_NANOBTS:
- case GSM_BTS_TYPE_OSMOBTS:
- /* Set the default OML Stream ID to 0xff */
- bts->oml_tei = 0xff;
- bts->c0->nominal_power = 23;
- break;
- case GSM_BTS_TYPE_RBS2000:
- INIT_LLIST_HEAD(&bts->rbs2000.is.conn_groups);
- INIT_LLIST_HEAD(&bts->rbs2000.con.conn_groups);
- break;
- case GSM_BTS_TYPE_BS11:
- case GSM_BTS_TYPE_UNKNOWN:
- case GSM_BTS_TYPE_NOKIA_SITE:
- /* Set default BTS reset timer */
- bts->nokia.bts_reset_timer_cnf = 15;
- case _NUM_GSM_BTS_TYPE:
- break;
- }
-
- return 0;
-}
-
struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type,
uint8_t bsic)
{
struct gsm_bts_model *model = bts_model_find(type);
+ struct gsm_bts_sm *bts_sm;
struct gsm_bts *bts;
if (!model && type != GSM_BTS_TYPE_UNKNOWN)
return NULL;
- bts = gsm_bts_alloc(net, net->num_bts);
- if (!bts)
+ bts_sm = gsm_bts_sm_alloc(net, net->num_bts);
+ if (!bts_sm)
return NULL;
+ bts = bts_sm->bts[0];
net->num_bts++;
bts->type = type;
- bts->model = model;
+ gsm_set_bts_model(bts, model);
bts->bsic = bsic;
llist_add_tail(&bts->list, &net->bts_list);
@@ -255,158 +148,15 @@ void gsm48_ra_id_by_bts(struct gsm48_ra_id *buf, struct gsm_bts *bts)
gsm48_encode_ra(buf, &raid);
}
-int gsm_parse_reg(void *ctx, regex_t *reg, char **str, int argc, const char **argv)
-{
- int ret;
-
- ret = 0;
- if (*str) {
- talloc_free(*str);
- *str = NULL;
- }
- regfree(reg);
-
- if (argc > 0) {
- *str = talloc_strdup(ctx, argv[0]);
- ret = regcomp(reg, argv[0], 0);
-
- /* handle compilation failures */
- if (ret != 0) {
- talloc_free(*str);
- *str = NULL;
- }
- }
-
- return ret;
-}
-
-/* Assume there are only 256 possible bts */
-osmo_static_assert(sizeof(((struct gsm_bts *) 0)->nr) == 1, _bts_nr_is_256);
-static void depends_calc_index_bit(int bts_nr, int *idx, int *bit)
-{
- *idx = bts_nr / (8 * 4);
- *bit = bts_nr % (8 * 4);
-}
-
-void bts_depend_mark(struct gsm_bts *bts, int dep)
-{
- int idx, bit;
- depends_calc_index_bit(dep, &idx, &bit);
-
- bts->depends_on[idx] |= 1 << bit;
-}
-
-void bts_depend_clear(struct gsm_bts *bts, int dep)
-{
- int idx, bit;
- depends_calc_index_bit(dep, &idx, &bit);
-
- bts->depends_on[idx] &= ~(1 << bit);
-}
-
-int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other)
-{
- int idx, bit;
- depends_calc_index_bit(other->nr, &idx, &bit);
-
- /* Check if there is a depends bit */
- return (base->depends_on[idx] & (1 << bit)) > 0;
-}
-
-static int bts_is_online(struct gsm_bts *bts)
-{
- /* TODO: support E1 BTS too */
- if (!is_ipaccess_bts(bts))
- return 1;
-
- if (!bts->oml_link)
- return 0;
-
- return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED;
-}
-
-int bts_depend_check(struct gsm_bts *bts)
-{
- struct gsm_bts *other_bts;
-
- llist_for_each_entry(other_bts, &bts->network->bts_list, list) {
- if (!bts_depend_is_depedency(bts, other_bts))
- continue;
- if (bts_is_online(other_bts))
- continue;
- return 0;
- }
- return 1;
-}
-
-/* get the radio link timeout (based on SACCH decode errors, according
- * to algorithm specified in TS 05.08 section 5.2. A value of -1
- * indicates we should use an infinitely long timeout, which only works
- * with OsmoBTS as the BTS implementation */
-int gsm_bts_get_radio_link_timeout(const struct gsm_bts *bts)
-{
- const struct gsm48_cell_options *cell_options = &bts->si_common.cell_options;
-
- if (bts->infinite_radio_link_timeout)
- return -1;
- else {
- /* Encoding as per Table 10.5.21 of TS 04.08 */
- return (cell_options->radio_link_timeout + 1) << 2;
- }
-}
-
-/* set the radio link timeout (based on SACCH decode errors, according
- * to algorithm specified in TS 05.08 Section 5.2. A value of -1
- * indicates we should use an infinitely long timeout, which only works
- * with OsmoBTS as the BTS implementation */
-void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value)
-{
- struct gsm48_cell_options *cell_options = &bts->si_common.cell_options;
-
- if (value < 0)
- bts->infinite_radio_link_timeout = true;
- else {
- bts->infinite_radio_link_timeout = false;
- /* Encoding as per Table 10.5.21 of TS 04.08 */
- if (value < 4)
- value = 4;
- if (value > 64)
- value = 64;
- cell_options->radio_link_timeout = (value >> 2) - 1;
- }
-}
-
-bool classmark_is_r99(struct gsm_classmark *cm)
-{
- int rev_lev = 0;
- if (cm->classmark1_set)
- rev_lev = cm->classmark1.rev_lev;
- else if (cm->classmark2_len > 0)
- rev_lev = (cm->classmark2[0] >> 5) & 0x3;
- return rev_lev >= 2;
-}
-
-static const struct osmo_stat_item_desc bts_stat_desc[] = {
- { "chanloadavg", "Channel load average.", "%", 16, 0 },
- { "T3122", "T3122 IMMEDIATE ASSIGNMENT REJECT wait indicator.", "s", 16, GSM_T3122_DEFAULT },
-};
-
-static const struct osmo_stat_item_group_desc bts_statg_desc = {
- .group_name_prefix = "bts",
- .group_description = "base transceiver station",
- .class_id = OSMO_STATS_CLASS_GLOBAL,
- .num_items = ARRAY_SIZE(bts_stat_desc),
- .item_desc = bts_stat_desc,
-};
-
void gsm_abis_mo_reset(struct gsm_abis_mo *mo)
{
mo->nm_state.operational = NM_OPSTATE_NULL;
mo->nm_state.availability = NM_AVSTATE_POWER_OFF;
+ mo->nm_state.administrative = NM_STATE_LOCKED;
}
-static void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts,
- uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3)
+void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts,
+ uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3)
{
mo->bts = bts;
mo->obj_class = obj_class;
@@ -416,66 +166,10 @@ static void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts,
gsm_abis_mo_reset(mo);
}
-const struct value_string bts_attribute_names[] = {
- OSMO_VALUE_STRING(BTS_TYPE_VARIANT),
- OSMO_VALUE_STRING(BTS_SUB_MODEL),
- OSMO_VALUE_STRING(TRX_PHY_VERSION),
- { 0, NULL }
-};
-
-enum bts_attribute str2btsattr(const char *s)
-{
- return get_string_value(bts_attribute_names, s);
-}
-
-const char *btsatttr2str(enum bts_attribute v)
-{
- return get_value_string(bts_attribute_names, v);
-}
-
-const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = {
- { BTS_UNKNOWN, "unknown" },
- { BTS_OSMO_LITECELL15, "osmo-bts-lc15" },
- { BTS_OSMO_OCTPHY, "osmo-bts-octphy" },
- { BTS_OSMO_SYSMO, "osmo-bts-sysmo" },
- { BTS_OSMO_TRX, "omso-bts-trx" },
- { 0, NULL }
-};
-
-enum gsm_bts_type_variant str2btsvariant(const char *arg)
-{
- return get_string_value(osmo_bts_variant_names, arg);
-}
-
-const char *btsvariant2str(enum gsm_bts_type_variant v)
-{
- return get_value_string(osmo_bts_variant_names, v);
-}
-
-const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE + 1] = {
- { GSM_BTS_TYPE_UNKNOWN, "unknown" },
- { GSM_BTS_TYPE_BS11, "bs11" },
- { GSM_BTS_TYPE_NANOBTS, "nanobts" },
- { GSM_BTS_TYPE_RBS2000, "rbs2000" },
- { GSM_BTS_TYPE_NOKIA_SITE, "nokia_site" },
- { GSM_BTS_TYPE_OSMOBTS, "sysmobts" },
- { 0, NULL }
-};
-
-enum gsm_bts_type str2btstype(const char *arg)
-{
- return get_string_value(bts_type_names, arg);
-}
-
-const char *btstype2str(enum gsm_bts_type type)
-{
- return get_value_string(bts_type_names, type);
-}
-
const struct value_string gsm_chreq_descs[] = {
{ GSM_CHREQ_REASON_EMERG, "emergency call" },
{ GSM_CHREQ_REASON_PAG, "answer to paging" },
- { GSM_CHREQ_REASON_CALL, "call re-establishment" },
+ { GSM_CHREQ_REASON_CALL, "call (re-)establishment" },
{ GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" },
{ GSM_CHREQ_REASON_PDCH, "one phase packet access" },
{ GSM_CHREQ_REASON_OTHER, "other" },
@@ -490,31 +184,22 @@ const struct value_string gsm_pchant_names[] = {
{ GSM_PCHAN_TCH_H, "TCH/H" },
{ GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" },
{ GSM_PCHAN_PDCH, "PDCH" },
- { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" },
+ { GSM_PCHAN_TCH_F_PDCH, "DYNAMIC/IPACCESS" },
{ GSM_PCHAN_UNKNOWN, "UNKNOWN" },
{ GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" },
{ GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" },
- { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" },
- { 0, NULL }
-};
-
-const struct value_string gsm_pchan_ids[] = {
- { GSM_PCHAN_NONE, "NONE" },
- { GSM_PCHAN_CCCH, "CCCH" },
- { GSM_PCHAN_CCCH_SDCCH4,"CCCH_SDCCH4" },
- { GSM_PCHAN_TCH_F, "TCH_F" },
- { GSM_PCHAN_TCH_H, "TCH_H" },
- { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" },
- { GSM_PCHAN_PDCH, "PDCH" },
- { GSM_PCHAN_TCH_F_PDCH, "TCH_F_PDCH" },
- { GSM_PCHAN_UNKNOWN, "UNKNOWN" },
- { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH_SDCCH4_CBCH" },
- { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8_CBCH" },
- { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH_F_TCH_H_PDCH" },
+ { GSM_PCHAN_OSMO_DYN, "DYNAMIC/OSMOCOM" },
+ /* make get_string_value() return GSM_PCHAN_TCH_F_PDCH for both "DYNAMIC/IPACCESS" and "TCH/F_PDCH" */
+ { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" },
+ /* make get_string_value() return GSM_PCHAN_OSMO_DYN for both "DYNAMIC/OSMOCOM" and "TCH/F_TCH/H_SDCCH8_PDCH" */
+ { GSM_PCHAN_OSMO_DYN, "TCH/F_TCH/H_SDCCH8_PDCH" },
+ /* When adding items here, you must also add matching items to gsm_pchant_descs[]! */
{ 0, NULL }
};
-const struct value_string gsm_pchant_descs[13] = {
+/* VTY command descriptions. These have to be in the same order as gsm_pchant_names[], so that the automatic VTY command
+ * composition in bts_trx_vty_init() works out. */
+const struct value_string gsm_pchant_descs[] = {
{ GSM_PCHAN_NONE, "Physical Channel not configured" },
{ GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" },
{ GSM_PCHAN_CCCH_SDCCH4,
@@ -523,14 +208,24 @@ const struct value_string gsm_pchant_descs[13] = {
{ GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" },
{ GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" },
{ GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" },
- { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" },
+ { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH"
+ " (dynamic/ipaccess is an alias for tch/f_pdch)" },
{ GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" },
{ GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" },
{ GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" },
- { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" },
+ { GSM_PCHAN_OSMO_DYN, "Dynamic TCH/F or TCH/H or SDCCH/8 or GPRS PDCH"
+ " (dynamic/osmocom is an alias for tch/f_tch/h_sdcch8_pdch)" },
+ /* These duplicate entries are needed to provide a description for both the DYNAMIC/... aliases and their
+ * explicit versions 'TCH/F_PDCH' / 'TCH/F_TCH/H_SDCCH8_PDCH', see bts_trx_vty_init() */
+ { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH"
+ " (dynamic/ipaccess is an alias for tch/f_pdch)" },
+ { GSM_PCHAN_OSMO_DYN, "Dynamic TCH/F or TCH/H or SDCCH/8 or GPRS PDCH"
+ " (dynamic/osmocom is an alias for tch/f_tch/h_sdcch8_pdch)" },
{ 0, NULL }
};
+osmo_static_assert(ARRAY_SIZE(gsm_pchant_names) == ARRAY_SIZE(gsm_pchant_descs), _pchan_vty_docs);
+
const char *gsm_pchan_name(enum gsm_phys_chan_config c)
{
return get_value_string(gsm_pchant_names, c);
@@ -541,12 +236,6 @@ enum gsm_phys_chan_config gsm_pchan_parse(const char *name)
return get_string_value(gsm_pchant_names, name);
}
-/* TODO: move to libosmocore, next to gsm_chan_t_names? */
-const char *gsm_lchant_name(enum gsm_chan_t c)
-{
- return get_value_string(gsm_chan_t_names, c);
-}
-
static const struct value_string chreq_names[] = {
{ GSM_CHREQ_REASON_EMERG, "EMERGENCY" },
{ GSM_CHREQ_REASON_PAG, "PAGING" },
@@ -576,44 +265,6 @@ struct gsm_bts *gsm_bts_num(const struct gsm_network *net, int num)
return NULL;
}
-bool gsm_bts_matches_lai(const struct gsm_bts *bts, const struct osmo_location_area_id *lai)
-{
- return osmo_plmn_cmp(&lai->plmn, &bts->network->plmn) == 0
- && lai->lac == bts->location_area_code;
-}
-
-bool gsm_bts_matches_cell_id(const struct gsm_bts *bts, const struct gsm0808_cell_id *cell_id)
-{
- const union gsm0808_cell_id_u *id = &cell_id->id;
- if (!bts || !cell_id)
- return false;
-
- switch (cell_id->id_discr) {
- case CELL_IDENT_WHOLE_GLOBAL:
- return gsm_bts_matches_lai(bts, &id->global.lai)
- && id->global.cell_identity == bts->cell_identity;
- case CELL_IDENT_LAC_AND_CI:
- return id->lac_and_ci.lac == bts->location_area_code
- && id->lac_and_ci.ci == bts->cell_identity;
- case CELL_IDENT_CI:
- return id->ci == bts->cell_identity;
- case CELL_IDENT_NO_CELL:
- return false;
- case CELL_IDENT_LAI_AND_LAC:
- return gsm_bts_matches_lai(bts, &id->lai_and_lac);
- case CELL_IDENT_LAC:
- return id->lac == bts->location_area_code;
- case CELL_IDENT_BSS:
- return true;
- case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
- case CELL_IDENT_UTRAN_RNC:
- case CELL_IDENT_UTRAN_LAC_RNC:
- return false;
- default:
- OSMO_ASSERT(false);
- }
-}
-
/* From a list of local BTSes that match the cell_id, return the Nth one, or NULL if there is no such
* match. */
struct gsm_bts *gsm_bts_by_cell_id(const struct gsm_network *net,
@@ -635,379 +286,8 @@ struct gsm_bts *gsm_bts_by_cell_id(const struct gsm_network *net,
return NULL;
}
-struct gsm_bts_ref *gsm_bts_ref_find(const struct llist_head *list, const struct gsm_bts *bts)
-{
- struct gsm_bts_ref *ref;
- if (!bts)
- return NULL;
- llist_for_each_entry(ref, list, entry) {
- if (ref->bts == bts)
- return ref;
- }
- return NULL;
-}
-
-/* Add a BTS reference to the local_neighbors list.
- * Return 1 if added, 0 if such an entry already existed, and negative on errors. */
-int gsm_bts_local_neighbor_add(struct gsm_bts *bts, struct gsm_bts *neighbor)
-{
- struct gsm_bts_ref *ref;
- if (!bts || !neighbor)
- return -ENOMEM;
-
- if (bts == neighbor)
- return -EINVAL;
-
- /* Already got this entry? */
- ref = gsm_bts_ref_find(&bts->local_neighbors, neighbor);
- if (ref)
- return 0;
-
- ref = talloc_zero(bts, struct gsm_bts_ref);
- if (!ref)
- return -ENOMEM;
- ref->bts = neighbor;
- llist_add_tail(&ref->entry, &bts->local_neighbors);
- return 1;
-}
-
-/* Remove a BTS reference from the local_neighbors list.
- * Return 1 if removed, 0 if no such entry existed, and negative on errors. */
-int gsm_bts_local_neighbor_del(struct gsm_bts *bts, const struct gsm_bts *neighbor)
-{
- struct gsm_bts_ref *ref;
- if (!bts || !neighbor)
- return -ENOMEM;
-
- ref = gsm_bts_ref_find(&bts->local_neighbors, neighbor);
- if (!ref)
- return 0;
-
- llist_del(&ref->entry);
- talloc_free(ref);
- return 1;
-}
-
-struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts)
-{
- struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx);
- int k;
-
- if (!trx)
- return NULL;
-
- trx->bts = bts;
- trx->nr = bts->num_trx++;
- trx->mo.nm_state.administrative = NM_STATE_UNLOCKED;
-
- gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER,
- bts->nr, trx->nr, 0xff);
- gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC,
- bts->nr, trx->nr, 0xff);
-
- for (k = 0; k < TRX_NR_TS; k++) {
- struct gsm_bts_trx_ts *ts = &trx->ts[k];
- int l;
-
-
- ts->trx = trx;
- ts->nr = k;
- ts->pchan_from_config = ts->pchan_on_init = ts->pchan_is = GSM_PCHAN_NONE;
- ts->tsc = -1;
-
- ts_fsm_alloc(ts);
-
- gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL,
- bts->nr, trx->nr, ts->nr);
-
- ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data);
- ts->hopping.arfcns.data = ts->hopping.arfcns_data;
- ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data);
- ts->hopping.ma.data = ts->hopping.ma_data;
-
- for (l = 0; l < TS_MAX_LCHAN; l++) {
- struct gsm_lchan *lchan;
- char *name;
- lchan = &ts->lchan[l];
-
- lchan->ts = ts;
- lchan->nr = l;
- lchan->type = GSM_LCHAN_NONE;
-
- name = gsm_lchan_name_compute(lchan);
- lchan->name = talloc_strdup(trx, name);
- }
- }
-
- if (trx->nr != 0)
- trx->nominal_power = bts->c0->nominal_power;
-
- llist_add_tail(&trx->list, &bts->trx_list);
-
- return trx;
-}
-
-
-static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 };
-static const uint8_t bts_cell_timer_default[] =
- { 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 };
-static const struct gprs_rlc_cfg rlc_cfg_default = {
- .parameter = {
- [RLC_T3142] = 20,
- [RLC_T3169] = 5,
- [RLC_T3191] = 5,
- [RLC_T3193] = 160, /* 10ms */
- [RLC_T3195] = 5,
- [RLC_N3101] = 10,
- [RLC_N3103] = 4,
- [RLC_N3105] = 8,
- [CV_COUNTDOWN] = 15,
- [T_DL_TBF_EXT] = 250 * 10, /* ms */
- [T_UL_TBF_EXT] = 250 * 10, /* ms */
- },
- .paging = {
- .repeat_time = 5 * 50, /* ms */
- .repeat_count = 3,
- },
- .cs_mask = 0x1fff,
- .initial_cs = 2,
- .initial_mcs = 6,
-};
-
-/* Initialize those parts that don't require osmo-bsc specific dependencies.
- * This part is shared among the thin programs in osmo-bsc/src/utils/.
- * osmo-bsc requires further initialization that pulls in more dependencies (see
- * bsc_bts_alloc_register()). */
-struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
-{
- struct gsm_bts *bts = talloc_zero(net, struct gsm_bts);
- struct gsm48_multi_rate_conf mr_cfg;
- int i;
-
- if (!bts)
- return NULL;
-
- bts->nr = bts_num;
- bts->num_trx = 0;
- INIT_LLIST_HEAD(&bts->trx_list);
- bts->network = net;
-
- bts->ms_max_power = 15; /* dBm */
-
- gsm_mo_init(&bts->mo, bts, NM_OC_BTS,
- bts->nr, 0xff, 0xff);
- gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER,
- 0xff, 0xff, 0xff);
-
- for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
- bts->gprs.nsvc[i].bts = bts;
- bts->gprs.nsvc[i].id = i;
- gsm_mo_init(&bts->gprs.nsvc[i].mo, bts, NM_OC_GPRS_NSVC,
- bts->nr, i, 0xff);
- }
- memcpy(&bts->gprs.nse.timer, bts_nse_timer_default,
- sizeof(bts->gprs.nse.timer));
- gsm_mo_init(&bts->gprs.nse.mo, bts, NM_OC_GPRS_NSE,
- bts->nr, 0xff, 0xff);
- memcpy(&bts->gprs.cell.timer, bts_cell_timer_default,
- sizeof(bts->gprs.cell.timer));
- gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL,
- bts->nr, 0xff, 0xff);
- memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default,
- sizeof(bts->gprs.cell.rlc_cfg));
-
- /* init statistics */
- bts->bts_ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr);
- if (!bts->bts_ctrs) {
- talloc_free(bts);
- return NULL;
- }
- bts->bts_statg = osmo_stat_item_group_alloc(bts, &bts_statg_desc, 0);
-
- /* create our primary TRX */
- bts->c0 = gsm_bts_trx_alloc(bts);
- if (!bts->c0) {
- rate_ctr_group_free(bts->bts_ctrs);
- osmo_stat_item_group_free(bts->bts_statg);
- talloc_free(bts);
- return NULL;
- }
- bts->c0->ts[0].pchan_from_config = GSM_PCHAN_CCCH_SDCCH4; /* TODO: really?? */
-
- bts->rach_b_thresh = -1;
- bts->rach_ldavg_slots = -1;
-
- bts->paging.free_chans_need = -1;
- INIT_LLIST_HEAD(&bts->paging.pending_requests);
-
- bts->features.data = &bts->_features_data[0];
- bts->features.data_len = sizeof(bts->_features_data);
-
- /* si handling */
- bts->bcch_change_mark = 1;
- bts->chan_load_avg = 0;
-
- /* timer overrides */
- bts->T3122 = 0; /* not overriden by default */
- bts->T3113_dynamic = true; /* dynamic by default */
-
- bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED;
- bts->dtxd = false;
- bts->gprs.ctrl_ack_type_use_block = true; /* use RLC/MAC control block */
- bts->neigh_list_manual_mode = NL_MODE_AUTOMATIC;
- bts->early_classmark_allowed_3g = true; /* 3g Early Classmark Sending controlled by bts->early_classmark_allowed param */
- bts->si_unused_send_empty = true;
- bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */
- bts->si_common.cell_sel_par.rxlev_acc_min = 0;
- bts->si_common.si2quater_neigh_list.arfcn = bts->si_common.data.earfcn_list;
- bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list;
- bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST;
- bts->si_common.si2quater_neigh_list.thresh_hi = 0;
- osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
- bts->si_common.neigh_list.data = bts->si_common.data.neigh_list;
- bts->si_common.neigh_list.data_len =
- sizeof(bts->si_common.data.neigh_list);
- bts->si_common.si5_neigh_list.data = bts->si_common.data.si5_neigh_list;
- bts->si_common.si5_neigh_list.data_len =
- sizeof(bts->si_common.data.si5_neigh_list);
- bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc;
- bts->si_common.cell_alloc.data_len =
- sizeof(bts->si_common.data.cell_alloc);
- bts->si_common.rach_control.re = 1; /* no re-establishment */
- bts->si_common.rach_control.tx_integer = 9; /* 12 slots spread - 217/115 slots delay */
- bts->si_common.rach_control.max_trans = 3; /* 7 retransmissions */
- bts->si_common.rach_control.t2 = 4; /* no emergency calls */
- bts->si_common.chan_desc.att = 1; /* attachment required */
- bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5; /* paging frames */
- bts->si_common.chan_desc.bs_ag_blks_res = 1; /* reserved AGCH blocks */
- bts->si_common.chan_desc.t3212 = T_def_get(net->T_defs, 3212, T_CUSTOM, -1);
- gsm_bts_set_radio_link_timeout(bts, 32); /* Use RADIO LINK TIMEOUT of 32 */
-
- INIT_LLIST_HEAD(&bts->abis_queue);
- INIT_LLIST_HEAD(&bts->loc_list);
- INIT_LLIST_HEAD(&bts->local_neighbors);
-
- /* Enable all codecs by default. These get reset to a more fine grained selection IF a
- * 'codec-support' config appears in the config file (see bsc_vty.c). */
- bts->codec = (struct bts_codec_conf){
- .hr = 1,
- .efr = 1,
- .amr = 1,
- };
-
- /* Set reasonable defaults for AMR-FR and AMR-HR rate configuration.
- * It is possible to set up to 4 codecs per active set, while 5,15K must
- * be selected. */
- mr_cfg = (struct gsm48_multi_rate_conf) {
- .m4_75 = 0,
- .m5_15 = 1,
- .m5_90 = 1,
- .m6_70 = 0,
- .m7_40 = 0,
- .m7_95 = 0,
- .m10_2 = 1,
- .m12_2 = 1
- };
- memcpy(bts->mr_full.gsm48_ie, &mr_cfg, sizeof(bts->mr_full.gsm48_ie));
- bts->mr_full.ms_mode[0].mode = 1;
- bts->mr_full.ms_mode[1].mode = 2;
- bts->mr_full.ms_mode[2].mode = 6;
- bts->mr_full.ms_mode[3].mode = 7;
- bts->mr_full.bts_mode[0].mode = 1;
- bts->mr_full.bts_mode[1].mode = 2;
- bts->mr_full.bts_mode[2].mode = 6;
- bts->mr_full.bts_mode[3].mode = 7;
- for (i = 0; i < 3; i++) {
- bts->mr_full.ms_mode[i].hysteresis = 8;
- bts->mr_full.ms_mode[i].threshold = 32;
- bts->mr_full.bts_mode[i].hysteresis = 8;
- bts->mr_full.bts_mode[i].threshold = 32;
- }
- bts->mr_full.num_modes = 4;
-
- mr_cfg = (struct gsm48_multi_rate_conf) {
- .m4_75 = 0,
- .m5_15 = 1,
- .m5_90 = 1,
- .m6_70 = 0,
- .m7_40 = 1,
- .m7_95 = 1,
- .m10_2 = 0,
- .m12_2 = 0
- };
- memcpy(bts->mr_half.gsm48_ie, &mr_cfg, sizeof(bts->mr_half.gsm48_ie));
- bts->mr_half.ms_mode[0].mode = 1;
- bts->mr_half.ms_mode[1].mode = 2;
- bts->mr_half.ms_mode[2].mode = 4;
- bts->mr_half.ms_mode[3].mode = 5;
- bts->mr_half.bts_mode[0].mode = 1;
- bts->mr_half.bts_mode[1].mode = 2;
- bts->mr_half.bts_mode[2].mode = 4;
- bts->mr_half.bts_mode[3].mode = 5;
- for (i = 0; i < 3; i++) {
- bts->mr_half.ms_mode[i].hysteresis = 8;
- bts->mr_half.ms_mode[i].threshold = 32;
- bts->mr_half.bts_mode[i].hysteresis = 8;
- bts->mr_half.bts_mode[i].threshold = 32;
- }
- bts->mr_half.num_modes = 4;
-
- return bts;
-}
-
-/* reset the state of all MO in the BTS */
-void gsm_bts_mo_reset(struct gsm_bts *bts)
-{
- struct gsm_bts_trx *trx;
- unsigned int i;
-
- gsm_abis_mo_reset(&bts->mo);
- gsm_abis_mo_reset(&bts->site_mgr.mo);
- for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++)
- gsm_abis_mo_reset(&bts->gprs.nsvc[i].mo);
- gsm_abis_mo_reset(&bts->gprs.nse.mo);
- gsm_abis_mo_reset(&bts->gprs.cell.mo);
-
- llist_for_each_entry(trx, &bts->trx_list, list) {
- gsm_abis_mo_reset(&trx->mo);
- gsm_abis_mo_reset(&trx->bb_transc.mo);
-
- for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
- struct gsm_bts_trx_ts *ts = &trx->ts[i];
- gsm_abis_mo_reset(&ts->mo);
- }
- }
-}
-
-struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num)
-{
- struct gsm_bts_trx *trx;
-
- if (num >= bts->num_trx)
- return NULL;
-
- llist_for_each_entry(trx, &bts->trx_list, list) {
- if (trx->nr == num)
- return trx;
- }
-
- return NULL;
-}
-
static char ts2str[255];
-char *gsm_trx_name(const struct gsm_bts_trx *trx)
-{
- if (!trx)
- snprintf(ts2str, sizeof(ts2str), "(trx=NULL)");
- else
- snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)",
- trx->bts->nr, trx->nr);
-
- return ts2str;
-}
-
-
char *gsm_ts_name(const struct gsm_bts_trx_ts *ts)
{
snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)",
@@ -1046,96 +326,57 @@ char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts)
return ts2str;
}
-char *gsm_lchan_name_compute(const struct gsm_lchan *lchan)
-{
- struct gsm_bts_trx_ts *ts = lchan->ts;
-
- snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)",
- ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr);
-
- return ts2str;
-}
-
/* obtain the MO structure for a given object instance */
-static inline struct gsm_abis_mo *
-gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class,
- const struct abis_om_obj_inst *obj_inst)
+struct gsm_abis_mo *gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
{
struct gsm_bts_trx *trx;
- struct gsm_abis_mo *mo = NULL;
switch (obj_class) {
case NM_OC_BTS:
- mo = &bts->mo;
- break;
+ return &bts->mo;
case NM_OC_RADIO_CARRIER:
- if (obj_inst->trx_nr >= bts->num_trx) {
- return NULL;
- }
trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
- mo = &trx->mo;
- break;
+ return trx != NULL ? &trx->mo : NULL;
case NM_OC_BASEB_TRANSC:
- if (obj_inst->trx_nr >= bts->num_trx) {
- return NULL;
- }
trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
- mo = &trx->bb_transc.mo;
- break;
+ return trx != NULL ? &trx->bb_transc.mo : NULL;
case NM_OC_CHANNEL:
- if (obj_inst->trx_nr >= bts->num_trx) {
- return NULL;
- }
- trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
if (obj_inst->ts_nr >= TRX_NR_TS)
return NULL;
- mo = &trx->ts[obj_inst->ts_nr].mo;
- break;
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ return trx != NULL ? &trx->ts[obj_inst->ts_nr].mo : NULL;
case NM_OC_SITE_MANAGER:
- mo = &bts->site_mgr.mo;
- break;
+ return &bts->site_mgr->mo;
case NM_OC_BS11:
switch (obj_inst->bts_nr) {
case BS11_OBJ_CCLK:
- mo = &bts->bs11.cclk.mo;
- break;
+ return &bts->bs11.cclk.mo;
case BS11_OBJ_BBSIG:
- if (obj_inst->ts_nr > bts->num_trx)
- return NULL;
trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
- mo = &trx->bs11.bbsig.mo;
- break;
+ return trx != NULL ? &trx->bs11.bbsig.mo : NULL;
case BS11_OBJ_PA:
- if (obj_inst->ts_nr > bts->num_trx)
- return NULL;
trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
- mo = &trx->bs11.pa.mo;
- break;
- default:
- return NULL;
+ return trx != NULL ? &trx->bs11.pa.mo : NULL;
}
break;
case NM_OC_BS11_RACK:
- mo = &bts->bs11.rack.mo;
- break;
+ return &bts->bs11.rack.mo;
case NM_OC_BS11_ENVABTSE:
if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse))
return NULL;
- mo = &bts->bs11.envabtse[obj_inst->trx_nr].mo;
- break;
+ return &bts->bs11.envabtse[obj_inst->trx_nr].mo;
case NM_OC_GPRS_NSE:
- mo = &bts->gprs.nse.mo;
- break;
+ return &bts->site_mgr->gprs.nse.mo;
case NM_OC_GPRS_CELL:
- mo = &bts->gprs.cell.mo;
- break;
+ return &bts->gprs.cell.mo;
case NM_OC_GPRS_NSVC:
- if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->site_mgr->gprs.nsvc))
return NULL;
- mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo;
- break;
+ return &bts->site_mgr->gprs.nsvc[obj_inst->trx_nr].mo;
}
- return mo;
+
+ return NULL;
}
/* obtain the gsm_nm_state data structure for a given object instance */
@@ -1188,43 +429,51 @@ gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class,
obj = &trx->ts[obj_inst->ts_nr];
break;
case NM_OC_SITE_MANAGER:
- obj = &bts->site_mgr;
+ obj = bts->site_mgr;
break;
case NM_OC_GPRS_NSE:
- obj = &bts->gprs.nse;
+ obj = &bts->site_mgr->gprs.nse;
break;
case NM_OC_GPRS_CELL:
obj = &bts->gprs.cell;
break;
case NM_OC_GPRS_NSVC:
- if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->site_mgr->gprs.nsvc))
return NULL;
- obj = &bts->gprs.nsvc[obj_inst->trx_nr];
+ obj = &bts->site_mgr->gprs.nsvc[obj_inst->trx_nr];
break;
}
return obj;
}
/* See Table 10.5.25 of GSM04.08 */
-uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
- uint8_t ts_nr, uint8_t lchan_nr)
+int gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
+ uint8_t ts_nr, uint8_t lchan_nr, bool vamos_is_secondary)
{
uint8_t cbits, chan_nr;
switch (pchan) {
case GSM_PCHAN_TCH_F:
case GSM_PCHAN_TCH_F_PDCH:
- OSMO_ASSERT(lchan_nr == 0);
- cbits = 0x01;
+ if (lchan_nr != 0)
+ return -EINVAL;
+ if (vamos_is_secondary)
+ cbits = ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Bm_ACCHs;
+ else
+ cbits = ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs;
break;
case GSM_PCHAN_PDCH:
- OSMO_ASSERT(lchan_nr == 0);
- cbits = RSL_CHAN_OSMO_PDCH >> 3;
+ if (lchan_nr != 0)
+ return -EINVAL;
+ cbits = ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH;
break;
case GSM_PCHAN_TCH_H:
- OSMO_ASSERT(lchan_nr < 2);
- cbits = 0x02;
- cbits += lchan_nr;
+ if (lchan_nr >= 2)
+ return -EINVAL;
+ if (vamos_is_secondary)
+ cbits = ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(lchan_nr);
+ else
+ cbits = ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(lchan_nr);
break;
case GSM_PCHAN_CCCH_SDCCH4:
case GSM_PCHAN_CCCH_SDCCH4_CBCH:
@@ -1234,22 +483,22 @@ uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
* See osmo-bts-xxx/oml.c:opstart_compl().
*/
if (lchan_nr == CCCH_LCHAN)
- chan_nr = 0;
- else
- OSMO_ASSERT(lchan_nr < 4);
- cbits = 0x04;
- cbits += lchan_nr;
+ lchan_nr = 0;
+ else if (lchan_nr > 4)
+ return -EINVAL;
+ cbits = ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(lchan_nr);
break;
case GSM_PCHAN_SDCCH8_SACCH8C:
case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
- OSMO_ASSERT(lchan_nr < 8);
- cbits = 0x08;
- cbits += lchan_nr;
+ if (lchan_nr >= 8)
+ return -EINVAL;
+ cbits = ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(lchan_nr);
break;
default:
case GSM_PCHAN_CCCH:
- OSMO_ASSERT(lchan_nr == 0);
- cbits = 0x10;
+ if (lchan_nr != 0)
+ return -EINVAL;
+ cbits = ABIS_RSL_CHAN_NR_CBITS_BCCH;
break;
}
@@ -1258,74 +507,43 @@ uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
return chan_nr;
}
-uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan)
+/* For RSL, to talk to osmo-bts, we introduce Osmocom specific channel number cbits to indicate VAMOS secondary lchans.
+ * However, in RR, which is sent to the MS, these special cbits must not be sent, but their "normal" equivalent; for RR
+ * messages, pass allow_osmo_cbits = false. */
+int gsm_lchan_and_pchan2chan_nr(const struct gsm_lchan *lchan, enum gsm_phys_chan_config pchan, bool allow_osmo_cbits)
{
- /* Note: non-standard Osmocom style dyn TS PDCH mode chan_nr is only used within
- * rsl_tx_dyn_ts_pdch_act_deact(). */
- return gsm_pchan2chan_nr(lchan->ts->pchan_is, lchan->ts->nr, lchan->nr);
-}
-
-/* return the gsm_lchan for the CBCH (if it exists at all) */
-struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts)
-{
- struct gsm_lchan *lchan = NULL;
- struct gsm_bts_trx *trx = bts->c0;
-
- if (trx->ts[0].pchan_from_config == GSM_PCHAN_CCCH_SDCCH4_CBCH)
- lchan = &trx->ts[0].lchan[2];
- else {
- int i;
- for (i = 0; i < 8; i++) {
- if (trx->ts[i].pchan_from_config == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) {
- lchan = &trx->ts[i].lchan[2];
- break;
- }
- }
+ int rc;
+ uint8_t lchan_nr = lchan->nr;
+
+ /* Take care that we never send Osmocom specific cbits to non-Osmo BTS. */
+ if (allow_osmo_cbits && lchan->vamos.is_secondary
+ && lchan->ts->trx->bts->model->type != GSM_BTS_TYPE_OSMOBTS) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Cannot address VAMOS shadow lchan on this BTS type: %s\n",
+ get_value_string(bts_type_names, lchan->ts->trx->bts->model->type));
+ return -ENOTSUP;
}
-
- return lchan;
+ if (allow_osmo_cbits && lchan->ts->trx->bts->model->type != GSM_BTS_TYPE_OSMOBTS)
+ allow_osmo_cbits = false;
+
+ /* The VAMOS lchans are behind the primary ones in the ts->lchan[] array. They keep their lchan->nr as in the
+ * array, but on the wire they are the "shadow" lchans for the primary lchans. For example, for TCH/F, there is
+ * a primary ts->lchan[0] and a VAMOS ts->lchan[1]. Still, the VAMOS lchan should send chan_nr = 0. */
+ if (lchan->vamos.is_secondary)
+ lchan_nr -= lchan->ts->max_primary_lchans;
+ rc = gsm_pchan2chan_nr(pchan, lchan->ts->nr, lchan_nr,
+ allow_osmo_cbits ? lchan->vamos.is_secondary : false);
+ /* Log an error so that we don't need to add logging to each caller of this function */
+ if (rc < 0)
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Error encoding Channel Number: pchan %s ts %u ss %u%s\n",
+ gsm_pchan_name(lchan->ts->pchan_from_config), lchan->ts->nr, lchan_nr,
+ lchan->vamos.is_secondary ? " (VAMOS shadow)" : "");
+ return rc;
}
-/* determine logical channel based on TRX and channel number IE */
-struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
- int *rc)
+int gsm_lchan2chan_nr(const struct gsm_lchan *lchan, bool allow_osmo_cbits)
{
- uint8_t ts_nr = chan_nr & 0x07;
- uint8_t cbits = chan_nr >> 3;
- uint8_t lch_idx;
- struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
- bool ok;
-
- if (rc)
- *rc = -EINVAL;
-
- if (cbits == 0x01) {
- lch_idx = 0; /* TCH/F */
- ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_F)
- || ts->pchan_on_init == GSM_PCHAN_PDCH; /* PDCH? really? */
- } else if ((cbits & 0x1e) == 0x02) {
- lch_idx = cbits & 0x1; /* TCH/H */
- ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_H);
- } else if ((cbits & 0x1c) == 0x04) {
- lch_idx = cbits & 0x3; /* SDCCH/4 */
- ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH_SDCCH4);
- } else if ((cbits & 0x18) == 0x08) {
- lch_idx = cbits & 0x7; /* SDCCH/8 */
- ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_SDCCH8_SACCH8C);
- } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) {
- lch_idx = 0; /* CCCH? */
- ok = ts_is_capable_of_pchan(ts, GSM_PCHAN_CCCH);
- /* FIXME: we should not return first sdcch4 !!! */
- } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) {
- lch_idx = 0;
- ok = (ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH);
- } else
- return NULL;
-
- if (rc && ok)
- *rc = 0;
-
- return &ts->lchan[lch_idx];
+ return gsm_lchan_and_pchan2chan_nr(lchan, lchan->ts->pchan_is, allow_osmo_cbits);
}
static const uint8_t subslots_per_pchan[] = {
@@ -1339,12 +557,12 @@ static const uint8_t subslots_per_pchan[] = {
[GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4,
[GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8,
/* Dyn TS: maximum allowed subslots */
- [GSM_PCHAN_TCH_F_TCH_H_PDCH] = 2,
+ [GSM_PCHAN_OSMO_DYN] = 8,
[GSM_PCHAN_TCH_F_PDCH] = 1,
};
-/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of
- * logical channels available in the timeslot. */
+/*! Return the maximum number of logical channels that may be used in a timeslot of the given physical channel
+ * configuration. */
uint8_t pchan_subslots(enum gsm_phys_chan_config pchan)
{
if (pchan < 0 || pchan >= ARRAY_SIZE(subslots_per_pchan))
@@ -1352,6 +570,30 @@ uint8_t pchan_subslots(enum gsm_phys_chan_config pchan)
return subslots_per_pchan[pchan];
}
+static const uint8_t subslots_per_pchan_vamos[] = {
+ [GSM_PCHAN_NONE] = 0,
+ [GSM_PCHAN_CCCH] = 0,
+ [GSM_PCHAN_PDCH] = 0,
+ [GSM_PCHAN_CCCH_SDCCH4] = 0,
+ /* VAMOS: on a TCH/F, there may be a TCH/H shadow */
+ [GSM_PCHAN_TCH_F] = 2,
+ [GSM_PCHAN_TCH_H] = 2,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = 0,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 0,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 0,
+ [GSM_PCHAN_OSMO_DYN] = 0,
+ [GSM_PCHAN_TCH_F_PDCH] = 2,
+};
+
+/* Return the maximum number of VAMOS secondary lchans that may be used in a timeslot of the given physical channel
+ * configuration. */
+uint8_t pchan_subslots_vamos(enum gsm_phys_chan_config pchan)
+{
+ if (pchan < 0 || pchan >= ARRAY_SIZE(subslots_per_pchan_vamos))
+ return 0;
+ return subslots_per_pchan_vamos[pchan];
+}
+
static bool pchan_is_tch(enum gsm_phys_chan_config pchan)
{
switch (pchan) {
@@ -1368,44 +610,24 @@ bool ts_is_tch(struct gsm_bts_trx_ts *ts)
return pchan_is_tch(ts->pchan_is);
}
-bool trx_is_usable(const struct gsm_bts_trx *trx)
-{
- /* FIXME: How does this behave for BS-11 ? */
- if (is_ipaccess_bts(trx->bts)) {
- if (!nm_is_running(&trx->mo.nm_state) ||
- !nm_is_running(&trx->bb_transc.mo.nm_state))
- return false;
- }
-
- return true;
-}
-
-void gsm_trx_all_ts_dispatch(struct gsm_bts_trx *trx, uint32_t ts_ev, void *data)
-{
- int i;
- for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
- struct gsm_bts_trx_ts *ts = &trx->ts[i];
- osmo_fsm_inst_dispatch(ts->fi, ts_ev, data);
- }
-}
-
-void gsm_bts_all_ts_dispatch(struct gsm_bts *bts, uint32_t ts_ev, void *data)
-{
- struct gsm_bts_trx *trx;
- llist_for_each_entry(trx, &bts->trx_list, list)
- gsm_trx_all_ts_dispatch(trx, ts_ev, data);
+struct gsm_bts *conn_get_bts(struct gsm_subscriber_connection *conn) {
+ if (!conn || !conn->lchan)
+ return NULL;
+ return conn->lchan->ts->trx->bts;
}
-static void _chan_desc_fill_tail(struct gsm48_chan_desc *cd, const struct gsm_lchan *lchan)
+static void _chan_desc_fill_tail(struct gsm48_chan_desc *cd, const struct gsm_lchan *lchan,
+ uint8_t tsc)
{
if (!lchan->ts->hopping.enabled) {
uint16_t arfcn = lchan->ts->trx->arfcn & 0x3ff;
- cd->h0.tsc = gsm_ts_tsc(lchan->ts);
+ cd->h0.tsc = tsc;
cd->h0.h = 0;
+ cd->h0.spare = 0;
cd->h0.arfcn_high = arfcn >> 8;
cd->h0.arfcn_low = arfcn & 0xff;
} else {
- cd->h1.tsc = gsm_ts_tsc(lchan->ts);
+ cd->h1.tsc = tsc;
cd->h1.h = 1;
cd->h1.maio_high = lchan->ts->hopping.maio >> 2;
cd->h1.maio_low = lchan->ts->hopping.maio & 0x03;
@@ -1413,28 +635,48 @@ static void _chan_desc_fill_tail(struct gsm48_chan_desc *cd, const struct gsm_lc
}
}
-void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd,
- const struct gsm_lchan *lchan)
+int gsm48_lchan_and_pchan2chan_desc(struct gsm48_chan_desc *cd,
+ const struct gsm_lchan *lchan,
+ enum gsm_phys_chan_config pchan,
+ uint8_t tsc, bool allow_osmo_cbits)
+{
+ int chan_nr = gsm_lchan_and_pchan2chan_nr(lchan, pchan, allow_osmo_cbits);
+ if (chan_nr < 0) {
+ /* Log an error so that we don't need to add logging to each caller of this function */
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Error encoding Channel Number: pchan %s ts %u ss %u%s (rc = %d)\n",
+ gsm_pchan_name(pchan), lchan->ts->nr, lchan->nr,
+ lchan->vamos.is_secondary ? " (VAMOS shadow)" : "", chan_nr);
+ return chan_nr;
+ }
+ cd->chan_nr = chan_nr;
+ _chan_desc_fill_tail(cd, lchan, tsc);
+ return 0;
+}
+
+int gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd,
+ const struct gsm_lchan *lchan,
+ uint8_t tsc, bool allow_osmo_cbits)
{
- cd->chan_nr = gsm_lchan2chan_nr(lchan);
- _chan_desc_fill_tail(cd, lchan);
+ return gsm48_lchan_and_pchan2chan_desc(cd, lchan, lchan->ts->pchan_is, tsc, allow_osmo_cbits);
}
-/* like gsm48_lchan2chan_desc() above, but use ts->pchan_from_config to
- * return a channel description based on what is configured, rather than
- * what the current state of the pchan type is */
-void gsm48_lchan2chan_desc_as_configured(struct gsm48_chan_desc *cd,
- const struct gsm_lchan *lchan)
+uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts)
{
- cd->chan_nr = gsm_pchan2chan_nr(lchan->ts->pchan_from_config, lchan->ts->nr, lchan->nr);
- _chan_desc_fill_tail(cd, lchan);
+ if (ts->tsc != -1)
+ return ts->tsc;
+ else
+ return ts->trx->bts->bsic & 7;
}
bool nm_is_running(const struct gsm_nm_state *s) {
- return (s->operational == NM_OPSTATE_ENABLED) && (
- (s->availability == NM_AVSTATE_OK) ||
- (s->availability == 0xff)
- );
+ if (s->operational != NM_OPSTATE_ENABLED)
+ return false;
+ if (s->availability != NM_AVSTATE_OK)
+ return false;
+ if (s->administrative != NM_STATE_UNLOCKED)
+ return false;
+ return true;
}
/* determine the logical channel type based on the physical channel type */
@@ -1462,6 +704,8 @@ enum gsm_phys_chan_config gsm_pchan_by_lchan_type(enum gsm_chan_t type)
return GSM_PCHAN_TCH_F;
case GSM_LCHAN_TCH_H:
return GSM_PCHAN_TCH_H;
+ case GSM_LCHAN_SDCCH:
+ return GSM_PCHAN_SDCCH8_SACCH8C;
case GSM_LCHAN_NONE:
case GSM_LCHAN_PDTCH:
/* TODO: so far lchan->type is NONE in PDCH mode. PDTCH is only
@@ -1473,6 +717,22 @@ enum gsm_phys_chan_config gsm_pchan_by_lchan_type(enum gsm_chan_t type)
}
}
+enum channel_rate chan_t_to_chan_rate(enum gsm_chan_t chan_t)
+{
+ switch (chan_t) {
+ case GSM_LCHAN_SDCCH:
+ return CH_RATE_SDCCH;
+ case GSM_LCHAN_TCH_F:
+ return CH_RATE_FULL;
+ case GSM_LCHAN_TCH_H:
+ return CH_RATE_HALF;
+ default:
+ /* For other channel types, the channel_rate value is never used. It is fine to return an invalid value,
+ * and callers don't actually need to check for this. */
+ return -1;
+ }
+}
+
/* Can the timeslot in principle be used as this PCHAN kind? */
bool ts_is_capable_of_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan)
{
@@ -1486,11 +746,12 @@ bool ts_is_capable_of_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config
return false;
}
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
switch (pchan) {
case GSM_PCHAN_TCH_F:
case GSM_PCHAN_TCH_H:
case GSM_PCHAN_PDCH:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
return true;
default:
return false;
@@ -1558,11 +819,12 @@ bool ts_is_capable_of_lchant(struct gsm_bts_trx_ts *ts, enum gsm_chan_t type)
return false;
}
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
switch (type) {
case GSM_LCHAN_TCH_F:
case GSM_LCHAN_TCH_H:
case GSM_LCHAN_PDTCH:
+ case GSM_LCHAN_SDCCH:
return true;
default:
return false;
@@ -1602,72 +864,10 @@ bool ts_is_capable_of_lchant(struct gsm_bts_trx_ts *ts, enum gsm_chan_t type)
}
}
-static int trx_count_free_ts(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan)
-{
- struct gsm_bts_trx_ts *ts;
- struct gsm_lchan *lchan;
- int j;
- int count = 0;
-
- if (!trx_is_usable(trx))
- return 0;
-
- for (j = 0; j < ARRAY_SIZE(trx->ts); j++) {
- ts = &trx->ts[j];
- if (!ts_is_usable(ts))
- continue;
-
- if (ts->pchan_is == GSM_PCHAN_PDCH) {
- /* Dynamic timeslots in PDCH mode will become TCH if needed. */
- switch (ts->pchan_on_init) {
- case GSM_PCHAN_TCH_F_PDCH:
- if (pchan == GSM_PCHAN_TCH_F)
- count++;
- continue;
-
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
- if (pchan == GSM_PCHAN_TCH_F)
- count++;
- else if (pchan == GSM_PCHAN_TCH_H)
- count += 2;
- continue;
-
- default:
- /* Not dynamic, not applicable. */
- continue;
- }
- }
-
- if (ts->pchan_is != pchan)
- continue;
-
- ts_for_each_lchan(lchan, ts) {
- if (lchan_state_is(lchan, LCHAN_ST_UNUSED))
- count++;
- }
- }
-
- return count;
-}
-
-/* Count number of free TS of given pchan type */
-int bts_count_free_ts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
-{
- struct gsm_bts_trx *trx;
- int count = 0;
-
- llist_for_each_entry(trx, &bts->trx_list, list)
- count += trx_count_free_ts(trx, pchan);
-
- return count;
-}
-
bool ts_is_usable(const struct gsm_bts_trx_ts *ts)
{
- if (!trx_is_usable(ts->trx)) {
- LOGP(DRLL, LOGL_DEBUG, "%s not usable\n", gsm_trx_name(ts->trx));
+ if (!trx_is_usable(ts->trx))
return false;
- }
if (!ts->fi)
return false;
@@ -1683,27 +883,146 @@ bool ts_is_usable(const struct gsm_bts_trx_ts *ts)
return true;
}
+void conn_update_ms_power_class(struct gsm_subscriber_connection *conn, uint8_t power_class)
+{
+ struct gsm_bts *bts = conn_get_bts(conn);
+
+ /* MS Power class remains the same => do nothing */
+ if (power_class == conn->ms_power_class)
+ return;
+
+ LOGP(DRLL, LOGL_DEBUG, "MS Power class update: %" PRIu8 " -> %" PRIu8 "\n",
+ conn->ms_power_class, power_class);
+
+ conn->ms_power_class = power_class;
+
+ /* If there's an associated lchan, attempt to update its max power to be
+ on track with band maximum values */
+ if (bts && conn->lchan)
+ lchan_update_ms_power_ctrl_level(conn->lchan, bts->ms_max_power);
+}
+
const struct value_string lchan_activate_mode_names[] = {
- OSMO_VALUE_STRING(FOR_NONE),
- OSMO_VALUE_STRING(FOR_MS_CHANNEL_REQUEST),
- OSMO_VALUE_STRING(FOR_ASSIGNMENT),
- OSMO_VALUE_STRING(FOR_HANDOVER),
- OSMO_VALUE_STRING(FOR_VTY),
+ OSMO_VALUE_STRING(ACTIVATE_FOR_NONE),
+ OSMO_VALUE_STRING(ACTIVATE_FOR_MS_CHANNEL_REQUEST),
+ OSMO_VALUE_STRING(ACTIVATE_FOR_ASSIGNMENT),
+ OSMO_VALUE_STRING(ACTIVATE_FOR_HANDOVER),
+ OSMO_VALUE_STRING(ACTIVATE_FOR_VGCS_CHANNEL),
+ OSMO_VALUE_STRING(ACTIVATE_FOR_VTY),
{}
};
-struct osmo_cell_global_id *cgi_for_msc(struct bsc_msc_data *msc, struct gsm_bts *bts)
-{
- static struct osmo_cell_global_id cgi;
- cgi.lai.plmn = msc->network->plmn;
- if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
- cgi.lai.plmn.mcc = msc->core_plmn.mcc;
- if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID) {
- cgi.lai.plmn.mnc = msc->core_plmn.mnc;
- cgi.lai.plmn.mnc_3_digits = msc->core_plmn.mnc_3_digits;
+const struct value_string lchan_modify_for_names[] = {
+ OSMO_VALUE_STRING(MODIFY_FOR_NONE),
+ OSMO_VALUE_STRING(MODIFY_FOR_ASSIGNMENT),
+ OSMO_VALUE_STRING(MODIFY_FOR_VTY),
+ {}
+};
+
+const struct value_string assign_for_names[] = {
+ OSMO_VALUE_STRING(ASSIGN_FOR_NONE),
+ OSMO_VALUE_STRING(ASSIGN_FOR_BSSMAP_REQ),
+ OSMO_VALUE_STRING(ASSIGN_FOR_CONGESTION_RESOLUTION),
+ OSMO_VALUE_STRING(ASSIGN_FOR_VTY),
+ {}
+};
+
+/* This may be specific to RR Channel Release, and the mappings were chosen by pure naive guessing without a proper
+ * specification available. */
+enum gsm48_rr_cause bsc_gsm48_rr_cause_from_gsm0808_cause(enum gsm0808_cause c)
+{
+ switch (c) {
+ case GSM0808_CAUSE_PREEMPTION:
+ return GSM48_RR_CAUSE_PREMPTIVE_REL;
+ case GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE:
+ case GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS:
+ case GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING:
+ case GSM0808_CAUSE_INCORRECT_VALUE:
+ case GSM0808_CAUSE_UNKNOWN_MESSAGE_TYPE:
+ case GSM0808_CAUSE_UNKNOWN_INFORMATION_ELEMENT:
+ return GSM48_RR_CAUSE_PROT_ERROR_UNSPC;
+ case GSM0808_CAUSE_CALL_CONTROL:
+ case GSM0808_CAUSE_HANDOVER_SUCCESSFUL:
+ case GSM0808_CAUSE_BETTER_CELL:
+ case GSM0808_CAUSE_DIRECTED_RETRY:
+ case GSM0808_CAUSE_REDUCE_LOAD_IN_SERVING_CELL:
+ case GSM0808_CAUSE_RELOCATION_TRIGGERED:
+ case GSM0808_CAUSE_ALT_CHAN_CONFIG_REQUESTED:
+ return GSM48_RR_CAUSE_NORMAL;
+ default:
+ return GSM48_RR_CAUSE_ABNORMAL_UNSPEC;
+ }
+}
+
+/* Map RSL_ERR_* cause codes to gsm48_rr_cause codes.
+ * The mappings were chosen by naive guessing without a proper specification available. */
+enum gsm48_rr_cause bsc_gsm48_rr_cause_from_rsl_cause(uint8_t c)
+{
+ switch (c) {
+ case RSL_ERR_NORMAL_UNSPEC:
+ return GSM48_RR_CAUSE_NORMAL;
+ case RSL_ERR_MAND_IE_ERROR:
+ return GSM48_RR_CAUSE_INVALID_MAND_INF;
+ case RSL_ERR_OPT_IE_ERROR:
+ return GSM48_RR_CAUSE_COND_IE_ERROR;
+ case RSL_ERR_INVALID_MESSAGE:
+ case RSL_ERR_MSG_DISCR:
+ case RSL_ERR_MSG_TYPE:
+ case RSL_ERR_MSG_SEQ:
+ case RSL_ERR_IE_ERROR:
+ case RSL_ERR_IE_NONEXIST:
+ case RSL_ERR_IE_LENGTH:
+ case RSL_ERR_IE_CONTENT:
+ case RSL_ERR_PROTO:
+ return GSM48_RR_CAUSE_PROT_ERROR_UNSPC;
+ default:
+ return GSM48_RR_CAUSE_ABNORMAL_UNSPEC;
+ }
+}
+
+/* Default Interference Measurement Parameters */
+const struct gsm_interf_meas_params interf_meas_params_def = {
+ .avg_period = 6, /* 6 SACCH periods */
+ .bounds_dbm = {
+ 115, /* 0: -115 dBm */
+ 109, /* X1: -109 dBm */
+ 103, /* X2: -103 dBm */
+ 97, /* X3: -97 dBm */
+ 91, /* X4: -91 dBm */
+ 85, /* X5: -85 dBm */
+ },
+};
+
+enum rsl_cmod_spd chan_mode_to_rsl_cmod_spd(enum gsm48_chan_mode chan_mode)
+{
+ switch (gsm48_chan_mode_to_non_vamos(chan_mode)) {
+ case GSM48_CMODE_SIGN:
+ return RSL_CMOD_SPD_SIGN;
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_SPEECH_AMR:
+ return RSL_CMOD_SPD_SPEECH;
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ return RSL_CMOD_SPD_DATA;
+ default:
+ return -EINVAL;
}
- cgi.lai.lac = (msc->core_lac != -1) ? msc->core_lac : bts->location_area_code;
- cgi.cell_identity = (msc->core_ci != -1) ? msc->core_ci : bts->cell_identity;
+}
- return &cgi;
+int gsm_audio_support_cmp(const struct gsm_audio_support *a, const struct gsm_audio_support *b)
+{
+ int rc;
+ if (a == b)
+ return 0;
+ if (!a)
+ return -1;
+ if (!b)
+ return 1;
+ rc = OSMO_CMP(a->hr, b->hr);
+ if (rc)
+ return rc;
+ return OSMO_CMP(a->ver, b->ver);
}
diff --git a/src/osmo-bsc/gsm_timers.c b/src/osmo-bsc/gsm_timers.c
deleted file mode 100644
index fc3ec246e..000000000
--- a/src/osmo-bsc/gsm_timers.c
+++ /dev/null
@@ -1,207 +0,0 @@
-/* Implementation to define Tnnn timers globally and use for FSM state changes. */
-/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
- *
- * Author: Neels Hofmeyr <neels@hofmeyr.de>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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/core/fsm.h>
-
-#include <osmocom/bsc/gsm_timers.h>
-
-/* a = return_val * b. Return 0 if factor is below 1. */
-static int T_factor(enum T_unit a, enum T_unit b)
-{
- if (b == a
- || b == T_CUSTOM || a == T_CUSTOM)
- return 1;
-
- switch (b) {
- case T_MS:
- switch (a) {
- case T_S:
- return 1000;
- case T_M:
- return 60*1000;
- default:
- return 0;
- }
- case T_S:
- switch (a) {
- case T_M:
- return 60;
- default:
- return 0;
- }
- default:
- return 0;
- }
-}
-
-static int T_round(int val, enum T_unit from_unit, enum T_unit to_unit)
-{
- int f;
- if (!val)
- return 0;
-
- f = T_factor(from_unit, to_unit);
- if (f < 1) {
- f = T_factor(to_unit, from_unit);
- return (val / f) + (val % f? 1 : 0);
- }
- return val * f;
-}
-
-/* Return the value of a T timer from a list of T_defs.
- * Any value is rounded up to match as_unit: 1100 ms as T_S becomes 2 seconds, as T_M becomes one minute.
- * If no such timer is defined, return the default value passed, or abort the program if default < 0.
- *
- * Usage examples:
- *
- * - Initialization:
- *
- * struct T_def global_T_defs[] = {
- * { .T=7, .default_val=50, .desc="Water Boiling Timeout" }, // default is .unit=T_S == 0
- * { .T=8, .default_val=300, .desc="Tea brewing" },
- * { .T=9, .default_val=5, .unit=T_M, .desc="Let tea cool down before drinking" },
- * { .T=10, .default_val=20, .unit=T_M, .desc="Forgot to drink tea while it's warm" },
- * {} // <-- important! last entry shall be zero
- * };
- * T_defs_reset(global_T_defs); // make all values the default
- * T_defs_vty_init(global_T_defs, CONFIG_NODE);
- *
- * val = T_def_get(global_T_defs, 7, T_S, -1); // -> 50
- * sleep(val);
- *
- * val = T_def_get(global_T_defs, 7, T_M, -1); // 50 seconds becomes 1 minute -> 1
- * sleep_minutes(val);
- *
- * val = T_def_get(global_T_defs, 99, T_S, -1); // not defined, program aborts!
- *
- * val = T_def_get(global_T_defs, 99, T_S, 3); // not defined, returns 3
- */
-int T_def_get(const struct T_def *T_defs, int T, enum T_unit as_unit, int val_if_not_present)
-{
- const struct T_def *d = T_def_get_entry((struct T_def*)T_defs, T);
- if (!d) {
- OSMO_ASSERT(val_if_not_present >= 0);
- return val_if_not_present;
- }
- return T_round(d->val, d->unit, as_unit);
-}
-
-/* Set all T_def values to the default_val. */
-void T_defs_reset(struct T_def *T_defs)
-{
- struct T_def *d;
- for_each_T_def(d, T_defs)
- d->val = d->default_val;
-}
-
-/* Return a pointer to a T_def from an array, or NULL. */
-struct T_def *T_def_get_entry(struct T_def *T_defs, int T)
-{
- struct T_def *d;
- for_each_T_def(d, T_defs) {
- if (d->T == T)
- return d;
- }
- return NULL;
-}
-
-/* Return a state_timeout entry from an array, or return NULL if the entry is zero.
- *
- * The timeouts_array shall contain exactly 32 elements, which corresponds to the number of states
- * allowed by osmo_fsm_*. Lookup is by array index.
- *
- * For example:
- * struct state_timeout my_fsm_timeouts[32] = {
- * [MY_FSM_STATE_3] = { .T = 423 },
- * [MY_FSM_STATE_7] = { .T = 235 },
- * [MY_FSM_STATE_8] = { .keep_timer = true },
- * // any state that is omitted will remain zero == no timeout
- * };
- * get_state_timeout(MY_FSM_STATE_0, &my_fsm_timeouts) -> NULL,
- * get_state_timeout(MY_FSM_STATE_7, &my_fsm_timeouts) -> { .T = 235 }
- *
- * The intention is then to obtain the timer like T_def_get(global_T_defs, T=235); see also
- * fsm_inst_state_chg_T() below.
- */
-const struct state_timeout *get_state_timeout(uint32_t state,
- const struct state_timeout *timeouts_array)
-{
- const struct state_timeout *t;
- OSMO_ASSERT(state < 32);
- t = &timeouts_array[state];
- if (!t->keep_timer && !t->T)
- return NULL;
- return t;
-}
-
-/* Call osmo_fsm_inst_state_chg() or osmo_fsm_inst_state_chg_keep_timer(), depending on the T value
- * defined for this state in the timeouts_array, and obtaining the actual timeout value from T_defs.
- * A T timer configured in sub-second precision is rounded up to the next full second.
- *
- * See get_state_timeout() and T_def_get().
- *
- * Should a T number be defined in timeouts_array that is not defined in T_defs, use default_timeout.
- * This is best used by wrapping this function call in a macro suitable for a specific FSM
- * implementation, which can become as short as: my_fsm_state_chg(fi, NEXT_STATE):
- *
- * #define my_fsm_state_chg(fi, NEXT_STATE) \
- * fsm_inst_state_chg_T(fi, NEXT_STATE, my_fsm_timeouts, global_T_defs, 5)
- *
- * my_fsm_state_chg(fi, MY_FSM_STATE_1);
- * // -> No timeout configured, will enter state without timeout.
- *
- * my_fsm_state_chg(fi, MY_FSM_STATE_3);
- * // T423 configured for this state, will look up T423 in T_defs, or use 5 seconds if unset.
- *
- * my_fsm_state_chg(fi, MY_FSM_STATE_8);
- * // keep_timer configured for this state, will invoke osmo_fsm_inst_state_chg_keep_timer().
- *
- */
-int _fsm_inst_state_chg_T(struct osmo_fsm_inst *fi, uint32_t state,
- const struct state_timeout *timeouts_array,
- const struct T_def *T_defs, int default_timeout,
- const char *file, int line)
-{
- const struct state_timeout *t = get_state_timeout(state, timeouts_array);
- int val;
-
- /* No timeout defined for this state? */
- if (!t)
- return _osmo_fsm_inst_state_chg(fi, state, 0, 0, file, line);
-
- if (t->keep_timer) {
- int rc = _osmo_fsm_inst_state_chg_keep_timer(fi, state, file, line);
- if (t->T && !rc)
- fi->T = t->T;
- return rc;
- }
-
- val = T_def_get(T_defs, t->T, T_S, default_timeout);
- return _osmo_fsm_inst_state_chg(fi, state, val, t->T, file, line);
-}
-
-const struct value_string T_unit_names[] = {
- { T_S, "s" },
- { T_MS, "ms" },
- { T_CUSTOM, "(custom)" },
- { 0, NULL }
-};
diff --git a/src/osmo-bsc/gsm_timers_vty.c b/src/osmo-bsc/gsm_timers_vty.c
deleted file mode 100644
index e744dfac5..000000000
--- a/src/osmo-bsc/gsm_timers_vty.c
+++ /dev/null
@@ -1,118 +0,0 @@
-/* Implementation to configure Tnnn timers in VTY */
-/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
- *
- * Author: Neels Hofmeyr <neels@hofmeyr.de>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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 <string.h>
-
-#include <osmocom/vty/vty.h>
-#include <osmocom/vty/command.h>
-
-#include <osmocom/bsc/gsm_timers.h>
-
-/* Global singleton list used for the VTY configuration. See T_defs_vty_init(). */
-static struct T_def *g_vty_T_defs = NULL;
-
-/* Parse an argument like "T1234", "t1234" or "1234" and return the corresponding T_def entry from
- * g_vty_T_defs, if any. */
-struct T_def *parse_T_arg(struct vty *vty, const char *T_str)
-{
- int T;
- struct T_def *d;
-
- if (T_str[0] == 't' || T_str[0] == 'T')
- T_str++;
- T = atoi(T_str);
-
- d = T_def_get_entry(g_vty_T_defs, T);
- if (!d)
- vty_out(vty, "No such timer: T%d%s", T, VTY_NEWLINE);
- return d;
-}
-
-/* Installed in the VTY on T_defs_vty_init(). */
-DEFUN(cfg_timer, cfg_timer_cmd,
- "timer TNNNN (default|<1-65535>)",
- "Configure GSM Timers\n"
- "T-number, optionally preceded by 't' or 'T'."
- "See also 'show timer' for a list of available timers.\n"
- "Set to default timer value\n" "Timer value\n")
-{
- const char *val_str = argv[1];
- struct T_def *d;
-
- d = parse_T_arg(vty, argv[0]);
- if (!d)
- return CMD_WARNING;
-
- if (!strcmp(val_str, "default"))
- d->val = d->default_val;
- else
- d->val = atoi(val_str);
- vty_out(vty, "T%d = %u %s (%s)%s", d->T, d->val, T_unit_name(d->unit), d->desc, VTY_NEWLINE);
- return CMD_SUCCESS;
-}
-
-/* Print a T_def to the VTY. */
-static void show_one_timer(struct vty *vty, struct T_def *d)
-{
- vty_out(vty, "T%d = %u %s (default = %u %s) \t%s%s",
- d->T, d->val, T_unit_name(d->unit),
- d->default_val, T_unit_name(d->unit), d->desc, VTY_NEWLINE);
-}
-
-/* Installed in the VTY on T_defs_vty_init(). */
-DEFUN(show_timer, show_timer_cmd,
- "show timer [TNNNN]",
- SHOW_STR "GSM Timers\n"
- "Specific timer to show, or all timers if omitted.\n")
-{
- struct T_def *d;
-
- if (argc) {
- d = parse_T_arg(vty, argv[0]);
- if (!d)
- return CMD_WARNING;
- show_one_timer(vty, d);
- return CMD_SUCCESS;
- }
-
- for_each_T_def(d, g_vty_T_defs)
- show_one_timer(vty, d);
- return CMD_SUCCESS;
-}
-
-/* Install GSM timer configuration commands in the VTY. */
-void T_defs_vty_init(struct T_def *T_defs, int cfg_parent_node)
-{
- g_vty_T_defs = T_defs;
- install_element_ve(&show_timer_cmd);
- install_element(cfg_parent_node, &cfg_timer_cmd);
-}
-
-/* Write GSM timer configuration to the vty. */
-void T_defs_vty_write(struct vty *vty, const char *indent)
-{
- struct T_def *d;
- for_each_T_def(d, g_vty_T_defs) {
- if (d->val != d->default_val)
- vty_out(vty, "%stimer t%d %u%s", indent, d->T, d->val, VTY_NEWLINE);
- }
-}
diff --git a/src/osmo-bsc/handover_ctrl.c b/src/osmo-bsc/handover_ctrl.c
new file mode 100644
index 000000000..139d0382b
--- /dev/null
+++ b/src/osmo-bsc/handover_ctrl.c
@@ -0,0 +1,216 @@
+/* OsmoBSC handover control interface implementation */
+/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier <pmaier@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>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/handover_decision_2.h>
+#include <osmocom/ctrl/control_cmd.h>
+
+/* In handover_cfg.h the config items are described in VTY syntax. To be able to
+ * use those here in the CTRL interface, we parse the config arguments like the
+ * VTY would. (the value specification may be in the form of "<from-to>" or
+ * "A|B|C|..." */
+static bool verify_vty_cmd_arg(void *ctx, const char *range, const char *value)
+{
+ bool success;
+ char *range_tok;
+ char *valid_val;
+
+ /* "default" value is always a valid value */
+ if (strcmp(value, "default") == 0)
+ return true;
+
+ /* Try to check for a range first since it is the most common case */
+ if (range[0] == '<') {
+ if (vty_cmd_range_match(range, value))
+ return true;
+ else
+ return false;
+ }
+
+ /* Try to tokenize the string to check for distintinct values */
+ success = false;
+ range_tok = talloc_zero_size(ctx, strlen(range) + 1);
+ memcpy(range_tok, range, strlen(range));
+ valid_val = strtok(range_tok, "|");
+ while (valid_val != NULL) {
+ if (strcmp(valid_val, value) == 0) {
+ success = true;
+ break;
+ }
+ valid_val = strtok(NULL, "|");
+ }
+
+ talloc_free(range_tok);
+ return success;
+}
+
+/* NOTE: The following macro scheme has been designed for using it in the VTY
+ * code. However, for the most part it also works for CTRL interface code as
+ * well. */
+#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY_CMD_PREFIX, VTY_CMD, VTY_CMD_ARG, VTY_ARG_EVAL, VTY_WRITE_FMT, VTY_WRITE_CONV, VTY6) \
+CTRL_CMD_DEFINE(NAME, VTY_CMD_PREFIX VTY_CMD); \
+static int get_##NAME(struct ctrl_cmd *cmd, void *_data) \
+{ \
+ struct gsm_network *net = cmd->node; \
+ struct handover_cfg *ho = net->ho; \
+ TYPE val; \
+ if (ho_isset_##NAME(ho)) { \
+ val = ho_get_##NAME(ho); \
+ cmd->reply = talloc_asprintf(cmd, VTY_WRITE_FMT, VTY_WRITE_CONV(val)); \
+ } else \
+ cmd->reply = talloc_asprintf(cmd, "%s", #DEFAULT_VAL); \
+ return CTRL_CMD_REPLY; \
+} \
+static int set_##NAME(struct ctrl_cmd *cmd, void *_data) \
+{ \
+ struct gsm_network *net = cmd->node; \
+ struct handover_cfg *ho = net->ho; \
+ TYPE value; \
+ if (strcmp(cmd->value, "default") == 0) \
+ value = VTY_ARG_EVAL(#DEFAULT_VAL); \
+ else \
+ value = VTY_ARG_EVAL(cmd->value); \
+ ho_set_##NAME(ho, value); \
+ return get_##NAME(cmd, _data); \
+} \
+static int verify_##NAME(struct ctrl_cmd *cmd, const char *value, void *_data) \
+{ \
+ if (verify_vty_cmd_arg(cmd, VTY_CMD_ARG, value) != true) \
+ return -1; \
+ return 0; \
+} \
+CTRL_CMD_DEFINE(bts_##NAME, VTY_CMD_PREFIX VTY_CMD); \
+static int get_bts_##NAME(struct ctrl_cmd *cmd, void *_data) \
+{ \
+ struct gsm_bts *bts = cmd->node; \
+ struct handover_cfg *ho = bts->ho; \
+ TYPE val; \
+ if (ho_isset_##NAME(ho)) { \
+ val = ho_get_##NAME(ho); \
+ cmd->reply = talloc_asprintf(cmd, VTY_WRITE_FMT, VTY_WRITE_CONV(val)); \
+ } else { \
+ cmd->reply = talloc_asprintf(cmd, "%s", #DEFAULT_VAL); \
+ } \
+ return CTRL_CMD_REPLY; \
+} \
+static int set_bts_##NAME(struct ctrl_cmd *cmd, void *_data) \
+{ \
+ struct gsm_bts *bts = cmd->node; \
+ struct handover_cfg *ho = bts->ho; \
+ TYPE value; \
+ if (strcmp(cmd->value, "default") == 0) \
+ value = VTY_ARG_EVAL(#DEFAULT_VAL); \
+ else \
+ value = VTY_ARG_EVAL(cmd->value); \
+ ho_set_##NAME(ho, value); \
+ return get_bts_##NAME(cmd, _data); \
+} \
+static int verify_bts_##NAME(struct ctrl_cmd *cmd, const char *value, void *_data) \
+{ \
+ return verify_##NAME(cmd, value, _data); \
+} \
+
+/* Expand the above macro using the definitions from handover_cfg.h */
+HO_CFG_ALL_MEMBERS
+#undef HO_CFG_ONE_MEMBER
+
+CTRL_CMD_DEFINE(congestion_check_interval, "handover2 congestion-check");
+static int get_congestion_check_interval(struct ctrl_cmd *cmd, void *_data)
+{
+ struct gsm_network *net = cmd->node;
+ if (net->hodec2.congestion_check_interval_s > 0)
+ cmd->reply = talloc_asprintf(cmd, "%u", net->hodec2.congestion_check_interval_s);
+ else
+ cmd->reply = "disabled";
+ return CTRL_CMD_REPLY;
+}
+
+static int set_congestion_check_interval(struct ctrl_cmd *cmd, void *_data)
+{
+ struct gsm_network *net = cmd->node;
+ int value;
+
+ /* Trigger congestion check and leave without changing anything */
+ if (strcmp(cmd->value, "now") == 0) {
+ hodec2_congestion_check(net);
+ return get_congestion_check_interval(cmd, _data);
+ }
+
+ if (strcmp(cmd->value, "disabled") == 0)
+ value = 0;
+ else
+ value = atoi(cmd->value);
+ hodec2_on_change_congestion_check_interval(net, value);
+ return get_congestion_check_interval(cmd, _data);
+}
+
+static int verify_congestion_check_interval(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (strcmp(value, "disabled") == 0)
+ return 0;
+ if (strcmp(value, "now") == 0)
+ return 0;
+ if (verify_vty_cmd_arg(cmd, "<1-999>", value))
+ return 0;
+ return -1;
+}
+
+/* Filter name member in cmd for illegal '/' characters */
+static struct ctrl_cmd_element *filter_name(void *ctx,
+ struct ctrl_cmd_element *cmd)
+{
+ unsigned int i;
+ char *name;
+
+ if (osmo_separated_identifiers_valid(cmd->name, " -"))
+ return cmd;
+
+ name = talloc_strdup(ctx, cmd->name);
+ for (i = 0; i < strlen(name); i++) {
+ if (name[i] == '/')
+ name[i] = '-';
+ }
+
+ cmd->name = name;
+ return cmd;
+}
+
+int bsc_ho_ctrl_cmds_install(void *ctx)
+{
+ int rc = 0;
+
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_congestion_check_interval);
+
+#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY_ARG_EVAL, VTY4, VTY5, VTY6) \
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, filter_name(ctx, &cmd_##NAME)); \
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, filter_name(ctx, &cmd_bts_##NAME)); \
+
+HO_CFG_ALL_MEMBERS
+#undef HO_CFG_ONE_MEMBER
+
+ return rc;
+}
diff --git a/src/osmo-bsc/handover_decision.c b/src/osmo-bsc/handover_decision.c
index 0bfbce20d..2fb466cbe 100644
--- a/src/osmo-bsc/handover_decision.c
+++ b/src/osmo-bsc/handover_decision.c
@@ -34,6 +34,7 @@
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/bts.h>
/* did we get a RXLEV for a given cell in the given report? */
static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr,
@@ -180,7 +181,7 @@ static void attempt_handover(struct gsm_meas_rep *mr)
if (nmp->arfcn == 0)
continue;
- /* caculate average rxlev for this cell over the window */
+ /* calculate average rxlev for this cell over the window */
avg = neigh_meas_avg(nmp, ho_get_hodec1_rxlev_neigh_avg_win(bts->ho));
/* check if hysteresis is fulfilled */
@@ -200,8 +201,7 @@ static void attempt_handover(struct gsm_meas_rep *mr)
req = (struct handover_out_req){
.from_hodec_id = HODEC1,
.old_lchan = mr->lchan,
- .target_nik = {
- .from_bts = bts->nr,
+ .target_cell_ab = {
.arfcn = best_cell->arfcn,
.bsic = best_cell->bsic,
},
@@ -214,7 +214,6 @@ static void attempt_handover(struct gsm_meas_rep *mr)
static void on_measurement_report(struct gsm_meas_rep *mr)
{
struct gsm_bts *bts = mr->lchan->ts->trx->bts;
- enum meas_rep_field dlev, dqual;
int av_rxlev;
unsigned int pwr_interval;
@@ -231,24 +230,16 @@ static void on_measurement_report(struct gsm_meas_rep *mr)
return;
}
- if (mr->flags & MEAS_REP_F_DL_DTX) {
- dlev = MEAS_REP_DL_RXLEV_SUB;
- dqual = MEAS_REP_DL_RXQUAL_SUB;
- } else {
- dlev = MEAS_REP_DL_RXLEV_FULL;
- dqual = MEAS_REP_DL_RXQUAL_FULL;
- }
-
/* parse actual neighbor cell info */
if (mr->num_cell > 0 && mr->num_cell < 7)
process_meas_neigh(mr);
- av_rxlev = get_meas_rep_avg(mr->lchan, dlev,
+ av_rxlev = get_meas_rep_avg(mr->lchan, TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_AUTO,
ho_get_hodec1_rxlev_avg_win(bts->ho));
/* Interference HO */
if (rxlev2dbm(av_rxlev) > -85 &&
- meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5)) {
+ meas_rep_n_out_of_m_be(mr->lchan, TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_AUTO, 3, 4, 5)) {
LOGPC(DHO, LOGL_INFO, "HO cause: Interference HO av_rxlev=%d dBm\n",
rxlev2dbm(av_rxlev));
attempt_handover(mr);
@@ -256,7 +247,7 @@ static void on_measurement_report(struct gsm_meas_rep *mr)
}
/* Bad Quality */
- if (meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5)) {
+ if (meas_rep_n_out_of_m_be(mr->lchan, TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_AUTO, 3, 4, 5)) {
LOGPC(DHO, LOGL_INFO, "HO cause: Bad Quality av_rxlev=%d dBm\n", rxlev2dbm(av_rxlev));
attempt_handover(mr);
return;
diff --git a/src/osmo-bsc/handover_decision_2.c b/src/osmo-bsc/handover_decision_2.c
index a8fff6319..073e013d9 100644
--- a/src/osmo-bsc/handover_decision_2.c
+++ b/src/osmo-bsc/handover_decision_2.c
@@ -24,9 +24,12 @@
#include <stdbool.h>
#include <errno.h>
+#include <limits.h>
+#include <math.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/handover_decision.h>
#include <osmocom/bsc/handover_decision_2.h>
@@ -37,6 +40,9 @@
#include <osmocom/bsc/penalty_timers.h>
#include <osmocom/bsc/neighbor_ident.h>
#include <osmocom/bsc/timeslot_fsm.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/chan_counts.h>
#define LOGPHOBTS(bts, level, fmt, args...) \
LOGP(DHODEC, level, "(BTS %u) " fmt, bts->nr, ## args)
@@ -47,8 +53,8 @@
lchan->ts->trx->nr, \
lchan->ts->nr, \
lchan->nr, \
- gsm_lchant_name(lchan->type), \
- gsm48_chan_mode_name(lchan->tch_mode), \
+ gsm_chan_t_name(lchan->type), \
+ gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), \
bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \
## args)
@@ -58,8 +64,8 @@
lchan->ts->trx->nr, \
lchan->ts->nr, \
lchan->nr, \
- gsm_lchant_name(lchan->type), \
- gsm48_chan_mode_name(lchan->tch_mode), \
+ gsm_chan_t_name(lchan->type), \
+ gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), \
new_bts->nr, \
bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \
## args)
@@ -70,18 +76,18 @@
lchan->ts->trx->nr, \
lchan->ts->nr, \
lchan->nr, \
- gsm_lchant_name(lchan->type), \
- gsm48_chan_mode_name(lchan->tch_mode), \
+ gsm_chan_t_name(lchan->type), \
+ gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode), \
gsm0808_cell_id_list_name(remote_cil), \
bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \
## args)
#define LOGPHOCAND(candidate, level, fmt, args...) do {\
- if ((candidate)->bts) \
- LOGPHOLCHANTOBTS((candidate)->lchan, (candidate)->bts, level, fmt, ## args); \
- else if ((candidate)->cil) \
- LOGPHOLCHANTOREMOTE((candidate)->lchan, (candidate)->cil, level, fmt, ## args); \
- } while(0)
+ if ((candidate)->target.bts) \
+ LOGPHOLCHANTOBTS((candidate)->current.lchan, (candidate)->target.bts, level, fmt, ## args); \
+ else if ((candidate)->target.cell_ids.id_list_len) \
+ LOGPHOLCHANTOREMOTE((candidate)->current.lchan, &(candidate)->target.cell_ids, level, fmt, ## args); \
+ } while (0)
#define REQUIREMENT_A_TCHF 0x01
@@ -97,12 +103,39 @@
#define REQUIREMENT_C_MASK (REQUIREMENT_C_TCHF | REQUIREMENT_C_TCHH)
struct ho_candidate {
- struct gsm_lchan *lchan; /* candidate for whom */
- struct neighbor_ident_key nik; /* neighbor ARFCN+BSIC */
- struct gsm_bts *bts; /* target BTS in local BSS */
- const struct gsm0808_cell_id_list2 *cil; /* target cells in remote BSS */
uint8_t requirements; /* what is fulfilled */
- int avg; /* average RX level */
+ struct {
+ struct gsm_lchan *lchan;
+ struct gsm_bts *bts;
+ int rxlev;
+ /* free/min-free for the current TCH kind, same as either free_tch_f or free_tch_h below */
+ int free_tch;
+ int min_free_tch;
+ /* free/min-free for the two TCH kinds, to calculate F<->H cross effects for dynamic timeslots */
+ int free_tchf;
+ int min_free_tchf;
+ int free_tchh;
+ int min_free_tchh;
+ /* Effects of freeing a dynamic timeslot, i.e. turning it into PDCH mode and making available more free
+ * TCH: */
+ int lchan_frees_tchf;
+ int lchan_frees_tchh;
+ } current;
+ struct {
+ struct cell_ab ab; /* neighbor ARFCN+BSIC */
+ struct gsm0808_cell_id_list2 cell_ids; /* target cells in remote BSS */
+ struct gsm_bts *bts;
+ int rxlev;
+ int rxlev_afs_bias;
+ int free_tchf;
+ int min_free_tchf;
+ int free_tchh;
+ int min_free_tchh;
+ /* Effects of occupying a dynamic timeslot, i.e. turning from PDCH into a specific TCH kind, and
+ * reducing the number of free TCH for both TCH/F and TCH/H: */
+ int next_tchf_reduces_tchh;
+ int next_tchh_reduces_tchf;
+ } target;
};
enum ho_reason {
@@ -135,6 +168,24 @@ static enum ho_reason global_ho_reason;
static void congestion_check_cb(void *arg);
+static bool is_upgrade_to_tchf(const struct ho_candidate *c, uint8_t for_requirement)
+{
+ return c->current.lchan
+ && (c->current.lchan->type == GSM_LCHAN_TCH_H)
+ && ((c->requirements & for_requirement) & REQUIREMENT_TCHF_MASK);
+}
+
+static unsigned int ts_usage_count(struct gsm_bts_trx_ts *ts)
+{
+ struct gsm_lchan *lchan;
+ unsigned int count = 0;
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
+ if (lchan_state_is(lchan, LCHAN_ST_ESTABLISHED))
+ count++;
+ }
+ return count;
+}
+
/* This function gets called on ho2 init, whenever the congestion check interval is changed, and also
* when the timer has fired to trigger again after the next congestion check timeout. */
static void reinit_congestion_timer(struct gsm_network *net)
@@ -173,49 +224,6 @@ void hodec2_on_change_congestion_check_interval(struct gsm_network *net, unsigne
reinit_congestion_timer(net);
}
-static void _conn_penalty_time_add(struct gsm_subscriber_connection *conn,
- const void *for_object,
- int penalty_time)
-{
- if (!for_object) {
- LOGP(DHODEC, LOGL_ERROR, "%s Unable to set Handover-2 penalty timer:"
- " no target cell pointer\n",
- bsc_subscr_name(conn->bsub));
- return;
- }
-
- if (!conn->hodec2.penalty_timers) {
- conn->hodec2.penalty_timers = penalty_timers_init(conn);
- OSMO_ASSERT(conn->hodec2.penalty_timers);
- }
-
- penalty_timers_add(conn->hodec2.penalty_timers, for_object, penalty_time);
-}
-
-static void nik_penalty_time_add(struct gsm_subscriber_connection *conn,
- struct neighbor_ident_key *nik,
- int penalty_time)
-{
- _conn_penalty_time_add(conn,
- neighbor_ident_get(conn->network->neighbor_bss_cells, nik),
- penalty_time);
-}
-
-static void bts_penalty_time_add(struct gsm_subscriber_connection *conn,
- struct gsm_bts *bts,
- int penalty_time)
-{
- _conn_penalty_time_add(conn, bts, penalty_time);
-}
-
-static unsigned int conn_penalty_time_remaining(struct gsm_subscriber_connection *conn,
- const void *for_object)
-{
- if (!conn->hodec2.penalty_timers)
- return 0;
- return penalty_timers_remaining(conn->hodec2.penalty_timers, for_object);
-}
-
/* did we get a RXLEV for a given cell in the given report? Mark matches as MRC_F_PROCESSED. */
static struct gsm_meas_rep_cell *cell_in_rep(struct gsm_meas_rep *mr, uint16_t arfcn, uint8_t bsic)
{
@@ -234,6 +242,35 @@ static struct gsm_meas_rep_cell *cell_in_rep(struct gsm_meas_rep *mr, uint16_t a
return NULL;
}
+static int current_rxlev(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ return get_meas_rep_avg(lchan, TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_DL, ho_get_hodec2_tdma_meas_set(bts->ho),
+ ho_get_hodec2_rxlev_avg_win(bts->ho));
+}
+
+static int current_rxqual(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ return get_meas_rep_avg(lchan, TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, ho_get_hodec2_tdma_meas_set(bts->ho),
+ ho_get_hodec2_rxqual_avg_win(bts->ho));
+}
+
+static bool is_low_rxlev(int rxlev_current, struct handover_cfg *neigh_cfg)
+{
+ return rxlev_current >= 0
+ && rxlev2dbm(rxlev_current) < ho_get_hodec2_min_rxlev(neigh_cfg);
+}
+
+static bool is_low_rxqual(int rxqual_current, struct handover_cfg *neigh_cfg)
+{
+ /* min_rxqual is actually a bit of a misnomer, low quality is a high number. So the "min" refers to the minimum
+ * acceptable level of quality, and "min or better" here means "rxqual number must be SMALLER-or-equal than the
+ * min-rxqual setting". */
+ return rxqual_current >= 0
+ && rxqual_current > ho_get_hodec2_min_rxqual(neigh_cfg);
+}
+
/* obtain averaged rxlev for given neighbor */
static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window)
{
@@ -363,6 +400,26 @@ static bool codec_type_is_supported(struct gsm_subscriber_connection *conn,
return false;
}
+#define LOAD_PRECISION 6
+
+/* Return a number representing overload, i.e. the fraction of lchans used above the congestion threshold.
+ * Think of it as a percentage of used lchans above congestion, just represented in a fixed-point fraction with N
+ * decimal digits of fractional part. If there is no congestion (free_tch >= min_free_tch), return 0.
+ */
+static int32_t load_above_congestion(int free_tch, int min_free_tch)
+{
+ int32_t v;
+ OSMO_ASSERT(free_tch >= 0);
+ /* Avoid division by zero when no congestion threshold is set, and return zero overload when there is no
+ * congestion. */
+ if (free_tch >= min_free_tch)
+ return 0;
+ v = min_free_tch - free_tch;
+ v *= pow(10, LOAD_PRECISION);
+ v /= min_free_tch;
+ return v;
+}
+
/*
* Check what requirements the given cell fulfills.
* A bit mask of fulfilled requirements is returned.
@@ -415,113 +472,116 @@ static bool codec_type_is_supported(struct gsm_subscriber_connection *conn,
* * The number of free slots are checked for TCH/F and TCH/H slot types
* individually.
*/
-static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts, int tchf_count, int tchh_count)
+static void check_requirements(struct ho_candidate *c)
{
- int count;
uint8_t requirement = 0;
unsigned int penalty_time;
- struct gsm_bts *current_bts = lchan->ts->trx->bts;
+ int32_t current_overbooked;
+ struct gsm0808_cell_id target_cell_id;
+ c->requirements = 0;
/* Requirement A */
/* the handover/assignment must not be disabled */
- if (current_bts == bts) {
- if (!ho_get_hodec2_as_active(bts->ho)) {
- LOGPHOLCHAN(lchan, LOGL_DEBUG, "Assignment disabled\n");
- return 0;
+ if (c->current.bts == c->target.bts) {
+ if (!ho_get_hodec2_as_active(c->target.bts->ho)) {
+ LOGPHOLCHAN(c->current.lchan, LOGL_DEBUG, "Assignment disabled\n");
+ return;
}
} else {
- if (!ho_get_ho_active(bts->ho)) {
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ if (!ho_get_ho_active(c->target.bts->ho)) {
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
"not a candidate, handover is disabled in target BTS\n");
- return 0;
+ return;
}
}
/* the handover penalty timer must not run for this bts */
- penalty_time = conn_penalty_time_remaining(lchan->conn, bts);
+ gsm_bts_cell_id(&target_cell_id, c->target.bts);
+ penalty_time = penalty_timers_remaining(&c->current.lchan->conn->hodec2.penalty_timers, &target_cell_id);
if (penalty_time) {
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, target BTS still in penalty time"
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "not a candidate, target BTS still in penalty time"
" (%u seconds left)\n", penalty_time);
- return 0;
+ return;
}
/* compatibility check for codecs.
* if so, the candidates for full rate and half rate are selected */
- switch (lchan->tch_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(c->current.lchan->current_ch_mode_rate.chan_mode)) {
case GSM48_CMODE_SPEECH_V1:
- switch (lchan->type) {
+ switch (c->current.lchan->type) {
case GSM_LCHAN_TCH_F: /* mandatory */
requirement |= REQUIREMENT_A_TCHF;
break;
case GSM_LCHAN_TCH_H:
- if (!bts->codec.hr) {
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ if (!c->target.bts->codec.hr) {
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
"tch_mode='%s' type='%s' not supported\n",
get_value_string(gsm48_chan_mode_names,
- lchan->tch_mode),
- gsm_lchant_name(lchan->type));
+ c->current.lchan->current_ch_mode_rate.chan_mode),
+ gsm_chan_t_name(c->current.lchan->type));
break;
}
- if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR1))
+ if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_HR1))
requirement |= REQUIREMENT_A_TCHH;
break;
default:
- LOGPHOLCHAN(lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n",
- get_value_string(gsm48_chan_mode_names, lchan->tch_mode));
- return 0;
+ LOGPHOLCHAN(c->current.lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n",
+ get_value_string(gsm48_chan_mode_names,
+ c->current.lchan->current_ch_mode_rate.chan_mode));
+ return;
}
break;
case GSM48_CMODE_SPEECH_EFR:
- if (!bts->codec.efr) {
- LOGPHOBTS(bts, LOGL_DEBUG, "EFR not supported\n");
+ if (!c->target.bts->codec.efr) {
+ LOGPHOBTS(c->target.bts, LOGL_DEBUG, "EFR not supported\n");
break;
}
- if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR2))
+ if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_FR2))
requirement |= REQUIREMENT_A_TCHF;
break;
case GSM48_CMODE_SPEECH_AMR:
- if (!bts->codec.amr) {
- LOGPHOBTS(bts, LOGL_DEBUG, "AMR not supported\n");
+ if (!c->target.bts->codec.amr) {
+ LOGPHOBTS(c->target.bts, LOGL_DEBUG, "AMR not supported\n");
break;
}
- if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR3))
+ if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_FR3))
requirement |= REQUIREMENT_A_TCHF;
- if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR3))
+ if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_HR3))
requirement |= REQUIREMENT_A_TCHH;
break;
default:
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n");
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n");
/* FIXME: should allow handover of non-speech lchans */
- return 0;
+ return;
}
/* no candidate, because new cell is incompatible */
if (!requirement) {
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, because codec of MS and BTS are incompatible\n");
- return 0;
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "not a candidate, because codec of MS and BTS are incompatible\n");
+ return;
}
/* remove slot types that are not available */
- if (!tchf_count && requirement & REQUIREMENT_A_TCHF) {
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ if (!c->target.free_tchf && (requirement & REQUIREMENT_A_TCHF)) {
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
"removing TCH/F, since all TCH/F lchans are in use\n");
requirement &= ~(REQUIREMENT_A_TCHF);
}
- if (!tchh_count && requirement & REQUIREMENT_A_TCHH) {
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ if (!c->target.free_tchh && (requirement & REQUIREMENT_A_TCHH)) {
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
"removing TCH/H, since all TCH/H lchans are in use\n");
requirement &= ~(REQUIREMENT_A_TCHH);
}
if (!requirement) {
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, because no suitable slots available\n");
- return 0;
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG, "not a candidate, because no suitable slots available\n");
+ return;
}
/* omit same channel type on same BTS (will not change anything) */
- if (bts == current_bts) {
- switch (lchan->type) {
+ if (c->target.bts == c->current.bts) {
+ switch (c->current.lchan->type) {
case GSM_LCHAN_TCH_F:
requirement &= ~(REQUIREMENT_A_TCHF);
break;
@@ -533,9 +593,9 @@ static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts,
}
if (!requirement) {
- LOGPHOLCHAN(lchan, LOGL_DEBUG,
+ LOGPHOLCHAN(c->current.lchan, LOGL_DEBUG,
"Reassignment within cell not an option, no differing channel types available\n");
- return 0;
+ return;
}
}
@@ -543,26 +603,26 @@ static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts,
// This was useful in osmo-nitb. We're in osmo-bsc now and have no idea whether the osmo-msc does
// internal or external call control. Maybe a future config switch wants to add this behavior?
/* Built-in call control requires equal codec rates. Remove rates that are not equal. */
- if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
- && current_bts->network->mncc_recv != mncc_sock_from_cc) {
- switch (lchan->type) {
+ if (gsm48_chan_mode_to_non_vamos(c->current.lchan->tch_mode) == GSM48_CMODE_SPEECH_AMR
+ && c->current.bts->network->mncc_recv != mncc_sock_from_cc) {
+ switch (c->current.lchan->type) {
case GSM_LCHAN_TCH_F:
if ((requirement & REQUIREMENT_A_TCHF)
- && !!memcmp(&current_bts->mr_full, &bts->mr_full,
+ && !!memcmp(&c->current.bts->mr_full, &c->target.bts->mr_full,
sizeof(struct amr_multirate_conf)))
requirement &= ~(REQUIREMENT_A_TCHF);
if ((requirement & REQUIREMENT_A_TCHH)
- && !!memcmp(&current_bts->mr_full, &bts->mr_half,
+ && !!memcmp(&c->current.bts->mr_full, &c->target.bts->mr_half,
sizeof(struct amr_multirate_conf)))
requirement &= ~(REQUIREMENT_A_TCHH);
break;
case GSM_LCHAN_TCH_H:
if ((requirement & REQUIREMENT_A_TCHF)
- && !!memcmp(&current_bts->mr_half, &bts->mr_full,
+ && !!memcmp(&c->current.bts->mr_half, &c->target.bts->mr_full,
sizeof(struct amr_multirate_conf)))
requirement &= ~(REQUIREMENT_A_TCHF);
if ((requirement & REQUIREMENT_A_TCHH)
- && !!memcmp(&current_bts->mr_half, &bts->mr_half,
+ && !!memcmp(&c->current.bts->mr_half, &c->target.bts->mr_half,
sizeof(struct amr_multirate_conf)))
requirement &= ~(REQUIREMENT_A_TCHH);
break;
@@ -571,20 +631,20 @@ static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts,
}
if (!requirement) {
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
"not a candidate, cannot provide identical codec rate\n");
- return 0;
+ return;
}
}
#endif
/* the maximum number of unsynchronized handovers must no be exceeded */
- if (current_bts != bts
- && bts_handover_count(bts, HO_SCOPE_ALL) >= ho_get_hodec2_ho_max(bts->ho)) {
- LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ if (c->current.bts != c->target.bts
+ && bts_handover_count(c->target.bts, HO_SCOPE_ALL) >= ho_get_hodec2_ho_max(c->target.bts->ho)) {
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
"not a candidate, number of allowed handovers (%d) would be exceeded\n",
- ho_get_hodec2_ho_max(bts->ho));
- return 0;
+ ho_get_hodec2_ho_max(c->target.bts->ho));
+ return;
}
/* Requirement B */
@@ -592,88 +652,177 @@ static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts,
/* the minimum free timeslots that are defined for this cell must
* be maintained _after_ handover/assignment */
if (requirement & REQUIREMENT_A_TCHF) {
- if (tchf_count - 1 >= ho_get_hodec2_tchf_min_slots(bts->ho))
+ if ((c->target.free_tchf - 1) >= c->target.min_free_tchf
+ && (!c->target.next_tchf_reduces_tchh
+ || (c->target.free_tchh - c->target.next_tchf_reduces_tchh) >= c->target.min_free_tchh))
requirement |= REQUIREMENT_B_TCHF;
}
if (requirement & REQUIREMENT_A_TCHH) {
- if (tchh_count - 1 >= ho_get_hodec2_tchh_min_slots(bts->ho))
+ if ((c->target.free_tchh - 1) >= c->target.min_free_tchh
+ && (!c->target.next_tchh_reduces_tchf
+ || (c->target.free_tchf - c->target.next_tchh_reduces_tchf) >= c->target.min_free_tchf))
requirement |= REQUIREMENT_B_TCHH;
}
/* Requirement C */
- /* the nr of free timeslots of the target cell must be >= the
- * free slots of the current cell _after_ handover/assignment */
- count = bts_count_free_ts(current_bts,
- (lchan->type == GSM_LCHAN_TCH_H) ?
- GSM_PCHAN_TCH_H : GSM_PCHAN_TCH_F);
+ /* the load percentage above congestion on the target cell *after* HO must be < the load percentage above
+ * congestion on the current cell, hence the - 1 on the target. */
+ current_overbooked = load_above_congestion(c->current.free_tch, c->current.min_free_tch);
if (requirement & REQUIREMENT_A_TCHF) {
- if (tchf_count - 1 >= count + 1)
+ bool ok;
+ int32_t target_overbooked;
+ int target_free_tchf_after_ho;
+
+ /* To evaluate whether a handover improves or worsens congestion on TCH/F, first figure out how many
+ * TCH/F lchans will be occupied on the target after the handover. If the target is a different cell,
+ * then we obviously reduce by one TCH/F. If source and target cell are the same (re-assignment), then
+ * the source lchan may also free a TCH/F at the same time. Add up all of these effects to figure out
+ * the congestion percentages before and after handover. */
+ target_free_tchf_after_ho = c->target.free_tchf - 1;
+ if (c->current.bts == c->target.bts)
+ target_free_tchf_after_ho += c->current.lchan_frees_tchf;
+ target_overbooked = load_above_congestion(target_free_tchf_after_ho, c->target.min_free_tchf);
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
+ "current overbooked = %s%%, TCH/F target overbooked after HO = %s%%\n",
+ osmo_int_to_float_str_c(OTC_SELECT, current_overbooked, LOAD_PRECISION - 2),
+ osmo_int_to_float_str_c(OTC_SELECT, target_overbooked, LOAD_PRECISION - 2));
+ ok = target_overbooked < current_overbooked;
+ /* Look at dynamic timeslot effects on TCH/H: */
+ if (ok && c->target.next_tchf_reduces_tchh) {
+ /* Looking at the current TCH type and the target cell's TCH/F alone, congestion balancing
+ * should happen. However, what if the target TCH/F is a dynamic timeslot -- would that cause
+ * congestion on TCH/H above the current cell's TCH/H congestion? */
+ int32_t current_tchh_overbooked = load_above_congestion(c->current.free_tchh,
+ c->current.min_free_tchh);
+ int32_t target_tchh_overbooked;
+ int target_free_tchh_after_ho = c->target.free_tchh - c->target.next_tchf_reduces_tchh;
+ /* If this is a re-assignment within the same cell, and if the current candidate would free a
+ * dynamic timeslot, then the target-overbooking after HO is reduced again by the freed dynamic
+ * TS. */
+ if (c->current.bts == c->target.bts)
+ target_free_tchh_after_ho += c->current.lchan_frees_tchh;
+ target_tchh_overbooked = load_above_congestion(target_free_tchh_after_ho,
+ c->target.min_free_tchh);
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
+ "dyn TS: current TCH/H overbooked = %s%%, TCH/H target overbooked after HO = %s%%\n",
+ osmo_int_to_float_str_c(OTC_SELECT, current_tchh_overbooked, LOAD_PRECISION - 2),
+ osmo_int_to_float_str_c(OTC_SELECT, target_tchh_overbooked, LOAD_PRECISION - 2));
+ /* For the current TCH kind, a handover should only happen if things actually get better
+ * (condition is '<'). For dynamic timeslot cross effects TCH/F->TCH/H, it is fine to not make
+ * it worse. Hence the smaller-or-equal condition. */
+ ok = target_tchh_overbooked <= current_tchh_overbooked;
+ }
+ if (ok)
requirement |= REQUIREMENT_C_TCHF;
}
if (requirement & REQUIREMENT_A_TCHH) {
- if (tchh_count - 1 >= count + 1)
+ bool ok;
+ int32_t target_overbooked;
+ int target_free_tchh_after_ho;
+
+ /* To evaluate whether a handover improves or worsens congestion on TCH/H, first figure out how many
+ * TCH/H lchans will be occupied on the target after the handover. If the target is a different cell,
+ * then we obviously reduce by one TCH/H. If source and target cell are the same (re-assignment), then
+ * the source lchan may also free one or two TCH/H at the same time. Add up all of these effects to
+ * figure out the congestion percentages before and after handover. */
+ target_free_tchh_after_ho = c->target.free_tchh - 1;
+ if (c->current.bts == c->target.bts)
+ target_free_tchh_after_ho += c->current.lchan_frees_tchh;
+ target_overbooked = load_above_congestion(target_free_tchh_after_ho, c->target.min_free_tchh);
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
+ "current overbooked = %s%%, TCH/H target overbooked after HO = %s%%\n",
+ osmo_int_to_float_str_c(OTC_SELECT, current_overbooked, LOAD_PRECISION - 2),
+ osmo_int_to_float_str_c(OTC_SELECT, target_overbooked, LOAD_PRECISION - 2));
+ ok = target_overbooked < current_overbooked;
+ /* Look at dynamic timeslot effects on TCH/F: */
+ if (ok && c->target.next_tchh_reduces_tchf) {
+ /* Looking at the current TCH type and the target cell's TCH/H alone, congestion balancing
+ * should happen. However, what if the target TCH/H is a dynamic timeslot -- would that cause
+ * congestion on TCH/F above the current cell's TCH/F congestion? */
+ int32_t current_tchf_overbooked = load_above_congestion(c->current.free_tchf,
+ c->current.min_free_tchf);
+ int32_t target_tchf_overbooked;
+ int target_free_tchf_after_ho = c->target.free_tchf - c->target.next_tchh_reduces_tchf;
+ /* If this is a re-assignment within the same cell, and if the current candidate would free a
+ * dynamic timeslot, then the target-overbooking after HO is reduced again by the freed dynamic
+ * TS. */
+ if (c->current.bts == c->target.bts)
+ target_free_tchf_after_ho += c->current.lchan_frees_tchf;
+ target_tchf_overbooked = load_above_congestion(target_free_tchf_after_ho,
+ c->target.min_free_tchf);
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_DEBUG,
+ "dyn TS: current TCH/F overbooked = %s%%, TCH/F target overbooked after HO = %s%%\n",
+ osmo_int_to_float_str_c(OTC_SELECT, current_tchf_overbooked, LOAD_PRECISION - 2),
+ osmo_int_to_float_str_c(OTC_SELECT, target_tchf_overbooked, LOAD_PRECISION - 2));
+ /* For the current TCH kind, a handover should only happen if things actually get better
+ * (condition is '<'). For dynamic timeslot cross effects TCH/H->TCH/F, it is fine to not make
+ * it worse. Hence the smaller-or-equal condition. */
+ ok = target_tchf_overbooked <= current_tchf_overbooked;
+ }
+ if (ok)
requirement |= REQUIREMENT_C_TCHH;
}
/* return mask of fulfilled requirements */
- return requirement;
+ c->requirements = requirement;
}
-static uint8_t check_requirements_remote_bss(struct gsm_lchan *lchan,
- const struct gsm0808_cell_id_list2 *cil)
+static void check_requirements_remote_bss(struct ho_candidate *c)
{
uint8_t requirement = 0;
unsigned int penalty_time;
+ c->requirements = 0;
/* Requirement A */
/* the handover penalty timer must not run for this bts */
- penalty_time = conn_penalty_time_remaining(lchan->conn, cil);
+ penalty_time = penalty_timers_remaining_list(&c->current.lchan->conn->hodec2.penalty_timers, &c->target.cell_ids);
if (penalty_time) {
- LOGPHOLCHANTOREMOTE(lchan, cil, LOGL_DEBUG,
+ LOGPHOLCHANTOREMOTE(c->current.lchan, &c->target.cell_ids, LOGL_DEBUG,
"not a candidate, target BSS still in penalty time"
" (%u seconds left)\n", penalty_time);
- return 0;
+ return;
}
/* compatibility check for codecs -- we have no notion of what the remote BSS supports. We can
* only assume that a handover would work, and use only the local requirements. */
- switch (lchan->tch_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(c->current.lchan->current_ch_mode_rate.chan_mode)) {
case GSM48_CMODE_SPEECH_V1:
- switch (lchan->type) {
+ switch (c->current.lchan->type) {
case GSM_LCHAN_TCH_F: /* mandatory */
requirement |= REQUIREMENT_A_TCHF;
break;
case GSM_LCHAN_TCH_H:
- if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR1))
+ if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_HR1))
requirement |= REQUIREMENT_A_TCHH;
break;
default:
- LOGPHOLCHAN(lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n",
- get_value_string(gsm48_chan_mode_names, lchan->tch_mode));
- return 0;
+ LOGPHOLCHAN(c->current.lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n",
+ get_value_string(gsm48_chan_mode_names,
+ c->current.lchan->current_ch_mode_rate.chan_mode));
+ return;
}
break;
case GSM48_CMODE_SPEECH_EFR:
- if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR2))
+ if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_FR2))
requirement |= REQUIREMENT_A_TCHF;
break;
case GSM48_CMODE_SPEECH_AMR:
- if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR3))
+ if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_FR3))
requirement |= REQUIREMENT_A_TCHF;
- if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR3))
+ if (codec_type_is_supported(c->current.lchan->conn, GSM0808_SCT_HR3))
requirement |= REQUIREMENT_A_TCHH;
break;
default:
- LOGPHOLCHAN(lchan, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n");
+ LOGPHOLCHAN(c->current.lchan, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n");
/* FIXME: should allow handover of non-speech lchans */
- return 0;
+ return;
}
if (!requirement) {
- LOGPHOLCHAN(lchan, LOGL_ERROR, "lchan doesn't fit its own requirements??\n");
- return 0;
+ LOGPHOLCHAN(c->current.lchan, LOGL_ERROR, "lchan doesn't fit its own requirements??\n");
+ return;
}
/* Requirement B and C */
@@ -686,38 +835,34 @@ static uint8_t check_requirements_remote_bss(struct gsm_lchan *lchan,
requirement |= REQUIREMENT_B_TCHH | REQUIREMENT_C_TCHH;
/* return mask of fulfilled requirements */
- return requirement;
+ c->requirements = requirement;
}
/* Trigger handover or assignment depending on the target BTS */
static int trigger_local_ho_or_as(struct ho_candidate *c, uint8_t requirements)
{
- struct gsm_lchan *lchan = c->lchan;
- struct gsm_bts *new_bts = c->bts;
- struct handover_out_req req;
- struct gsm_bts *current_bts = lchan->ts->trx->bts;
int afs_bias = 0;
bool full_rate = false;
/* afs_bias becomes > 0, if AFS is used and is improved */
- if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
- afs_bias = ho_get_hodec2_afs_bias_rxlev(new_bts->ho);
+ if (gsm48_chan_mode_to_non_vamos(c->current.lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR)
+ afs_bias = ho_get_hodec2_afs_bias_rxlev(c->target.bts->ho);
/* select TCH rate, prefer TCH/F if AFS is improved */
- switch (lchan->type) {
+ switch (c->current.lchan->type) {
case GSM_LCHAN_TCH_F:
/* keep on full rate, if TCH/F is a candidate */
if ((requirements & REQUIREMENT_TCHF_MASK)) {
- if (current_bts == new_bts) {
- LOGPHOLCHAN(lchan, LOGL_INFO, "Not performing assignment: Already on target type\n");
- return 0;
+ if (c->current.bts == c->target.bts) {
+ LOGPHOLCHAN(c->current.lchan, LOGL_INFO, "Not performing assignment: Already on target type\n");
+ return -EALREADY;
}
full_rate = true;
break;
}
/* change to half rate */
if (!(requirements & REQUIREMENT_TCHH_MASK)) {
- LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR,
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_ERROR,
"neither TCH/F nor TCH/H requested, aborting ho/as\n");
return -EINVAL;
}
@@ -736,38 +881,53 @@ static int trigger_local_ho_or_as(struct ho_candidate *c, uint8_t requirements)
}
/* keep on half rate */
if (!(requirements & REQUIREMENT_TCHH_MASK)) {
- LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR,
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_ERROR,
"neither TCH/F nor TCH/H requested, aborting ho/as\n");
return -EINVAL;
}
- if (current_bts == new_bts) {
- LOGPHOLCHAN(lchan, LOGL_INFO, "Not performing assignment: Already on target type\n");
- return 0;
+ if (c->current.bts == c->target.bts) {
+ LOGPHOLCHAN(c->current.lchan, LOGL_INFO, "Not performing assignment: Already on target type\n");
+ return -EALREADY;
}
break;
default:
- LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, "lchan is neither TCH/F nor TCH/H, aborting ho/as\n");
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_ERROR, "c->current.lchan is neither TCH/F nor TCH/H, aborting ho/as\n");
return -EINVAL;
}
/* trigger handover or assignment */
- if (current_bts == new_bts)
- LOGPHOLCHAN(lchan, LOGL_NOTICE, "Triggering assignment to %s, due to %s\n",
+ if (c->current.bts == c->target.bts) {
+ LOGPHOLCHAN(c->current.lchan, LOGL_NOTICE, "Triggering assignment to %s, due to %s\n",
full_rate ? "TCH/F" : "TCH/H",
ho_reason_name(global_ho_reason));
- else
- LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_INFO,
+ return reassignment_request_to_chan_type(ASSIGN_FOR_CONGESTION_RESOLUTION, c->current.lchan,
+ full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H);
+ } else {
+ struct handover_out_req req = {
+ .from_hodec_id = HODEC2,
+ .old_lchan = c->current.lchan,
+ .new_lchan_type = full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H,
+ };
+ bts_cell_ab(&req.target_cell_ab, c->target.bts);
+ LOGPHOLCHANTOBTS(c->current.lchan, c->target.bts, LOGL_INFO,
"Triggering handover to %s, due to %s\n",
full_rate ? "TCH/F" : "TCH/H",
ho_reason_name(global_ho_reason));
-
- req = (struct handover_out_req){
- .from_hodec_id = HODEC2,
- .old_lchan = lchan,
- .target_nik = *bts_ident_key(new_bts),
- .new_lchan_type = full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H,
- };
- handover_request(&req);
+ handover_request(&req);
+
+ /* Apply penalty timer hodec2_penalty_low_rxqual_ho */
+ if (global_ho_reason == HO_REASON_INTERFERENCE
+ || global_ho_reason == HO_REASON_BAD_QUALITY) {
+ struct gsm0808_cell_id bts_id;
+ struct gsm_subscriber_connection *conn = c->current.lchan->conn;
+ int timeout = ho_get_hodec2_penalty_low_rxqual_ho(c->current.bts->ho);
+ gsm_bts_cell_id(&bts_id, c->current.bts);
+ LOGPHOCAND(c, LOGL_DEBUG, "Applying penalty-time low-rxqual-ho %d s on bts %u (%s), reason: %s\n",
+ timeout, c->current.bts->nr, gsm0808_cell_id_name_c(OTC_SELECT, &bts_id),
+ ho_reason_name(global_ho_reason));
+ penalty_timers_add(conn, &conn->hodec2.penalty_timers, &bts_id, timeout);
+ }
+ }
return 0;
}
@@ -775,14 +935,14 @@ static int trigger_remote_bss_ho(struct ho_candidate *c, uint8_t requirements)
{
struct handover_out_req req;
- LOGPHOLCHANTOREMOTE(c->lchan, c->cil, LOGL_INFO,
+ LOGPHOLCHANTOREMOTE(c->current.lchan, &c->target.cell_ids, LOGL_INFO,
"Triggering inter-BSC handover, due to %s\n",
ho_reason_name(global_ho_reason));
req = (struct handover_out_req){
.from_hodec_id = HODEC2,
- .old_lchan = c->lchan,
- .target_nik = c->nik,
+ .old_lchan = c->current.lchan,
+ .target_cell_ab = c->target.ab,
};
handover_request(&req);
return 0;
@@ -790,76 +950,159 @@ static int trigger_remote_bss_ho(struct ho_candidate *c, uint8_t requirements)
static int trigger_ho(struct ho_candidate *c, uint8_t requirements)
{
- if (c->bts)
+ if (c->target.bts)
return trigger_local_ho_or_as(c, requirements);
else
return trigger_remote_bss_ho(c, requirements);
}
-/* verbosely log about a handover candidate */
-static inline void debug_candidate(struct ho_candidate *candidate,
- int8_t rxlev, int tchf_count, int tchh_count)
-{
- struct gsm_lchan *lchan = candidate->lchan;
-
-#define HO_CANDIDATE_FMT(tchx, TCHX) "TCH/" #TCHX "={free %d (want %d), [%s%s%s]%s}"
-#define HO_CANDIDATE_ARGS(tchx, TCHX) \
- tch##tchx##_count, ho_get_hodec2_tch##tchx##_min_slots(candidate->bts->ho), \
- candidate->requirements & REQUIREMENT_A_TCH##TCHX ? "A" : \
- (candidate->requirements & REQUIREMENT_TCH##TCHX##_MASK) == 0? "-" : "", \
- candidate->requirements & REQUIREMENT_B_TCH##TCHX ? "B" : "", \
- candidate->requirements & REQUIREMENT_B_TCH##TCHX ? "C" : "", \
- (candidate->requirements & REQUIREMENT_TCH##TCHX##_MASK) == 0 ? " not a candidate" : \
- ((candidate->requirements & REQUIREMENT_TCH##TCHX##_MASK) == REQUIREMENT_A_TCH##TCHX ? \
+#define REQUIREMENTS_FMT "[%s%s%s]%s"
+#define REQUIREMENTS_ARGS(REQUIREMENTS, TCHX) \
+ (REQUIREMENTS) & REQUIREMENT_A_TCH##TCHX ? "A" : \
+ ((REQUIREMENTS) & REQUIREMENT_TCH##TCHX##_MASK) == 0? "-" : "", \
+ (REQUIREMENTS) & REQUIREMENT_B_TCH##TCHX ? "B" : "", \
+ (REQUIREMENTS) & REQUIREMENT_C_TCH##TCHX ? "C" : "", \
+ ((REQUIREMENTS) & REQUIREMENT_TCH##TCHX##_MASK) == 0 ? " not a candidate" : \
+ (((REQUIREMENTS) & REQUIREMENT_TCH##TCHX##_MASK) == REQUIREMENT_A_TCH##TCHX ? \
" more congestion" : \
- (candidate->requirements & REQUIREMENT_B_TCH##TCHX ? \
+ ((REQUIREMENTS) & REQUIREMENT_B_TCH##TCHX ? \
" good" : \
- /* now has to be candidate->requirements & REQUIREMENT_C_TCHX != 0: */ \
+ /* now has to be (REQUIREMENTS) & REQUIREMENT_C_TCHX != 0: */ \
" less-or-equal congestion"))
- if (!candidate->bts && !candidate->cil)
- LOGPHOLCHAN(lchan, LOGL_DEBUG, "Empty candidate\n");
- if (candidate->bts && candidate->cil)
- LOGPHOLCHAN(lchan, LOGL_ERROR, "Invalid candidate: both local- and remote-BSS target\n");
-
- if (candidate->cil)
- LOGPHOLCHANTOREMOTE(lchan, candidate->cil, LOGL_DEBUG,
- "RX level %d -> %d\n",
- rxlev2dbm(rxlev), rxlev2dbm(candidate->avg));
-
- if (candidate->bts == lchan->ts->trx->bts)
- LOGPHOLCHANTOBTS(lchan, candidate->bts, LOGL_DEBUG,
- "RX level %d; "
+/* verbosely log about a handover candidate */
+static inline void debug_candidate(struct ho_candidate *candidate)
+{
+#define HO_CANDIDATE_FMT(tchx, TCHX) "TCH/" #TCHX "={free %d (want %d), " REQUIREMENTS_FMT "}"
+#define HO_CANDIDATE_ARGS(tchx, TCHX) \
+ candidate->target.free_tch##tchx, candidate->target.min_free_tch##tchx, \
+ REQUIREMENTS_ARGS(candidate->requirements, TCHX)
+
+ if (!candidate->target.bts && !candidate->target.cell_ids.id_list_len)
+ LOGPHOLCHAN(candidate->current.lchan, LOGL_DEBUG, "Empty candidate\n");
+ if (candidate->target.bts && candidate->target.cell_ids.id_list_len)
+ LOGPHOLCHAN(candidate->current.lchan, LOGL_ERROR, "Invalid candidate: both local- and remote-BSS target\n");
+
+ if (candidate->target.cell_ids.id_list_len)
+ LOGPHOLCHANTOREMOTE(candidate->current.lchan, &candidate->target.cell_ids, LOGL_DEBUG,
+ "RX level %d dBm -> %d dBm\n",
+ rxlev2dbm(candidate->current.rxlev), rxlev2dbm(candidate->target.rxlev));
+
+ if (candidate->target.bts == candidate->current.bts)
+ LOGPHOLCHANTOBTS(candidate->current.lchan, candidate->target.bts, LOGL_DEBUG,
+ "RX level %d dBm; "
HO_CANDIDATE_FMT(f, F) "; " HO_CANDIDATE_FMT(h, H) "\n",
- rxlev2dbm(candidate->avg),
+ rxlev2dbm(candidate->current.rxlev),
HO_CANDIDATE_ARGS(f, F), HO_CANDIDATE_ARGS(h, H));
- else if (candidate->bts)
- LOGPHOLCHANTOBTS(lchan, candidate->bts, LOGL_DEBUG,
- "RX level %d -> %d; "
+ else if (candidate->target.bts)
+ LOGPHOLCHANTOBTS(candidate->current.lchan, candidate->target.bts, LOGL_DEBUG,
+ "RX level %d dBm -> %d dBm; "
HO_CANDIDATE_FMT(f, F) "; " HO_CANDIDATE_FMT(h, H) "\n",
- rxlev2dbm(rxlev), rxlev2dbm(candidate->avg),
+ rxlev2dbm(candidate->current.rxlev), rxlev2dbm(candidate->target.rxlev),
HO_CANDIDATE_ARGS(f, F), HO_CANDIDATE_ARGS(h, H));
}
+static void candidate_set_free_tch(struct ho_candidate *c)
+{
+ struct chan_counts *bts_counts;
+ struct gsm_lchan *next_lchan;
+
+ bts_counts = &c->current.bts->chan_counts;
+ c->current.free_tchf = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F];
+ c->current.min_free_tchf = ho_get_hodec2_tchf_min_slots(c->current.bts->ho);
+ c->current.free_tchh = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H];
+ c->current.min_free_tchh = ho_get_hodec2_tchh_min_slots(c->current.bts->ho);
+
+ switch (c->current.lchan->ts->pchan_is) {
+ case GSM_PCHAN_TCH_F:
+ c->current.free_tch = c->current.free_tchf;
+ c->current.min_free_tch = c->current.min_free_tchf;
+ c->current.lchan_frees_tchf = 1;
+ if (c->current.lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN)
+ c->current.lchan_frees_tchh = 2;
+ else
+ c->current.lchan_frees_tchh = 0;
+ break;
+ case GSM_PCHAN_TCH_H:
+ c->current.free_tch = c->current.free_tchh;
+ c->current.min_free_tch = c->current.min_free_tchh;
+ c->current.lchan_frees_tchh = 1;
+ /* Freeing one of two TCH/H does not free a dyn TS and would not free a TCH/F. It has to be the last
+ * TCH/H of a dynamic timeslot that is freed to get a new TCH/F in the current cell from the handover.
+ * Hence the ts_usage_count() condition. */
+ if (c->current.lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN
+ && ts_usage_count(c->current.lchan->ts) == 1)
+ c->current.lchan_frees_tchf = 1;
+ else
+ c->current.lchan_frees_tchf = 0;
+ break;
+ default:
+ break;
+ }
+
+ /* For inter-BSC handover, the target BTS is in a different BSC and hence NULL here. */
+ if (c->target.bts) {
+ bts_counts = &c->target.bts->chan_counts;
+ c->target.free_tchf = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F];
+ c->target.min_free_tchf = ho_get_hodec2_tchf_min_slots(c->target.bts->ho);
+ c->target.free_tchh = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H];
+ c->target.min_free_tchh = ho_get_hodec2_tchh_min_slots(c->target.bts->ho);
+
+ /* Would the next TCH/F lchan occupy a dynamic timeslot that currently counts for free TCH/H timeslots?
+ */
+ next_lchan = lchan_avail_by_type(c->target.bts, GSM_LCHAN_TCH_F,
+ SELECT_FOR_HANDOVER, NULL, false);
+ if (next_lchan && next_lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN)
+ c->target.next_tchf_reduces_tchh = 2;
+ else
+ c->target.next_tchf_reduces_tchh = 0;
+
+ /* Would the next TCH/H lchan occupy a dynamic timeslot that currently counts for free TCH/F timeslots?
+ * Note that a dyn TS already in TCH/H mode (half occupied) would not reduce free TCH/F. */
+ next_lchan = lchan_avail_by_type(c->target.bts, GSM_LCHAN_TCH_H,
+ SELECT_FOR_HANDOVER, NULL, false);
+ if (next_lchan && next_lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN
+ && next_lchan->ts->pchan_is != GSM_PCHAN_TCH_H)
+ c->target.next_tchh_reduces_tchf = 1;
+ else
+ c->target.next_tchh_reduces_tchf = 0;
+ } else {
+
+ c->target.free_tchf = 0;
+ c->target.min_free_tchf = 0;
+ c->target.next_tchh_reduces_tchf = 0;
+ c->target.free_tchh = 0;
+ c->target.min_free_tchh = 0;
+ c->target.next_tchf_reduces_tchh = 0;
+ }
+}
+
/* add candidate for re-assignment within the current cell */
static void collect_assignment_candidate(struct gsm_lchan *lchan, struct ho_candidate *clist,
- unsigned int *candidates, int av_rxlev)
+ unsigned int *candidates, int rxlev_current)
{
struct gsm_bts *bts = lchan->ts->trx->bts;
- int tchf_count, tchh_count;
struct ho_candidate c;
- tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F);
- tchh_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_H);
-
c = (struct ho_candidate){
- .lchan = lchan,
- .bts = bts,
- .requirements = check_requirements(lchan, bts, tchf_count, tchh_count),
- .avg = av_rxlev,
+ .current = {
+ .lchan = lchan,
+ .bts = bts,
+ .rxlev = rxlev_current,
+ },
+ .target = {
+ .bts = bts,
+ .rxlev = rxlev_current, /* same cell, same rxlev */
+ },
};
+ candidate_set_free_tch(&c);
+ check_requirements(&c);
+
+ debug_candidate(&c);
+
+ if (!c.requirements)
+ return;
- debug_candidate(&c, 0, tchf_count, tchh_count);
clist[*candidates] = c;
(*candidates)++;
}
@@ -867,22 +1110,17 @@ static void collect_assignment_candidate(struct gsm_lchan *lchan, struct ho_cand
/* add candidates for handover to all neighbor cells */
static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_meas_proc *nmp,
struct ho_candidate *clist, unsigned int *candidates,
- bool include_weaker_rxlev, int av_rxlev,
+ bool include_weaker_rxlev, int rxlev_current,
int *neighbors_count)
{
struct gsm_bts *bts = lchan->ts->trx->bts;
- int tchf_count = 0;
- int tchh_count = 0;
struct gsm_bts *neighbor_bts;
- const struct gsm0808_cell_id_list2 *neighbor_cil;
- struct neighbor_ident_key ni = {
- .from_bts = bts->nr,
+ struct gsm0808_cell_id_list2 neighbor_cil;
+ struct cell_ab target_ab = {
.arfcn = nmp->arfcn,
.bsic = nmp->bsic,
};
- int avg;
struct ho_candidate c;
- int min_rxlev;
struct handover_cfg *neigh_cfg;
/* skip empty slots */
@@ -900,20 +1138,10 @@ static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_mea
return;
}
- neighbor_bts = bts_by_neighbor_ident(bts->network, &ni);
+ find_handover_target_cell(&neighbor_bts, &neighbor_cil,
+ lchan->conn, &target_ab, false);
- neighbor_cil = neighbor_ident_get(bts->network->neighbor_bss_cells, &ni);
-
- if (neighbor_bts && neighbor_cil) {
- LOGPHOBTS(bts, LOGL_ERROR, "Configuration error: %s exists as both local"
- " neighbor (bts %u) and remote-BSS neighbor (%s). Will consider only"
- " the local-BSS neighbor.\n",
- neighbor_ident_key_name(&ni),
- neighbor_bts->nr, gsm0808_cell_id_list_name(neighbor_cil));
- neighbor_cil = NULL;
- }
-
- if (!neighbor_bts && !neighbor_cil) {
+ if (!neighbor_bts && !neighbor_cil.id_list_len) {
LOGPHOBTS(bts, LOGL_DEBUG, "no neighbor ARFCN %u BSIC %u configured for this cell\n",
nmp->arfcn, nmp->bsic);
return;
@@ -929,51 +1157,54 @@ static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_mea
* instead assume the local BTS' config to apply. */
neigh_cfg = (neighbor_bts ? : bts)->ho;
- /* calculate average rxlev for this cell over the window */
- avg = neigh_meas_avg(nmp, ho_get_hodec2_rxlev_neigh_avg_win(bts->ho));
-
c = (struct ho_candidate){
- .lchan = lchan,
- .avg = avg,
- .nik = ni,
- .bts = neighbor_bts,
- .cil = neighbor_cil,
+ .current = {
+ .lchan = lchan,
+ .bts = bts,
+ .rxlev = rxlev_current,
+ },
+ .target = {
+ .ab = target_ab,
+ .bts = neighbor_bts,
+ .cell_ids = neighbor_cil,
+ .rxlev = neigh_meas_avg(nmp, ho_get_hodec2_rxlev_neigh_avg_win(bts->ho)),
+ },
};
+ candidate_set_free_tch(&c);
/* Heed rxlev hysteresis only if the RXLEV/RXQUAL/TA levels of the MS aren't critically bad and
* we're just looking for an improvement. If levels are critical, we desperately need a handover
* and thus skip the hysteresis check. */
if (!include_weaker_rxlev) {
- unsigned int pwr_hyst = ho_get_hodec2_pwr_hysteresis(bts->ho);
- if (avg <= (av_rxlev + pwr_hyst)) {
+ int pwr_hyst = ho_get_hodec2_pwr_hysteresis(bts->ho);
+ if ((c.target.rxlev - c.current.rxlev) <= pwr_hyst) {
LOGPHOCAND(&c, LOGL_DEBUG,
- "Not a candidate, because RX level (%d) is lower"
- " or equal than current RX level (%d) + hysteresis (%d)\n",
- rxlev2dbm(avg), rxlev2dbm(av_rxlev), pwr_hyst);
+ "Not a candidate, because RX level (%d dBm) is lower"
+ " or equal than current RX level (%d dBm) + hysteresis (%d)\n",
+ rxlev2dbm(c.target.rxlev), rxlev2dbm(c.current.rxlev), pwr_hyst);
return;
}
}
/* if the minimum level is not reached.
* In case of a remote-BSS, use the current BTS' configuration. */
- min_rxlev = ho_get_hodec2_min_rxlev(neigh_cfg);
- if (rxlev2dbm(avg) < min_rxlev) {
+ if (is_low_rxlev(c.target.rxlev, neigh_cfg)) {
LOGPHOCAND(&c, LOGL_DEBUG,
- "Not a candidate, because RX level (%d) is lower"
- " than the minimum required RX level (%d)\n",
- rxlev2dbm(avg), min_rxlev);
+ "Not a candidate, because RX level (%d dBm) is lower"
+ " than the minimum required RX level (%d dBm)\n",
+ rxlev2dbm(c.target.rxlev), ho_get_hodec2_min_rxlev(neigh_cfg));
return;
}
if (neighbor_bts) {
- tchf_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_F);
- tchh_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_H);
- c.requirements = check_requirements(lchan, neighbor_bts, tchf_count,
- tchh_count);
+ check_requirements(&c);
} else
- c.requirements = check_requirements_remote_bss(lchan, neighbor_cil);
+ check_requirements_remote_bss(&c);
- debug_candidate(&c, av_rxlev, tchf_count, tchh_count);
+ debug_candidate(&c);
+
+ if (!c.requirements)
+ return;
clist[*candidates] = c;
(*candidates)++;
@@ -981,45 +1212,45 @@ static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_mea
static void collect_candidates_for_lchan(struct gsm_lchan *lchan,
struct ho_candidate *clist, unsigned int *candidates,
- int *_av_rxlev, bool include_weaker_rxlev)
+ int *_rxlev_current, bool include_weaker_rxlev)
{
struct gsm_bts *bts = lchan->ts->trx->bts;
- int av_rxlev;
+ int rxlev_current;
bool assignment;
bool handover;
int neighbors_count = 0;
- unsigned int rxlev_avg_win = ho_get_hodec2_rxlev_avg_win(bts->ho);
OSMO_ASSERT(candidates);
- /* caculate average rxlev for this cell over the window */
- av_rxlev = get_meas_rep_avg(lchan,
- ho_get_hodec2_full_tdma(bts->ho) ?
- MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB,
- rxlev_avg_win);
- if (_av_rxlev)
- *_av_rxlev = av_rxlev;
+ rxlev_current = current_rxlev(lchan);
+ if (_rxlev_current)
+ *_rxlev_current = rxlev_current;
- /* in case there is no measurment report (yet) */
- if (av_rxlev < 0) {
+ /* in case there is no measurement report (yet) */
+ if (rxlev_current < 0) {
LOGPHOLCHAN(lchan, LOGL_DEBUG, "Not collecting candidates, not enough measurements"
" (got %d, want %u)\n",
- lchan->meas_rep_count, rxlev_avg_win);
+ lchan->meas_rep_count, ho_get_hodec2_rxlev_avg_win(bts->ho));
return;
}
assignment = ho_get_hodec2_as_active(bts->ho);
handover = ho_get_ho_active(bts->ho);
- if (assignment)
- collect_assignment_candidate(lchan, clist, candidates, av_rxlev);
+ /* See if re-assignment within the same cell can resolve congestion.
+ * But: when TCH/F has low rxlev, do not re-assign. If a low rxlev TCH/F were re-assigned to TCH/H, we would
+ * subsequently oscillate back to TCH/F due to low rxlev. So skip TCH/F with low rxlev. */
+ if (assignment
+ && !(lchan->type == GSM_LCHAN_TCH_F
+ && (is_low_rxlev(rxlev_current, bts->ho) || is_low_rxqual(current_rxqual(lchan), bts->ho))))
+ collect_assignment_candidate(lchan, clist, candidates, rxlev_current);
if (handover) {
int i;
for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) {
collect_handover_candidate(lchan, &lchan->neigh_meas[i],
clist, candidates,
- include_weaker_rxlev, av_rxlev, &neighbors_count);
+ include_weaker_rxlev, rxlev_current, &neighbors_count);
}
}
}
@@ -1033,7 +1264,7 @@ static void collect_candidates_for_lchan(struct gsm_lchan *lchan,
* Do not perform this process, if handover and assignment are disabled for
* the current cell.
* Do not perform handover, if the minimum acceptable RX level
- * is not reched for this cell.
+ * is not reached for this cell.
*
* If one or more 'better cells' are available, check the current and neighbor
* cell measurements in descending order of their RX levels (down-link):
@@ -1075,12 +1306,12 @@ static void collect_candidates_for_lchan(struct gsm_lchan *lchan,
* If minimum RXLEV, minimum RXQUAL or maximum TA are exceeded, the caller should pass
* include_weaker_rxlev=true so that handover is performed despite congestion.
*/
-static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_rxlev)
+static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_rxlev, bool request_upgrade_to_tch_f)
{
struct gsm_bts *bts = lchan->ts->trx->bts;
- int ahs = (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
+ int ahs = (gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR
&& lchan->type == GSM_LCHAN_TCH_H);
- int av_rxlev;
+ int rxlev_current;
struct ho_candidate clist[1 + ARRAY_SIZE(lchan->neigh_meas)];
unsigned int candidates = 0;
int i;
@@ -1096,7 +1327,7 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r
return 0;
}
- collect_candidates_for_lchan(lchan, clist, &candidates, &av_rxlev, include_weaker_rxlev);
+ collect_candidates_for_lchan(lchan, clist, &candidates, &rxlev_current, include_weaker_rxlev);
/* If assignment is disabled and no neighbor cell report exists, or no neighbor cell qualifies,
* we may not even have any candidates. */
@@ -1115,14 +1346,15 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r
continue;
/* Only consider Local-BSS cells */
- if (!clist[i].bts)
+ if (!clist[i].target.bts)
continue;
- better = clist[i].avg - av_rxlev;
- /* Apply AFS bias? */
+ better = clist[i].target.rxlev - clist[i].current.rxlev;
+ /* Apply AFS bias? Skip AFS bias for all intra-cell candidates. */
afs_bias = 0;
- if (ahs && (clist[i].requirements & REQUIREMENT_B_TCHF))
- afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
+ if (clist[i].target.bts != bts
+ && ahs && (clist[i].requirements & REQUIREMENT_B_TCHF))
+ afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].target.bts->ho);
better += afs_bias;
if (better > best_better_db) {
best_cand = &clist[i];
@@ -1134,7 +1366,7 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r
/* perform handover, if there is a candidate */
if (best_cand) {
LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate, RX level %d%s\n",
- rxlev2dbm(best_cand->avg),
+ rxlev2dbm(best_cand->target.rxlev),
best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : "");
return trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_B_MASK);
}
@@ -1148,14 +1380,15 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r
continue;
/* Only consider Local-BSS cells */
- if (!clist[i].bts)
+ if (!clist[i].target.bts)
continue;
- better = clist[i].avg - av_rxlev;
- /* Apply AFS bias? */
+ better = clist[i].target.rxlev - clist[i].current.rxlev;
+ /* Apply AFS bias? Skip AFS bias for all intra-cell candidates. */
afs_bias = 0;
- if (ahs && (clist[i].requirements & REQUIREMENT_C_TCHF))
- afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
+ if (clist[i].target.bts != bts
+ && ahs && (clist[i].requirements & REQUIREMENT_C_TCHF))
+ afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].target.bts->ho);
better += afs_bias;
if (better > best_better_db) {
best_cand = &clist[i];
@@ -1167,7 +1400,7 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r
/* perform handover, if there is a candidate */
if (best_cand) {
LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate, RX level %d%s\n",
- rxlev2dbm(best_cand->avg),
+ rxlev2dbm(best_cand->target.rxlev),
best_applied_afs_bias? " (applied AHS -> AFS rxlev bias)" : "");
return trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_C_MASK);
}
@@ -1188,15 +1421,19 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r
if (!(clist[i].requirements & REQUIREMENT_A_MASK))
continue;
- better = clist[i].avg - av_rxlev;
- /* Apply AFS bias?
+ better = clist[i].target.rxlev - clist[i].current.rxlev;
+ /* Apply AFS bias? Skip AFS bias for all intra-cell candidates.
* (never to remote-BSS neighbors, since we will not change the lchan type for those.) */
afs_bias = 0;
if (ahs && (clist[i].requirements & REQUIREMENT_A_TCHF)
- && clist[i].bts)
- afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
+ && clist[i].target.bts && clist[i].target.bts != bts)
+ afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].target.bts->ho);
better += afs_bias;
- if (better > best_better_db) {
+ if (better > best_better_db
+ || (better >= best_better_db /* Upgrade from TCH/H to TCH/F: allow for equal rxlev */
+ && request_upgrade_to_tch_f
+ && is_upgrade_to_tchf(&clist[i], REQUIREMENT_A_MASK))) {
+
best_cand = &clist[i];
best_better_db = better;
best_applied_afs_bias = afs_bias? true : false;
@@ -1205,10 +1442,22 @@ static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_r
/* perform handover, if there is a candidate */
if (best_cand) {
+ int rc;
LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate: RX level %d%s\n",
- rxlev2dbm(best_cand->avg),
+ rxlev2dbm(best_cand->target.rxlev),
best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : "");
- return trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_A_MASK);
+ rc = trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_A_MASK);
+
+ /* After upgrading TCH/H to TCH/F due to bad RxQual, start penalty timer to avoid re-assignment within
+ * the same cell again, to avoid oscillation from RxQual noise combined with congestion resolution. */
+ if (!rc && best_cand->target.bts == best_cand->current.bts
+ && is_upgrade_to_tchf(best_cand, REQUIREMENT_A_MASK)) {
+ struct gsm0808_cell_id bts_id;
+ gsm_bts_cell_id(&bts_id, best_cand->target.bts);
+ penalty_timers_add(lchan->conn, &lchan->conn->hodec2.penalty_timers, &bts_id,
+ ho_get_hodec2_penalty_low_rxqual_as(bts->ho));
+ }
+ return rc;
}
/* Damn, all is congested, has too low RXLEV or cannot service the voice call due to codec
@@ -1273,15 +1522,9 @@ static void on_measurement_report(struct gsm_meas_rep *mr)
return;
}
- /* get average levels. if not enought measurements yet, value is < 0 */
- av_rxlev = get_meas_rep_avg(lchan,
- ho_get_hodec2_full_tdma(bts->ho) ?
- MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB,
- ho_get_hodec2_rxlev_avg_win(bts->ho));
- av_rxqual = get_meas_rep_avg(lchan,
- ho_get_hodec2_full_tdma(bts->ho) ?
- MEAS_REP_DL_RXQUAL_FULL : MEAS_REP_DL_RXQUAL_SUB,
- ho_get_hodec2_rxqual_avg_win(bts->ho));
+ /* get average levels. if not enough measurements yet, value is < 0 */
+ av_rxlev = current_rxlev(lchan);
+ av_rxqual = current_rxqual(lchan);
if (av_rxlev < 0 && av_rxqual < 0) {
LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Not enough recent measurements\n");
return;
@@ -1289,7 +1532,7 @@ static void on_measurement_report(struct gsm_meas_rep *mr)
/* improve levels in case of AFS, if defined */
if (lchan->type == GSM_LCHAN_TCH_F
- && lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+ && gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) {
int av_rxlev_was = av_rxlev;
int av_rxqual_was = av_rxqual;
int rxlev_bias = ho_get_hodec2_afs_bias_rxlev(bts->ho);
@@ -1321,31 +1564,34 @@ static void on_measurement_report(struct gsm_meas_rep *mr)
global_ho_reason = HO_REASON_BAD_QUALITY;
LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment due to bad quality\n");
}
- find_alternative_lchan(lchan, true);
+ find_alternative_lchan(lchan, true, true);
return;
}
/* Low Level */
- if (av_rxlev >= 0 && rxlev2dbm(av_rxlev) < ho_get_hodec2_min_rxlev(bts->ho)) {
+ if (is_low_rxlev(av_rxlev, bts->ho)) {
global_ho_reason = HO_REASON_LOW_RXLEVEL;
LOGPHOLCHAN(lchan, LOGL_NOTICE, "RX level is TOO LOW: %d < %d\n",
rxlev2dbm(av_rxlev), ho_get_hodec2_min_rxlev(bts->ho));
- find_alternative_lchan(lchan, true);
+ find_alternative_lchan(lchan, true, true);
return;
}
/* Max Distance */
if (lchan->meas_rep_count > 0
- && lchan->rqd_ta > ho_get_hodec2_max_distance(bts->ho)) {
+ && lchan->last_ta > ho_get_hodec2_max_distance(bts->ho)) {
+ struct gsm0808_cell_id bts_id;
global_ho_reason = HO_REASON_MAX_DISTANCE;
LOGPHOLCHAN(lchan, LOGL_NOTICE, "TA is TOO HIGH: %u > %d\n",
- lchan->rqd_ta, ho_get_hodec2_max_distance(bts->ho));
+ lchan->last_ta, ho_get_hodec2_max_distance(bts->ho));
/* start penalty timer to prevent coming back too
* early. it must be started before selecting a better cell,
* so there is no assignment selected, due to running
* penalty timer. */
- bts_penalty_time_add(lchan->conn, bts, ho_get_hodec2_penalty_max_dist(bts->ho));
- find_alternative_lchan(lchan, true);
+ gsm_bts_cell_id(&bts_id, bts);
+ penalty_timers_add(lchan->conn, &lchan->conn->hodec2.penalty_timers, &bts_id,
+ ho_get_hodec2_penalty_max_dist(bts->ho));
+ find_alternative_lchan(lchan, true, false);
return;
}
@@ -1356,8 +1602,121 @@ static void on_measurement_report(struct gsm_meas_rep *mr)
/* try handover to a better cell */
if (av_rxlev >= 0 && (mr->nr % pwr_interval) == 0) {
global_ho_reason = HO_REASON_BETTER_CELL;
- find_alternative_lchan(lchan, false);
+ find_alternative_lchan(lchan, false, false);
+ }
+}
+
+static bool lchan_is_on_dynamic_ts(struct gsm_lchan *lchan)
+{
+ return lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN
+ || lchan->ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH;
+}
+
+/* Given two candidates, pick the one that should rather be moved during handover.
+ * Return the better candidate in out-parameters best_cand and best_avg_db.
+ */
+static struct ho_candidate *pick_better_lchan_to_move(struct ho_candidate *a,
+ struct ho_candidate *b,
+ uint8_t for_requirement)
+{
+ int a_rxlev_change;
+ int b_rxlev_change;
+ struct ho_candidate *ret = a;
+
+ if (!a)
+ return b;
+ if (!b)
+ return a;
+
+ a_rxlev_change = a->target.rxlev - a->current.rxlev;
+ b_rxlev_change = b->target.rxlev - b->current.rxlev;
+
+ /* Typically, a congestion related handover reduces RXLEV. If there is a candidate that actually improves RXLEV,
+ * prefer that, because it pre-empts a likely handover due to measurement results later. Also favor unchanged
+ * RXLEV over a loss of RXLEV (favor staying within the same cell over moving to a worse cell). */
+ if (a_rxlev_change >= 0 && a_rxlev_change > b_rxlev_change)
+ return a;
+ if (b_rxlev_change >= 0 && b_rxlev_change > a_rxlev_change)
+ return b;
+
+ if (a_rxlev_change < 0 && b_rxlev_change < 0) {
+ /* For handover that reduces RXLEV, favor the highest resulting RXLEV, AFS bias applied. */
+ int a_rxlev = a->target.rxlev + a->target.rxlev_afs_bias;
+ int b_rxlev = b->target.rxlev + b->target.rxlev_afs_bias;
+
+ if (a_rxlev > b_rxlev)
+ return a;
+ if (b_rxlev > a_rxlev)
+ return b;
+ /* There is no target RXLEV difference between the two candidates. Let other factors influence the
+ * choice. */
+ }
+
+ /* Prefer picking a dynamic timeslot: free PDCH and allow more timeslot type flexibility for further
+ * congestion resolution. */
+ if (lchan_is_on_dynamic_ts(b->current.lchan)) {
+ unsigned int ac, bc;
+
+ if (!lchan_is_on_dynamic_ts(a->current.lchan))
+ return b;
+
+ /* Both are dynamic timeslots. Prefer one that completely (or to a higher degree) frees its
+ * timeslot. */
+ ac = ts_usage_count(a->current.lchan->ts);
+ bc = ts_usage_count(b->current.lchan->ts);
+ if (bc < ac)
+ return b;
+ if (ac < bc)
+ return a;
+ /* (If both are dynamic timeslots, favor moving the later dynamic timeslot. That is a vague preference
+ * for later dynamic TS to become PDCH and join up with plain PDCH that follow it -- not actually clear
+ * whether that helps, and depends on user's TS config. No harm done either way.) */
+ ret = b;
+ }
+
+ /* When upgrading TCH/H to TCH/F, favor moving a TCH/H with lower current rxlev, because presumably that
+ * one benefits more from a higher bandwidth. */
+ if (is_upgrade_to_tchf(a, for_requirement) && is_upgrade_to_tchf(b, for_requirement)) {
+ if (b->current.rxlev < a->current.rxlev)
+ return b;
+ if (a->current.rxlev < b->current.rxlev)
+ return a;
+ }
+
+ return ret;
+}
+
+static struct ho_candidate *pick_best_candidate(struct ho_candidate *clist, int clist_len,
+ uint8_t for_requirement)
+{
+ struct ho_candidate *result = NULL;
+ int i;
+
+ for (i = 0; i < clist_len; i++) {
+ struct ho_candidate *c = &clist[i];
+
+ /* For multiple passes of congestion resolution, already handovered candidates are marked by lchan =
+ * NULL. (though at the time of writing, multiple passes of congestion resolution are DISABLED.) */
+ if (!c->current.lchan)
+ continue;
+
+ /* Omit remote BSS */
+ if (!c->target.bts)
+ continue;
+
+ if (!(c->requirements & for_requirement))
+ continue;
+
+ /* improve AHS */
+ if (is_upgrade_to_tchf(c, for_requirement))
+ c->target.rxlev_afs_bias = ho_get_hodec2_afs_bias_rxlev(c->target.bts->ho);
+ else
+ c->target.rxlev_afs_bias = 0;
+
+ result = pick_better_lchan_to_move(result, c, for_requirement);
}
+
+ return result;
}
/*
@@ -1376,7 +1735,7 @@ static void on_measurement_report(struct gsm_meas_rep *mr)
* Do not perform this process, if handover and assignment are disabled for
* the current cell.
* Do not perform handover, if the minimum acceptable RX level
- * is not reched for this cell.
+ * is not reached for this cell.
* Only check candidates that will solve/reduce congestion.
*
* If a cell is congested, all slots are checked for all their RX levels
@@ -1417,13 +1776,9 @@ static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int
int i, j;
struct ho_candidate *clist;
unsigned int candidates;
- struct ho_candidate *best_cand = NULL, *worst_cand = NULL;
- struct gsm_lchan *delete_lchan = NULL;
- unsigned int best_avg_db, worst_avg_db;
- int avg;
+ struct ho_candidate *best_cand = NULL;
int rc = 0;
int any_ho = 0;
- int is_improved = 0;
if (tchf_congestion < 0)
tchf_congestion = 0;
@@ -1454,6 +1809,10 @@ static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int
/* (Do not consider dynamic TS that are in PDCH mode) */
switch (ts->pchan_is) {
case GSM_PCHAN_TCH_F:
+ /* No need to collect TCH/F candidates if no TCH/F needs to be moved. */
+ if (tchf_congestion == 0)
+ continue;
+
lc = &ts->lchan[0];
/* omit if channel not active */
if (lc->type != GSM_LCHAN_TCH_F
@@ -1468,6 +1827,10 @@ static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int
collect_candidates_for_lchan(lc, clist, &candidates, NULL, true);
break;
case GSM_PCHAN_TCH_H:
+ /* No need to collect TCH/H candidates if no TCH/H needs to be moved. */
+ if (tchh_congestion == 0)
+ continue;
+
for (j = 0; j < 2; j++) {
lc = &ts->lchan[j];
/* omit if channel not active */
@@ -1497,71 +1860,31 @@ static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int
if (log_check_level(DHODEC, LOGL_DEBUG)) {
LOGPHOBTS(bts, LOGL_DEBUG, "Considering %u candidates to solve congestion:\n", candidates);
for (i = 0; i < candidates; i++) {
- LOGPHOCAND(&clist[i], LOGL_DEBUG, "#%d: req=0x%x avg-rxlev=%d\n",
- i, clist[i].requirements, clist[i].avg);
+
+ LOGPHOCAND(&clist[i], LOGL_DEBUG, "#%d: req={TCH/F:" REQUIREMENTS_FMT ", TCH/H:" REQUIREMENTS_FMT "} avg-rxlev=%d dBm\n",
+ i, REQUIREMENTS_ARGS(clist[i].requirements, F),
+ REQUIREMENTS_ARGS(clist[i].requirements, H),
+ rxlev2dbm(clist[i].target.rxlev));
}
}
#if 0
next_b1:
#endif
- /* select best candidate that fulfills requirement B,
- * omit change from AHS to AFS */
- best_avg_db = 0;
- for (i = 0; i < candidates; i++) {
- /* delete subscriber that just have handovered */
- if (clist[i].lchan == delete_lchan)
- clist[i].lchan = NULL;
- /* omit all subscribers that are handovered */
- if (!clist[i].lchan)
- continue;
-
- /* Do not resolve congestion towards remote BSS, which would cause oscillation if the
- * remote BSS is also congested. */
- /* TODO: attempt inter-BSC HO if no local cells qualify, and rely on the remote BSS to
- * deny receiving the handover if it also considers itself congested. Maybe do that only
- * when the cell is absolutely full, i.e. not only min-free-slots. (x) */
- if (!clist[i].bts)
- continue;
-
- if (!(clist[i].requirements & REQUIREMENT_B_MASK))
- continue;
- /* omit assignment from AHS to AFS */
- if (clist[i].lchan->ts->trx->bts == clist[i].bts
- && clist[i].lchan->type == GSM_LCHAN_TCH_H
- && (clist[i].requirements & REQUIREMENT_B_TCHF))
- continue;
- /* omit candidates that will not solve/reduce congestion */
- if (clist[i].lchan->type == GSM_LCHAN_TCH_F
- && tchf_congestion <= 0)
- continue;
- if (clist[i].lchan->type == GSM_LCHAN_TCH_H
- && tchh_congestion <= 0)
- continue;
-
- avg = clist[i].avg;
- /* improve AHS */
- if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
- && clist[i].lchan->type == GSM_LCHAN_TCH_H
- && (clist[i].requirements & REQUIREMENT_B_TCHF)) {
- avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
- is_improved = 1;
- } else
- is_improved = 0;
- LOGPHOCAND(&clist[i], LOGL_DEBUG, "candidate %d: avg=%d best_avg_db=%d\n",
- i, avg, best_avg_db);
- if (avg > best_avg_db) {
- best_cand = &clist[i];
- best_avg_db = avg;
- }
- }
-
- /* perform handover, if there is a candidate */
+ /* select best candidate that does not cause congestion in the target.
+ * Do not resolve congestion towards remote BSS, which would cause oscillation if the remote BSS is also
+ * congested.
+ * Treating specially below: upgrading TCH/H to TCH/F within the same cell, so omit here.
+ */
+ /* TODO: attempt inter-BSC HO if no local cells qualify, and rely on the remote BSS to
+ * deny receiving the handover if it also considers itself congested. Maybe do that only
+ * when the cell is absolutely full, i.e. not only min-free-slots. (x) */
+ best_cand = pick_best_candidate(clist, candidates, REQUIREMENT_B_MASK);
if (best_cand) {
any_ho = 1;
LOGPHOCAND(best_cand, LOGL_DEBUG, "Best candidate: RX level %d%s\n",
- rxlev2dbm(best_cand->avg),
- is_improved ? " (applied AHS->AFS bias)" : "");
+ rxlev2dbm(best_cand->target.rxlev),
+ best_cand->target.rxlev_afs_bias ? " (applied AHS->AFS bias)" : "");
trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_B_MASK);
#if 0
/* if there is still congestion, mark lchan as deleted
@@ -1585,132 +1908,17 @@ next_b1:
}
#if 0
-next_b2:
-#endif
- /* select worst candidate that fulfills requirement B,
- * select candidates that change from AHS to AFS only */
- if (tchh_congestion > 0) {
- /* since this will only check half rate channels, it will
- * only need to be checked, if tchh is congested */
- worst_avg_db = 999;
- for (i = 0; i < candidates; i++) {
- /* delete subscriber that just have handovered */
- if (clist[i].lchan == delete_lchan)
- clist[i].lchan = NULL;
- /* omit all subscribers that are handovered */
- if (!clist[i].lchan)
- continue;
-
- /* Do not resolve congestion towards remote BSS, which would cause oscillation if
- * the remote BSS is also congested. */
- /* TODO: see (x) above */
- if (!clist[i].bts)
- continue;
-
- if (!(clist[i].requirements & REQUIREMENT_B_MASK))
- continue;
- /* omit all but assignment from AHS to AFS */
- if (clist[i].lchan->ts->trx->bts != clist[i].bts
- || clist[i].lchan->type != GSM_LCHAN_TCH_H
- || !(clist[i].requirements & REQUIREMENT_B_TCHF))
- continue;
-
- avg = clist[i].avg;
- /* improve AHS */
- if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
- && clist[i].lchan->type == GSM_LCHAN_TCH_H) {
- avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
- is_improved = 1;
- } else
- is_improved = 0;
- if (avg < worst_avg_db) {
- worst_cand = &clist[i];
- worst_avg_db = avg;
- }
- }
- }
-
- /* perform handover, if there is a candidate */
- if (worst_cand) {
- any_ho = 1;
- LOGPHOCAND(worst_cand, LOGL_INFO, "Worst candidate: RX level %d from TCH/H -> TCH/F%s\n",
- rxlev2dbm(worst_cand->avg),
- is_improved ? " (applied AHS -> AFS rxlev bias)" : "");
- trigger_ho(worst_cand, worst_cand->requirements & REQUIREMENT_B_MASK);
-#if 0
- /* if there is still congestion, mark lchan as deleted
- * and redo this process */
- tchh_congestion--;
- if (tchh_congestion > 0) {
- delete_lchan = worst_cand->lchan;
- best_cand = NULL;
- goto next_b2;
- }
-#else
- /* must exit here, because triggering handover/assignment
- * will cause change in requirements. more check for this
- * bts is performed in the next iteration.
- */
-#endif
- goto exit;
- }
-
-#if 0
next_c1:
#endif
- /* select best candidate that fulfills requirement C,
- * omit change from AHS to AFS */
- best_avg_db = 0;
- for (i = 0; i < candidates; i++) {
- /* delete subscriber that just have handovered */
- if (clist[i].lchan == delete_lchan)
- clist[i].lchan = NULL;
- /* omit all subscribers that are handovered */
- if (!clist[i].lchan)
- continue;
-
- /* Do not resolve congestion towards remote BSS, which would cause oscillation if
- * the remote BSS is also congested. */
- /* TODO: see (x) above */
- if (!clist[i].bts)
- continue;
-
- if (!(clist[i].requirements & REQUIREMENT_C_MASK))
- continue;
- /* omit assignment from AHS to AFS */
- if (clist[i].lchan->ts->trx->bts == clist[i].bts
- && clist[i].lchan->type == GSM_LCHAN_TCH_H
- && (clist[i].requirements & REQUIREMENT_C_TCHF))
- continue;
- /* omit candidates that will not solve/reduce congestion */
- if (clist[i].lchan->type == GSM_LCHAN_TCH_F
- && tchf_congestion <= 0)
- continue;
- if (clist[i].lchan->type == GSM_LCHAN_TCH_H
- && tchh_congestion <= 0)
- continue;
-
- avg = clist[i].avg;
- /* improve AHS */
- if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
- && clist[i].lchan->type == GSM_LCHAN_TCH_H
- && (clist[i].requirements & REQUIREMENT_C_TCHF)) {
- avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
- is_improved = 1;
- } else
- is_improved = 0;
- if (avg > best_avg_db) {
- best_cand = &clist[i];
- best_avg_db = avg;
- }
- }
-
- /* perform handover, if there is a candidate */
+ /* Select best candidate that balances congestion.
+ * Again no remote BSS.
+ * Again no TCH/H -> F upgrades within the same cell. */
+ best_cand = pick_best_candidate(clist, candidates, REQUIREMENT_C_MASK);
if (best_cand) {
any_ho = 1;
LOGPHOCAND(best_cand, LOGL_INFO, "Best candidate: RX level %d%s\n",
- rxlev2dbm(best_cand->avg),
- is_improved ? " (applied AHS -> AFS rxlev bias)" : "");
+ rxlev2dbm(best_cand->target.rxlev),
+ best_cand->target.rxlev_afs_bias ? " (applied AHS -> AFS rxlev bias)" : "");
trigger_ho(best_cand, best_cand->requirements & REQUIREMENT_C_MASK);
#if 0
/* if there is still congestion, mark lchan as deleted
@@ -1733,84 +1941,7 @@ next_c1:
goto exit;
}
- LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a best candidate that fulfills requirement C"
- " (omitting change from AHS to AFS)\n");
-
-#if 0
-next_c2:
-#endif
- /* select worst candidate that fulfills requirement C,
- * select candidates that change from AHS to AFS only */
- if (tchh_congestion > 0) {
- /* since this will only check half rate channels, it will
- * only need to be checked, if tchh is congested */
- worst_avg_db = 999;
- for (i = 0; i < candidates; i++) {
- /* delete subscriber that just have handovered */
- if (clist[i].lchan == delete_lchan)
- clist[i].lchan = NULL;
- /* omit all subscribers that are handovered */
- if (!clist[i].lchan)
- continue;
-
- /* Do not resolve congestion towards remote BSS, which would cause oscillation if
- * the remote BSS is also congested. */
- /* TODO: see (x) above */
- if (!clist[i].bts)
- continue;
-
- if (!(clist[i].requirements & REQUIREMENT_C_MASK))
- continue;
- /* omit all but assignment from AHS to AFS */
- if (clist[i].lchan->ts->trx->bts != clist[i].bts
- || clist[i].lchan->type != GSM_LCHAN_TCH_H
- || !(clist[i].requirements & REQUIREMENT_C_TCHF))
- continue;
-
- avg = clist[i].avg;
- /* improve AHS */
- if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR
- && clist[i].lchan->type == GSM_LCHAN_TCH_H) {
- avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho);
- is_improved = 1;
- } else
- is_improved = 0;
- LOGP(DHODEC, LOGL_DEBUG, "candidate %d: avg=%d worst_avg_db=%d\n", i, avg,
- worst_avg_db);
- if (avg < worst_avg_db) {
- worst_cand = &clist[i];
- worst_avg_db = avg;
- }
- }
- }
-
- /* perform handover, if there is a candidate */
- if (worst_cand) {
- any_ho = 1;
- LOGPHOCAND(worst_cand, LOGL_INFO, "Worst candidate: RX level %d from TCH/H -> TCH/F%s\n",
- rxlev2dbm(worst_cand->avg),
- is_improved ? " (applied AHS -> AFS rxlev bias)" : "");
- trigger_ho(worst_cand, worst_cand->requirements & REQUIREMENT_C_MASK);
-#if 0
- /* if there is still congestion, mark lchan as deleted
- * and redo this process */
- tchh_congestion--;
- if (tchh_congestion > 0) {
- delete_lchan = worst_cand->lchan;
- worst_cand = NULL;
- goto next_c2;
- }
-#else
- /* must exit here, because triggering handover/assignment
- * will cause change in requirements. more check for this
- * bts is performed in the next iteration.
- */
-#endif
- goto exit;
- }
- LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a worst candidate that fulfills requirement C,"
- " selecting candidates that change from AHS to AFS only\n");
-
+ LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a best candidate that fulfills requirement C\n");
exit:
/* free array */
@@ -1830,8 +1961,9 @@ exit:
static void bts_congestion_check(struct gsm_bts *bts)
{
+ struct chan_counts *bts_counts;
int min_free_tchf, min_free_tchh;
- int tchf_count, tchh_count;
+ int free_tchf, free_tchh;
global_ho_reason = HO_REASON_CONGESTION;
@@ -1857,19 +1989,20 @@ static void bts_congestion_check(struct gsm_bts *bts)
return;
}
- tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F);
- tchh_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_H);
+ bts_counts = &bts->chan_counts;
+ free_tchf = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_F];
+ free_tchh = bts_counts->val[CHAN_COUNTS1_ALL][CHAN_COUNTS2_FREE][GSM_LCHAN_TCH_H];
LOGPHOBTS(bts, LOGL_INFO, "Congestion check: (free/want-free) TCH/F=%d/%d TCH/H=%d/%d\n",
- tchf_count, min_free_tchf, tchh_count, min_free_tchh);
+ free_tchf, min_free_tchf, free_tchh, min_free_tchh);
/* only check BTS if congested */
- if (tchf_count >= min_free_tchf && tchh_count >= min_free_tchh) {
+ if (free_tchf >= min_free_tchf && free_tchh >= min_free_tchh) {
LOGPHOBTS(bts, LOGL_DEBUG, "Not congested\n");
return;
}
LOGPHOBTS(bts, LOGL_DEBUG, "Attempting to resolve congestion...\n");
- bts_resolve_congestion(bts, min_free_tchf - tchf_count, min_free_tchh - tchh_count);
+ bts_resolve_congestion(bts, min_free_tchf - free_tchf, min_free_tchh - free_tchh);
}
void hodec2_congestion_check(struct gsm_network *net)
@@ -1890,7 +2023,6 @@ static void congestion_check_cb(void *arg)
static void on_handover_end(struct gsm_subscriber_connection *conn, enum handover_result result)
{
struct gsm_bts *old_bts = NULL;
- struct gsm_bts *new_bts = NULL;
int penalty;
struct handover *ho = &conn->ho;
@@ -1900,8 +2032,6 @@ static void on_handover_end(struct gsm_subscriber_connection *conn, enum handove
if (conn->lchan)
old_bts = conn->lchan->ts->trx->bts;
- if (ho->new_lchan)
- new_bts = ho->new_lchan->ts->trx->bts;
/* Only interested in handovers within this BSS or going out into another BSS. Incoming handovers
* from another BSS are accounted for in the other BSS. */
@@ -1928,11 +2058,7 @@ static void on_handover_end(struct gsm_subscriber_connection *conn, enum handove
LOG_HO(conn, LOGL_NOTICE, "Failed, starting penalty timer (%d s)\n", penalty);
conn->hodec2.failures = 0;
-
- if (new_bts)
- bts_penalty_time_add(conn, new_bts, penalty);
- else
- nik_penalty_time_add(conn, &ho->target_cell, penalty);
+ penalty_timers_add_list(conn, &conn->hodec2.penalty_timers, &ho->target_cell_ids, penalty);
}
static struct handover_decision_callbacks hodec2_callbacks = {
diff --git a/src/osmo-bsc/handover_fsm.c b/src/osmo-bsc/handover_fsm.c
index 35f2e5553..24766a5ea 100644
--- a/src/osmo-bsc/handover_fsm.c
+++ b/src/osmo-bsc/handover_fsm.c
@@ -25,10 +25,13 @@
#include <osmocom/gsm/rsl.h>
#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
+
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/handover_cfg.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/lchan_fsm.h>
@@ -38,9 +41,12 @@
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/osmo_bsc.h>
#include <osmocom/bsc/osmo_bsc_lcls.h>
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
#include <osmocom/bsc/codec_pref.h>
#include <osmocom/bsc/gsm_08_08.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/lcs_loc_req.h>
+#include <osmocom/bsc/bsc_stats.h>
+#include <osmocom/bsc/lchan.h>
#define LOG_FMT_BTS "bts %u lac-ci %u-%u arfcn-bsic %d-%d"
#define LOG_ARGS_BTS(bts) \
@@ -55,9 +61,9 @@
lchan ? lchan->ts->trx->bts->nr : 0, \
lchan ? lchan->ts->trx->nr : 0, \
lchan ? lchan->ts->nr : 0, \
- lchan ? gsm_lchant_name(lchan->type) : "?", \
+ lchan ? gsm_chan_t_name(lchan->type) : "?", \
lchan ? lchan->nr : 0, \
- lchan ? gsm48_chan_mode_name(lchan->tch_mode) : "?"
+ lchan ? gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode) : "?"
#define LOG_FMT_TO_LCHAN "%u-%u-%u-%s%s%s-%u"
#define LOG_ARGS_TO_LCHAN(lchan) \
@@ -77,12 +83,39 @@
/* Assume presence of local var 'conn' as struct gsm_subscriber_connection.
* This is a macro to preserve the source file and line number in logging. */
-#define ho_count(counter) do { \
- LOG_HO(conn, LOGL_DEBUG, "incrementing rate counter: %s %s\n", \
+#define ho_count_bsc(counter) do { \
+ /* If a handover target could not be found, the counter index may be -1. */ \
+ if (counter < 0) \
+ break; \
+ LOG_HO(conn, LOGL_DEBUG, "(BSC) incrementing rate counter: %s %s\n", \
bsc_ctr_description[counter].name, \
bsc_ctr_description[counter].description); \
- rate_ctr_inc(&conn->network->bsc_ctrs->ctr[counter]); \
- } while(0)
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->network->bsc_ctrs, counter)); \
+ } while (0)
+
+/* Assume presence of local var 'conn' as struct gsm_subscriber_connection.
+ * Handles bts == NULL gracefully
+ * This is a macro to preserve the source file and line number in logging. */
+#define ho_count_bts(bts, counter) do { \
+ /* If a handover target could not be found, the counter index may be -1. */ \
+ if (counter < 0) \
+ break; \
+ LOG_HO(conn, LOGL_DEBUG, "(BTS) incrementing rate counter: %s %s\n", \
+ bts_ctr_description[counter].name, \
+ bts_ctr_description[counter].description); \
+ if (bts) \
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, counter)); \
+ else \
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->network->bts_unknown_ctrs, counter)); \
+ } while (0)
+
+/* Count handover result on both bts and bsc level.
+ * Call with 'counter' being the counter name without the "BSC_"/"BTS_" part,
+ * e.g. ho_count(conn_get_bts(conn), CTR_HANDOVER_ATTEMPTED); */
+#define ho_count(bts, counter) do { \
+ ho_count_bsc(BSC_##counter); \
+ ho_count_bts(bts, BTS_##counter); \
+ } while (0)
static uint8_t g_next_ho_ref = 1;
@@ -106,7 +139,7 @@ const char *handover_status(struct gsm_subscriber_connection *conn)
"("LOG_FMT_FROM_LCHAN") --HO-> ("LOG_FMT_BTS",%s) " LOG_FMT_HO_SCOPE,
LOG_ARGS_FROM_LCHAN(conn->lchan),
LOG_ARGS_BTS(ho->new_bts),
- gsm_lchant_name(ho->new_lchan_type),
+ gsm_chan_t_name(ho->new_lchan_type),
LOG_ARGS_HO_SCOPE(conn));
else
snprintf(buf, sizeof(buf),
@@ -118,7 +151,7 @@ const char *handover_status(struct gsm_subscriber_connection *conn)
snprintf(buf, sizeof(buf),
"("LOG_FMT_FROM_LCHAN") --HO-> (%s) " LOG_FMT_HO_SCOPE,
LOG_ARGS_FROM_LCHAN(conn->lchan),
- neighbor_ident_key_name(&ho->target_cell),
+ cell_ab_to_str_c(OTC_SELECT, &ho->target_cell_ab),
LOG_ARGS_HO_SCOPE(conn));
else if (ho->scope & HO_INTER_BSC_IN) {
@@ -135,14 +168,14 @@ const char *handover_status(struct gsm_subscriber_connection *conn)
ho->inter_bsc_in.cell_id_serving_name,
ho->inter_bsc_in.cell_id_target_name,
LOG_ARGS_BTS(ho->new_bts),
- gsm_lchant_name(ho->new_lchan_type),
+ gsm_chan_t_name(ho->new_lchan_type),
LOG_ARGS_HO_SCOPE(conn));
else
snprintf(buf, sizeof(buf),
"(remote:%s) --HO-> (local:%s,%s) " LOG_FMT_HO_SCOPE,
ho->inter_bsc_in.cell_id_serving_name,
ho->inter_bsc_in.cell_id_target_name,
- gsm_lchant_name(ho->new_lchan_type),
+ gsm_chan_t_name(ho->new_lchan_type),
LOG_ARGS_HO_SCOPE(conn));
} else
snprintf(buf, sizeof(buf), LOG_FMT_HO_SCOPE, LOG_ARGS_HO_SCOPE(conn));
@@ -159,12 +192,12 @@ struct gsm_subscriber_connection *ho_fi_conn(struct osmo_fsm_inst *fi)
return fi->priv;
}
-static const struct state_timeout ho_fsm_timeouts[32] = {
- [HO_ST_WAIT_LCHAN_ACTIVE] = { .T = 23042 },
- [HO_ST_WAIT_RR_HO_DETECT] = { .T = 23042 },
- [HO_ST_WAIT_RR_HO_COMPLETE] = { .T = 23042 },
- [HO_ST_WAIT_LCHAN_ESTABLISHED] = { .T = 23042 },
- [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T = 23042 },
+static const struct osmo_tdef_state_timeout ho_fsm_timeouts[32] = {
+ [HO_ST_WAIT_LCHAN_ACTIVE] = { /* Guarded by X5 + X6 in lchan_fsm_timeouts */ },
+ [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T = -9 },
+ [HO_ST_WAIT_RR_HO_DETECT] = { .T = 3103 },
+ [HO_ST_WAIT_RR_HO_COMPLETE] = { .keep_timer = true }, /* Keep T3103 */
+ [HO_ST_WAIT_LCHAN_ESTABLISHED] = { /* Guarded by T3101 in lchan_fsm_timeouts */ },
[HO_OUT_ST_WAIT_HO_COMMAND] = { .T = 7 },
[HO_OUT_ST_WAIT_CLEAR] = { .T = 8 },
};
@@ -173,25 +206,25 @@ static const struct state_timeout ho_fsm_timeouts[32] = {
* The actual timeout value is in turn obtained from network->T_defs.
* Assumes local variable fi exists. */
#define ho_fsm_state_chg(state) \
- fsm_inst_state_chg_T(fi, state, \
- ho_fsm_timeouts, \
- ((struct gsm_subscriber_connection*)(fi->priv))->network->T_defs, \
- 5)
+ osmo_tdef_fsm_inst_state_chg(fi, state, \
+ ho_fsm_timeouts, \
+ ((struct gsm_subscriber_connection*)(fi->priv))->network->T_defs, \
+ 5)
/* Log failure and transition to HO_ST_FAILURE, which triggers the appropriate actions. */
#define ho_fail(result, fmt, args...) do { \
LOG_HO(conn, LOGL_ERROR, "Handover failed in state %s, %s: " fmt "\n", \
osmo_fsm_inst_state_name(conn->fi), handover_result_name(result), ## args); \
handover_end(conn, result); \
- } while(0)
+ } while (0)
#define ho_success() do { \
LOG_HO(conn, LOGL_DEBUG, "Handover succeeded\n"); \
handover_end(conn, HO_RESULT_OK); \
- } while(0)
+ } while (0)
/* issue handover to a cell identified by ARFCN and BSIC */
-void handover_request(struct handover_out_req *req)
+int handover_request(struct handover_out_req *req)
{
struct gsm_subscriber_connection *conn;
OSMO_ASSERT(req->old_lchan);
@@ -199,8 +232,9 @@ void handover_request(struct handover_out_req *req)
conn = req->old_lchan->conn;
OSMO_ASSERT(conn && conn->fi);
- /* To make sure we're allowed to start a handover, go through a gscon event dispatch. */
- osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_START, req);
+ /* To make sure we're allowed to start a handover, go through a gscon event dispatch. If that is accepted, the
+ * same req is passed to handover_start(). */
+ return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_START, req);
}
/* Check that ho has old_lchan and/or new_lchan and conn pointers match.
@@ -235,22 +269,23 @@ static void ho_fsm_update_id(struct osmo_fsm_inst *fi, const char *label)
if (conn->fi->id)
osmo_fsm_inst_update_id_f(fi, "%s_%s", label, conn->fi->id);
else
- osmo_fsm_inst_update_id_f(fi, "%s_conn%u", label, conn->sccp.conn_id);
+ osmo_fsm_inst_update_id_f(fi, "%s_conn%u", label, conn->sccp.conn.conn_id);
}
static void handover_reset(struct gsm_subscriber_connection *conn)
{
- struct mgwep_ci *ci;
+ struct osmo_mgcpc_ep_ci *ci;
if (conn->ho.new_lchan)
/* New lchan was activated but never passed to a conn */
- lchan_release(conn->ho.new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(conn->ho.new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL,
+ NULL);
ci = conn->ho.created_ci_for_msc;
if (ci) {
gscon_forget_mgw_endpoint_ci(conn, ci);
/* If this is the last endpoint released, the mgw_endpoint_fsm will terminate and tell
* the gscon about it. */
- mgw_endpoint_ci_dlcx(ci);
+ osmo_mgcpc_ep_ci_dlcx(ci);
}
conn->ho = (struct handover){
@@ -258,7 +293,7 @@ static void handover_reset(struct gsm_subscriber_connection *conn)
};
}
-void handover_fsm_init()
+static __attribute__((constructor)) void handover_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&ho_fsm) == 0);
}
@@ -283,9 +318,10 @@ void handover_start(struct handover_out_req *req)
OSMO_ASSERT(req && req->old_lchan && req->old_lchan->conn);
struct gsm_subscriber_connection *conn = req->old_lchan->conn;
+ const struct cell_ab *search_for = &req->target_cell_ab;
struct handover *ho = &conn->ho;
- struct gsm_bts *bts;
- const struct gsm0808_cell_id_list2 *cil;
+ struct gsm_bts *local_target_cell = NULL;
+ struct gsm0808_cell_id_list2 remote_target_cells = {};
if (conn->ho.fi) {
LOG_HO(conn, LOGL_ERROR, "Handover requested while another handover is ongoing; Ignore\n");
@@ -293,29 +329,36 @@ void handover_start(struct handover_out_req *req)
}
handover_reset(conn);
+ /* When handover_start() is invoked by the gscon, it expects a HANDOVER_END event. The best way to ensure this
+ * is to always create a handover_fsm instance, even if the target cell is not resolved yet. Any failure should
+ * then call handover_end(), which ensures that the conn snaps back to a valid state. */
handover_fsm_alloc(conn);
+ ho_count(conn_get_bts(conn), CTR_HANDOVER_ATTEMPTED);
ho->from_hodec_id = req->from_hodec_id;
ho->new_lchan_type = req->new_lchan_type == GSM_LCHAN_NONE ?
req->old_lchan->type : req->new_lchan_type;
- ho->target_cell = req->target_nik;
+ ho->target_cell_ab = req->target_cell_ab;
- bts = bts_by_neighbor_ident(conn->network, &req->target_nik);
- if (bts) {
- ho->new_bts = bts;
+ if (find_handover_target_cell(&local_target_cell, &remote_target_cells,
+ conn, search_for, true)) {
+ handover_end(conn, HO_RESULT_ERROR);
+ return;
+ }
+
+ if (local_target_cell) {
+ ho->new_bts = local_target_cell;
handover_start_intra_bsc(conn);
return;
}
- cil = neighbor_ident_get(conn->network->neighbor_bss_cells, &req->target_nik);
- if (cil) {
- handover_start_inter_bsc_out(conn, cil);
+ if (remote_target_cells.id_list_len) {
+ handover_start_inter_bsc_out(conn, &remote_target_cells);
return;
}
- LOG_HO(conn, LOGL_ERROR, "Cannot handover %s: neighbor unknown\n",
- neighbor_ident_key_name(&req->target_nik));
- handover_end(conn, HO_RESULT_FAIL_NO_CHANNEL);
+ /* should never reach this, because find_handover_target_cell() would have returned error. */
+ OSMO_ASSERT(false);
}
/*! Hand over the specified logical channel to the specified new BTS and possibly change the lchan type.
@@ -326,28 +369,35 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn)
struct handover *ho = &conn->ho;
struct osmo_fsm_inst *fi = conn->ho.fi;
struct lchan_activate_info info;
+ struct gsm_bts *bts = conn_get_bts(conn);
OSMO_ASSERT(ho->new_bts);
OSMO_ASSERT(ho->new_lchan_type != GSM_LCHAN_NONE);
OSMO_ASSERT(!ho->new_lchan);
- ho->scope = (ho->new_bts == conn->lchan->ts->trx->bts) ? HO_INTRA_CELL : HO_INTRA_BSC;
+ ho->scope = (ho->new_bts == bts) ? HO_INTRA_CELL : HO_INTRA_BSC;
ho->ho_ref = g_next_ho_ref++;
ho->async = true;
+ gsm_bts_cell_id_list(&ho->target_cell_ids, ho->new_bts);
- ho->new_lchan = lchan_select_by_type(ho->new_bts, ho->new_lchan_type);
+ ho->new_lchan = lchan_select_by_type(ho->new_bts,
+ ho->new_lchan_type,
+ SELECT_FOR_HANDOVER,
+ NULL);
- if (ho->scope & HO_INTRA_CELL)
+ if (ho->scope & HO_INTRA_CELL) {
+ ho_count(bts, CTR_INTRA_CELL_HO_ATTEMPTED);
ho_fsm_update_id(fi, "intraCell");
- else
+ } else {
+ ho_count(bts, CTR_INTRA_BSC_HO_ATTEMPTED);
ho_fsm_update_id(fi, "intraBSC");
-
- ho_count(BSC_CTR_HANDOVER_ATTEMPTED);
+ ho_count_bts(ho->new_bts, BTS_CTR_INCOMING_INTRA_BSC_HO_ATTEMPTED);
+ }
if (!ho->new_lchan) {
ho_fail(HO_RESULT_FAIL_NO_CHANNEL,
"No %s lchan available on BTS %u",
- gsm_lchant_name(ho->new_lchan_type), ho->new_bts->nr);
+ gsm_chan_t_name(ho->new_lchan_type), ho->new_bts->nr);
return;
}
LOG_HO(conn, LOGL_DEBUG, "Selected lchan %s\n", gsm_lchan_name(ho->new_lchan));
@@ -355,18 +405,53 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn)
ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ACTIVE);
info = (struct lchan_activate_info){
- .activ_for = FOR_HANDOVER,
+ .activ_for = ACTIVATE_FOR_HANDOVER,
.for_conn = conn,
- .chan_mode = conn->lchan->tch_mode,
- .requires_voice_stream = conn->lchan->mgw_endpoint_ci_bts ? true : false,
+ .ch_mode_rate = conn->lchan->current_ch_mode_rate,
+ .encr = conn->lchan->encr,
+ .ch_indctr = conn->lchan->current_ch_indctr,
.msc_assigned_cic = conn->ho.inter_bsc_in.msc_assigned_cic,
.re_use_mgw_endpoint_from_lchan = conn->lchan,
.wait_before_switching_rtp = true,
};
+ info.ch_mode_rate.chan_rate = chan_t_to_chan_rate(ho->new_lchan->type);
+
+ /* For intra-cell handover, we know the accurate Timing Advance from the previous lchan. For inter-cell
+ * handover, no Timing Advance for the new cell is known, so leave it unset. */
+ if (ho->new_bts == bts) {
+ info.ta = conn->lchan->last_ta;
+ info.ta_known = true;
+ }
lchan_activate(ho->new_lchan, &info);
}
+/* 3GPP TS 48.008 ยง 3.2.2.58 Old BSS to New BSS Information */
+static void parse_old2new_bss_info(struct gsm_subscriber_connection *conn,
+ const uint8_t* data, uint16_t len,
+ struct handover_in_req *req)
+{
+ /* ยง 3.2.2.58: Information contained here shall take precedence over
+ duplicate information from Information Elements in the HANDOVER
+ REQUEST as long as the coding is understood by the new BSS */
+ /* ยง 3.2.2.58: <<Reception of an erroneous "Old BSS to New BSS
+ information" shall not cause a rejection of the HANDOVER REQUEST
+ message; the "Old BSS to New BSS information" information element
+ shall be discarded and the handover resource allocation procedure
+ shall continue>>. See also 3.1.19.7. */
+ struct tlv_parsed tp;
+ if (tlv_parse(&tp, &gsm0808_old_bss_to_new_bss_info_att_tlvdef, data, len, 0, 0) <= 0) {
+ LOG_HO(conn, LOGL_NOTICE, "Failed to parse IE \"Old BSS to New BSS information\"\n");
+ return;
+ }
+
+ if (TLVP_VAL(&tp, GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID)) {
+ req->last_eutran_plmn_valid = true;
+ osmo_plmn_from_bcd(TLVP_VAL(&tp, GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID),
+ &req->last_eutran_plmn);
+ }
+}
+
/* 3GPP TS 48.008 ยง 3.2.1.8 Handover Request */
static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struct msgb *msg,
struct handover_in_req *req)
@@ -378,6 +463,7 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
int payload_length;
bool aoip = gscon_is_aoip(conn);
bool sccplite = gscon_is_sccplite(conn);
+ bool has_a54 = false;
if ((aoip && sccplite) || !(aoip || sccplite)) {
LOG_HO(conn, LOGL_ERROR, "Received BSSMAP Handover Request, but conn is not"
@@ -408,6 +494,16 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
LOG_HO(conn, LOGL_ERROR, "Failed to parse Encryption Information IE\n");
return false;
}
+ req->ei_as_bitmask = *e->val;
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_KC_128))) {
+ if (e->len != 16) {
+ LOG_HO(conn, LOGL_ERROR, "Invalid length in Kc128 IE: %u bytes (expected 16)\n", e->len);
+ return false;
+ }
+ memcpy(req->kc128, e->val, 16);
+ req->kc128_present = true;
+ }
if ((e = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_TYPE_1))) {
if (e->len != sizeof(req->classmark.classmark1)) {
@@ -430,6 +526,18 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
"Missing mandatory IE: 3GPP mandates either Classmark Information 1 or 2"
" in BSSMAP Handover Request, but neither are present. Will continue without.\n");
+ if ((e = TLVP_GET(tp, GSM0808_IE_CHOSEN_ENCR_ALG))) {
+ req->chosen_encr_alg = e->val[0];
+ if (req->chosen_encr_alg < 1 || req->chosen_encr_alg > 8)
+ LOG_HO(conn, LOGL_ERROR, "Chosen Encryption Algorithm (Serving) is invalid: %u\n",
+ req->chosen_encr_alg);
+ }
+
+ LOG_HO(conn, LOGL_DEBUG, "Handover Request encryption info: chosen=A5/%u key=%s kc128=%s\n",
+ (req->chosen_encr_alg ? : 1) - 1,
+ req->ei.key_len ? osmo_hexdump_nospc(req->ei.key, req->ei.key_len) : "none",
+ has_a54 ? osmo_hexdump_nospc(req->kc128, 16) : "none");
+
if (TLVP_PRESENT(tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
int rc;
unsigned int u;
@@ -496,21 +604,30 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
return false;
}
- /* A lot of IEs remain ignored... */
-
- return true;
-}
+ if ((e = TLVP_GET(tp, GSM0808_IE_OLD_BSS_TO_NEW_BSS_INFORMATION))) {
+ parse_old2new_bss_info(conn, e->val, e->len, req);
+ }
-static bool chan_mode_is_tch(enum gsm48_chan_mode mode)
-{
- switch (mode) {
- case GSM48_CMODE_SPEECH_V1:
- case GSM48_CMODE_SPEECH_EFR:
- case GSM48_CMODE_SPEECH_AMR:
- return true;
- default:
+ /* Decode "Codec List (MSC Preferred)". First set len = 0 to empty the list. (For inter-BSC incoming handover,
+ * there can't possibly be a list here already, because the conn has just now been created; just do ensure
+ * sanity.) */
+ conn->codec_list = (struct gsm0808_speech_codec_list){};
+ if ((e = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST))) {
+ if (gsm0808_dec_speech_codec_list(&conn->codec_list, e->val, e->len) < 0) {
+ LOG_HO(conn, LOGL_ERROR, "incoming inter-BSC Handover: HO Request:"
+ " Unable to decode Codec List (MSC Preferred)\n");
+ return false;
+ }
+ }
+ if (aoip && !conn->codec_list.len) {
+ LOG_HO(conn, LOGL_ERROR, "incoming inter-BSC Handover: HO Request:"
+ " Invalid or empty Codec List (MSC Preferred)\n");
return false;
}
+
+ /* A lot of IEs remain ignored... */
+
+ return true;
}
void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
@@ -521,10 +638,9 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
struct bsc_msc_data *msc = conn->sccp.msc;
struct handover_in_req *req = &ho->inter_bsc_in;
int match_idx;
- enum gsm48_chan_mode mode;
- bool full_rate = false;
- uint16_t s15_s0;
struct osmo_fsm_inst *fi;
+ struct channel_mode_and_rate ch_mode_rate = {};
+ int chosen_a5_n;
handover_fsm_alloc(conn);
@@ -540,12 +656,10 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
ho_fsm_update_id(fi, "interBSCin");
if (!parse_ho_request(conn, ho_request_msg, req)) {
- ho_fail(HO_RESULT_ERROR, "Invalid Handover Request message from MSC\n");
+ ho_fail(HO_RESULT_ERROR, "Invalid Handover Request message from MSC");
return;
}
- ho_count(BSC_CTR_INTER_BSC_HO_IN_ATTEMPTED);
-
/* Figure out which cell to handover to. */
for (match_idx = 0; ; match_idx++) {
struct gsm_bts *bts;
@@ -562,7 +676,7 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
bts->nr, req->cell_id_target_name);
/* Figure out channel type */
- if (match_codec_pref(&mode, &full_rate, &s15_s0, &req->ct, &req->scl, msc, bts)) {
+ if (match_codec_pref(&ch_mode_rate, &req->ct, &req->scl, msc, bts, RATE_PREF_NONE)) {
LOG_HO(conn, LOGL_DEBUG,
"BTS %u has no matching channel codec (%s, speech codec list len = %u)\n",
bts->nr, gsm0808_channel_type_name(&req->ct), req->scl.len);
@@ -570,10 +684,14 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
}
LOG_HO(conn, LOGL_DEBUG, "BTS %u: Found matching audio type: %s %s (for %s)\n",
- bts->nr, gsm48_chan_mode_name(mode), full_rate? "full-rate" : "half-rate",
+ bts->nr, gsm48_chan_mode_name(ch_mode_rate.chan_mode),
+ ch_mode_rate.chan_rate == CH_RATE_FULL ? "full-rate" : "half-rate",
gsm0808_channel_type_name(&req->ct));
- lchan = lchan_select_by_chan_mode(bts, mode, full_rate);
+ lchan = lchan_select_by_chan_mode(bts,
+ ch_mode_rate.chan_mode,
+ ch_mode_rate.chan_rate,
+ SELECT_FOR_HANDOVER, NULL);
if (!lchan) {
LOG_HO(conn, LOGL_DEBUG, "BTS %u has no matching free channels\n", bts->nr);
continue;
@@ -585,6 +703,9 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
break;
}
+ ho_count(ho->new_bts, CTR_HANDOVER_ATTEMPTED);
+ ho_count(ho->new_bts, CTR_INTER_BSC_HO_IN_ATTEMPTED);
+
if (!ho->new_bts) {
ho_fail(HO_RESULT_ERROR, "No local cell matches the target %s",
req->cell_id_target_name);
@@ -603,45 +724,109 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ACTIVE);
info = (struct lchan_activate_info){
- .activ_for = FOR_HANDOVER,
+ .activ_for = ACTIVATE_FOR_HANDOVER,
.for_conn = conn,
- .chan_mode = mode,
- .s15_s0 = s15_s0,
- .requires_voice_stream = chan_mode_is_tch(mode),
+ .ch_mode_rate = ch_mode_rate,
+ .ch_indctr = req->ct.ch_indctr,
.msc_assigned_cic = req->msc_assigned_cic,
};
+ /* Figure out the encryption algorithm */
+ chosen_a5_n = select_best_cipher(req->ei_as_bitmask, bsc_gsmnet->a5_encryption_mask);
+ if (chosen_a5_n < 0) {
+ ho_fail(HO_RESULT_FAIL_RR_HO_FAIL,
+ "There is no A5 encryption mode that both BSC and MSC permit: MSC 0x%x & BSC 0x%x = 0",
+ req->ei_as_bitmask, bsc_gsmnet->a5_encryption_mask);
+ return;
+ }
+ if (chosen_a5_n > 0 && !req->ei.key_len) {
+ /* There is no key. Is A5/0 permitted? */
+ if ((req->ei_as_bitmask & bsc_gsmnet->a5_encryption_mask & 0x1) == 0x1) {
+ chosen_a5_n = 0;
+ } else {
+ ho_fail(HO_RESULT_ERROR,
+ "Encryption is required, but there is no key (Encryption Information)");
+ return;
+ }
+ }
+
+ /* Put encryption info in the chan activation info */
+ info.encr.alg_a5_n = chosen_a5_n;
+ if (chosen_a5_n > 0) {
+ if (req->ei.key_len > sizeof(info.encr.key)) {
+ ho_fail(HO_RESULT_ERROR, "Encryption Information IE key length is too large: %u",
+ req->ei.key_len);
+ return;
+ }
+ memcpy(info.encr.key, req->ei.key, req->ei.key_len);
+ info.encr.key_len = req->ei.key_len;
+ }
+
+ if (req->kc128_present) {
+ memcpy(info.encr.kc128, req->kc128, 16);
+ info.encr.kc128_present = true;
+ }
+
+ if (req->last_eutran_plmn_valid) {
+ conn->fast_return.allowed = ho->new_bts->srvcc_fast_return_allowed;
+ conn->fast_return.last_eutran_plmn_valid = true;
+ memcpy(&conn->fast_return.last_eutran_plmn, &req->last_eutran_plmn,
+ sizeof(conn->fast_return.last_eutran_plmn));
+ ho_count(ho->new_bts, CTR_SRVCC_ATTEMPTED);
+ }
+
lchan_activate(ho->new_lchan, &info);
}
-#define FUNC_RESULT_COUNTER(name) \
-static int result_counter_##name(enum handover_result result) \
+/* Create functions result_counter_{BSC,BTS}_{HANDOVER,...}(), to evaluate the handover result and return
+ * BSC_CTR_HANDOVER_ATTEMPTED,
+ * BSC_CTR_HANDOVER_COMPLETED,
+ * BSC_CTR_HANDOVER_STOPPED,
+ * BSC_CTR_HANDOVER_NO_CHANNEL,
+ * BSC_CTR_HANDOVER_TIMEOUT,
+ * BSC_CTR_HANDOVER_FAILED,
+ * BSC_CTR_HANDOVER_ERROR,
+ * or
+ * BTS_CTR_HANDOVER_ATTEMPTED,
+ * BTS_CTR_HANDOVER_COMPLETED,
+ * BTS_CTR_HANDOVER_STOPPED,
+ * BTS_CTR_HANDOVER_NO_CHANNEL,
+ * BTS_CTR_HANDOVER_TIMEOUT,
+ * BTS_CTR_HANDOVER_FAILED,
+ * BTS_CTR_HANDOVER_ERROR,
+ */
+#define FUNC_RESULT_COUNTER(obj, name) \
+static int result_counter_##obj##_##name(enum handover_result result) \
{ \
switch (result) { \
case HO_RESULT_OK: \
- return BSC_CTR_##name##_COMPLETED; \
+ return obj##_CTR_##name##_COMPLETED; \
case HO_RESULT_FAIL_NO_CHANNEL: \
- return BSC_CTR_##name##_NO_CHANNEL; \
+ return obj##_CTR_##name##_NO_CHANNEL; \
case HO_RESULT_FAIL_RR_HO_FAIL: \
- return BSC_CTR_##name##_FAILED; \
+ return obj##_CTR_##name##_FAILED; \
case HO_RESULT_FAIL_TIMEOUT: \
- return BSC_CTR_##name##_TIMEOUT; \
+ return obj##_CTR_##name##_TIMEOUT; \
case HO_RESULT_CONN_RELEASE: \
- return BSC_CTR_##name##_STOPPED; \
+ return obj##_CTR_##name##_STOPPED; \
default: \
case HO_RESULT_ERROR: \
- return BSC_CTR_##name##_ERROR; \
+ return obj##_CTR_##name##_ERROR; \
} \
}
-FUNC_RESULT_COUNTER(ASSIGNMENT)
-FUNC_RESULT_COUNTER(HANDOVER)
-FUNC_RESULT_COUNTER(INTER_BSC_HO_IN)
+FUNC_RESULT_COUNTER(BSC, HANDOVER)
+FUNC_RESULT_COUNTER(BSC, INTRA_CELL_HO)
+FUNC_RESULT_COUNTER(BSC, INTRA_BSC_HO)
+FUNC_RESULT_COUNTER(BSC, INTER_BSC_HO_IN)
-static int result_counter_INTER_BSC_HO_OUT(enum handover_result result) {
+/* INTRA_BSC_HO_OUT does not have a NO_CHANNEL result, so can't do this with FUNC_RESULT_COUNTER() macro. */
+static int result_counter_BSC_INTER_BSC_HO_OUT(enum handover_result result) {
switch (result) {
case HO_RESULT_OK:
return BSC_CTR_INTER_BSC_HO_OUT_COMPLETED;
+ case HO_RESULT_FAIL_RR_HO_FAIL:
+ return BSC_CTR_INTER_BSC_HO_OUT_FAILED;
case HO_RESULT_FAIL_TIMEOUT:
return BSC_CTR_INTER_BSC_HO_OUT_TIMEOUT;
case HO_RESULT_CONN_RELEASE:
@@ -652,24 +837,64 @@ static int result_counter_INTER_BSC_HO_OUT(enum handover_result result) {
}
}
-static int result_counter(enum handover_scope scope, enum handover_result result)
+static int result_counter_bsc(enum handover_scope scope, enum handover_result result)
{
switch (scope) {
+ default:
+ return -1;
case HO_INTRA_CELL:
- return result_counter_ASSIGNMENT(result);
+ return result_counter_BSC_INTRA_CELL_HO(result);
+ case HO_INTRA_BSC:
+ return result_counter_BSC_INTRA_BSC_HO(result);
+ case HO_INTER_BSC_OUT:
+ return result_counter_BSC_INTER_BSC_HO_OUT(result);
+ case HO_INTER_BSC_IN:
+ return result_counter_BSC_INTER_BSC_HO_IN(result);
+ }
+}
+
+FUNC_RESULT_COUNTER(BTS, HANDOVER)
+FUNC_RESULT_COUNTER(BTS, INTRA_CELL_HO)
+FUNC_RESULT_COUNTER(BTS, INTRA_BSC_HO)
+FUNC_RESULT_COUNTER(BTS, INCOMING_INTRA_BSC_HO)
+FUNC_RESULT_COUNTER(BTS, INTER_BSC_HO_IN)
+
+/* INTRA_BSC_HO_OUT does not have a NO_CHANNEL result, so can't do this with FUNC_RESULT_COUNTER() macro. */
+static int result_counter_BTS_INTER_BSC_HO_OUT(enum handover_result result) {
+ switch (result) {
+ case HO_RESULT_OK:
+ return BTS_CTR_INTER_BSC_HO_OUT_COMPLETED;
+ case HO_RESULT_FAIL_RR_HO_FAIL:
+ return BTS_CTR_INTER_BSC_HO_OUT_FAILED;
+ case HO_RESULT_FAIL_TIMEOUT:
+ return BTS_CTR_INTER_BSC_HO_OUT_TIMEOUT;
+ case HO_RESULT_CONN_RELEASE:
+ return BTS_CTR_INTER_BSC_HO_OUT_STOPPED;
default:
- LOGP(DHO, LOGL_ERROR, "invalid enum handover_scope value: %s\n",
- handover_scope_name(scope));
- /* use "normal" HO_INTRA_BSC counter... */
+ case HO_RESULT_ERROR:
+ return BTS_CTR_INTER_BSC_HO_OUT_ERROR;
+ }
+}
+
+static int result_counter_bts(enum handover_scope scope, enum handover_result result)
+{
+ switch (scope) {
+ default:
+ return -1;
+ case HO_INTRA_CELL:
+ return result_counter_BTS_INTRA_CELL_HO(result);
case HO_INTRA_BSC:
- return result_counter_HANDOVER(result);
+ return result_counter_BTS_INTRA_BSC_HO(result);
case HO_INTER_BSC_OUT:
- return result_counter_INTER_BSC_HO_OUT(result);
+ return result_counter_BTS_INTER_BSC_HO_OUT(result);
case HO_INTER_BSC_IN:
- return result_counter_INTER_BSC_HO_IN(result);
+ return result_counter_BTS_INTER_BSC_HO_IN(result);
}
}
+FUNC_RESULT_COUNTER(BSC, SRVCC)
+FUNC_RESULT_COUNTER(BTS, SRVCC)
+
static void send_handover_performed(struct gsm_subscriber_connection *conn)
{
struct gsm_lchan *lchan = conn->lchan;
@@ -695,7 +920,7 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn)
};
/* Chosen Channel 3.2.2.33 */
- ho_perf_params.chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode);
+ ho_perf_params.chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_mode);
if (!ho_perf_params.chosen_channel) {
LOG_HO(conn, LOGL_ERROR, "Failed to generate Chosen Channel IE, can't send HANDOVER PERFORMED!\n");
return;
@@ -703,19 +928,20 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn)
ho_perf_params.chosen_channel_present = true;
/* Chosen Encryption Algorithm 3.2.2.44 */
- ho_perf_params.chosen_encr_alg = lchan->encr.alg_id;
+ ho_perf_params.chosen_encr_alg = ALG_A5_NR_TO_BSSAP(lchan->encr.alg_a5_n);
ho_perf_params.chosen_encr_alg_present = true;
- if (ho->new_lchan->activate.info.requires_voice_stream) {
+ if (bsc_chan_ind_requires_rtp_stream(ho->new_lchan->activate.info.ch_indctr)) {
/* Speech Version (chosen) 3.2.2.51 */
- ho_perf_params.speech_version_chosen = gsm0808_permitted_speech(lchan->type, lchan->tch_mode);
+ ho_perf_params.speech_version_chosen = gsm0808_permitted_speech(lchan->type,
+ lchan->current_ch_mode_rate.chan_mode);
ho_perf_params.speech_version_chosen_present = true;
/* Speech Codec (chosen) 3.2.2.104 */
if (gscon_is_aoip(conn)) {
/* Extrapolate speech codec from speech mode */
gsm0808_speech_codec_from_chan_type(&sc, ho_perf_params.speech_version_chosen);
- sc.cfg = conn->assignment.req.s15_s0;
+ sc.cfg = conn->lchan->current_ch_mode_rate.s15_s0;
memcpy(&ho_perf_params.speech_codec_chosen, &sc, sizeof(sc));
ho_perf_params.speech_codec_chosen_present = true;
}
@@ -727,6 +953,7 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn)
return;
}
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_PERFORMED));
rc = gscon_sigtran_send(conn, msg);
if (rc < 0) {
LOG_HO(conn, LOGL_ERROR, "message sending failed, can't send HANDOVER PERFORMED!\n");
@@ -739,6 +966,7 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
{
struct handover_decision_callbacks *hdc;
struct handover *ho = &conn->ho;
+ struct gsm_bts *bts = conn_get_bts(conn);
/* Sanity -- an error result ensures beyond doubt that we don't use the new lchan below
* when the handover isn't actually allowed to change this conn. */
@@ -763,7 +991,7 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
result = bsc_tx_bssmap_ho_complete(conn, ho->new_lchan);
}
/* Not 'else': above checks may still result in HO_RESULT_ERROR. */
- if (result == HO_RESULT_ERROR) {
+ if (result != HO_RESULT_OK) {
/* Return a BSSMAP Handover Failure, as described in 3GPP TS 48.008 3.1.5.2.2
* "Handover Resource Allocation Failure" */
bsc_tx_bssmap_ho_failure(conn);
@@ -789,7 +1017,8 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
/* 3GPP TS 48.008 3.1.5.3.3 "Abnormal Conditions": if neither MS reports
* HO Failure nor the MSC sends a Clear Command, we should release the
* dedicated radio resources and send a Clear Request to the MSC. */
- lchan_release(conn->lchan, true, true, GSM48_RR_CAUSE_ABNORMAL_TIMER);
+ lchan_release(conn->lchan, true, true, GSM48_RR_CAUSE_ABNORMAL_TIMER,
+ gscon_last_eutran_plmn(conn));
/* Once the channel release is through, the BSSMAP Clear will follow. */
break;
}
@@ -802,7 +1031,7 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
if (result == HO_RESULT_OK)
conn->ho.created_ci_for_msc = NULL;
- /* If the performed handover was an INTRA BSC HANDOVER, inform the MSC that a handover has happend */
+ /* If the performed handover was an INTRA BSC HANDOVER, inform the MSC that a handover has happened */
if (result == HO_RESULT_OK && ((ho->scope & HO_INTRA_CELL) || (ho->scope & HO_INTRA_BSC)))
send_handover_performed(conn);
@@ -810,13 +1039,42 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
if (hdc && hdc->on_handover_end)
hdc->on_handover_end(conn, result);
- ho_count(result_counter(ho->scope, result));
+ /* HO_INTER_BSC_IN has the source BTS on a remote BSS, so count all of those on the target BTS; also count
+ * errors onto the HO target BTS if no lchan was obtained. */
+ if (ho->scope & HO_INTER_BSC_IN)
+ bts = ho->new_bts;
+
+ ho_count_bsc(result_counter_BSC_HANDOVER(result));
+ ho_count_bsc(result_counter_bsc(ho->scope, result));
+ ho_count_bts(bts, result_counter_BTS_HANDOVER(result));
+ ho_count_bts(bts, result_counter_bts(ho->scope, result));
+ /* For inter-cell HO, also increment the "INCOMING" counters on the target BTS. */
+ if (ho->scope & HO_INTRA_BSC)
+ ho_count_bts(ho->new_bts, result_counter_BTS_INCOMING_INTRA_BSC_HO(result));
+ if (ho->scope & HO_INTER_BSC_IN && conn->fast_return.last_eutran_plmn_valid) {
+ /* From outside local BSC and with Last EUTRAN PLMN Id => SRVCC */
+ ho_count_bsc(result_counter_BSC_SRVCC(result));
+ ho_count_bts(bts, result_counter_BTS_SRVCC(result));
+ }
LOG_HO(conn, LOGL_INFO, "Result: %s\n", handover_result_name(result));
if (ho->new_lchan && result == HO_RESULT_OK) {
+ struct gsm_bts *bts;
+
gscon_change_primary_lchan(conn, conn->ho.new_lchan);
ho->new_lchan = NULL;
+
+ bts = conn_get_bts(conn);
+ if (is_siemens_bts(bts) && ts_is_tch(conn->lchan->ts)) {
+ /* HACK: store the actual Classmark 2 LV from the subscriber and use it here! */
+ uint8_t cm2_lv[] = { 0x02, 0x00, 0x00 };
+ send_siemens_mrpci(conn->lchan, cm2_lv);
+ }
+
+ /* If a Perform Location Request (LCS) is busy, inform the SMLC that there is a new lchan */
+ if (conn->lcs.loc_req)
+ osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_HANDOVER_PERFORMED, NULL);
}
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_END, &result);
@@ -824,7 +1082,7 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
/* Detach the new_lchan last, so we can still see it in above logging */
if (ho->new_lchan) {
/* Release new lchan, it didn't work out */
- lchan_release(ho->new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(ho->new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
ho->new_lchan = NULL;
}
@@ -847,10 +1105,24 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
static void ho_fsm_wait_lchan_active(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ struct handover *ho = &conn->ho;
switch (event) {
case HO_EV_LCHAN_ACTIVE:
- ho_fsm_state_chg(HO_ST_WAIT_RR_HO_DETECT);
+ /* - If the lchan is voiceless, no need to even think about the MGW.
+ * - If this is an intra-BSC Handover, we already have an RTP stream towards the MSC and aren't
+ * touching it.
+ * - If we're on SCCPlite, the MSC manages the MGW endpoint, all we do is the BTS side CI, so we can
+ * skip the part that would CRCX towards the MSC.
+ * So create an MSC side endpoint CI only if a voice lchan is established for an incoming inter-BSC
+ * handover on AoIP. Otherwise go on to send a Handover Command and wait for the Detect.
+ */
+ if (bsc_chan_ind_requires_rtp_stream(ho->new_lchan->activate.info.ch_indctr)
+ && (ho->scope & HO_INTER_BSC_IN)
+ && gscon_is_aoip(conn))
+ ho_fsm_state_chg(HO_ST_WAIT_MGW_ENDPOINT_TO_MSC);
+ else
+ ho_fsm_state_chg(HO_ST_WAIT_RR_HO_DETECT);
return;
case HO_EV_LCHAN_ERROR:
@@ -863,6 +1135,75 @@ static void ho_fsm_wait_lchan_active(struct osmo_fsm_inst *fi, uint32_t event, v
}
}
+/* Only for voice, only for inter-BSC Handover into this BSC, and only for AoIP:
+ *
+ * Establish the MGW endpoint CI that points towards the MSC. This needs to happen after the lchan (lchan_rtp_fsm) has
+ * created an MGW endpoint with the first CRCX, so that an endpoint is available, and before sending the Handover
+ * Request Acknowledge, so that the RTP address and port established towards the MSC can be included in the Handover
+ * Request Acknowledge message.
+ * (For SCCPlite, the MSC manages the CN side endpoint CI itself, and we don't need to send any RTP address in the
+ * Handover Request Acknowledge.)
+ *
+ * Actually, it should be possible to kick this off even above in handover_start_inter_bsc_in(), to do the CRCX towards
+ * the MSC at the same time as establishing the lchan. The gscon_ensure_mgw_endpoint() doesn't care which one of
+ * lchan_rtp_fsm or handover_start_inter_bsc_in() calls it first. The benefit would be that we'd send out the Handover
+ * Command ever so slightly sooner -- which isn't critical really, because a) how long does a CRCX take, milliseconds?
+ * and b) the time critical part is *after* the Handover Command was kicked off to keep the transition between cells as
+ * short as possible. The drawback of doing this earlier is code complexity: receiving the HO_EV_MSC_MGW_OK /
+ * HO_EV_MSC_MGW_FAIL events would need to be juggled in between the HO_EV_LCHAN_ACTIVE / HO_EV_LCHAN_ERROR. So the
+ * decision for now is to leave it here.
+ */
+static void ho_fsm_wait_mgw_endpoint_to_msc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ struct handover *ho = &conn->ho;
+
+ if (!gscon_connect_mgw_to_msc(conn,
+ ho->new_lchan,
+ ho->inter_bsc_in.msc_assigned_rtp_addr,
+ ho->inter_bsc_in.msc_assigned_rtp_port,
+ fi,
+ HO_EV_MSC_MGW_OK,
+ HO_EV_MSC_MGW_FAIL,
+ NULL,
+ &ho->created_ci_for_msc)) {
+ ho_fail(HO_RESULT_ERROR,
+ "Unable to connect MGW endpoint to the MSC side");
+ }
+}
+
+static void ho_fsm_wait_mgw_endpoint_to_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
+ const struct mgcp_conn_peer *mgw_info;
+
+ switch (event) {
+
+ case HO_EV_MSC_MGW_OK:
+ /* Ensure the endpoint is really there, and log it. This state is only entered for AoIP connections, see
+ * ho_fsm_wait_lchan_active() above. */
+ mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc);
+ if (!mgw_info) {
+ ho_fail(HO_RESULT_ERROR,
+ "Unable to retrieve RTP port info allocated by MGW for"
+ " the MSC side.");
+ return;
+ }
+ LOG_HO(conn, LOGL_DEBUG, "MGW's MSC side CI: %s:%u\n",
+ mgw_info->addr, mgw_info->port);
+ ho_fsm_state_chg(HO_ST_WAIT_RR_HO_DETECT);
+ return;
+
+ case HO_EV_MSC_MGW_FAIL:
+ ho_fail(HO_RESULT_ERROR,
+ "Unable to connect MGW endpoint to the MSC side");
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
static void ho_fsm_wait_rr_ho_detect_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
int rc;
@@ -870,6 +1211,7 @@ static void ho_fsm_wait_rr_ho_detect_onenter(struct osmo_fsm_inst *fi, uint32_t
struct handover *ho = &conn->ho;
struct msgb *rr_ho_cmd = gsm48_make_ho_cmd(ho->new_lchan,
+ ho->scope, ho->async,
ho->new_lchan->ms_power,
ho->ho_ref);
if (!rr_ho_cmd) {
@@ -980,88 +1322,24 @@ static void ho_fsm_wait_rr_ho_complete(struct osmo_fsm_inst *fi, uint32_t event,
}
}
-static void ho_fsm_post_lchan_established(struct osmo_fsm_inst *fi);
-
static void ho_fsm_wait_lchan_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
if (conn->ho.fi && lchan_state_is(conn->ho.new_lchan, LCHAN_ST_ESTABLISHED)) {
LOG_HO(conn, LOGL_DEBUG, "lchan already established earlier\n");
- ho_fsm_post_lchan_established(fi);
+ ho_success();
}
}
static void ho_fsm_wait_lchan_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
- switch (event) {
-
- case HO_EV_LCHAN_ESTABLISHED:
- ho_fsm_post_lchan_established(fi);
- break;
-
- default:
- OSMO_ASSERT(false);
- }
-}
-
-static void ho_fsm_post_lchan_established(struct osmo_fsm_inst *fi)
-{
struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
- struct handover *ho = &conn->ho;
-
- if (ho->new_lchan->activate.info.requires_voice_stream
- && (ho->scope & HO_INTER_BSC_IN))
- ho_fsm_state_chg(HO_ST_WAIT_MGW_ENDPOINT_TO_MSC);
- else
- ho_success();
-}
-
-static void ho_fsm_wait_mgw_endpoint_to_msc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
- struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
- struct handover *ho = &conn->ho;
-
- if (!gscon_connect_mgw_to_msc(conn,
- ho->new_lchan,
- ho->inter_bsc_in.msc_assigned_rtp_addr,
- ho->inter_bsc_in.msc_assigned_rtp_port,
- fi,
- HO_EV_MSC_MGW_OK,
- HO_EV_MSC_MGW_FAIL,
- NULL,
- &ho->created_ci_for_msc)) {
- ho_fail(HO_RESULT_ERROR,
- "Unable to connect MGW endpoint to the MSC side");
- }
-}
-static void ho_fsm_wait_mgw_endpoint_to_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
switch (event) {
-
- case HO_EV_MSC_MGW_OK:
- /* For AoIP, we created the MGW endpoint. Ensure it is really there, and log it. */
- if (gscon_is_aoip(conn)) {
- const struct mgcp_conn_peer *mgw_info;
- mgw_info = mgwep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc);
- if (!mgw_info) {
- ho_fail(HO_RESULT_ERROR,
- "Unable to retrieve RTP port info allocated by MGW for"
- " the MSC side.");
- return;
- }
- LOG_HO(conn, LOGL_DEBUG, "MGW's MSC side CI: %s:%u\n",
- mgw_info->addr, mgw_info->port);
- }
+ case HO_EV_LCHAN_ESTABLISHED:
ho_success();
- return;
-
- case HO_EV_MSC_MGW_FAIL:
- ho_fail(HO_RESULT_ERROR,
- "Unable to connect MGW endpoint to the MSC side");
- return;
+ break;
default:
OSMO_ASSERT(false);
@@ -1076,10 +1354,11 @@ static void handover_start_inter_bsc_out(struct gsm_subscriber_connection *conn,
int rc;
struct handover *ho = &conn->ho;
struct osmo_fsm_inst *fi = conn->ho.fi;
+ struct gsm_bts *bts = conn_get_bts(conn);
ho->scope = HO_INTER_BSC_OUT;
ho_fsm_update_id(fi, "interBSCout");
- ho_count(BSC_CTR_INTER_BSC_HO_OUT_ATTEMPTED);
+ ho_count(bts, CTR_INTER_BSC_HO_OUT_ATTEMPTED);
rc = bsc_tx_bssmap_ho_required(conn->lchan, target_cells);
if (rc) {
@@ -1128,6 +1407,8 @@ static void ho_out_fsm_wait_clear(struct osmo_fsm_inst *fi, uint32_t event, void
{
struct gsm_subscriber_connection *conn = ho_fi_conn(fi);
switch (event) {
+ /* See also ho_fsm_allstate_action() for ho_success() on HO_EV_CONN_RELEASING */
+
case HO_EV_RR_HO_FAIL:
ho_fail(HO_RESULT_FAIL_RR_HO_FAIL, "Received RR Handover Failure message");
return;
@@ -1156,6 +1437,19 @@ static const struct osmo_fsm_state ho_fsm_states[] = {
,
.out_state_mask = 0
| S(HO_ST_WAIT_LCHAN_ACTIVE)
+ | S(HO_ST_WAIT_MGW_ENDPOINT_TO_MSC)
+ | S(HO_ST_WAIT_RR_HO_DETECT)
+ ,
+ },
+ [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = {
+ .name = "WAIT_MGW_ENDPOINT_TO_MSC",
+ .onenter = ho_fsm_wait_mgw_endpoint_to_msc_onenter,
+ .action = ho_fsm_wait_mgw_endpoint_to_msc,
+ .in_event_mask = 0
+ | S(HO_EV_MSC_MGW_OK)
+ | S(HO_EV_MSC_MGW_FAIL)
+ ,
+ .out_state_mask = 0
| S(HO_ST_WAIT_RR_HO_DETECT)
,
},
@@ -1193,20 +1487,7 @@ static const struct osmo_fsm_state ho_fsm_states[] = {
.in_event_mask = 0
| S(HO_EV_LCHAN_ESTABLISHED)
,
- .out_state_mask = 0
- | S(HO_ST_WAIT_MGW_ENDPOINT_TO_MSC)
- ,
},
- [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = {
- .name = "WAIT_MGW_ENDPOINT_TO_MSC",
- .onenter = ho_fsm_wait_mgw_endpoint_to_msc_onenter,
- .action = ho_fsm_wait_mgw_endpoint_to_msc,
- .in_event_mask = 0
- | S(HO_EV_MSC_MGW_OK)
- | S(HO_EV_MSC_MGW_FAIL)
- ,
- },
-
[HO_OUT_ST_WAIT_HO_COMMAND] = {
.name = "inter-BSC-OUT:WAIT_HO_COMMAND",
.action = ho_out_fsm_wait_ho_command,
diff --git a/src/osmo-bsc/handover_logic.c b/src/osmo-bsc/handover_logic.c
index 5725213ef..1e1b6c319 100644
--- a/src/osmo-bsc/handover_logic.c
+++ b/src/osmo-bsc/handover_logic.c
@@ -43,6 +43,7 @@
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/neighbor_ident.h>
#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/gsm/gsm0808.h>
#include <osmocom/gsm/gsm0808_utils.h>
@@ -111,7 +112,7 @@ int bts_handover_count(struct gsm_bts *bts, int ho_scopes)
if (!nm_is_running(&ts->mo.nm_state))
continue;
- ts_for_each_lchan(lchan, ts) {
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
if (!lchan->conn)
continue;
if (!lchan->conn->ho.fi)
@@ -125,46 +126,176 @@ int bts_handover_count(struct gsm_bts *bts, int ho_scopes)
return count;
}
-struct gsm_bts *bts_by_neighbor_ident(const struct gsm_network *net,
- const struct neighbor_ident_key *search_for)
+/* Find out a handover target cell for the given arfcn_bsic,
+ * and make sure there are no ambiguous matches.
+ * Given a source BTS and a target ARFCN+BSIC, find which cell is the right handover target.
+ * ARFCN+BSIC may be re-used within and/or across BSS, so make sure that only those cells that are explicitly
+ * listed as neighbor of the source cell are viable handover targets.
+ * The (legacy) default configuration is that, when no explicit neighbors are listed, that all local cells are
+ * neighbors, in which case each ARFCN+BSIC must exist at most once.
+ * If there is more than one viable handover target cell found for the given ARFCN+BSIC, that constitutes a
+ * configuration error and should not result in handover, so that the system's misconfiguration is more likely
+ * to be found.
+ */
+int find_handover_target_cell(struct gsm_bts **local_target_cell_p,
+ struct gsm0808_cell_id_list2 *remote_target_cells,
+ struct gsm_subscriber_connection *conn,
+ const struct cell_ab *search_for,
+ bool log_errors)
{
- struct gsm_bts *found = NULL;
- struct gsm_bts *bts;
- struct gsm_bts *wildcard_match = NULL;
-
- llist_for_each_entry(bts, &net->bts_list, list) {
- struct neighbor_ident_key entry = {
- .from_bts = NEIGHBOR_IDENT_KEY_ANY_BTS,
- .arfcn = bts->c0->arfcn,
- .bsic = bts->bsic,
- };
- if (neighbor_ident_key_match(&entry, search_for, true)) {
- if (found) {
- LOGP(DHO, LOGL_ERROR, "CONFIG ERROR: Multiple BTS match %s: %d and %d\n",
- neighbor_ident_key_name(search_for),
- found->nr, bts->nr);
- return found;
+ struct gsm_network *net = conn->network;
+ struct gsm_bts *local_target_cell = NULL;
+ bool ho_active;
+ bool as_active;
+ struct gsm_bts *from_bts = conn->lchan->ts->trx->bts;
+ *remote_target_cells = (struct gsm0808_cell_id_list2){};
+
+ if (local_target_cell_p)
+ *local_target_cell_p = NULL;
+
+ if (!search_for) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR, "Handover without target cell\n");
+ return -EINVAL;
+ }
+
+ if (!from_bts) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR, "Handover without source cell\n");
+ return -EINVAL;
+ }
+
+ ho_active = ho_get_ho_active(from_bts->ho);
+ as_active = (ho_get_algorithm(from_bts->ho) == 2)
+ && ho_get_hodec2_as_active(from_bts->ho);
+ if (!ho_active && !as_active) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR, "Cannot start Handover: Handover and Assignment disabled for this source cell (%s)\n",
+ cell_ab_to_str_c(OTC_SELECT, search_for));
+ return -EINVAL;
+ }
+
+ if (llist_empty(&from_bts->neighbors)) {
+ /* No explicit neighbor entries exist for this BTS. Hence apply the legacy default behavior that all
+ * local cells are neighbors. */
+ struct gsm_bts *bts;
+ int i;
+
+ LOG_HO(conn, LOGL_DEBUG, "No explicit neighbors, regarding all local cells as neighbors\n");
+
+ /* For i == 0, look for an exact 1:1 match of all ident_key fields.
+ * For i == 1, interpret wildcard values, when no exact match exists. */
+ for (i = 0; i < 2; i++) {
+ bool exact_match = !i;
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ struct cell_ab bts_ab;
+ bts_cell_ab(&bts_ab, bts);
+ if (cell_ab_match(&bts_ab, search_for, exact_match)) {
+ if (local_target_cell) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR,
+ "NEIGHBOR CONFIGURATION ERROR: Multiple local cells match %s"
+ " (BTS %d and BTS %d)."
+ " Aborting Handover because of ambiguous network topology.\n",
+ cell_ab_to_str_c(OTC_SELECT, search_for),
+ local_target_cell->nr, bts->nr);
+ return -EINVAL;
+ }
+ local_target_cell = bts;
+ }
}
- found = bts;
+ if (local_target_cell)
+ break;
+ }
+
+ if (!local_target_cell) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR, "Cannot Handover, no cell matches %s\n",
+ cell_ab_to_str_c(OTC_SELECT, search_for));
+ return -EINVAL;
+ }
+
+ if (local_target_cell == from_bts && !as_active) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR,
+ "Cannot start re-assignment, Assignment disabled for this cell (%s)\n",
+ cell_ab_to_str_c(OTC_SELECT, search_for));
+ return -EINVAL;
+ }
+ if (local_target_cell != from_bts && !ho_active) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR,
+ "Cannot start Handover, Handover disabled for this cell (%s)\n",
+ cell_ab_to_str_c(OTC_SELECT, search_for));
+ return -EINVAL;
}
- if (neighbor_ident_key_match(&entry, search_for, false))
- wildcard_match = bts;
+
+ if (local_target_cell_p)
+ *local_target_cell_p = local_target_cell;
+ return 0;
}
- if (found)
- return found;
+ /* One or more local- or remote-BSS cell neighbors are configured. Find a match among those, but also detect
+ * ambiguous matches (if multiple cells match, it is a configuration error). */
- return wildcard_match;
-}
+ LOG_HO(conn, LOGL_DEBUG, "There are explicit neighbors configured for this cell\n");
-struct neighbor_ident_key *bts_ident_key(const struct gsm_bts *bts)
-{
- static struct neighbor_ident_key key;
- key = (struct neighbor_ident_key){
- .arfcn = bts->c0->arfcn,
- .bsic = bts->bsic,
- };
- return &key;
+ if (resolve_neighbors(&local_target_cell, remote_target_cells, from_bts, search_for, log_errors)) {
+ LOG_HO(conn, LOGL_ERROR, "Cannot handover BTS %u -> %s: neighbor unknown\n",
+ from_bts->nr, cell_ab_to_str_c(OTC_SELECT, search_for));
+ return -ENOENT;
+ }
+
+ /* We have found possibly a local_target_cell (when != NULL), and / or remote_target_cells (when .id_list_len >
+ * 0). Figure out what to do with them. */
+
+ if (remote_target_cells->id_list_len)
+ LOG_HO(conn, LOGL_DEBUG, "Found remote target cell(s) %s\n",
+ gsm0808_cell_id_list_name_c(OTC_SELECT, remote_target_cells));
+
+ if (local_target_cell && remote_target_cells->id_list_len) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR, "NEIGHBOR CONFIGURATION ERROR: Both a local and a remote-BSS cell"
+ " match BTS %u -> %s (BTS %d and remote %s)."
+ " Aborting Handover because of ambiguous network topology.\n",
+ from_bts->nr, cell_ab_to_str_c(OTC_SELECT, search_for), local_target_cell->bts_nr,
+ gsm0808_cell_id_list_name_c(OTC_SELECT, remote_target_cells));
+ return -EINVAL;
+ }
+
+ if (local_target_cell == from_bts && !as_active) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR,
+ "Cannot start re-assignment, Assignment disabled for this cell (BTS %u)\n",
+ from_bts->nr);
+ return -EINVAL;
+ }
+
+ if (((local_target_cell && local_target_cell != from_bts)
+ || remote_target_cells->id_list_len)
+ && !ho_active) {
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR,
+ "Cannot start Handover, Handover disabled for this cell (BTS %u -> %s)\n",
+ from_bts->bts_nr, cell_ab_to_str_c(OTC_SELECT, search_for));
+ return -EINVAL;
+ }
+
+ /* Return the result. After above checks, only one of local or remote cell has been found. */
+ if (local_target_cell) {
+ if (local_target_cell_p)
+ *local_target_cell_p = local_target_cell;
+ return 0;
+ }
+
+ if (remote_target_cells->id_list_len)
+ return 0;
+
+ if (log_errors)
+ LOG_HO(conn, LOGL_ERROR, "Cannot handover %s: neighbor unknown\n",
+ cell_ab_to_str_c(OTC_SELECT, search_for));
+
+ return -ENODEV;
}
static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
diff --git a/src/osmo-bsc/handover_vty.c b/src/osmo-bsc/handover_vty.c
index 79795115e..afc12d9d1 100644
--- a/src/osmo-bsc/handover_vty.c
+++ b/src/osmo-bsc/handover_vty.c
@@ -27,6 +27,7 @@
#include <osmocom/bsc/vty.h>
#include <osmocom/bsc/handover_cfg.h>
#include <osmocom/bsc/handover_decision_2.h>
+#include <osmocom/bsc/bts.h>
static struct handover_cfg *ho_cfg_from_vty(struct vty *vty)
{
@@ -46,10 +47,10 @@ static struct handover_cfg *ho_cfg_from_vty(struct vty *vty)
VTY_CMD_PREFIX, 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_PREFIX VTY_CMD " (" VTY_CMD_ARG "|default)", \
- VTY_DOC \
- "Use default (" #DEFAULT_VAL "), remove explicit setting on this node\n") \
+DEFUN_ATTR(cfg_ho_##NAME, cfg_ho_##NAME##_cmd, \
+ VTY_CMD_PREFIX VTY_CMD " (" VTY_CMD_ARG "|default)", \
+ VTY_DOC \
+ "Use default (" #DEFAULT_VAL "), remove explicit setting on this node\n", CMD_ATTR_IMMEDIATE) \
{ \
struct handover_cfg *ho = ho_cfg_from_vty(vty); \
const char *val = argv[0]; \
@@ -103,14 +104,16 @@ static inline const char *congestion_check_interval2a(int val)
return str;
}
-DEFUN(cfg_net_ho_congestion_check_interval, cfg_net_ho_congestion_check_interval_cmd,
- "handover2 congestion-check (disabled|<1-999>|now)",
- HO_CFG_STR_HANDOVER2
- "Configure congestion check interval\n"
- "Disable congestion checking, do not handover based on cell overload\n"
- "Congestion check interval in seconds (default "
- OSMO_STRINGIFY_VAL(HO_CFG_CONGESTION_CHECK_DEFAULT) ")\n"
- "Manually trigger a congestion check to run right now\n")
+DEFUN_ATTR(cfg_net_ho_congestion_check_interval, cfg_net_ho_congestion_check_interval_cmd,
+ "handover2 congestion-check (disabled|<1-999>|now)",
+ HO_CFG_STR_HANDOVER2
+ "Configure congestion check interval\n"
+ "Disable congestion checking, do not handover based on cell load. Note: there is one global congestion check"
+ " interval, i.e. contrary to other handover2 settings, this is not configurable per individual cell.\n"
+ "Congestion check interval in seconds (default "
+ OSMO_STRINGIFY_VAL(HO_CFG_CONGESTION_CHECK_DEFAULT) ")\n"
+ "Manually trigger a congestion check to run right now\n",
+ CMD_ATTR_IMMEDIATE)
{
if (!strcmp(argv[0], "now")) {
hodec2_congestion_check(gsmnet_from_vty(vty));
@@ -167,11 +170,10 @@ HODEC1_CFG_ALL_MEMBERS
#undef HO_CFG_ONE_MEMBER
}
-void ho_vty_init()
+void ho_vty_init(void)
{
ho_vty_init_cmds(GSMNET_NODE);
install_element(GSMNET_NODE, &cfg_net_ho_congestion_check_interval_cmd);
ho_vty_init_cmds(BTS_NODE);
}
-
diff --git a/src/osmo-bsc/lb.c b/src/osmo-bsc/lb.c
new file mode 100644
index 000000000..511a54528
--- /dev/null
+++ b/src/osmo-bsc/lb.c
@@ -0,0 +1,827 @@
+/* Lb interface low level SCCP handling */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.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/lb.h>
+
+#include <osmocom/gsm/bssmap_le.h>
+#include <osmocom/sigtran/sccp_helpers.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/lcs_loc_req.h>
+#include <osmocom/bsc/bssmap_reset.h>
+#include <osmocom/bsc/gsm_data.h>
+
+/* Send reset to SMLC */
+int bssmap_le_tx_reset(void)
+{
+ struct osmo_ss7_instance *ss7;
+ struct msgb *msg;
+ struct bssap_le_pdu reset = {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_RESET,
+ .reset = GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ },
+ };
+
+ if (!bsc_gsmnet->smlc->sccp_user) {
+ LOGP(DRESET, LOGL_DEBUG, "Not sending RESET to SMLC, Lb link down\n");
+ return -1;
+ }
+
+ ss7 = osmo_ss7_instance_find(bsc_gsmnet->smlc->cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGP(DRESET, LOGL_INFO, "Sending RESET to SMLC: %s\n", osmo_sccp_addr_name(ss7, &bsc_gsmnet->smlc->smlc_addr));
+ msg = osmo_bssap_le_enc(&reset);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_UDT_RESET));
+ return osmo_sccp_tx_unitdata_msg(bsc_gsmnet->smlc->sccp_user, &bsc_gsmnet->smlc->bsc_addr,
+ &bsc_gsmnet->smlc->smlc_addr, msg);
+}
+
+/* Send reset-ack to SMLC */
+int bssmap_le_tx_reset_ack(void)
+{
+ struct osmo_ss7_instance *ss7;
+ struct msgb *msg;
+ struct bssap_le_pdu reset_ack = {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_RESET_ACK,
+ },
+ };
+
+ ss7 = osmo_ss7_instance_find(bsc_gsmnet->smlc->cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGP(DRESET, LOGL_NOTICE, "Sending RESET ACK to SMLC: %s\n", osmo_sccp_addr_name(ss7, &bsc_gsmnet->smlc->smlc_addr));
+ msg = osmo_bssap_le_enc(&reset_ack);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_UDT_RESET_ACK));
+ return osmo_sccp_tx_unitdata_msg(bsc_gsmnet->smlc->sccp_user, &bsc_gsmnet->smlc->bsc_addr,
+ &bsc_gsmnet->smlc->smlc_addr, msg);
+}
+
+static int handle_unitdata_from_smlc(const struct osmo_sccp_addr *smlc_addr, struct msgb *msg,
+ const struct osmo_sccp_user *scu)
+{
+ struct osmo_ss7_instance *ss7;
+ struct bssap_le_pdu bssap_le;
+ struct osmo_bssap_le_err *err = NULL;
+ struct rate_ctr_group *ctrg = bsc_gsmnet->smlc->ctrs;
+
+ ss7 = osmo_sccp_get_ss7(osmo_sccp_get_sccp(scu));
+ OSMO_ASSERT(ss7);
+
+ if (osmo_sccp_addr_cmp(smlc_addr, &bsc_gsmnet->smlc->smlc_addr, OSMO_SCCP_ADDR_T_MASK)) {
+ LOGP(DLCS, LOGL_ERROR, "Rx BSSMAP-LE UnitData from unknown remote address: %s\n",
+ osmo_sccp_addr_name(ss7, smlc_addr));
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UNKNOWN_PEER));
+ return -EINVAL;
+ }
+
+ if (osmo_bssap_le_dec(&bssap_le, &err, msg, msg)) {
+ LOGP(DLCS, LOGL_ERROR, "Rx BSSAP-LE UnitData with error: %s\n", err->logmsg);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG));
+ return -EINVAL;
+ }
+
+ if (bssap_le.discr != BSSAP_LE_MSG_DISCR_BSSMAP_LE) {
+ LOGP(DLCS, LOGL_ERROR, "Rx BSSAP-LE: discr %d not implemented\n", bssap_le.discr);
+ return -ENOTSUP;
+ }
+
+ switch (bssap_le.bssmap_le.msg_type) {
+ case BSSMAP_LE_MSGT_RESET:
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_RESET));
+ LOGP(DLCS, LOGL_NOTICE, "RESET from SMLC: %s\n", osmo_sccp_addr_name(ss7, smlc_addr));
+ return osmo_fsm_inst_dispatch(bsc_gsmnet->smlc->bssmap_reset->fi, BSSMAP_RESET_EV_RX_RESET, NULL);
+
+ case BSSMAP_LE_MSGT_RESET_ACK:
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_RESET_ACK));
+ LOGP(DLCS, LOGL_NOTICE, "RESET-ACK from SMLC: %s\n", osmo_sccp_addr_name(ss7, smlc_addr));
+ return osmo_fsm_inst_dispatch(bsc_gsmnet->smlc->bssmap_reset->fi, BSSMAP_RESET_EV_RX_RESET_ACK, NULL);
+
+ default:
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG));
+ LOGP(DLCS, LOGL_ERROR, "Rx unimplemented UDT message type %s\n",
+ osmo_bssap_le_pdu_to_str_c(OTC_SELECT, &bssap_le));
+ return -EINVAL;
+ }
+}
+
+static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
+{
+ struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *)oph;
+ struct osmo_sccp_user *scu = _scu;
+ struct osmo_sccp_instance *sccp = osmo_sccp_get_sccp(scu);
+ struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(sccp);
+ struct gsm_subscriber_connection *conn;
+ int rc = 0;
+
+ switch (OSMO_PRIM_HDR(&scu_prim->oph)) {
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_UNITDATA, PRIM_OP_INDICATION):
+ /* Handle inbound UnitData */
+ DEBUGP(DLCS, "N-UNITDATA.ind(%s)\n", osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+ rc = handle_unitdata_from_smlc(&scu_prim->u.unitdata.calling_addr, oph->msg, scu);
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_INDICATION):
+ /* Handle inbound connections. A Location Request is always started on the A interface, and OsmoBSC
+ * forwards this to the SMLC by performing an N-CONNECT from BSC -> SMLC. This is the reverse
+ * direction: N-CONNECT from SMLC -> BSC, which should never happen. */
+ LOGP(DLCS, LOGL_ERROR, "N-CONNECT.ind(X->%u): inbound connect from SMLC is not expected to happen\n",
+ scu_prim->u.connect.conn_id);
+ rc = osmo_sccp_tx_disconn(scu, scu_prim->u.connect.conn_id, &scu_prim->u.connect.called_addr, 0);
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_CONNECT, PRIM_OP_CONFIRM):
+ /* Handle inbound confirmation of outbound connection */
+ DEBUGP(DLCS, "N-CONNECT.cnf(%u)\n", scu_prim->u.connect.conn_id);
+ conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.connect.conn_id);
+ if (conn) {
+ conn->lcs.lb.state = SUBSCR_SCCP_ST_CONNECTED;
+ if (msgb_l2len(oph->msg) > 0) {
+ rc = lcs_loc_req_rx_bssmap_le(conn, oph->msg);
+ }
+ } else {
+ LOGP(DLCS, LOGL_ERROR, "N-CONNECT.cfm(%u) for unknown conn\n", scu_prim->u.connect.conn_id);
+ rc = -EINVAL;
+ }
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_DATA, PRIM_OP_INDICATION):
+ /* Handle incoming connection oriented data */
+ DEBUGP(DLCS, "N-DATA.ind(%u)\n", scu_prim->u.data.conn_id);
+
+ conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.data.conn_id);
+ if (!conn) {
+ LOGP(DLCS, LOGL_ERROR, "N-DATA.ind(%u) for unknown conn_id\n", scu_prim->u.data.conn_id);
+ rc = -EINVAL;
+ } else if (conn->lcs.lb.state != SUBSCR_SCCP_ST_CONNECTED) {
+ LOGP(DLCS, LOGL_ERROR, "N-DATA.ind(%u) for conn that is not confirmed\n",
+ scu_prim->u.data.conn_id);
+ rc = -EINVAL;
+ } else {
+ rc = lcs_loc_req_rx_bssmap_le(conn, oph->msg);
+ }
+ break;
+
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_DISCONNECT, PRIM_OP_INDICATION):
+ DEBUGP(DLCS, "N-DISCONNECT.ind(%u, %s, cause=%i)\n", scu_prim->u.disconnect.conn_id,
+ osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)),
+ scu_prim->u.disconnect.cause);
+ /* indication of disconnect */
+ conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.disconnect.conn_id);
+ if (!conn) {
+ LOGP(DLCS, LOGL_ERROR, "N-DISCONNECT.ind for unknown conn_id %u\n",
+ scu_prim->u.disconnect.conn_id);
+ rc = -EINVAL;
+ } else {
+ bsc_sccp_inst_unregister_gscon(bsc_sccp, &conn->lcs.lb.conn);
+ conn->lcs.lb.conn.conn_id = SCCP_CONN_ID_UNSET;
+ conn->lcs.lb.state = SUBSCR_SCCP_ST_NONE;
+ if (msgb_l2len(oph->msg) > 0) {
+ rc = lcs_loc_req_rx_bssmap_le(conn, oph->msg);
+ }
+ }
+ break;
+
+ default:
+ LOGP(DLCS, LOGL_ERROR, "Unhandled SIGTRAN primitive %s.%s\n",
+ osmo_scu_prim_type_name(oph->primitive),
+ get_value_string(osmo_prim_op_names, oph->operation));
+ break;
+ }
+
+ msgb_free(oph->msg);
+ return rc;
+}
+
+static int lb_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct osmo_ss7_instance *ss7;
+ uint32_t conn_id;
+ int rc;
+ struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(bsc_gsmnet->smlc->sccp);
+
+ OSMO_ASSERT(conn);
+ OSMO_ASSERT(msg);
+
+ if (conn->lcs.lb.state != SUBSCR_SCCP_ST_NONE) {
+ LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR,
+ "Cannot open BSSMAP-LE conn to SMLC, another conn is still active for this subscriber\n");
+ return -EINVAL;
+ }
+
+ conn_id = bsc_sccp_inst_next_conn_id(bsc_sccp);
+ if (conn_id == SCCP_CONN_ID_UNSET) {
+ LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR, "Unable to allocate SCCP Connection ID for BSSMAP-LE to SMLC\n");
+ return -ENOSPC;
+ }
+ conn->lcs.lb.conn.conn_id = conn_id;
+ if (bsc_sccp_inst_register_gscon(bsc_sccp, &conn->lcs.lb.conn) < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to register Lb SCCP connection (id=%u)\n", conn->lcs.lb.conn.conn_id);
+ return -1;
+ }
+ ss7 = osmo_ss7_instance_find(bsc_gsmnet->smlc->cs7_instance);
+ OSMO_ASSERT(ss7);
+ LOGPFSMSL(conn->fi, DLCS, LOGL_INFO, "Opening new SCCP connection (id=%u) to SMLC: %s\n", conn_id,
+ osmo_sccp_addr_name(ss7, &bsc_gsmnet->smlc->smlc_addr));
+
+ rc = osmo_sccp_tx_conn_req_msg(bsc_gsmnet->smlc->sccp_user, conn_id, &bsc_gsmnet->smlc->bsc_addr,
+ &bsc_gsmnet->smlc->smlc_addr, msg);
+ if (rc >= 0) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_SUCCESS));
+ conn->lcs.lb.state = SUBSCR_SCCP_ST_WAIT_CONN_CONF;
+ } else {
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_ERR_SEND));
+ goto failed_unregister_conn_id;
+ }
+
+ return rc;
+
+failed_unregister_conn_id:
+ bsc_sccp_inst_unregister_gscon(bsc_sccp, &conn->lcs.lb.conn);
+ conn->lcs.lb.conn.conn_id = SCCP_CONN_ID_UNSET;
+ return rc;
+}
+
+void lb_close_conn(struct gsm_subscriber_connection *conn)
+{
+ struct bsc_sccp_inst *bsc_sccp;
+ if (conn->lcs.lb.state == SUBSCR_SCCP_ST_NONE)
+ return;
+ OSMO_ASSERT(conn->lcs.lb.conn.conn_id != SCCP_CONN_ID_UNSET);
+
+ osmo_sccp_tx_disconn(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn.conn_id, &bsc_gsmnet->smlc->bsc_addr, 0);
+
+ conn->lcs.lb.state = SUBSCR_SCCP_ST_NONE;
+ bsc_sccp = osmo_sccp_get_priv(bsc_gsmnet->smlc->sccp);
+ bsc_sccp_inst_unregister_gscon(bsc_sccp, &conn->lcs.lb.conn);
+ conn->lcs.lb.conn.conn_id = SCCP_CONN_ID_UNSET;
+}
+
+/* Send data to SMLC, take ownership of *msg */
+int lb_send(struct gsm_subscriber_connection *conn, const struct bssap_le_pdu *bssap_le)
+{
+ int rc;
+ struct msgb *msg;
+
+ OSMO_ASSERT(conn);
+
+ if (!bssmap_reset_is_conn_ready(bsc_gsmnet->smlc->bssmap_reset)) {
+ LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR, "Lb link to SMLC is not ready (no RESET-ACK), cannot send %s\n",
+ osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
+ /* If the remote side was lost, make sure that the SCCP conn is discarded in the local state and towards
+ * the STP. */
+ lb_close_conn(conn);
+ return -EINVAL;
+ }
+
+ msg = osmo_bssap_le_enc(bssap_le);
+ if (!msg) {
+ LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR, "Failed to encode %s\n",
+ osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
+ return -EINVAL;
+ }
+
+ if (conn->lcs.lb.state == SUBSCR_SCCP_ST_NONE) {
+ rc = lb_open_conn(conn, msg);
+ goto count_tx;
+ }
+
+ LOGPFSMSL(conn->fi, DLCS, LOGL_DEBUG, "Tx %s\n", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
+ rc = osmo_sccp_tx_data_msg(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn.conn_id, msg);
+ if (rc >= 0)
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_SUCCESS));
+ else
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_ERR_SEND));
+
+count_tx:
+ if (rc < 0)
+ return rc;
+
+ switch (bssap_le->bssmap_le.msg_type) {
+ case BSSMAP_LE_MSGT_PERFORM_LOC_REQ:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_REQUEST));
+ break;
+ case BSSMAP_LE_MSGT_PERFORM_LOC_ABORT:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_ABORT));
+ break;
+ case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO:
+ switch (bssap_le->bssmap_le.conn_oriented_info.apdu.msg_type) {
+ case BSSLAP_MSGT_TA_RESPONSE:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_RESPONSE));
+ break;
+ case BSSLAP_MSGT_REJECT:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_REJECT));
+ break;
+ case BSSLAP_MSGT_RESET:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_RESET));
+ break;
+ case BSSLAP_MSGT_ABORT:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_ABORT));
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* Default point-code to be used as local address (BSC) */
+#define BSC_DEFAULT_PC "0.23.3"
+
+/* Default point-code to be used as remote address (SMLC) */
+#define SMLC_DEFAULT_PC "0.23.6"
+
+#define DEFAULT_ASP_LOCAL_IP "localhost"
+#define DEFAULT_ASP_REMOTE_IP "localhost"
+
+void lb_cancel_all(void)
+{
+ struct gsm_subscriber_connection *conn;
+ llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry)
+ lcs_loc_req_reset(conn);
+};
+
+void lb_reset_link_up(void *data)
+{
+ LOGP(DLCS, LOGL_INFO, "Lb link ready\n");
+}
+
+void lb_reset_link_lost(void *data)
+{
+ struct gsm_subscriber_connection *conn;
+ LOGP(DLCS, LOGL_INFO, "Lb link down\n");
+
+ /* Abort all ongoing Location Requests */
+ llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry)
+ lcs_loc_req_reset(conn);
+};
+
+void lb_reset_tx_reset(void *data)
+{
+ bssmap_le_tx_reset();
+}
+
+void lb_reset_tx_reset_ack(void *data)
+{
+ bssmap_le_tx_reset_ack();
+}
+
+static void lb_start_reset_fsm(void)
+{
+ struct bssmap_reset_cfg cfg = {
+ .conn_cfm_failure_threshold = 3,
+ .ops = {
+ .tx_reset = lb_reset_tx_reset,
+ .tx_reset_ack = lb_reset_tx_reset_ack,
+ .link_up = lb_reset_link_up,
+ .link_lost = lb_reset_link_lost,
+ },
+ };
+
+ if (bsc_gsmnet->smlc->bssmap_reset) {
+ LOGP(DLCS, LOGL_ERROR, "will not allocate a second reset FSM for Lb\n");
+ return;
+ }
+
+ bsc_gsmnet->smlc->bssmap_reset = bssmap_reset_alloc(bsc_gsmnet, "Lb", &cfg);
+}
+
+static void lb_stop_reset_fsm(void)
+{
+ bssmap_reset_term_and_free(bsc_gsmnet->smlc->bssmap_reset);
+ bsc_gsmnet->smlc->bssmap_reset = NULL;
+}
+
+static int lb_start(void)
+{
+ uint32_t default_pc;
+ struct osmo_ss7_instance *cs7_inst = NULL;
+ struct osmo_sccp_instance *sccp;
+ enum osmo_ss7_asp_protocol used_proto = OSMO_SS7_ASP_PROT_M3UA;
+ char inst_name[32];
+ const char *smlc_name = "smlc";
+ struct bsc_sccp_inst *bsc_sccp;
+
+ /* Already set up? */
+ if (bsc_gsmnet->smlc->sccp_user)
+ return -EALREADY;
+
+ LOGP(DLCS, LOGL_INFO, "Starting Lb link\n");
+
+ if (!bsc_gsmnet->smlc->cs7_instance_valid) {
+ bsc_gsmnet->smlc->cs7_instance = 0;
+ }
+ cs7_inst = osmo_ss7_instance_find_or_create(tall_bsc_ctx, bsc_gsmnet->smlc->cs7_instance);
+ OSMO_ASSERT(cs7_inst);
+
+ /* If unset, use default SCCP address for the SMLC */
+ if (!bsc_gsmnet->smlc->smlc_addr.presence)
+ osmo_sccp_make_addr_pc_ssn(&bsc_gsmnet->smlc->smlc_addr,
+ osmo_ss7_pointcode_parse(NULL, SMLC_DEFAULT_PC),
+ OSMO_SCCP_SSN_SMLC_BSSAP_LE);
+
+ /* Set up SCCP user and one ASP+AS */
+ snprintf(inst_name, sizeof(inst_name), "Lb-%u-%s", cs7_inst->cfg.id, osmo_ss7_asp_protocol_name(used_proto));
+ LOGP(DLCS, LOGL_NOTICE, "Initializing SCCP connection for Lb/%s on cs7 instance %u\n",
+ osmo_ss7_asp_protocol_name(used_proto), cs7_inst->cfg.id);
+
+ /* SS7 Protocol stack */
+ default_pc = osmo_ss7_pointcode_parse(NULL, BSC_DEFAULT_PC);
+ sccp = osmo_sccp_simple_client_on_ss7_id(tall_bsc_ctx, cs7_inst->cfg.id, inst_name,
+ default_pc, used_proto,
+ 0, DEFAULT_ASP_LOCAL_IP,
+ 0, DEFAULT_ASP_REMOTE_IP);
+ if (!sccp)
+ return -EINVAL;
+ bsc_gsmnet->smlc->sccp = sccp;
+ bsc_sccp = bsc_sccp_inst_alloc(tall_bsc_ctx);
+ bsc_sccp->sccp = sccp;
+ osmo_sccp_set_priv(sccp, bsc_sccp);
+
+ /* If unset, use default local SCCP address */
+ if (!bsc_gsmnet->smlc->bsc_addr.presence)
+ osmo_sccp_local_addr_by_instance(&bsc_gsmnet->smlc->bsc_addr, sccp,
+ OSMO_SCCP_SSN_BSC_BSSAP_LE);
+
+ if (!osmo_sccp_check_addr(&bsc_gsmnet->smlc->bsc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
+ LOGP(DLCS, LOGL_ERROR,
+ "%s %s: invalid local (BSC) SCCP address: %s\n",
+ inst_name, smlc_name, osmo_sccp_inst_addr_name(sccp, &bsc_gsmnet->smlc->bsc_addr));
+ return -EINVAL;
+ }
+
+ if (!osmo_sccp_check_addr(&bsc_gsmnet->smlc->smlc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
+ LOGP(DLCS, LOGL_ERROR,
+ "%s %s: invalid remote (SMLC) SCCP address: %s\n",
+ inst_name, smlc_name, osmo_sccp_inst_addr_name(sccp, &bsc_gsmnet->smlc->smlc_addr));
+ return -EINVAL;
+ }
+
+ LOGP(DLCS, LOGL_NOTICE, "Lb: %s %s: local (BSC) SCCP address: %s\n",
+ inst_name, smlc_name, osmo_sccp_inst_addr_name(sccp, &bsc_gsmnet->smlc->bsc_addr));
+ LOGP(DLCS, LOGL_NOTICE, "Lb: %s %s: remote (SMLC) SCCP address: %s\n",
+ inst_name, smlc_name, osmo_sccp_inst_addr_name(sccp, &bsc_gsmnet->smlc->smlc_addr));
+
+ bsc_gsmnet->smlc->sccp_user = osmo_sccp_user_bind(sccp, smlc_name, sccp_sap_up, bsc_gsmnet->smlc->bsc_addr.ssn);
+ if (!bsc_gsmnet->smlc->sccp_user)
+ return -EINVAL;
+
+ lb_start_reset_fsm();
+ return 0;
+}
+
+static int lb_stop(void)
+{
+ /* Not set up? */
+ if (!bsc_gsmnet->smlc->sccp_user)
+ return -EALREADY;
+
+ LOGP(DLCS, LOGL_INFO, "Shutting down Lb link\n");
+
+ lb_cancel_all();
+ lb_stop_reset_fsm();
+ osmo_sccp_user_unbind(bsc_gsmnet->smlc->sccp_user);
+ bsc_gsmnet->smlc->sccp_user = NULL;
+ return 0;
+}
+
+int lb_start_or_stop(void)
+{
+ int rc;
+ if (bsc_gsmnet->smlc->enable) {
+ rc = lb_start();
+ switch (rc) {
+ case 0:
+ /* all is fine */
+ break;
+ case -EALREADY:
+ /* no need to log about anything */
+ break;
+ default:
+ LOGP(DLCS, LOGL_ERROR, "Failed to start Lb interface (rc=%d)\n", rc);
+ break;
+ }
+ } else {
+ rc = lb_stop();
+ switch (rc) {
+ case 0:
+ /* all is fine */
+ break;
+ case -EALREADY:
+ /* no need to log about anything */
+ break;
+ default:
+ LOGP(DLCS, LOGL_ERROR, "Failed to stop Lb interface (rc=%d)\n", rc);
+ break;
+ }
+ }
+ return rc;
+}
+
+static void smlc_vty_init(void);
+
+int lb_init(void)
+{
+ OSMO_ASSERT(!bsc_gsmnet->smlc);
+ bsc_gsmnet->smlc = talloc_zero(bsc_gsmnet, struct smlc_config);
+ OSMO_ASSERT(bsc_gsmnet->smlc);
+ bsc_gsmnet->smlc->ctrs = rate_ctr_group_alloc(bsc_gsmnet, &smlc_ctrg_desc, 0);
+
+ smlc_vty_init();
+ return 0;
+}
+
+/*********************************************************************************
+ * VTY Interface (Configuration + Introspection)
+ *********************************************************************************/
+
+DEFUN(cfg_smlc, cfg_smlc_cmd,
+ "smlc", "Configure Lb Link to Serving Mobile Location Centre\n")
+{
+ vty->node = SMLC_NODE;
+ return CMD_SUCCESS;
+}
+
+static struct cmd_node smlc_node = {
+ SMLC_NODE,
+ "%s(config-smlc)# ",
+ 1,
+};
+
+DEFUN(cfg_smlc_enable, cfg_smlc_enable_cmd,
+ "enable",
+ "Start up Lb interface connection to the remote SMLC\n")
+{
+ bsc_gsmnet->smlc->enable = true;
+ if (vty->type != VTY_FILE) {
+ if (lb_start_or_stop())
+ vty_out(vty, "%% Error: failed to enable Lb interface%s", VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smlc_no_enable, cfg_smlc_no_enable_cmd,
+ "no enable",
+ NO_STR "Stop Lb interface connection to the remote SMLC\n")
+{
+ bsc_gsmnet->smlc->enable = false;
+ if (vty->type != VTY_FILE) {
+ if (lb_start_or_stop())
+ vty_out(vty, "%% Error: failed to disable Lb interface%s", VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+static void enforce_ssn(struct vty *vty, struct osmo_sccp_addr *addr, enum osmo_sccp_ssn want_ssn)
+{
+ if (addr->presence & OSMO_SCCP_ADDR_T_SSN) {
+ if (addr->ssn != want_ssn)
+ vty_out(vty,
+ "setting an SSN (%u) different from the standard (%u) is not allowed, will use standard SSN for address: %s%s",
+ addr->ssn, want_ssn, osmo_sccp_addr_dump(addr), VTY_NEWLINE);
+ }
+
+ addr->presence |= OSMO_SCCP_ADDR_T_SSN;
+ addr->ssn = want_ssn;
+}
+
+/* Prevent mixing addresses from different CS7 instances */
+bool smlc_set_cs7_instance(struct vty *vty, const char *from_vty_cmd, const char *from_addr,
+ struct osmo_ss7_instance *ss7)
+{
+ if (bsc_gsmnet->smlc->cs7_instance_valid) {
+ if (bsc_gsmnet->smlc->cs7_instance != ss7->cfg.id) {
+ LOGP(DLCS, LOGL_ERROR,
+ "%s: expecting address from cs7 instance %u, but '%s' is from %u\n",
+ from_vty_cmd, bsc_gsmnet->smlc->cs7_instance, from_addr, ss7->cfg.id);
+ vty_out(vty, "Error:"
+ " %s: expecting address from cs7 instance %u, but '%s' is from %u%s",
+ from_vty_cmd, bsc_gsmnet->smlc->cs7_instance, from_addr, ss7->cfg.id, VTY_NEWLINE);
+ return false;
+ }
+ } else {
+ bsc_gsmnet->smlc->cs7_instance = ss7->cfg.id;
+ bsc_gsmnet->smlc->cs7_instance_valid = true;
+ LOGP(DLCS, LOGL_NOTICE, "Lb interface is using cs7 instance %u\n", bsc_gsmnet->smlc->cs7_instance);
+ }
+ return true;
+}
+
+DEFUN(cfg_smlc_cs7_bsc_addr,
+ cfg_smlc_cs7_bsc_addr_cmd,
+ "bsc-addr NAME",
+ "Local SCCP address of this BSC towards the SMLC\n" "Name of cs7 addressbook entry\n")
+{
+ const char *bsc_addr_name = argv[0];
+ struct osmo_ss7_instance *ss7;
+
+ ss7 = osmo_sccp_addr_by_name(&bsc_gsmnet->smlc->bsc_addr, bsc_addr_name);
+ if (!ss7) {
+ vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", bsc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+
+ if (!smlc_set_cs7_instance(vty, "smlc / bsc-addr", bsc_addr_name, ss7))
+ return CMD_WARNING;
+
+ enforce_ssn(vty, &bsc_gsmnet->smlc->bsc_addr, OSMO_SCCP_SSN_BSC_BSSAP_LE);
+ bsc_gsmnet->smlc->bsc_addr_name = talloc_strdup(bsc_gsmnet, bsc_addr_name);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smlc_cs7_smlc_addr,
+ cfg_smlc_cs7_smlc_addr_cmd,
+ "smlc-addr NAME",
+ "Remote SCCP address of the SMLC\n" "Name of cs7 addressbook entry\n")
+{
+ const char *smlc_addr_name = argv[0];
+ struct osmo_ss7_instance *ss7;
+
+ ss7 = osmo_sccp_addr_by_name(&bsc_gsmnet->smlc->smlc_addr, smlc_addr_name);
+ if (!ss7) {
+ vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", smlc_addr_name, VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+
+ if (!smlc_set_cs7_instance(vty, "smlc / smlc-addr", smlc_addr_name, ss7))
+ return CMD_WARNING;
+
+ enforce_ssn(vty, &bsc_gsmnet->smlc->smlc_addr, OSMO_SCCP_SSN_SMLC_BSSAP_LE);
+ bsc_gsmnet->smlc->smlc_addr_name = talloc_strdup(bsc_gsmnet, smlc_addr_name);
+ return CMD_SUCCESS;
+}
+
+static int config_write_smlc(struct vty *vty)
+{
+ /* Nothing to write? */
+ if (!(bsc_gsmnet->smlc->enable
+ || bsc_gsmnet->smlc->bsc_addr_name
+ || bsc_gsmnet->smlc->smlc_addr_name))
+ return 0;
+
+ vty_out(vty, "smlc%s", VTY_NEWLINE);
+
+ if (bsc_gsmnet->smlc->enable)
+ vty_out(vty, " enable%s", VTY_NEWLINE);
+
+ if (bsc_gsmnet->smlc->bsc_addr_name) {
+ vty_out(vty, " bsc-addr %s%s",
+ bsc_gsmnet->smlc->bsc_addr_name, VTY_NEWLINE);
+ }
+ if (bsc_gsmnet->smlc->smlc_addr_name) {
+ vty_out(vty, " smlc-addr %s%s",
+ bsc_gsmnet->smlc->smlc_addr_name, VTY_NEWLINE);
+ }
+
+ return 0;
+}
+
+DEFUN(show_smlc, show_smlc_cmd,
+ "show smlc",
+ SHOW_STR "Display state of SMLC / Lb\n")
+{
+ vty_out(vty, "not implemented%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+void smlc_vty_init(void)
+{
+ install_element_ve(&show_smlc_cmd);
+
+ install_element(CONFIG_NODE, &cfg_smlc_cmd);
+ install_node(&smlc_node, config_write_smlc);
+ install_element(SMLC_NODE, &cfg_smlc_enable_cmd);
+ install_element(SMLC_NODE, &cfg_smlc_no_enable_cmd);
+ install_element(SMLC_NODE, &cfg_smlc_cs7_bsc_addr_cmd);
+ install_element(SMLC_NODE, &cfg_smlc_cs7_smlc_addr_cmd);
+}
+
+const struct rate_ctr_desc smlc_ctr_description[] = {
+ [SMLC_CTR_BSSMAP_LE_RX_UNKNOWN_PEER] = {
+ "bssmap_le:rx:unknown_peer",
+ "Number of received BSSMAP-LE messages from an unknown Calling SCCP address"
+ },
+ [SMLC_CTR_BSSMAP_LE_RX_UDT_RESET] = {
+ "bssmap_le:rx:udt:reset:request",
+ "Number of received BSSMAP-LE UDT RESET messages"
+ },
+ [SMLC_CTR_BSSMAP_LE_RX_UDT_RESET_ACK] = {
+ "bssmap_le:rx:udt:reset:ack",
+ "Number of received BSSMAP-LE UDT RESET ACKNOWLEDGE messages"
+ },
+ [SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG] = {
+ "bssmap_le:rx:udt:err:inval",
+ "Number of received invalid BSSMAP-LE UDT messages"
+ },
+ [SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG] = {
+ "bssmap_le:rx:dt1:err:inval",
+ "Number of received invalid BSSMAP-LE"
+ },
+ [SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS] = {
+ "bssmap_le:rx:dt1:location:response_success",
+ "Number of received BSSMAP-LE Perform Location Response messages containing a location estimate"
+ },
+ [SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE] = {
+ "bssmap_le:rx:dt1:location:response_failure",
+ "Number of received BSSMAP-LE Perform Location Response messages containing a failure cause"
+ },
+
+ [SMLC_CTR_BSSMAP_LE_TX_ERR_INVALID_MSG] = {
+ "bssmap_le:tx:err:inval",
+ "Number of outgoing BSSMAP-LE messages that are invalid (a bug?)"
+ },
+ [SMLC_CTR_BSSMAP_LE_TX_ERR_CONN_NOT_READY] = {
+ "bssmap_le:tx:err:conn_not_ready",
+ "Number of BSSMAP-LE messages we tried to send when the connection was not ready yet"
+ },
+ [SMLC_CTR_BSSMAP_LE_TX_ERR_SEND] = {
+ "bssmap_le:tx:err:send",
+ "Number of socket errors while sending BSSMAP-LE messages"
+ },
+ [SMLC_CTR_BSSMAP_LE_TX_SUCCESS] = {
+ "bssmap_le:tx:success",
+ "Number of successfully sent BSSMAP-LE messages"
+ },
+
+ [SMLC_CTR_BSSMAP_LE_TX_UDT_RESET] = {
+ "bssmap_le:tx:udt:reset:request",
+ "Number of transmitted BSSMAP-LE UDT RESET messages"
+ },
+ [SMLC_CTR_BSSMAP_LE_TX_UDT_RESET_ACK] = {
+ "bssmap_le:tx:udt:reset:ack",
+ "Number of transmitted BSSMAP-LE UDT RESET ACK messages"
+ },
+ [SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_REQUEST] = {
+ "bssmap_le:tx:dt1:location:response",
+ "Number of transmitted BSSMAP-LE DT1 Perform Location Request messages"
+ },
+ [SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_ABORT] = {
+ "bssmap_le:rx:dt1:location:abort",
+ "Number of received BSSMAP-LE Perform Location Abort messages"
+ },
+
+ [SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_REQUEST] = {
+ "bssmap_le:rx:dt1:bsslap:ta_request",
+ "Number of received BSSMAP-LE Connection Oriented Information messages"
+ " with BSSLAP APDU containing TA Request"
+ },
+
+ [SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_TA_RESPONSE] = {
+ "bssmap_le:tx:dt1:bsslap:ta_response",
+ "Number of sent BSSMAP-LE Connection Oriented Information messages"
+ " with BSSLAP APDU containing TA Response"
+ },
+ [SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_REJECT] = {
+ "bssmap_le:tx:dt1:bsslap:reject",
+ "Number of sent BSSMAP-LE Connection Oriented Information messages"
+ " with BSSLAP APDU containing Reject"
+ },
+ [SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_RESET] = {
+ "bssmap_le:tx:dt1:bsslap:reset",
+ "Number of sent BSSMAP-LE Connection Oriented Information messages"
+ " with BSSLAP APDU containing Reset"
+ },
+ [SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_ABORT] = {
+ "bssmap_le:tx:dt1:bsslap:abort",
+ "Number of sent BSSMAP-LE Connection Oriented Information messages"
+ " with BSSLAP APDU containing Abort"
+ },
+
+};
+
+const struct rate_ctr_group_desc smlc_ctrg_desc = {
+ "smlc",
+ "serving mobile location centre",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(smlc_ctr_description),
+ smlc_ctr_description,
+};
diff --git a/src/osmo-bsc/lchan.c b/src/osmo-bsc/lchan.c
new file mode 100644
index 000000000..45d8d96f5
--- /dev/null
+++ b/src/osmo-bsc/lchan.c
@@ -0,0 +1,150 @@
+/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * (C) 2008-2018 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <stdint.h>
+#include <stdbool.h>
+#include <inttypes.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/bsc/lchan.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bts_trx.h>
+#include <osmocom/bsc/abis_rsl.h>
+
+void lchan_init(struct gsm_lchan *lchan, struct gsm_bts_trx_ts *ts, unsigned int nr)
+{
+ lchan->ts = ts;
+ lchan->nr = nr;
+ lchan->type = GSM_LCHAN_NONE;
+
+ lchan_update_name(lchan);
+}
+
+void lchan_update_name(struct gsm_lchan *lchan)
+{
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+ if (lchan->name)
+ talloc_free(lchan->name);
+ lchan->name = talloc_asprintf(ts->trx, "(bts=%d,trx=%d,ts=%d,ss=%s%d)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ lchan->vamos.is_secondary ? "shadow" : "",
+ lchan->nr - (lchan->vamos.is_secondary ? ts->max_primary_lchans : 0));
+}
+
+/* If the lchan is currently active, return the duration since activation in milliseconds.
+ * Otherwise return 0. */
+uint64_t gsm_lchan_active_duration_ms(const struct gsm_lchan *lchan)
+{
+ struct timespec now, elapsed;
+
+ if (lchan->active_start.tv_sec == 0 && lchan->active_start.tv_nsec == 0)
+ return 0;
+
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+ timespecsub(&now, &lchan->active_start, &elapsed);
+
+ return elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000;
+}
+
+/* For a VAMOS secondary shadow lchan, return its primary lchan. If the lchan is not a secondary lchan, return NULL. */
+struct gsm_lchan *gsm_lchan_vamos_to_primary(const struct gsm_lchan *lchan_vamos)
+{
+ struct gsm_lchan *lchan_primary;
+ if (!lchan_vamos || !lchan_vamos->vamos.is_secondary)
+ return NULL;
+ /* OsmoBSC currently does not support mixed TCH/F + TCH/H VAMOS multiplexes. Hence the primary <-> secondary
+ * relation is a simple index shift in the lchan array. If mixed multiplexes were allowed, a TCH/F primary might
+ * have two TCH/H VAMOS secondary lchans, etc. Fortunately, we don't need to care about that. */
+ lchan_primary = (struct gsm_lchan *)lchan_vamos - lchan_vamos->ts->max_primary_lchans;
+ if (!lchan_primary->fi)
+ return NULL;
+ return lchan_primary;
+}
+
+/* For a primary lchan, return its VAMOS secondary shadow lchan. If the lchan is not a primary lchan, return NULL. */
+struct gsm_lchan *gsm_lchan_primary_to_vamos(const struct gsm_lchan *lchan_primary)
+{
+ struct gsm_lchan *lchan_vamos;
+ if (!lchan_primary || lchan_primary->vamos.is_secondary)
+ return NULL;
+ /* OsmoBSC currently does not support mixed TCH/F + TCH/H VAMOS multiplexes. Hence the primary <-> secondary
+ * relation is a simple index shift in the lchan array. If mixed multiplexes were allowed, a TCH/F primary might
+ * have two TCH/H VAMOS secondary lchans, etc. Fortunately, we don't need to care about that. */
+ lchan_vamos = (struct gsm_lchan *)lchan_primary + lchan_primary->ts->max_primary_lchans;
+ if (!lchan_vamos->fi)
+ return NULL;
+ return lchan_vamos;
+}
+
+void lchan_update_ms_power_ctrl_level(struct gsm_lchan *lchan, int ms_power_dbm)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ struct gsm_subscriber_connection *conn = lchan->conn;
+ int max_pwr_dbm_pwclass, new_pwr;
+ bool send_pwr_ctrl_msg = false;
+
+ LOG_LCHAN(lchan, LOGL_DEBUG,
+ "MS Power level update requested: %d dBm\n", ms_power_dbm);
+
+ if (!conn)
+ goto ms_power_default;
+
+ if (conn->ms_power_class == 0)
+ goto ms_power_default;
+
+ if ((max_pwr_dbm_pwclass = (int)ms_class_gmsk_dbm(bts->band, conn->ms_power_class)) < 0) {
+ LOG_LCHAN(lchan, LOGL_INFO,
+ "Failed getting max ms power for power class %" PRIu8
+ " on band %s, providing default max ms power\n",
+ conn->ms_power_class, gsm_band_name(bts->band));
+ goto ms_power_default;
+ }
+
+ /* Current configured max pwr is above maximum one allowed on
+ current band + ms power class, so use that one. */
+ if (ms_power_dbm > max_pwr_dbm_pwclass)
+ ms_power_dbm = max_pwr_dbm_pwclass;
+
+ms_power_default:
+ if ((new_pwr = ms_pwr_ctl_lvl(bts->band, ms_power_dbm)) < 0) {
+ LOG_LCHAN(lchan, LOGL_INFO,
+ "Failed getting max ms power level %d on band %s,"
+ " providing default max ms power\n",
+ ms_power_dbm, gsm_band_name(bts->band));
+ return;
+ }
+
+ LOG_LCHAN(lchan, LOGL_DEBUG,
+ "MS Power level update (power class %" PRIu8 "): %" PRIu8 " -> %d\n",
+ conn ? conn->ms_power_class : 0, lchan->ms_power, new_pwr);
+
+ /* If chan was already activated and max ms_power changes (due to power
+ classmark received), send an MS Power Control message */
+ if (lchan->activate.activ_ack && new_pwr != lchan->ms_power)
+ send_pwr_ctrl_msg = true;
+
+ lchan->ms_power = new_pwr;
+
+ if (send_pwr_ctrl_msg)
+ rsl_chan_ms_power_ctrl(lchan);
+}
diff --git a/src/osmo-bsc/lchan_fsm.c b/src/osmo-bsc/lchan_fsm.c
index 9181b322c..55875f0b4 100644
--- a/src/osmo-bsc/lchan_fsm.c
+++ b/src/osmo-bsc/lchan_fsm.c
@@ -22,22 +22,28 @@
#include <osmocom/gsm/rsl.h>
#include <osmocom/core/byteswap.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_rtp_fsm.h>
#include <osmocom/bsc/timeslot_fsm.h>
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/handover.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/bsc_rll.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/gsm_08_08.h>
#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/codec_pref.h>
-
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bsc_stats.h>
+#include <osmocom/bsc/lchan.h>
+#include <osmocom/bsc/vgcs_fsm.h>
static struct osmo_fsm lchan_fsm;
@@ -57,36 +63,78 @@ bool lchan_may_receive_data(struct gsm_lchan *lchan)
switch (lchan->fi->state) {
case LCHAN_ST_WAIT_RLL_RTP_ESTABLISH:
case LCHAN_ST_ESTABLISHED:
+ case LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK:
return true;
default:
return false;
}
}
-void lchan_set_last_error(struct gsm_lchan *lchan, const char *fmt, ...)
+bool lchan_is_asci(struct gsm_lchan *lchan)
{
- va_list ap;
- /* This dance allows using an existing error reason in above fmt */
- char *last_error_was = lchan->last_error;
- lchan->last_error = NULL;
+ if (lchan->activate.info.type_for == LCHAN_TYPE_FOR_VGCS ||
+ lchan->activate.info.type_for == LCHAN_TYPE_FOR_VBS)
+ return true;
+ return false;
+}
- if (fmt) {
- va_start(ap, fmt);
- lchan->last_error = talloc_vasprintf(lchan->ts->trx, fmt, ap);
- va_end(ap);
+static void lchan_on_mode_modify_success(struct gsm_lchan *lchan)
+{
+ lchan->modify.concluded = true;
+
+ switch (lchan->modify.info.modify_for) {
- LOG_LCHAN(lchan, LOGL_ERROR, "%s\n", lchan->last_error);
+ case MODIFY_FOR_ASSIGNMENT:
+ osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_MODIFIED, lchan);
+ break;
+
+ default:
+ break;
}
+}
+
+#define lchan_on_mode_modify_failure(lchan, modify_for, for_conn) \
+ _lchan_on_mode_modify_failure(lchan, modify_for, for_conn, \
+ __FILE__, __LINE__)
+static void _lchan_on_mode_modify_failure(struct gsm_lchan *lchan, enum lchan_modify_for modify_for,
+ struct gsm_subscriber_connection *for_conn,
+ const char *file, int line)
+{
+ if (lchan->modify.concluded)
+ return;
+ lchan->modify.concluded = true;
+
+ switch (modify_for) {
+
+ case MODIFY_FOR_ASSIGNMENT:
+ LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling Assignment FSM of error (%s)\n",
+ lchan->last_error ? : "unknown error");
+ if (!for_conn) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "lchan Channel Mode Modify failed, "
+ "but modify request has no conn\n");
+ break;
+ }
+ _osmo_fsm_inst_dispatch(for_conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR, lchan,
+ file, line);
+ return;
- if (last_error_was)
- talloc_free(last_error_was);
+ case MODIFY_FOR_VTY:
+ LOG_LCHAN(lchan, LOGL_ERROR, "VTY user invoked lchan Channel Mode Modify failed (%s)\n",
+ lchan->last_error ? : "unknown error");
+ break;
+
+ default:
+ LOG_LCHAN(lchan, LOGL_ERROR, "lchan Channel Mode Modify failed (%s)\n",
+ lchan->last_error ? : "unknown error");
+ break;
+ }
}
/* The idea here is that we must not require to change any lchan state in order to deny a request. */
#define lchan_on_activation_failure(lchan, for_conn, activ_for) \
_lchan_on_activation_failure(lchan, for_conn, activ_for, \
__FILE__, __LINE__)
-static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_activate_mode activ_for,
+static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_activate_for activ_for,
struct gsm_subscriber_connection *for_conn,
const char *file, int line)
{
@@ -96,7 +144,7 @@ static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_act
switch (activ_for) {
- case FOR_MS_CHANNEL_REQUEST:
+ case ACTIVATE_FOR_MS_CHANNEL_REQUEST:
if (!lchan->activate.immediate_assignment_sent) {
/* Failure before Immediate Assignment message, send a reject. */
LOG_LCHAN(lchan, LOGL_NOTICE, "Tx Immediate Assignment Reject (%s)\n",
@@ -108,14 +156,26 @@ static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_act
* lchan_on_activation_failure(), no additional action or logging needed. */
break;
- case FOR_ASSIGNMENT:
+ case ACTIVATE_FOR_ASSIGNMENT:
LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling Assignment FSM of error (%s)\n",
lchan->last_error ? : "unknown error");
- _osmo_fsm_inst_dispatch(for_conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR, lchan,
- file, line);
- return;
+ if (!for_conn) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for Assignment failed, but activation request has"
+ " no conn\n");
+ break;
+ }
+ if (!for_conn->assignment.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for Assignment failed, but conn has no ongoing"
+ " assignment procedure\n");
+ break;
+ }
+ _osmo_fsm_inst_dispatch(for_conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ERROR,
+ lchan, file, line);
+ break;
- case FOR_HANDOVER:
+ case ACTIVATE_FOR_HANDOVER:
LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling Handover FSM of error (%s)\n",
lchan->last_error ? : "unknown error");
if (!for_conn) {
@@ -133,11 +193,32 @@ static void _lchan_on_activation_failure(struct gsm_lchan *lchan, enum lchan_act
_osmo_fsm_inst_dispatch(for_conn->ho.fi, HO_EV_LCHAN_ERROR, lchan, file, line);
break;
- case FOR_VTY:
+ case ACTIVATE_FOR_VGCS_CHANNEL:
+ LOG_LCHAN(lchan, LOGL_NOTICE, "Signalling VGCS Assignment FSM of error (%s)\n",
+ lchan->last_error ? : "unknown error");
+ if (!for_conn) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for VGCS Assignment failed, but activation request has no conn\n");
+ break;
+ }
+ if (!for_conn->vgcs_chan.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for VGCS Assignment failed, but conn has no ongoing"
+ " assignment procedure\n");
+ break;
+ }
+ _osmo_fsm_inst_dispatch(for_conn->vgcs_chan.fi, VGCS_EV_LCHAN_ERROR, lchan, file, line);
+ break;
+
+ case ACTIVATE_FOR_VTY:
LOG_LCHAN(lchan, LOGL_ERROR, "VTY user invoked lchan activation failed (%s)\n",
lchan->last_error ? : "unknown error");
break;
+ case ACTIVATE_FOR_MODE_MODIFY_RTP:
+ lchan_on_mode_modify_failure(lchan, lchan->modify.info.modify_for, for_conn);
+ break;
+
default:
LOG_LCHAN(lchan, LOGL_ERROR, "lchan activation failed (%s)\n",
lchan->last_error ? : "unknown error");
@@ -151,25 +232,42 @@ static void lchan_on_fully_established(struct gsm_lchan *lchan)
return;
lchan->activate.concluded = true;
+ /* Set active state timekeeping markers. */
+ osmo_clock_gettime(CLOCK_MONOTONIC, &lchan->active_start);
+ lchan->active_stored = lchan->active_start;
+
+ /* Increment rate counters tracking fully established lchans. */
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_H:
+ case GSM_LCHAN_TCH_F:
+ rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_CHAN_TCH_FULLY_ESTABLISHED));
+ break;
+ case GSM_LCHAN_SDCCH:
+ rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_CHAN_SDCCH_FULLY_ESTABLISHED));
+ break;
+ default:
+ break;
+ }
+
switch (lchan->activate.info.activ_for) {
- case FOR_MS_CHANNEL_REQUEST:
+ case ACTIVATE_FOR_MS_CHANNEL_REQUEST:
/* No signalling to do here, MS is free to use the channel, and should go on to connect
* to the MSC and establish a subscriber connection. */
break;
- case FOR_ASSIGNMENT:
+ case ACTIVATE_FOR_ASSIGNMENT:
if (!lchan->conn) {
LOG_LCHAN(lchan, LOGL_ERROR,
"lchan activation for assignment succeeded, but lchan has no conn:"
" cannot trigger appropriate actions. Release.\n");
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
break;
}
if (!lchan->conn->assignment.fi) {
LOG_LCHAN(lchan, LOGL_ERROR,
"lchan activation for assignment succeeded, but lchan has no"
" assignment ongoing: cannot trigger appropriate actions. Release.\n");
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
break;
}
osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ESTABLISHED,
@@ -179,18 +277,18 @@ static void lchan_on_fully_established(struct gsm_lchan *lchan)
* will try to roll back a modified RTP connection. */
break;
- case FOR_HANDOVER:
+ case ACTIVATE_FOR_HANDOVER:
if (!lchan->conn) {
LOG_LCHAN(lchan, LOGL_ERROR,
"lchan activation for handover succeeded, but lchan has no conn\n");
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
break;
}
if (!lchan->conn->ho.fi) {
LOG_LCHAN(lchan, LOGL_ERROR,
"lchan activation for handover succeeded, but lchan has no"
" handover ongoing\n");
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
break;
}
osmo_fsm_inst_dispatch(lchan->conn->ho.fi, HO_EV_LCHAN_ESTABLISHED, lchan);
@@ -199,6 +297,26 @@ static void lchan_on_fully_established(struct gsm_lchan *lchan)
* we will try to roll back a modified RTP connection. */
break;
+ case ACTIVATE_FOR_VGCS_CHANNEL:
+ if (!lchan->conn) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan activation for VGCS assignment succeeded, but lchan has no conn\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
+ break;
+ }
+ if (!lchan->conn->vgcs_chan.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "lchan Activation for VGCS assignment requested, but conn has no VGCS resource FSM.\n");
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
+ break;
+ }
+ osmo_fsm_inst_dispatch(lchan->conn->vgcs_chan.fi, VGCS_EV_LCHAN_ACTIVE, lchan);
+ break;
+
+ case ACTIVATE_FOR_MODE_MODIFY_RTP:
+ lchan_on_mode_modify_success(lchan);
+ break;
+
default:
LOG_LCHAN(lchan, LOGL_NOTICE, "lchan %s fully established\n",
lchan_activate_mode_name(lchan->activate.info.activ_for));
@@ -206,24 +324,29 @@ static void lchan_on_fully_established(struct gsm_lchan *lchan)
}
}
-struct state_timeout lchan_fsm_timeouts[32] = {
- [LCHAN_ST_WAIT_TS_READY] = { .T=23001 },
- [LCHAN_ST_WAIT_ACTIV_ACK] = { .T=23002 },
- [LCHAN_ST_WAIT_RLL_RTP_ESTABLISH] = { .T=3101 },
- [LCHAN_ST_WAIT_RLL_RTP_RELEASED] = { .T=3109 },
- [LCHAN_ST_WAIT_BEFORE_RF_RELEASE] = { .T=3111 },
- [LCHAN_ST_WAIT_RF_RELEASE_ACK] = { .T=3111 },
- [LCHAN_ST_WAIT_AFTER_ERROR] = { .T=993111 },
+struct osmo_tdef_state_timeout lchan_fsm_timeouts[32] = {
+ [LCHAN_ST_WAIT_TS_READY] = { .T = -5 },
+ [LCHAN_ST_WAIT_ACTIV_ACK] = { .T = -6 },
+ [LCHAN_ST_WAIT_RLL_RTP_ESTABLISH] = { .T = 3101 },
+ [LCHAN_ST_WAIT_RLL_RTP_RELEASED] = { .T = 3109 },
+ [LCHAN_ST_WAIT_BEFORE_RF_RELEASE] = { .T = 3111 },
+ [LCHAN_ST_WAIT_RF_RELEASE_ACK] = { .T = -6 },
+ [LCHAN_ST_WAIT_AFTER_ERROR] = { .T = -3111 },
+ [LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK] = { .T = -13 },
+ [LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK] = { .T = -14 },
+ [LCHAN_ST_BORKEN] = { .T = -28 },
+ [LCHAN_ST_RECOVER_WAIT_ACTIV_ACK] = { .T = -6 },
+ [LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK] = { .T = -6 },
};
/* Transition to a state, using the T timer defined in lchan_fsm_timeouts.
* The actual timeout value is in turn obtained from network->T_defs.
* Assumes local variable fi exists. */
#define lchan_fsm_state_chg(state) \
- fsm_inst_state_chg_T(fi, state, \
- lchan_fsm_timeouts, \
- ((struct gsm_lchan*)(fi->priv))->ts->trx->bts->network->T_defs, \
- 5)
+ osmo_tdef_fsm_inst_state_chg(fi, state, \
+ lchan_fsm_timeouts, \
+ ((struct gsm_lchan*)(fi->priv))->ts->trx->bts->network->T_defs, \
+ 5)
/* Set a failure message, trigger the common actions to take on failure, transition to a state to
* continue with (using state timeouts from lchan_fsm_timeouts[]). Assumes local variable fi exists. */
@@ -235,7 +358,7 @@ struct state_timeout lchan_fsm_timeouts[32] = {
const uint32_t state_chg = STATE_CHG; \
LOG_LCHAN(_lchan, LOGL_DEBUG, "Handling failure, will then transition to state %s\n", \
osmo_fsm_state_name(fsm, state_chg)); \
- lchan_set_last_error(_lchan, "lchan %s in state %s: " fmt, \
+ LCHAN_SET_LAST_ERROR(_lchan, "lchan %s in state %s: " fmt, \
_lchan->activate.concluded ? "failure" : "allocation failed", \
osmo_fsm_state_name(fsm, state_was), ## args); \
lchan_on_activation_failure(_lchan, _lchan->activate.info.activ_for, _lchan->conn); \
@@ -244,7 +367,7 @@ struct state_timeout lchan_fsm_timeouts[32] = {
else \
LOG_LCHAN(_lchan, LOGL_DEBUG, "After failure handling, already in state %s\n", \
osmo_fsm_state_name(fsm, state_chg)); \
- } while(0)
+ } while (0)
/* Which state to transition to when lchan_fail() is called in a given state. */
uint32_t lchan_fsm_on_error[32] = {
@@ -258,6 +381,10 @@ uint32_t lchan_fsm_on_error[32] = {
[LCHAN_ST_WAIT_RF_RELEASE_ACK] = LCHAN_ST_BORKEN,
[LCHAN_ST_WAIT_AFTER_ERROR] = LCHAN_ST_UNUSED,
[LCHAN_ST_BORKEN] = LCHAN_ST_BORKEN,
+ [LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK] = LCHAN_ST_WAIT_RF_RELEASE_ACK,
+ [LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK] = LCHAN_ST_WAIT_RF_RELEASE_ACK,
+ [LCHAN_ST_RECOVER_WAIT_ACTIV_ACK] = LCHAN_ST_BORKEN,
+ [LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK] = LCHAN_ST_BORKEN,
};
#define lchan_fail(fmt, args...) lchan_fail_to(lchan_fsm_on_error[fi->state], fmt, ## args)
@@ -268,6 +395,16 @@ void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info)
OSMO_ASSERT(lchan && info);
+ if ((info->type_for == LCHAN_TYPE_FOR_VAMOS || lchan->vamos.is_secondary)
+ && !osmo_bts_has_feature(&lchan->ts->trx->bts->features, BTS_FEAT_VAMOS)) {
+ lchan->last_error = talloc_strdup(lchan->ts->trx, "VAMOS related channel activation requested,"
+ " but BTS does not support VAMOS");
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "VAMOS related channel activation requested, but BTS %u does not support VAMOS\n",
+ lchan->ts->trx->bts->nr);
+ goto abort;
+ }
+
if (!lchan_state_is(lchan, LCHAN_ST_UNUSED))
goto abort;
@@ -276,7 +413,7 @@ void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info)
switch (info->activ_for) {
- case FOR_ASSIGNMENT:
+ case ACTIVATE_FOR_ASSIGNMENT:
if (!info->for_conn
|| !info->for_conn->fi) {
LOG_LCHAN(lchan, LOGL_ERROR, "Activation requested, but no conn\n");
@@ -293,7 +430,7 @@ void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info)
}
break;
- case FOR_HANDOVER:
+ case ACTIVATE_FOR_HANDOVER:
if (!info->for_conn
|| !info->for_conn->fi) {
LOG_LCHAN(lchan, LOGL_ERROR, "Activation requested, but no conn\n");
@@ -315,6 +452,28 @@ void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info)
}
break;
+ case ACTIVATE_FOR_VGCS_CHANNEL:
+ if (!info->for_conn
+ || !info->for_conn->fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR, "Activation requested, but no conn\n");
+ goto abort;
+ }
+ if (!info->for_conn->vgcs_chan.fi) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Activation for VGCS assignment requested, but conn has no VGCS resource FSM.\n");
+ goto abort;
+ }
+ if (info->for_conn->vgcs_chan.new_lchan != lchan) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Activation for VGCS assignment requested, but conn's VGCS assignment state does"
+ " not reflect this lchan to be activated (instead: %s)\n",
+ info->for_conn->vgcs_chan.new_lchan ?
+ gsm_lchan_name(info->for_conn->vgcs_chan.new_lchan)
+ : "NULL");
+ goto abort;
+ }
+ break;
+
default:
break;
}
@@ -335,23 +494,50 @@ abort:
/* Remain in state UNUSED */
}
-static void lchan_fsm_update_id(struct gsm_lchan *lchan)
+void lchan_mode_modify(struct gsm_lchan *lchan, struct lchan_modify_info *info)
{
- osmo_fsm_inst_update_id_f(lchan->fi, "%u-%u-%u-%s-%u",
- lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
- gsm_pchan_id(lchan->ts->pchan_on_init), lchan->nr);
+ OSMO_ASSERT(lchan && info);
+
+ if ((info->type_for == LCHAN_TYPE_FOR_VAMOS || lchan->vamos.is_secondary)
+ && !osmo_bts_has_feature(&lchan->ts->trx->bts->features, BTS_FEAT_VAMOS)) {
+ lchan->last_error = talloc_strdup(lchan->ts->trx, "VAMOS related Channel Mode Modify requested,"
+ " but BTS does not support VAMOS");
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "VAMOS related Channel Mode Modify requested, but BTS %u does not support VAMOS\n",
+ lchan->ts->trx->bts->nr);
+ lchan_on_mode_modify_failure(lchan, info->modify_for, lchan->conn);
+ return;
+ }
+
+ /* To make sure that the lchan is actually allowed to initiate Mode Modify, feed through an FSM event. */
+ if (osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_REQUEST_MODE_MODIFY, info)) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Channel Mode Modify requested, but cannot dispatch LCHAN_EV_REQUEST_MODE_MODIFY event\n");
+ lchan_on_mode_modify_failure(lchan, info->modify_for, lchan->conn);
+ }
+}
+
+void lchan_fsm_update_id(struct gsm_lchan *lchan)
+{
+ lchan_update_name(lchan);
+ if (!lchan->fi)
+ return;
+ osmo_fsm_inst_update_id_f_sanitize(lchan->fi, '_', "%u-%u-%u-%s-%s%u",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ gsm_pchan_name(lchan->ts->pchan_on_init),
+ lchan->vamos.is_secondary ? "shadow" : "",
+ lchan->nr - (lchan->vamos.is_secondary ? lchan->ts->max_primary_lchans : 0));
if (lchan->fi_rtp)
osmo_fsm_inst_update_id_f(lchan->fi_rtp, lchan->fi->id);
}
-extern void lchan_rtp_fsm_init();
-
-void lchan_fsm_init()
+static __attribute__((constructor)) void lchan_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&lchan_fsm) == 0);
- lchan_rtp_fsm_init();
}
+static void lchan_reset(struct gsm_lchan *lchan);
+
void lchan_fsm_alloc(struct gsm_lchan *lchan)
{
OSMO_ASSERT(lchan->ts);
@@ -363,6 +549,7 @@ void lchan_fsm_alloc(struct gsm_lchan *lchan)
lchan->fi->priv = lchan;
lchan_fsm_update_id(lchan);
LOGPFSML(lchan->fi, LOGL_DEBUG, "new lchan\n");
+ lchan_reset(lchan);
}
/* Clear volatile state of the lchan. Clear all except
@@ -376,8 +563,11 @@ static void lchan_reset(struct gsm_lchan *lchan)
{
LOG_LCHAN(lchan, LOGL_DEBUG, "Clearing lchan state\n");
- if (lchan->conn)
- gscon_forget_lchan(lchan->conn, lchan);
+ if (lchan->conn) {
+ struct gsm_subscriber_connection *conn = lchan->conn;
+ lchan_forget_conn(lchan);
+ gscon_forget_lchan(conn, lchan);
+ }
if (lchan->rqd_ref) {
talloc_free(lchan->rqd_ref);
@@ -386,7 +576,7 @@ static void lchan_reset(struct gsm_lchan *lchan)
if (lchan->fi_rtp)
osmo_fsm_inst_term(lchan->fi_rtp, OSMO_FSM_TERM_REQUEST, 0);
if (lchan->mgw_endpoint_ci_bts) {
- mgw_endpoint_ci_dlcx(lchan->mgw_endpoint_ci_bts);
+ osmo_mgcpc_ep_ci_dlcx(lchan->mgw_endpoint_ci_bts);
lchan->mgw_endpoint_ci_bts = NULL;
}
@@ -400,97 +590,127 @@ static void lchan_reset(struct gsm_lchan *lchan)
.meas_rep_last_seen_nr = 255,
.last_error = lchan->last_error,
+
+ .release.rr_cause = GSM48_RR_CAUSE_NORMAL,
+
+ .tsc_set = 1,
+ .interf_dbm = INTERF_DBM_UNKNOWN,
+ .interf_band = INTERF_BAND_UNKNOWN,
};
}
static void lchan_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ struct gsm_bts *bts = lchan->ts->trx->bts;
lchan_reset(lchan);
+ chan_counts_ts_update(lchan->ts);
osmo_fsm_inst_dispatch(lchan->ts->fi, TS_EV_LCHAN_UNUSED, lchan);
+
+ /* Poll the channel request queue, so that waiting calls can make use of the lchan that just
+ * has become unused now. */
+ abis_rsl_chan_rqd_queue_poll(bts);
}
-/* Configure the multirate setting on this channel. */
-static int lchan_mr_config(struct gsm_lchan *lchan, const struct gsm48_multi_rate_conf *mr_conf)
+static void lchan_fsm_cbch_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ chan_counts_ts_update(lchan->ts);
+}
+
+static void lchan_fsm_wait_after_error_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
- bool full_rate = (lchan->type == GSM_LCHAN_TCH_F);
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
struct gsm_bts *bts = lchan->ts->trx->bts;
- struct bsc_msc_data *msc = lchan->conn->sccp.msc;
- struct amr_multirate_conf *mr;
+
+ /* We also need to poll the channel request queue when the FSM enters the WAIT_AFTER_ERROR
+ * state. In case of an emergency call the channel request queue will skip the waiting
+ * period. */
+ abis_rsl_chan_rqd_queue_poll(bts);
+}
+
+/* Configure the multirate setting on this channel. */
+static int mr_config_filter(struct gsm48_multi_rate_conf *mr_conf_result,
+ bool full_rate,
+ const struct amr_multirate_conf *amr_mrc,
+ const struct gsm48_multi_rate_conf *mr_filter_msc,
+ uint16_t s15_s0,
+ const struct gsm_lchan *lchan_for_logging)
+{
int rc;
- int rc_rate;
- struct gsm48_multi_rate_conf mr_conf_filtered;
- const struct gsm48_multi_rate_conf *mr_conf_bts;
-
- /* There are two different active sets, depending on the channel rate,
- * make sure the appropate one is selected. */
- if (full_rate)
- mr = &bts->mr_full;
- else
- mr = &bts->mr_half;
-
- /* The VTY allows to forbid certain codec rates. Unfortunately we can
- * not articulate all of the prohibitions on through S0-S15 on the A
- * interface. To ensure that the VTY settings are observed we create
- * a manipulated copy of the mr_conf that ensures forbidden codec rates
- * are not used in the multirate configuration IE. */
- rc_rate = calc_amr_rate_intersection(&mr_conf_filtered, &msc->amr_conf, mr_conf);
- if (rc_rate < 0) {
- /* The msc->amr_conf comes from osmo-bsc.cfg's 'msc' / 'amr-config *k (forbidden|allowed)'.
- * The mr_conf ultimately comes from the Assignment Request received from the MSC,
- * see bssmap_handle_assignm_req(): first we gsm0808_dec_speech_codec_list() and then we
- * match_codec_pref() to overlay the permitted speech with the BTS-specific AMR modes for this tch type,
- * to obtain the s15_s0 bits, which end up in mr_conf. */
- LOG_LCHAN(lchan, LOGL_ERROR,
- "Intersection of AMR rate configurations is empty:"
- " osmo-bsc.cfg=0x%02x & (requested-codec-list & permitted-modes)=0x%02x."
- " In the config: make sure that the 'msc'/'amr-config' allows AMR rates"
- " that are also allowed in 'network'/'bts N'/'amr %s modes ...'\n",
- ((uint8_t*)&msc->amr_conf)[1], ((uint8_t*)mr_conf)[1],
- full_rate ? "tch-f" : "tch-h");
+ struct gsm48_multi_rate_conf *mr_filter_bts = (struct gsm48_multi_rate_conf*)amr_mrc->gsm48_ie;
+
+ /* Generate mr conf struct from S15-S0 bits */
+ if (gsm48_mr_cfg_from_gsm0808_sc_cfg(mr_conf_result, s15_s0) < 0) {
+ LOG_LCHAN(lchan_for_logging, LOGL_ERROR,
+ "can not determine multirate configuration, S15-S0 (%04x) are ambiguous!\n", s15_s0);
return -EINVAL;
}
- LOG_LCHAN(lchan, LOGL_DEBUG,
- "AMR rate configurations: MSC=0x%x & BTS=0x%x = 0x%x\n",
- ((uint8_t*)&msc->amr_conf)[1], ((uint8_t*)mr_conf)[1],
- ((uint8_t*)&mr_conf_filtered)[1]);
- /* The two last codec rates which are defined for AMR do only work with
- * full rate channels. We will pinch off those rates fรผr half-rate
- * channels to ensure they are not included accidently. */
- if (!full_rate) {
- if (mr_conf_filtered.m10_2 || mr_conf_filtered.m12_2)
- LOG_LCHAN(lchan, LOGL_ERROR, "ignoring unsupported amr codec rates\n");
- mr_conf_filtered.m10_2 = 0;
- mr_conf_filtered.m12_2 = 0;
+ /* Do not include 12.2 kbps rate when S1 is set. */
+ if ((!full_rate) && (s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20)) {
+ /* See also 3GPP TS 28.062, chapter 7.11.3.1.3:
+ *
+ * In case this Configuration "Config-NB-Code = 1" is signalled in the TFO Negotiation for the HR_AMR
+ * Codec Type, then it shall be assumed that AMR mode 12.2 kbps is (of course) not included.
+ *
+ * Further below, we log an error if 12k2 is included for a TCH/H lchan: removing this here ensures that
+ * we don't log that error for GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20 on a TCH/H lchan. */
+ mr_conf_result->m12_2 = 0;
}
- /* Ensure that the resulting filtered conf is coherent with the
- * configuration that is set for the BTS and the specified rate */
- mr_conf_bts = (struct gsm48_multi_rate_conf *)mr->gsm48_ie;
- rc_rate = calc_amr_rate_intersection(&mr_conf_filtered, mr_conf_bts, &mr_conf_filtered);
- if (rc_rate < 0) {
- LOG_LCHAN(lchan, LOGL_ERROR,
- "can not encode multirate configuration (invalid amr rate setting, BTS)\n");
- return -EINVAL;
+ if (mr_filter_msc) {
+ rc = calc_amr_rate_intersection(mr_conf_result, mr_filter_msc, mr_conf_result);
+ if (rc < 0) {
+ LOG_LCHAN(lchan_for_logging, LOGL_ERROR,
+ "can not encode multirate configuration (invalid amr rate setting, MSC)\n");
+ return -EINVAL;
+ }
}
- /* Proceed with the generation of the multirate configuration IE
- * (MS and BTS) */
- rc = gsm48_multirate_config(lchan->mr_ms_lv, &mr_conf_filtered, mr->ms_mode, mr->num_modes);
- if (rc != 0) {
- LOG_LCHAN(lchan, LOGL_ERROR, "can not encode multirate configuration (MS)\n");
+ rc = calc_amr_rate_intersection(mr_conf_result, mr_filter_bts, mr_conf_result);
+ if (rc < 0) {
+ LOG_LCHAN(lchan_for_logging, LOGL_ERROR,
+ "can not encode multirate configuration (invalid amr rate setting, BTS)\n");
return -EINVAL;
}
- rc = gsm48_multirate_config(lchan->mr_bts_lv, &mr_conf_filtered, mr->bts_mode, mr->num_modes);
- if (rc != 0) {
- LOG_LCHAN(lchan, LOGL_ERROR, "can not encode multirate configuration (BTS)\n");
- return -EINVAL;
+
+ /* Set the ICMI according to the BTS. Above gsm48_mr_cfg_from_gsm0808_sc_cfg() always sets ICMI = 1, which
+ * carried through all of the above rate intersections. */
+ mr_conf_result->icmi = mr_filter_bts->icmi;
+ mr_conf_result->smod = mr_filter_bts->smod;
+
+ /* 10k2 and 12k2 only work for full rate */
+ if (!full_rate) {
+ if (mr_conf_result->m10_2 || mr_conf_result->m12_2)
+ LOG_LCHAN(lchan_for_logging, LOGL_ERROR,
+ "half rate lchan: ignoring unsupported AMR codec rates 10k2 and 12k2\n");
+ mr_conf_result->m10_2 = 0;
+ mr_conf_result->m12_2 = 0;
}
return 0;
}
+/* Configure the multirate setting on this channel. */
+static int lchan_mr_config(struct gsm48_multi_rate_conf *mr_conf, const struct gsm_lchan *lchan, uint16_t s15_s0)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ bool full_rate = lchan->type == GSM_LCHAN_TCH_F;
+ struct amr_multirate_conf *amr_mrc = full_rate ? &bts->mr_full : &bts->mr_half;
+ struct gsm48_multi_rate_conf *mr_filter_msc = NULL;
+
+ /* If activated for VTY, there may not be a conn indicating an MSC AMR configuration. */
+ if (lchan->conn && lchan->conn->sccp.msc)
+ mr_filter_msc = &lchan->conn->sccp.msc->amr_conf;
+
+ return mr_config_filter(mr_conf,
+ full_rate,
+ amr_mrc, mr_filter_msc,
+ s15_s0,
+ lchan);
+}
+
static void lchan_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct lchan_activate_info *info = data;
@@ -502,10 +722,16 @@ static void lchan_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *dat
OSMO_ASSERT(info);
OSMO_ASSERT(!lchan->conn);
OSMO_ASSERT(!lchan->mgw_endpoint_ci_bts);
- lchan_set_last_error(lchan, NULL);
+ if (lchan->last_error)
+ talloc_free(lchan->last_error);
+ lchan->last_error = NULL;
lchan->release.requested = false;
lchan->activate.info = *info;
+ /* To avoid confusion, invalidate info.chreq_reason value if it isn't for a CHREQ */
+ if (lchan->activate.info.activ_for != ACTIVATE_FOR_MS_CHANNEL_REQUEST)
+ lchan->activate.info.chreq_reason = -1;
+
lchan->activate.concluded = false;
lchan_fsm_state_chg(LCHAN_ST_WAIT_TS_READY);
break;
@@ -515,90 +741,132 @@ static void lchan_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *dat
}
}
+static int lchan_activate_set_ch_mode_rate_and_mr_config(struct gsm_lchan *lchan)
+{
+ struct osmo_fsm_inst *fi = lchan->fi;
+ lchan->activate.ch_mode_rate = lchan->activate.info.ch_mode_rate;
+ lchan->activate.ch_mode_rate.chan_mode = (lchan->activate.info.type_for == LCHAN_TYPE_FOR_VAMOS)
+ ? gsm48_chan_mode_to_vamos(lchan->activate.info.ch_mode_rate.chan_mode)
+ : gsm48_chan_mode_to_non_vamos(lchan->activate.info.ch_mode_rate.chan_mode);
+ if (lchan->activate.ch_mode_rate.chan_mode < 0) {
+ lchan_fail("Invalid chan_mode: %s", gsm48_chan_mode_name(lchan->activate.info.ch_mode_rate.chan_mode));
+ return -EINVAL;
+ }
+
+ if (gsm48_chan_mode_to_non_vamos(lchan->activate.ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) {
+ if (lchan_mr_config(&lchan->activate.mr_conf_filtered, lchan, lchan->activate.ch_mode_rate.s15_s0) < 0) {
+ lchan_fail("Can not generate multirate configuration IE");
+ return -EINVAL;
+ }
+ }
+
+ lchan->activate.ch_indctr = lchan->activate.info.ch_indctr;
+ return 0;
+}
+
+static int lchan_send_imm_ass(struct osmo_fsm_inst *fi)
+{
+ int rc;
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ rc = rsl_tx_imm_assignment(lchan);
+ if (rc) {
+ lchan_fail("Failed to Tx RR Immediate Assignment message (rc=%d %s)",
+ rc, strerror(-rc));
+ return rc;
+ }
+ LOG_LCHAN(lchan, LOGL_DEBUG, "Tx RR Immediate Assignment\n");
+ lchan->activate.immediate_assignment_sent = true;
+ return rc;
+}
+
+static void post_activ_ack_accept_preliminary_settings(struct gsm_lchan *lchan);
+
static void lchan_fsm_wait_ts_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
- struct gsm48_multi_rate_conf mr_conf;
struct gsm_bts *bts = lchan->ts->trx->bts;
- struct mgwep_ci *use_mgwep_ci;
+ struct osmo_mgcpc_ep_ci *use_mgwep_ci;
struct gsm_lchan *old_lchan = lchan->activate.info.re_use_mgw_endpoint_from_lchan;
struct lchan_activate_info *info = &lchan->activate.info;
+ int ms_power_dbm = bts->ms_max_power;
+ bool requires_rtp_stream;
if (lchan->release.requested) {
lchan_fail("Release requested while activating");
return;
}
- lchan->conn = info->for_conn;
+ chan_counts_ts_update(lchan->ts);
- if (old_lchan)
- lchan->encr = old_lchan->encr;
- else {
- lchan->encr = (struct gsm_encr){
- .alg_id = RSL_ENC_ALG_A5(0), /* no encryption */
- };
- }
+ lchan->conn = info->for_conn;
/* If there is a previous lchan, and the new lchan is on the same cell as previous one,
- * take over power and TA values. Otherwise, use max power and zero TA. */
+ * take over MS power values. Otherwise, use configured MS max power. */
if (old_lchan && old_lchan->ts->trx->bts == bts) {
- lchan->ms_power = old_lchan->ms_power;
- lchan->bs_power = old_lchan->bs_power;
- lchan->rqd_ta = old_lchan->rqd_ta;
- } else {
- lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
- /* From lchan_reset():
- * - bs_power is still zero, 0dB reduction, output power = Pn.
- * - TA is still zero, to be determined by RACH. */
- }
-
- if (info->chan_mode == GSM48_CMODE_SPEECH_AMR) {
- gsm48_mr_cfg_from_gsm0808_sc_cfg(&mr_conf, info->s15_s0);
- if (lchan_mr_config(lchan, &mr_conf) < 0) {
- lchan_fail("Can not generate multirate configuration IE\n");
- return;
- }
+ if ((ms_power_dbm = ms_pwr_dbm(bts->band, old_lchan->ms_power)) == 0)
+ ms_power_dbm = bts->ms_max_power;
+ }
+ lchan_update_ms_power_ctrl_level(lchan, ms_power_dbm);
+
+ /* Default BS Power reduction value (in dB) */
+ lchan->bs_power_db = (bts->bs_power_ctrl.mode == GSM_PWR_CTRL_MODE_DYN_BTS) ?
+ bts->bs_power_ctrl.bs_power_max_db :
+ bts->bs_power_ctrl.bs_power_val_db;
+ /* BS Power Control is generally not allowed on the BCCH/CCCH carrier.
+ * However, we allow it in the BCCH carrier power reduction mode of operation. */
+ if (lchan->ts->trx == bts->c0) {
+ lchan->bs_power_db = OSMO_MIN(lchan->ts->c0_max_power_red_db,
+ lchan->bs_power_db);
}
- switch (info->chan_mode) {
-
- case GSM48_CMODE_SIGN:
- lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
- lchan->tch_mode = GSM48_CMODE_SIGN;
- break;
+ if (lchan_activate_set_ch_mode_rate_and_mr_config(lchan))
+ return;
- case GSM48_CMODE_SPEECH_V1:
- case GSM48_CMODE_SPEECH_EFR:
- case GSM48_CMODE_SPEECH_AMR:
- lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
- lchan->tch_mode = info->chan_mode;
- break;
+ /* If enabling VAMOS mode and no specific TSC Set was selected, make sure to select a sane TSC Set by
+ * default: Set 1 for the primary and Set 2 for the shadow lchan. For non-VAMOS lchans, TSC Set 1. */
+ if (lchan->activate.info.tsc_set.present)
+ lchan->activate.tsc_set = lchan->activate.info.tsc_set.val;
+ else
+ lchan->activate.tsc_set = lchan->vamos.is_secondary ? 2 : 1;
- default:
- lchan_fail("Not implemented: cannot activate for chan mode %s",
- gsm48_chan_mode_name(info->chan_mode));
- return;
- }
+ /* Use the TSC provided in the activation request, if any. Otherwise use the timeslot's configured TSC. */
+ lchan->activate.tsc = lchan->activate.info.tsc.present ? lchan->activate.info.tsc.val : gsm_ts_tsc(lchan->ts);
use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);
+ requires_rtp_stream = bsc_chan_ind_requires_rtp_stream(lchan->activate.info.ch_indctr);
LOG_LCHAN(lchan, LOGL_INFO,
- "Activation requested: %s voice=%s MGW-ci=%s type=%s tch-mode=%s\n",
+ "Activation requested: %s rtp=%s MGW-ci=%s type=%s tch-mode=%s encr-alg=A5/%u ck=%s\n",
lchan_activate_mode_name(lchan->activate.info.activ_for),
- lchan->activate.info.requires_voice_stream ? "yes" : "no",
- lchan->activate.info.requires_voice_stream ?
- (use_mgwep_ci ? mgwep_ci_name(use_mgwep_ci) : "new")
+ requires_rtp_stream ? "yes" : "no",
+ requires_rtp_stream ?
+ (use_mgwep_ci ? osmo_mgcpc_ep_ci_name(use_mgwep_ci) : "new")
: "none",
- gsm_lchant_name(lchan->type),
- gsm48_chan_mode_name(lchan->tch_mode));
+ gsm_chan_t_name(lchan->type),
+ gsm48_chan_mode_name(lchan->activate.ch_mode_rate.chan_mode),
+ lchan->activate.info.encr.alg_a5_n,
+ lchan->activate.info.encr.key_len ? osmo_hexdump_nospc(lchan->activate.info.encr.key,
+ lchan->activate.info.encr.key_len) : "none");
/* Ask for the timeslot to make ready for this lchan->type.
* We'll receive LCHAN_EV_TS_READY or LCHAN_EV_TS_ERROR in response. */
osmo_fsm_inst_dispatch(lchan->ts->fi, TS_EV_LCHAN_REQUESTED, lchan);
/* Prepare an MGW endpoint CI if appropriate. */
- if (lchan->activate.info.requires_voice_stream)
+ if (requires_rtp_stream)
lchan_rtp_fsm_start(lchan);
+
+ if (lchan->activate.info.imm_ass_time == IMM_ASS_TIME_PRE_TS_ACK) {
+ /* Send the Immediate Assignment even before the timeslot is ready, saving a dyn TS timeslot roundtrip
+ * on Abis (experimental).
+ *
+ * Until the Channel Activation ACK is received, various values still are preliminary and hence live in
+ * lchan->activate.*. We're doing things early here and need e.g. an accurate lchan->tsc, so already
+ * copy the preliminary values from lchan->activate.* into the operative places in lchan->* prematurely.
+ */
+ post_activ_ack_accept_preliminary_settings(lchan);
+ lchan_send_imm_ass(fi);
+ }
}
static void lchan_fsm_wait_ts_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -619,7 +887,7 @@ static void lchan_fsm_wait_ts_ready(struct osmo_fsm_inst *fi, uint32_t event, vo
return;
}
- lchan_fail("Failed to setup RTP stream: %s in state %s\n",
+ lchan_fail("Failed to setup RTP stream: %s in state %s",
osmo_fsm_event_name(fi->fsm, event),
osmo_fsm_inst_state_name(fi));
return;
@@ -642,22 +910,47 @@ static void lchan_fsm_wait_activ_ack_onenter(struct osmo_fsm_inst *fi, uint32_t
}
switch (lchan->activate.info.activ_for) {
- case FOR_MS_CHANNEL_REQUEST:
+ case ACTIVATE_FOR_MS_CHANNEL_REQUEST:
act_type = RSL_ACT_INTRA_IMM_ASS;
break;
- case FOR_HANDOVER:
+ case ACTIVATE_FOR_HANDOVER:
act_type = lchan->conn->ho.async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC;
ho_ref = lchan->conn->ho.ho_ref;
break;
+ case ACTIVATE_FOR_ASSIGNMENT:
+ case ACTIVATE_FOR_VGCS_CHANNEL:
default:
- case FOR_ASSIGNMENT:
act_type = RSL_ACT_INTRA_NORM_ASS;
break;
}
+ /* rsl_tx_chan_activ() and build_encr_info() access lchan->encr, make sure it reflects the values requested for
+ * activation.
+ * TODO: rather leave it in lchan->activate.info.encr until the ACK is received, which means that
+ * rsl_tx_chan_activ() should use lchan->activate.info.encr and build_encr_info() should be passed encr as an
+ * explicit argument. */
+ lchan->encr = lchan->activate.info.encr;
+
rc = rsl_tx_chan_activ(lchan, act_type, ho_ref);
- if (rc)
+ if (rc) {
lchan_fail_to(LCHAN_ST_UNUSED, "Tx Chan Activ failed: %s (%d)", strerror(-rc), rc);
+ return;
+ }
+
+ if (lchan->activate.info.ta_known)
+ lchan->last_ta = lchan->activate.info.ta;
+
+ if (lchan->activate.info.imm_ass_time == IMM_ASS_TIME_PRE_CHAN_ACK) {
+ /* Send the Immediate Assignment directly after the Channel Activation request, saving one Abis
+ * roundtrip between ChanRqd and Imm Ass.
+ *
+ * Until the Channel Activation ACK is received, various values still are preliminary and hence live in
+ * lchan->activate.*. We're doing things early here and need e.g. an accurate lchan->tsc, so already
+ * copy the preliminary values from lchan->activate.* into the operative places in lchan->* prematurely.
+ */
+ post_activ_ack_accept_preliminary_settings(lchan);
+ lchan_send_imm_ass(fi);
+ }
}
static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi);
@@ -677,6 +970,7 @@ static void lchan_fsm_wait_activ_ack(struct osmo_fsm_inst *fi, uint32_t event, v
if (data) {
uint32_t next_state;
lchan->release.rsl_error_cause = *(uint8_t*)data;
+ lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(lchan->release.rsl_error_cause);
lchan->release.in_error = true;
if (lchan->release.rsl_error_cause != RSL_ERR_RCH_ALR_ACTV_ALLOC)
next_state = LCHAN_ST_BORKEN;
@@ -689,6 +983,7 @@ static void lchan_fsm_wait_activ_ack(struct osmo_fsm_inst *fi, uint32_t event, v
rsl_err_name(lchan->release.rsl_error_cause), lchan->release.rsl_error_cause);
} else {
lchan->release.rsl_error_cause = RSL_ERR_IE_NONEXIST;
+ lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(lchan->release.rsl_error_cause);
lchan->release.in_error = true;
lchan_fail_to(LCHAN_ST_BORKEN, "Chan Activ NACK without cause IE");
}
@@ -714,10 +1009,24 @@ static void lchan_fsm_wait_activ_ack(struct osmo_fsm_inst *fi, uint32_t event, v
}
}
+static void post_activ_ack_accept_preliminary_settings(struct gsm_lchan *lchan)
+{
+ lchan->current_ch_mode_rate = lchan->activate.ch_mode_rate;
+ lchan->current_mr_conf = lchan->activate.mr_conf_filtered;
+ lchan->current_ch_indctr = lchan->activate.ch_indctr;
+ lchan->vamos.enabled = (lchan->activate.info.type_for == LCHAN_TYPE_FOR_VAMOS);
+ lchan->tsc_set = lchan->activate.tsc_set;
+ lchan->tsc = lchan->activate.tsc;
+}
+
static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi)
{
- int rc;
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ post_activ_ack_accept_preliminary_settings(lchan);
+ LOG_LCHAN(lchan, LOGL_INFO, "Rx Activ ACK %s\n",
+ gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode));
+
if (lchan->release.requested) {
lchan_fail_to(LCHAN_ST_WAIT_RF_RELEASE_ACK, "Release requested while activating");
return;
@@ -725,51 +1034,49 @@ static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi)
switch (lchan->activate.info.activ_for) {
- case FOR_MS_CHANNEL_REQUEST:
- rc = rsl_tx_imm_assignment(lchan);
- if (rc) {
- lchan_fail("Failed to Tx RR Immediate Assignment message (rc=%d %s)\n",
- rc, strerror(-rc));
- return;
+ case ACTIVATE_FOR_MS_CHANNEL_REQUEST:
+ if (lchan->activate.info.imm_ass_time == IMM_ASS_TIME_POST_CHAN_ACK) {
+ if (lchan_send_imm_ass(fi)) {
+ /* lchan_fail() was already called in lchan_send_imm_ass() */
+ return;
+ }
}
- LOG_LCHAN(lchan, LOGL_DEBUG, "Tx RR Immediate Assignment\n");
- lchan->activate.immediate_assignment_sent = true;
break;
- case FOR_ASSIGNMENT:
+ case ACTIVATE_FOR_ASSIGNMENT:
if (!lchan->conn) {
LOG_LCHAN(lchan, LOGL_ERROR,
"lchan activation for assignment succeeded, but lchan has no conn:"
" cannot trigger appropriate actions. Release.\n");
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
- break;
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
+ return;
}
if (!lchan->conn->assignment.fi) {
LOG_LCHAN(lchan, LOGL_ERROR,
"lchan activation for assignment succeeded, but lchan has no"
" assignment ongoing: cannot trigger appropriate actions. Release.\n");
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
- break;
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
+ return;
}
/* After the Chan Activ Ack, the MS expects to receive an RR Assignment Command.
* Let the assignment_fsm handle that. */
osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ACTIVE, lchan);
break;
- case FOR_HANDOVER:
+ case ACTIVATE_FOR_HANDOVER:
if (!lchan->conn) {
LOG_LCHAN(lchan, LOGL_ERROR,
"lchan activation for handover succeeded, but lchan has no conn:"
" cannot trigger appropriate actions. Release.\n");
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
- break;
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
+ return;
}
if (!lchan->conn->ho.fi) {
LOG_LCHAN(lchan, LOGL_ERROR,
"lchan activation for handover succeeded, but lchan has no"
" handover ongoing: cannot trigger appropriate actions. Release.\n");
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
- break;
+ lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
+ return;
}
/* After the Chan Activ Ack of the new lchan, send the MS an RR Handover Command on the
* old channel. The handover_fsm handles that. */
@@ -788,23 +1095,44 @@ static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi)
static void lchan_fsm_wait_rll_rtp_establish_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ bool requires_rtp_stream = bsc_chan_ind_requires_rtp_stream(lchan->activate.info.ch_indctr);
+
if (lchan->fi_rtp)
osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_LCHAN_READY, 0);
+ /* Prepare an MGW endpoint CI if appropriate (late). */
+ else if (requires_rtp_stream)
+ lchan_rtp_fsm_start(lchan);
+
+ /* When activating a channel for VTY or VGCS/VBS, skip waiting for activity from
+ * lchan_rtp_fsm, but only if no rtp stream is required. */
+ if ((lchan->activate.info.activ_for == ACTIVATE_FOR_VTY ||
+ lchan->activate.info.activ_for == ACTIVATE_FOR_VGCS_CHANNEL) &&
+ !requires_rtp_stream) {
+ lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
+ }
}
static void lchan_fsm_wait_rll_rtp_establish(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ bool requires_rtp_stream = bsc_chan_ind_requires_rtp_stream(lchan->activate.info.ch_indctr);
+
switch (event) {
case LCHAN_EV_RLL_ESTABLISH_IND:
- if (!lchan->activate.info.requires_voice_stream
- || lchan_rtp_established(lchan))
+ if (!requires_rtp_stream || lchan_rtp_established(lchan)) {
+ LOG_LCHAN(lchan, LOGL_DEBUG,
+ "%s\n",
+ (requires_rtp_stream ?
+ "RTP already established earlier" : "no voice stream required"));
lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
+ }
return;
case LCHAN_EV_RTP_READY:
- if (lchan->sapis[0] != LCHAN_SAPI_UNUSED)
+ /* If RLL was established or if it does not need to be establised, because of VGCS/VBS channel. */
+ if (lchan->sapis[0] != LCHAN_SAPI_UNUSED ||
+ lchan->activate.info.activ_for == ACTIVATE_FOR_VGCS_CHANNEL)
lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
return;
@@ -816,7 +1144,90 @@ static void lchan_fsm_wait_rll_rtp_establish(struct osmo_fsm_inst *fi, uint32_t
return;
}
- lchan_fail("Failed to setup RTP stream: %s in state %s\n",
+ lchan_fail("Failed to setup RTP stream: %s in state %s",
+ osmo_fsm_event_name(fi->fsm, event),
+ osmo_fsm_inst_state_name(fi));
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_fsm_wait_rr_chan_mode_modify_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ gsm48_lchan_modify(lchan, lchan->modify.ch_mode_rate.chan_mode);
+}
+
+static void lchan_fsm_wait_rr_chan_mode_modify_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case LCHAN_EV_RR_CHAN_MODE_MODIFY_ACK:
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK);
+ return;
+
+ case LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR:
+ lchan_fail("Failed to change channel mode on the MS side: %s in state %s",
+ osmo_fsm_event_name(fi->fsm, event),
+ osmo_fsm_inst_state_name(fi));
+ return;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_fsm_wait_rsl_chan_mode_modify_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ int rc;
+
+ rc = rsl_chan_mode_modify_req(lchan);
+ if (rc < 0) {
+ lchan_fail("Failed to send rsl message to change the channel mode on the BTS side: state %s",
+ osmo_fsm_inst_state_name(fi));
+ }
+}
+
+static void lchan_fsm_wait_rsl_chan_mode_modify_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_EV_RSL_CHAN_MODE_MODIFY_ACK:
+ /* The Channel Mode Modify was ACKed, now the requested values become the accepted and used values. */
+ lchan->current_ch_mode_rate = lchan->modify.ch_mode_rate;
+ lchan->current_mr_conf = lchan->modify.mr_conf_filtered;
+ lchan->current_ch_indctr = lchan->modify.ch_indctr;
+ lchan->tsc_set = lchan->modify.tsc_set;
+ lchan->tsc = lchan->modify.tsc;
+ lchan->vamos.enabled = (lchan->modify.info.type_for == LCHAN_TYPE_FOR_VAMOS);
+
+ if (bsc_chan_ind_requires_rtp_stream(lchan->modify.info.ch_indctr) && !lchan->fi_rtp) {
+ /* Continue with RTP stream establishing as done in lchan_activate(). Place the requested values in
+ * lchan->activate.info and continue with voice stream setup. */
+ lchan->activate.info = (struct lchan_activate_info){
+ .activ_for = ACTIVATE_FOR_MODE_MODIFY_RTP,
+ .for_conn = lchan->conn,
+ .ch_mode_rate = lchan->modify.ch_mode_rate,
+ .ch_indctr = lchan->modify.info.ch_indctr,
+ .msc_assigned_cic = lchan->modify.info.msc_assigned_cic,
+ };
+ if (lchan_activate_set_ch_mode_rate_and_mr_config(lchan))
+ return;
+
+ lchan->activate.concluded = false;
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH);
+ } else {
+ lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
+ lchan_on_mode_modify_success(lchan);
+ }
+ return;
+
+ case LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK:
+ lchan_fail("Failed to change channel mode on the BTS side: %s in state %s",
osmo_fsm_event_name(fi->fsm, event),
osmo_fsm_inst_state_name(fi));
return;
@@ -889,11 +1300,22 @@ static void handle_rll_rel_ind_or_conf(struct osmo_fsm_inst *fi, uint32_t event,
lchan->sapis[sapi] = LCHAN_SAPI_UNUSED;
rll_indication(lchan, link_id, BSC_RLLR_IND_REL_IND);
- /* Releasing SAPI 0 means the conn becomes invalid; but not if the link_id contains a TCH flag.
- * (TODO: is this the correct interpretation?) */
+ /* Releasing SAPI 0 means the conn becomes invalid; but not if the link_id contains a SACCH flag. */
if (lchan->conn && sapi == 0 && !(link_id & 0xc0)) {
- LOG_LCHAN(lchan, LOGL_DEBUG, "lchan is releasing\n");
- gscon_lchan_releasing(lchan->conn, lchan);
+ /* A VGCS/VBS channel must stay active, even if all SAPIs are released.
+ * When a talker releases, the channel is available for the listeners and the next talker. The actual
+ * channel release is performed by the VGCS/VBS call control. */
+ if (!lchan_is_asci(lchan)) {
+ LOG_LCHAN(lchan, LOGL_DEBUG, "lchan is releasing\n");
+ gscon_lchan_releasing(lchan->conn, lchan);
+ }
+
+ /* if SAPI=0 is gone, it makes no sense if other SAPIs are still around,
+ * this is not a valid configuration and we should forget about them.
+ * This is particularly relevant in case of Ericsson RBS6000, which doesn't
+ * seem to send a RLL_REL_IND for SAPI=3 if there was already one for SAPI=0 */
+ for_each_active_sapi(sapi, 1, lchan)
+ lchan->sapis[sapi] = LCHAN_SAPI_UNUSED;
}
/* The caller shall check whether all SAPIs are released and cause a state chg */
@@ -902,6 +1324,9 @@ static void handle_rll_rel_ind_or_conf(struct osmo_fsm_inst *fi, uint32_t event,
static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ struct lchan_modify_info *modif_info;
+ struct osmo_mgcpc_ep_ci *use_mgwep_ci;
+ bool requires_rtp_stream = bsc_chan_ind_requires_rtp_stream(lchan->modify.info.ch_indctr);
switch (event) {
case LCHAN_EV_RLL_ESTABLISH_IND:
@@ -911,7 +1336,8 @@ static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void
case LCHAN_EV_RLL_REL_IND:
case LCHAN_EV_RLL_REL_CONF:
handle_rll_rel_ind_or_conf(fi, event, data);
- if (!lchan_active_sapis(lchan, 0))
+ /* Only release channel, if there is no SAPI and this channel is not a VGCS channel. */
+ if (!lchan_active_sapis(lchan, 0) && !lchan_is_asci(lchan))
lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_RELEASED);
return;
@@ -923,11 +1349,62 @@ static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void
return;
}
- lchan_fail("RTP stream closed unexpectedly: %s in state %s\n",
+ lchan_fail("RTP stream closed unexpectedly: %s in state %s",
osmo_fsm_event_name(fi->fsm, event),
osmo_fsm_inst_state_name(fi));
return;
+ case LCHAN_EV_REQUEST_MODE_MODIFY:
+ modif_info = data;
+ lchan->modify.info = *modif_info;
+ lchan->modify.concluded = false;
+
+ use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);
+
+ lchan->modify.ch_mode_rate = lchan->modify.info.ch_mode_rate;
+ lchan->modify.ch_mode_rate.chan_mode = (lchan->modify.info.type_for == LCHAN_TYPE_FOR_VAMOS)
+ ? gsm48_chan_mode_to_vamos(lchan->modify.info.ch_mode_rate.chan_mode)
+ : gsm48_chan_mode_to_non_vamos(lchan->modify.info.ch_mode_rate.chan_mode);
+ if (lchan->modify.ch_mode_rate.chan_mode < 0) {
+ lchan_fail("Invalid chan_mode: %s", gsm48_chan_mode_name(lchan->modify.info.ch_mode_rate.chan_mode));
+ return;
+ }
+
+ if (gsm48_chan_mode_to_non_vamos(modif_info->ch_mode_rate.chan_mode) == GSM48_CMODE_SPEECH_AMR) {
+ if (lchan_mr_config(&lchan->modify.mr_conf_filtered, lchan, modif_info->ch_mode_rate.s15_s0)
+ < 0) {
+ lchan_fail("Can not generate multirate configuration IE");
+ return;
+ }
+ }
+
+ lchan->modify.ch_indctr = lchan->modify.info.ch_indctr;
+
+ /* If enabling VAMOS mode and no specific TSC Set was selected, make sure to select a sane TSC Set by
+ * default: Set 1 for the primary and Set 2 for the shadow lchan. For non-VAMOS lchans, TSC Set 1. */
+ if (lchan->modify.info.tsc_set.present)
+ lchan->modify.tsc_set = lchan->modify.info.tsc_set.val;
+ else
+ lchan->modify.tsc_set = lchan->vamos.is_secondary ? 2 : 1;
+
+ /* Use the TSC provided in the modification request, if any. Otherwise use the timeslot's configured
+ * TSC. */
+ lchan->modify.tsc = lchan->modify.info.tsc.present ? lchan->modify.info.tsc.val : gsm_ts_tsc(lchan->ts);
+
+ LOG_LCHAN(lchan, LOGL_INFO,
+ "Modification requested: %s rtp=%s MGW-ci=%s type=%s tch-mode=%s tsc=%d/%u\n",
+ lchan_modify_for_name(lchan->modify.info.modify_for),
+ requires_rtp_stream ? "yes" : "no",
+ requires_rtp_stream ?
+ (use_mgwep_ci ? osmo_mgcpc_ep_ci_name(use_mgwep_ci) : "new")
+ : "none",
+ gsm_chan_t_name(lchan->type),
+ gsm48_chan_mode_name(lchan->modify.ch_mode_rate.chan_mode),
+ lchan->modify.tsc_set, lchan->modify.tsc);
+
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK);
+ return;
+
default:
OSMO_ASSERT(false);
}
@@ -950,8 +1427,14 @@ static bool should_sacch_deact(struct gsm_lchan *lchan)
static void lchan_do_release(struct gsm_lchan *lchan)
{
- if (lchan->release.do_rr_release && lchan->sapis[0] != LCHAN_SAPI_UNUSED)
- gsm48_send_rr_release(lchan);
+ if (lchan->release.do_rr_release) {
+ /* To main DCCH in dedicated and group transmit mode */
+ if (lchan->sapis[0] != LCHAN_SAPI_UNUSED)
+ gsm48_send_rr_release(lchan, false);
+ /* As UI to all listeners in group receive mode */
+ if (lchan_is_asci(lchan))
+ gsm48_send_rr_release(lchan, true);
+ }
if (lchan->fi_rtp)
osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_RELEASE, 0);
@@ -1013,7 +1496,7 @@ static void lchan_fsm_wait_rll_rtp_released(struct osmo_fsm_inst *fi, uint32_t e
* TODO: that's how the code was before lchan FSM, is this correct/useful? */
handle_rll_rel_ind_or_conf(fi, event, data);
break;
-
+
case LCHAN_EV_RTP_RELEASED:
case LCHAN_EV_RTP_ERROR:
break;
@@ -1035,8 +1518,9 @@ static void lchan_fsm_wait_rf_release_ack_onenter(struct osmo_fsm_inst *fi, uint
* lchan_reset(), we make sure it does. But in case of releases from error handling, the
* conn might as well notice now already that its lchan is becoming unusable. */
if (lchan->conn) {
- gscon_forget_lchan(lchan->conn, lchan);
+ struct gsm_subscriber_connection *conn = lchan->conn;
lchan_forget_conn(lchan);
+ gscon_forget_lchan(conn, lchan);
}
rc = rsl_tx_rf_chan_release(lchan);
@@ -1061,6 +1545,13 @@ static void lchan_fsm_wait_rf_release_ack(struct osmo_fsm_inst *fi, uint32_t eve
/* ignore late lchan_rtp_fsm release events */
return;
+ case LCHAN_EV_RLL_REL_IND:
+ /* let's just ignore this. We are already logging the fact
+ * that this message was received inside abis_rsl.c. There can
+ * be any number of reasons why the radio link layer failed.
+ */
+ return;
+
default:
OSMO_ASSERT(false);
}
@@ -1069,45 +1560,75 @@ static void lchan_fsm_wait_rf_release_ack(struct osmo_fsm_inst *fi, uint32_t eve
static void lchan_fsm_borken_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ enum bts_counter_id ctr;
+ switch (prev_state) {
+ case LCHAN_ST_UNUSED:
+ ctr = BTS_CTR_LCHAN_BORKEN_FROM_UNUSED;
+ break;
+ case LCHAN_ST_WAIT_ACTIV_ACK:
+ ctr = BTS_CTR_LCHAN_BORKEN_FROM_WAIT_ACTIV_ACK;
+ break;
+ case LCHAN_ST_WAIT_RF_RELEASE_ACK:
+ ctr = BTS_CTR_LCHAN_BORKEN_FROM_WAIT_RF_RELEASE_ACK;
+ break;
+ case LCHAN_ST_BORKEN:
+ ctr = BTS_CTR_LCHAN_BORKEN_FROM_BORKEN;
+ break;
+ case LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK:
+ ctr = BTS_CTR_LCHAN_BORKEN_FROM_WAIT_RR_CHAN_MODE_MODIFY_ACK;
+ break;
+ case LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK:
+ ctr = BTS_CTR_LCHAN_BORKEN_FROM_WAIT_RSL_CHAN_MODE_MODIFY_ACK;
+ break;
+ default:
+ ctr = BTS_CTR_LCHAN_BORKEN_FROM_UNKNOWN;
+ }
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, ctr));
+ if (prev_state != LCHAN_ST_BORKEN)
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1);
+
+ /* The actual action besides all the beancounting above */
lchan_reset(lchan);
+ chan_counts_ts_update(lchan->ts);
}
static void lchan_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ struct gsm_bts *bts = lchan->ts->trx->bts;
switch (event) {
case LCHAN_EV_RSL_CHAN_ACTIV_ACK:
/* A late Chan Activ ACK? Release. */
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_ACK));
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1);
lchan->release.in_error = true;
lchan->release.rsl_error_cause = RSL_ERR_INTERWORKING;
+ lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(lchan->release.rsl_error_cause);
lchan_fsm_state_chg(LCHAN_ST_WAIT_RF_RELEASE_ACK);
return;
case LCHAN_EV_RSL_CHAN_ACTIV_NACK:
/* A late Chan Activ NACK? Ok then, unused. */
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_NACK));
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1);
lchan_fsm_state_chg(LCHAN_ST_UNUSED);
return;
case LCHAN_EV_RSL_RF_CHAN_REL_ACK:
/* A late Release ACK? */
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_RF_CHAN_REL_ACK));
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1);
lchan->release.in_error = true;
lchan->release.rsl_error_cause = RSL_ERR_INTERWORKING;
+ lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(lchan->release.rsl_error_cause);
lchan_fsm_state_chg(LCHAN_ST_WAIT_AFTER_ERROR);
- /* TODO: we used to do this only for sysmobts:
- int do_free = is_sysmobts_v2(ts->trx->bts);
- LOGP(DRSL, LOGL_NOTICE,
- "%s CHAN REL ACK for broken channel. %s.\n",
- gsm_lchan_name(lchan),
- do_free ? "Releasing it" : "Keeping it broken");
- if (do_free)
- do_lchan_free(lchan);
- * Clarify the reason. If a BTS sends a RF Chan Rel ACK, we can consider it released,
- * independently from the BTS model, right?? */
return;
case LCHAN_EV_RTP_RELEASED:
case LCHAN_EV_RTP_ERROR:
+ case LCHAN_EV_RLL_REL_IND:
return;
default:
@@ -1115,6 +1636,71 @@ static void lchan_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *dat
}
}
+static void lchan_fsm_recover_wait_activ_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ LOG_LCHAN(lchan, LOGL_INFO, "attempting to recover from BORKEN lchan\n");
+
+ lchan->type = GSM_LCHAN_SDCCH;
+ lchan->activate.info.ta_known = true;
+
+ chan_counts_ts_update(lchan->ts);
+
+ rc = rsl_tx_chan_activ(lchan, RSL_ACT_INTRA_NORM_ASS, 0);
+ if (rc)
+ lchan_fail("Tx Chan Activ failed: %s (%d)", strerror(-rc), rc);
+}
+
+static void lchan_fsm_recover_wait_activ_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ switch (event) {
+
+ case LCHAN_EV_RSL_CHAN_ACTIV_ACK:
+ lchan_fsm_state_chg(LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK);
+ break;
+
+ case LCHAN_EV_RSL_CHAN_ACTIV_NACK:
+ /* If an earlier lchan activ got through to the BTS, but the
+ * ACK did not get back to the BSC, it may still be active on
+ * the BTS side. Proceed to release it. */
+ LOG_LCHAN(lchan, LOGL_NOTICE, "received NACK for activation of BORKEN lchan, assuming still active\n");
+ lchan_fsm_state_chg(LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lchan_fsm_recover_wait_rf_release_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ int rc;
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
+ rc = rsl_tx_rf_chan_release(lchan);
+ if (rc)
+ lchan_fail("Tx RSL RF Channel Release failed: %s (%d)\n", strerror(-rc), rc);
+}
+
+static void lchan_fsm_recover_wait_rf_release_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ switch (event) {
+
+ case LCHAN_EV_RSL_RF_CHAN_REL_ACK:
+ LOG_LCHAN(lchan, LOGL_NOTICE, "successfully recovered BORKEN lchan\n");
+ lchan_fsm_state_chg(LCHAN_ST_UNUSED);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
#define S(x) (1 << (x))
static const struct osmo_fsm_state lchan_fsm_states[] = {
@@ -1128,10 +1714,12 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
.out_state_mask = 0
| S(LCHAN_ST_WAIT_TS_READY)
| S(LCHAN_ST_CBCH)
+ | S(LCHAN_ST_BORKEN)
,
},
[LCHAN_ST_CBCH] = {
.name = "CBCH",
+ .onenter = lchan_fsm_cbch_onenter,
.out_state_mask = 0
| S(LCHAN_ST_UNUSED)
,
@@ -1185,6 +1773,35 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
| S(LCHAN_ST_WAIT_RLL_RTP_RELEASED)
,
},
+ [LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK] = {
+ .name = "WAIT_CHAN_RR_MODE_MODIFY_ACK",
+ .onenter = lchan_fsm_wait_rr_chan_mode_modify_ack_onenter,
+ .action = lchan_fsm_wait_rr_chan_mode_modify_ack,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RR_CHAN_MODE_MODIFY_ACK)
+ | S(LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK)
+ | S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
+ | S(LCHAN_ST_BORKEN)
+ ,
+ },
+ [LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK] = {
+ .name = "WAIT_RSL_CHAN_MODE_MODIFY_ACK",
+ .onenter = lchan_fsm_wait_rsl_chan_mode_modify_ack_onenter,
+ .action = lchan_fsm_wait_rsl_chan_mode_modify_ack,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RSL_CHAN_MODE_MODIFY_ACK)
+ | S(LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_ESTABLISHED)
+ | S(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH)
+ | S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
+ | S(LCHAN_ST_BORKEN)
+ ,
+ },
[LCHAN_ST_ESTABLISHED] = {
.name = "ESTABLISHED",
.onenter = lchan_fsm_established_onenter,
@@ -1195,12 +1812,14 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
| S(LCHAN_EV_RLL_ESTABLISH_IND) /* ignored */
| S(LCHAN_EV_RTP_ERROR)
| S(LCHAN_EV_RTP_RELEASED)
+ | S(LCHAN_EV_REQUEST_MODE_MODIFY)
,
.out_state_mask = 0
| S(LCHAN_ST_UNUSED)
| S(LCHAN_ST_WAIT_RLL_RTP_RELEASED)
| S(LCHAN_ST_WAIT_BEFORE_RF_RELEASE)
| S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
+ | S(LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK)
,
},
[LCHAN_ST_WAIT_RLL_RTP_RELEASED] = {
@@ -1236,6 +1855,7 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
.action = lchan_fsm_wait_rf_release_ack,
.in_event_mask = 0
| S(LCHAN_EV_RSL_RF_CHAN_REL_ACK)
+ | S(LCHAN_EV_RLL_REL_IND) /* ignore late REL_IND of SAPI[0] */
| S(LCHAN_EV_RTP_RELEASED) /* ignore late lchan_rtp_fsm release events */
,
.out_state_mask = 0
@@ -1246,6 +1866,7 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
},
[LCHAN_ST_WAIT_AFTER_ERROR] = {
.name = "WAIT_AFTER_ERROR",
+ .onenter = lchan_fsm_wait_after_error_onenter,
.in_event_mask = 0
| S(LCHAN_EV_RTP_RELEASED) /* ignore late lchan_rtp_fsm release events */
,
@@ -1263,10 +1884,38 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
| S(LCHAN_EV_RSL_RF_CHAN_REL_ACK)
| S(LCHAN_EV_RTP_ERROR)
| S(LCHAN_EV_RTP_RELEASED)
+ | S(LCHAN_EV_RLL_REL_IND)
,
.out_state_mask = 0
+ | S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
| S(LCHAN_ST_UNUSED)
| S(LCHAN_ST_WAIT_AFTER_ERROR)
+ | S(LCHAN_ST_RECOVER_WAIT_ACTIV_ACK)
+ ,
+ },
+ [LCHAN_ST_RECOVER_WAIT_ACTIV_ACK] = {
+ .name = "RECOVER_WAIT_ACTIV_ACK",
+ .onenter = lchan_fsm_recover_wait_activ_ack_onenter,
+ .action = lchan_fsm_recover_wait_activ_ack,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RSL_CHAN_ACTIV_ACK)
+ | S(LCHAN_EV_RSL_CHAN_ACTIV_NACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_BORKEN)
+ | S(LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK)
+ ,
+ },
+ [LCHAN_ST_RECOVER_WAIT_RF_RELEASE_ACK] = {
+ .name = "RECOVER_WAIT_RF_RELEASE_ACK",
+ .onenter = lchan_fsm_recover_wait_rf_release_ack_onenter,
+ .action = lchan_fsm_recover_wait_rf_release_ack,
+ .in_event_mask = 0
+ | S(LCHAN_EV_RSL_RF_CHAN_REL_ACK)
+ ,
+ .out_state_mask = 0
+ | S(LCHAN_ST_BORKEN)
+ | S(LCHAN_ST_UNUSED)
,
},
};
@@ -1285,25 +1934,49 @@ static const struct value_string lchan_fsm_event_names[] = {
OSMO_VALUE_STRING(LCHAN_EV_RLL_REL_CONF),
OSMO_VALUE_STRING(LCHAN_EV_RSL_RF_CHAN_REL_ACK),
OSMO_VALUE_STRING(LCHAN_EV_RLL_ERR_IND),
- OSMO_VALUE_STRING(LCHAN_EV_CHAN_MODE_MODIF_ACK),
- OSMO_VALUE_STRING(LCHAN_EV_CHAN_MODE_MODIF_ERROR),
+ OSMO_VALUE_STRING(LCHAN_EV_RR_CHAN_MODE_MODIFY_ACK),
+ OSMO_VALUE_STRING(LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR),
+ OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_MODE_MODIFY_ACK),
+ OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK),
+ OSMO_VALUE_STRING(LCHAN_EV_REQUEST_MODE_MODIFY),
{}
};
-void lchan_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void lchan_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
case LCHAN_EV_TS_ERROR:
+ {
+ struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ if (fi->state == LCHAN_ST_BORKEN) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_TS_ERROR));
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(lchan->ts->trx->bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1);
+ }
lchan_fail_to(LCHAN_ST_UNUSED, "LCHAN_EV_TS_ERROR");
return;
+ }
+
+ case LCHAN_EV_RLL_ERR_IND:
+ /* let's just ignore this. We are already logging the
+ * fact that this message was received inside
+ * abis_rsl.c. There can be any number of reasons why the
+ * radio link layer failed */
+ return;
default:
return;
}
}
-int lchan_fsm_timer_cb(struct osmo_fsm_inst *fi)
+void lchan_fsm_skip_error(struct gsm_lchan *lchan)
+{
+ struct osmo_fsm_inst *fi = lchan->fi;
+ if (fi->state == LCHAN_ST_WAIT_AFTER_ERROR)
+ lchan_fsm_state_chg(LCHAN_ST_UNUSED);
+}
+
+static int lchan_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
switch (fi->state) {
@@ -1316,18 +1989,31 @@ int lchan_fsm_timer_cb(struct osmo_fsm_inst *fi)
lchan_fsm_state_chg(LCHAN_ST_UNUSED);
return 0;
+ case LCHAN_ST_BORKEN:
+ lchan_fsm_state_chg(LCHAN_ST_RECOVER_WAIT_ACTIV_ACK);
+ return 0;
+
default:
lchan->release.in_error = true;
lchan->release.rsl_error_cause = RSL_ERR_INTERWORKING;
- lchan_fail("Timeout");
+ lchan->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(lchan->release.rsl_error_cause);
+ if (fi->state == LCHAN_ST_WAIT_RLL_RTP_ESTABLISH) {
+ lchan_fail("Timeout (rll_ready=%s,rtp_require=%s,voice_ready=%s)",
+ (lchan->sapis[0] != LCHAN_SAPI_UNUSED) ? "yes" : "no",
+ bsc_chan_ind_requires_rtp_stream(lchan->activate.info.ch_indctr) ? "yes" : "no",
+ lchan_rtp_established(lchan) ? "yes" : "no");
+ } else {
+ lchan_fail("Timeout");
+ }
return 0;
}
}
void lchan_release(struct gsm_lchan *lchan, bool do_rr_release,
- bool err, enum gsm48_rr_cause cause_rr)
+ bool err, enum gsm48_rr_cause cause_rr,
+ const struct osmo_plmn_id *last_eutran_plmn)
{
- if (!lchan || !lchan->fi)
+ if (!lchan || !lchan->fi || lchan->fi->state == LCHAN_ST_UNUSED)
return;
if (lchan->release.in_release_handler)
@@ -1337,8 +2023,12 @@ void lchan_release(struct gsm_lchan *lchan, bool do_rr_release,
struct osmo_fsm_inst *fi = lchan->fi;
lchan->release.in_error = err;
- lchan->release.rsl_error_cause = cause_rr;
lchan->release.do_rr_release = do_rr_release;
+ lchan->release.rr_cause = cause_rr;
+ if (last_eutran_plmn) {
+ lchan->release.last_eutran_plmn_valid = true;
+ memcpy(&lchan->release.last_eutran_plmn, last_eutran_plmn, sizeof(*last_eutran_plmn));
+ }
/* States waiting for events will notice the desire to release when done waiting, so it is enough
* to mark for release. */
@@ -1377,9 +2067,13 @@ exit_release_handler:
lchan->release.in_release_handler = false;
}
-void lchan_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+static void lchan_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+ if (lchan->fi->state == LCHAN_ST_BORKEN) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(lchan->ts->trx->bts->bts_ctrs, BTS_CTR_LCHAN_BORKEN_EV_TEARDOWN));
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(lchan->ts->trx->bts->bts_statg, BTS_STAT_LCHAN_BORKEN), 1);
+ }
lchan_reset(lchan);
if (lchan->last_error) {
talloc_free(lchan->last_error);
@@ -1421,6 +2115,7 @@ static struct osmo_fsm lchan_fsm = {
.allstate_action = lchan_fsm_allstate_action,
.allstate_event_mask = 0
| S(LCHAN_EV_TS_ERROR)
+ | S(LCHAN_EV_RLL_ERR_IND)
,
.timer_cb = lchan_fsm_timer_cb,
.cleanup = lchan_fsm_cleanup,
diff --git a/src/osmo-bsc/lchan_rtp_fsm.c b/src/osmo-bsc/lchan_rtp_fsm.c
index 84cc28729..e8384c604 100644
--- a/src/osmo-bsc/lchan_rtp_fsm.c
+++ b/src/osmo-bsc/lchan_rtp_fsm.c
@@ -21,15 +21,17 @@
*/
#include <osmocom/core/fsm.h>
+#include <osmocom/netif/rtp.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/gsm_timers.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_rtp_fsm.h>
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/lchan.h>
static struct osmo_fsm lchan_rtp_fsm;
@@ -41,34 +43,34 @@ struct gsm_lchan *lchan_rtp_fi_lchan(struct osmo_fsm_inst *fi)
return fi->priv;
}
-struct state_timeout lchan_rtp_fsm_timeouts[32] = {
- [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = { .T=23004 },
- [LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK] = { .T=23005 },
- [LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK] = { .T=23006 },
- [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = { .T=23004 },
+struct osmo_tdef_state_timeout lchan_rtp_fsm_timeouts[32] = {
+ [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = { .T = -9 },
+ [LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK] = { .T = -7 },
+ [LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK] = { .T = -8 },
+ [LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = { .T = -10 },
};
/* Transition to a state, using the T timer defined in lchan_rtp_fsm_timeouts.
* The actual timeout value is in turn obtained from network->T_defs.
* Assumes local variable fi exists. */
#define lchan_rtp_fsm_state_chg(state) \
- fsm_inst_state_chg_T(fi, state, \
- lchan_rtp_fsm_timeouts, \
- ((struct gsm_lchan*)(fi->priv))->ts->trx->bts->network->T_defs, \
- 5)
+ osmo_tdef_fsm_inst_state_chg(fi, state, \
+ lchan_rtp_fsm_timeouts, \
+ ((struct gsm_lchan*)(fi->priv))->ts->trx->bts->network->T_defs, \
+ 5)
/* Set a failure message, trigger the common actions to take on failure, transition to a state to
* continue with (using state timeouts from lchan_rtp_fsm_timeouts[]). Assumes local variable fi exists. */
#define lchan_rtp_fail(fmt, args...) do { \
struct gsm_lchan *_lchan = fi->priv; \
uint32_t state_was = fi->state; \
- lchan_set_last_error(_lchan, "lchan-rtp failure in state %s: " fmt, \
+ LCHAN_SET_LAST_ERROR(_lchan, "lchan-rtp failure in state %s: " fmt, \
osmo_fsm_state_name(fi->fsm, state_was), ## args); \
osmo_fsm_inst_dispatch(_lchan->fi, LCHAN_EV_RTP_ERROR, 0); \
- } while(0)
+ } while (0)
/* Called from lchan_fsm_init(), does not need to be visible in lchan_rtp_fsm.h */
-void lchan_rtp_fsm_init()
+static __attribute__((constructor)) void lchan_rtp_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&lchan_rtp_fsm) == 0);
}
@@ -121,7 +123,7 @@ void lchan_rtp_fsm_start(struct gsm_lchan *lchan)
/* While activating an lchan, for example for Handover, we may want to re-use another lchan's MGW
* endpoint CI. If Handover fails half way, the old lchan must keep its MGW endpoint CI, and we must not
* clean it up. Hence keep another lchan's mgw_endpoint_ci_bts out of lchan until all is done. */
-struct mgwep_ci *lchan_use_mgw_endpoint_ci_bts(struct gsm_lchan *lchan)
+struct osmo_mgcpc_ep_ci *lchan_use_mgw_endpoint_ci_bts(struct gsm_lchan *lchan)
{
if (lchan->mgw_endpoint_ci_bts)
return lchan->mgw_endpoint_ci_bts;
@@ -136,34 +138,63 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_available_onenter(struct osmo_fsm_in
uint32_t prev_state)
{
struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
- struct mgw_endpoint *mgwep;
- struct mgwep_ci *use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);
- struct mgcp_conn_peer crcx_info = {};
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ struct osmo_mgcpc_ep *mgwep;
+ struct osmo_mgcpc_ep_ci *use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);
+ struct mgcp_conn_peer crcx_info;
+
+ if (!is_ipa_abisip_bts(lchan->ts->trx->bts)) {
+ LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "Audio link to-BTS via E1, skipping IPACC\n");
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_LCHAN_READY);
+ return;
+ }
if (use_mgwep_ci) {
LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "MGW endpoint already available: %s\n",
- mgwep_ci_name(use_mgwep_ci));
+ osmo_mgcpc_ep_ci_name(use_mgwep_ci));
lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_LCHAN_READY);
return;
}
- mgwep = gscon_ensure_mgw_endpoint(lchan->conn, lchan->activate.info.msc_assigned_cic);
+ mgwep = gscon_ensure_mgw_endpoint(lchan->conn, lchan->activate.info.msc_assigned_cic, lchan);
if (!mgwep) {
lchan_rtp_fail("Internal error: cannot obtain MGW endpoint handle for conn");
return;
}
- lchan->mgw_endpoint_ci_bts = mgw_endpoint_ci_add(mgwep, "to-BTS");
+ lchan->mgw_endpoint_ci_bts = osmo_mgcpc_ep_ci_add(mgwep, "to-BTS");
+ crcx_info = (struct mgcp_conn_peer){
+ .ptime = 20,
+ .x_osmo_osmux_cid = -1, /* -1 is wildcard, .x_osmo_osmux_use set below */
+ };
if (lchan->conn) {
- crcx_info.call_id = lchan->conn->sccp.conn_id;
+ crcx_info.call_id = lchan->conn->sccp.conn.conn_id;
if (lchan->conn->sccp.msc)
crcx_info.x_osmo_ign = lchan->conn->sccp.msc->x_osmo_ign;
}
- crcx_info.ptime = 20;
mgcp_pick_codec(&crcx_info, lchan, true);
- mgw_endpoint_ci_request(lchan->mgw_endpoint_ci_bts, MGCP_VERB_CRCX, &crcx_info,
+ /* Set up Osmux use in MGW according to configured policy */
+ bool amr_picked = mgcp_codec_is_picked(&crcx_info, CODEC_AMR_8000_1);
+ switch (bts->use_osmux) {
+ case OSMUX_USAGE_OFF:
+ crcx_info.x_osmo_osmux_use = false;
+ break;
+ case OSMUX_USAGE_ON:
+ crcx_info.x_osmo_osmux_use = amr_picked;
+ break;
+ case OSMUX_USAGE_ONLY:
+ if (!amr_picked) {
+ lchan_rtp_fail("Only AMR codec can be used when configured with policy 'osmux only'."
+ " Check your configuration.");
+ return;
+ }
+ crcx_info.x_osmo_osmux_use = true;
+ break;
+ }
+
+ osmo_mgcpc_ep_ci_request(lchan->mgw_endpoint_ci_bts, MGCP_VERB_CRCX, &crcx_info,
fi, LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE, LCHAN_RTP_EV_MGW_ENDPOINT_ERROR,
0);
}
@@ -171,11 +202,26 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_available_onenter(struct osmo_fsm_in
static void lchan_rtp_fsm_wait_mgw_endpoint_available(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ struct gsm_bts *bts = lchan->ts->trx->bts;
switch (event) {
case LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE:
LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "MGW endpoint: %s\n",
- mgwep_ci_name(lchan_use_mgw_endpoint_ci_bts(lchan)));
+ osmo_mgcpc_ep_ci_name(lchan_use_mgw_endpoint_ci_bts(lchan)));
+ if (osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(lchan->mgw_endpoint_ci_bts,
+ &lchan->abis_ip.osmux.local_cid)) {
+ if (bts->use_osmux == OSMUX_USAGE_OFF) {
+ lchan_rtp_fail("Got Osmux CID from MGW but we didn't ask for it");
+ return;
+ }
+ lchan->abis_ip.osmux.use = true;
+ } else {
+ if (bts->use_osmux == OSMUX_USAGE_ONLY) {
+ lchan_rtp_fail("Got no Osmux CID from MGW but Osmux is mandatory");
+ return;
+ }
+ lchan->abis_ip.osmux.use = false;
+ }
lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_LCHAN_READY);
return;
@@ -244,10 +290,10 @@ static void lchan_rtp_fsm_post_lchan_ready(struct osmo_fsm_inst *fi)
{
struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
- if (is_ipaccess_bts(lchan->ts->trx->bts))
+ if (is_ipa_abisip_bts(lchan->ts->trx->bts))
lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK);
else
- lchan_rtp_fsm_switch_rtp(fi);
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED);
}
static void lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
@@ -261,20 +307,41 @@ static void lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter(struct osmo_fsm_inst *fi,
return;
}
- val = ipacc_speech_mode(lchan->tch_mode, lchan->type);
- if (val < 0) {
- lchan_rtp_fail("Cannot determine Abis/IP speech mode for tch_mode=%s type=%s\n",
- get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
- gsm_lchant_name(lchan->type));
- return;
+ if (lchan->current_ch_indctr == GSM0808_CHAN_DATA) {
+ enum rsl_ipac_rtp_csd_format_d format_d = RSL_IPAC_RTP_CSD_TRAU_BTS;
+
+ if (lchan->activate.ch_mode_rate.data_transparent) {
+ val = ipacc_rtp_csd_fmt_transp(&lchan->activate.ch_mode_rate, format_d);
+ if (val < 0) {
+ lchan_rtp_fail("Cannot determine Abis/IP RTP CSD format for rsl_cmod_csd_t=%d",
+ lchan->activate.ch_mode_rate.data_rate.t);
+ return;
+ }
+ } else {
+ val = ipacc_rtp_csd_fmt_non_transp(&lchan->activate.ch_mode_rate, format_d);
+ if (val < 0) {
+ lchan_rtp_fail("Cannot determine Abis/IP RTP CSD format for rsl_cmod_csd_nt=%d",
+ lchan->activate.ch_mode_rate.data_rate.nt);
+ return;
+ }
+ }
+ lchan->abis_ip.rtp_csd_fmt = val;
+ } else {
+ val = ipacc_speech_mode(lchan->activate.ch_mode_rate.chan_mode, lchan->type);
+ if (val < 0) {
+ lchan_rtp_fail("Cannot determine Abis/IP speech mode for tch_mode=%s type=%s",
+ get_value_string(gsm48_chan_mode_names, lchan->activate.ch_mode_rate.chan_mode),
+ gsm_chan_t_name(lchan->type));
+ return;
+ }
+ lchan->abis_ip.speech_mode = val;
}
- lchan->abis_ip.speech_mode = val;
- val = ipacc_payload_type(lchan->tch_mode, lchan->type);
+ val = ipacc_payload_type(lchan->activate.ch_mode_rate.chan_mode, lchan->type);
if (val < 0) {
- lchan_rtp_fail("Cannot determine Abis/IP payload type for tch_mode=%s type=%s\n",
- get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
- gsm_lchant_name(lchan->type));
+ lchan_rtp_fail("Cannot determine Abis/IP payload type for tch_mode=%s type=%s",
+ get_value_string(gsm48_chan_mode_names, lchan->activate.ch_mode_rate.chan_mode),
+ gsm_chan_t_name(lchan->type));
return;
}
lchan->abis_ip.rtp_payload = val;
@@ -294,10 +361,7 @@ static void lchan_rtp_fsm_wait_ipacc_crcx_ack(struct osmo_fsm_inst *fi, uint32_t
switch (event) {
case LCHAN_RTP_EV_IPACC_CRCX_ACK:
- /* the CRCX ACK parsing has already noted the RTP port information at
- * lchan->abis_ip.bound_*, see ipac_parse_rtp(). We'll use that in
- * lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter(). */
- lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK);
+ lchan_rtp_fsm_switch_rtp(fi);
return;
case LCHAN_RTP_EV_IPACC_CRCX_NACK:
@@ -323,13 +387,14 @@ static void lchan_rtp_fsm_wait_ipacc_mdcx_ack_onenter(struct osmo_fsm_inst *fi,
int rc;
struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
const struct mgcp_conn_peer *mgw_rtp;
+ struct in_addr sin;
if (lchan->release.requested) {
lchan_rtp_fail("Release requested while activating");
return;
}
- mgw_rtp = mgwep_ci_get_rtp_info(lchan_use_mgw_endpoint_ci_bts(lchan));
+ mgw_rtp = osmo_mgcpc_ep_ci_get_rtp_info(lchan_use_mgw_endpoint_ci_bts(lchan));
if (!mgw_rtp) {
lchan_rtp_fail("Cannot send IPACC MDCX to BTS:"
@@ -337,8 +402,13 @@ static void lchan_rtp_fsm_wait_ipacc_mdcx_ack_onenter(struct osmo_fsm_inst *fi,
return;
}
- /* Other RTP settings were already setup in lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter() */
- lchan->abis_ip.connect_ip = ntohl(inet_addr(mgw_rtp->addr));
+ /* Other RTP settings were already set up in lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter() */
+ if (inet_pton(AF_INET, mgw_rtp->addr, &sin) != 1) {
+ /* Only IPv4 addresses are supported in IPACC */
+ lchan_rtp_fail("Invalid remote IPv4 address %s", mgw_rtp->addr);
+ return;
+ }
+ lchan->abis_ip.connect_ip = ntohl(sin.s_addr);
lchan->abis_ip.connect_port = mgw_rtp->port;
/* send-recv */
@@ -353,21 +423,16 @@ static void lchan_rtp_fsm_wait_ipacc_mdcx_ack_onenter(struct osmo_fsm_inst *fi,
static void lchan_rtp_fsm_wait_ipacc_mdcx_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
- struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
switch (event) {
case LCHAN_RTP_EV_IPACC_MDCX_ACK:
- lchan_rtp_fsm_switch_rtp(fi);
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_READY);
return;
case LCHAN_RTP_EV_IPACC_MDCX_NACK:
lchan_rtp_fail("Received NACK on IPACC MDCX");
return;
- case LCHAN_RTP_EV_READY_TO_SWITCH_RTP:
- lchan->activate.info.wait_before_switching_rtp = false;
- return;
-
case LCHAN_RTP_EV_RELEASE:
case LCHAN_RTP_EV_ROLLBACK:
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
@@ -397,7 +462,7 @@ static void lchan_rtp_fsm_wait_ready_to_switch_rtp(struct osmo_fsm_inst *fi, uin
}
static void connect_mgw_endpoint_to_lchan(struct osmo_fsm_inst *fi,
- struct mgwep_ci *ci,
+ struct osmo_mgcpc_ep_ci *ci,
struct gsm_lchan *to_lchan)
{
int rc;
@@ -406,9 +471,16 @@ static void connect_mgw_endpoint_to_lchan(struct osmo_fsm_inst *fi,
struct in_addr addr;
const char *addr_str;
+ if (lchan->abis_ip.osmux.use && !lchan->abis_ip.osmux.remote_cid_present) {
+ lchan_rtp_fail("BTS didn't provide any remote Osmux CID for the call");
+ return;
+ }
+
mdcx_info = (struct mgcp_conn_peer){
.port = to_lchan->abis_ip.bound_port,
.ptime = 20,
+ .x_osmo_osmux_use = lchan->abis_ip.osmux.use,
+ .x_osmo_osmux_cid = lchan->abis_ip.osmux.remote_cid,
};
mgcp_pick_codec(&mdcx_info, to_lchan, true);
@@ -427,8 +499,8 @@ static void connect_mgw_endpoint_to_lchan(struct osmo_fsm_inst *fi,
}
LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "Sending BTS side RTP port info %s:%u to MGW %s\n",
- mdcx_info.addr, mdcx_info.port, mgwep_ci_name(ci));
- mgw_endpoint_ci_request(ci, MGCP_VERB_MDCX, &mdcx_info,
+ mdcx_info.addr, mdcx_info.port, osmo_mgcpc_ep_ci_name(ci));
+ osmo_mgcpc_ep_ci_request(ci, MGCP_VERB_MDCX, &mdcx_info,
fi, LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED,
LCHAN_RTP_EV_MGW_ENDPOINT_ERROR, 0);
}
@@ -444,9 +516,15 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter(struct osmo_fsm_i
return;
}
+ if (!is_ipa_abisip_bts(lchan->ts->trx->bts)) {
+ LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "Audio link to-BTS via E1, skipping IPACC\n");
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_READY);
+ return;
+ }
+
/* At this point, we are taking over an old lchan's MGW endpoint (if any). */
if (!lchan->mgw_endpoint_ci_bts && old_lchan) {
- /* The old lchan shall forget the enpoint now. We might put it back upon ROLLBACK */
+ /* The old lchan shall forget the endpoint now. We might put it back upon ROLLBACK */
lchan->mgw_endpoint_ci_bts = old_lchan->mgw_endpoint_ci_bts;
old_lchan->mgw_endpoint_ci_bts = NULL;
}
@@ -461,10 +539,15 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter(struct osmo_fsm_i
static void lchan_rtp_fsm_wait_mgw_endpoint_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
- switch (event) {
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+ switch (event) {
case LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED:
- lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_READY);
+ if (is_ipa_abisip_bts(lchan->ts->trx->bts))
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK);
+ else {
+ lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_READY);
+ }
return;
case LCHAN_RTP_EV_MGW_ENDPOINT_ERROR:
@@ -506,6 +589,12 @@ static void lchan_rtp_fsm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *
lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_ROLLBACK);
return;
+ case LCHAN_RTP_EV_READY_TO_SWITCH_RTP:
+ /* Ignore / silence an "event not permitted" error. In case of an inter-BSC incoming handover, there is
+ * no previous lchan to be switched over, and we are already in this state when the usual handover code
+ * path emits this event. */
+ return;
+
default:
OSMO_ASSERT(false);
}
@@ -520,7 +609,11 @@ static void lchan_rtp_fsm_rollback_onenter(struct osmo_fsm_inst *fi, uint32_t pr
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
return;
}
- connect_mgw_endpoint_to_lchan(fi, lchan->mgw_endpoint_ci_bts, old_lchan);
+
+ if (is_ipa_abisip_bts(lchan->ts->trx->bts))
+ connect_mgw_endpoint_to_lchan(fi, lchan->mgw_endpoint_ci_bts, old_lchan);
+ else
+ osmo_fsm_inst_dispatch(fi, LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED, 0);
}
static void lchan_rtp_fsm_rollback(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -540,7 +633,7 @@ static void lchan_rtp_fsm_rollback(struct osmo_fsm_inst *fi, uint32_t event, voi
LOG_LCHAN_RTP(lchan, LOGL_ERROR,
"Error while connecting the MGW back to the old lchan's RTP port:"
" %s %s\n",
- mgwep_ci_name(lchan->mgw_endpoint_ci_bts),
+ osmo_mgcpc_ep_ci_name(lchan->mgw_endpoint_ci_bts),
gsm_lchan_name(old_lchan));
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, 0);
return;
@@ -565,13 +658,19 @@ static void lchan_rtp_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t
static void lchan_rtp_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
+ struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
switch (event) {
case LCHAN_RTP_EV_RELEASE:
case LCHAN_RTP_EV_ROLLBACK:
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
return;
-
+ case LCHAN_RTP_EV_IPACC_MDCX_ACK:
+ LOG_LCHAN_RTP(lchan, LOGL_NOTICE,
+ "Received MDCX ACK on established lchan's RTP port: %s\n",
+ osmo_mgcpc_ep_ci_name(lchan->mgw_endpoint_ci_bts));
+ return;
default:
OSMO_ASSERT(false);
}
@@ -623,23 +722,8 @@ static const struct osmo_fsm_state lchan_rtp_fsm_states[] = {
| S(LCHAN_RTP_EV_ROLLBACK)
,
.out_state_mask = 0
- | S(LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK)
- ,
- },
- [LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK] = {
- .name = "WAIT_IPACC_MDCX_ACK",
- .onenter = lchan_rtp_fsm_wait_ipacc_mdcx_ack_onenter,
- .action = lchan_rtp_fsm_wait_ipacc_mdcx_ack,
- .in_event_mask = 0
- | S(LCHAN_RTP_EV_READY_TO_SWITCH_RTP)
- | S(LCHAN_RTP_EV_IPACC_MDCX_ACK)
- | S(LCHAN_RTP_EV_IPACC_MDCX_NACK)
- | S(LCHAN_RTP_EV_RELEASE)
- | S(LCHAN_RTP_EV_ROLLBACK)
- ,
- .out_state_mask = 0
| S(LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP)
- | S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED)
+ | S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED) /*old: LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK*/
,
},
[LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP] = {
@@ -665,7 +749,24 @@ static const struct osmo_fsm_state lchan_rtp_fsm_states[] = {
| S(LCHAN_RTP_EV_ROLLBACK)
,
.out_state_mask = 0
+ | S(LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK)
+ | S(LCHAN_RTP_ST_READY)
+ | S(LCHAN_RTP_ST_ROLLBACK)
+ ,
+ },
+ [LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK] = {
+ .name = "WAIT_IPACC_MDCX_ACK",
+ .onenter = lchan_rtp_fsm_wait_ipacc_mdcx_ack_onenter,
+ .action = lchan_rtp_fsm_wait_ipacc_mdcx_ack,
+ .in_event_mask = 0
+ | S(LCHAN_RTP_EV_IPACC_MDCX_ACK)
+ | S(LCHAN_RTP_EV_IPACC_MDCX_NACK)
+ | S(LCHAN_RTP_EV_RELEASE)
+ | S(LCHAN_RTP_EV_ROLLBACK)
+ ,
+ .out_state_mask = 0
| S(LCHAN_RTP_ST_READY)
+ | S(LCHAN_RTP_ST_ROLLBACK)
,
},
[LCHAN_RTP_ST_READY] = {
@@ -676,6 +777,7 @@ static const struct osmo_fsm_state lchan_rtp_fsm_states[] = {
| S(LCHAN_RTP_EV_ESTABLISHED)
| S(LCHAN_RTP_EV_RELEASE)
| S(LCHAN_RTP_EV_ROLLBACK)
+ | S(LCHAN_RTP_EV_READY_TO_SWITCH_RTP)
,
.out_state_mask = 0
| S(LCHAN_RTP_ST_ESTABLISHED)
@@ -689,6 +791,7 @@ static const struct osmo_fsm_state lchan_rtp_fsm_states[] = {
.in_event_mask = 0
| S(LCHAN_RTP_EV_RELEASE)
| S(LCHAN_RTP_EV_ROLLBACK)
+ | S(LCHAN_RTP_EV_IPACC_MDCX_ACK)
,
},
[LCHAN_RTP_ST_ROLLBACK] = {
@@ -721,7 +824,7 @@ static const struct value_string lchan_rtp_fsm_event_names[] = {
{}
};
-int lchan_rtp_fsm_timer_cb(struct osmo_fsm_inst *fi)
+static int lchan_rtp_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
lchan->release.in_error = true;
@@ -730,11 +833,11 @@ int lchan_rtp_fsm_timer_cb(struct osmo_fsm_inst *fi)
return 0;
}
-void lchan_rtp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+static void lchan_rtp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
if (lchan->mgw_endpoint_ci_bts) {
- mgw_endpoint_ci_dlcx(lchan->mgw_endpoint_ci_bts);
+ osmo_mgcpc_ep_ci_dlcx(lchan->mgw_endpoint_ci_bts);
lchan->mgw_endpoint_ci_bts = NULL;
}
lchan->fi_rtp = NULL;
@@ -763,3 +866,97 @@ static struct osmo_fsm lchan_rtp_fsm = {
.timer_cb = lchan_rtp_fsm_timer_cb,
.cleanup = lchan_rtp_fsm_cleanup,
};
+
+/* Depending on the channel mode and rate, return the codec type that is signalled towards the MGW. */
+static enum mgcp_codecs chan_mode_to_mgcp_codec(enum gsm48_chan_mode chan_mode, bool full_rate)
+{
+ switch (gsm48_chan_mode_to_non_vamos(chan_mode)) {
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ return CODEC_CLEARMODE;
+
+ case GSM48_CMODE_SPEECH_V1:
+ if (full_rate)
+ return CODEC_GSM_8000_1;
+ return CODEC_GSMHR_8000_1;
+
+ case GSM48_CMODE_SPEECH_EFR:
+ return CODEC_GSMEFR_8000_1;
+
+ case GSM48_CMODE_SPEECH_AMR:
+ return CODEC_AMR_8000_1;
+
+ default:
+ return -1;
+ }
+}
+
+static int chan_mode_to_mgcp_bss_pt(enum mgcp_codecs codec)
+{
+ switch (codec) {
+ case CODEC_GSMHR_8000_1:
+ return RTP_PT_GSM_HALF;
+
+ case CODEC_GSMEFR_8000_1:
+ return RTP_PT_GSM_EFR;
+
+ case CODEC_AMR_8000_1:
+ return RTP_PT_AMR;
+
+ default:
+ /* Not an error, we just leave it to libosmo-mgcp-client to
+ * decide over the PT. */
+ return -1;
+ }
+}
+
+void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *lchan, bool bss_side)
+{
+ enum mgcp_codecs codec = chan_mode_to_mgcp_codec(lchan->activate.ch_mode_rate.chan_mode,
+ lchan->type == GSM_LCHAN_TCH_H? false : true);
+ int custom_pt;
+
+ if (codec < 0) {
+ LOG_LCHAN(lchan, LOGL_ERROR,
+ "Unable to determine MGCP codec type for %s in chan-mode %s\n",
+ gsm_chan_t_name(lchan->type), gsm48_chan_mode_name(lchan->activate.ch_mode_rate.chan_mode));
+ verb_info->codecs_len = 0;
+ return;
+ }
+
+ verb_info->codecs[0] = codec;
+ verb_info->codecs_len = 1;
+
+ /* Setup custom payload types (only for BSS side and when required) */
+ custom_pt = chan_mode_to_mgcp_bss_pt(codec);
+ if (bss_side && custom_pt > 0) {
+ verb_info->ptmap[0].codec = codec;
+ verb_info->ptmap[0].pt = custom_pt;
+ verb_info->ptmap_len = 1;
+ }
+
+ /* AMR requires additional parameters to be set up (framing mode) */
+ if (verb_info->codecs[0] == CODEC_AMR_8000_1) {
+ verb_info->param_present = true;
+ verb_info->param.amr_octet_aligned_present = true;
+ }
+
+ if (bss_side && verb_info->codecs[0] == CODEC_AMR_8000_1) {
+ /* FIXME: At the moment all BTSs we support are using the
+ * octet-aligned payload format. However, in the future
+ * we may support BTSs that are using bandwidth-efficient
+ * format. In this case we will have to add functionality
+ * that distinguishes by the BTS model which mode to use. */
+ verb_info->param.amr_octet_aligned = true;
+ }
+ else if (!bss_side && verb_info->codecs[0] == CODEC_AMR_8000_1) {
+ verb_info->param.amr_octet_aligned = lchan->conn->sccp.msc->amr_octet_aligned;
+ }
+}
+
+bool mgcp_codec_is_picked(const struct mgcp_conn_peer *verb_info, enum mgcp_codecs codec)
+{
+ return verb_info->codecs[0] == codec;
+}
diff --git a/src/osmo-bsc/lchan_select.c b/src/osmo-bsc/lchan_select.c
index f70ad4a6e..c830bd1ee 100644
--- a/src/osmo-bsc/lchan_select.c
+++ b/src/osmo-bsc/lchan_select.c
@@ -2,7 +2,7 @@
*
* (C) 2008 by Harald Welte <laforge@gnumonks.org>
* (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * (C) 2018-2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
@@ -21,6 +21,8 @@
*
*/
+#include <stdlib.h>
+
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
@@ -28,139 +30,342 @@
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/bts.h>
+
+struct lchan_select_ts_list {
+ struct gsm_bts_trx_ts **list;
+ unsigned int num;
+};
-static struct gsm_lchan *
-_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan,
- enum gsm_phys_chan_config as_pchan)
+const struct value_string lchan_select_reason_names[] = {
+ OSMO_VALUE_STRING(SELECT_FOR_MS_CHAN_REQ),
+ OSMO_VALUE_STRING(SELECT_FOR_ASSIGNMENT),
+ OSMO_VALUE_STRING(SELECT_FOR_HANDOVER),
+ OSMO_VALUE_STRING(SELECT_FOR_VGCS),
+ {0, NULL}
+};
+
+static struct gsm_lchan *pick_better_lchan(struct gsm_lchan *a, struct gsm_lchan *b)
+{
+ if (!a)
+ return b;
+ if (!b)
+ return a;
+ /* comparing negative dBm values: smaller value means less interference. */
+ if (b->interf_dbm < a->interf_dbm)
+ return b;
+ return a;
+}
+
+static struct gsm_lchan *_lc_find(struct lchan_select_ts_list *ts_list,
+ enum gsm_phys_chan_config pchan,
+ enum gsm_phys_chan_config as_pchan,
+ bool allow_pchan_switch, bool log)
{
struct gsm_lchan *lchan;
- struct gsm_bts_trx_ts *ts;
- int j, start, stop, dir;
+ struct gsm_lchan *found_lchan = NULL;
-#define LOGPLCHANALLOC(fmt, args...) \
- LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s%s%s: " fmt, \
+#define LOGPLCHANALLOC(fmt, args...) do { \
+ if (log) \
+ LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s%s%s%s: " fmt, \
gsm_pchan_name(pchan), \
pchan == as_pchan ? "" : " as ", \
- pchan == as_pchan ? "" : gsm_pchan_name(as_pchan), ## args)
+ pchan == as_pchan ? "" : gsm_pchan_name(as_pchan), \
+ ((pchan != as_pchan) && !allow_pchan_switch) ? " without pchan switch" : "", \
+ ## args); \
+ } while (0)
- if (!trx_is_usable(trx)) {
- LOGPLCHANALLOC("%s trx not usable\n", gsm_trx_name(trx));
- return NULL;
- }
-
- if (trx->bts->chan_alloc_reverse) {
- /* check TS 7..0 */
- start = 7;
- stop = -1;
- dir = -1;
- } else {
- /* check TS 0..7 */
- start = 0;
- stop = 8;
- dir = 1;
- }
+ for (unsigned int tn = 0; tn < ts_list->num; tn++) {
+ struct gsm_bts_trx_ts *ts = ts_list->list[tn];
+ int lchans_as_pchan;
- for (j = start; j != stop; j += dir) {
- ts = &trx->ts[j];
- if (!ts_is_usable(ts))
- continue;
/* The caller first selects what kind of TS to search in, e.g. looking for exact
- * GSM_PCHAN_TCH_F, or maybe among dynamic GSM_PCHAN_TCH_F_TCH_H_PDCH... */
+ * GSM_PCHAN_TCH_F, or maybe among dynamic GSM_PCHAN_OSMO_DYN... */
if (ts->pchan_on_init != pchan) {
LOGPLCHANALLOC("%s is != %s\n", gsm_ts_and_pchan_name(ts),
gsm_pchan_name(pchan));
continue;
}
/* Next, is this timeslot in or can it be switched to the pchan we want to use it for? */
- if (!ts_usable_as_pchan(ts, as_pchan)) {
- LOGPLCHANALLOC("%s is not usable as %s\n", gsm_ts_and_pchan_name(ts),
- gsm_pchan_name(as_pchan));
+ if (!ts_usable_as_pchan(ts, as_pchan, allow_pchan_switch)) {
+ LOGPLCHANALLOC("%s is not usable as %s%s\n", gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(as_pchan),
+ allow_pchan_switch ? "" : " without pchan switch");
continue;
}
/* TS is (going to be) in desired pchan mode. Go ahead and check for an available lchan. */
- ts_as_pchan_for_each_lchan(lchan, ts, as_pchan) {
- if (lchan->fi->state == LCHAN_ST_UNUSED) {
- LOGPLCHANALLOC("%s ss=%d is available%s\n",
+ lchans_as_pchan = pchan_subslots(as_pchan);
+ ts_for_n_lchans(lchan, ts, lchans_as_pchan) {
+ struct gsm_lchan *was = found_lchan;
+
+ if (lchan->fi->state != LCHAN_ST_UNUSED) {
+ LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n",
gsm_ts_and_pchan_name(ts), lchan->nr,
- ts->pchan_is != as_pchan ? " after dyn PCHAN change" : "");
- return lchan;
+ gsm_chan_t_name(lchan->type),
+ osmo_fsm_inst_state_name(lchan->fi));
+ continue;
}
- LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n",
- gsm_ts_and_pchan_name(ts), lchan->nr,
- gsm_lchant_name(lchan->type),
- osmo_fsm_inst_state_name(lchan->fi));
+
+ found_lchan = pick_better_lchan(found_lchan, lchan);
+ if (found_lchan != was)
+ LOGPLCHANALLOC("%s ss=%d interf=%u=%ddBm is %s%s\n",
+ gsm_ts_and_pchan_name(ts), lchan->nr,
+ lchan->interf_band, lchan->interf_dbm,
+ was == NULL ? "available" : "better",
+ ts->pchan_is != as_pchan ? ", after dyn PCHAN change" : "");
+ else
+ LOGPLCHANALLOC("%s ss=%d interf=%u=%ddBm is also available but not better\n",
+ gsm_ts_and_pchan_name(ts), lchan->nr,
+ lchan->interf_band, lchan->interf_dbm);
+
+ /* When picking an lchan with least interference, continue to loop across all lchans. When
+ * ignoring interference levels, return the first match. */
+ if (found_lchan && !ts->trx->bts->chan_alloc_avoid_interf)
+ return found_lchan;
}
}
- return NULL;
+ if (found_lchan)
+ LOGPLCHANALLOC("%s ss=%d interf=%ddBm%s is the best pick\n",
+ gsm_ts_and_pchan_name(found_lchan->ts), found_lchan->nr,
+ found_lchan->interf_dbm,
+ found_lchan->ts->pchan_is != as_pchan ? ", after dyn PCHAN change," : "");
+ else
+ LOGPLCHANALLOC("Nothing found\n");
+ return found_lchan;
#undef LOGPLCHANALLOC
}
-static struct gsm_lchan *
-_lc_dyn_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan,
- enum gsm_phys_chan_config dyn_as_pchan)
+static struct gsm_lchan *lc_dyn_find(struct lchan_select_ts_list *ts_list,
+ enum gsm_phys_chan_config pchan,
+ enum gsm_phys_chan_config dyn_as_pchan,
+ bool log)
{
- struct gsm_bts_trx *trx;
- struct gsm_lchan *lc;
+ struct gsm_lchan *lchan;
- if (bts->chan_alloc_reverse) {
- llist_for_each_entry_reverse(trx, &bts->trx_list, list) {
- lc = _lc_find_trx(trx, pchan, dyn_as_pchan);
- if (lc)
- return lc;
- }
- } else {
- llist_for_each_entry(trx, &bts->trx_list, list) {
- lc = _lc_find_trx(trx, pchan, dyn_as_pchan);
- if (lc)
- return lc;
- }
- }
+ /* First find an lchan that needs no change in its timeslot pchan mode.
+ * In particular, this ensures that handover to a dynamic timeslot in TCH/H favors timeslots that are currently
+ * using only one of two TCH/H, so that we don't switch more dynamic timeslots to TCH/H than necessary.
+ * For non-dynamic timeslots, it is not necessary to do a second pass with allow_pchan_switch ==
+ * true, because they never switch anyway. */
+ if ((lchan = _lc_find(ts_list, pchan, dyn_as_pchan, false, log)))
+ return lchan;
+ if ((lchan = _lc_find(ts_list, pchan, dyn_as_pchan, true, log)))
+ return lchan;
return NULL;
}
-static struct gsm_lchan *
-_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
+static struct gsm_lchan *lc_find(struct lchan_select_ts_list *ts_list,
+ enum gsm_phys_chan_config pchan,
+ bool log)
{
- return _lc_dyn_find_bts(bts, pchan, pchan);
+ return _lc_find(ts_list, pchan, pchan, false, log);
}
-struct gsm_lchan *lchan_select_by_chan_mode(struct gsm_bts *bts,
- enum gsm48_chan_mode chan_mode, bool full_rate)
+enum gsm_chan_t chan_mode_to_chan_type(enum gsm48_chan_mode chan_mode, enum channel_rate chan_rate)
{
- enum gsm_chan_t type;
-
- switch (chan_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(chan_mode)) {
case GSM48_CMODE_SIGN:
- type = GSM_LCHAN_SDCCH;
- break;
- case GSM48_CMODE_SPEECH_V1:
+ switch (chan_rate) {
+ case CH_RATE_SDCCH:
+ return GSM_LCHAN_SDCCH;
+ case CH_RATE_HALF:
+ return GSM_LCHAN_TCH_H;
+ case CH_RATE_FULL:
+ return GSM_LCHAN_TCH_F;
+ default:
+ return GSM_LCHAN_NONE;
+ }
case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ /* these rates work over full-rate channels only */
+ if (chan_rate != CH_RATE_FULL)
+ return GSM_LCHAN_NONE;
+ /* fall through */
+ case GSM48_CMODE_SPEECH_V1:
case GSM48_CMODE_SPEECH_AMR:
- type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H;
- break;
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ switch (chan_rate) {
+ case CH_RATE_HALF:
+ return GSM_LCHAN_TCH_H;
+ case CH_RATE_FULL:
+ return GSM_LCHAN_TCH_F;
+ default:
+ return GSM_LCHAN_NONE;
+ }
default:
- return NULL;
+ return GSM_LCHAN_NONE;
}
+}
- return lchan_select_by_type(bts, type);
+static int qsort_func(const void *_a, const void *_b)
+{
+ const struct gsm_bts_trx *trx_a = *(const struct gsm_bts_trx **)_a;
+ const struct gsm_bts_trx *trx_b = *(const struct gsm_bts_trx **)_b;
+
+ int pwr_a = trx_a->nominal_power - trx_a->max_power_red;
+ int pwr_b = trx_b->nominal_power - trx_b->max_power_red;
+
+ /* Sort in descending order */
+ return pwr_b - pwr_a;
}
-/* Return a matching lchan from a specific BTS that is currently available. The next logical step is
- * lchan_activate() on it, which would possibly cause dynamic timeslot pchan switching, taken care of by
- * the lchan and timeslot FSMs. */
-struct gsm_lchan *lchan_select_by_type(struct gsm_bts *bts, enum gsm_chan_t type)
+static void populate_ts_list(struct lchan_select_ts_list *ts_list,
+ struct gsm_bts *bts,
+ bool chan_alloc_reverse,
+ bool sort_by_trx_power,
+ bool log)
+{
+ struct gsm_bts_trx **trx_list;
+ struct gsm_bts_trx *trx;
+ unsigned int num = 0;
+
+ /* Allocate an array with pointers to all TRX instances of a BTS */
+ trx_list = talloc_array_ptrtype(bts, trx_list, bts->num_trx);
+ OSMO_ASSERT(trx_list != NULL);
+
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ trx_list[trx->nr] = trx;
+
+ /* Sort by TRX power in descending order (if needed) */
+ if (sort_by_trx_power)
+ qsort(&trx_list[0], bts->num_trx, sizeof(trx), &qsort_func);
+
+ for (unsigned int trxn = 0; trxn < bts->num_trx; trxn++) {
+ trx = trx_list[trxn];
+ for (unsigned int tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[tn];
+ if (ts_is_usable(ts))
+ ts_list->list[num++] = ts;
+ else if (log)
+ LOGP(DRLL, LOGL_DEBUG, "%s is not usable\n", gsm_ts_name(ts));
+ }
+ }
+
+ talloc_free(trx_list);
+ ts_list->num = num;
+
+ /* Reverse the timeslot list if required */
+ if (chan_alloc_reverse) {
+ for (unsigned int tn = 0; tn < num / 2; tn++) {
+ struct gsm_bts_trx_ts *temp = ts_list->list[tn];
+ ts_list->list[tn] = ts_list->list[num - tn - 1];
+ ts_list->list[num - tn - 1] = temp;
+ }
+ }
+}
+
+static bool chan_alloc_ass_dynamic_reverse(struct gsm_bts *bts,
+ void *ctx, bool log)
+{
+ const struct load_counter *ll = &bts->c0->lchan_load;
+ const struct gsm_lchan *old_lchan = ctx;
+ unsigned int lchan_load;
+ int avg_ul_rxlev;
+
+ OSMO_ASSERT(old_lchan != NULL);
+ OSMO_ASSERT(old_lchan->ts->trx->bts == bts);
+
+#define LOG_COND(fmt, args...) do { \
+ if (log) \
+ LOG_LCHAN(old_lchan, LOGL_DEBUG, fmt, ## args); \
+ } while (0)
+
+ /* Condition a) Channel load on the C0 (BCCH carrier) */
+ lchan_load = ll->total ? ll->used * 100 / ll->total : 0;
+ if (lchan_load < bts->chan_alloc_dyn_params.c0_chan_load_thresh) {
+ LOG_COND("C0 Channel Load %u%% < thresh %u%% => using ascending order\n",
+ lchan_load, bts->chan_alloc_dyn_params.c0_chan_load_thresh);
+ return false;
+ }
+
+ /* Condition b) average Uplink RxLev */
+ avg_ul_rxlev = get_meas_rep_avg(old_lchan, TDMA_MEAS_FIELD_RXLEV,
+ TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_AUTO,
+ bts->chan_alloc_dyn_params.ul_rxlev_avg_num);
+ if (avg_ul_rxlev < 0) {
+ LOG_COND("Unknown AVG UL RxLev => using ascending order\n");
+ return false;
+ }
+ if (avg_ul_rxlev < bts->chan_alloc_dyn_params.ul_rxlev_thresh) {
+ LOG_COND("AVG UL RxLev %u < thresh %u => using ascending order\n",
+ avg_ul_rxlev, bts->chan_alloc_dyn_params.ul_rxlev_thresh);
+ return false;
+ }
+
+ LOG_COND("C0 Channel Load %u%% >= thresh %u%% and "
+ "AVG UL RxLev %u >= thresh %u => using descending order\n",
+ lchan_load, bts->chan_alloc_dyn_params.c0_chan_load_thresh,
+ avg_ul_rxlev, bts->chan_alloc_dyn_params.ul_rxlev_thresh);
+
+#undef LOG_COND
+
+ return true;
+}
+
+struct gsm_lchan *lchan_select_by_chan_mode(struct gsm_bts *bts,
+ enum gsm48_chan_mode chan_mode,
+ enum channel_rate chan_rate,
+ enum lchan_select_reason reason,
+ void *ctx)
+{
+ enum gsm_chan_t type = chan_mode_to_chan_type(chan_mode, chan_rate);
+ if (type == GSM_LCHAN_NONE)
+ return NULL;
+ return lchan_select_by_type(bts, type, reason, ctx);
+}
+
+struct gsm_lchan *lchan_avail_by_type(struct gsm_bts *bts,
+ enum gsm_chan_t type,
+ enum lchan_select_reason reason,
+ void *ctx, bool log)
{
struct gsm_lchan *lchan = NULL;
enum gsm_phys_chan_config first, first_cbch, second, second_cbch;
+ struct lchan_select_ts_list ts_list;
+ bool sort_by_trx_power = false;
+ bool chan_alloc_reverse = false;
+
+ if (log) {
+ LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_avail_by_type(type=%s, reason=%s)\n",
+ gsm_chan_t_name(type), lchan_select_reason_name(reason));
+ }
+
+ switch (reason) {
+ case SELECT_FOR_MS_CHAN_REQ:
+ chan_alloc_reverse = bts->chan_alloc_chan_req_reverse;
+ break;
+ case SELECT_FOR_ASSIGNMENT:
+ if (bts->chan_alloc_assignment_dynamic) {
+ chan_alloc_reverse = chan_alloc_ass_dynamic_reverse(bts, ctx, log);
+ sort_by_trx_power = bts->chan_alloc_dyn_params.sort_by_trx_power;
+ } else {
+ chan_alloc_reverse = bts->chan_alloc_assignment_reverse;
+ }
+ break;
+ case SELECT_FOR_HANDOVER:
+ chan_alloc_reverse = bts->chan_alloc_handover_reverse;
+ break;
+ case SELECT_FOR_VGCS:
+ chan_alloc_reverse = bts->chan_alloc_vgcs_reverse;
+ break;
+ }
+
+ /* Allocate an array with pointers to all timeslots of a BTS */
+ ts_list.list = talloc_array_ptrtype(bts, ts_list.list, bts->num_trx * 8);
+ if (OSMO_UNLIKELY(ts_list.list == NULL))
+ return NULL;
- LOGP(DRLL, LOGL_DEBUG, "(bts=%d) lchan_select_by_type(%s)\n", bts->nr, gsm_lchant_name(type));
+ /* Populate this array with the actual pointers */
+ populate_ts_list(&ts_list, bts, chan_alloc_reverse, sort_by_trx_power, log);
switch (type) {
case GSM_LCHAN_SDCCH:
- if (bts->chan_alloc_reverse) {
+ if (chan_alloc_reverse) {
first = GSM_PCHAN_SDCCH8_SACCH8C;
first_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
second = GSM_PCHAN_CCCH_SDCCH4;
@@ -172,74 +377,79 @@ struct gsm_lchan *lchan_select_by_type(struct gsm_bts *bts, enum gsm_chan_t type
second_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
}
- lchan = _lc_find_bts(bts, first);
+ lchan = lc_find(&ts_list, first, log);
if (lchan == NULL)
- lchan = _lc_find_bts(bts, first_cbch);
+ lchan = lc_find(&ts_list, first_cbch, log);
if (lchan == NULL)
- lchan = _lc_find_bts(bts, second);
+ lchan = lc_find(&ts_list, second, log);
if (lchan == NULL)
- lchan = _lc_find_bts(bts, second_cbch);
+ lchan = lc_find(&ts_list, second_cbch, log);
+ /* No dedicated SDCCH available -- try fully dynamic
+ * TCH/F_TCH/H_SDCCH8_PDCH if BTS supports it: */
+ if (lchan == NULL && osmo_bts_has_feature(&bts->features, BTS_FEAT_DYN_TS_SDCCH8))
+ lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN,
+ GSM_PCHAN_SDCCH8_SACCH8C, log);
break;
case GSM_LCHAN_TCH_F:
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+ lchan = lc_find(&ts_list, GSM_PCHAN_TCH_F, log);
/* If we don't have TCH/F available, try dynamic TCH/F_PDCH */
- if (!lchan) {
- lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH,
- GSM_PCHAN_TCH_F);
- /* TCH/F_PDCH used as TCH/F -- here, type is already
- * set to GSM_LCHAN_TCH_F, but for clarity's sake... */
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
+ if (!lchan)
+ lchan = lc_dyn_find(&ts_list, GSM_PCHAN_TCH_F_PDCH,
+ GSM_PCHAN_TCH_F, log);
/* Try fully dynamic TCH/F_TCH/H_PDCH as TCH/F... */
- if (!lchan && bts->network->dyn_ts_allow_tch_f) {
- lchan = _lc_dyn_find_bts(bts,
- GSM_PCHAN_TCH_F_TCH_H_PDCH,
- GSM_PCHAN_TCH_F);
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
+ if (!lchan && bts->network->dyn_ts_allow_tch_f)
+ lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN,
+ GSM_PCHAN_TCH_F, log);
break;
case GSM_LCHAN_TCH_H:
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
- /* If we don't have TCH/H available, fall-back to TCH/F */
- if (!lchan) {
- lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
+ lchan = lc_find(&ts_list, GSM_PCHAN_TCH_H, log);
/* No dedicated TCH/x available -- try fully dynamic
* TCH/F_TCH/H_PDCH */
- if (!lchan) {
- lchan = _lc_dyn_find_bts(bts,
- GSM_PCHAN_TCH_F_TCH_H_PDCH,
- GSM_PCHAN_TCH_H);
- if (lchan)
- type = GSM_LCHAN_TCH_H;
- }
- /*
- * No need to check TCH/F_TCH/H_PDCH channels for TCH/F:
- * if no TCH/H was available, neither will be TCH/F.
- */
- /* If we don't have TCH/F either, try dynamic TCH/F_PDCH */
- if (!lchan) {
- lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH,
- GSM_PCHAN_TCH_F);
- if (lchan)
- type = GSM_LCHAN_TCH_F;
- }
+ if (!lchan)
+ lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN,
+ GSM_PCHAN_TCH_H, log);
break;
default:
- LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type);
+ LOG_BTS(bts, DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type);
}
- if (lchan) {
- lchan->type = type;
- LOG_LCHAN(lchan, LOGL_INFO, "Selected\n");
- } else
- LOGP(DRLL, LOGL_ERROR, "(bts=%d) Failed to select %s channel\n",
- bts->nr, gsm_lchant_name(type));
+ talloc_free(ts_list.list);
+
+ return lchan;
+}
+
+/* Return a matching lchan from a specific BTS that is currently available. The next logical step is
+ * lchan_activate() on it, which would possibly cause dynamic timeslot pchan switching, taken care of by
+ * the lchan and timeslot FSMs. */
+struct gsm_lchan *lchan_select_by_type(struct gsm_bts *bts,
+ enum gsm_chan_t type,
+ enum lchan_select_reason reason,
+ void *ctx)
+{
+ struct gsm_lchan *lchan = NULL;
+
+ LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_select_by_type(type=%s, reason=%s)\n",
+ gsm_chan_t_name(type), lchan_select_reason_name(reason));
+
+ lchan = lchan_avail_by_type(bts, type, reason, ctx, true);
+ if (!lchan) {
+ LOG_BTS(bts, DRLL, LOGL_NOTICE, "Failed to select %s channel (%s)\n",
+ gsm_chan_t_name(type), lchan_select_reason_name(reason));
+ return NULL;
+ }
+
+ lchan_select_set_type(lchan, type);
return lchan;
}
+
+/* Set available lchan to given type. Usually used on lchan obtained with
+ * lchan_avail_by_type. The next logical step is lchan_activate() on it, which
+ * would possibly cause dynamic timeslot pchan switching, taken care of by the
+ * lchan and timeslot FSMs. */
+void lchan_select_set_type(struct gsm_lchan *lchan, enum gsm_chan_t type)
+{
+ lchan->type = type;
+ LOG_LCHAN(lchan, LOGL_INFO, "Selected\n");
+}
diff --git a/src/osmo-bsc/lcs_loc_req.c b/src/osmo-bsc/lcs_loc_req.c
new file mode 100644
index 000000000..bb0c5e273
--- /dev/null
+++ b/src/osmo-bsc/lcs_loc_req.c
@@ -0,0 +1,615 @@
+/* Handle LCS BSSMAP-LE Perform Location Request */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.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/lcs_loc_req.h>
+
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/lb.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gad.h>
+#include <osmocom/gsm/bsslap.h>
+#include <osmocom/gsm/bssmap_le.h>
+#include <osmocom/gsm/gsm0808_lcs.h>
+#include <osmocom/bsc/lcs_ta_req.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/bts_trx.h>
+#include <osmocom/bsc/bts.h>
+
+enum lcs_loc_req_fsm_state {
+ LCS_LOC_REQ_ST_INIT,
+ LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE,
+ LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING,
+ LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE,
+ LCS_LOC_REQ_ST_FAILED,
+};
+
+static const struct value_string lcs_loc_req_fsm_event_names[] = {
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_TA_REQ_START),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_TA_REQ_END),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_HANDOVER_PERFORMED),
+ OSMO_VALUE_STRING(LCS_LOC_REQ_EV_CONN_CLEAR),
+ {}
+};
+
+static struct osmo_fsm lcs_loc_req_fsm;
+
+static const struct osmo_tdef_state_timeout lcs_loc_req_fsm_timeouts[32] = {
+ [LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE] = { .T = -11 },
+};
+
+/* Transition to a state, using the T timer defined in lcs_loc_req_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define lcs_loc_req_fsm_state_chg(FI, STATE) \
+ osmo_tdef_fsm_inst_state_chg(FI, STATE, \
+ lcs_loc_req_fsm_timeouts, \
+ (bsc_gsmnet)->T_defs, \
+ 5)
+
+#define lcs_loc_req_fail(cause, fmt, args...) do { \
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Perform Location Request failed in state %s: " fmt "\n", \
+ lcs_loc_req ? osmo_fsm_inst_state_name(lcs_loc_req->fi) : "NULL", ## args); \
+ lcs_loc_req->lcs_cause = (struct lcs_cause_ie){ \
+ .present = true, \
+ .cause_val = cause, \
+ }; \
+ lcs_loc_req_fsm_state_chg(lcs_loc_req->fi, LCS_LOC_REQ_ST_FAILED); \
+ } while (0)
+
+static struct lcs_loc_req *lcs_loc_req_alloc(struct osmo_fsm_inst *parent_fi, uint32_t parent_event_term)
+{
+ struct lcs_loc_req *lcs_loc_req;
+
+ struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&lcs_loc_req_fsm, parent_fi, parent_event_term);
+ OSMO_ASSERT(fi);
+
+ lcs_loc_req = talloc(fi, struct lcs_loc_req);
+ OSMO_ASSERT(lcs_loc_req);
+ fi->priv = lcs_loc_req;
+ *lcs_loc_req = (struct lcs_loc_req){
+ .fi = fi,
+ };
+
+ return lcs_loc_req;
+}
+
+static bool parse_bssmap_perf_loc_req(struct lcs_loc_req *lcs_loc_req, struct msgb *msg)
+{
+ struct tlv_parsed tp_arr[1];
+ struct tlv_parsed *tp = &tp_arr[0];
+ const struct tlv_p_entry *e;
+ int payload_length;
+
+#define PARSE_ERR(ERRMSG) do { \
+ lcs_loc_req_fail(LCS_CAUSE_PROTOCOL_ERROR, "rx BSSMAP Perform Location Request: " ERRMSG); \
+ return false; \
+ } while (0)
+
+ payload_length = msg->tail - msg->l4h;
+ if (tlv_parse2(tp_arr, 1, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0) <= 0)
+ PARSE_ERR("Failed to parse IEs");
+
+ if (!(e = TLVP_GET(tp, GSM0808_IE_LOCATION_TYPE)))
+ PARSE_ERR("Missing Location Type IE");
+ if (osmo_bssmap_le_ie_dec_location_type(&lcs_loc_req->req.location_type, -1, -1, NULL, NULL, e->val, e->len))
+ PARSE_ERR("Failed to parse Location Type IE");
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER))) {
+ if (gsm0808_dec_cell_id(&lcs_loc_req->req.cell_id, e->val, e->len) <= 0)
+ PARSE_ERR("Failed to parse Cell Identifier IE");
+ lcs_loc_req->req.cell_id_present = true;
+ }
+
+ /* 3GPP TS 49.031, section 10.14 (C) "LCS Client Type" */
+ if (TLVP_PRES_LEN(tp, GSM0808_IE_LCS_CLIENT_TYPE, 1)) {
+ lcs_loc_req->req.client_type = *TLVP_VAL(tp, GSM0808_IE_LCS_CLIENT_TYPE);
+ lcs_loc_req->req.client_type_present = true;
+ } else if (lcs_loc_req->req.location_type.location_information == BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC)
+ PARSE_ERR("Missing LCS Client Type IE");
+
+ /* 3GPP TS 49.031, section 10.15 (O) "LCS Priority" */
+ if (TLVP_PRES_LEN(tp, GSM0808_IE_LCS_PRIORITY, 1)) {
+ lcs_loc_req->req.priority = *TLVP_VAL(tp, GSM0808_IE_LCS_PRIORITY);
+ lcs_loc_req->req.priority_present = true;
+ }
+
+ /* 3GPP TS 49.031, section 10.16 (C) "LCS QoS" */
+ if (TLVP_PRES_LEN(tp, GSM0808_IE_LCS_QOS, sizeof(lcs_loc_req->req.qos))) {
+ size_t qos_len = TLVP_LEN(tp, GSM0808_IE_LCS_QOS);
+ if (qos_len > sizeof(lcs_loc_req->req.qos))
+ qos_len = sizeof(lcs_loc_req->req.qos);
+ memcpy(&lcs_loc_req->req.qos, TLVP_VAL(tp, GSM0808_IE_LCS_QOS), qos_len);
+ lcs_loc_req->req.qos_present = true;
+ } else if (lcs_loc_req->req.location_type.location_information == BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC)
+ PARSE_ERR("Missing LCS QoS IE");
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_IMSI))) {
+ if (osmo_mobile_identity_decode(&lcs_loc_req->req.imsi, e->val, e->len, false)
+ || lcs_loc_req->req.imsi.type != GSM_MI_TYPE_IMSI)
+ PARSE_ERR("Failed to parse IMSI IE");
+ }
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_IMEI))) {
+ if (osmo_mobile_identity_decode(&lcs_loc_req->req.imei, e->val, e->len, false)
+ || lcs_loc_req->req.imei.type != GSM_MI_TYPE_IMEI)
+ PARSE_ERR("Failed to parse IMEI IE");
+ }
+
+ /* A lot of IEs remain ignored... */
+
+ return true;
+#undef PARSE_ERR
+}
+
+void lcs_loc_req_start(struct gsm_subscriber_connection *conn, struct msgb *loc_req_msg)
+{
+ struct lcs_loc_req *lcs_loc_req;
+
+ if (conn->lcs.loc_req) {
+ LOG_LCS_LOC_REQ(conn, LOGL_ERROR,
+ "Ignoring Perform Location Request, another request is still pending\n");
+ return;
+ }
+
+ lcs_loc_req = lcs_loc_req_alloc(conn->fi, GSCON_EV_LCS_LOC_REQ_END);
+
+ lcs_loc_req->conn = conn;
+ conn->lcs.loc_req = lcs_loc_req;
+
+ if (!parse_bssmap_perf_loc_req(lcs_loc_req, loc_req_msg))
+ return;
+
+ if (!conn->bsub) {
+ if (lcs_loc_req->req.imsi.type != GSM_MI_TYPE_IMSI) {
+ lcs_loc_req_fail(LCS_CAUSE_DATA_MISSING_IN_REQ,
+ "tx Perform Location Request: Missing identity:"
+ " No IMSI included in request, and also no active subscriber");
+ return;
+ }
+
+ conn->bsub = bsc_subscr_find_or_create_by_mi(bsc_gsmnet->bsc_subscribers, &lcs_loc_req->req.imsi,
+ BSUB_USE_CONN);
+ if (!conn->bsub) {
+ lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
+ "tx Perform Location Request: Cannot assign subscriber");
+ return;
+ }
+ }
+
+ /* state change to start the timeout */
+ lcs_loc_req_fsm_state_chg(lcs_loc_req->fi, LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE);
+}
+
+static int handle_bssmap_le_conn_oriented_info(struct lcs_loc_req *lcs_loc_req, const struct bssmap_le_pdu *bssmap_le)
+{
+ switch (bssmap_le->conn_oriented_info.apdu.msg_type) {
+ case BSSLAP_MSGT_TA_REQUEST:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_TA_REQUEST));
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_DEBUG, "rx BSSLAP TA Request\n");
+ /* The TA Request message contains only the message type. */
+ return lcs_ta_req_start(lcs_loc_req);
+ default:
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "rx BSSLAP APDU with unsupported message type %d\n",
+ bssmap_le->conn_oriented_info.apdu.msg_type);
+ return -ENOTSUP;
+ };
+}
+
+int lcs_loc_req_rx_bssmap_le(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ struct lcs_loc_req *lcs_loc_req = conn->lcs.loc_req;
+ struct bssap_le_pdu bssap_le;
+ struct osmo_bssap_le_err *err = NULL;
+ struct rate_ctr_group *ctrg = bsc_gsmnet->smlc->ctrs;
+
+ if (!lcs_loc_req) {
+ LOGPFSMSL(conn->fi, DLCS, LOGL_ERROR,
+ "Rx BSSMAP-LE message, but no Location Request is ongoing\n");
+ return -EINVAL;
+ }
+
+ if (osmo_bssap_le_dec(&bssap_le, &err, msg, msg)) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Rx BSSAP-LE message with error: %s\n", err->logmsg);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG));
+ return -EINVAL;
+ }
+
+ if (bssap_le.discr != BSSAP_LE_MSG_DISCR_BSSMAP_LE) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Rx BSSAP-LE: discr %d not implemented\n", bssap_le.discr);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG));
+ return -ENOTSUP;
+ }
+
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_DEBUG, "Rx %s\n", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, &bssap_le));
+
+ switch (bssap_le.bssmap_le.msg_type) {
+ case BSSMAP_LE_MSGT_PERFORM_LOC_RESP:
+ if (bssap_le.bssmap_le.perform_loc_resp.location_estimate_present)
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS));
+ else
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE));
+ return osmo_fsm_inst_dispatch(lcs_loc_req->fi, LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE,
+ &bssap_le.bssmap_le);
+
+ case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO:
+ return handle_bssmap_le_conn_oriented_info(lcs_loc_req, &bssap_le.bssmap_le);
+
+ default:
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Rx BSSMAP-LE from SMLC with unsupported message type: %s\n",
+ osmo_bssap_le_pdu_to_str_c(OTC_SELECT, &bssap_le));
+ return -ENOTSUP;
+ }
+}
+
+void lcs_loc_req_reset(struct gsm_subscriber_connection *conn)
+{
+ struct lcs_loc_req *lcs_loc_req = conn->lcs.loc_req;
+ if (!lcs_loc_req)
+ return;
+ lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Aborting Location Request due to RESET on Lb");
+}
+
+static int lcs_loc_req_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Timeout");
+ return 1;
+}
+
+static int lcs_loc_req_send(struct lcs_loc_req *lcs_loc_req, const struct bssap_le_pdu *bssap_le)
+{
+ int rc = lb_send(lcs_loc_req->conn, bssap_le);
+ if (rc)
+ lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
+ "Failed to send %s", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
+ return rc;
+}
+
+static void lcs_loc_req_wait_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ struct bssap_le_pdu plr;
+ struct gsm_lchan *lchan;
+
+ if (prev_state == LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING) {
+ /* LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING should halt the FSM timeout. As soon as the TA Request is
+ * served, re-entering LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE, but of course there is then no need to
+ * send a second BSSMAP-LE Perform Location Request to the SMLC. */
+ return;
+ }
+
+ if (!lcs_loc_req->req.cell_id_present) {
+ lcs_loc_req_fail(LCS_CAUSE_PROTOCOL_ERROR,
+ "Cannot encode BSSMAP-LE Perform Location Request,"
+ " because mandatory Cell Identity is not known");
+ return;
+ }
+
+ plr = (struct bssap_le_pdu){
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_REQ,
+ .perform_loc_req = {
+ .location_type = lcs_loc_req->req.location_type,
+ .cell_id = lcs_loc_req->req.cell_id,
+ .imsi = lcs_loc_req->req.imsi,
+ .imei = lcs_loc_req->req.imei,
+
+ .lcs_client_type_present = lcs_loc_req->req.client_type_present,
+ .lcs_client_type = lcs_loc_req->req.client_type,
+
+ .more_items = true,
+
+ .lcs_priority_present = lcs_loc_req->req.priority_present,
+ .lcs_priority = lcs_loc_req->req.priority,
+
+ .lcs_qos_present = lcs_loc_req->req.qos_present,
+ .lcs_qos = lcs_loc_req->req.qos,
+ },
+ },
+ };
+
+ /* If we already have an active lchan, send the known TA directly to the SMLC */
+ lchan = lcs_loc_req->conn->lchan;
+ if (lchan) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_DEBUG,
+ "Active lchan present, including BSSLAP APDU with TA Layer 3\n");
+ plr.bssmap_le.perform_loc_req.apdu_present = true;
+ plr.bssmap_le.perform_loc_req.apdu = (struct bsslap_pdu){
+ .msg_type = BSSLAP_MSGT_TA_LAYER3,
+ .ta_layer3 = {
+ .ta = lchan->last_ta,
+ },
+ };
+ } else {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_DEBUG,
+ "No active lchan, not including BSSLAP APDU\n");
+ }
+
+ /* Establish Lb connection to SMLC and send the BSSMAP-LE Perform Location Request */
+ lcs_loc_req_send(lcs_loc_req, &plr);
+}
+
+static void lcs_loc_req_bssmap_le_abort(struct lcs_loc_req *lcs_loc_req)
+{
+ struct bssap_le_pdu pla = {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_ABORT,
+ .perform_loc_abort = {
+ .present = true,
+ .cause_val = LCS_CAUSE_REQUEST_ABORTED,
+ },
+ },
+ };
+
+ lcs_loc_req_send(lcs_loc_req, &pla);
+}
+
+/* After a handover, send the new lchan information to the SMLC via a BSSLAP Reset message.
+ * See 3GPP TS 48.071 4.2.6 Reset. */
+static void lcs_loc_req_handover_performed(struct lcs_loc_req *lcs_loc_req)
+{
+ struct gsm_lchan *lchan = lcs_loc_req->conn->lchan;
+ struct bssap_le_pdu bsslap = {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
+ },
+ };
+ struct bsslap_pdu *apdu = &bsslap.bssmap_le.conn_oriented_info.apdu;
+
+ if (!lchan) {
+ /* The handover was out of this BSS. Abort the location procedure. */
+ *apdu = (struct bsslap_pdu){
+ .msg_type = BSSLAP_MSGT_ABORT,
+ .abort = BSSLAP_CAUSE_INTER_BSS_HO,
+ };
+ } else {
+ *apdu = (struct bsslap_pdu){
+ .msg_type = BSSLAP_MSGT_RESET,
+ .reset = {
+ .cell_id = lchan->ts->trx->bts->cell_identity,
+ .ta = lchan->last_ta,
+ .cause = BSSLAP_CAUSE_INTRA_BSS_HO,
+ },
+ };
+ if (gsm48_lchan2chan_desc(&apdu->reset.chan_desc, lchan, lchan->tsc, false)) {
+ lcs_loc_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Error encoding Channel Number");
+ return;
+ }
+ }
+
+ lcs_loc_req_send(lcs_loc_req, &bsslap);
+}
+
+static void lcs_loc_req_wait_loc_resp_and_ta_req_ongoing_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ const struct bssmap_le_pdu *bssmap_le;
+
+ switch (event) {
+
+ case LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE:
+ bssmap_le = data;
+ OSMO_ASSERT(bssmap_le->msg_type == BSSMAP_LE_MSGT_PERFORM_LOC_RESP);
+ lcs_loc_req->resp = bssmap_le->perform_loc_resp;
+ lcs_loc_req->resp_present = true;
+ lcs_loc_req_fsm_state_chg(fi, LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE);
+ break;
+
+ case LCS_LOC_REQ_EV_TA_REQ_START:
+ if (fi->state != LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING)
+ lcs_loc_req_fsm_state_chg(fi, LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING);
+ break;
+
+ case LCS_LOC_REQ_EV_TA_REQ_END:
+ if (fi->state != LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE)
+ lcs_loc_req_fsm_state_chg(fi, LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE);
+ break;
+
+ case LCS_LOC_REQ_EV_HANDOVER_PERFORMED:
+ lcs_loc_req_handover_performed(lcs_loc_req);
+ break;
+
+ case LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT:
+ case LCS_LOC_REQ_EV_CONN_CLEAR:
+ if (lcs_loc_req->ta_req)
+ osmo_fsm_inst_dispatch(lcs_loc_req->ta_req->fi, LCS_TA_REQ_EV_ABORT, NULL);
+ lcs_loc_req_bssmap_le_abort(lcs_loc_req);
+ osmo_fsm_inst_term(lcs_loc_req->fi, OSMO_FSM_TERM_REGULAR, NULL);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void lcs_loc_req_got_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ struct msgb *msg;
+ int rc;
+ struct gsm0808_perform_location_response plr = {
+ .location_estimate_present = lcs_loc_req->resp.location_estimate_present,
+ .location_estimate = lcs_loc_req->resp.location_estimate,
+ .lcs_cause = lcs_loc_req->resp.lcs_cause,
+ };
+
+ if (plr.location_estimate_present) {
+ struct osmo_gad gad;
+ struct osmo_gad_err *err;
+ if (osmo_gad_dec(&gad, &err, OTC_SELECT, &plr.location_estimate))
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Perform Location Response contains Location Estimate with error: %s\n",
+ err->logmsg);
+ else
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_INFO,
+ "Perform Location Response contains Location Estimate: %s\n",
+ osmo_gad_to_str_c(OTC_SELECT, &gad));
+ }
+
+ if (plr.lcs_cause.present) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Perform Location Response contains error cause: %d\n",
+ plr.lcs_cause.cause_val);
+ }
+
+ msg = gsm0808_create_perform_location_response(&plr);
+ if (!msg) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Failed to encode BSSMAP Perform Location Response (A-interface)\n");
+ } else {
+ rc = gscon_sigtran_send(lcs_loc_req->conn, msg);
+ if (rc < 0)
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Failed to send Perform Location Response (A-interface)\n");
+ else
+ rate_ctr_inc(rate_ctr_group_get_ctr(lcs_loc_req->conn->sccp.msc->msc_ctrs, plr.location_estimate_present ? MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS : MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE));
+ }
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+static void lcs_loc_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ struct msgb *msg;
+ int rc;
+ struct bssap_le_pdu pla = {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_ABORT,
+ .perform_loc_abort = lcs_loc_req->lcs_cause,
+ },
+ };
+ struct gsm0808_perform_location_response plr = {
+ .lcs_cause = lcs_loc_req->lcs_cause,
+ };
+
+ /* If we're paging this subscriber for LCS, stop paging. */
+ if (lcs_loc_req->conn->bsub)
+ paging_request_cancel(lcs_loc_req->conn->bsub, BSC_PAGING_FOR_LCS);
+
+ /* Send Perform Location Abort to SMLC, only if we got started on the Lb */
+ if (lcs_loc_req->conn->lcs.lb.state == SUBSCR_SCCP_ST_CONNECTED)
+ lcs_loc_req_send(lcs_loc_req, &pla);
+
+ /* Send Perform Location Result with failure cause to MSC */
+ msg = gsm0808_create_perform_location_response(&plr);
+ if (!msg) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Failed to encode BSSMAP Perform Location Response (A-interface)\n");
+ } else {
+ rc = gscon_sigtran_send(lcs_loc_req->conn, msg);
+ if (rc < 0)
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
+ "Failed to send BSSMAP Perform Location Response (A-interface)\n");
+ else
+ rate_ctr_inc(rate_ctr_group_get_ctr(lcs_loc_req->conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE));
+ }
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+void lcs_loc_req_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct lcs_loc_req *lcs_loc_req = fi->priv;
+ if (lcs_loc_req->conn && lcs_loc_req->conn->lcs.loc_req == lcs_loc_req)
+ lcs_loc_req->conn->lcs.loc_req = NULL;
+ /* FSM termination will dispatch GSCON_EV_LCS_LOC_REQ_END to the conn FSM */
+}
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state lcs_loc_req_fsm_states[] = {
+ [LCS_LOC_REQ_ST_INIT] = {
+ .name = "INIT",
+ .out_state_mask = 0
+ | S(LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_ST_FAILED)
+ ,
+ },
+ [LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE] = {
+ .name = "WAIT_LOCATION_RESPONSE",
+ .in_event_mask = 0
+ | S(LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT)
+ | S(LCS_LOC_REQ_EV_TA_REQ_START)
+ | S(LCS_LOC_REQ_EV_TA_REQ_END)
+ | S(LCS_LOC_REQ_EV_HANDOVER_PERFORMED)
+ | S(LCS_LOC_REQ_EV_CONN_CLEAR)
+ ,
+ .out_state_mask = 0
+ | S(LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING)
+ | S(LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_ST_FAILED)
+ ,
+ .onenter = lcs_loc_req_wait_loc_resp_onenter,
+ .action = lcs_loc_req_wait_loc_resp_and_ta_req_ongoing_action,
+ },
+ [LCS_LOC_REQ_ST_BSSLAP_TA_REQ_ONGOING] = {
+ .name = "BSSLAP_TA_REQ_ONGOING",
+ .in_event_mask = 0
+ | S(LCS_LOC_REQ_EV_RX_LB_PERFORM_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT)
+ | S(LCS_LOC_REQ_EV_TA_REQ_END)
+ | S(LCS_LOC_REQ_EV_HANDOVER_PERFORMED)
+ | S(LCS_LOC_REQ_EV_CONN_CLEAR)
+ ,
+ .out_state_mask = 0
+ | S(LCS_LOC_REQ_ST_WAIT_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE)
+ | S(LCS_LOC_REQ_ST_FAILED)
+ ,
+ .action = lcs_loc_req_wait_loc_resp_and_ta_req_ongoing_action,
+ },
+ [LCS_LOC_REQ_ST_GOT_LOCATION_RESPONSE] = {
+ .name = "GOT_LOCATION_RESPONSE",
+ .onenter = lcs_loc_req_got_loc_resp_onenter,
+ },
+ [LCS_LOC_REQ_ST_FAILED] = {
+ .name = "FAILED",
+ .onenter = lcs_loc_req_failed_onenter,
+ },
+};
+
+static struct osmo_fsm lcs_loc_req_fsm = {
+ .name = "lcs_loc_req",
+ .states = lcs_loc_req_fsm_states,
+ .num_states = ARRAY_SIZE(lcs_loc_req_fsm_states),
+ .log_subsys = DLCS,
+ .event_names = lcs_loc_req_fsm_event_names,
+ .timer_cb = lcs_loc_req_fsm_timer_cb,
+ .cleanup = lcs_loc_req_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void lcs_loc_req_fsm_register(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&lcs_loc_req_fsm) == 0);
+}
diff --git a/src/osmo-bsc/lcs_ta_req.c b/src/osmo-bsc/lcs_ta_req.c
new file mode 100644
index 000000000..a1fa87301
--- /dev/null
+++ b/src/osmo-bsc/lcs_ta_req.c
@@ -0,0 +1,305 @@
+/* Handle LCS BSSLAP TA Request */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.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/lcs_ta_req.h>
+
+#include <osmocom/bsc/lcs_loc_req.h>
+#include <osmocom/bsc/lb.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/bsslap.h>
+
+enum lcs_ta_req_fsm_state {
+ LCS_TA_REQ_ST_INIT,
+ LCS_TA_REQ_ST_WAIT_TA,
+ LCS_TA_REQ_ST_GOT_TA,
+ LCS_TA_REQ_ST_FAILED,
+};
+
+static const struct value_string lcs_ta_req_fsm_event_names[] = {
+ OSMO_VALUE_STRING(LCS_TA_REQ_EV_GOT_TA),
+ OSMO_VALUE_STRING(LCS_TA_REQ_EV_ABORT),
+ {}
+};
+
+static const struct osmo_tdef_state_timeout lcs_ta_req_fsm_timeouts[32] = {
+ [LCS_TA_REQ_ST_WAIT_TA] = { .T = -12 },
+};
+
+/* Transition to a state, using the T timer defined in lcs_ta_req_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define lcs_ta_req_fsm_state_chg(FI, STATE) \
+ osmo_tdef_fsm_inst_state_chg(FI, STATE, \
+ lcs_ta_req_fsm_timeouts, \
+ (bsc_gsmnet)->T_defs, \
+ -1)
+
+#define lcs_ta_req_fail(cause, fmt, args...) do { \
+ LOG_LCS_TA_REQ(lcs_ta_req, LOGL_ERROR, "BSSLAP TA Request failed in state %s: " fmt "\n", \
+ lcs_ta_req ? osmo_fsm_inst_state_name(lcs_ta_req->fi) : "NULL", ## args); \
+ lcs_ta_req->failure_cause = cause; \
+ lcs_ta_req_fsm_state_chg(lcs_ta_req->fi, LCS_TA_REQ_ST_FAILED); \
+ } while (0)
+
+static struct osmo_fsm lcs_ta_req_fsm;
+
+static struct lcs_ta_req *lcs_ta_req_alloc(struct osmo_fsm_inst *parent_fi, uint32_t parent_event_term)
+{
+ struct lcs_ta_req *lcs_ta_req;
+
+ struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc_child(&lcs_ta_req_fsm, parent_fi, parent_event_term);
+ OSMO_ASSERT(fi);
+
+ lcs_ta_req = talloc(fi, struct lcs_ta_req);
+ OSMO_ASSERT(lcs_ta_req);
+ fi->priv = lcs_ta_req;
+ *lcs_ta_req = (struct lcs_ta_req){
+ .fi = fi,
+ };
+
+ return lcs_ta_req;
+}
+
+int lcs_ta_req_start(struct lcs_loc_req *lcs_loc_req)
+{
+ struct lcs_ta_req *lcs_ta_req;
+ if (lcs_loc_req->ta_req) {
+ LOG_LCS_TA_REQ(lcs_loc_req->ta_req, LOGL_ERROR,
+ "Cannot start another TA Request FSM, this TA Request is still active\n");
+ return -ENOTSUP;
+ }
+ lcs_ta_req = lcs_ta_req_alloc(lcs_loc_req->fi, LCS_LOC_REQ_EV_TA_REQ_END);
+ if (!lcs_ta_req) {
+ LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR, "Cannot allocate TA Request FSM");
+ return -ENOSPC;
+ }
+ lcs_ta_req->loc_req = lcs_loc_req;
+ lcs_loc_req->ta_req = lcs_ta_req;
+
+ return lcs_ta_req_fsm_state_chg(lcs_ta_req->fi, LCS_TA_REQ_ST_WAIT_TA);
+}
+
+static int lcs_ta_req_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ lcs_ta_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Timeout");
+ return 1;
+}
+
+void lcs_ta_req_wait_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ struct lcs_loc_req *loc_req = lcs_ta_req->loc_req;
+ struct gsm_lchan *lchan;
+ struct bsc_paging_params paging;
+
+ if (osmo_fsm_inst_dispatch(loc_req->fi, LCS_LOC_REQ_EV_TA_REQ_START, lcs_ta_req)) {
+ lcs_ta_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Failed to dispatch LCS_LOC_REQ_EV_TA_REQ_START");
+ return;
+ }
+
+ /* Do we already have an active lchan with knowledge of TA? */
+ lchan = loc_req->conn->lchan;
+ if (lchan) {
+ lcs_ta_req_fsm_state_chg(fi, LCS_TA_REQ_ST_GOT_TA);
+ return;
+ }
+
+ /* No lchan yet, need to start Paging */
+ if (loc_req->req.imsi.type != GSM_MI_TYPE_IMSI) {
+ lcs_ta_req_fail(LCS_CAUSE_PROTOCOL_ERROR,
+ "No IMSI in BSSMAP Location Request and no active lchan, cannot start Paging");
+ return;
+ }
+
+ paging = (struct bsc_paging_params){
+ .reason = BSC_PAGING_FOR_LCS,
+ .msc = loc_req->conn->sccp.msc,
+ .bsub = loc_req->conn->bsub,
+ .tmsi = GSM_RESERVED_TMSI,
+ .imsi = loc_req->req.imsi,
+ .chan_needed = RSL_CHANNEED_ANY,
+ };
+ if (paging.bsub)
+ bsc_subscr_get(paging.bsub, BSUB_USE_PAGING_START);
+
+ if (!loc_req->req.cell_id_present) {
+ LOG_LCS_TA_REQ(lcs_ta_req, LOGL_DEBUG,
+ "No Cell Identity in BSSMAP Location Request, paging entire BSS\n");
+ paging.cil = (struct gsm0808_cell_id_list2){
+ .id_discr = CELL_IDENT_BSS,
+ };
+ } else {
+ paging.cil = (struct gsm0808_cell_id_list2){
+ .id_discr = loc_req->req.cell_id.id_discr,
+ .id_list = { loc_req->req.cell_id.id },
+ .id_list_len = 1,
+ };
+ }
+
+ bsc_paging_start(&paging);
+}
+
+static void lcs_ta_req_wait_ta_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+
+ case LCS_TA_REQ_EV_GOT_TA:
+ lcs_ta_req_fsm_state_chg(fi, LCS_TA_REQ_ST_GOT_TA);
+ break;
+
+ case LCS_TA_REQ_EV_ABORT:
+ lcs_ta_req_fsm_state_chg(fi, LCS_TA_REQ_ST_FAILED);
+ break;
+
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static int lcs_ta_req_send(struct lcs_ta_req *lcs_ta_req, const struct bssap_le_pdu *bssap_le)
+{
+ int rc = lb_send(lcs_ta_req->loc_req->conn, bssap_le);
+ if (rc)
+ lcs_ta_req_fail(LCS_CAUSE_SYSTEM_FAILURE,
+ "Failed to send %s", osmo_bssap_le_pdu_to_str_c(OTC_SELECT, bssap_le));
+ return rc;
+}
+
+void lcs_ta_req_got_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ struct bssap_le_pdu bsslap_ta_resp;
+ struct gsm_lchan *lchan = lcs_ta_req->loc_req->conn->lchan;
+
+ if (!lchan) {
+ lcs_ta_req_fail(LCS_CAUSE_SYSTEM_FAILURE, "Internal error: no lchan");
+ return;
+ }
+
+ bsslap_ta_resp = (struct bssap_le_pdu) {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
+ .conn_oriented_info = {
+ .apdu = {
+ .msg_type = BSSLAP_MSGT_TA_RESPONSE,
+ .ta_response = {
+ .cell_id = lchan->ts->trx->bts->cell_identity,
+ .ta = lchan->last_ta,
+ },
+ },
+ },
+ },
+ };
+
+ lcs_ta_req_send(lcs_ta_req, &bsslap_ta_resp);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+void lcs_ta_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ struct bssap_le_pdu bsslap_abort;
+
+ bsslap_abort = (struct bssap_le_pdu) {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = {
+ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
+ .conn_oriented_info = {
+ .apdu = {
+ .msg_type = BSSLAP_MSGT_ABORT,
+ .abort = BSSLAP_CAUSE_OTHER_RADIO_EVT_FAIL,
+ },
+ },
+ },
+ };
+
+ lcs_ta_req_send(lcs_ta_req, &bsslap_abort);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+}
+
+void lcs_ta_req_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct lcs_ta_req *lcs_ta_req = fi->priv;
+ if (lcs_ta_req->loc_req->ta_req == lcs_ta_req)
+ lcs_ta_req->loc_req->ta_req = NULL;
+ /* FSM termination will dispatch LCS_LOC_REQ_EV_TA_REQ_END to the lcs_loc_req FSM */
+}
+
+
+#define S(x) (1 << (x))
+
+static const struct osmo_fsm_state lcs_ta_req_fsm_states[] = {
+ [LCS_TA_REQ_ST_INIT] = {
+ .name = "init",
+ .out_state_mask = 0
+ | S(LCS_TA_REQ_ST_WAIT_TA)
+ | S(LCS_TA_REQ_ST_GOT_TA)
+ ,
+ },
+ [LCS_TA_REQ_ST_WAIT_TA] = {
+ .name = "wait_ta",
+ .in_event_mask = 0
+ | S(LCS_TA_REQ_EV_GOT_TA)
+ | S(LCS_TA_REQ_EV_ABORT)
+ ,
+ .out_state_mask = 0
+ | S(LCS_TA_REQ_ST_GOT_TA)
+ | S(LCS_TA_REQ_ST_FAILED)
+ ,
+ .onenter = lcs_ta_req_wait_ta_onenter,
+ .action = lcs_ta_req_wait_ta_action,
+ },
+ [LCS_TA_REQ_ST_GOT_TA] = {
+ .name = "got_ta",
+ .in_event_mask = 0
+ ,
+ .out_state_mask = 0
+ ,
+ .onenter = lcs_ta_req_got_ta_onenter,
+ },
+ [LCS_TA_REQ_ST_FAILED] = {
+ .name = "failed",
+ .onenter = lcs_ta_req_failed_onenter,
+ },
+};
+
+static struct osmo_fsm lcs_ta_req_fsm = {
+ .name = "lcs_ta_req",
+ .states = lcs_ta_req_fsm_states,
+ .num_states = ARRAY_SIZE(lcs_ta_req_fsm_states),
+ .log_subsys = DLCS,
+ .event_names = lcs_ta_req_fsm_event_names,
+ .timer_cb = lcs_ta_req_fsm_timer_cb,
+ .cleanup = lcs_ta_req_fsm_cleanup,
+};
+
+static __attribute__((constructor)) void lcs_ta_req_fsm_register(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&lcs_ta_req_fsm) == 0);
+}
diff --git a/src/osmo-bsc/meas_feed.c b/src/osmo-bsc/meas_feed.c
index 8450f69ca..b18478ff4 100644
--- a/src/osmo-bsc/meas_feed.c
+++ b/src/osmo-bsc/meas_feed.c
@@ -6,7 +6,7 @@
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
-#include <osmocom/core/write_queue.h>
+#include <osmocom/core/osmo_io.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
@@ -19,15 +19,18 @@
#include <osmocom/bsc/meas_feed.h>
#include <osmocom/bsc/vty.h>
#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/lchan.h>
struct meas_feed_state {
- struct osmo_wqueue wqueue;
+ struct osmo_io_fd *io_fd;
char scenario[31+1];
char *dst_host;
uint16_t dst_port;
+ size_t txqueue_max;
};
-static struct meas_feed_state g_mfs = {};
+static struct meas_feed_state g_mfs = { .txqueue_max = MEAS_FEED_TXQUEUE_MAX_LEN_DEFAULT };
static int process_meas_rep(struct gsm_meas_rep *mr)
{
@@ -35,6 +38,8 @@ static int process_meas_rep(struct gsm_meas_rep *mr)
struct meas_feed_meas *mfm;
struct bsc_subscr *bsub;
+ OSMO_ASSERT(g_mfs.io_fd != NULL);
+
/* ignore measurements as long as we don't know who it is */
if (!mr->lchan) {
LOGP(DMEAS, LOGL_DEBUG, "meas_feed: no lchan, not sending report\n");
@@ -47,7 +52,7 @@ static int process_meas_rep(struct gsm_meas_rep *mr)
bsub = mr->lchan->conn->bsub;
- msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed");
+ msg = msgb_alloc(sizeof(struct meas_feed_meas), "meas_feed_msg");
if (!msg)
return 0;
@@ -82,7 +87,7 @@ static int process_meas_rep(struct gsm_meas_rep *mr)
mfm->ss_nr = mr->lchan->nr;
/* and send it to the socket */
- if (osmo_wqueue_enqueue(&g_mfs.wqueue, msg) != 0) {
+ if (osmo_iofd_write_msgb(g_mfs.io_fd, msg)) {
LOGP(DMEAS, LOGL_ERROR, "meas_feed %s: sending measurement report failed\n",
gsm_lchan_name(mr->lchan));
msgb_free(msg);
@@ -107,64 +112,54 @@ static int meas_feed_sig_cb(unsigned int subsys, unsigned int signal,
return 0;
}
-static int feed_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+static void meas_feed_close(void)
{
- return write(ofd->fd, msgb_data(msg), msgb_length(msg));
+ if (g_mfs.io_fd == NULL)
+ return;
+ osmo_signal_unregister_handler(SS_LCHAN, meas_feed_sig_cb, NULL);
+ osmo_iofd_close(g_mfs.io_fd);
+ osmo_iofd_free(g_mfs.io_fd);
+ g_mfs.io_fd = NULL;
}
-static int feed_read_cb(struct osmo_fd *ofd)
+static void meas_feed_noop_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg)
{
- int rc;
- char buf[256];
-
- rc = read(ofd->fd, buf, sizeof(buf));
- ofd->fd &= ~BSC_FD_READ;
-
- return rc;
}
int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port)
{
int rc;
- int already_initialized = 0;
-
- if (g_mfs.wqueue.bfd.fd)
- already_initialized = 1;
-
-
- if (already_initialized &&
- !strcmp(dst_host, g_mfs.dst_host) &&
- dst_port == g_mfs.dst_port)
- return 0;
-
- if (!already_initialized) {
- osmo_wqueue_init(&g_mfs.wqueue, 10);
- g_mfs.wqueue.write_cb = feed_write_cb;
- g_mfs.wqueue.read_cb = feed_read_cb;
- osmo_signal_register_handler(SS_LCHAN, meas_feed_sig_cb, NULL);
- LOGP(DMEAS, LOGL_DEBUG, "meas_feed: registered signal callback\n");
+ /* osmo_io code throws an error if 'write_cb' is NULL, so we set a no-op */
+ struct osmo_io_ops meas_feed_oio = {
+ .read_cb = NULL,
+ .write_cb = meas_feed_noop_cb,
+ .segmentation_cb = NULL
+ };
+ /* Already initialized */
+ if (g_mfs.io_fd != NULL) {
+ /* No change needed, do nothing */
+ if (!strcmp(dst_host, g_mfs.dst_host) && dst_port == g_mfs.dst_port)
+ return 0;
+ meas_feed_close();
}
- if (already_initialized) {
- osmo_wqueue_clear(&g_mfs.wqueue);
- osmo_fd_unregister(&g_mfs.wqueue.bfd);
- close(g_mfs.wqueue.bfd.fd);
- /* don't set to zero, as that would mean 'not yet initialized' */
- g_mfs.wqueue.bfd.fd = -1;
+ rc = osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, dst_host, dst_port, OSMO_SOCK_F_CONNECT);
+ if (rc < 0) {
+ osmo_signal_unregister_handler(SS_LCHAN, meas_feed_sig_cb, NULL);
+ return rc;
}
- rc = osmo_sock_init_ofd(&g_mfs.wqueue.bfd, AF_UNSPEC, SOCK_DGRAM,
- IPPROTO_UDP, dst_host, dst_port,
- OSMO_SOCK_F_CONNECT);
- if (rc < 0)
+ g_mfs.io_fd = osmo_iofd_setup(NULL, rc, "meas_iofd", OSMO_IO_FD_MODE_READ_WRITE, &meas_feed_oio, NULL);
+ if (!g_mfs.io_fd)
+ return -1;
+ osmo_iofd_set_txqueue_max_length(g_mfs.io_fd, g_mfs.txqueue_max);
+ if ((rc = osmo_iofd_register(g_mfs.io_fd, rc)))
return rc;
- g_mfs.wqueue.bfd.when &= ~BSC_FD_READ;
-
- if (g_mfs.dst_host)
- talloc_free(g_mfs.dst_host);
- g_mfs.dst_host = talloc_strdup(NULL, dst_host);
+ osmo_talloc_replace_string(NULL, &g_mfs.dst_host, dst_host);
g_mfs.dst_port = dst_port;
-
+ osmo_signal_register_handler(SS_LCHAN, meas_feed_sig_cb, NULL);
+ LOGP(DMEAS, LOGL_DEBUG, "meas_feed: started %s\n",
+ osmo_sock_get_name2(osmo_iofd_get_fd(g_mfs.io_fd)));
return 0;
}
@@ -174,6 +169,18 @@ void meas_feed_cfg_get(char **host, uint16_t *port)
*host = g_mfs.dst_host;
}
+void meas_feed_txqueue_max_length_set(unsigned int max_length)
+{
+ g_mfs.txqueue_max = max_length;
+ if (g_mfs.io_fd)
+ osmo_iofd_set_txqueue_max_length(g_mfs.io_fd, max_length);
+}
+
+unsigned int meas_feed_txqueue_max_length_get(void)
+{
+ return g_mfs.txqueue_max;
+}
+
void meas_feed_scenario_set(const char *name)
{
osmo_strlcpy(g_mfs.scenario, name, sizeof(g_mfs.scenario));
diff --git a/src/osmo-bsc/meas_rep.c b/src/osmo-bsc/meas_rep.c
index 73d9a1f21..776c610df 100644
--- a/src/osmo-bsc/meas_rep.c
+++ b/src/osmo-bsc/meas_rep.c
@@ -31,11 +31,15 @@ static int get_field(const struct gsm_meas_rep *rep,
case MEAS_REP_DL_RXLEV_FULL:
if (!(rep->flags & MEAS_REP_F_DL_VALID))
return -EINVAL;
- return rep->dl.full.rx_lev;
+ /* Add BS Power value to rxlev: improve the RXLEV value by the amount of power that the BTS is reducing
+ * transmission. Note that bs_power is coded as dB, a positive value indicating the amount of power reduction
+ * on the downlink; rxlev is coded in dB, where a higher number means stronger signal. */
+ return rep->dl.full.rx_lev + rep->bs_power_db;
case MEAS_REP_DL_RXLEV_SUB:
if (!(rep->flags & MEAS_REP_F_DL_VALID))
return -EINVAL;
- return rep->dl.sub.rx_lev;
+ /* Apply BS Power as explained above */
+ return rep->dl.sub.rx_lev + rep->bs_power_db;
case MEAS_REP_DL_RXQUAL_FULL:
if (!(rep->flags & MEAS_REP_F_DL_VALID))
return -EINVAL;
@@ -76,9 +80,53 @@ unsigned int calc_initial_idx(unsigned int array_size,
return idx;
}
-/* obtain an average over the last 'num' fields in the meas reps */
+static inline enum meas_rep_field choose_meas_rep_field(enum tdma_meas_field field, enum tdma_meas_dir dir,
+ enum tdma_meas_set set, const struct gsm_meas_rep *meas_rep)
+{
+ if (set == TDMA_MEAS_SET_AUTO) {
+ bool dtx_in_use;
+ dtx_in_use = (meas_rep->flags & ((dir == TDMA_MEAS_DIR_UL) ? MEAS_REP_F_UL_DTX : MEAS_REP_F_DL_DTX));
+ set = (dtx_in_use ? TDMA_MEAS_SET_SUB : TDMA_MEAS_SET_FULL);
+ }
+
+ osmo_static_assert(TDMA_MEAS_FIELD_RXLEV >= 0 && TDMA_MEAS_FIELD_RXLEV <= 1
+ && TDMA_MEAS_FIELD_RXQUAL >= 0 && TDMA_MEAS_FIELD_RXQUAL <= 1
+ && TDMA_MEAS_DIR_UL >= 0 && TDMA_MEAS_DIR_UL <= 1
+ && TDMA_MEAS_DIR_DL >= 0 && TDMA_MEAS_DIR_DL <= 1
+ && TDMA_MEAS_SET_FULL >= 0 && TDMA_MEAS_SET_FULL <= 1
+ && TDMA_MEAS_SET_SUB >= 0 && TDMA_MEAS_SET_SUB <= 1,
+ choose_meas_rep_field__mux_macro_input_ranges);
+#define MUX(FIELD, DIR, SET) ((FIELD) + ((DIR) << 1) + ((SET) << 2))
+
+ switch (MUX(field, dir, set)) {
+ case MUX(TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_FULL):
+ return MEAS_REP_UL_RXLEV_FULL;
+ case MUX(TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_SUB):
+ return MEAS_REP_UL_RXLEV_SUB;
+ case MUX(TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_FULL):
+ return MEAS_REP_DL_RXLEV_FULL;
+ case MUX(TDMA_MEAS_FIELD_RXLEV, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_SUB):
+ return MEAS_REP_DL_RXLEV_SUB;
+ case MUX(TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_FULL):
+ return MEAS_REP_UL_RXQUAL_FULL;
+ case MUX(TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_UL, TDMA_MEAS_SET_SUB):
+ return MEAS_REP_UL_RXQUAL_SUB;
+ case MUX(TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_FULL):
+ return MEAS_REP_DL_RXQUAL_FULL;
+ case MUX(TDMA_MEAS_FIELD_RXQUAL, TDMA_MEAS_DIR_DL, TDMA_MEAS_SET_SUB):
+ return MEAS_REP_DL_RXQUAL_SUB;
+ default:
+ OSMO_ASSERT(false);
+ }
+
+#undef MUX
+}
+
+/* obtain an average over the last 'num' fields in the meas reps. For 'field', pass either DL_RXLEV or DL_RXQUAL, and
+ * by tdma_meas_set, choose between full, subset or automatic choice of set. */
int get_meas_rep_avg(const struct gsm_lchan *lchan,
- enum meas_rep_field field, unsigned int num)
+ enum tdma_meas_field field, enum tdma_meas_dir dir, enum tdma_meas_set set,
+ unsigned int num)
{
unsigned int i, idx;
int avg = 0, valid_num = 0;
@@ -94,7 +142,11 @@ int get_meas_rep_avg(const struct gsm_lchan *lchan,
for (i = 0; i < num; i++) {
int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep);
- int val = get_field(&lchan->meas_rep[j], field);
+ enum meas_rep_field use_field;
+ int val;
+
+ use_field = choose_meas_rep_field(field, dir, set, &lchan->meas_rep[j]);
+ val = get_field(&lchan->meas_rep[j], use_field);
if (val >= 0) {
avg += val;
@@ -110,8 +162,8 @@ int get_meas_rep_avg(const struct gsm_lchan *lchan,
/* Check if N out of M last values for FIELD are >= bd */
int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan,
- enum meas_rep_field field,
- unsigned int n, unsigned int m, int be)
+ enum tdma_meas_field field, enum tdma_meas_dir dir, enum tdma_meas_set set,
+ unsigned int n, unsigned int m, int be)
{
unsigned int i, idx;
int count = 0;
@@ -121,7 +173,11 @@ int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan,
for (i = 0; i < m; i++) {
int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep);
- int val = get_field(&lchan->meas_rep[j], field);
+ enum meas_rep_field use_field;
+ int val;
+
+ use_field = choose_meas_rep_field(field, dir, set, &lchan->meas_rep[j]);
+ val = get_field(&lchan->meas_rep[j], use_field);
if (val >= be) /* implies that val < 0 will not count */
count++;
@@ -132,3 +188,10 @@ int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan,
return 0;
}
+
+const struct value_string tdma_meas_set_names[] = {
+ { TDMA_MEAS_SET_FULL, "full" },
+ { TDMA_MEAS_SET_SUB, "subset" },
+ { TDMA_MEAS_SET_AUTO, "auto" },
+ {}
+};
diff --git a/src/osmo-bsc/mgw_endpoint_fsm.c b/src/osmo-bsc/mgw_endpoint_fsm.c
deleted file mode 100644
index fc49886c3..000000000
--- a/src/osmo-bsc/mgw_endpoint_fsm.c
+++ /dev/null
@@ -1,777 +0,0 @@
-/* osmo-bsc API to manage all sides of an MGW endpoint
- *
- * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
- * All Rights Reserved
- *
- * Author: Neels Hofmeyr <neels@hofmeyr.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 <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-
-#include <osmocom/core/fsm.h>
-#include <osmocom/core/byteswap.h>
-#include <osmocom/netif/rtp.h>
-
-#include <osmocom/bsc/debug.h>
-#include <osmocom/bsc/gsm_timers.h>
-
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
-#include <osmocom/bsc/lchan_fsm.h>
-#include <osmocom/bsc/gsm_data.h>
-
-#define LOG_CI(ci, level, fmt, args...) do { \
- if (!ci || !ci->mgwep) \
- LOGP(DLGLOBAL, level, "(unknown MGW endpoint) " fmt, ## args); \
- else \
- LOG_MGWEP(ci->mgwep, level, "CI[%d] %s%s%s: " fmt, \
- (int)(ci - ci->mgwep->ci), \
- ci->label ? : "-", \
- ci->mgcp_ci_str[0] ? " CI=" : "", \
- ci->mgcp_ci_str[0] ? ci->mgcp_ci_str : "", \
- ## args); \
- } while(0)
-
-#define LOG_CI_VERB(ci, level, fmt, args...) do { \
- if (ci->verb_info.addr) \
- LOG_CI(ci, level, "%s %s:%u: " fmt, \
- mgcp_verb_name(ci->verb), ci->verb_info.addr, ci->verb_info.port, \
- ## args); \
- else \
- LOG_CI(ci, level, "%s: " fmt, \
- mgcp_verb_name(ci->verb), \
- ## args); \
- } while(0)
-
-#define FIRST_CI_EVENT (_MGWEP_EV_LAST + (_MGWEP_EV_LAST & 1)) /* rounded up to even nr */
-#define USABLE_CI ((32 - FIRST_CI_EVENT)/2)
-#define EV_TO_CI_IDX(event) ((event - FIRST_CI_EVENT) / 2)
-
-#define CI_EV_SUCCESS(ci) (FIRST_CI_EVENT + (((ci) - ci->mgwep->ci) * 2))
-#define CI_EV_FAILURE(ci) (CI_EV_SUCCESS(ci) + 1)
-
-static struct osmo_fsm mgwep_fsm;
-
-struct mgwep_ci {
- struct mgw_endpoint *mgwep;
-
- bool occupied;
- char label[64];
- struct osmo_fsm_inst *mgcp_client_fi;
-
- bool pending;
- bool sent;
- enum mgcp_verb verb;
- struct mgcp_conn_peer verb_info;
- struct osmo_fsm_inst *notify;
- uint32_t notify_success;
- uint32_t notify_failure;
- void *notify_data;
-
- bool got_port_info;
- struct mgcp_conn_peer rtp_info;
- char mgcp_ci_str[MGCP_CONN_ID_LENGTH];
-};
-
-struct mgw_endpoint {
- struct mgcp_client *mgcp_client;
- struct osmo_fsm_inst *fi;
- char endpoint[MGCP_ENDPOINT_MAXLEN];
-
- struct mgwep_ci ci[USABLE_CI];
-};
-
-static const struct value_string mgcp_verb_names[] = {
- { MGCP_VERB_CRCX, "CRCX" },
- { MGCP_VERB_MDCX, "MDCX" },
- { MGCP_VERB_DLCX, "DLCX" },
- { MGCP_VERB_AUEP, "AUEP" },
- { MGCP_VERB_RSIP, "RSIP" },
- {}
-};
-
-static inline const char *mgcp_verb_name(enum mgcp_verb val)
-{ return get_value_string(mgcp_verb_names, val); }
-
-static struct mgwep_ci *mgwep_check_ci(struct mgwep_ci *ci)
-{
- if (!ci)
- return NULL;
- if (!ci->mgwep)
- return NULL;
- if (ci < ci->mgwep->ci || ci >= &ci->mgwep->ci[USABLE_CI])
- return NULL;
- return ci;
-}
-
-static struct mgwep_ci *mgwep_ci_for_event(struct mgw_endpoint *mgwep, uint32_t event)
-{
- int idx;
- if (event < FIRST_CI_EVENT)
- return NULL;
- idx = EV_TO_CI_IDX(event);
- if (idx >= sizeof(mgwep->ci))
- return NULL;
- return mgwep_check_ci(&mgwep->ci[idx]);
-}
-
-const char *mgw_endpoint_name(const struct mgw_endpoint *mgwep)
-{
- if (!mgwep)
- return "NULL";
- if (mgwep->endpoint[0])
- return mgwep->endpoint;
- return osmo_fsm_inst_name(mgwep->fi);
-}
-
-const char *mgcp_conn_peer_name(const struct mgcp_conn_peer *info)
-{
- /* I'd be fine with a smaller buffer and accept truncation, but gcc possibly refuses to build if
- * this buffer is too small. */
- static char buf[1024];
-
- if (!info)
- return "NULL";
-
- if (info->endpoint[0]
- && info->addr[0])
- snprintf(buf, sizeof(buf), "%s:%s:%u",
- info->endpoint, info->addr, info->port);
- else if (info->endpoint[0])
- snprintf(buf, sizeof(buf), "%s", info->endpoint);
- else if (info->addr[0])
- snprintf(buf, sizeof(buf), "%s:%u", info->addr, info->port);
- else
- return "empty";
- return buf;
-}
-
-const char *mgwep_ci_name(const struct mgwep_ci *ci)
-{
- const struct mgcp_conn_peer *rtp_info;
-
- if (!ci)
- return "NULL";
-
- rtp_info = mgwep_ci_get_rtp_info(ci);
-
- if (rtp_info)
- return mgcp_conn_peer_name(rtp_info);
- return mgw_endpoint_name(ci->mgwep);
-}
-
-static struct value_string mgwep_fsm_event_names[33] = {};
-
-static char mgwep_fsm_event_name_bufs[32][32] = {};
-
-static void fill_event_names()
-{
- int i;
- for (i = 0; i < (ARRAY_SIZE(mgwep_fsm_event_names) - 1); i++) {
- if (i < _MGWEP_EV_LAST)
- continue;
- if (i < FIRST_CI_EVENT || EV_TO_CI_IDX(i) > USABLE_CI) {
- mgwep_fsm_event_names[i] = (struct value_string){i, "Unused"};
- continue;
- }
- snprintf(mgwep_fsm_event_name_bufs[i], sizeof(mgwep_fsm_event_name_bufs[i]),
- "MGW Response for CI #%d", EV_TO_CI_IDX(i));
- mgwep_fsm_event_names[i] = (struct value_string){i, mgwep_fsm_event_name_bufs[i]};
- }
-}
-
-static struct T_def *g_T_defs = NULL;
-
-void mgw_endpoint_fsm_init(struct T_def *T_defs)
-{
- g_T_defs = T_defs;
- OSMO_ASSERT(osmo_fsm_register(&mgwep_fsm) == 0);
- fill_event_names();
-}
-
-struct mgw_endpoint *mgwep_fi_mgwep(struct osmo_fsm_inst *fi)
-{
- OSMO_ASSERT(fi);
- OSMO_ASSERT(fi->fsm == &mgwep_fsm);
- OSMO_ASSERT(fi->priv);
- return fi->priv;
-}
-
-struct mgw_endpoint *mgw_endpoint_alloc(struct osmo_fsm_inst *parent, uint32_t parent_term_event,
- struct mgcp_client *mgcp_client,
- const char *fsm_id,
- const char *endpoint_str_fmt, ...)
-{
- va_list ap;
- struct osmo_fsm_inst *fi;
- struct mgw_endpoint *mgwep;
- int rc;
-
- if (!mgcp_client)
- return NULL;
-
- /* use mgcp_client as talloc ctx, so that the conn, lchan, ts can deallocate while MGCP DLCX are
- * still going on. */
- fi = osmo_fsm_inst_alloc_child(&mgwep_fsm, parent, parent_term_event);
- OSMO_ASSERT(fi);
-
- osmo_fsm_inst_update_id(fi, fsm_id);
-
- mgwep = talloc_zero(fi, struct mgw_endpoint);
- OSMO_ASSERT(mgwep);
-
- mgwep->mgcp_client = mgcp_client;
- mgwep->fi = fi;
- mgwep->fi->priv = mgwep;
-
- va_start(ap, endpoint_str_fmt);
- rc = vsnprintf(mgwep->endpoint, sizeof(mgwep->endpoint), endpoint_str_fmt, ap);
- va_end(ap);
-
- if (rc <= 0 || rc >= sizeof(mgwep->endpoint)) {
- LOG_MGWEP(mgwep, LOGL_ERROR, "Endpoint name too long or too short: %s\n",
- mgwep->endpoint);
- osmo_fsm_inst_term(mgwep->fi, OSMO_FSM_TERM_ERROR, 0);
- return NULL;
- }
-
- return mgwep;
-}
-
-struct mgwep_ci *mgw_endpoint_ci_add(struct mgw_endpoint *mgwep,
- const char *label_fmt, ...)
-{
- va_list ap;
- int i;
- struct mgwep_ci *ci;
-
- for (i = 0; i < USABLE_CI; i++) {
- ci = &mgwep->ci[i];
-
- if (ci->occupied || ci->mgcp_client_fi)
- continue;
-
- *ci = (struct mgwep_ci){
- .mgwep = mgwep,
- .occupied = true,
- };
- if (label_fmt) {
- va_start(ap, label_fmt);
- vsnprintf(ci->label, sizeof(ci->label), label_fmt, ap);
- va_end(ap);
- }
- return ci;
- }
-
- LOG_MGWEP(mgwep, LOGL_ERROR,
- "Cannot allocate another endpoint, all "
- OSMO_STRINGIFY_VAL(USABLE_CI) " are in use\n");
-
- return NULL;
-}
-
-static void mgwep_fsm_check_state_chg_after_response(struct osmo_fsm_inst *fi);
-
-static void on_failure(struct mgwep_ci *ci)
-{
- if (!ci->occupied)
- return;
-
- if (ci->notify)
- osmo_fsm_inst_dispatch(ci->notify, ci->notify_failure, ci->notify_data);
-
- *ci = (struct mgwep_ci){
- .mgwep = ci->mgwep,
- };
-
-
- mgwep_fsm_check_state_chg_after_response(ci->mgwep->fi);
-}
-
-static void on_success(struct mgwep_ci *ci, void *data)
-{
- struct mgcp_conn_peer *rtp_info;
-
- if (!ci->occupied)
- return;
-
- ci->pending = false;
-
- switch (ci->verb) {
- case MGCP_VERB_CRCX:
- /* If we sent a wildcarded endpoint name on CRCX, we need to store the resulting endpoint
- * name here. Also, we receive the MGW's RTP port information. */
- rtp_info = data;
- OSMO_ASSERT(rtp_info);
- ci->got_port_info = true;
- ci->rtp_info = *rtp_info;
- osmo_strlcpy(ci->mgcp_ci_str, mgcp_conn_get_ci(ci->mgcp_client_fi),
- sizeof(ci->mgcp_ci_str));
- if (rtp_info->endpoint[0]) {
- int rc;
- rc = osmo_strlcpy(ci->mgwep->endpoint, rtp_info->endpoint,
- sizeof(ci->mgwep->endpoint));
- if (rc <= 0 || rc >= sizeof(ci->mgwep->endpoint)) {
- LOG_CI(ci, LOGL_ERROR, "Unable to copy endpoint name '%s'\n",
- rtp_info->endpoint);
- mgw_endpoint_ci_dlcx(ci);
- on_failure(ci);
- return;
- }
- }
- break;
-
- default:
- break;
- }
-
- LOG_CI(ci, LOGL_DEBUG, "received successful response to %s RTP=%s%s\n",
- mgcp_verb_name(ci->verb),
- mgcp_conn_peer_name(ci->got_port_info? &ci->rtp_info : NULL),
- ci->notify ? "" : " (not sending a notification)");
-
- if (ci->notify)
- osmo_fsm_inst_dispatch(ci->notify, ci->notify_success, ci->notify_data);
-
- mgwep_fsm_check_state_chg_after_response(ci->mgwep->fi);
-}
-
-const struct mgcp_conn_peer *mgwep_ci_get_rtp_info(const struct mgwep_ci *ci)
-{
- ci = mgwep_check_ci((struct mgwep_ci*)ci);
- if (!ci)
- return NULL;
- if (!ci->got_port_info)
- return NULL;
- return &ci->rtp_info;
-}
-
-bool mgwep_ci_get_crcx_info_to_sockaddr(const struct mgwep_ci *ci, struct sockaddr_storage *dest)
-{
- const struct mgcp_conn_peer *rtp_info;
- struct sockaddr_in *sin;
-
- rtp_info = mgwep_ci_get_rtp_info(ci);
- if (!rtp_info)
- return false;
-
- sin = (struct sockaddr_in *)dest;
-
- sin->sin_family = AF_INET;
- sin->sin_addr.s_addr = inet_addr(rtp_info->addr);
- sin->sin_port = osmo_ntohs(rtp_info->port);
- return true;
-}
-
-
-static const struct state_timeout mgwep_fsm_timeouts[32] = {
- [MGWEP_ST_WAIT_MGW_RESPONSE] = { .T=23042 },
-};
-
-/* Transition to a state, using the T timer defined in assignment_fsm_timeouts.
- * The actual timeout value is in turn obtained from g_T_defs.
- * Assumes local variable fi exists. */
-#define mgwep_fsm_state_chg(state) \
- fsm_inst_state_chg_T(fi, state, mgwep_fsm_timeouts, g_T_defs, 5)
-
-void mgw_endpoint_ci_request(struct mgwep_ci *ci,
- enum mgcp_verb verb, const struct mgcp_conn_peer *verb_info,
- struct osmo_fsm_inst *notify,
- uint32_t event_success, uint32_t event_failure,
- void *notify_data)
-{
- struct mgw_endpoint *mgwep;
- struct osmo_fsm_inst *fi;
- struct mgwep_ci cleared_ci;
- ci = mgwep_check_ci(ci);
-
- if (!ci) {
- LOGP(DLGLOBAL, LOGL_ERROR, "Invalid MGW endpoint request: no ci\n");
- goto dispatch_error;
- }
- if (!verb_info && verb != MGCP_VERB_DLCX) {
- LOG_CI(ci, LOGL_ERROR, "Invalid MGW endpoint request: missing verb details for %s\n",
- mgcp_verb_name(verb));
- goto dispatch_error;
- }
- if ((verb < 0) || (verb > MGCP_VERB_RSIP)) {
- LOG_CI(ci, LOGL_ERROR, "Invalid MGW endpoint request: unknown verb: %s\n",
- mgcp_verb_name(verb));
- goto dispatch_error;
- }
-
- mgwep = ci->mgwep;
- fi = mgwep->fi;
-
- /* Clear volatile state by explicitly keeping those that should remain. Because we can't assign
- * the char[] directly, dance through cleared_ci and copy back. */
- cleared_ci = (struct mgwep_ci){
- .mgwep = mgwep,
- .mgcp_client_fi = ci->mgcp_client_fi,
- .got_port_info = ci->got_port_info,
- .rtp_info = ci->rtp_info,
-
- .occupied = true,
- /* .pending = true follows below */
- .verb = verb,
- .notify = notify,
- .notify_success = event_success,
- .notify_failure = event_failure,
- .notify_data = notify_data,
- };
- osmo_strlcpy(cleared_ci.label, ci->label, sizeof(cleared_ci.label));
- osmo_strlcpy(cleared_ci.mgcp_ci_str, ci->mgcp_ci_str, sizeof(cleared_ci.mgcp_ci_str));
- *ci = cleared_ci;
-
- LOG_CI_VERB(ci, LOGL_DEBUG, "notify=%s\n", osmo_fsm_inst_name(ci->notify));
-
- if (verb_info)
- ci->verb_info = *verb_info;
-
- if (mgwep->endpoint[0]) {
- if (ci->verb_info.endpoint[0] && strcmp(ci->verb_info.endpoint, mgwep->endpoint))
- LOG_CI(ci, LOGL_ERROR,
- "Warning: Requested %s on endpoint %s, but this CI is on endpoint %s."
- " Using the proper endpoint instead.\n",
- mgcp_verb_name(verb), ci->verb_info.endpoint, mgwep->endpoint);
- osmo_strlcpy(ci->verb_info.endpoint, mgwep->endpoint, sizeof(ci->verb_info.endpoint));
- }
-
- switch (ci->verb) {
- case MGCP_VERB_CRCX:
- if (ci->mgcp_client_fi) {
- LOG_CI(ci, LOGL_ERROR, "CRCX can be called only once per MGW endpoint CI\n");
- on_failure(ci);
- return;
- }
- break;
-
- case MGCP_VERB_MDCX:
- case MGCP_VERB_DLCX:
- if (!ci->mgcp_client_fi) {
- LOG_CI_VERB(ci, LOGL_ERROR, "The first verb on an unused MGW endpoint CI must be CRCX, not %s\n",
- mgcp_verb_name(ci->verb));
- on_failure(ci);
- return;
- }
- break;
-
- default:
- LOG_CI(ci, LOGL_ERROR, "This verb is not supported: %s\n", mgcp_verb_name(ci->verb));
- on_failure(ci);
- return;
- }
-
- ci->pending = true;
-
- LOG_CI_VERB(ci, LOGL_DEBUG, "Scheduling\n");
-
- if (mgwep->fi->state != MGWEP_ST_WAIT_MGW_RESPONSE)
- mgwep_fsm_state_chg(MGWEP_ST_WAIT_MGW_RESPONSE);
-
- return;
-dispatch_error:
- if (notify)
- osmo_fsm_inst_dispatch(notify, event_failure, notify_data);
-}
-
-static int send_verb(struct mgwep_ci *ci)
-{
- int rc;
- struct mgw_endpoint *mgwep = ci->mgwep;
-
- if (!ci->occupied || !ci->pending || ci->sent)
- return 0;
-
- switch (ci->verb) {
-
- case MGCP_VERB_CRCX:
- OSMO_ASSERT(!ci->mgcp_client_fi);
- LOG_CI_VERB(ci, LOGL_DEBUG, "Sending\n");
- ci->mgcp_client_fi = mgcp_conn_create(mgwep->mgcp_client, mgwep->fi,
- CI_EV_FAILURE(ci), CI_EV_SUCCESS(ci),
- &ci->verb_info);
- ci->sent = true;
- if (!ci->mgcp_client_fi){
- LOG_CI_VERB(ci, LOGL_ERROR, "Cannot send\n");
- on_failure(ci);
- }
- osmo_fsm_inst_update_id(ci->mgcp_client_fi, ci->label);
- break;
-
- case MGCP_VERB_MDCX:
- OSMO_ASSERT(ci->mgcp_client_fi);
- LOG_CI_VERB(ci, LOGL_DEBUG, "Sending\n");
- rc = mgcp_conn_modify(ci->mgcp_client_fi, CI_EV_SUCCESS(ci), &ci->verb_info);
- ci->sent = true;
- if (rc) {
- LOG_CI_VERB(ci, LOGL_ERROR, "Cannot send (rc=%d %s)\n", rc, strerror(-rc));
- on_failure(ci);
- }
- break;
-
- case MGCP_VERB_DLCX:
- LOG_CI(ci, LOGL_DEBUG, "Sending MGCP: %s %s\n",
- mgcp_verb_name(ci->verb), ci->mgcp_ci_str);
- /* The way this is designed, we actually need to forget all about the ci right away. */
- mgcp_conn_delete(ci->mgcp_client_fi);
- if (ci->notify)
- osmo_fsm_inst_dispatch(ci->notify, ci->notify_success, ci->notify_data);
- *ci = (struct mgwep_ci){
- .mgwep = mgwep,
- };
- break;
-
- default:
- OSMO_ASSERT(false);
- }
-
- return 1;
-}
-
-void mgw_endpoint_clear(struct mgw_endpoint *mgwep)
-{
- if (!mgwep)
- return;
- osmo_fsm_inst_term(mgwep->fi, OSMO_FSM_TERM_REGULAR, 0);
-}
-
-static void mgwep_count(struct mgw_endpoint *mgwep, int *occupied, int *pending_not_sent,
- int *waiting_for_response)
-{
- int i;
-
- if (occupied)
- *occupied = 0;
-
- if (pending_not_sent)
- *pending_not_sent = 0;
-
- if (waiting_for_response)
- *waiting_for_response = 0;
-
- for (i = 0; i < ARRAY_SIZE(mgwep->ci); i++) {
- struct mgwep_ci *ci = &mgwep->ci[i];
- if (ci->occupied) {
- if (occupied)
- (*occupied)++;
- } else
- continue;
-
- if (ci->pending)
- LOG_CI_VERB(ci, LOGL_DEBUG, "%s\n",
- ci->sent ? "waiting for response" : "waiting to be sent");
- else
- LOG_CI_VERB(ci, LOGL_DEBUG, "%s\n", mgcp_conn_peer_name(mgwep_ci_get_rtp_info(ci)));
-
- if (ci->pending && ci->sent)
- if (waiting_for_response)
- (*waiting_for_response)++;
- if (ci->pending && !ci->sent)
- if (pending_not_sent)
- (*pending_not_sent)++;
- }
-}
-
-static void mgwep_fsm_check_state_chg_after_response(struct osmo_fsm_inst *fi)
-{
- int waiting_for_response;
- int occupied;
- struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
-
- mgwep_count(mgwep, &occupied, NULL, &waiting_for_response);
- LOG_MGWEP(mgwep, LOGL_DEBUG, "CI in use: %d, waiting for response: %d\n", occupied, waiting_for_response);
-
- if (!occupied) {
- /* All CI have been released. The endpoint no longer exists. Notify the parent FSM, by
- * terminating. */
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
- return;
- }
-
- if (!waiting_for_response) {
- if (fi->state != MGWEP_ST_IN_USE)
- mgwep_fsm_state_chg(MGWEP_ST_IN_USE);
- return;
- }
-
-}
-
-static void mgwep_fsm_wait_mgw_response_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
- int count = 0;
- int i;
- struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
-
- for (i = 0; i < ARRAY_SIZE(mgwep->ci); i++) {
- count += send_verb(&mgwep->ci[i]);
- }
-
- LOG_MGWEP(mgwep, LOGL_DEBUG, "Sent messages: %d\n", count);
- mgwep_fsm_check_state_chg_after_response(fi);
-
-}
-
-static void mgwep_fsm_handle_ci_events(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
- struct mgwep_ci *ci;
- struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
- ci = mgwep_ci_for_event(mgwep, event);
- if (ci) {
- if (event == CI_EV_SUCCESS(ci))
- on_success(ci, data);
- else
- on_failure(ci);
- }
-}
-
-static void mgwep_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
- int pending_not_sent;
- struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
-
- mgwep_count(mgwep, NULL, &pending_not_sent, NULL);
- if (pending_not_sent)
- mgwep_fsm_state_chg(MGWEP_ST_WAIT_MGW_RESPONSE);
-}
-
-#define S(x) (1 << (x))
-
-static const struct osmo_fsm_state mgwep_fsm_states[] = {
- [MGWEP_ST_UNUSED] = {
- .name = "UNUSED",
- .in_event_mask = 0,
- .out_state_mask = 0
- | S(MGWEP_ST_WAIT_MGW_RESPONSE)
- ,
- },
- [MGWEP_ST_WAIT_MGW_RESPONSE] = {
- .name = "WAIT_MGW_RESPONSE",
- .onenter = mgwep_fsm_wait_mgw_response_onenter,
- .action = mgwep_fsm_handle_ci_events,
- .in_event_mask = 0xffffffff,
- .out_state_mask = 0
- | S(MGWEP_ST_IN_USE)
- ,
- },
- [MGWEP_ST_IN_USE] = {
- .name = "IN_USE",
- .onenter = mgwep_fsm_in_use_onenter,
- .action = mgwep_fsm_handle_ci_events,
- .in_event_mask = 0xffffffff, /* mgcp_client_fsm may send parent term anytime */
- .out_state_mask = 0
- | S(MGWEP_ST_WAIT_MGW_RESPONSE)
- ,
- },
-};
-
-static int mgwep_fsm_timer_cb(struct osmo_fsm_inst *fi)
-{
- int i;
- struct mgw_endpoint *mgwep = mgwep_fi_mgwep(fi);
-
- switch (fi->T) {
- default:
- for (i = 0; i < ARRAY_SIZE(mgwep->ci); i++) {
- struct mgwep_ci *ci = &mgwep->ci[i];
- if (!ci->occupied)
- continue;
- if (!(ci->pending && ci->sent))
- continue;
- on_failure(ci);
- }
- return 0;
- }
-
- return 0;
-}
-
-static struct osmo_fsm mgwep_fsm = {
- .name = "mgw-endpoint",
- .states = mgwep_fsm_states,
- .num_states = ARRAY_SIZE(mgwep_fsm_states),
- .log_subsys = DRSL,
- .event_names = mgwep_fsm_event_names,
- .timer_cb = mgwep_fsm_timer_cb,
- /* The FSM termination will automatically trigger any mgcp_client_fsm instances to DLCX. */
-};
-
-/* Depending on the channel mode and rate, return the codec type that is signalled towards the MGW. */
-enum mgcp_codecs chan_mode_to_mgcp_codec(enum gsm48_chan_mode chan_mode, bool full_rate)
-{
- switch (chan_mode) {
- case GSM48_CMODE_SPEECH_V1:
- if (full_rate)
- return CODEC_GSM_8000_1;
- return CODEC_GSMHR_8000_1;
-
- case GSM48_CMODE_SPEECH_EFR:
- return CODEC_GSMEFR_8000_1;
-
- case GSM48_CMODE_SPEECH_AMR:
- return CODEC_AMR_8000_1;
-
- default:
- return -1;
- }
-}
-
-int chan_mode_to_mgcp_bss_pt(enum mgcp_codecs codec)
-{
- switch (codec) {
- case CODEC_GSMHR_8000_1:
- return RTP_PT_GSM_HALF;
-
- case CODEC_GSMEFR_8000_1:
- return RTP_PT_GSM_EFR;
-
- case CODEC_AMR_8000_1:
- return RTP_PT_AMR;
-
- default:
- /* Not an error, we just leave it to libosmo-mgcp-client to
- * decide over the PT. */
- return -1;
- }
-}
-
-void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *lchan, bool bss_side)
-{
- enum mgcp_codecs codec = chan_mode_to_mgcp_codec(lchan->tch_mode,
- lchan->type == GSM_LCHAN_TCH_H? false : true);
- int custom_pt;
-
- if (codec < 0) {
- LOG_LCHAN(lchan, LOGL_ERROR,
- "Unable to determine MGCP codec type for %s in chan-mode %s\n",
- gsm_lchant_name(lchan->type), gsm48_chan_mode_name(lchan->tch_mode));
- verb_info->codecs_len = 0;
- return;
- }
-
- verb_info->codecs[0] = codec;
- verb_info->codecs_len = 1;
-
- /* Setup custom payload types (only for BSS side and when required) */
- custom_pt = chan_mode_to_mgcp_bss_pt(codec);
- if (bss_side && custom_pt > 0) {
- verb_info->ptmap[0].codec = codec;
- verb_info->ptmap[0].pt = custom_pt;
- verb_info->ptmap_len = 1;
- }
-}
diff --git a/src/osmo-bsc/neighbor_ident.c b/src/osmo-bsc/neighbor_ident.c
index 4a0cd47ad..3e42c5f2d 100644
--- a/src/osmo-bsc/neighbor_ident.c
+++ b/src/osmo-bsc/neighbor_ident.c
@@ -33,88 +33,247 @@
#include <osmocom/bsc/neighbor_ident.h>
-struct neighbor_ident_list {
- struct llist_head list;
-};
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
-struct neighbor_ident {
- struct llist_head entry;
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/debug.h>
- struct neighbor_ident_key key;
- struct gsm0808_cell_id_list2 val;
-};
+void bts_cell_ab(struct cell_ab *arfcn_bsic, const struct gsm_bts *bts)
+{
+ *arfcn_bsic = (struct cell_ab){
+ .arfcn = bts->c0->arfcn,
+ .bsic = bts->bsic,
+ };
+}
+
+/* Find the local gsm_bts pointer that a specific other BTS' neighbor config refers to. Return NULL if there is no such
+ * local cell in this BSS.
+ */
+int resolve_local_neighbor(struct gsm_bts **local_neighbor_p, const struct gsm_bts *from_bts,
+ const struct neighbor *neighbor)
+{
+ struct gsm_bts *bts;
+ struct gsm_bts *bts_exact = NULL;
+ struct gsm_bts *bts_wildcard = NULL;
+ *local_neighbor_p = NULL;
+
+ switch (neighbor->type) {
+ case NEIGHBOR_TYPE_BTS_NR:
+ bts = gsm_bts_num(bsc_gsmnet, neighbor->bts_nr);
+ goto check_bts;
+
+ case NEIGHBOR_TYPE_CELL_ID:
+ /* Find cell id below */
+ break;
+
+ default:
+ return -ENOTSUP;
+ }
+
+ /* NEIGHBOR_TYPE_CELL_ID */
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ struct gsm0808_cell_id cell_id;
+ gsm_bts_cell_id(&cell_id, bts);
+
+ if (gsm0808_cell_ids_match(&cell_id, &neighbor->cell_id.id, true)) {
+ if (bts_exact) {
+ LOGP(DHO, LOGL_ERROR,
+ "Neighbor config error: Multiple BTS match %s (BTS %u and BTS %u)\n",
+ gsm0808_cell_id_name_c(OTC_SELECT, &neighbor->cell_id.id),
+ bts_exact->nr, bts->nr);
+ return -EINVAL;
+ } else {
+ bts_exact = bts;
+ }
+ }
+
+ if (!bts_wildcard && gsm0808_cell_ids_match(&cell_id, &neighbor->cell_id.id, false))
+ bts_wildcard = bts;
+ }
-#define APPEND_THING(func, args...) do { \
- int remain = buflen - (pos - buf); \
- int l = func(pos, remain, ##args); \
- if (l < 0 || l > remain) \
- pos = buf + buflen; \
- else \
- pos += l; \
- } while(0)
-#define APPEND_STR(fmt, args...) APPEND_THING(snprintf, fmt, ##args)
+ bts = (bts_exact ? : bts_wildcard);
-const char *_neighbor_ident_key_name(char *buf, size_t buflen, const struct neighbor_ident_key *ni_key)
+check_bts:
+ /* A cell cannot be its own neighbor */
+ if (bts == from_bts) {
+ LOGP(DHO, LOGL_ERROR,
+ "Neighbor config error: BTS %u -> %s: this cell is configured as its own neighbor\n",
+ from_bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor));
+ return -EINVAL;
+ }
+
+ if (!bts)
+ return -ENOENT;
+
+ /* Double check whether ARFCN + BSIC config matches, if present. */
+ if (neighbor->cell_id.ab_present) {
+ struct cell_ab cell_ab;
+ bts_cell_ab(&cell_ab, bts);
+ if (!cell_ab_match(&cell_ab, &neighbor->cell_id.ab, false)) {
+ LOGP(DHO, LOGL_ERROR, "Neighbor config error: Local BTS %d matches %s, but not ARFCN+BSIC %s\n",
+ bts->nr, gsm0808_cell_id_name_c(OTC_SELECT, &neighbor->cell_id.id),
+ cell_ab_to_str_c(OTC_SELECT, &cell_ab));
+ return -EINVAL;
+ }
+ }
+
+ *local_neighbor_p = bts;
+ return 0;
+}
+
+int resolve_neighbors(struct gsm_bts **local_neighbor_p, struct gsm0808_cell_id_list2 *remote_neighbors,
+ struct gsm_bts *from_bts, const struct cell_ab *target_ab, bool log_errors)
{
- char *pos = buf;
+ struct neighbor *n;
+ struct gsm_bts *local_neighbor = NULL;
+ struct gsm0808_cell_id_list2 remotes = {};
+
+ if (local_neighbor_p)
+ *local_neighbor_p = NULL;
+ if (remote_neighbors)
+ *remote_neighbors = (struct gsm0808_cell_id_list2){ 0 };
+
+ llist_for_each_entry(n, &from_bts->neighbors, entry) {
+ struct gsm_bts *neigh_bts;
+ if (resolve_local_neighbor(&neigh_bts, from_bts, n) == 0) {
+ /* This neighbor entry is a local cell neighbor. Do ARFCN and BSIC match? */
+ struct cell_ab ab;
+ bts_cell_ab(&ab, neigh_bts);
+ if (!cell_ab_match(&ab, target_ab, false))
+ continue;
+
+ /* Found a local cell neighbor that matches the target_ab */
+
+ /* If we already found one, these are ambiguous local neighbors */
+ if (local_neighbor) {
+ if (log_errors)
+ LOGP(DHO, LOGL_ERROR, "Neighbor config error:"
+ " Local BTS %d -> %s resolves to local neighbor BTSes %u *and* %u\n",
+ from_bts->nr, cell_ab_to_str_c(OTC_SELECT, target_ab), local_neighbor->nr,
+ neigh_bts->nr);
+ return -ENOTSUP;
+ }
+ local_neighbor = neigh_bts;
+
+ } else if (n->type == NEIGHBOR_TYPE_CELL_ID && n->cell_id.ab_present) {
+ /* This neighbor entry is a remote-BSS neighbor. There may be multiple remote neighbors,
+ * collect those in a gsm0808_cell_id_list2 (remote_target_cells). A limitation is that all of
+ * them need to be of the same cell id type. */
+ struct gsm0808_cell_id_list2 add_item;
+ int rc;
+
+ if (!cell_ab_match(&n->cell_id.ab, target_ab, false))
+ continue;
+
+ /* Convert the gsm0808_cell_id to a list, so that we can use gsm0808_cell_id_list_add(). */
+ gsm0808_cell_id_to_list(&add_item, &n->cell_id.id);
+ rc = gsm0808_cell_id_list_add(&remotes, &add_item);
+ if (rc < 0) {
+ if (log_errors)
+ LOGP(DHO, LOGL_ERROR, "Neighbor config error:"
+ " Local BTS %d -> %s resolves to remote-BSS neighbor %s;"
+ " Could not store this in neighbors list %s\n",
+ from_bts->nr, cell_ab_to_str_c(OTC_SELECT, target_ab),
+ gsm0808_cell_id_name_c(OTC_SELECT, &n->cell_id.id),
+ gsm0808_cell_id_list_name_c(OTC_SELECT, &remotes));
+ return rc;
+ }
+ }
+ /* else: neighbor entry that does not resolve to anything. */
+ }
- APPEND_STR("BTS ");
- if (ni_key->from_bts == NEIGHBOR_IDENT_KEY_ANY_BTS)
- APPEND_STR("*");
- else if (ni_key->from_bts >= 0 && ni_key->from_bts <= 255)
- APPEND_STR("%d", ni_key->from_bts);
- else
- APPEND_STR("invalid(%d)", ni_key->from_bts);
+ if (local_neighbor_p)
+ *local_neighbor_p = local_neighbor;
+ if (remote_neighbors)
+ *remote_neighbors = remotes;
- APPEND_STR(" to ");
- if (ni_key->bsic == BSIC_ANY)
- APPEND_STR("ARFCN %u (any BSIC)", ni_key->arfcn);
- else
- APPEND_STR("ARFCN %u BSIC %u", ni_key->arfcn, ni_key->bsic & 0x3f);
- return buf;
+ if (!local_neighbor && !remotes.id_list_len)
+ return -ENOENT;
+ return 0;
}
-const char *neighbor_ident_key_name(const struct neighbor_ident_key *ni_key)
+int cell_ab_to_str_buf(char *buf, size_t buflen, const struct cell_ab *cell)
{
- static char buf[64];
- return _neighbor_ident_key_name(buf, sizeof(buf), ni_key);
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_PRINTF(sb, "ARFCN-BSIC:%u", cell->arfcn);
+ if (cell->bsic == BSIC_ANY)
+ OSMO_STRBUF_PRINTF(sb, "-any");
+ else {
+ OSMO_STRBUF_PRINTF(sb, "-%u", cell->bsic);
+ if (cell->bsic > 0x3f)
+ OSMO_STRBUF_PRINTF(sb, "[ERANGE>63]");
+ }
+ return sb.chars_needed;
+}
+
+char *cell_ab_to_str_c(void *ctx, const struct cell_ab *cell)
+{
+ OSMO_NAME_C_IMPL(ctx, 64, "ERROR", cell_ab_to_str_buf, cell)
+}
+
+int neighbor_to_str_buf(char *buf, size_t buflen, const struct neighbor *n)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ switch (n->type) {
+ case NEIGHBOR_TYPE_BTS_NR:
+ OSMO_STRBUF_PRINTF(sb, "BTS %u", n->bts_nr);
+ break;
+ case NEIGHBOR_TYPE_CELL_ID:
+ OSMO_STRBUF_APPEND_NOLEN(sb, gsm0808_cell_id_name_buf, &n->cell_id.id);
+ if (n->cell_id.ab_present) {
+ OSMO_STRBUF_PRINTF(sb, " ");
+ OSMO_STRBUF_APPEND(sb, cell_ab_to_str_buf, &n->cell_id.ab);
+ }
+ break;
+ case NEIGHBOR_TYPE_UNSET:
+ OSMO_STRBUF_PRINTF(sb, "UNSET");
+ break;
+ default:
+ OSMO_STRBUF_PRINTF(sb, "INVALID");
+ break;
+ }
+ return sb.chars_needed;
}
-struct neighbor_ident_list *neighbor_ident_init(void *talloc_ctx)
+char *neighbor_to_str_c(void *ctx, const struct neighbor *n)
{
- struct neighbor_ident_list *nil = talloc_zero(talloc_ctx, struct neighbor_ident_list);
- OSMO_ASSERT(nil);
- INIT_LLIST_HEAD(&nil->list);
- return nil;
+ OSMO_NAME_C_IMPL(ctx, 64, "ERROR", neighbor_to_str_buf, n);
}
-void neighbor_ident_free(struct neighbor_ident_list *nil)
+bool neighbor_same(const struct neighbor *a, const struct neighbor *b, bool check_cell_ab)
{
- if (!nil)
- return;
- talloc_free(nil);
+ if (a == b)
+ return true;
+ if (a->type != b->type)
+ return false;
+
+ switch (a->type) {
+ case NEIGHBOR_TYPE_BTS_NR:
+ return a->bts_nr == b->bts_nr;
+
+ case NEIGHBOR_TYPE_CELL_ID:
+ if (check_cell_ab
+ && (a->cell_id.ab_present != b->cell_id.ab_present
+ || !cell_ab_match(&a->cell_id.ab, &b->cell_id.ab, true)))
+ return false;
+ return gsm0808_cell_ids_match(&a->cell_id.id, &b->cell_id.id, true);
+ default:
+ return a->type == b->type;
+ }
}
/* Return true when the entry matches the search_for requirements.
* If exact_match is false, a BSIC_ANY entry acts as wildcard to match any search_for on that ARFCN,
- * and a BSIC_ANY in search_for likewise returns any one entry that matches the ARFCN;
- * also a from_bts == NEIGHBOR_IDENT_KEY_ANY_BTS in either entry or search_for will match.
- * If exact_match is true, only identical bsic values and identical from_bts values return a match.
+ * and a BSIC_ANY in search_for likewise returns any one entry that matches the ARFCN.
+ * If exact_match is true, only identical bsic values return a match.
* Note, typically wildcard BSICs are only in entry, e.g. the user configured list, and search_for
* contains a specific BSIC, e.g. as received from a Measurement Report. */
-bool neighbor_ident_key_match(const struct neighbor_ident_key *entry,
- const struct neighbor_ident_key *search_for,
- bool exact_match)
+bool cell_ab_match(const struct cell_ab *entry,
+ const struct cell_ab *search_for,
+ bool exact_match)
{
- if (exact_match
- && entry->from_bts != search_for->from_bts)
- return false;
-
- if (search_for->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS
- && entry->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS
- && entry->from_bts != search_for->from_bts)
- return false;
-
if (entry->arfcn != search_for->arfcn)
return false;
@@ -127,129 +286,210 @@ bool neighbor_ident_key_match(const struct neighbor_ident_key *entry,
return entry->bsic == search_for->bsic;
}
-static struct neighbor_ident *_neighbor_ident_get(const struct neighbor_ident_list *nil,
- const struct neighbor_ident_key *key,
- bool exact_match)
+bool cell_ab_valid(const struct cell_ab *cell)
{
- struct neighbor_ident *ni;
- struct neighbor_ident *wildcard_match = NULL;
+ if (cell->bsic != BSIC_ANY && cell->bsic > 0x3f)
+ return false;
+ return true;
+}
- /* Do both exact-bsic and wildcard matching in the same iteration:
- * Any exact match returns immediately, while for a wildcard match we still go through all
- * remaining items in case an exact match exists. */
- llist_for_each_entry(ni, &nil->list, entry) {
- if (neighbor_ident_key_match(&ni->key, key, true))
- return ni;
- if (!exact_match) {
- if (neighbor_ident_key_match(&ni->key, key, false))
- wildcard_match = ni;
+int neighbors_check_cfg(void)
+{
+ /* A local neighbor can be configured by BTS number, or by a cell ID. A local neighbor can omit the ARFCN+BSIC,
+ * in which case those are taken from that local BTS config. If a local neighbor has ARFCN+BSIC configured, it
+ * must match the local cell's configuration.
+ *
+ * A remote neighbor must always be a cell ID *and* ARFCN+BSIC.
+ *
+ * Hence any cell ID with ARFCN+BSIC where the cell ID is not found among the local cells is a remote-BSS
+ * neighbor.
+ */
+ struct gsm_bts *bts;
+ bool ok = true;
+
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ struct neighbor *neighbor;
+ struct gsm_bts *local_neighbor;
+ llist_for_each_entry(neighbor, &bts->neighbors, entry) {
+ switch (neighbor->type) {
+
+ case NEIGHBOR_TYPE_BTS_NR:
+ if (!gsm_bts_num(bsc_gsmnet, neighbor->bts_nr)) {
+ LOGP(DHO, LOGL_ERROR, "Neighbor Configuration Error:"
+ " BTS %u -> BTS %u: There is no BTS nr %u\n",
+ bts->nr, neighbor->bts_nr, neighbor->bts_nr);
+ ok = false;
+ }
+ break;
+
+ default:
+ switch (resolve_local_neighbor(&local_neighbor, bts, neighbor)) {
+ case 0:
+ break;
+ case -ENOENT:
+ if (!neighbor->cell_id.ab_present) {
+ LOGP(DHO, LOGL_ERROR, "Neighbor Configuration Error:"
+ " BTS %u -> %s: There is no such local neighbor\n",
+ bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor));
+ ok = false;
+ }
+ break;
+ default:
+ /* Error already logged in resolve_local_neighbor() */
+ ok = false;
+ break;
+ }
+ break;
+ }
}
}
- return wildcard_match;
-}
-static void _neighbor_ident_free(struct neighbor_ident *ni)
-{
- llist_del(&ni->entry);
- talloc_free(ni);
+ if (!ok)
+ return -EINVAL;
+ return 0;
}
-bool neighbor_ident_key_valid(const struct neighbor_ident_key *key)
+/* Neighbor Resolution CTRL iface */
+
+CTRL_CMD_DEFINE_RO(neighbor_resolve_cgi_ps_from_lac_ci, "neighbor_resolve_cgi_ps_from_lac_ci");
+
+static int gsm_bts_get_cgi_ps(const struct gsm_bts *bts, struct osmo_cell_global_id_ps *cgi_ps)
{
- if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS
- && (key->from_bts < 0 || key->from_bts > 255))
- return false;
+ if (bts->gprs.mode == BTS_GPRS_NONE)
+ return -ENOTSUP;
- if (key->bsic != BSIC_ANY && key->bsic > 0x3f)
- return false;
- return true;
+ cgi_ps->rai.lac.plmn = bts->network->plmn;
+ cgi_ps->rai.lac.lac = bts->location_area_code;
+ cgi_ps->rai.rac = bts->gprs.rac;
+ cgi_ps->cell_identity = bts->cell_identity;
+
+ return 0;
}
-/*! Add Cell Identifiers to an ARFCN+BSIC entry.
- * Exactly one kind of identifier is allowed per ARFCN+BSIC entry, and any number of entries of that kind
- * may be added up to the capacity of gsm0808_cell_id_list2, by one or more calls to this function. To
- * replace an existing entry, first call neighbor_ident_del(nil, key).
- * \returns number of entries in the resulting identifier list, or negative on error:
- * see gsm0808_cell_id_list_add() for the meaning of returned error codes;
- * return -ENOMEM when the list is not initialized, -ERANGE when the BSIC value is too large. */
-int neighbor_ident_add(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key,
- const struct gsm0808_cell_id_list2 *val)
+/* Attempt resolution of cgi_ps from ARFCN+BSIC of neighbor from BTS identified by LAC+CI */
+int neighbor_address_resolution(const struct gsm_network *net, const struct cell_ab *ab,
+ uint16_t lac, uint16_t cell_id,
+ struct osmo_cell_global_id_ps *res_cgi_ps)
{
- struct neighbor_ident *ni;
- int rc;
+ struct gsm_bts *bts_tmp, *bts_found = NULL;
+ struct osmo_cell_global_id_ps local_cgi_ps;
+ const struct osmo_cell_global_id_ps *cgi_ps = NULL;
+ struct gsm_bts *local_neighbor = NULL;
+ struct gsm0808_cell_id_list2 remote_neighbors = { 0 };
+
+ llist_for_each_entry(bts_tmp, &net->bts_list, list) {
+ if (bts_tmp->location_area_code != lac)
+ continue;
+ if (bts_tmp->cell_identity != cell_id)
+ continue;
+ bts_found = bts_tmp;
+ break;
+ }
+
+ if (!bts_found)
+ goto notfound_err;
- if (!nil)
- return -ENOMEM;
-
- if (!neighbor_ident_key_valid(key))
- return -ERANGE;
-
- ni = _neighbor_ident_get(nil, key, true);
- if (!ni) {
- ni = talloc_zero(nil, struct neighbor_ident);
- OSMO_ASSERT(ni);
- *ni = (struct neighbor_ident){
- .key = *key,
- .val = *val,
- };
- llist_add_tail(&ni->entry, &nil->list);
- return ni->val.id_list_len;
+ LOG_BTS(bts_found, DLINP, LOGL_DEBUG, "Resolving neighbor BTS %u -> %s\n", bts_found->nr,
+ cell_ab_to_str_c(OTC_SELECT, ab));
+
+ if (resolve_neighbors(&local_neighbor, &remote_neighbors, bts_found, ab, true))
+ goto notfound_err;
+
+ /* resolve_neighbors() returns either a local_neighbor or remote_neighbors.
+ * Local-BSS neighbor? */
+ if (local_neighbor) {
+ /* Supporting GPRS? */
+ if (gsm_bts_get_cgi_ps(local_neighbor, &local_cgi_ps) >= 0)
+ cgi_ps = &local_cgi_ps;
}
- rc = gsm0808_cell_id_list_add(&ni->val, val);
+ /* Remote-BSS neighbor?
+ * By spec, there can be multiple remote neighbors for a given ARFCN+BSIC, but so far osmo-bsc enforces only a
+ * single remote neighbor. */
+ if (remote_neighbors.id_list_len
+ && remote_neighbors.id_discr == CELL_IDENT_WHOLE_GLOBAL_PS) {
+ cgi_ps = &remote_neighbors.id_list[0].global_ps;
+ }
- if (rc < 0)
- return rc;
+ /* No neighbor found */
+ if (!cgi_ps)
+ goto notfound_err;
- return ni->val.id_list_len;
-}
+ *res_cgi_ps = *cgi_ps;
+ return 0;
-/*! Find cell identity for given BTS, ARFCN and BSIC, as previously added by neighbor_ident_add().
- */
-const struct gsm0808_cell_id_list2 *neighbor_ident_get(const struct neighbor_ident_list *nil,
- const struct neighbor_ident_key *key)
-{
- struct neighbor_ident *ni;
- if (!nil)
- return NULL;
- ni = _neighbor_ident_get(nil, key, false);
- if (!ni)
- return NULL;
- return &ni->val;
+notfound_err:
+ return -1;
}
-bool neighbor_ident_del(struct neighbor_ident_list *nil, const struct neighbor_ident_key *key)
+static int get_neighbor_resolve_cgi_ps_from_lac_ci(struct ctrl_cmd *cmd, void *data)
{
- struct neighbor_ident *ni;
- if (!nil)
- return false;
- ni = _neighbor_ident_get(nil, key, true);
- if (!ni)
- return false;
- _neighbor_ident_free(ni);
- return true;
+ struct gsm_network *net = (struct gsm_network *)data;
+ char *tmp = NULL, *tok, *saveptr;
+ struct cell_ab ab;
+ unsigned int lac, cell_id;
+ struct osmo_cell_global_id_ps cgi_ps;
+
+ if (!cmd->variable)
+ goto fmt_err;
+
+ tmp = talloc_strdup(cmd, cmd->variable);
+ if (!tmp) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!(tok = strtok_r(tmp, ".", &saveptr)))
+ goto fmt_err;
+ OSMO_ASSERT(strcmp(tok, "neighbor_resolve_cgi_ps_from_lac_ci") == 0);
+
+ if (!(tok = strtok_r(NULL, ".", &saveptr)))
+ goto fmt_err;
+ lac = atoi(tok);
+
+ if (!(tok = strtok_r(NULL, ".", &saveptr)))
+ goto fmt_err;
+ cell_id = atoi(tok);
+
+ if (!(tok = strtok_r(NULL, ".", &saveptr)))
+ goto fmt_err;
+ ab.arfcn = atoi(tok);
+
+ if (!(tok = strtok_r(NULL, "\0", &saveptr)))
+ goto fmt_err;
+ ab.bsic = atoi(tok);
+
+ if (!cell_ab_valid(&ab))
+ goto fmt_err;
+
+ if (neighbor_address_resolution(net, &ab, lac, cell_id, &cgi_ps) < 0)
+ goto notfound_err;
+
+ ctrl_cmd_reply_printf(cmd, "%s", osmo_cgi_ps_name(&cgi_ps));
+ talloc_free(tmp);
+ return CTRL_CMD_REPLY;
+
+notfound_err:
+ talloc_free(tmp);
+ cmd->reply = talloc_strdup(cmd, "No target CGI PS found");
+ return CTRL_CMD_ERROR;
+fmt_err:
+ talloc_free(tmp);
+ cmd->reply = talloc_strdup(cmd, "The format is <src_lac>,<src_cell_id>,<dst_arfcn>,<dst_bsic>");
+ return CTRL_CMD_ERROR;
}
-void neighbor_ident_clear(struct neighbor_ident_list *nil)
+int neighbor_ctrl_cmds_install(struct gsm_network *net)
{
- struct neighbor_ident *ni;
- while ((ni = llist_first_entry_or_null(&nil->list, struct neighbor_ident, entry)))
- _neighbor_ident_free(ni);
+ int rc;
+
+ rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_neighbor_resolve_cgi_ps_from_lac_ci);
+ return rc;
}
-/*! Iterate all neighbor_ident_list entries and call iter_cb for each.
- * If iter_cb returns false, the iteration is stopped. */
-void neighbor_ident_iter(const struct neighbor_ident_list *nil,
- bool (* iter_cb )(const struct neighbor_ident_key *key,
- const struct gsm0808_cell_id_list2 *val,
- void *cb_data),
- void *cb_data)
+struct ctrl_handle *neighbor_controlif_setup(struct gsm_network *net)
{
- struct neighbor_ident *ni, *ni_next;
- if (!nil)
- return;
- llist_for_each_entry_safe(ni, ni_next, &nil->list, entry) {
- if (!iter_cb(&ni->key, &ni->val, cb_data))
- return;
- }
+ /* DEPRECATED: see osmobsc-usermanual.pdf, section 16.1.1 Neighbor Address Resolution Service */
+ return ctrl_interface_setup_dynip2(net, net->neigh_ctrl.addr, net->neigh_ctrl.port,
+ NULL, _LAST_CTRL_NODE_NEIGHBOR);
}
diff --git a/src/osmo-bsc/neighbor_ident_ctrl.c b/src/osmo-bsc/neighbor_ident_ctrl.c
new file mode 100644
index 000000000..a9d7b5dc5
--- /dev/null
+++ b/src/osmo-bsc/neighbor_ident_ctrl.c
@@ -0,0 +1,753 @@
+/* CTRL interface implementation to manage identity of neighboring BSS cells for inter-BSC handover. */
+/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier <pmaier@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 <errno.h>
+#include <inttypes.h>
+#include <time.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/vty.h>
+
+/* Continue to parse ARFCN and BSIC, which are optional parameters at the end of the parameter string in most of the
+ * commands. The result is ignored when parameter n is set to NULL. */
+static int continue_parse_arfcn_and_bsic(char **saveptr, struct neighbor *n)
+{
+ int arfcn;
+ int bsic;
+ char *tok;
+
+ tok = strtok_r(NULL, "-", saveptr);
+
+ /* No ARFCN and BSIC persent - stop */
+ if (!tok)
+ return 0;
+
+ if (osmo_str_to_int(&arfcn, tok, 10, 0, 1023) < 0)
+ return -EINVAL;
+
+ tok = strtok_r(NULL, "-", saveptr);
+
+ /* When an ARFCN is given, then the BSIC parameter is
+ * mandatory */
+ if (!tok)
+ return -EINVAL;
+
+ if (strcmp(tok, "any") == 0) {
+ bsic = BSIC_ANY;
+ } else {
+ if (osmo_str_to_int(&bsic, tok, 10, 0, 63) < 0)
+ return 1;
+ }
+
+ /* Make sure there are no excess parameters */
+ if (strtok_r(NULL, "-", saveptr))
+ return -EINVAL;
+
+ if (n) {
+ n->cell_id.ab_present = true;
+ n->cell_id.ab.arfcn = arfcn;
+ n->cell_id.ab.bsic = bsic;
+ }
+
+ return 0;
+}
+
+/* This and the following: Add/Remove a BTS as neighbor */
+static int verify_neighbor_bts(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ struct gsm_bts *bts = cmd->node;
+ const int neigh_bts_nr = atoi(value);
+ struct gsm_bts *neigh_bts = gsm_bts_num(bts->network, neigh_bts_nr);
+
+ if (!neigh_bts) {
+ cmd->reply = "Invalid Neighbor BTS number - no such BTS";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int verify_neighbor_bts_add(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ return verify_neighbor_bts(cmd, value, _data);
+}
+
+static int get_neighbor_bts_list(struct ctrl_cmd *cmd, void *data)
+{
+ /* Max. 256 BTS neighbors (as of now, any bts can be its own neighbor per cfg) comma-separated ->
+ * max. 255 commas * + trailing '\0': 256
+ * 10 of those numbers (0...9) are 1-digit numbers: + 10 = 266
+ * 90 of those numbers are 2-digit numbers (10...99): + 90 = 356
+ * 255 - 100 + 1 = 156 are 3-digit numbers (100...255): + 156 = 512 bytes
+ * Double BTS num entries are not possible (check exists and is being tested against in python tests). */
+ char log_buf[512];
+ struct osmo_strbuf reply = { .buf = log_buf,
+ .len = sizeof(log_buf),
+ .pos = log_buf
+ };
+ struct gsm_bts *neighbor_bts, *bts = (struct gsm_bts *)cmd->node;
+ if (!bts) {
+ cmd->reply = "BTS not found";
+ return CTRL_CMD_ERROR;
+ }
+ struct neighbor *n;
+ llist_for_each_entry(n, &bts->neighbors, entry)
+ if (resolve_local_neighbor(&neighbor_bts, bts, n) == 0)
+ OSMO_STRBUF_PRINTF(reply, "%" PRIu8 ",", neighbor_bts->nr);
+ if (reply.buf == reply.pos)
+ cmd->reply = "";
+ else { /* Get rid of trailing comma */
+ reply.pos[-1] = '\0';
+ if (!(cmd->reply = talloc_strdup(cmd, reply.buf)))
+ goto oom;
+ }
+ return CTRL_CMD_REPLY;
+
+oom:
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+}
+
+CTRL_CMD_DEFINE_RO(neighbor_bts_list, "neighbor-bts list");
+
+static int set_neighbor_bts_add(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ const int bts_nr = atoi(cmd->value);
+ int rc;
+
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_BTS_NR,
+ .bts_nr = bts_nr,
+ };
+ rc = neighbor_ident_add_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to add neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: "<num>"
+ * num: BTS number (0-255) */
+CTRL_CMD_DEFINE_WO(neighbor_bts_add, "neighbor-bts add");
+
+static int verify_neighbor_bts_del(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ return verify_neighbor_bts(cmd, value, _data);
+}
+
+static int set_neighbor_bts_del(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ const int bts_nr = atoi(cmd->value);
+ int rc;
+
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_BTS_NR,
+ .bts_nr = bts_nr,
+ };
+ rc = neighbor_ident_del_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to delete neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: (see "add" command above) */
+CTRL_CMD_DEFINE_WO(neighbor_bts_del, "neighbor-bts del");
+
+/* This and the following: Add/Remove a LAC as neighbor */
+static int parse_lac(void *ctx, struct neighbor *n, const char *value)
+{
+ char *tmp = NULL, *tok, *saveptr;
+ int rc = 0;
+ int lac;
+
+ if (n)
+ memset(n, 0, sizeof(*n));
+
+ tmp = talloc_strdup(ctx, value);
+ if (!tmp)
+ return -EINVAL;
+
+ /* Parse LAC */
+ tok = strtok_r(tmp, "-", &saveptr);
+ if (tok) {
+ if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Optional parameters: ARFCN and BSIC */
+ if (continue_parse_arfcn_and_bsic(&saveptr, n)) {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ if (n) {
+ n->type = NEIGHBOR_TYPE_CELL_ID;
+ n->cell_id.id.id_discr = CELL_IDENT_LAC;
+ n->cell_id.id.id.lac = lac;
+ }
+
+exit:
+ talloc_free(tmp);
+ return rc;
+}
+
+static int verify_neighbor_lac_add(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (parse_lac(cmd, NULL, value))
+ return 1;
+ return 0;
+}
+
+static int set_neighbor_lac_add(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ struct neighbor n;
+
+ parse_lac(cmd, &n, cmd->value);
+ rc = neighbor_ident_add_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to add neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: "<lac>[-<arfcn>-<bsic>]"
+ * lac: Location area of neighbor cell (0-65535)
+ * arfcn: ARFCN of neighbor cell (0-1023)
+ * bsic: BSIC of neighbor cell */
+CTRL_CMD_DEFINE_WO(neighbor_lac_add, "neighbor-lac add");
+
+static int verify_neighbor_lac_del(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (parse_lac(cmd, NULL, value))
+ return 1;
+ return 0;
+}
+
+static int set_neighbor_lac_del(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ struct neighbor n;
+ parse_lac(cmd, &n, cmd->value);
+ rc = neighbor_ident_del_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to delete neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: (see "add" command above) */
+CTRL_CMD_DEFINE_WO(neighbor_lac_del, "neighbor-lac del");
+
+/* This and the following: Add/Remove a LAC-CI as neighbor */
+static int parse_lac_ci(void *ctx, struct neighbor *n, const char *value)
+{
+ char *tmp = NULL, *tok, *saveptr;
+ int rc = 0;
+ int lac;
+ int ci;
+
+ if (n)
+ memset(n, 0, sizeof(*n));
+
+ tmp = talloc_strdup(ctx, value);
+ if (!tmp)
+ return -EINVAL;
+
+ /* Parse LAC */
+ tok = strtok_r(tmp, "-", &saveptr);
+ if (tok) {
+ if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Parse CI */
+ tok = strtok_r(NULL, "-", &saveptr);
+ if (tok) {
+ if (osmo_str_to_int(&ci, tok, 10, 0, 65535) < 0) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Optional parameters: ARFCN and BSIC */
+ if (continue_parse_arfcn_and_bsic(&saveptr, n)) {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ if (n) {
+ n->type = NEIGHBOR_TYPE_CELL_ID;
+ n->cell_id.id.id_discr = CELL_IDENT_LAC_AND_CI;
+ n->cell_id.id.id.lac = lac;
+ n->cell_id.id.id.ci = ci;
+ }
+
+exit:
+ talloc_free(tmp);
+ return rc;
+}
+
+static int verify_neighbor_lac_ci_add(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (parse_lac_ci(cmd, NULL, value))
+ return 1;
+ return 0;
+}
+
+static int set_neighbor_lac_ci_add(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ struct neighbor n;
+
+ parse_lac_ci(cmd, &n, cmd->value);
+ rc = neighbor_ident_add_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to add neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: "<lac>-<ci>[-<arfcn>-<bsic>]"
+ * lac: Location area of neighbor cell (0-65535)
+ * ci: Cell ID of neighbor cell (0-65535)
+ * arfcn: ARFCN of neighbor cell (0-1023)
+ * bsic: BSIC of neighbor cell */
+CTRL_CMD_DEFINE_WO(neighbor_lac_ci_add, "neighbor-lac-ci add");
+
+static int verify_neighbor_lac_ci_del(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (parse_lac_ci(cmd, NULL, value))
+ return 1;
+ return 0;
+}
+
+static int set_neighbor_lac_ci_del(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ struct neighbor n;
+ parse_lac_ci(cmd, &n, cmd->value);
+ rc = neighbor_ident_del_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to delete neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: (see "add" command above) */
+CTRL_CMD_DEFINE_WO(neighbor_lac_ci_del, "neighbor-lac-ci del");
+
+/* This and the following: Add/Remove a CGI as neighbor */
+static int parse_cgi(void *ctx, struct neighbor *n, const char *value)
+{
+ char *tmp = NULL, *tok, *saveptr;
+ int rc = 0;
+ uint16_t mcc;
+ uint16_t mnc;
+ bool mnc_3_digits;
+ int lac;
+ int ci;
+
+ if (n)
+ memset(n, 0, sizeof(*n));
+
+ tmp = talloc_strdup(ctx, value);
+ if (!tmp)
+ return -EINVAL;
+
+ /* Parse MCC */
+ tok = strtok_r(tmp, "-", &saveptr);
+ if (tok) {
+ if (osmo_mcc_from_str(tok, &mcc)) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Parse MNC */
+ tok = strtok_r(NULL, "-", &saveptr);
+ if (tok) {
+ if (osmo_mnc_from_str(tok, &mnc, &mnc_3_digits)) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Parse LAC */
+ tok = strtok_r(NULL, "-", &saveptr);
+ if (tok) {
+ if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Parse CI */
+ tok = strtok_r(NULL, "-", &saveptr);
+ if (tok) {
+ if (osmo_str_to_int(&ci, tok, 10, 0, 65535) < 0) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Optional parameters: ARFCN and BSIC */
+ if (continue_parse_arfcn_and_bsic(&saveptr, n)) {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ if (n) {
+ n->type = NEIGHBOR_TYPE_CELL_ID;
+ n->cell_id.id.id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ n->cell_id.id.id.global.lai.lac = lac;
+ n->cell_id.id.id.global.lai.plmn.mcc = mcc;
+ n->cell_id.id.id.global.lai.plmn.mnc = mnc;
+ n->cell_id.id.id.global.lai.plmn.mnc_3_digits = mnc_3_digits;
+ n->cell_id.id.id.global.cell_identity = ci;
+ }
+
+exit:
+ talloc_free(tmp);
+ return rc;
+}
+
+static int verify_neighbor_cgi_add(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (parse_cgi(cmd, NULL, value))
+ return 1;
+ return 0;
+}
+
+static int set_neighbor_cgi_add(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ struct neighbor n;
+
+ parse_cgi(cmd, &n, cmd->value);
+ rc = neighbor_ident_add_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to add neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: "<mcc>-<mnc>-<lac>-<ci>[-<arfcn>-<bsic>]"
+ * mcc: Mobile country code of neighbor cell (0-999)
+ * mnc: Mobile network code of neighbor cell (0-999)
+ * lac: Location area of neighbor cell (0-65535)
+ * ci: Cell ID of neighbor cell (0-65535)
+ * arfcn: ARFCN of neighbor cell (0-1023)
+ * bsic: BSIC of neighbor cell */
+CTRL_CMD_DEFINE_WO(neighbor_cgi_add, "neighbor-cgi add");
+
+static int verify_neighbor_cgi_del(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (parse_cgi(cmd, NULL, value))
+ return 1;
+ return 0;
+}
+
+static int set_neighbor_cgi_del(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ struct neighbor n;
+ parse_cgi(cmd, &n, cmd->value);
+ rc = neighbor_ident_del_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to delete neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: (see "add" command above) */
+CTRL_CMD_DEFINE_WO(neighbor_cgi_del, "neighbor-cgi del");
+
+/* This and the following: Add/Remove a CGI-PS as neighbor */
+static int parse_cgi_ps(void *ctx, struct neighbor *n, const char *value)
+{
+ char *tmp = NULL, *tok, *saveptr;
+ int rc = 0;
+ uint16_t mcc;
+ uint16_t mnc;
+ bool mnc_3_digits;
+ int lac;
+ int rac;
+ int ci;
+
+ if (n)
+ memset(n, 0, sizeof(*n));
+
+ tmp = talloc_strdup(ctx, value);
+ if (!tmp)
+ return -EINVAL;
+
+ /* Parse MCC */
+ tok = strtok_r(tmp, "-", &saveptr);
+ if (tok) {
+ if (osmo_mcc_from_str(tok, &mcc)) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Parse MNC */
+ tok = strtok_r(NULL, "-", &saveptr);
+ if (tok) {
+ if (osmo_mnc_from_str(tok, &mnc, &mnc_3_digits)) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Parse LAC */
+ tok = strtok_r(NULL, "-", &saveptr);
+ if (tok) {
+ if (osmo_str_to_int(&lac, tok, 10, 0, 65535) < 0) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Parse RAC */
+ tok = strtok_r(NULL, "-", &saveptr);
+ if (tok) {
+ if (osmo_str_to_int(&rac, tok, 10, 0, 255) < 0) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Parse CI */
+ tok = strtok_r(NULL, "-", &saveptr);
+ if (tok) {
+ if (osmo_str_to_int(&ci, tok, 10, 0, 65535) < 0) {
+ rc = -EINVAL;
+ goto exit;
+ }
+ } else {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ /* Optional parameters: ARFCN and BSIC */
+ if (continue_parse_arfcn_and_bsic(&saveptr, n)) {
+ rc = -EINVAL;
+ goto exit;
+ }
+
+ if (n) {
+ n->type = NEIGHBOR_TYPE_CELL_ID;
+ n->cell_id.id.id_discr = CELL_IDENT_WHOLE_GLOBAL_PS;
+ n->cell_id.id.id.global_ps.rai.lac.lac = lac;
+ n->cell_id.id.id.global_ps.rai.rac = lac;
+ n->cell_id.id.id.global_ps.rai.lac.plmn.mcc = mcc;
+ n->cell_id.id.id.global_ps.rai.lac.plmn.mnc = mnc;
+ n->cell_id.id.id.global_ps.rai.lac.plmn.mnc_3_digits = mnc_3_digits;
+ n->cell_id.id.id.global_ps.cell_identity = ci;
+ }
+
+exit:
+ talloc_free(tmp);
+ return rc;
+}
+
+static int verify_neighbor_cgi_ps_add(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (parse_cgi_ps(cmd, NULL, value))
+ return 1;
+ return 0;
+}
+
+static int set_neighbor_cgi_ps_add(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ struct neighbor n;
+
+ parse_cgi_ps(cmd, &n, cmd->value);
+ rc = neighbor_ident_add_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to add neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: "<mcc>-<mnc>-<lac>-<rac>-<ci>[-<arfcn>-<bsic>]"
+ * mcc: Mobile country code of neighbor cell (0-999)
+ * mnc: Mobile network code of neighbor cell (0-999)
+ * lac: Location area of neighbor cell (0-65535)
+ * rac: Routing area of neighbor cell (0-65535)
+ * ci: Cell ID of neighbor cell (0-65535)
+ * arfcn: ARFCN of neighbor cell (0-1023)
+ * bsic: BSIC of neighbor cell */
+CTRL_CMD_DEFINE_WO(neighbor_cgi_ps_add, "neighbor-cgi-ps add");
+
+static int verify_neighbor_cgi_ps_del(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ if (parse_cgi_ps(cmd, NULL, value))
+ return 1;
+ return 0;
+}
+
+static int set_neighbor_cgi_ps_del(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ struct neighbor n;
+ parse_cgi_ps(cmd, &n, cmd->value);
+ rc = neighbor_ident_del_neighbor(NULL, bts, &n);
+ if (rc != CMD_SUCCESS) {
+ cmd->reply = "Failed to delete neighbor";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+/* Parameter format: (see "add" command above) */
+CTRL_CMD_DEFINE_WO(neighbor_cgi_ps_del, "neighbor-cgi-ps del");
+
+/* This and the following: clear all neighbor cell information */
+static int set_neighbor_clear(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ struct neighbor *neighbor;
+ struct neighbor *neighbor_tmp;
+
+ llist_for_each_entry_safe(neighbor, neighbor_tmp, &bts->neighbors, entry) {
+ llist_del(&neighbor->entry);
+ talloc_free(neighbor);
+ }
+
+ cmd->reply = "OK";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(neighbor_clear, "neighbor-clear");
+
+/* Register control interface commands implemented above */
+int neighbor_ident_ctrl_init(void)
+{
+ int rc = 0;
+
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_bts_add);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_bts_del);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_add);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_del);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_ci_add);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_lac_ci_del);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_add);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_del);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_bts_list);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_ps_add);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_cgi_ps_del);
+ rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_neighbor_clear);
+
+ return rc;
+}
diff --git a/src/osmo-bsc/neighbor_ident_vty.c b/src/osmo-bsc/neighbor_ident_vty.c
index 203b15057..44b9057f7 100644
--- a/src/osmo-bsc/neighbor_ident_vty.c
+++ b/src/osmo-bsc/neighbor_ident_vty.c
@@ -23,6 +23,9 @@
#include <stdlib.h>
#include <string.h>
#include <errno.h>
+#include <inttypes.h>
+
+#include <osmocom/ctrl/ports.h>
#include <osmocom/vty/command.h>
#include <osmocom/gsm/gsm0808.h>
@@ -30,43 +33,7 @@
#include <osmocom/bsc/vty.h>
#include <osmocom/bsc/neighbor_ident.h>
#include <osmocom/bsc/gsm_data.h>
-
-static struct gsm_network *g_net = NULL;
-static struct neighbor_ident_list *g_neighbor_cells = NULL;
-
-/* Parse VTY parameters matching NEIGHBOR_IDENT_VTY_KEY_PARAMS. Pass a pointer so that argv[0] is the
- * ARFCN value followed by the BSIC keyword and value. vty *must* reference a BTS_NODE. */
-bool neighbor_ident_vty_parse_key_params(struct vty *vty, const char **argv,
- struct neighbor_ident_key *key)
-{
- struct gsm_bts *bts = vty->index;
-
- OSMO_ASSERT(vty->node == BTS_NODE);
- OSMO_ASSERT(bts);
-
- return neighbor_ident_bts_parse_key_params(vty, bts, argv, key);
-}
-
-/* same as neighbor_ident_vty_parse_key_params() but pass an explicit bts, so it works on any node. */
-bool neighbor_ident_bts_parse_key_params(struct vty *vty, struct gsm_bts *bts, const char **argv,
- struct neighbor_ident_key *key)
-{
- const char *arfcn_str = argv[0];
- const char *bsic_str = argv[1];
-
- OSMO_ASSERT(bts);
-
- *key = (struct neighbor_ident_key){
- .from_bts = bts->nr,
- .arfcn = atoi(arfcn_str),
- };
-
- if (!strcmp(bsic_str, "any"))
- key->bsic = BSIC_ANY;
- else
- key->bsic = atoi(bsic_str);
- return true;
-}
+#include <osmocom/bsc/bts.h>
#define NEIGHBOR_ADD_CMD "neighbor "
#define NEIGHBOR_DEL_CMD "no neighbor "
@@ -75,64 +42,51 @@ bool neighbor_ident_bts_parse_key_params(struct vty *vty, struct gsm_bts *bts, c
#define NEIGHBOR_DEL_DOC NO_STR "Remove local or remote-BSS neighbor cell\n"
#define LAC_PARAMS "lac <0-65535>"
+#define LAC_ARGC 1
#define LAC_DOC "Neighbor cell by LAC\n" "LAC\n"
#define LAC_CI_PARAMS "lac-ci <0-65535> <0-65535>"
+#define LAC_CI_ARGC 2
#define LAC_CI_DOC "Neighbor cell by LAC and CI\n" "LAC\n" "CI\n"
#define CGI_PARAMS "cgi <0-999> <0-999> <0-65535> <0-65535>"
+#define CGI_ARGC 4
#define CGI_DOC "Neighbor cell by cgi\n" "MCC\n" "MNC\n" "LAC\n" "CI\n"
+#define CGI_PS_PARAMS "cgi-ps <0-999> <0-999> <0-65535> <0-255> <0-65535>"
+#define CGI_PS_ARGC 5
+#define CGI_PS_DOC "Neighbor cell by cgi (Packet Switched, with RAC)\n" "MCC\n" "MNC\n" "LAC\n" "RAC\n" "CI\n"
+
#define LOCAL_BTS_PARAMS "bts <0-255>"
#define LOCAL_BTS_DOC "Neighbor cell by local BTS number\n" "BTS number\n"
-static struct gsm_bts *neighbor_ident_vty_parse_bts_nr(struct vty *vty, const char **argv)
-{
- const char *bts_nr_str = argv[0];
- struct gsm_bts *bts = gsm_bts_num(g_net, atoi(bts_nr_str));
- if (!bts)
- vty_out(vty, "%% No such BTS: nr = %s%s\n", bts_nr_str, VTY_NEWLINE);
- return bts;
-}
-
-static struct gsm_bts *bts_by_cell_id(struct vty *vty, struct gsm0808_cell_id *cell_id)
-{
- struct gsm_bts *bts = gsm_bts_by_cell_id(g_net, cell_id, 0);
- if (!bts)
- vty_out(vty, "%% No such BTS: %s%s\n", gsm0808_cell_id_name(cell_id), VTY_NEWLINE);
- return bts;
-}
-
-static struct gsm0808_cell_id *neighbor_ident_vty_parse_lac(struct vty *vty, const char **argv)
+static int neighbor_ident_vty_parse_lac(struct vty *vty, struct gsm0808_cell_id *cell_id, const char **argv)
{
- static struct gsm0808_cell_id cell_id;
- cell_id = (struct gsm0808_cell_id){
+ *cell_id = (struct gsm0808_cell_id){
.id_discr = CELL_IDENT_LAC,
.id.lac = atoi(argv[0]),
};
- return &cell_id;
+ return 0;
}
-static struct gsm0808_cell_id *neighbor_ident_vty_parse_lac_ci(struct vty *vty, const char **argv)
+static int neighbor_ident_vty_parse_lac_ci(struct vty *vty, struct gsm0808_cell_id *cell_id, const char **argv)
{
- static struct gsm0808_cell_id cell_id;
- cell_id = (struct gsm0808_cell_id){
+ *cell_id = (struct gsm0808_cell_id){
.id_discr = CELL_IDENT_LAC_AND_CI,
.id.lac_and_ci = {
.lac = atoi(argv[0]),
.ci = atoi(argv[1]),
},
};
- return &cell_id;
+ return 0;
}
-static struct gsm0808_cell_id *neighbor_ident_vty_parse_cgi(struct vty *vty, const char **argv)
+static int neighbor_ident_vty_parse_cgi(struct vty *vty, struct gsm0808_cell_id *cell_id, const char **argv)
{
- static struct gsm0808_cell_id cell_id;
- cell_id = (struct gsm0808_cell_id){
+ *cell_id = (struct gsm0808_cell_id){
.id_discr = CELL_IDENT_WHOLE_GLOBAL,
};
- struct osmo_cell_global_id *cgi = &cell_id.id.global;
+ struct osmo_cell_global_id *cgi = &cell_id->id.global;
const char *mcc = argv[0];
const char *mnc = argv[1];
const char *lac = argv[2];
@@ -140,397 +94,500 @@ static struct gsm0808_cell_id *neighbor_ident_vty_parse_cgi(struct vty *vty, con
if (osmo_mcc_from_str(mcc, &cgi->lai.plmn.mcc)) {
vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE);
- return NULL;
+ return -1;
}
if (osmo_mnc_from_str(mnc, &cgi->lai.plmn.mnc, &cgi->lai.plmn.mnc_3_digits)) {
vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE);
- return NULL;
+ return -1;
}
cgi->lai.lac = atoi(lac);
cgi->cell_identity = atoi(ci);
- return &cell_id;
+ return 0;
}
-static int add_local_bts(struct vty *vty, struct gsm_bts *neigh)
+static int neighbor_ident_vty_parse_cgi_ps(struct vty *vty, struct gsm0808_cell_id *cell_id, const char **argv)
{
- int rc;
- struct gsm_bts *bts = vty->index;
- if (vty->node != BTS_NODE) {
- vty_out(vty, "%% Error: cannot add local BTS neighbor, not on BTS node%s",
- VTY_NEWLINE);
- return CMD_WARNING;
+ *cell_id = (struct gsm0808_cell_id){
+ .id_discr = CELL_IDENT_WHOLE_GLOBAL_PS,
+ };
+ struct osmo_cell_global_id_ps *cgi_ps = &cell_id->id.global_ps;
+ const char *mcc = argv[0];
+ const char *mnc = argv[1];
+ const char *lac = argv[2];
+ const char *rac = argv[3];
+ const char *ci = argv[4];
+
+ if (osmo_mcc_from_str(mcc, &cgi_ps->rai.lac.plmn.mcc)) {
+ vty_out(vty, "%% Error decoding MCC: %s%s", mcc, VTY_NEWLINE);
+ return -1;
}
- if (!bts) {
- vty_out(vty, "%% Error: cannot add local BTS neighbor, no BTS on this node%s",
- VTY_NEWLINE);
- return CMD_WARNING;
+
+ if (osmo_mnc_from_str(mnc, &cgi_ps->rai.lac.plmn.mnc, &cgi_ps->rai.lac.plmn.mnc_3_digits)) {
+ vty_out(vty, "%% Error decoding MNC: %s%s", mnc, VTY_NEWLINE);
+ return -1;
}
- if (!neigh) {
- vty_out(vty, "%% Error: cannot add local BTS neighbor to BTS %u, no such neighbor BTS%s"
- "%% (To add remote-BSS neighbors, pass full ARFCN and BSIC as well)%s",
- bts->nr, VTY_NEWLINE, VTY_NEWLINE);
- return CMD_WARNING;
+
+ cgi_ps->rai.lac.lac = atoi(lac);
+ cgi_ps->rai.rac = atoi(rac);
+ cgi_ps->cell_identity = atoi(ci);
+ return 0;
+}
+
+void neighbor_ident_vty_parse_arfcn_bsic(struct cell_ab *ab, const char **argv)
+{
+ const char *arfcn_str = argv[0];
+ const char *bsic_str = argv[1];
+
+ *ab = (struct cell_ab){
+ .arfcn = atoi(arfcn_str),
+ .bsic = (!strcmp(bsic_str, "any")) ? BSIC_ANY : atoi(bsic_str),
+ };
+}
+
+#define LOGPORVTY(vty, fmt, args...) \
+{ \
+ if (vty) \
+ vty_out(vty, "%% " fmt "%s", ## args, VTY_NEWLINE); \
+ else \
+ LOGP(DLINP, LOGL_NOTICE, fmt "\n", ## args); \
+} while (0) \
+
+/* Add a neighbor from neighborlist. When the parameter *vty is set to NULL all error messages are redirected to the
+ * logtext. */
+int neighbor_ident_add_neighbor(struct vty *vty, struct gsm_bts *bts, struct neighbor *n)
+{
+ struct neighbor *neighbor;
+
+ OSMO_ASSERT(bts);
+ OSMO_ASSERT(!vty || (vty->node == BTS_NODE));
+
+ llist_for_each_entry(neighbor, &bts->neighbors, entry) {
+ /* Check against duplicates */
+ if (neighbor_same(neighbor, n, false)) {
+ /* Found a match on Cell ID or BTS number, without ARFCN+BSIC. If they are fully identical, ignore the
+ * duplicate. If the ARFCN+BSIC part differs, it's an error. */
+ LOGPORVTY(vty, "BTS %u already had neighbor %s", bts->nr, neighbor_to_str_c(OTC_SELECT, neighbor));
+ if (!neighbor_same(neighbor, n, true)) {
+ LOGPORVTY(vty, "ERROR: duplicate Cell ID in neighbor config, with differing ARFCN+BSIC: %s",
+ neighbor_to_str_c(OTC_SELECT, n));
+ return CMD_WARNING;
+ }
+ /* Exact same neighbor again, just ignore. */
+ return CMD_SUCCESS;
+ }
+
+ /* Allow only one cell ID per remote-BSS neighbor, see OS#3656 */
+ if (n->type == NEIGHBOR_TYPE_CELL_ID
+ && n->cell_id.ab_present && neighbor->cell_id.ab_present
+ && cell_ab_match(&n->cell_id.ab, &neighbor->cell_id.ab, true)) {
+ LOGPORVTY(vty, "Error: only one Cell Identifier entry is allowed per remote neighbor."
+ " Already have: BTS %u -> %s", bts->nr,
+ neighbor_to_str_c(OTC_SELECT, neighbor));
+ return CMD_WARNING;
+ }
}
- rc = gsm_bts_local_neighbor_add(bts, neigh);
- if (rc < 0) {
- vty_out(vty, "%% Error: cannot add local BTS %u as neighbor to BTS %u: %s%s",
- neigh->nr, bts->nr, strerror(-rc), VTY_NEWLINE);
- return CMD_WARNING;
- } else
- vty_out(vty, "%% BTS %u %s local neighbor BTS %u with LAC %u CI %u and ARFCN %u BSIC %u%s",
- bts->nr, rc? "now has" : "already had",
- neigh->nr, neigh->location_area_code, neigh->cell_identity,
- neigh->c0->arfcn, neigh->bsic, VTY_NEWLINE);
+
+ neighbor = talloc_zero(bts, struct neighbor);
+ *neighbor = *n;
+ llist_add_tail(&neighbor->entry, &bts->neighbors);
return CMD_SUCCESS;
}
-static int del_local_bts(struct vty *vty, struct gsm_bts *neigh)
+/* Delete a neighbor from neighborlist. When the parameter *vty is set to NULL all error messages are redirected to the
+ * logtext. */
+int neighbor_ident_del_neighbor(struct vty *vty, struct gsm_bts *bts, struct neighbor *n)
{
- int rc;
- struct gsm_bts *bts = vty->index;
- if (vty->node != BTS_NODE) {
- vty_out(vty, "%% Error: cannot remove local BTS neighbor, not on BTS node%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
- if (!bts) {
- vty_out(vty, "%% Error: cannot remove local BTS neighbor, no BTS on this node%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
- if (!neigh) {
- vty_out(vty, "%% Error: cannot remove local BTS neighbor from BTS %u, no such neighbor BTS%s",
- bts->nr, VTY_NEWLINE);
- return CMD_WARNING;
+ struct neighbor *neighbor;
+
+ OSMO_ASSERT(bts);
+ OSMO_ASSERT(!vty || (vty->node == BTS_NODE));
+
+ llist_for_each_entry(neighbor, &bts->neighbors, entry) {
+ if (neighbor->type != n->type)
+ continue;
+
+ switch (n->type) {
+ case NEIGHBOR_TYPE_BTS_NR:
+ if (neighbor->bts_nr == n->bts_nr)
+ break;
+ continue;
+
+ case NEIGHBOR_TYPE_CELL_ID:
+ if (gsm0808_cell_ids_match(&neighbor->cell_id.id, &n->cell_id.id, true))
+ break;
+ continue;
+ default:
+ continue;
+ }
+
+ llist_del(&neighbor->entry);
+ talloc_free(neighbor);
+ return CMD_SUCCESS;
}
- rc = gsm_bts_local_neighbor_del(bts, neigh);
- if (rc < 0) {
- vty_out(vty, "%% Error: cannot remove local BTS %u neighbor from BTS %u: %s%s",
- neigh->nr, bts->nr, strerror(-rc), VTY_NEWLINE);
- return CMD_WARNING;
+
+ LOGPORVTY(vty, "Error: no such neighbor on BTS %d: %s",
+ bts->nr, neighbor_to_str_c(OTC_SELECT, n));
+ return CMD_WARNING;
+}
+
+static int del_neighbor_by_cell_ab(struct vty *vty, const struct cell_ab *cell_ab)
+{
+ struct gsm_bts *bts = vty->index;
+ struct neighbor *neighbor, *safe;
+ struct gsm_bts *neighbor_bts;
+ struct cell_ab neighbor_ab;
+ int count = 0;
+
+ OSMO_ASSERT((vty->node == BTS_NODE) && bts);
+
+ llist_for_each_entry_safe(neighbor, safe, &bts->neighbors, entry) {
+ switch (neighbor->type) {
+ case NEIGHBOR_TYPE_BTS_NR:
+ if (resolve_local_neighbor(&neighbor_bts, bts, neighbor))
+ continue;
+ bts_cell_ab(&neighbor_ab, neighbor_bts);
+ if (!cell_ab_match(&neighbor_ab, cell_ab, false))
+ continue;
+ break;
+
+ case NEIGHBOR_TYPE_CELL_ID:
+ if (!neighbor->cell_id.ab_present)
+ continue;
+ if (!cell_ab_match(&neighbor->cell_id.ab, cell_ab, false))
+ continue;
+ break;
+ default:
+ continue;
+ }
+
+ llist_del(&neighbor->entry);
+ talloc_free(neighbor);
+ count++;
}
- if (rc == 0)
- vty_out(vty, "%% BTS %u is no neighbor of BTS %u%s",
- neigh->nr, bts->nr, VTY_NEWLINE);
- return CMD_SUCCESS;
+ if (count)
+ return CMD_SUCCESS;
+
+ vty_out(vty, "%% Cannot remove: no such neighbor on BTS %u: %s%s",
+ bts->nr, cell_ab_to_str_c(OTC_SELECT, cell_ab), VTY_NEWLINE);
+ return CMD_WARNING;
}
DEFUN(cfg_neighbor_add_bts_nr, cfg_neighbor_add_bts_nr_cmd,
NEIGHBOR_ADD_CMD LOCAL_BTS_PARAMS,
NEIGHBOR_ADD_DOC LOCAL_BTS_DOC)
{
- return add_local_bts(vty, neighbor_ident_vty_parse_bts_nr(vty, argv));
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_BTS_NR,
+ .bts_nr = atoi(argv[0]),
+ };
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
}
DEFUN(cfg_neighbor_add_lac, cfg_neighbor_add_lac_cmd,
NEIGHBOR_ADD_CMD LAC_PARAMS,
NEIGHBOR_ADD_DOC LAC_DOC)
{
- return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_lac(vty, argv)));
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ };
+ if (neighbor_ident_vty_parse_lac(vty, &n.cell_id.id, argv))
+ return CMD_WARNING;
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
}
DEFUN(cfg_neighbor_add_lac_ci, cfg_neighbor_add_lac_ci_cmd,
NEIGHBOR_ADD_CMD LAC_CI_PARAMS,
NEIGHBOR_ADD_DOC LAC_CI_DOC)
{
- return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_lac_ci(vty, argv)));
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ };
+ if (neighbor_ident_vty_parse_lac_ci(vty, &n.cell_id.id, argv))
+ return CMD_WARNING;
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
}
DEFUN(cfg_neighbor_add_cgi, cfg_neighbor_add_cgi_cmd,
NEIGHBOR_ADD_CMD CGI_PARAMS,
NEIGHBOR_ADD_DOC CGI_DOC)
{
- return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_cgi(vty, argv)));
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ };
+ if (neighbor_ident_vty_parse_cgi(vty, &n.cell_id.id, argv))
+ return CMD_WARNING;
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
}
-bool neighbor_ident_key_matches_bts(const struct neighbor_ident_key *key, struct gsm_bts *bts)
+DEFUN(cfg_neighbor_add_cgi_ps, cfg_neighbor_add_cgi_ps_cmd,
+ NEIGHBOR_ADD_CMD CGI_PS_PARAMS,
+ NEIGHBOR_ADD_DOC CGI_PS_DOC)
{
- if (!bts || !key)
- return false;
- return key->arfcn == bts->c0->arfcn
- && (key->bsic == BSIC_ANY || key->bsic == bts->bsic);
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ };
+ if (neighbor_ident_vty_parse_cgi_ps(vty, &n.cell_id.id, argv))
+ return CMD_WARNING;
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
}
-static int add_remote_or_local_bts(struct vty *vty, const struct gsm0808_cell_id *cell_id,
- const struct neighbor_ident_key *key)
+static int neighbor_del_all(struct vty *vty)
{
- int rc;
- struct gsm_bts *local_neigh;
- const struct gsm0808_cell_id_list2 *exists;
- struct gsm0808_cell_id_list2 cil;
struct gsm_bts *bts = vty->index;
+ struct neighbor *n;
+ OSMO_ASSERT((vty->node == BTS_NODE) && bts);
- if (vty->node != BTS_NODE) {
- vty_out(vty, "%% Error: cannot add BTS neighbor, not on BTS node%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
- if (!bts) {
- vty_out(vty, "%% Error: cannot add BTS neighbor, no BTS on this node%s",
- VTY_NEWLINE);
- return CMD_WARNING;
+ if (llist_empty(&bts->neighbors)) {
+ vty_out(vty, "%% No neighbors configured%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
}
- /* Is there a local BTS that matches the cell_id? */
- local_neigh = gsm_bts_by_cell_id(g_net, cell_id, 0);
- if (local_neigh) {
- /* But do the advertised ARFCN and BSIC match as intended?
- * The user may omit ARFCN and BSIC for local cells, but if they are provided,
- * they need to match. */
- if (!neighbor_ident_key_matches_bts(key, local_neigh)) {
- vty_out(vty, "%% Error: bts %u: neighbor cell id %s indicates local BTS %u,"
- " but it does not match ARFCN+BSIC %s%s",
- bts->nr, gsm0808_cell_id_name(cell_id), local_neigh->nr,
- neighbor_ident_key_name(key), VTY_NEWLINE);
- /* TODO: error out fatally for non-interactive VTY? */
- return CMD_WARNING;
- }
- return add_local_bts(vty, local_neigh);
+ /* Remove all local neighbors and print to VTY for the user to know what changed */
+ while ((n = llist_first_entry_or_null(&bts->neighbors, struct neighbor, entry))) {
+ vty_out(vty, "%% Removed neighbor: BTS %u to %s%s",
+ bts->nr, neighbor_to_str_c(OTC_SELECT, n), VTY_NEWLINE);
+ llist_del(&n->entry);
+ talloc_free(n);
}
+ return CMD_SUCCESS;
+}
- /* Allow only one cell ID per remote-BSS neighbor, see OS#3656 */
- exists = neighbor_ident_get(g_neighbor_cells, key);
- if (exists) {
- vty_out(vty, "%% Error: only one Cell Identifier entry is allowed per remote neighbor."
- " Already have: %s -> %s%s", neighbor_ident_key_name(key),
- gsm0808_cell_id_list_name(exists), VTY_NEWLINE);
+DEFUN(cfg_neighbor_add_lac_arfcn_bsic, cfg_neighbor_add_lac_arfcn_bsic_cmd,
+ NEIGHBOR_ADD_CMD LAC_PARAMS " " CELL_AB_VTY_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_DOC CELL_AB_VTY_DOC)
+{
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ .cell_id.ab_present = true,
+ };
+ if (neighbor_ident_vty_parse_lac(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- }
-
- /* The cell_id is not known in this BSS, so it must be a remote cell. */
- gsm0808_cell_id_to_list(&cil, cell_id);
- rc = neighbor_ident_add(g_neighbor_cells, key, &cil);
-
- if (rc < 0) {
- const char *reason;
- switch (rc) {
- case -EINVAL:
- reason = ": mismatching type between current and newly added cell identifier";
- break;
- case -ENOSPC:
- reason = ": list is full";
- break;
- default:
- reason = "";
- break;
- }
+ neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + LAC_ARGC);
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
+}
- vty_out(vty, "%% Error adding neighbor-BSS Cell Identifier %s%s%s",
- gsm0808_cell_id_name(cell_id), reason, VTY_NEWLINE);
+DEFUN(cfg_neighbor_add_lac_ci_arfcn_bsic, cfg_neighbor_add_lac_ci_arfcn_bsic_cmd,
+ NEIGHBOR_ADD_CMD LAC_CI_PARAMS " " CELL_AB_VTY_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_CI_DOC CELL_AB_VTY_DOC)
+{
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ .cell_id.ab_present = true,
+ };
+ if (neighbor_ident_vty_parse_lac_ci(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- }
-
- vty_out(vty, "%% %s now has %d remote BSS Cell Identifier List %s%s",
- neighbor_ident_key_name(key), rc, rc == 1? "entry" : "entries", VTY_NEWLINE);
- return CMD_SUCCESS;
+ neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + LAC_CI_ARGC);
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
}
-static int del_by_key(struct vty *vty, const struct neighbor_ident_key *key)
+DEFUN(cfg_neighbor_add_cgi_arfcn_bsic, cfg_neighbor_add_cgi_arfcn_bsic_cmd,
+ NEIGHBOR_ADD_CMD CGI_PARAMS " " CELL_AB_VTY_PARAMS,
+ NEIGHBOR_ADD_DOC CGI_DOC CELL_AB_VTY_DOC)
{
- int removed = 0;
- int rc;
- struct gsm_bts *bts = vty->index;
- struct gsm_bts_ref *neigh, *safe;
-
- if (vty->node != BTS_NODE) {
- vty_out(vty, "%% Error: cannot remove BTS neighbor, not on BTS node%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
- if (!bts) {
- vty_out(vty, "%% Error: cannot remove BTS neighbor, no BTS on this node%s",
- VTY_NEWLINE);
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ .cell_id.ab_present = true,
+ };
+ if (neighbor_ident_vty_parse_cgi(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- }
-
- /* Is there a local BTS that matches the key? */
- llist_for_each_entry_safe(neigh, safe, &bts->local_neighbors, entry) {
- struct gsm_bts *neigh_bts = neigh->bts;
- if (!neighbor_ident_key_matches_bts(key, neigh->bts))
- continue;
- rc = gsm_bts_local_neighbor_del(bts, neigh->bts);
- if (rc > 0) {
- vty_out(vty, "%% Removed local neighbor bts %u to bts %u%s",
- bts->nr, neigh_bts->nr, VTY_NEWLINE);
- removed += rc;
- }
- }
-
- if (neighbor_ident_del(g_neighbor_cells, key)) {
- vty_out(vty, "%% Removed remote BSS neighbor %s%s",
- neighbor_ident_key_name(key), VTY_NEWLINE);
- removed ++;
- }
+ neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + CGI_ARGC);
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
+}
- if (!removed) {
- vty_out(vty, "%% Cannot remove, no such neighbor: %s%s",
- neighbor_ident_key_name(key), VTY_NEWLINE);
+DEFUN(cfg_neighbor_add_cgi_ps_arfcn_bsic, cfg_neighbor_add_cgi_ps_arfcn_bsic_cmd,
+ NEIGHBOR_ADD_CMD CGI_PS_PARAMS " " CELL_AB_VTY_PARAMS,
+ NEIGHBOR_ADD_DOC CGI_PS_DOC CELL_AB_VTY_DOC)
+{
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ .cell_id.ab_present = true,
+ };
+ if (neighbor_ident_vty_parse_cgi_ps(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- }
- return CMD_SUCCESS;
+ neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + CGI_PS_ARGC);
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
}
-DEFUN(cfg_neighbor_add_lac_arfcn_bsic, cfg_neighbor_add_lac_arfcn_bsic_cmd,
- NEIGHBOR_ADD_CMD LAC_PARAMS " " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
- NEIGHBOR_ADD_DOC LAC_DOC NEIGHBOR_IDENT_VTY_KEY_DOC)
+DEFUN(cfg_neighbor_del_bts_nr, cfg_neighbor_del_bts_nr_cmd,
+ NEIGHBOR_DEL_CMD LOCAL_BTS_PARAMS,
+ NEIGHBOR_DEL_DOC LOCAL_BTS_DOC)
{
- struct neighbor_ident_key nik;
- struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_lac(vty, argv);
- if (!cell_id)
- return CMD_WARNING;
- if (!neighbor_ident_vty_parse_key_params(vty, argv + 1, &nik))
- return CMD_WARNING;
- return add_remote_or_local_bts(vty, cell_id, &nik);
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_BTS_NR,
+ .bts_nr = atoi(argv[0]),
+ };
+ return neighbor_ident_del_neighbor(vty, vty->index, &n);
}
-DEFUN(cfg_neighbor_add_lac_ci_arfcn_bsic, cfg_neighbor_add_lac_ci_arfcn_bsic_cmd,
- NEIGHBOR_ADD_CMD LAC_CI_PARAMS " " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
- NEIGHBOR_ADD_DOC LAC_CI_DOC NEIGHBOR_IDENT_VTY_KEY_DOC)
+DEFUN(cfg_neighbor_del_lac, cfg_neighbor_del_lac_cmd,
+ NEIGHBOR_DEL_CMD LAC_PARAMS,
+ NEIGHBOR_DEL_DOC LAC_DOC)
{
- struct neighbor_ident_key nik;
- struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_lac_ci(vty, argv);
- if (!cell_id)
- return CMD_WARNING;
- if (!neighbor_ident_vty_parse_key_params(vty, argv + 2, &nik))
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ };
+ if (neighbor_ident_vty_parse_lac(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- return add_remote_or_local_bts(vty, cell_id, &nik);
+ return neighbor_ident_del_neighbor(vty, vty->index, &n);
}
-DEFUN(cfg_neighbor_add_cgi_arfcn_bsic, cfg_neighbor_add_cgi_arfcn_bsic_cmd,
- NEIGHBOR_ADD_CMD CGI_PARAMS " " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
- NEIGHBOR_ADD_DOC CGI_DOC NEIGHBOR_IDENT_VTY_KEY_DOC)
+DEFUN(cfg_neighbor_del_lac_ci, cfg_neighbor_del_lac_ci_cmd,
+ NEIGHBOR_DEL_CMD LAC_CI_PARAMS,
+ NEIGHBOR_DEL_DOC LAC_CI_DOC)
{
- struct neighbor_ident_key nik;
- struct gsm0808_cell_id *cell_id = neighbor_ident_vty_parse_cgi(vty, argv);
- if (!cell_id)
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ };
+ if (neighbor_ident_vty_parse_lac_ci(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- if (!neighbor_ident_vty_parse_key_params(vty, argv + 4, &nik))
+ return neighbor_ident_del_neighbor(vty, vty->index, &n);
+}
+
+DEFUN(cfg_neighbor_del_cgi, cfg_neighbor_del_cgi_cmd,
+ NEIGHBOR_DEL_CMD CGI_PARAMS,
+ NEIGHBOR_DEL_DOC CGI_DOC)
+{
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ };
+ if (neighbor_ident_vty_parse_cgi(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- return add_remote_or_local_bts(vty, cell_id, &nik);
+ return neighbor_ident_del_neighbor(vty, vty->index, &n);
}
-DEFUN(cfg_neighbor_del_bts_nr, cfg_neighbor_del_bts_nr_cmd,
- NEIGHBOR_DEL_CMD LOCAL_BTS_PARAMS,
- NEIGHBOR_DEL_DOC LOCAL_BTS_DOC)
+DEFUN(cfg_neighbor_del_cgi_ps, cfg_neighbor_del_cgi_ps_cmd,
+ NEIGHBOR_DEL_CMD CGI_PS_PARAMS,
+ NEIGHBOR_DEL_DOC CGI_PS_DOC)
{
- return del_local_bts(vty, neighbor_ident_vty_parse_bts_nr(vty, argv));
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ };
+ if (neighbor_ident_vty_parse_cgi_ps(vty, &n.cell_id.id, argv))
+ return CMD_WARNING;
+ return neighbor_ident_del_neighbor(vty, vty->index, &n);
}
DEFUN(cfg_neighbor_del_arfcn_bsic, cfg_neighbor_del_arfcn_bsic_cmd,
- NEIGHBOR_DEL_CMD NEIGHBOR_IDENT_VTY_KEY_PARAMS,
- NEIGHBOR_DEL_DOC NEIGHBOR_IDENT_VTY_KEY_DOC)
+ NEIGHBOR_DEL_CMD CELL_AB_VTY_PARAMS,
+ NEIGHBOR_DEL_DOC CELL_AB_VTY_DOC)
{
- struct neighbor_ident_key key;
+ struct cell_ab ab;
+ neighbor_ident_vty_parse_arfcn_bsic(&ab, argv);
+ return del_neighbor_by_cell_ab(vty, &ab);
+}
- if (!neighbor_ident_vty_parse_key_params(vty, argv, &key))
- return CMD_WARNING;
+DEFUN(cfg_neighbor_del_all, cfg_neighbor_del_all_cmd,
+ "no neighbors",
+ NO_STR
+ "Remove all local and remote-BSS neighbor config for this cell."
+ " Note that this falls back to the legacy behavior of regarding all local cells as neighbors.\n")
+{
+ return neighbor_del_all(vty);
+}
- return del_by_key(vty, &key);
+DEFUN(cfg_neighbor_bind, cfg_neighbor_bind_cmd,
+ "neighbor-resolution bind " VTY_IPV46_CMD " [<0-65535>]",
+ NEIGHBOR_DOC "Bind Neighbor Resolution Service (CTRL interface) to given ip and port\n"
+ IP_STR IPV6_STR "Port to bind the service to [defaults to 4248 if not provided]\n")
+{
+ vty_out(vty, "%% Warning: The CTRL interface for Neighbor Address Resolution is now deprecated."
+ "Upgrade osmo-pcu and drop the 'neighbor-resolution bind " VTY_IPV46_CMD " [<0-65535>]' VTY "
+ "option in order to let osmo-pcu use the new resolution method using the PCUIF over IPA "
+ "multiplex, which will work out of the box without required configuration.%s", VTY_NEWLINE);
+ osmo_talloc_replace_string(bsc_gsmnet, &bsc_gsmnet->neigh_ctrl.addr, argv[0]);
+ if (argc > 1)
+ bsc_gsmnet->neigh_ctrl.port = atoi(argv[1]);
+ else
+ bsc_gsmnet->neigh_ctrl.port = OSMO_CTRL_PORT_BSC_NEIGH;
+ return CMD_SUCCESS;
}
-struct write_neighbor_ident_entry_data {
- struct vty *vty;
- const char *indent;
- struct gsm_bts *bts;
-};
+void neighbor_ident_vty_write_network(struct vty *vty, const char *indent)
+{
+ if (bsc_gsmnet->neigh_ctrl.addr)
+ vty_out(vty, "%sneighbor-resolution bind %s %" PRIu16 "%s", indent, bsc_gsmnet->neigh_ctrl.addr,
+ bsc_gsmnet->neigh_ctrl.port, VTY_NEWLINE);
+}
-static bool write_neighbor_ident_list(const struct neighbor_ident_key *key,
- const struct gsm0808_cell_id_list2 *val,
- void *cb_data)
+static int vty_write_cell_id_u(struct vty *vty, enum CELL_IDENT id_discr, const union gsm0808_cell_id_u *cell_id_u)
{
- struct write_neighbor_ident_entry_data *d = cb_data;
- struct vty *vty = d->vty;
- int i;
-
- if (d->bts) {
- if (d->bts->nr != key->from_bts)
- return true;
- } else if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS)
- return true;
-
-#define NEIGH_BSS_WRITE(fmt, args...) do { \
- vty_out(vty, "%sneighbor " fmt " arfcn %u ", d->indent, ## args, key->arfcn); \
- if (key->bsic == BSIC_ANY) \
- vty_out(vty, "bsic any"); \
- else \
- vty_out(vty, "bsic %u", key->bsic & 0x3f); \
- vty_out(vty, "%s", VTY_NEWLINE); \
- } while(0)
-
- switch (val->id_discr) {
+ const struct osmo_cell_global_id *cgi;
+ const struct osmo_cell_global_id_ps *cgi_ps;
+
+ switch (id_discr) {
case CELL_IDENT_LAC:
- for (i = 0; i < val->id_list_len; i++) {
- NEIGH_BSS_WRITE("lac %u", val->id_list[i].lac);
- }
+ vty_out(vty, "lac %u", cell_id_u->lac);
break;
case CELL_IDENT_LAC_AND_CI:
- for (i = 0; i < val->id_list_len; i++) {
- NEIGH_BSS_WRITE("lac-ci %u %u",
- val->id_list[i].lac_and_ci.lac,
- val->id_list[i].lac_and_ci.ci);
- }
+ vty_out(vty, "lac-ci %u %u", cell_id_u->lac_and_ci.lac, cell_id_u->lac_and_ci.ci);
break;
case CELL_IDENT_WHOLE_GLOBAL:
- for (i = 0; i < val->id_list_len; i++) {
- const struct osmo_cell_global_id *cgi = &val->id_list[i].global;
- NEIGH_BSS_WRITE("cgi %s %s %u %u",
- osmo_mcc_name(cgi->lai.plmn.mcc),
- osmo_mnc_name(cgi->lai.plmn.mnc, cgi->lai.plmn.mnc_3_digits),
- cgi->lai.lac, cgi->cell_identity);
- }
+ cgi = &cell_id_u->global;
+ vty_out(vty, "cgi %s %s %u %u",
+ osmo_mcc_name(cgi->lai.plmn.mcc),
+ osmo_mnc_name(cgi->lai.plmn.mnc, cgi->lai.plmn.mnc_3_digits),
+ cgi->lai.lac, cgi->cell_identity);
+ break;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ cgi_ps = &cell_id_u->global_ps;
+ vty_out(vty, "cgi-ps %s %s %u %u %u",
+ osmo_mcc_name(cgi_ps->rai.lac.plmn.mcc),
+ osmo_mnc_name(cgi_ps->rai.lac.plmn.mnc, cgi_ps->rai.lac.plmn.mnc_3_digits),
+ cgi_ps->rai.lac.lac, cgi_ps->rai.rac,
+ cgi_ps->cell_identity);
break;
default:
- vty_out(vty, "%% Unsupported Cell Identity%s", VTY_NEWLINE);
+ return -1;
}
-#undef NEIGH_BSS_WRITE
-
- return true;
+ return 0;
}
-void neighbor_ident_vty_write_remote_bss(struct vty *vty, const char *indent, struct gsm_bts *bts)
+void neighbor_ident_vty_write_bts(struct vty *vty, const char *indent, struct gsm_bts *bts)
{
- struct write_neighbor_ident_entry_data d = {
- .vty = vty,
- .indent = indent,
- .bts = bts,
- };
+ struct neighbor *n;
- neighbor_ident_iter(g_neighbor_cells, write_neighbor_ident_list, &d);
-}
+ llist_for_each_entry(n, &bts->neighbors, entry) {
+ switch (n->type) {
+ case NEIGHBOR_TYPE_BTS_NR:
+ vty_out(vty, "%sneighbor bts %u%s", indent, n->bts_nr, VTY_NEWLINE);
+ break;
-void neighbor_ident_vty_write_local_neighbors(struct vty *vty, const char *indent, struct gsm_bts *bts)
-{
- struct gsm_bts_ref *neigh;
+ case NEIGHBOR_TYPE_CELL_ID:
+ vty_out(vty, "%sneighbor ", indent);
+ if (vty_write_cell_id_u(vty, n->cell_id.id.id_discr, &n->cell_id.id.id)) {
+ vty_out(vty, "[Unsupported Cell Identity]%s", VTY_NEWLINE);
+ continue;
+ }
+
+ if (n->cell_id.ab_present) {
+ vty_out(vty, " arfcn %u ", n->cell_id.ab.arfcn);
+ if (n->cell_id.ab.bsic == BSIC_ANY)
+ vty_out(vty, "bsic any");
+ else
+ vty_out(vty, "bsic %u", n->cell_id.ab.bsic & 0x3f);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+ break;
- llist_for_each_entry(neigh, &bts->local_neighbors, entry) {
- vty_out(vty, "%sneighbor bts %u%s", indent, neigh->bts->nr, VTY_NEWLINE);
+ default:
+ /* Ignore anything invalid */
+ break;
+ }
}
}
-void neighbor_ident_vty_write(struct vty *vty, const char *indent, struct gsm_bts *bts)
-{
- neighbor_ident_vty_write_local_neighbors(vty, indent, bts);
- neighbor_ident_vty_write_remote_bss(vty, indent, bts);
-}
-
DEFUN(show_bts_neighbor, show_bts_neighbor_cmd,
- "show bts <0-255> neighbor " NEIGHBOR_IDENT_VTY_KEY_PARAMS,
+ "show bts <0-255> neighbor " CELL_AB_VTY_PARAMS,
SHOW_STR "Display information about a BTS\n" "BTS number\n"
"Query which cell would be the target for this neighbor ARFCN+BSIC\n"
- NEIGHBOR_IDENT_VTY_KEY_DOC)
+ CELL_AB_VTY_DOC)
{
- int found = 0;
- struct neighbor_ident_key key;
- struct gsm_bts_ref *neigh;
- const struct gsm0808_cell_id_list2 *res;
- struct gsm_bts *bts = gsm_bts_num(g_net, atoi(argv[0]));
- struct write_neighbor_ident_entry_data d = {
- .vty = vty,
- .indent = "% ",
- .bts = bts,
- };
+ struct cell_ab ab;
+ struct gsm_bts *local_neighbor = NULL;
+ struct gsm0808_cell_id_list2 remote_neighbors = { 0 };
+ struct gsm_bts *bts = gsm_bts_num(bsc_gsmnet, atoi(argv[0]));
if (!bts) {
vty_out(vty, "%% Error: cannot find BTS '%s'%s", argv[0],
@@ -538,43 +595,58 @@ DEFUN(show_bts_neighbor, show_bts_neighbor_cmd,
return CMD_WARNING;
}
- if (!neighbor_ident_bts_parse_key_params(vty, bts, &argv[1], &key))
- return CMD_WARNING;
+ neighbor_ident_vty_parse_arfcn_bsic(&ab, &argv[1]);
- /* Is there a local BTS that matches the key? */
- llist_for_each_entry(neigh, &bts->local_neighbors, entry) {
- if (!neighbor_ident_key_matches_bts(&key, neigh->bts))
- continue;
- vty_out(vty, "%% %s resolves to local BTS %u lac-ci %u %u%s",
- neighbor_ident_key_name(&key), neigh->bts->nr, neigh->bts->location_area_code,
- neigh->bts->cell_identity, VTY_NEWLINE);
- found++;
+ switch (resolve_neighbors(&local_neighbor, &remote_neighbors, bts, &ab, true)) {
+ case 0:
+ break;
+ case -ENOENT:
+ vty_out(vty, "%% No entry for BTS %u -> %s%s", bts->nr, cell_ab_to_str_c(OTC_SELECT, &ab), VTY_NEWLINE);
+ return CMD_WARNING;
+ default:
+ vty_out(vty, "%% Error while resolving neighbors BTS %u -> %s%s", bts->nr,
+ cell_ab_to_str_c(OTC_SELECT, &ab), VTY_NEWLINE);
+ return CMD_WARNING;
}
- res = neighbor_ident_get(g_neighbor_cells, &key);
- if (res) {
- write_neighbor_ident_list(&key, res, &d);
- found++;
+ /* From successful rc == 0, there is exactly either a local_neighbor or a nonempty remote_neighbors list. */
+
+ vty_out(vty, "%% BTS %u -> %s resolves to", bts->nr, cell_ab_to_str_c(OTC_SELECT, &ab));
+ if (local_neighbor) {
+ vty_out(vty, " local BTS %u lac-ci %u %u%s",
+ local_neighbor->nr,
+ local_neighbor->location_area_code,
+ local_neighbor->cell_identity, VTY_NEWLINE);
}
- if (!found)
- vty_out(vty, "%% No entry for %s%s", neighbor_ident_key_name(&key), VTY_NEWLINE);
+ if (remote_neighbors.id_list_len) {
+ vty_out(vty, " remote-BSS neighbors: %s%s",
+ gsm0808_cell_id_list_name_c(OTC_SELECT, &remote_neighbors),
+ VTY_NEWLINE);
+ }
return CMD_SUCCESS;
}
-void neighbor_ident_vty_init(struct gsm_network *net, struct neighbor_ident_list *nil)
+void neighbor_ident_vty_init(void)
{
- g_net = net;
- g_neighbor_cells = nil;
+ install_element(GSMNET_NODE, &cfg_neighbor_bind_cmd);
+
install_element(BTS_NODE, &cfg_neighbor_add_bts_nr_cmd);
install_element(BTS_NODE, &cfg_neighbor_add_lac_cmd);
install_element(BTS_NODE, &cfg_neighbor_add_lac_ci_cmd);
install_element(BTS_NODE, &cfg_neighbor_add_cgi_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_add_cgi_ps_cmd);
install_element(BTS_NODE, &cfg_neighbor_add_lac_arfcn_bsic_cmd);
install_element(BTS_NODE, &cfg_neighbor_add_lac_ci_arfcn_bsic_cmd);
install_element(BTS_NODE, &cfg_neighbor_add_cgi_arfcn_bsic_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_add_cgi_ps_arfcn_bsic_cmd);
install_element(BTS_NODE, &cfg_neighbor_del_bts_nr_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_del_lac_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_del_lac_ci_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_del_cgi_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_del_cgi_ps_cmd);
install_element(BTS_NODE, &cfg_neighbor_del_arfcn_bsic_cmd);
+ install_element(BTS_NODE, &cfg_neighbor_del_all_cmd);
install_element_ve(&show_bts_neighbor_cmd);
}
diff --git a/src/osmo-bsc/net_init.c b/src/osmo-bsc/net_init.c
index 5ea564d70..e5d3fad28 100644
--- a/src/osmo-bsc/net_init.c
+++ b/src/osmo-bsc/net_init.c
@@ -17,37 +17,85 @@
*
*/
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/gsm23236.h>
+
#include <osmocom/bsc/osmo_bsc.h>
-#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/handover_cfg.h>
#include <osmocom/bsc/chan_alloc.h>
#include <osmocom/bsc/neighbor_ident.h>
-#include <osmocom/bsc/gsm_timers.h>
-
-static struct T_def gsm_network_T_defs[] = {
- { .T=7, .default_val=10, .desc="inter-BSC Handover MO, HO Required to HO Command" },
- { .T=8, .default_val=10, .desc="inter-BSC Handover MO, HO Command to final Clear" },
- { .T=10, .default_val=6, .desc="RR Assignment" },
- { .T=101, .default_val=10, .desc="inter-BSC Handover MT, HO Request to HO Accept" },
- { .T=3101, .default_val=3, .desc="RR Immediate Assignment" },
- { .T=3103, .default_val=5, .desc="Handover" },
- { .T=3105, .default_val=100, .unit=T_MS, .desc="Physical Information" },
- { .T=3107, .default_val=5, .desc="(unused)" },
- { .T=3109, .default_val=5, .desc="RSL SACCH deactivation" },
- { .T=3111, .default_val=2, .desc="Wait time before RSL RF Channel Release" },
- { .T=993111, .default_val=4, .desc="Wait time after lchan was released in error (should be T3111 + 2s)" },
- { .T=3113, .default_val=7, .desc="Paging"},
- { .T=3115, .default_val=10, .desc="(unused)" },
- { .T=3117, .default_val=10, .desc="(unused)" },
- { .T=3119, .default_val=10, .desc="(unused)" },
- { .T=3122, .default_val=GSM_T3122_DEFAULT, .desc="Wait time after RR Immediate Assignment Reject" },
- { .T=3141, .default_val=10, .desc="(unused)" },
- { .T=3212, .default_val=5, .unit=T_CUSTOM,
- .desc="Periodic Location Update timer, sent to MS (1 = 6 minutes)" },
- { .T=993210, .default_val=20, .desc="After L3 Complete, wait for MSC to confirm" },
- { .T=999, .default_val=60, .desc="After Clear Request, wait for MSC to Clear Command (sanity)" },
- { .T=992427, .default_val=4, .desc="MGCP timeout (2427 is the default MGCP port number)" },
+#include <osmocom/bsc/bts_setup_ramp.h>
+#include <osmocom/bsc/paging.h>
+
+static struct osmo_tdef gsm_network_T_defs[] = {
+ { .T = 4, .default_val = 5, .desc = "Timeout to receive BSSMAP RESET ACKNOWLEDGE from the MSC" },
+ { .T = 7, .default_val = 10, .desc = "inter-BSC/MSC Handover outgoing, BSSMAP HO Required to HO Command timeout" },
+ { .T = 8, .default_val = 10, .desc = "inter-BSC/MSC Handover outgoing, BSSMAP HO Command to final Clear timeout" },
+ { .T = 10, .default_val = 6, .desc = "RR Assignment" },
+ { .T = 101, .default_val = 10, .desc = "inter-BSC/MSC Handover incoming, BSSMAP HO Request to HO Accept" },
+ { .T = 3101, .default_val = 3, .desc = "RR Immediate Assignment" },
+ { .T = 3103, .default_val = 5, .desc = "Handover" },
+ { .T = 3105, .default_val = GSM_T3105_DEFAULT, .min_val = 1, .unit = OSMO_TDEF_MS, .desc = "Physical Information" },
+ { .T = 3107, .default_val = 5, .desc = "(unused)" },
+ { .T = 3109, .default_val = 5, .desc = "RSL SACCH deactivation" },
+ { .T = 3111, .default_val = 2, .desc = "Wait time before RSL RF Channel Release" },
+ { .T = 3113, .default_val = 7, .desc = "Paging"},
+ { .T = 3115, .default_val = 10, .desc = "(unused)" },
+ { .T = 3117, .default_val = 10, .desc = "(unused)" },
+ { .T = 3119, .default_val = 10, .desc = "(unused)" },
+ { .T = 3122, .default_val = GSM_T3122_DEFAULT, .desc = "Wait time after RR Immediate Assignment Reject" },
+ { .T = 3141, .default_val = 10, .desc = "(unused)" },
+ { .T = 3212, .default_val = 5, .unit = OSMO_TDEF_CUSTOM,
+ .desc = "Periodic Location Update timer, sent to MS (1 = 6 minutes)" },
+ { .T = -4, .default_val = 60, .desc = "After Clear Request, wait for MSC to Clear Command (sanity)" },
+ { .T = -5, .default_val = 5, .desc = "Timeout to switch dynamic timeslot PCHAN modes"},
+ { .T = -6, .default_val = 5, .desc = "Timeout for RSL Channel Activate ACK after sending RSL Channel Activate" },
+ { .T = -7, .default_val = 5, .desc = "Timeout for RSL IPA CRCX ACK after sending RSL IPA CRCX" },
+ { .T = -8, .default_val = 5, .desc = "Timeout for RSL IPA MDCX ACK after sending RSL IPA MDCX" },
+ { .T = -9, .default_val = 5, .desc = "Timeout for availability of MGW endpoint" },
+ { .T = -10, .default_val = 5, .desc = "Timeout for fully configured MGW endpoint" },
+ { .T = -11, .default_val = 5, .desc = "Timeout for Perform Location Response from SMLC" },
+ { .T = -12, .default_val = 5, .desc = "Timeout for obtaining TA after BSSLAP TA Request" },
+ { .T = -13, .default_val = 5, .desc = "Timeout for RR Channel Mode Modify ACK (BSC <-> MS)" },
+ { .T = -14, .default_val = 5, .desc = "Timeout for RSL Channel Mode Modify ACK (BSC <-> BTS)" },
+ { .T = -16, .default_val = 1000, .unit = OSMO_TDEF_MS,
+ .desc = "Granularity for all_allocated:* rate counters: amount of milliseconds that one counter increment"
+ " represents. See also X17, X18" },
+ { .T = -17, .default_val = 0, .unit = OSMO_TDEF_MS,
+ .desc = "Rounding threshold for all_allocated:* rate counters: round up to the next counter increment"
+ " after this many milliseconds. If set to half of X16 (or 0), employ the usual round() behavior:"
+ " round up after half of a granularity period. If set to 1, behave like ceil(): already"
+ " increment the counter immediately when all channels are allocated. If set >= X16, behave like"
+ " floor(): only increment after a full X16 period of all channels being occupied."
+ " See also X16, X18" },
+ { .T = -18, .default_val = 60000, .unit = OSMO_TDEF_MS,
+ .desc = "Forget-sum period for all_allocated:* rate counters:"
+ " after this amount of idle time, forget internally cumulated time remainders. Zero to always"
+ " keep remainders. See also X16, X17." },
+ { .T = -25, .default_val = 5, .desc = "Timeout for initial user data after an MSC initiated an SCCP connection to the BSS" },
+ { .T = -28, .default_val = 30, .desc = "Interval at which to try to recover a BORKEN lchan" },
+ { .T = -3105, .default_val = GSM_NY1_DEFAULT, .unit = OSMO_TDEF_CUSTOM,
+ .desc = "Ny1: Maximum number of Physical Information (re)transmissions" },
+ { .T = -3111, .default_val = 4, .desc = "Wait time after lchan was released in error (should be T3111 + 2s)" },
+ { .T = -3113, .default_val = PAGING_THRESHOLD_X3113_DEFAULT_SEC,
+ .desc = "Maximum Paging Request Transmit Delay Threshold: " \
+ "If the estimated transmit delay of the messages in the paging queue surpasses this threshold, then new incoming "
+ "paging requests will if possible replace a request in retransmission state from the queue or otherwise be discarded, "
+ "hence limiting the size of the queue and maximum delay of its scheduled requests. "
+ "X3113 also serves as the upper boundary for dynamic T3113 when estimating the expected maximum delay to get a response" },
+ { .T = -3210, .default_val = 20, .desc = "After L3 Complete, wait for MSC to confirm" },
+ {}
+};
+
+struct osmo_tdef g_mgw_tdefs[] = {
+ { .T = -2427, .default_val = 5, .desc = "timeout for MGCP response from MGW" },
+ {}
+};
+
+struct osmo_tdef_group bsc_tdef_group[] = {
+ { .name = "net", .tdefs = gsm_network_T_defs, .desc = "GSM network" },
+ { .name = "mgw", .tdefs = g_mgw_tdefs, .desc = "MGW (Media Gateway) interface" },
{}
};
@@ -72,14 +120,21 @@ struct gsm_network *gsm_network_init(void *ctx)
INIT_LLIST_HEAD(&net->subscr_conns);
- net->bsc_subscribers = talloc_zero(net, struct llist_head);
- INIT_LLIST_HEAD(net->bsc_subscribers);
+ net->bsc_subscribers = bsc_subscr_store_alloc(net);
INIT_LLIST_HEAD(&net->bts_list);
net->num_bts = 0;
net->T_defs = gsm_network_T_defs;
- T_defs_reset(net->T_defs);
+ osmo_tdefs_reset(net->T_defs);
+
+ net->mgw.tdefs = g_mgw_tdefs;
+ osmo_tdefs_reset(net->mgw.tdefs);
+
+ net->null_nri_ranges = osmo_nri_ranges_alloc(net);
+ net->nri_bitlen = OSMO_NRI_BITLEN_DEFAULT;
+
+ bts_setup_ramp_init_network(net);
return net;
}
diff --git a/src/osmo-bsc/nm_bb_transc_fsm.c b/src/osmo-bsc/nm_bb_transc_fsm.c
new file mode 100644
index 000000000..a781cb1dc
--- /dev/null
+++ b/src/osmo-bsc/nm_bb_transc_fsm.c
@@ -0,0 +1,454 @@
+/* NM BaseBand Transceiver FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/debug.h>
+
+#define X(s) (1 << (s))
+
+#define nm_bb_transc_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+static inline void nm_bb_transc_fsm_becomes_enabled(struct gsm_bts_bb_trx *bb_transc)
+{
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+ nm_obj_fsm_becomes_enabled_disabled(trx->bts, bb_transc, NM_OC_BASEB_TRANSC, true);
+}
+
+static inline void nm_bb_transc_fsm_becomes_disabled(struct gsm_bts_bb_trx *bb_transc)
+{
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+ nm_obj_fsm_becomes_enabled_disabled(trx->bts, bb_transc, NM_OC_BASEB_TRANSC, false);
+}
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+
+ bb_transc->mo.sw_act_rep_received = false;
+ bb_transc->mo.get_attr_sent = false;
+ bb_transc->mo.get_attr_rep_received = false;
+ bb_transc->mo.adm_unlock_sent = false;
+ bb_transc->mo.rsl_connect_sent = false;
+ bb_transc->mo.rsl_connect_ack_received = false;
+ bb_transc->mo.opstart_sent = false;
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ bb_transc->mo.sw_act_rep_received = true;
+ break;
+ case NM_EV_SETUP_RAMP_READY:
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /*should not happen... */
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_DEPENDENCY:
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void configure_loop(struct gsm_bts_bb_trx *bb_transc, const struct gsm_nm_state *state, bool allow_opstart)
+{
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+
+ if (bts_setup_ramp_wait(trx->bts))
+ return;
+
+ /* nanoBTS only: delay until SW Activated Report is received, which
+ * tells us the IPA Object version (may be used to set attr conditionally). */
+ if (is_nanobts(trx->bts) && !bb_transc->mo.sw_act_rep_received)
+ return;
+
+ /* Request TRX-level attributes */
+ if (!bb_transc->mo.get_attr_sent && !bb_transc->mo.get_attr_rep_received) {
+ uint8_t attr_buf[3]; /* enlarge if needed */
+ uint8_t *ptr = &attr_buf[0];
+
+ *(ptr++) = NM_ATT_MANUF_STATE;
+ *(ptr++) = NM_ATT_SW_CONFIG;
+ if (is_ipa_abisip_bts(trx->bts))
+ *(ptr++) = NM_ATT_IPACC_SUPP_FEATURES;
+
+ OSMO_ASSERT((ptr - attr_buf) <= sizeof(attr_buf));
+ abis_nm_get_attr(trx->bts, NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff,
+ &attr_buf[0], (ptr - attr_buf));
+ bb_transc->mo.get_attr_sent = true;
+ }
+
+ if (bb_transc->mo.get_attr_rep_received &&
+ state->administrative != NM_STATE_UNLOCKED && !bb_transc->mo.adm_unlock_sent) {
+ bb_transc->mo.adm_unlock_sent = true;
+ /* Note: nanoBTS sometimes fails NACKing the BaseBand
+ Transceiver Unlock command while in Dependency, specially
+ during first attempt after boot. When NACK is received, the
+ OML link is dropped and the whole procedure is restarted. */
+ abis_nm_chg_adm_state(trx->bts, NM_OC_BASEB_TRANSC,
+ trx->bts->bts_nr, trx->nr, 0xff,
+ NM_STATE_UNLOCKED);
+ }
+
+ /* Provision BTS with RSL IP addr & port to connect to: */
+ if (allow_opstart && state->administrative == NM_STATE_UNLOCKED &&
+ !bb_transc->mo.rsl_connect_sent && !bb_transc->mo.rsl_connect_ack_received) {
+ bb_transc->mo.rsl_connect_sent = true;
+ abis_nm_ipaccess_rsl_connect(trx, trx->bts->ip_access.rsl_ip,
+ 3003, trx->rsl_tei_primary);
+ }
+
+ /* OPSTART after receiving RSL CONNECT ACK. We cannot delay until the
+ * RSL/IPA socket is connected to us because nanoBTS only attempts
+ * connection after receiving an OPSTART: */
+ if (allow_opstart && state->administrative == NM_STATE_UNLOCKED &&
+ bb_transc->mo.rsl_connect_ack_received && !bb_transc->mo.opstart_sent) {
+ bb_transc->mo.opstart_sent = true;
+ abis_nm_opstart(trx->bts, NM_OC_BASEB_TRANSC, trx->bts->bts_nr, trx->nr, 0xff);
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+
+ if (trx->bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE);
+ return;
+ }
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, false);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ bb_transc->mo.sw_act_rep_received = true;
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, false);
+ break;
+ case NM_EV_GET_ATTR_REP:
+ bb_transc->mo.get_attr_rep_received = true;
+ bb_transc->mo.get_attr_sent = false;
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, false);
+ return;
+ case NM_EV_RSL_CONNECT_ACK:
+ bb_transc->mo.rsl_connect_ack_received = true;
+ bb_transc->mo.rsl_connect_sent = false;
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, false);
+ break;
+ case NM_EV_RSL_CONNECT_NACK:
+ ipaccess_drop_oml_deferred(trx->bts);
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ configure_loop(bb_transc, new_state, false);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, false);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, true);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+ struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc);
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ bb_transc->mo.sw_act_rep_received = true;
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, true);
+ break;
+ case NM_EV_GET_ATTR_REP:
+ bb_transc->mo.get_attr_rep_received = true;
+ bb_transc->mo.get_attr_sent = false;
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, true);
+ return;
+ case NM_EV_RSL_CONNECT_ACK:
+ bb_transc->mo.rsl_connect_ack_received = true;
+ bb_transc->mo.rsl_connect_sent = false;
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, true);
+ break;
+ case NM_EV_RSL_CONNECT_NACK:
+ ipaccess_drop_oml_deferred(trx->bts);
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ /* There's no point in moving back to Dependency, since it's broken
+ and it acts actually as if it was in Offline state */
+ if (!trx->bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY);
+ } else {
+ /* Moreover, in nanoBTS we need to check here for tx
+ Opstart since we may have gone Unlocked state
+ in this event, which means Opstart may be txed here. */
+ configure_loop(bb_transc, new_state, true);
+ }
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ configure_loop(bb_transc, new_state, true);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, true);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+
+ /* Reset state, we don't need it in this state and it will need to be
+ reused as soon as we move back to Disabled */
+ bb_transc->mo.get_attr_sent = false;
+ bb_transc->mo.get_attr_rep_received = false;
+ bb_transc->mo.adm_unlock_sent = false;
+ bb_transc->mo.rsl_connect_ack_received = false;
+ bb_transc->mo.rsl_connect_sent = false;
+ bb_transc->mo.opstart_sent = false;
+
+ nm_bb_transc_fsm_becomes_enabled(bb_transc);
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED)
+ return;
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_bb_transc_fsm_becomes_disabled(bb_transc);
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_bb_transc_fsm_becomes_disabled(bb_transc);
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_bb_transc_fsm_becomes_disabled(bb_transc);
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OPSTART_ACK:
+ case NM_EV_OPSTART_NACK:
+ /* TODO: if on state OFFLINE and rx NACK, try again? */
+ bb_transc->mo.opstart_sent = false;
+ break;
+ case NM_EV_OML_DOWN:
+ if (fi->state != NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED) {
+ if (fi->state == NM_BB_TRANSC_ST_OP_ENABLED)
+ nm_bb_transc_fsm_becomes_disabled(bb_transc);
+ nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static struct osmo_fsm_state nm_bb_transc_fsm_states[] = {
+ [NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE) |
+ X(NM_BB_TRANSC_ST_OP_ENABLED),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_GET_ATTR_REP) |
+ X(NM_EV_RSL_CONNECT_ACK) |
+ X(NM_EV_RSL_CONNECT_NACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE) |
+ X(NM_BB_TRANSC_ST_OP_ENABLED),
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_GET_ATTR_REP) |
+ X(NM_EV_RSL_CONNECT_ACK) |
+ X(NM_EV_RSL_CONNECT_NACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_BB_TRANSC_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_BB_TRANSC_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_STATE_CHG_REP),
+ .out_state_mask =
+ X(NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_bb_transc_fsm = {
+ .name = "NM_BB_TRANSC_OP",
+ .states = nm_bb_transc_fsm_states,
+ .num_states = ARRAY_SIZE(nm_bb_transc_fsm_states),
+ .allstate_event_mask =
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_OML_DOWN),
+ .allstate_action = st_op_allstate,
+ .event_names = nm_fsm_event_names,
+ .log_subsys = DNM,
+};
+
+static __attribute__((constructor)) void nm_bb_transc_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_bb_transc_fsm) == 0);
+}
diff --git a/src/osmo-bsc/nm_bts_fsm.c b/src/osmo-bsc/nm_bts_fsm.c
new file mode 100644
index 000000000..aaccea0bf
--- /dev/null
+++ b/src/osmo-bsc/nm_bts_fsm.c
@@ -0,0 +1,443 @@
+/* NM BTS FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/debug.h>
+
+#define X(s) (1 << (s))
+
+#define nm_bts_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+
+ bts->mo.sw_act_rep_received = false;
+ bts->mo.get_attr_sent = false;
+ bts->mo.get_attr_rep_received = false;
+ bts->mo.set_attr_sent = false;
+ bts->mo.set_attr_ack_received = false;
+ bts->mo.adm_unlock_sent = false;
+ bts->mo.opstart_sent = false;
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ bts->mo.sw_act_rep_received = true;
+ break;
+ case NM_EV_SETUP_RAMP_READY:
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /*should not happen... */
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_DEPENDENCY:
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void configure_loop(struct gsm_bts *bts, const struct gsm_nm_state *state, bool allow_opstart)
+{
+ struct msgb *msgb;
+
+ if (bts_setup_ramp_wait(bts))
+ return;
+
+ /* nanoBTS only: delay until SW Activated Report is received, which
+ * tells us the IPA Object version (may be used to set attr conditionally). */
+ if (is_nanobts(bts) && !bts->mo.sw_act_rep_received)
+ return;
+
+ /* Request generic BTS-level attributes */
+ if (!bts->mo.get_attr_sent && !bts->mo.get_attr_rep_received) {
+ uint8_t attr_buf[3]; /* enlarge if needed */
+ uint8_t *ptr = &attr_buf[0];
+
+ *(ptr++) = NM_ATT_MANUF_ID;
+ *(ptr++) = NM_ATT_SW_CONFIG;
+ if (is_ipa_abisip_bts(bts))
+ *(ptr++) = NM_ATT_IPACC_SUPP_FEATURES;
+
+ OSMO_ASSERT((ptr - attr_buf) <= sizeof(attr_buf));
+ abis_nm_get_attr(bts, NM_OC_BTS, 0, 0xff, 0xff,
+ &attr_buf[0], (ptr - attr_buf));
+ bts->mo.get_attr_sent = true;
+ }
+
+ if (bts->mo.get_attr_rep_received &&
+ !bts->mo.set_attr_sent && !bts->mo.set_attr_ack_received) {
+ bts->mo.set_attr_sent = true;
+ msgb = nanobts_gen_set_bts_attr(bts);
+ abis_nm_set_bts_attr(bts, msgb->data, msgb->len);
+ msgb_free(msgb);
+ }
+
+ if (bts->mo.set_attr_ack_received &&
+ state->administrative != NM_STATE_UNLOCKED && !bts->mo.adm_unlock_sent) {
+ bts->mo.adm_unlock_sent = true;
+ abis_nm_chg_adm_state(bts, NM_OC_BTS,
+ bts->bts_nr, 0xff, 0xff,
+ NM_STATE_UNLOCKED);
+ /* Message containing BTS attributes, including the interference band bounds, was ACKed by the BTS.
+ * Store the sent bounds as the ones being used for logging and comparing interference levels. */
+ bts->interf_meas_params_used = bts->interf_meas_params_cfg;
+ }
+
+ if (allow_opstart && state->administrative == NM_STATE_UNLOCKED &&
+ bts->mo.set_attr_ack_received) {
+ if (!bts->mo.opstart_sent) {
+ bts->mo.opstart_sent = true;
+ abis_nm_opstart(bts, NM_OC_BTS, bts->bts_nr, 0xff, 0xff);
+ }
+ }
+}
+
+static void rx_get_attr_rep(struct gsm_bts *bts, bool allow_opstart)
+{
+ struct gsm_gprs_nsvc *nsvc;
+
+ bts->mo.get_attr_rep_received = true;
+ bts->mo.get_attr_sent = false;
+
+ /* Announce bts_features are available to related NSVC MOs */
+ for (int i = 0; i < ARRAY_SIZE(bts->site_mgr->gprs.nsvc); i++) {
+ nsvc = gsm_bts_sm_nsvc_num(bts->site_mgr, i);
+ if (nsvc)
+ osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_FEATURE_NEGOTIATED, NULL);
+ }
+
+ /* Move FSM forward */
+ configure_loop(bts, &bts->mo.nm_state, allow_opstart);
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+
+ /* nanoBTS is broken, doesn't follow TS 12.21. Opstart MUST be sent
+ during Dependency, so we simply move to OFFLINE state here to avoid
+ duplicating code */
+ if (bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_OFFLINE);
+ return;
+ }
+ configure_loop(bts, &bts->mo.nm_state, false);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ bts->mo.sw_act_rep_received = true;
+ configure_loop(bts, &bts->mo.nm_state, false);
+ break;
+ case NM_EV_GET_ATTR_REP:
+ rx_get_attr_rep(bts, false);
+ return;
+ case NM_EV_SET_ATTR_ACK:
+ bts->mo.set_attr_ack_received = true;
+ bts->mo.set_attr_sent = false;
+ configure_loop(bts, &bts->mo.nm_state, false);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_OFFLINE);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ configure_loop(bts, new_state, false);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(bts, &bts->mo.nm_state, false);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+
+ configure_loop(bts, &bts->mo.nm_state, true);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ bts->mo.sw_act_rep_received = true;
+ configure_loop(bts, &bts->mo.nm_state, true);
+ break;
+ case NM_EV_GET_ATTR_REP:
+ rx_get_attr_rep(bts, true);
+ return;
+ case NM_EV_SET_ATTR_ACK:
+ bts->mo.set_attr_ack_received = true;
+ bts->mo.set_attr_sent = false;
+ configure_loop(bts, &bts->mo.nm_state, true);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ /* There's no point in moving back to Dependency, since it's broken
+ and it acts actually as if it was in Offline state */
+ if (!bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_DEPENDENCY);
+ } else {
+ /* Moreover, in nanoBTS we need to check here for tx
+ Opstart since we may have gone Unlocked state
+ in this event, which means Opstart may be txed here. */
+ configure_loop(bts, new_state, true);
+ }
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ configure_loop(bts, new_state, true);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(bts, &bts->mo.nm_state, true);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+
+ /* Reset state, we don't need it in this state and it will need to be
+ reused as soon as we move back to Disabled */
+ bts->mo.opstart_sent = false;
+ bts->mo.adm_unlock_sent = false;
+ bts->mo.get_attr_sent = false;
+ bts->mo.get_attr_rep_received = false;
+ bts->mo.set_attr_sent = false;
+ bts->mo.set_attr_ack_received = false;
+
+ /* Resume power saving on the BCCH carrier, if was enabled */
+ if (bts->c0_max_power_red_db > 0) {
+ LOG_BTS(bts, DRSL, LOGL_NOTICE, "Resuming BCCH carrier power reduction "
+ "operation mode (maximum %u dB)\n", bts->c0_max_power_red_db);
+ gsm_bts_send_c0_power_red(bts, bts->c0_max_power_red_db);
+ }
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED)
+ return;
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OPSTART_ACK:
+ case NM_EV_OPSTART_NACK:
+ /* TODO: if on state OFFLINE and rx NACK, try again? */
+ bts->mo.opstart_sent = false;
+ break;
+ case NM_EV_OML_DOWN:
+ if (fi->state != NM_BTS_ST_OP_DISABLED_NOTINSTALLED)
+ nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static struct osmo_fsm_state nm_bts_fsm_states[] = {
+ [NM_BTS_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_BTS_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_BTS_ST_OP_DISABLED_OFFLINE) |
+ X(NM_BTS_ST_OP_ENABLED),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_BTS_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_GET_ATTR_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_ST_OP_DISABLED_OFFLINE) |
+ X(NM_BTS_ST_OP_ENABLED),
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_BTS_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_GET_ATTR_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_BTS_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_BTS_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_STATE_CHG_REP),
+ .out_state_mask =
+ X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_BTS_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_bts_fsm = {
+ .name = "NM_BTS_OP",
+ .states = nm_bts_fsm_states,
+ .num_states = ARRAY_SIZE(nm_bts_fsm_states),
+ .allstate_event_mask =
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_OML_DOWN),
+ .allstate_action = st_op_allstate,
+ .event_names = nm_fsm_event_names,
+ .log_subsys = DNM,
+};
+
+static __attribute__((constructor)) void nm_bts_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_bts_fsm) == 0);
+}
diff --git a/src/osmo-bsc/nm_bts_sm_fsm.c b/src/osmo-bsc/nm_bts_sm_fsm.c
new file mode 100644
index 000000000..24a1ec3c6
--- /dev/null
+++ b/src/osmo-bsc/nm_bts_sm_fsm.c
@@ -0,0 +1,326 @@
+/* NM BTS Site Manager FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/debug.h>
+
+#define X(s) (1 << (s))
+
+#define nm_bts_sm_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+ struct gsm_bts *bts = gsm_bts_sm_get_bts(site_mgr);
+
+ site_mgr->peer_has_no_avstate_offline = (bts->type == GSM_BTS_TYPE_NANOBTS);
+ site_mgr->mo.sw_act_rep_received = false;
+ site_mgr->mo.opstart_sent = false;
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+ struct gsm_bts *bts = gsm_bts_sm_get_bts(site_mgr);
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ site_mgr->mo.sw_act_rep_received = true;
+ break;
+ case NM_EV_SETUP_RAMP_READY:
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* nanobts always go directly into Reported ENABLED state during
+ startup, but we still need to OPSTART it, otherwise it won't
+ connect on RSL later on */
+ if (bts->type == GSM_BTS_TYPE_NANOBTS) {
+ site_mgr->mo.opstart_sent = true;
+ abis_nm_opstart(bts, NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff);
+ } else {
+ LOGPFSML(fi, LOGL_NOTICE, "Received BTS Site Mgr State Report Enabled "
+ "without Opstart. You are probably using a nanoBTS but don't "
+ "have your .cfg with 'type nanobts'. Otherwise, you probably "
+ "are using an old osmo-bts; automatically adjusting OML "
+ "behavior to be backward-compatible.\n");
+ }
+ site_mgr->peer_has_no_avstate_offline = true;
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_DEPENDENCY:
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void configure_loop(struct gsm_bts_sm *site_mgr, const struct gsm_nm_state *_state, bool allow_opstart)
+{
+ struct gsm_bts *bts = gsm_bts_sm_get_bts(site_mgr);
+
+ if (bts_setup_ramp_wait(bts))
+ return;
+
+ /* nanoBTS only: delay until SW Activated Report is received, which
+ * tells us the IPA Object version (may be used to set attr conditionally). */
+ if (is_nanobts(bts) && !site_mgr->mo.sw_act_rep_received)
+ return;
+
+ if (allow_opstart && !site_mgr->mo.opstart_sent) {
+ site_mgr->mo.opstart_sent = true;
+ abis_nm_opstart(bts, NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff);
+ }
+}
+
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ site_mgr->mo.sw_act_rep_received = true;
+ configure_loop(site_mgr, &site_mgr->mo.nm_state, false);
+ break;
+ case NM_EV_SETUP_RAMP_READY:
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+
+ configure_loop(site_mgr, &site_mgr->mo.nm_state, true);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ site_mgr->mo.sw_act_rep_received = true;
+ configure_loop(site_mgr, &site_mgr->mo.nm_state, true);
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(site_mgr, &site_mgr->mo.nm_state, true);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED)
+ return;
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OPSTART_ACK:
+ case NM_EV_OPSTART_NACK:
+ /* TODO: if on state OFFLINE and rx NACK, try again? */
+ site_mgr->mo.opstart_sent = false;
+ break;
+ case NM_EV_OML_DOWN:
+ if (fi->state != NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED)
+ nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static struct osmo_fsm_state nm_bts_sm_fsm_states[] = {
+ [NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_BTS_SM_ST_OP_DISABLED_OFFLINE) |
+ X(NM_BTS_SM_ST_OP_ENABLED),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_SM_ST_OP_DISABLED_OFFLINE) |
+ X(NM_BTS_SM_ST_OP_ENABLED),
+ .name = "DISABLED_DEPENDENCY",
+ .action = st_op_disabled_dependency,
+ },
+ [NM_BTS_SM_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_BTS_SM_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_BTS_SM_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_STATE_CHG_REP),
+ .out_state_mask =
+ X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_BTS_SM_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_bts_sm_fsm = {
+ .name = "NM_BTS_SM_OP",
+ .states = nm_bts_sm_fsm_states,
+ .num_states = ARRAY_SIZE(nm_bts_sm_fsm_states),
+ .allstate_event_mask =
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_OML_DOWN),
+ .allstate_action = st_op_allstate,
+ .event_names = nm_fsm_event_names,
+ .log_subsys = DNM,
+};
+
+static __attribute__((constructor)) void nm_bts_sm_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_bts_sm_fsm) == 0);
+}
diff --git a/src/osmo-bsc/nm_channel_fsm.c b/src/osmo-bsc/nm_channel_fsm.c
new file mode 100644
index 000000000..2f389d7ac
--- /dev/null
+++ b/src/osmo-bsc/nm_channel_fsm.c
@@ -0,0 +1,376 @@
+/* NM Radio Channel FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
+#include <osmocom/bsc/ipaccess.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/timeslot_fsm.h>
+
+#define X(s) (1 << (s))
+
+#define nm_chan_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+
+ ts->mo.set_attr_sent = false;
+ ts->mo.set_attr_ack_received = false;
+ ts->mo.adm_unlock_sent = false;
+ ts->mo.opstart_sent = false;
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ case NM_EV_SETUP_RAMP_READY:
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /*should not happen... */
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_DEPENDENCY:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void configure_loop(struct gsm_bts_trx_ts *ts, const struct gsm_nm_state *state, bool allow_opstart)
+{
+ enum abis_nm_chan_comb ccomb;
+ struct gsm_bts_trx *trx = ts->trx;
+
+ if (bts_setup_ramp_wait(ts->trx->bts))
+ return;
+
+ if (!ts->mo.set_attr_sent && !ts->mo.set_attr_ack_received) {
+ ts->mo.set_attr_sent = true;
+ ccomb = abis_nm_chcomb4pchan(ts->pchan_from_config);
+ if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL)
+ ipaccess_drop_oml_deferred(trx->bts);
+ }
+
+ if (state->administrative != NM_STATE_UNLOCKED && !ts->mo.adm_unlock_sent) {
+ ts->mo.adm_unlock_sent = true;
+ abis_nm_chg_adm_state(trx->bts, NM_OC_CHANNEL,
+ trx->bts->bts_nr, trx->nr, ts->nr,
+ NM_STATE_UNLOCKED);
+ }
+
+ if (allow_opstart && state->administrative == NM_STATE_UNLOCKED &&
+ ts->mo.set_attr_ack_received && !ts->mo.opstart_sent) {
+ ts->mo.opstart_sent = true;
+ abis_nm_opstart(trx->bts, NM_OC_CHANNEL, trx->bts->bts_nr, trx->nr, ts->nr);
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+
+ if (ts->trx->bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE);
+ return;
+ }
+ configure_loop(ts, &ts->mo.nm_state, false);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ configure_loop(ts, &ts->mo.nm_state, false);
+ break;
+ case NM_EV_SET_ATTR_ACK:
+ ts->mo.set_attr_ack_received = true;
+ ts->mo.set_attr_sent = false;
+ configure_loop(ts, &ts->mo.nm_state, false);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ configure_loop(ts, new_state, false);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(ts, &ts->mo.nm_state, false);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+
+ configure_loop(ts, &ts->mo.nm_state, true);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ configure_loop(ts, &ts->mo.nm_state, true);
+ break;
+ case NM_EV_SET_ATTR_ACK:
+ ts->mo.set_attr_ack_received = true;
+ ts->mo.set_attr_sent = false;
+ configure_loop(ts, &ts->mo.nm_state, true);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ /* There's no point in moving back to Dependency, since it's broken
+ and it acts actually as if it was in Offline state */
+ if (!ts->trx->bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY);
+ } else {
+ /* Moreover, in nanoBTS we need to check here for tx
+ Opstart since we may have gone Unlocked state
+ in this event, which means Opstart may be txed here. */
+ configure_loop(ts, new_state, true);
+ }
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ configure_loop(ts, new_state, true);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(ts, &ts->mo.nm_state, true);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+
+ /* Reset state, we don't need it in this state and it will need to be
+ reused as soon as we move back to Disabled */
+ ts->mo.opstart_sent = false;
+ ts->mo.adm_unlock_sent = false;
+ ts->mo.set_attr_ack_received = false;
+ ts->mo.set_attr_sent = false;
+
+ osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL);
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED)
+ return;
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OPSTART_ACK:
+ case NM_EV_OPSTART_NACK:
+ /* TODO: if on state OFFLINE and rx NACK, try again? */
+ ts->mo.opstart_sent = false;
+ break;
+ case NM_EV_OML_DOWN:
+ if (fi->state != NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) {
+ osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_DOWN, NULL);
+ nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_NOTINSTALLED);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static struct osmo_fsm_state nm_chan_fsm_states[] = {
+ [NM_CHAN_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_CHAN_ST_OP_DISABLED_OFFLINE) |
+ X(NM_CHAN_ST_OP_ENABLED),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_CHAN_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_DISABLED_OFFLINE) |
+ X(NM_CHAN_ST_OP_ENABLED),
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_CHAN_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_CHAN_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_CHAN_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_STATE_CHG_REP),
+ .out_state_mask =
+ X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_CHAN_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_chan_fsm = {
+ .name = "NM_CHAN_OP",
+ .states = nm_chan_fsm_states,
+ .num_states = ARRAY_SIZE(nm_chan_fsm_states),
+ .allstate_event_mask =
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_OML_DOWN),
+ .allstate_action = st_op_allstate,
+ .event_names = nm_fsm_event_names,
+ .log_subsys = DNM,
+};
+
+static __attribute__((constructor)) void nm_chan_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_chan_fsm) == 0);
+}
diff --git a/src/osmo-bsc/nm_common_fsm.c b/src/osmo-bsc/nm_common_fsm.c
new file mode 100644
index 000000000..7719f0672
--- /dev/null
+++ b/src/osmo-bsc/nm_common_fsm.c
@@ -0,0 +1,90 @@
+/* NM FSM, common bits */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/core/linuxlist.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/signal.h>
+
+const struct value_string nm_fsm_event_names[] = {
+ { NM_EV_SW_ACT_REP, "SW_ACT_REP" },
+ { NM_EV_STATE_CHG_REP, "STATE_CHG_REP" },
+ { NM_EV_GET_ATTR_REP, "GET_ATTR_REP" },
+ { NM_EV_SET_ATTR_ACK, "SET_ATTR_ACK" },
+ { NM_EV_OPSTART_ACK, "OPSTART_ACK" },
+ { NM_EV_OPSTART_NACK, "OPSTART_NACK" },
+ { NM_EV_OML_DOWN, "OML_DOWN" },
+ { NM_EV_FORCE_LOCK, "FORCE_LOCK_CHG" },
+ { NM_EV_FEATURE_NEGOTIATED, "FEATURE_NEGOTIATED" },
+ { NM_EV_RSL_CONNECT_ACK, "RSL_CONNECT_ACK" },
+ { NM_EV_RSL_CONNECT_NACK, "RSL_CONNECT_NACK" },
+ { 0, NULL }
+};
+
+void nm_obj_fsm_becomes_enabled_disabled(struct gsm_bts *bts, void *obj,
+ enum abis_nm_obj_class obj_class, bool running)
+{
+ struct nm_running_chg_signal_data nsd;
+
+ memset(&nsd, 0, sizeof(nsd));
+ nsd.bts = bts;
+ nsd.obj_class = obj_class;
+ nsd.obj = obj;
+ nsd.running = running;
+
+ osmo_signal_dispatch(SS_NM, S_NM_RUNNING_CHG, &nsd);
+}
+
+/* nm_configuring_fsm_inst_dispatch(struct gsm_abis_mo *mo, uint32_t event, void *data) */
+#define nm_configuring_fsm_inst_dispatch(mo, event, data) do { \
+ if ((mo)->nm_state.operational != NM_OPSTATE_ENABLED) \
+ _osmo_fsm_inst_dispatch((mo)->fi, event, data, __FILE__, __LINE__); \
+ } while (0)
+
+/*!
+ * Dispatch an event to all configuring/non-enabled BTS NM fsms
+ *
+ * \param[in] bts a pointer to the BTS instance
+ * \param[in] event the FSM event. See \fn osmo_fsm_inst_dispatch
+ * \param[in] data the private data of the event.
+ */
+void nm_fsm_dispatch_all_configuring(struct gsm_bts *bts, uint32_t event, void *data)
+{
+ struct gsm_bts_trx *trx;
+
+ nm_configuring_fsm_inst_dispatch(&bts->site_mgr->mo, event, data);
+ nm_configuring_fsm_inst_dispatch(&bts->mo, event, data);
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ nm_configuring_fsm_inst_dispatch(&trx->mo, event, data);
+ nm_configuring_fsm_inst_dispatch(&trx->bb_transc.mo, event, data);
+ for (unsigned long i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ nm_configuring_fsm_inst_dispatch(&ts->mo, event, data);
+ }
+ }
+
+ /* GPRS MOs */
+ nm_configuring_fsm_inst_dispatch(&bts->site_mgr->gprs.nse.mo, event, data);
+ for (unsigned long i = 0; i < ARRAY_SIZE(bts->site_mgr->gprs.nsvc); i++)
+ nm_configuring_fsm_inst_dispatch(&bts->site_mgr->gprs.nsvc[i].mo, event, data);
+ nm_configuring_fsm_inst_dispatch(&bts->gprs.cell.mo, event, data);
+}
diff --git a/src/osmo-bsc/nm_gprs_cell_fsm.c b/src/osmo-bsc/nm_gprs_cell_fsm.c
new file mode 100644
index 000000000..10f8d07de
--- /dev/null
+++ b/src/osmo-bsc/nm_gprs_cell_fsm.c
@@ -0,0 +1,432 @@
+/* NM GPRS Cell FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/debug.h>
+
+#define X(s) (1 << (s))
+
+#define nm_gprs_cell_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+
+ cell->mo.sw_act_rep_received = false;
+ cell->mo.get_attr_sent = false;
+ cell->mo.get_attr_rep_received = false;
+ cell->mo.set_attr_sent = false;
+ cell->mo.set_attr_ack_received = false;
+ cell->mo.adm_unlock_sent = false;
+ cell->mo.opstart_sent = false;
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ cell->mo.sw_act_rep_received = true;
+ break;
+ case NM_EV_SETUP_RAMP_READY:
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_DEPENDENCY:
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void configure_loop(struct gsm_gprs_cell *cell, const struct gsm_nm_state *state, bool allow_opstart)
+{
+ struct msgb *msgb;
+ struct gsm_bts *bts = container_of(cell, struct gsm_bts, gprs.cell);
+
+ if (bts->gprs.mode == BTS_GPRS_NONE)
+ return;
+
+ if (bts_setup_ramp_wait(bts))
+ return;
+
+ /* nanoBTS only: delay until SW Activated Report is received, which
+ * tells us the IPA Object version (may be used to set attr conditionally). */
+ if (is_nanobts(bts) && !cell->mo.sw_act_rep_received)
+ return;
+
+ if (!cell->mo.get_attr_sent && !cell->mo.get_attr_rep_received) {
+ uint8_t attr_buf[2]; /* enlarge if needed */
+ uint8_t *ptr = &attr_buf[0];
+
+ *(ptr++) = NM_ATT_SW_CONFIG;
+ if (is_ipa_abisip_bts(bts))
+ *(ptr++) = NM_ATT_IPACC_SUPP_FEATURES;
+
+ OSMO_ASSERT((ptr - attr_buf) <= sizeof(attr_buf));
+ abis_nm_get_attr(bts, NM_OC_GPRS_CELL,
+ bts->bts_nr, 0x00, 0xff,
+ &attr_buf[0], (ptr - attr_buf));
+ cell->mo.get_attr_sent = true;
+ }
+
+ /* OS#6172: old osmo-bts versions do NACK Get Attributes for GPRS Cell,
+ * so we do not check if cell->mo.get_attr_rep_received is set here. */
+ if (!cell->mo.set_attr_sent && !cell->mo.set_attr_ack_received) {
+ cell->mo.set_attr_sent = true;
+ msgb = nanobts_gen_set_cell_attr(bts);
+ OSMO_ASSERT(msgb);
+ abis_nm_ipaccess_set_attr(bts, NM_OC_GPRS_CELL, bts->bts_nr,
+ 0, 0xff, msgb->data, msgb->len);
+ msgb_free(msgb);
+ }
+
+ if (state->administrative != NM_STATE_UNLOCKED && !cell->mo.adm_unlock_sent) {
+ cell->mo.adm_unlock_sent = true;
+ abis_nm_chg_adm_state(bts, NM_OC_GPRS_CELL,
+ bts->bts_nr, 0, 0xff,
+ NM_STATE_UNLOCKED);
+ }
+
+ if (allow_opstart && state->administrative == NM_STATE_UNLOCKED &&
+ cell->mo.set_attr_ack_received) {
+ if (!cell->mo.opstart_sent) {
+ cell->mo.opstart_sent = true;
+ abis_nm_opstart(bts, NM_OC_GPRS_CELL, bts->bts_nr, 0, 0xff);
+ }
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ struct gsm_bts *bts = container_of(cell, struct gsm_bts, gprs.cell);
+
+ /* nanoBTS is broken, doesn't follow TS 12.21. Opstart MUST be sent
+ during Dependency, so we simply move to OFFLINE state here to avoid
+ duplicating code */
+ if (bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE);
+ return;
+ }
+ configure_loop(cell, &cell->mo.nm_state, false);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ cell->mo.sw_act_rep_received = true;
+ configure_loop(cell, &cell->mo.nm_state, false);
+ break;
+ case NM_EV_GET_ATTR_REP:
+ cell->mo.get_attr_rep_received = true;
+ cell->mo.get_attr_sent = false;
+ configure_loop(cell, &cell->mo.nm_state, false);
+ return;
+ case NM_EV_SET_ATTR_ACK:
+ cell->mo.set_attr_ack_received = true;
+ cell->mo.set_attr_sent = false;
+ configure_loop(cell, &cell->mo.nm_state, false);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ configure_loop(cell, new_state, false);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(cell, &cell->mo.nm_state, false);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+
+ configure_loop(cell, &cell->mo.nm_state, true);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ struct gsm_bts *bts = container_of(cell, struct gsm_bts, gprs.cell);
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ cell->mo.sw_act_rep_received = true;
+ configure_loop(cell, &cell->mo.nm_state, true);
+ break;
+ case NM_EV_GET_ATTR_REP:
+ cell->mo.get_attr_rep_received = true;
+ cell->mo.get_attr_sent = false;
+ configure_loop(cell, &cell->mo.nm_state, true);
+ return;
+ case NM_EV_SET_ATTR_ACK:
+ cell->mo.set_attr_ack_received = true;
+ cell->mo.set_attr_sent = false;
+ configure_loop(cell, &cell->mo.nm_state, true);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ /* There's no point in moving back to Dependency, since it's broken
+ and it acts actually as if it was in Offline state */
+ if (!bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY);
+ } else {
+ /* Moreover, in nanoBTS we need to check here for tx
+ Opstart since we may have gone Unlocked state
+ in this event, which means Opstart may be txed here. */
+ configure_loop(cell, new_state, true);
+ }
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ configure_loop(cell, new_state, true);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(cell, &cell->mo.nm_state, true);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+
+ /* Reset state, we don't need it in this state and it will need to be
+ reused as soon as we move back to Disabled */
+ cell->mo.opstart_sent = false;
+ cell->mo.adm_unlock_sent = false;
+ cell->mo.set_attr_ack_received = false;
+ cell->mo.set_attr_sent = false;
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED)
+ return;
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv;
+ struct gsm_bts *bts = container_of(cell, struct gsm_bts, gprs.cell);
+
+ switch (event) {
+ case NM_EV_OPSTART_ACK:
+ case NM_EV_OPSTART_NACK:
+ /* TODO: if on state OFFLINE and rx NACK, try again? */
+ cell->mo.opstart_sent = false;
+ break;
+ case NM_EV_FORCE_LOCK:
+ cell->mo.force_rf_lock = (bool)(intptr_t)data;
+ abis_nm_chg_adm_state(bts, NM_OC_GPRS_CELL,
+ bts->bts_nr, 0, 0xff,
+ cell->mo.force_rf_lock ? NM_STATE_LOCKED : NM_STATE_UNLOCKED);
+ break;
+ case NM_EV_OML_DOWN:
+ if (fi->state != NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED)
+ nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static struct osmo_fsm_state nm_gprs_cell_fsm_states[] = {
+ [NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE) |
+ X(NM_GPRS_CELL_ST_OP_ENABLED),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_GET_ATTR_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE) |
+ X(NM_GPRS_CELL_ST_OP_ENABLED),
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_GET_ATTR_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_CELL_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_GPRS_CELL_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_STATE_CHG_REP),
+ .out_state_mask =
+ X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_gprs_cell_fsm = {
+ .name = "NM_GPRS_CELL_OP",
+ .states = nm_gprs_cell_fsm_states,
+ .num_states = ARRAY_SIZE(nm_gprs_cell_fsm_states),
+ .allstate_event_mask =
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_FORCE_LOCK) |
+ X(NM_EV_OML_DOWN),
+ .allstate_action = st_op_allstate,
+ .event_names = nm_fsm_event_names,
+ .log_subsys = DNM,
+};
+
+static __attribute__((constructor)) void nm_gprs_cell_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_gprs_cell_fsm) == 0);
+}
diff --git a/src/osmo-bsc/nm_gprs_nse_fsm.c b/src/osmo-bsc/nm_gprs_nse_fsm.c
new file mode 100644
index 000000000..295f5fbce
--- /dev/null
+++ b/src/osmo-bsc/nm_gprs_nse_fsm.c
@@ -0,0 +1,403 @@
+/* NM GPRS NSE FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/bts_sm.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/debug.h>
+
+#define X(s) (1 << (s))
+
+#define nm_gprs_nse_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+
+ nse->mo.sw_act_rep_received = false;
+ nse->mo.set_attr_sent = false;
+ nse->mo.set_attr_ack_received = false;
+ nse->mo.adm_unlock_sent = false;
+ nse->mo.opstart_sent = false;
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ nse->mo.sw_act_rep_received = true;
+ break;
+ case NM_EV_SETUP_RAMP_READY:
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_DEPENDENCY:
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void configure_loop(struct gsm_gprs_nse *nse, const struct gsm_nm_state *state, bool allow_opstart)
+{
+ struct msgb *msgb;
+ struct gsm_bts_sm *bts_sm = container_of(nse, struct gsm_bts_sm, gprs.nse);
+ struct gsm_bts *bts = gsm_bts_sm_get_bts(bts_sm);
+
+ if (bts_setup_ramp_wait(bts))
+ return;
+
+ /* nanoBTS only: delay until SW Activated Report is received, which
+ * tells us the IPA Object version (may be used to set attr conditionally). */
+ if (is_nanobts(bts) && !nse->mo.sw_act_rep_received)
+ return;
+
+ if (!nse->mo.set_attr_sent && !nse->mo.set_attr_ack_received) {
+ nse->mo.set_attr_sent = true;
+ msgb = nanobts_gen_set_nse_attr(bts_sm);
+ abis_nm_ipaccess_set_attr(bts, NM_OC_GPRS_NSE, bts->bts_nr,
+ 0xff, 0xff, msgb->data,
+ msgb->len);
+ msgb_free(msgb);
+ }
+
+ /* Attributes must be set before unlocking */
+ if (state->administrative != NM_STATE_UNLOCKED && nse->mo.set_attr_ack_received &&
+ !nse->mo.adm_unlock_sent) {
+ nse->mo.adm_unlock_sent = true;
+ abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE,
+ bts->bts_nr, 0xff, 0xff,
+ NM_STATE_UNLOCKED);
+ }
+
+ if (allow_opstart && state->administrative == NM_STATE_UNLOCKED &&
+ nse->mo.set_attr_ack_received) {
+ if (!nse->mo.opstart_sent) {
+ nse->mo.opstart_sent = true;
+ abis_nm_opstart(bts, NM_OC_GPRS_NSE, bts->bts_nr, 0xff, 0xff);
+ }
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ struct gsm_bts_sm *bts_sm = container_of(nse, struct gsm_bts_sm, gprs.nse);
+
+ /* nanoBTS is broken, doesn't follow TS 12.21. Opstart MUST be sent
+ during Dependency, so we simply move to OFFLINE state here to avoid
+ duplicating code */
+ if (bts_sm->peer_has_no_avstate_offline) {
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE);
+ return;
+ }
+ configure_loop(nse, &nse->mo.nm_state, false);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ nse->mo.sw_act_rep_received = true;
+ configure_loop(nse, &nse->mo.nm_state, false);
+ break;
+ case NM_EV_SET_ATTR_ACK:
+ nse->mo.set_attr_ack_received = true;
+ nse->mo.set_attr_sent = false;
+ configure_loop(nse, &nse->mo.nm_state, false);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ configure_loop(nse, new_state, false);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(nse, &nse->mo.nm_state, false);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+
+ configure_loop(nse, &nse->mo.nm_state, true);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ struct gsm_bts_sm *bts_sm = container_of(nse, struct gsm_bts_sm, gprs.nse);
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ nse->mo.sw_act_rep_received = true;
+ configure_loop(nse, &nse->mo.nm_state, true);
+ break;
+ case NM_EV_SET_ATTR_ACK:
+ nse->mo.set_attr_ack_received = true;
+ nse->mo.set_attr_sent = false;
+ configure_loop(nse, &nse->mo.nm_state, true);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ /* There's no point in moving back to Dependency, since it's broken
+ and it acts actually as if it was in Offline state */
+ if (!bts_sm->peer_has_no_avstate_offline) {
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY);
+ } else {
+ /* Moreover, in nanoBTS we need to check here for tx
+ Opstart since we may have gone Unlocked state
+ in this event, which means Opstart may be txed here. */
+ configure_loop(nse, new_state, true);
+ }
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ configure_loop(nse, new_state, true);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(nse, &nse->mo.nm_state, true);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+
+ /* Reset state, we don't need it in this state and it will need to be
+ reused as soon as we move back to Disabled */
+ nse->mo.opstart_sent = false;
+ nse->mo.adm_unlock_sent = false;
+ nse->mo.set_attr_ack_received = false;
+ nse->mo.set_attr_sent = false;
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED)
+ return;
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv;
+ struct gsm_bts_sm *bts_sm = container_of(nse, struct gsm_bts_sm, gprs.nse);
+ struct gsm_bts *bts = gsm_bts_sm_get_bts(bts_sm);
+
+ switch (event) {
+ case NM_EV_OPSTART_ACK:
+ case NM_EV_OPSTART_NACK:
+ /* TODO: if on state OFFLINE and rx NACK, try again? */
+ nse->mo.opstart_sent = false;
+ break;
+ case NM_EV_FORCE_LOCK:
+ nse->mo.force_rf_lock = (bool)(intptr_t)data;
+ abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE,
+ bts->bts_nr, 0xff, 0xff,
+ nse->mo.force_rf_lock ? NM_STATE_LOCKED : NM_STATE_UNLOCKED);
+ break;
+ case NM_EV_OML_DOWN:
+ if (fi->state != NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED)
+ nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static struct osmo_fsm_state nm_gprs_nse_fsm_states[] = {
+ [NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE) |
+ X(NM_GPRS_NSE_ST_OP_ENABLED),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE) |
+ X(NM_GPRS_NSE_ST_OP_ENABLED),
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_NSE_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_GPRS_NSE_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_STATE_CHG_REP),
+ .out_state_mask =
+ X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_gprs_nse_fsm = {
+ .name = "NM_GPRS_NSE_OP",
+ .states = nm_gprs_nse_fsm_states,
+ .num_states = ARRAY_SIZE(nm_gprs_nse_fsm_states),
+ .allstate_event_mask =
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_FORCE_LOCK) |
+ X(NM_EV_OML_DOWN),
+ .allstate_action = st_op_allstate,
+ .event_names = nm_fsm_event_names,
+ .log_subsys = DNM,
+};
+
+static __attribute__((constructor)) void nm_gprs_nse_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_gprs_nse_fsm) == 0);
+}
diff --git a/src/osmo-bsc/nm_gprs_nsvc_fsm.c b/src/osmo-bsc/nm_gprs_nsvc_fsm.c
new file mode 100644
index 000000000..c37d46ad0
--- /dev/null
+++ b/src/osmo-bsc/nm_gprs_nsvc_fsm.c
@@ -0,0 +1,434 @@
+/* NM GPRS NSVC FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/bts_sm.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/debug.h>
+
+#define X(s) (1 << (s))
+
+#define nm_gprs_nsvc_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+
+ nsvc->mo.sw_act_rep_received = false;
+ nsvc->mo.set_attr_sent = false;
+ nsvc->mo.set_attr_sent = false;
+ nsvc->mo.set_attr_ack_received = false;
+ nsvc->mo.adm_unlock_sent = false;
+ nsvc->mo.opstart_sent = false;
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ nsvc->mo.sw_act_rep_received = true;
+ break;
+ case NM_EV_FEATURE_NEGOTIATED:
+ case NM_EV_SETUP_RAMP_READY:
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_DEPENDENCY:
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static bool has_valid_nsvc(const struct gsm_gprs_nsvc *nsvc)
+{
+ /* If not configured (enabled) at all. */
+ if (!nsvc->enabled)
+ return false;
+
+ /* remote address must be valid */
+ if (osmo_sockaddr_is_any(&nsvc->remote))
+ return false;
+ /* remote port must be valid */
+ switch (nsvc->remote.u.sa.sa_family) {
+ case AF_INET:
+ return nsvc->remote.u.sin.sin_port != 0;
+ case AF_INET6:
+ return nsvc->remote.u.sin6.sin6_port != 0;
+ default:
+ return false;
+ }
+}
+
+static void configure_loop(struct gsm_gprs_nsvc *nsvc, const struct gsm_nm_state *state, bool allow_opstart)
+{
+ struct msgb *msgb;
+
+ if (nsvc->bts->gprs.mode == BTS_GPRS_NONE)
+ return;
+
+ if (bts_setup_ramp_wait(nsvc->bts))
+ return;
+
+ /* nanoBTS only: delay until SW Activated Report is received, which
+ * tells us the IPA Object version (may be used to set attr conditionally). */
+ if (is_nanobts(nsvc->bts) && !nsvc->mo.sw_act_rep_received)
+ return;
+
+ /* We need to know BTS features in order to know if we can set IPv6 addresses */
+ if (gsm_bts_features_negotiated(nsvc->bts) && !nsvc->mo.set_attr_sent &&
+ !nsvc->mo.set_attr_ack_received) {
+ if (!osmo_bts_has_feature(&nsvc->bts->features, BTS_FEAT_IPV6_NSVC) &&
+ nsvc->remote.u.sa.sa_family == AF_INET6) {
+ LOGPFSML(nsvc->mo.fi, LOGL_ERROR,
+ "BTS%d does not support IPv6 NSVC but an IPv6 address was configured!\n",
+ nsvc->bts->nr);
+ return;
+ }
+ if (!has_valid_nsvc(nsvc))
+ return;
+
+ nsvc->mo.set_attr_sent = true;
+ msgb = nanobts_gen_set_nsvc_attr(nsvc);
+ OSMO_ASSERT(msgb);
+ abis_nm_ipaccess_set_attr(nsvc->bts, NM_OC_GPRS_NSVC, nsvc->bts->bts_nr,
+ nsvc->id, 0xff, msgb->data, msgb->len);
+ msgb_free(msgb);
+ }
+
+ if (nsvc->mo.set_attr_ack_received && state->administrative != NM_STATE_UNLOCKED &&
+ !nsvc->mo.adm_unlock_sent) {
+ nsvc->mo.adm_unlock_sent = true;
+ abis_nm_chg_adm_state(nsvc->bts, NM_OC_GPRS_NSVC,
+ nsvc->bts->bts_nr, nsvc->id, 0xff,
+ NM_STATE_UNLOCKED);
+ }
+
+ if (allow_opstart && state->administrative == NM_STATE_UNLOCKED &&
+ nsvc->mo.set_attr_ack_received) {
+ if (!nsvc->mo.opstart_sent) {
+ nsvc->mo.opstart_sent = true;
+ abis_nm_opstart(nsvc->bts, NM_OC_GPRS_NSVC,
+ nsvc->bts->bts_nr, nsvc->id, 0xff);
+ }
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+
+ /* nanoBTS is broken, doesn't follow TS 12.21. Opstart MUST be sent
+ during Dependency, so we simply move to OFFLINE state here to avoid
+ duplicating code */
+ if (nsvc->bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE);
+ return;
+ }
+ configure_loop(nsvc, &nsvc->mo.nm_state, false);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ nsvc->mo.sw_act_rep_received = true;
+ /* fall-through */
+ case NM_EV_FEATURE_NEGOTIATED:
+ configure_loop(nsvc, &nsvc->mo.nm_state, false);
+ return;
+ case NM_EV_SET_ATTR_ACK:
+ nsvc->mo.set_attr_ack_received = true;
+ nsvc->mo.set_attr_sent = false;
+ configure_loop(nsvc, &nsvc->mo.nm_state, false);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ configure_loop(nsvc, new_state, false);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(nsvc, &nsvc->mo.nm_state, false);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+
+ configure_loop(nsvc, &nsvc->mo.nm_state, true);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ nsvc->mo.sw_act_rep_received = true;
+ /* fall-through */
+ case NM_EV_FEATURE_NEGOTIATED:
+ configure_loop(nsvc, &nsvc->mo.nm_state, true);
+ return;
+ case NM_EV_SET_ATTR_ACK:
+ nsvc->mo.set_attr_ack_received = true;
+ nsvc->mo.set_attr_sent = false;
+ configure_loop(nsvc, &nsvc->mo.nm_state, true);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ /* There's no point in moving back to Dependency, since it's broken
+ and it acts actually as if it was in Offline state */
+ if (!nsvc->bts->site_mgr->peer_has_no_avstate_offline) {
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY);
+ } else {
+ /* Moreover, in nanoBTS we need to check here for tx
+ Opstart since we may have gone Unlocked state
+ in this event, which means Opstart may be txed here. */
+ configure_loop(nsvc, new_state, true);
+ }
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ configure_loop(nsvc, new_state, true);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(nsvc, &nsvc->mo.nm_state, true);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+
+ /* Reset state, we don't need it in this state and it will need to be
+ reused as soon as we move back to Disabled */
+ nsvc->mo.opstart_sent = false;
+ nsvc->mo.adm_unlock_sent = false;
+ nsvc->mo.set_attr_sent = false;
+ nsvc->mo.set_attr_ack_received = false;
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED)
+ return;
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OPSTART_ACK:
+ case NM_EV_OPSTART_NACK:
+ /* TODO: if on state OFFLINE and rx NACK, try again? */
+ nsvc->mo.opstart_sent = false;
+ break;
+ case NM_EV_OML_DOWN:
+ if (fi->state != NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED)
+ nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static struct osmo_fsm_state nm_gprs_nsvc_fsm_states[] = {
+ [NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_FEATURE_NEGOTIATED) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE) |
+ X(NM_GPRS_NSVC_ST_OP_ENABLED),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_FEATURE_NEGOTIATED) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE) |
+ X(NM_GPRS_NSVC_ST_OP_ENABLED),
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_FEATURE_NEGOTIATED) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_NSVC_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_GPRS_NSVC_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_STATE_CHG_REP),
+ .out_state_mask =
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_gprs_nsvc_fsm = {
+ .name = "NM_GPRS_NSVC_OP",
+ .states = nm_gprs_nsvc_fsm_states,
+ .num_states = ARRAY_SIZE(nm_gprs_nsvc_fsm_states),
+ .allstate_event_mask =
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_OML_DOWN),
+ .allstate_action = st_op_allstate,
+ .event_names = nm_fsm_event_names,
+ .log_subsys = DNM,
+};
+
+static __attribute__((constructor)) void nm_gprs_nsvc_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_gprs_nsvc_fsm) == 0);
+}
diff --git a/src/osmo-bsc/nm_rcarrier_fsm.c b/src/osmo-bsc/nm_rcarrier_fsm.c
new file mode 100644
index 000000000..f5d7c270f
--- /dev/null
+++ b/src/osmo-bsc/nm_rcarrier_fsm.c
@@ -0,0 +1,455 @@
+/* NM Radio Carrier FSM */
+
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
+#include <osmocom/bsc/nm_common_fsm.h>
+#include <osmocom/bsc/debug.h>
+
+#define X(s) (1 << (s))
+
+#define nm_rcarrier_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+static inline void nm_rcarrier_fsm_becomes_enabled(struct gsm_bts_trx *trx)
+{
+ nm_obj_fsm_becomes_enabled_disabled(trx->bts, trx, NM_OC_RADIO_CARRIER, true);
+}
+
+static inline void nm_rcarrier_fsm_becomes_disabled(struct gsm_bts_trx *trx)
+{
+ nm_obj_fsm_becomes_enabled_disabled(trx->bts, trx, NM_OC_RADIO_CARRIER, false);
+}
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+
+ trx->mo.sw_act_rep_received = false;
+ trx->mo.get_attr_sent = false;
+ trx->mo.get_attr_rep_received = false;
+ trx->mo.set_attr_sent = false;
+ trx->mo.set_attr_ack_received = false;
+ trx->mo.adm_unlock_sent = false;
+ trx->mo.opstart_sent = false;
+}
+
+static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ trx->mo.sw_act_rep_received = true;
+ break;
+ case NM_EV_SETUP_RAMP_READY:
+ break;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /*should not happen... */
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_DEPENDENCY:
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void configure_loop(struct gsm_bts_trx *trx, const struct gsm_nm_state *state, bool allow_opstart)
+{
+ struct msgb *msgb;
+
+ if (bts_setup_ramp_wait(trx->bts))
+ return;
+
+ /* nanoBTS only: delay until SW Activated Report is received, which
+ * tells us the IPA Object version (may be used to set attr conditionally). */
+ if (is_nanobts(trx->bts) && !trx->mo.sw_act_rep_received)
+ return;
+
+ if (!trx->mo.get_attr_sent && !trx->mo.get_attr_rep_received) {
+ uint8_t attr_buf[2]; /* enlarge if needed */
+ uint8_t *ptr = &attr_buf[0];
+
+ *(ptr++) = NM_ATT_SW_CONFIG;
+ if (is_ipa_abisip_bts(trx->bts))
+ *(ptr++) = NM_ATT_IPACC_SUPP_FEATURES;
+
+ OSMO_ASSERT((ptr - attr_buf) <= sizeof(attr_buf));
+ abis_nm_get_attr(trx->bts, NM_OC_RADIO_CARRIER,
+ trx->bts->bts_nr, trx->nr, 0xff,
+ &attr_buf[0], (ptr - attr_buf));
+ trx->mo.get_attr_sent = true;
+ }
+
+ /* OS#6172: old osmo-bts versions do NACK Get Attributes for Radio Carrier,
+ * so we do not check if trx->mo.get_attr_rep_received is set here. */
+ if (!trx->mo.set_attr_sent && !trx->mo.set_attr_ack_received) {
+ trx->mo.set_attr_sent = true;
+ msgb = nanobts_gen_set_radio_attr(trx->bts, trx);
+ abis_nm_set_radio_attr(trx, msgb->data, msgb->len);
+ msgb_free(msgb);
+ }
+
+ if (!trx->mo.force_rf_lock && state->administrative != NM_STATE_UNLOCKED &&
+ !trx->mo.adm_unlock_sent) {
+ trx->mo.adm_unlock_sent = true;
+ abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER,
+ trx->bts->bts_nr, trx->nr, 0xff,
+ NM_STATE_UNLOCKED);
+ }
+
+ if (allow_opstart && state->administrative == NM_STATE_UNLOCKED &&
+ trx->mo.set_attr_ack_received && !trx->mo.opstart_sent) {
+ trx->mo.opstart_sent = true;
+ abis_nm_opstart(trx->bts, NM_OC_RADIO_CARRIER, trx->bts->bts_nr, trx->nr, 0xff);
+ }
+}
+
+static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+
+ /* In general nanoBTS is broken, doesn't follow TS 12.21. Opstart MUST
+ * be sent during Dependency, so we simply move to OFFLINE state here to
+ * avoid duplicating code. However, RadioCarrier seems to be implemented
+ * correctly and goes to Offline state during startup. If some HW
+ * version is found with the above estated bug, this code needs to be
+ * enabled, similar to what we do in nm_bb_transc_fsm:
+ */
+ /*if (trx->bts->site_mgr.peer_has_no_avstate_offline) {
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_OFFLINE);
+ return;
+ }*/
+ configure_loop(trx, &trx->mo.nm_state, false);
+}
+
+static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ trx->mo.sw_act_rep_received = true;
+ configure_loop(trx, &trx->mo.nm_state, false);
+ break;
+ case NM_EV_GET_ATTR_REP:
+ trx->mo.get_attr_rep_received = true;
+ trx->mo.get_attr_sent = false;
+ configure_loop(trx, &trx->mo.nm_state, false);
+ return;
+ case NM_EV_SET_ATTR_ACK:
+ trx->mo.set_attr_ack_received = true;
+ trx->mo.set_attr_sent = false;
+ configure_loop(trx, &trx->mo.nm_state, false);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* should not happen... */
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_OFFLINE);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ configure_loop(trx, new_state, false);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(trx, &trx->mo.nm_state, false);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+
+ configure_loop(trx, &trx->mo.nm_state, true);
+}
+
+static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_SW_ACT_REP:
+ trx->mo.sw_act_rep_received = true;
+ configure_loop(trx, &trx->mo.nm_state, true);
+ break;
+ case NM_EV_GET_ATTR_REP:
+ trx->mo.get_attr_rep_received = true;
+ trx->mo.get_attr_sent = false;
+ configure_loop(trx, &trx->mo.nm_state, true);
+ return;
+ case NM_EV_SET_ATTR_ACK:
+ trx->mo.set_attr_ack_received = true;
+ trx->mo.set_attr_sent = false;
+ configure_loop(trx, &trx->mo.nm_state, true);
+ return;
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_ENABLED);
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ configure_loop(trx, new_state, true);
+ return;
+ default:
+ return;
+ }
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(trx, &trx->mo.nm_state, true);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+
+ /* Reset state, we don't need it in this state and it will need to be
+ reused as soon as we move back to Disabled */
+ trx->mo.opstart_sent = false;
+ trx->mo.adm_unlock_sent = false;
+ trx->mo.set_attr_ack_received = false;
+ trx->mo.set_attr_sent = false;
+
+ nm_rcarrier_fsm_becomes_enabled(trx);
+}
+
+static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+ struct nm_statechg_signal_data *nsd;
+ const struct gsm_nm_state *new_state;
+
+ switch (event) {
+ case NM_EV_STATE_CHG_REP:
+ nsd = (struct nm_statechg_signal_data *)data;
+ new_state = &nsd->new_state;
+ /* Op state stays in Enabled, hence either Avail or Admin changed: */
+ if (new_state->operational == NM_OPSTATE_ENABLED) {
+ /* Some sort of availability change we don't care about: */
+ if (nsd->old_state.administrative == new_state->administrative)
+ return;
+ /* HACK: Admin state change without Op state change:
+ * According to TS 52.021 sec 5.3.1, Locking the NM obj should make
+ * it go into Disabled Dependency state, but current and older
+ * versions of osmo-bts (and potentially nanobts?) don't move from
+ * Operative=Enabled state and only change the Adminsitrative one.
+ * Let's account for this behavior here: */
+ switch (new_state->administrative) {
+ case NM_STATE_LOCKED:
+ nm_rcarrier_fsm_becomes_disabled(trx);
+ break;
+ case NM_STATE_UNLOCKED:
+ nm_rcarrier_fsm_becomes_enabled(trx);
+ break;
+ }
+ return;
+ }
+ switch (new_state->availability) { /* operational = DISABLED */
+ case NM_AVSTATE_NOT_INSTALLED:
+ case NM_AVSTATE_POWER_OFF:
+ nm_rcarrier_fsm_becomes_disabled(trx);
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED);
+ return;
+ case NM_AVSTATE_DEPENDENCY:
+ nm_rcarrier_fsm_becomes_disabled(trx);
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY);
+ return;
+ case NM_AVSTATE_OFF_LINE:
+ case NM_AVSTATE_OK:
+ nm_rcarrier_fsm_becomes_disabled(trx);
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_OFFLINE);
+ return;
+ default:
+ return;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
+
+ switch (event) {
+ case NM_EV_OPSTART_ACK:
+ case NM_EV_OPSTART_NACK:
+ /* TODO: if on state OFFLINE and rx NACK, try again? */
+ trx->mo.opstart_sent = false;
+ break;
+ case NM_EV_FORCE_LOCK:
+ trx->mo.force_rf_lock = (bool)(intptr_t)data;
+ abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER,
+ trx->bts->bts_nr, trx->nr, 0xff,
+ trx->mo.force_rf_lock ? NM_STATE_LOCKED : NM_STATE_UNLOCKED);
+ break;
+ case NM_EV_OML_DOWN:
+ if (fi->state != NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) {
+ if (fi->state == NM_RCARRIER_ST_OP_ENABLED)
+ nm_rcarrier_fsm_becomes_disabled(trx);
+ nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static struct osmo_fsm_state nm_rcarrier_fsm_states[] = {
+ [NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE) |
+ X(NM_RCARRIER_ST_OP_ENABLED),
+ .name = "DISABLED_NOTINSTALLED",
+ .onenter = st_op_disabled_notinstalled_on_enter,
+ .action = st_op_disabled_notinstalled,
+ },
+ [NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_GET_ATTR_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE) |
+ X(NM_RCARRIER_ST_OP_ENABLED),
+ .name = "DISABLED_DEPENDENCY",
+ .onenter = st_op_disabled_dependency_on_enter,
+ .action = st_op_disabled_dependency,
+ },
+ [NM_RCARRIER_ST_OP_DISABLED_OFFLINE] = {
+ .in_event_mask =
+ X(NM_EV_SW_ACT_REP) |
+ X(NM_EV_STATE_CHG_REP) |
+ X(NM_EV_GET_ATTR_REP) |
+ X(NM_EV_SET_ATTR_ACK) |
+ X(NM_EV_SETUP_RAMP_READY),
+ .out_state_mask =
+ X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_RCARRIER_ST_OP_ENABLED),
+ .name = "DISABLED_OFFLINE",
+ .onenter = st_op_disabled_offline_on_enter,
+ .action = st_op_disabled_offline,
+ },
+ [NM_RCARRIER_ST_OP_ENABLED] = {
+ .in_event_mask =
+ X(NM_EV_STATE_CHG_REP),
+ .out_state_mask =
+ X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) |
+ X(NM_RCARRIER_ST_OP_DISABLED_DEPENDENCY) |
+ X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE),
+ .name = "ENABLED",
+ .onenter = st_op_enabled_on_enter,
+ .action = st_op_enabled,
+ },
+};
+
+struct osmo_fsm nm_rcarrier_fsm = {
+ .name = "NM_RCARRIER_OP",
+ .states = nm_rcarrier_fsm_states,
+ .num_states = ARRAY_SIZE(nm_rcarrier_fsm_states),
+ .allstate_event_mask =
+ X(NM_EV_OPSTART_ACK) |
+ X(NM_EV_OPSTART_NACK) |
+ X(NM_EV_FORCE_LOCK) |
+ X(NM_EV_OML_DOWN),
+ .allstate_action = st_op_allstate,
+ .event_names = nm_fsm_event_names,
+ .log_subsys = DNM,
+};
+
+static __attribute__((constructor)) void nm_rcarrier_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&nm_rcarrier_fsm) == 0);
+}
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
index 01a55ff85..2a9805444 100644
--- a/src/osmo-bsc/osmo_bsc_bssap.c
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -19,7 +19,8 @@
*
*/
-#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <osmocom/bsc/osmo_bsc.h>
#include <osmocom/bsc/osmo_bsc_grace.h>
@@ -28,11 +29,14 @@
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
-#include <osmocom/bsc/gsm_04_80.h>
+#include <osmocom/bsc/gsm_08_08.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/codec_pref.h>
+#include <osmocom/bsc/data_rate_pref.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bsc_stats.h>
#include <osmocom/gsm/protocol/gsm_08_08.h>
#include <osmocom/gsm/gsm0808.h>
@@ -42,6 +46,11 @@
#include <osmocom/bsc/handover.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/bsc/lcs_loc_req.h>
+#include <osmocom/bsc/bssmap_reset.h>
+#include <osmocom/bsc/assignment_fsm.h>
+#include <osmocom/bsc/vgcs_fsm.h>
#define IP_V4_ADDR_LEN 4
@@ -49,6 +58,25 @@
* helpers for the assignment command
*/
+/* We expect MSC to provide use with an Osmocom extension TLV in BSSMAP_RESET to
+ * announce Osmux support */
+static void update_msc_osmux_support(struct bsc_msc_data *msc,
+ struct msgb *msg, unsigned int length)
+{
+ struct tlv_parsed tp;
+ int rc;
+ bool old_value = msc->remote_supports_osmux;
+
+ rc = osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1);
+ if (rc < 0)
+ LOGP(DMSC, LOGL_NOTICE, "Failed parsing TLV looking for Osmux support\n");
+
+ msc->remote_supports_osmux = !!TLVP_PRESENT(&tp, GSM0808_IE_OSMO_OSMUX_SUPPORT);
+
+ if (old_value != msc->remote_supports_osmux)
+ LOGP(DMSC, LOGL_INFO, "MSC detected AoIP Osmux support changed: %d->%d\n",
+ old_value, msc->remote_supports_osmux);
+}
static int bssmap_handle_reset_ack(struct bsc_msc_data *msc,
struct msgb *msg, unsigned int length)
@@ -61,6 +89,8 @@ static int bssmap_handle_reset_ack(struct bsc_msc_data *msc,
* that we have successfully received the reset-ack message */
a_reset_ack_confirm(msc);
+ update_msc_osmux_support(msc, msg, length);
+
return 0;
}
@@ -72,194 +102,160 @@ static int bssmap_handle_reset(struct bsc_msc_data *msc,
osmo_sccp_addr_name(osmo_ss7_instance_find(msc->a.cs7_instance),
&msc->a.msc_addr));
- /* Instruct the bsc to close all open sigtran connections and to
- * close all active channels on the BTS side as well */
- osmo_bsc_sigtran_reset(msc);
+ update_msc_osmux_support(msc, msg, length);
- /* Drop all ongoing paging requests that this MSC has created on any BTS */
- paging_flush_network(msc->network, msc);
-
- /* Inform the MSC that we have received the reset request and
- * that we acted accordingly */
- osmo_bsc_sigtran_tx_reset_ack(msc);
+ if (!msc->a.bssmap_reset) {
+ LOGP(DMSC, LOGL_ERROR, "(msc%d) missing RESET FSM\n", msc->nr);
+ /* Make sure to shut down all open connections, if any */
+ osmo_bsc_sigtran_reset(msc);
+ return -1;
+ }
- return 0;
+ /* Normal case: let the reset FSM orchestrate link down / link up callbacks. */
+ return osmo_fsm_inst_dispatch(msc->a.bssmap_reset->fi, BSSMAP_RESET_EV_RX_RESET, NULL);
}
/* Page a subscriber based on TMSI and LAC via the specified BTS.
* The msc parameter is the MSC which issued the corresponding paging request.
* Log an error if paging failed. */
-static void
-page_subscriber(struct bsc_msc_data *msc, struct gsm_bts *bts,
- uint32_t tmsi, uint32_t lac, const char *mi_string, uint8_t chan_needed)
+static void page_subscriber(const struct bsc_paging_params *params, struct gsm_bts *bts, uint32_t lac)
{
- struct bsc_subscr *subscr;
int ret;
- subscr = bsc_subscr_find_or_create_by_imsi(msc->network->bsc_subscribers,
- mi_string);
-
- if (subscr)
- log_set_context(LOG_CTX_BSC_SUBSCR, subscr);
-
- LOGP(DMSC, LOGL_INFO, "Paging request from MSC BTS: %d IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n",
- bts->nr, mi_string, tmsi, tmsi, lac);
-
- if (!subscr) {
- LOGP(DMSC, LOGL_ERROR, "Paging request failed: Could not allocate subscriber for %s\n", mi_string);
+ if (!bsc_grace_allow_new_connection(bsc_gsmnet, bts)) {
+ LOG_PAGING_BTS(params, bts, DMSC, LOGL_DEBUG, "RF-locked: not paging on this BTS\n");
return;
}
- subscr->lac = lac;
- subscr->tmsi = tmsi;
+ LOG_PAGING_BTS(params, bts, DMSC, LOGL_INFO, "Paging on LAC %u\n", lac);
- ret = bsc_grace_paging_request(msc->network->bsc_data->rf_ctrl->policy, subscr, chan_needed, msc, bts);
+ ret = paging_request_bts(params, bts);
if (ret == 0)
- LOGP(DMSC, LOGL_ERROR, "Paging request failed: BTS: %d IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n",
- bts->nr, mi_string, tmsi, tmsi, lac);
-
- /* the paging code has grabbed its own references */
- bsc_subscr_put(subscr);
-
- log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
+ LOG_PAGING_BTS(params, bts, DMSC, LOGL_INFO,
+ "Paging request failed, or repeated paging on LAC %u\n", lac);
}
-static void
-page_all_bts(struct bsc_msc_data *msc, uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+static void page_all_bts(const struct bsc_paging_params *params)
{
struct gsm_bts *bts;
- llist_for_each_entry(bts, &msc->network->bts_list, list)
- page_subscriber(msc, bts, tmsi, GSM_LAC_RESERVED_ALL_BTS, mi_string, chan_needed);
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list)
+ page_subscriber(params, bts, GSM_LAC_RESERVED_ALL_BTS);
}
-static void
-page_cgi(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
- uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+static void page_cgi(const struct bsc_paging_params *params)
{
int i;
- for (i = 0; i < cil->id_list_len; i++) {
- struct osmo_cell_global_id *id = &cil->id_list[i].global;
- if (!osmo_plmn_cmp(&id->lai.plmn, &msc->network->plmn)) {
+ for (i = 0; i < params->cil.id_list_len; i++) {
+ const struct osmo_cell_global_id *id = &params->cil.id_list[i].global;
+ if (!osmo_plmn_cmp(&id->lai.plmn, &bsc_gsmnet->plmn)) {
int paged = 0;
struct gsm_bts *bts;
- llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
if (bts->location_area_code != id->lai.lac)
continue;
if (bts->cell_identity != id->cell_identity)
continue;
- page_subscriber(msc, bts, tmsi, id->lai.lac, mi_string, chan_needed);
+ page_subscriber(params, bts, id->lai.lac);
paged = 1;
}
if (!paged) {
- LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with LAC %d and CI %d not found\n",
- mi_string, id->lai.lac, id->cell_identity);
+ LOG_PAGING(params, DMSC, LOGL_NOTICE, "BTS with LAC %u and CI %u not found\n",
+ id->lai.lac, id->cell_identity);
}
} else {
- LOGP(DMSC, LOGL_DEBUG, "Paging IMSI %s: MCC-MNC in Cell Identifier List "
- "(%s) do not match our network (%s)\n",
- mi_string, osmo_plmn_name(&id->lai.plmn),
- osmo_plmn_name2(&msc->network->plmn));
+ LOG_PAGING(params, DMSC, LOGL_DEBUG,
+ "MCC-MNC in Cell Identifier List (%s) do not match our network (%s)\n",
+ osmo_plmn_name_c(OTC_SELECT, &id->lai.plmn),
+ osmo_plmn_name_c(OTC_SELECT, &bsc_gsmnet->plmn));
}
}
}
-static void
-page_lac_and_ci(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
- uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+static void page_lac_and_ci(const struct bsc_paging_params *params)
{
int i;
- for (i = 0; i < cil->id_list_len; i++) {
- struct osmo_lac_and_ci_id *id = &cil->id_list[i].lac_and_ci;
+ for (i = 0; i < params->cil.id_list_len; i++) {
+ const struct osmo_lac_and_ci_id *id = &params->cil.id_list[i].lac_and_ci;
int paged = 0;
struct gsm_bts *bts;
- llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
if (bts->location_area_code != id->lac)
continue;
if (bts->cell_identity != id->ci)
continue;
- page_subscriber(msc, bts, tmsi, id->lac, mi_string, chan_needed);
+ page_subscriber(params, bts, id->lac);
paged = 1;
}
if (!paged) {
- LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with LAC %d and CI %d not found\n",
- mi_string, id->lac, id->ci);
+ LOG_PAGING(params, DMSC, LOGL_NOTICE, "BTS with LAC %u and CI %u not found\n", id->lac, id->ci);
}
}
}
-static void
-page_ci(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
- uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+static void page_ci(const struct bsc_paging_params *params)
{
int i;
- for (i = 0; i < cil->id_list_len; i++) {
- uint16_t ci = cil->id_list[i].ci;
+ for (i = 0; i < params->cil.id_list_len; i++) {
+ uint16_t ci = params->cil.id_list[i].ci;
int paged = 0;
struct gsm_bts *bts;
- llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
if (bts->cell_identity != ci)
continue;
- page_subscriber(msc, bts, tmsi, GSM_LAC_RESERVED_ALL_BTS, mi_string, chan_needed);
+ page_subscriber(params, bts, GSM_LAC_RESERVED_ALL_BTS);
paged = 1;
}
if (!paged) {
- LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with CI %d not found\n",
- mi_string, ci);
+ LOG_PAGING(params, DMSC, LOGL_NOTICE, "BTS with CI %u not found\n", ci);
}
}
}
-static void
-page_lai_and_lac(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
- uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+static void page_lai_and_lac(const struct bsc_paging_params *params)
{
int i;
- for (i = 0; i < cil->id_list_len; i++) {
- struct osmo_location_area_id *id = &cil->id_list[i].lai_and_lac;
- if (!osmo_plmn_cmp(&id->plmn, &msc->network->plmn)) {
+ for (i = 0; i < params->cil.id_list_len; i++) {
+ const struct osmo_location_area_id *id = &params->cil.id_list[i].lai_and_lac;
+ if (!osmo_plmn_cmp(&id->plmn, &bsc_gsmnet->plmn)) {
int paged = 0;
struct gsm_bts *bts;
- llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
if (bts->location_area_code != id->lac)
continue;
- page_subscriber(msc, bts, tmsi, id->lac, mi_string, chan_needed);
+ page_subscriber(params, bts, id->lac);
paged = 1;
}
if (!paged) {
- LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with LAC %d not found\n",
- mi_string, id->lac);
+ LOG_PAGING(params, DMSC, LOGL_NOTICE, "BTS with LAC %u not found\n", id->lac);
}
} else {
- LOGP(DMSC, LOGL_DEBUG, "Paging IMSI %s: MCC-MNC in Cell Identifier List "
- "(%s) do not match our network (%s)\n",
- mi_string, osmo_plmn_name(&id->plmn),
- osmo_plmn_name2(&msc->network->plmn));
+ LOG_PAGING(params, DMSC, LOGL_DEBUG,
+ "MCC-MNC in Cell Identifier List (%s) do not match our network (%s)\n",
+ osmo_plmn_name_c(OTC_SELECT, &id->plmn),
+ osmo_plmn_name_c(OTC_SELECT, &bsc_gsmnet->plmn));
}
}
}
-static void
-page_lac(struct bsc_msc_data *msc, struct gsm0808_cell_id_list2 *cil,
- uint32_t tmsi, const char *mi_string, uint8_t chan_needed)
+static void page_lac(const struct bsc_paging_params *params)
{
int i;
- for (i = 0; i < cil->id_list_len; i++) {
- uint16_t lac = cil->id_list[i].lac;
+ for (i = 0; i < params->cil.id_list_len; i++) {
+ uint16_t lac = params->cil.id_list[i].lac;
int paged = 0;
struct gsm_bts *bts;
- llist_for_each_entry(bts, &msc->network->bts_list, list) {
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
if (bts->location_area_code != lac)
continue;
- page_subscriber(msc, bts, tmsi, lac, mi_string, chan_needed);
+ page_subscriber(params, bts, lac);
paged = 1;
}
if (!paged) {
- LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: BTS with LAC %d not found\n",
- mi_string, lac);
+ LOG_PAGING(params, DMSC, LOGL_NOTICE, "BTS with LAC %u not found\n", lac);
}
}
}
@@ -269,15 +265,19 @@ static int bssmap_handle_paging(struct bsc_msc_data *msc,
struct msgb *msg, unsigned int payload_length)
{
struct tlv_parsed tp;
- char mi_string[GSM48_MI_SIZE];
- uint32_t tmsi = GSM_RESERVED_TMSI;
uint8_t data_length;
int remain;
const uint8_t *data;
- uint8_t chan_needed = RSL_CHANNEED_ANY;
- struct gsm0808_cell_id_list2 cil;
+ struct bsc_paging_params paging = {
+ .reason = BSC_PAGING_FROM_CN,
+ .msc = msc,
+ .tmsi = GSM_RESERVED_TMSI,
+ };
- tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) {
+ LOGP(DMSC, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
remain = payload_length - 1;
if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) {
@@ -296,7 +296,7 @@ static int bssmap_handle_paging(struct bsc_msc_data *msc,
if (TLVP_PRESENT(&tp, GSM0808_IE_TMSI) &&
TLVP_LEN(&tp, GSM0808_IE_TMSI) == 4) {
- tmsi = ntohl(tlvp_val32_unal(&tp, GSM0808_IE_TMSI));
+ paging.tmsi = ntohl(tlvp_val32_unal(&tp, GSM0808_IE_TMSI));
remain -= TLVP_LEN(&tp, GSM0808_IE_TMSI);
}
@@ -308,8 +308,11 @@ static int bssmap_handle_paging(struct bsc_msc_data *msc,
/*
* parse the IMSI
*/
- gsm48_mi_to_string(mi_string, sizeof(mi_string),
- TLVP_VAL(&tp, GSM0808_IE_IMSI), TLVP_LEN(&tp, GSM0808_IE_IMSI));
+ if (osmo_mobile_identity_decode(&paging.imsi, TLVP_VAL(&tp, GSM0808_IE_IMSI), TLVP_LEN(&tp, GSM0808_IE_IMSI), false)
+ || paging.imsi.type != GSM_MI_TYPE_IMSI) {
+ LOGP(DMSC, LOGL_ERROR, "Paging: could not parse IMSI\n");
+ return -1;
+ }
/*
* There are various cell identifier list types defined at 3GPP TS ยง 08.08, we don't support all
@@ -318,106 +321,137 @@ static int bssmap_handle_paging(struct bsc_msc_data *msc,
*/
data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST);
- if (gsm0808_dec_cell_id_list2(&cil, data, data_length) < 0) {
- LOGP(DMSC, LOGL_ERROR, "Paging IMSI %s: Could not parse Cell Identifier List\n",
- mi_string);
+ if (gsm0808_dec_cell_id_list2(&paging.cil, data, data_length) < 0) {
+ LOG_PAGING(&paging, DMSC, LOGL_ERROR, "Could not parse Cell Identifier List\n");
return -1;
}
+ if (paging.cil.id_discr == CELL_IDENT_BSS && data_length != 1) {
+ LOG_PAGING(&paging, DMSC, LOGL_ERROR, "Cell Identifier List for BSS (0x%x)"
+ " has invalid length: %u, paging entire BSS anyway (%s)\n",
+ CELL_IDENT_BSS, data_length, osmo_hexdump(data, data_length));
+ }
remain = 0;
if (TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_NEEDED) && TLVP_LEN(&tp, GSM0808_IE_CHANNEL_NEEDED) == 1)
- chan_needed = TLVP_VAL(&tp, GSM0808_IE_CHANNEL_NEEDED)[0] & 0x03;
+ paging.chan_needed = TLVP_VAL(&tp, GSM0808_IE_CHANNEL_NEEDED)[0] & 0x03;
if (TLVP_PRESENT(&tp, GSM0808_IE_EMLPP_PRIORITY)) {
- LOGP(DMSC, LOGL_ERROR, "eMLPP is not handled\n");
+ LOG_PAGING(&paging, DMSC, LOGL_ERROR, "eMLPP IE present, but eMLPP is not handled\n");
}
- rate_ctr_inc(&msc->network->bsc_ctrs->ctr[BSC_CTR_PAGING_ATTEMPTED]);
+ return bsc_paging_start(&paging);
+}
+
+int bsc_paging_start(struct bsc_paging_params *params)
+{
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_PAGING_ATTEMPTED));
- switch (cil.id_discr) {
+ if (!params->bsub) {
+ params->bsub = bsc_subscr_find_or_create_by_imsi(bsc_gsmnet->bsc_subscribers, params->imsi.imsi,
+ BSUB_USE_PAGING_START);
+ if (!params->bsub) {
+ LOG_PAGING(params, DMSC, LOGL_ERROR, "Paging request failed: Could not allocate subscriber\n");
+ return -EINVAL;
+ }
+ }
+ if (params->tmsi != GSM_RESERVED_TMSI) {
+ if (bsc_subscr_set_tmsi(params->bsub, params->tmsi) < 0) {
+ LOG_PAGING(params, DMSC, LOGL_ERROR, "Paging request failed: Could not set TMSI on subscriber\n");
+ return -EINVAL;
+ }
+ }
+ log_set_context(LOG_CTX_BSC_SUBSCR, params->bsub);
+
+ switch (params->cil.id_discr) {
case CELL_IDENT_NO_CELL:
- page_all_bts(msc, tmsi, mi_string, chan_needed);
+ page_all_bts(params);
break;
case CELL_IDENT_WHOLE_GLOBAL:
- page_cgi(msc, &cil, tmsi, mi_string, chan_needed);
+ page_cgi(params);
break;
case CELL_IDENT_LAC_AND_CI:
- page_lac_and_ci(msc, &cil, tmsi, mi_string, chan_needed);
+ page_lac_and_ci(params);
break;
case CELL_IDENT_CI:
- page_ci(msc, &cil, tmsi, mi_string, chan_needed);
+ page_ci(params);
break;
case CELL_IDENT_LAI_AND_LAC:
- page_lai_and_lac(msc, &cil, tmsi, mi_string, chan_needed);
+ page_lai_and_lac(params);
break;
case CELL_IDENT_LAC:
- page_lac(msc, &cil, tmsi, mi_string, chan_needed);
+ page_lac(params);
break;
case CELL_IDENT_BSS:
- if (data_length != 1) {
- LOGP(DMSC, LOGL_ERROR, "Paging IMSI %s: Cell Identifier List for BSS (0x%x)"
- " has invalid length: %u, paging entire BSS anyway (%s)\n",
- mi_string, CELL_IDENT_BSS, data_length, osmo_hexdump(data, data_length));
- }
- page_all_bts(msc, tmsi, mi_string, chan_needed);
+ page_all_bts(params);
break;
default:
- LOGP(DMSC, LOGL_NOTICE, "Paging IMSI %s: unimplemented Cell Identifier List (0x%x),"
- " paging entire BSS instead (%s)\n",
- mi_string, cil.id_discr, osmo_hexdump(data, data_length));
- page_all_bts(msc, tmsi, mi_string, chan_needed);
+ LOG_PAGING(params, DMSC, LOGL_NOTICE,
+ "unimplemented Cell Identifier List type (0x%x), paging entire BSS instead\n",
+ params->cil.id_discr);
+ page_all_bts(params);
break;
}
+ bsc_subscr_put(params->bsub, BSUB_USE_PAGING_START);
+ log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
return 0;
}
-/* select the best cipher permitted by the intersection of both masks */
-static int select_best_cipher(uint8_t msc_mask, uint8_t bsc_mask)
+/* Select the best cipher permitted by the intersection of both masks. Return as the n in A5/n, or -1 if the
+ * intersection is empty. */
+int select_best_cipher(uint8_t msc_mask, uint8_t bsc_mask)
{
+ /* A5/7 ... A5/3: We assume higher is better,
+ * but: A5/1 is better than A5/2, which is better than A5/0 */
+ const uint8_t codec_by_strength[8] = { 7, 6, 5, 4, 3, 1, 2, 0 };
uint8_t intersection = msc_mask & bsc_mask;
int i;
- for (i = 7; i >= 0; i--) {
- if (intersection & (1 << i))
- return i;
+ for (i = 0; i < ARRAY_SIZE(codec_by_strength); i++) {
+ uint8_t codec = codec_by_strength[i];
+ if (intersection & (1 << codec))
+ return codec;
}
return -1;
}
-/*! We received a GSM 08.08 CIPHER MODE from the MSC */
-static int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher,
- const uint8_t *key, int len, int include_imeisv)
+static int bssmap_handle_clear_cmd(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
{
- if (cipher > 0 && key == NULL) {
- LOGP(DRSL, LOGL_ERROR, "%s: Need to have an encryption key.\n",
- bsc_subscr_name(conn->bsub));
- return -1;
- }
+ struct tlv_parsed tp;
+ enum gsm0808_cause cause_0808;
- if (len > MAX_A5_KEY_LEN) {
- LOGP(DRSL, LOGL_ERROR, "%s: The key is too long: %d\n",
- bsc_subscr_name(conn->bsub), len);
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
return -1;
}
- LOGP(DRSL, LOGL_DEBUG, "(subscr %s) Cipher Mode: cipher=%d key=%s include_imeisv=%d\n",
- bsc_subscr_name(conn->bsub), cipher, osmo_hexdump_nospc(key, len), include_imeisv);
+ cause_0808 = gsm0808_get_cause(&tp);
+ if (cause_0808 < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Clear Command: Mandatory Cause IE not present.\n");
+ /* Clear anyway, but without a proper cause. */
+ cause_0808 = GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE;
+ }
- conn->lchan->encr.alg_id = RSL_ENC_ALG_A5(cipher);
- if (key) {
- conn->lchan->encr.key_len = len;
- memcpy(conn->lchan->encr.key, key, len);
+ if (TLVP_PRESENT(&tp, GSM0808_IE_CSFB_INDICATION) &&
+ !conn->fast_return.last_eutran_plmn_valid) {
+ LOGPFSML(conn->fi, LOGL_NOTICE,
+ "Clear Command: CSFB Indication present, "
+ "but subscriber has no Last Used E-UTRAN PLMN Id! "
+ "This probably means MSC doesn't support proper return "
+ "to the last used PLMN after CS fallback.\n");
}
- return gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv);
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CLEAR_CMD, &cause_0808);
+
+ return 0;
}
/*
@@ -432,7 +466,6 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn,
struct msgb *msg, unsigned int payload_length)
{
uint16_t len;
- struct gsm_network *network = NULL;
const uint8_t *data;
struct tlv_parsed tp;
struct msgb *resp;
@@ -442,8 +475,9 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn,
uint16_t enc_key_len;
uint8_t enc_bits_msc;
int chosen_cipher;
+ const struct tlv_p_entry *ie_kc128;
- if (!conn) {
+ if (!conn || !conn->lchan) {
LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n");
return -1;
}
@@ -456,7 +490,11 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn,
conn->ciphering_handled = 1;
- tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0);
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
+
if (!TLVP_PRESENT(&tp, GSM0808_IE_ENCRYPTION_INFORMATION)) {
LOGP(DMSC, LOGL_ERROR, "IE Encryption Information missing.\n");
reject_cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
@@ -476,7 +514,6 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn,
goto reject;
}
- network = conn_get_bts(conn)->network;
data = TLVP_VAL(&tp, GSM0808_IE_ENCRYPTION_INFORMATION);
enc_bits_msc = data[0];
enc_key = &data[1];
@@ -491,24 +528,59 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn,
* a5_encryption == 2 --> 0x04 ... */
enc_bits_msc = data[0];
- /* The bit-mask of permitted ciphers from the MSC (sent in ASSIGNMENT COMMAND) is intersected
- * with the vty-configured mask a the BSC. Finally, the best (highest) possible cipher is
- * chosen. */
- chosen_cipher = select_best_cipher(enc_bits_msc, network->a5_encryption_mask);
+ chosen_cipher = select_best_cipher(enc_bits_msc, bsc_gsmnet->a5_encryption_mask);
if (chosen_cipher < 0) {
LOGP(DMSC, LOGL_ERROR, "Reject: no overlapping A5 ciphers between BSC (0x%02x) "
- "and MSC (0x%02x)\n", network->a5_encryption_mask, enc_bits_msc);
+ "and MSC (0x%02x)\n", bsc_gsmnet->a5_encryption_mask, enc_bits_msc);
reject_cause = GSM0808_CAUSE_CIPHERING_ALGORITHM_NOT_SUPPORTED;
goto reject;
}
- /* To complete the confusion, gsm0808_cipher_mode again expects the encryption as a number
- * from 0 to 7. */
- if (gsm0808_cipher_mode(conn, chosen_cipher, enc_key, enc_key_len,
- include_imeisv)) {
+ if (chosen_cipher > 0 && !enc_key_len) {
+ LOGP(DRSL, LOGL_ERROR, "%s: Need to have an encryption key.\n",
+ bsc_subscr_name(conn->bsub));
+ reject_cause = GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC;
+ goto reject;
+ }
+
+ if (enc_key_len > MAX_A5_KEY_LEN) {
+ LOGP(DRSL, LOGL_ERROR, "%s: The key is too long: %d\n",
+ bsc_subscr_name(conn->bsub), len);
reject_cause = GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC;
goto reject;
}
+
+ conn->lchan->encr.alg_a5_n = chosen_cipher;
+ if (enc_key_len) {
+ conn->lchan->encr.key_len = enc_key_len;
+ memcpy(conn->lchan->encr.key, enc_key, enc_key_len);
+ }
+ if ((ie_kc128 = TLVP_GET(&tp, GSM0808_IE_KC_128))) {
+ if (ie_kc128->len != sizeof(conn->lchan->encr.kc128)) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Kc128 IE has wrong length: %u (expect %zu)\n",
+ ie_kc128->len, sizeof(conn->lchan->encr.kc128));
+ reject_cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ goto reject;
+ }
+ memcpy(conn->lchan->encr.kc128, ie_kc128->val, sizeof(conn->lchan->encr.kc128));
+ conn->lchan->encr.kc128_present = true;
+ }
+
+ if (chosen_cipher == 4 && !conn->lchan->encr.kc128_present) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "A5/4 encryption selected, but no Kc128\n");
+ reject_cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ goto reject;
+ }
+
+ LOGP(DRSL, LOGL_DEBUG, "(subscr %s) Cipher Mode: cipher=%d key=%s kc128=%s include_imeisv=%d\n",
+ bsc_subscr_name(conn->bsub), chosen_cipher, osmo_hexdump_nospc(enc_key, enc_key_len),
+ ie_kc128? osmo_hexdump_nospc_c(OTC_SELECT, ie_kc128->val, ie_kc128->len) : "-",
+ include_imeisv);
+
+ if (gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv) < 0) {
+ reject_cause = GSM0808_CAUSE_RADIO_INTERFACE_FAILURE;
+ goto reject;
+ }
return 0;
reject:
@@ -518,6 +590,7 @@ reject:
return -1;
}
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_CIPHER_REJECT));
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
return -1;
}
@@ -526,20 +599,24 @@ reject:
static void bssmap_handle_ass_req_lcls(struct gsm_subscriber_connection *conn,
const struct tlv_parsed *tp)
{
- const uint8_t *config, *control, *gcr, gcr_len = TLVP_LEN(tp, GSM0808_IE_GLOBAL_CALL_REF);
-
- if (gcr_len > sizeof(conn->lcls.global_call_ref))
- LOGPFSML(conn->fi, LOGL_ERROR, "Global Call Ref IE of %u bytes is too long\n",
- gcr_len);
- else {
- gcr = TLVP_VAL_MINLEN(tp, GSM0808_IE_GLOBAL_CALL_REF, 13);
- if (gcr) {
+ const uint8_t *config, *control, *gcr;
+ uint8_t gcr_len;
+
+ /* TS 48.008 sec 3.2.2.115 Global Call Reference */
+ if (TLVP_PRESENT(tp, GSM0808_IE_GLOBAL_CALL_REF)) {
+ gcr = TLVP_VAL(tp, GSM0808_IE_GLOBAL_CALL_REF);
+ gcr_len = TLVP_LEN(tp, GSM0808_IE_GLOBAL_CALL_REF);
+ if (gcr_len > sizeof(conn->lcls.global_call_ref)) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Global Call Ref IE of %u bytes is too long: %s\n",
+ gcr_len, osmo_hexdump_nospc(gcr, gcr_len));
+ } else if (gcr_len < 13) { /* FIXME: document this magic value 13 */
+ LOGPFSML(conn->fi, LOGL_ERROR, "Global Call Ref IE of %u bytes is too short: %s\n",
+ gcr_len, osmo_hexdump_nospc(gcr, gcr_len));
+ } else {
LOGPFSM(conn->fi, "Setting GCR to %s\n", osmo_hexdump_nospc(gcr, gcr_len));
memcpy(&conn->lcls.global_call_ref, gcr, gcr_len);
conn->lcls.global_call_ref_len = gcr_len;
- } else
- LOGPFSML(conn->fi, LOGL_ERROR, "Global Call Ref IE of %u bytes is too short\n",
- gcr_len);
+ }
}
config = TLVP_VAL_MINLEN(tp, GSM0808_IE_LCLS_CONFIG, 1);
@@ -565,16 +642,14 @@ static int bssmap_handle_lcls_connect_ctrl(struct gsm_subscriber_connection *con
struct msgb *resp;
struct tlv_parsed tp;
const uint8_t *config, *control;
- int rc;
OSMO_ASSERT(conn);
- rc = tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
- if (rc < 0) {
- LOGPFSML(conn->fi, LOGL_ERROR, "Error parsing TLVs of LCLS CONNT CTRL: %s\n",
- msgb_hexdump(msg));
- return rc;
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
}
+
config = TLVP_VAL_MINLEN(&tp, GSM0808_IE_LCLS_CONFIG, 1);
control = TLVP_VAL_MINLEN(&tp, GSM0808_IE_LCLS_CONN_STATUS_CTRL, 1);
@@ -594,11 +669,415 @@ static int bssmap_handle_lcls_connect_ctrl(struct gsm_subscriber_connection *con
LOGPFSM(conn->fi, "Tx LCLS CONNECT CTRL ACK (%s)\n",
gsm0808_lcls_status_name(lcls_get_status(conn)));
resp = gsm0808_create_lcls_conn_ctrl_ack(lcls_get_status(conn));
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_LCLS_CONNECT_CTRL_ACK));
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
return 0;
}
+/* Select a preferred and an alternative data rate depending on the available
+ * capabilities. This decision does not include the actual channel load yet,
+ * this is also the reason why the result is a preferred and an alternate
+ * setting. The final decision is made in assignment_fsm.c when the actual
+ * lchan is requested. The preferred lchan will be requested first. If we
+ * find an alternate setting here, this one will be tried secondly if our
+ * primary choice fails. */
+static int select_data_rates(struct assignment_request *req, struct gsm0808_channel_type *ct,
+ struct gsm_subscriber_connection *conn)
+{
+ int rc, i, nc = 0;
+
+ switch (ct->ch_rate_type) {
+ case GSM0808_DATA_FULL_BM:
+ rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, true);
+ nc += (rc == 0) ? 1 : 0;
+ break;
+ case GSM0808_DATA_HALF_LM:
+ rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, false);
+ nc += (rc == 0) ? 1 : 0;
+ break;
+ case GSM0808_DATA_FULL_PREF_NO_CHANGE:
+ case GSM0808_DATA_FULL_PREF:
+ rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, true);
+ nc += (rc == 0) ? 1 : 0;
+ rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, false);
+ nc += (rc == 0) ? 1 : 0;
+ break;
+ case GSM0808_DATA_HALF_PREF_NO_CHANGE:
+ case GSM0808_DATA_HALF_PREF:
+ rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, false);
+ nc += (rc == 0) ? 1 : 0;
+ rc = match_data_rate_pref(&req->ch_mode_rate_list[nc], ct, true);
+ nc += (rc == 0) ? 1 : 0;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ if (!nc) {
+ LOGP(DMSC, LOGL_ERROR, "No supported data rate found for channel_type ="
+ " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[%s] }\n",
+ ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len));
+ return -EINVAL;
+ }
+
+ for (i = 0; i < nc; i++) {
+ DEBUGP(DMSC, "Found matching data rate (pref=%d): %s %s for channel_type ="
+ " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n",
+ i,
+ req->ch_mode_rate_list[i].chan_rate == CH_RATE_FULL ? "full rate" : "half rate",
+ get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_list[i].chan_mode),
+ ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len));
+ }
+
+ req->n_ch_mode_rate = nc;
+
+ return 0;
+}
+
+/* Select a preferred and an alternative codec rate depending on the available
+ * capabilities. This decision does not include the actual lchan availability yet,
+ * this is also the reason why the result is a preferred and an alternate
+ * setting. The final decision is made in assignment_fsm.c when the actual
+ * lchan is requested. The preferred lchan will be requested first. If we
+ * find an alternate setting here, this one will be tried secondly if our
+ * primary choice fails. */
+static int select_codecs(struct assignment_request *req, const struct gsm0808_channel_type *ct,
+ struct gsm_subscriber_connection *conn, struct gsm_bts *bts)
+{
+ int rc, i, nc = 0;
+ struct bsc_msc_data *msc;
+
+ if (!bts) {
+ LOGP(DMSC, LOGL_ERROR, "No lchan, cannot select codecs\n");
+ return -EINVAL;
+ }
+
+ msc = conn->sccp.msc;
+
+ switch (ct->ch_rate_type) {
+ case GSM0808_SPEECH_FULL_BM:
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
+ RATE_PREF_FR);
+ nc += (rc == 0) ? 1 : 0;
+ break;
+ case GSM0808_SPEECH_HALF_LM:
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
+ RATE_PREF_HR);
+ nc += (rc == 0) ? 1 : 0;
+ break;
+ case GSM0808_SPEECH_PERM:
+ case GSM0808_SPEECH_PERM_NO_CHANGE:
+ case GSM0808_SPEECH_FULL_PREF_NO_CHANGE:
+ case GSM0808_SPEECH_FULL_PREF:
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
+ RATE_PREF_FR);
+ nc += (rc == 0) ? 1 : 0;
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
+ RATE_PREF_HR);
+ nc += (rc == 0) ? 1 : 0;
+ break;
+ case GSM0808_SPEECH_HALF_PREF_NO_CHANGE:
+ case GSM0808_SPEECH_HALF_PREF:
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
+ RATE_PREF_HR);
+ nc += (rc == 0) ? 1 : 0;
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
+ RATE_PREF_FR);
+ nc += (rc == 0) ? 1 : 0;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ if (!nc) {
+ LOGP(DMSC, LOGL_ERROR, "No supported audio type found for channel_type ="
+ " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[%s] }\n",
+ ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len));
+ /* TODO: actually output codec names, e.g. implement
+ * gsm0808_permitted_speech_names[] and iterate perm_spch. */
+ return -EINVAL;
+ }
+
+ for (i = 0; i < nc; i++ ) {
+ DEBUGP(DMSC, "Found matching audio type (pref=%d): %s %s for channel_type ="
+ " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n",
+ i,
+ req->ch_mode_rate_list[i].chan_rate == CH_RATE_FULL ? "full rate" : "half rate",
+ get_value_string(gsm48_chan_mode_names, req->ch_mode_rate_list[i].chan_mode),
+ ct->ch_indctr, ct->ch_rate_type, osmo_hexdump(ct->perm_spch, ct->perm_spch_len));
+ }
+
+ req->n_ch_mode_rate = nc;
+
+ return 0;
+}
+
+static int select_sign_chan(struct assignment_request *req, struct gsm0808_channel_type *ct)
+{
+ int i, nc = 0;
+
+ switch (ct->ch_rate_type) {
+ case GSM0808_SIGN_ANY:
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_SDCCH;
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF;
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL;
+ break;
+ case GSM0808_SIGN_SDCCH:
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_SDCCH;
+ break;
+ case GSM0808_SIGN_SDCCH_FULL_BM:
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_SDCCH;
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL;
+ break;
+ case GSM0808_SIGN_SDCCH_HALF_LM:
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_SDCCH;
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF;
+ break;
+ case GSM0808_SIGN_FULL_BM:
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL;
+ break;
+ case GSM0808_SIGN_HALF_LM:
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF;
+ break;
+ case GSM0808_SIGN_FULL_PREF:
+ case GSM0808_SIGN_FULL_PREF_NO_CHANGE:
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL;
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF;
+ break;
+ case GSM0808_SIGN_HALF_PREF:
+ case GSM0808_SIGN_HALF_PREF_NO_CHANGE:
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_HALF;
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL;
+ break;
+ default:
+ break;
+ }
+
+ for (i = 0; i < nc; i++)
+ req->ch_mode_rate_list[i].chan_mode = GSM48_CMODE_SIGN;
+
+ req->n_ch_mode_rate = nc;
+
+ return nc > 0 ? 0 : -EINVAL;
+}
+
+static int bssmap_handle_ass_req_tp_cic(struct tlv_parsed *tp, bool aoip, uint16_t *cic, uint8_t *cause)
+{
+ if (TLVP_PRESENT(tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
+ /* CIC is permitted in both AoIP and SCCPlite */
+ *cic = osmo_load16be(TLVP_VAL(tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
+ return 0;
+ }
+
+ if (!aoip) {
+ /* no CIC but SCCPlite: illegal */
+ LOGP(DMSC, LOGL_ERROR, "SCCPlite MSC, but no CIC in ASSIGN REQ?\n");
+ *cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int bssmap_handle_ass_req_tp_rtp_addr(struct tlv_parsed *tp, bool aoip, char *msc_rtp_addr,
+ size_t msc_rtp_addr_len, uint16_t *msc_rtp_port, uint8_t *cause)
+{
+ struct sockaddr_storage rtp_addr;
+ int rc;
+ unsigned int rc2;
+
+ if (TLVP_PRESENT(tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
+ if (!aoip) {
+ /* SCCPlite and AoIP transport address: illegal */
+ LOGP(DMSC, LOGL_ERROR, "AoIP Transport address over IPA ?!?\n");
+ *cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ return -1;
+ }
+ /* Decode AoIP transport address element */
+ rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr,
+ TLVP_VAL(tp, GSM0808_IE_AOIP_TRASP_ADDR),
+ TLVP_LEN(tp, GSM0808_IE_AOIP_TRASP_ADDR));
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to decode AoIP transport address.\n");
+ *cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ return -1;
+ }
+
+ rc2 = osmo_sockaddr_to_str_and_uint(msc_rtp_addr, msc_rtp_addr_len, msc_rtp_port,
+ (const struct sockaddr *)&rtp_addr);
+ if (!rc2 || rc >= msc_rtp_addr_len) {
+ LOGP(DMSC, LOGL_ERROR, "Assignment request: RTP address is too long\n");
+ *cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
+ return -1;
+ }
+ return 0;
+ }
+
+ if (aoip) {
+ /* no AoIP transport level address but AoIP transport: illegal */
+ LOGP(DMSC, LOGL_ERROR, "AoIP transport address missing in ASSIGN REQ, "
+ "audio would not work; rejecting\n");
+ *cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int bssmap_handle_ass_req_tp_osmux(struct gsm_subscriber_connection *conn, struct tlv_parsed *tp,
+ bool *use_osmux, uint8_t *osmux_cid, uint8_t *cause)
+{
+ int rc;
+
+ if (TLVP_PRESENT(tp, GSM0808_IE_OSMO_OSMUX_CID)) {
+ if (conn->sccp.msc->use_osmux == OSMUX_USAGE_OFF) {
+ LOGP(DMSC, LOGL_ERROR, "MSC using Osmux but we have it disabled.\n");
+ *cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ return -1;
+ }
+ *use_osmux = true;
+ rc = gsm0808_dec_osmux_cid(osmux_cid,
+ TLVP_VAL(tp, GSM0808_IE_OSMO_OSMUX_CID),
+ TLVP_LEN(tp, GSM0808_IE_OSMO_OSMUX_CID));
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to decode Osmux CID.\n");
+ *cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ return -1;
+ }
+ return 0;
+ }
+
+ if (conn->sccp.msc->use_osmux == OSMUX_USAGE_ONLY) {
+ LOGP(DMSC, LOGL_ERROR, "MSC not using Osmux but we are forced to use it.\n");
+ *cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ return -1;
+ }
+
+ if (conn->sccp.msc->use_osmux == OSMUX_USAGE_ON)
+ LOGP(DMSC, LOGL_NOTICE, "MSC not using Osmux but we have Osmux enabled.\n");
+
+ return 0;
+}
+
+static int bssmap_handle_ass_req_tp_codec_list(struct gsm_subscriber_connection *conn, struct tlv_parsed *tp, bool aoip,
+ uint8_t *cause)
+{
+ int rc;
+
+ /* Decode speech codec list. First set len = 0. */
+ conn->codec_list = (struct gsm0808_speech_codec_list){};
+ /* Check for speech codec list element */
+ if (TLVP_PRESENT(tp, GSM0808_IE_SPEECH_CODEC_LIST)) {
+ /* Decode Speech Codec list */
+ rc = gsm0808_dec_speech_codec_list(&conn->codec_list,
+ TLVP_VAL(tp, GSM0808_IE_SPEECH_CODEC_LIST),
+ TLVP_LEN(tp, GSM0808_IE_SPEECH_CODEC_LIST));
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to decode speech codec list\n");
+ *cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ return -1;
+ }
+ }
+
+ if (aoip && !conn->codec_list.len) {
+ LOGP(DMSC, LOGL_ERROR, "%s: AoIP Assignment Request:"
+ " Missing or empty Speech Codec List IE\n", bsc_subscr_name(conn->bsub));
+ *cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int bssmap_handle_ass_req_ct_data(struct gsm_subscriber_connection *conn, struct tlv_parsed *tp,
+ struct gsm0808_channel_type *ct, struct assignment_request *req,
+ uint8_t *cause)
+{
+ bool aoip = gscon_is_aoip(conn);
+ int rc;
+
+ *req = (struct assignment_request){
+ .assign_for = ASSIGN_FOR_BSSMAP_REQ,
+ .aoip = aoip,
+ };
+
+ if (bssmap_handle_ass_req_tp_cic(tp, aoip, &req->msc_assigned_cic, cause) < 0)
+ return -1;
+
+ if (bssmap_handle_ass_req_tp_rtp_addr(tp, aoip, req->msc_rtp_addr, sizeof(req->msc_rtp_addr), &req->msc_rtp_port, cause) < 0)
+ return -1;
+
+ /* According to 3GPP TS 48.008 ยง 3.2.1.1 note 13, the codec list IE
+ * shall be included for aoip unless channel type is signalling. */
+ if (bssmap_handle_ass_req_tp_codec_list(conn, tp, aoip, cause) < 0)
+ return -1;
+
+ rc = select_data_rates(req, ct, conn);
+ if (rc < 0) {
+ *cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
+ return -1;
+ }
+
+ return 0;
+}
+
+int bssmap_handle_ass_req_ct_speech(struct gsm_subscriber_connection *conn, struct gsm_bts *bts,
+ struct tlv_parsed *tp, struct gsm0808_channel_type *ct,
+ struct assignment_request *req, uint8_t *cause)
+{
+ bool aoip = gscon_is_aoip(conn);
+ int rc;
+
+ *req = (struct assignment_request){
+ .assign_for = ASSIGN_FOR_BSSMAP_REQ,
+ .aoip = aoip,
+ };
+
+ if (bssmap_handle_ass_req_tp_cic(tp, aoip, &req->msc_assigned_cic, cause) < 0)
+ return -1;
+
+ if (bssmap_handle_ass_req_tp_rtp_addr(tp, aoip, req->msc_rtp_addr, sizeof(req->msc_rtp_addr), &req->msc_rtp_port, cause) < 0)
+ return -1;
+
+ if (bssmap_handle_ass_req_tp_osmux(conn, tp, &req->use_osmux, &req->osmux_cid, cause) < 0)
+ return -1;
+
+ if (bssmap_handle_ass_req_tp_codec_list(conn, tp, aoip, cause) < 0)
+ return -1;
+
+ /* Match codec information from the assignment command against the
+ * local preferences of the BSC and BTS */
+ rc = select_codecs(req, ct, conn, bts);
+ if (rc < 0) {
+ *cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int bssmap_handle_ass_req_ct_sign(struct gsm_subscriber_connection *conn, struct gsm0808_channel_type *ct,
+ struct assignment_request *req, uint8_t *cause)
+{
+ int rc;
+
+ *req = (struct assignment_request){
+ .assign_for = ASSIGN_FOR_BSSMAP_REQ,
+ .aoip = gscon_is_aoip(conn),
+ };
+
+ rc = select_sign_chan(req, ct);
+ if (rc < 0) {
+ *cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ return rc;
+ }
+
+ return 0;
+}
+
/*
* Handle the assignment request message.
*
@@ -608,15 +1087,9 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn,
struct msgb *msg, unsigned int length)
{
struct msgb *resp;
- struct bsc_msc_data *msc;
struct tlv_parsed tp;
- uint16_t cic = 0;
- enum gsm48_chan_mode chan_mode = GSM48_CMODE_SIGN;
- bool full_rate = false;
- uint16_t s15_s0 = 0;
- bool aoip = false;
- struct sockaddr_storage rtp_addr;
struct gsm0808_channel_type ct;
+ struct gsm0808_group_callref gc;
uint8_t cause;
int rc;
struct assignment_request req = {};
@@ -627,10 +1100,10 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn,
return -1;
}
- msc = conn->sccp.msc;
- aoip = gscon_is_aoip(conn);
-
- tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
/* Check for channel type element, if its missing, immediately reject */
if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)) {
@@ -648,140 +1121,93 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn,
goto reject;
}
- bssmap_handle_ass_req_lcls(conn, &tp);
+ /* Check for assignment to VGCS channel. */
+ if (TLVP_PRESENT(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)) {
+ struct gsm_bts *bts = conn_get_bts(conn);
- /* Currently we only support a limited subset of all
- * possible channel types, such as multi-slot or CSD */
- switch (ct.ch_indctr) {
- case GSM0808_CHAN_DATA:
- LOGP(DMSC, LOGL_ERROR, "Unsupported channel type, currently only speech is supported!\n");
- cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP;
- goto reject;
- case GSM0808_CHAN_SPEECH:
- if (TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) {
- /* CIC is permitted in both AoIP and SCCPlite */
- cic = osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE));
- } else {
- if (!aoip) {
- /* no CIC but SCCPlite: illegal */
- LOGP(DMSC, LOGL_ERROR, "SCCPlite MSC, but no CIC in ASSIGN REQ?\n");
- cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
- goto reject;
- }
+ OSMO_ASSERT(bts);
+ /* Decode Group Call Reference. */
+ rc = gsm0808_dec_group_callref(&gc, TLVP_VAL(&tp, GSM0808_IE_GROUP_CALL_REFERENCE),
+ TLVP_LEN(&tp, GSM0808_IE_GROUP_CALL_REFERENCE));
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to decode Group Call Reference.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
}
- if (TLVP_PRESENT(&tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
- if (!aoip) {
- /* SCCPlite and AoIP transport address: illegal */
- LOGP(DMSC, LOGL_ERROR, "AoIP Transport address over IPA ?!?\n");
- cause = GSM0808_CAUSE_INCORRECT_VALUE;
- goto reject;
- }
- /* Decode AoIP transport address element */
- rc = gsm0808_dec_aoip_trasp_addr(&rtp_addr,
- TLVP_VAL(&tp, GSM0808_IE_AOIP_TRASP_ADDR),
- TLVP_LEN(&tp, GSM0808_IE_AOIP_TRASP_ADDR));
- if (rc < 0) {
- LOGP(DMSC, LOGL_ERROR, "Unable to decode AoIP transport address.\n");
- cause = GSM0808_CAUSE_INCORRECT_VALUE;
- goto reject;
- }
- } else if (aoip) {
- /* no AoIP transport level address but AoIP transport: illegal */
- LOGP(DMSC, LOGL_ERROR, "AoIP transport address missing in ASSIGN REQ, "
- "audio would not work; rejecting\n");
- cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ req.target_lchan = vgcs_vbs_find_lchan(bts, &gc);
+ if (!req.target_lchan) {
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
goto reject;
}
+ req.assign_for = ASSIGN_FOR_BSSMAP_REQ;
+ req.vgcs = true;
- /* Decode speech codec list. First set len = 0. */
- conn->codec_list = (struct gsm0808_speech_codec_list){};
- /* Check for speech codec list element */
- if (TLVP_PRESENT(&tp, GSM0808_IE_SPEECH_CODEC_LIST)) {
- /* Decode Speech Codec list */
- rc = gsm0808_dec_speech_codec_list(&conn->codec_list,
- TLVP_VAL(&tp, GSM0808_IE_SPEECH_CODEC_LIST),
- TLVP_LEN(&tp, GSM0808_IE_SPEECH_CODEC_LIST));
- if (rc < 0) {
- LOGP(DMSC, LOGL_ERROR, "Unable to decode speech codec list\n");
- cause = GSM0808_CAUSE_INCORRECT_VALUE;
- goto reject;
- }
+ /* Copy timing advance. */
+ if (conn->lchan) {
+ req.target_lchan->activate.info.ta_known = conn->lchan->activate.info.ta_known;
+ req.target_lchan->activate.info.ta = conn->lchan->activate.info.ta;
}
- if (aoip && !conn->codec_list.len) {
- LOGP(DMSC, LOGL_ERROR, "%s: AoIP speech mode Assignment Request:"
- " Missing or empty Speech Codec List IE\n", bsc_subscr_name(conn->bsub));
- cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
- goto reject;
- }
+ /* Send reactivation on target lchan to prepare VGCS channel for assignment.
+ * See patent EP 1 858 275 A1. */
+ rsl_tx_chan_activ(req.target_lchan, RSL_ACT_TYPE_REACT | RSL_ACT_INTRA_NORM_ASS, 0);
- /* Match codec information from the assignment command against the
- * local preferences of the BSC and BTS */
- rc = match_codec_pref(&chan_mode, &full_rate, &s15_s0, &ct, &conn->codec_list,
- msc, conn_get_bts(conn));
- if (rc < 0) {
- LOGP(DCHAN, LOGL_ERROR, "No supported audio type found for channel_type ="
- " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }\n",
- ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len));
- /* TODO: actually output codec names, e.g. implement
- * gsm0808_permitted_speech_names[] and iterate perm_spch. */
- cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
- goto reject;
- }
+ return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_ASSIGNMENT_START, &req);
+ }
- DEBUGP(DCHAN, "Found matching audio type: %s %s for channel_type ="
- " { ch_indctr=0x%x, ch_rate_type=0x%x, perm_spch=[ %s] }, s15_s0=0x%04x\n",
- full_rate? "full rate" : "half rate",
- get_value_string(gsm48_chan_mode_names, chan_mode),
- ct.ch_indctr, ct.ch_rate_type, osmo_hexdump(ct.perm_spch, ct.perm_spch_len),
- s15_s0);
- if (log_check_level(DMSC, LOGL_DEBUG)) {
- int i;
- for (i = 0; i < ct.perm_spch_len; i++)
- LOGP(DCHAN, LOGL_DEBUG, "perm_spch[%d] = 0x%02x = %s\n",
- i, ct.perm_spch[i], gsm0808_permitted_speech_name(ct.perm_spch[i]));
- }
+ bssmap_handle_ass_req_lcls(conn, &tp);
- req = (struct assignment_request){
- .aoip = aoip,
- .msc_assigned_cic = cic,
- .chan_mode = chan_mode,
- .full_rate = full_rate,
- .s15_s0 = s15_s0
- };
- if (aoip) {
- unsigned int rc = osmo_sockaddr_to_str_and_uint(req.msc_rtp_addr,
- sizeof(req.msc_rtp_addr),
- &req.msc_rtp_port,
- (const struct sockaddr*)&rtp_addr);
- if (!rc || rc >= sizeof(req.msc_rtp_addr)) {
- LOGP(DMSC, LOGL_ERROR, "Assignment request: RTP address is too long\n");
- cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
- goto reject;
- }
- }
+ /* Currently we only support a limited subset of all
+ * possible channel types, such as multi-slot */
+ switch (ct.ch_indctr) {
+ case GSM0808_CHAN_DATA:
+ if (bssmap_handle_ass_req_ct_data(conn, &tp, &ct, &req, &cause) < 0)
+ goto reject;
+ break;
+ case GSM0808_CHAN_SPEECH:
+ if (bssmap_handle_ass_req_ct_speech(conn, conn_get_bts(conn), &tp, &ct, &req, &cause) < 0)
+ goto reject;
break;
case GSM0808_CHAN_SIGN:
- req = (struct assignment_request){
- .aoip = aoip,
- .chan_mode = chan_mode,
- };
+ if (bssmap_handle_ass_req_ct_sign(conn, &ct, &req, &cause) < 0)
+ goto reject;
break;
default:
cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS;
goto reject;
}
+ req.ch_indctr = ct.ch_indctr;
+
return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_ASSIGNMENT_START, &req);
reject:
resp = gsm0808_create_assignment_failure(cause, NULL);
OSMO_ASSERT(resp);
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_FAILURE));
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
return -1;
}
+/* Handle Handover Request message, part of inter-BSC handover:
+ * The MSC opened a new SCCP connection and is asking this BSS to accept an inter-BSC incoming handover.
+ * If we accept, we'll send a Handover Request Acknowledge.
+ * This function is only called when the Handover Request is *not* included in the initial SCCP N-Connect message, but
+ * follows an "empty" N-Connect in a separate DT1 message.
+ */
+static int bssmap_handle_handover_request(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ if (osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_INITIAL_USER_DATA, msg)) {
+ /* A Handover Request message should come in on a newly opened SCCP conn. Apparently the MSC has sent a
+ * Handover Request on an already busy SCCP conn, and naturally we cannot accept another subscriber
+ * here. This is unlikely to ever happen in practice. Respond in the only possible way: */
+ bsc_tx_bssmap_ho_failure(conn);
+ return -EINVAL;
+ }
+ return 0;
+}
+
/* Handle Handover Command message, part of inter-BSC handover:
* This BSS sent a Handover Required message.
* The MSC contacts the remote BSS and receives from it an RR Handover Command; this BSSMAP Handover
@@ -802,7 +1228,10 @@ static int bssmap_handle_handover_cmd(struct gsm_subscriber_connection *conn,
return -EINVAL;
}
- tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
/* Check for channel type element, if its missing, immediately reject */
if (!TLVP_PRESENT(&tp, GSM0808_IE_LAYER_3_INFORMATION)) {
@@ -827,10 +1256,183 @@ reject:
return -EINVAL;
}
+/* Handle Confusion message, MSC indicating an error to us:
+ *
+ * See 3GPP TS 48.008 ยง3.2.1.45
+ */
+static int bssmap_handle_confusion(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct tlv_parsed tp;
+ int diag_len;
+ enum gsm0808_cause cause;
+ enum gsm0808_cause_class cause_class;
+ struct gsm0808_diagnostics *diag;
+
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
+
+ /* Check for the Cause and Diagnostic mandatory elements */
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CAUSE) || !TLVP_PRESENT(&tp, GSM0808_IE_DIAGNOSTIC)) {
+ LOGPFSML(conn->fi, LOGL_ERROR,
+ "Received BSSMAP Confusion message,"
+ " but either Cause or Diagnostic mandatory IE is not present: %s\n",
+ osmo_hexdump(msg->l4h, length));
+ return -EINVAL;
+ }
+
+ diag_len = TLVP_LEN(&tp, GSM0808_IE_DIAGNOSTIC);
+ if (diag_len < 5) {
+ LOGPFSML(conn->fi, LOGL_ERROR,
+ "Received BSSMAP Confusion message with short Diagnostic length: %d (expected > 5)\n",
+ diag_len);
+ return -EINVAL;
+ }
+
+ cause = gsm0808_get_cause(&tp);
+ cause_class = gsm0808_cause_class(cause);
+ diag = (struct gsm0808_diagnostics *)TLVP_VAL(&tp, GSM0808_IE_DIAGNOSTIC);
+
+ LOGPFSML(conn->fi, LOGL_ERROR,
+ "Received BSSMAP Confusion: class 0x%x (%s), cause 0x%x (%s), "
+ "error octet %d (%s), error bit %d (%s), original message: %s\n",
+ cause_class, gsm0808_cause_class_name(cause_class),
+ cause, gsm0808_cause_name(cause),
+ diag->error_pointer_octet,
+ gsm0808_diagnostics_octet_location_str(diag->error_pointer_octet),
+ diag->error_pointer_bit,
+ gsm0808_diagnostics_bit_location_str(diag->error_pointer_bit),
+ osmo_hexdump(diag->msg, diag_len-2));
+
+ return 0;
+}
+
+/* Common ID; 3GPP TS 48.008 3.2.1.68 */
+static int bssmap_handle_common_id(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct tlv_parsed tp;
+
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
+
+ /* Check for the mandatory elements */
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) {
+ LOGPFSML(conn->fi, LOGL_ERROR,
+ "CommonID: missing mandatory IMSI IE: %s\n",
+ osmo_hexdump(msg->l4h, length));
+ return -EINVAL;
+ }
+
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_COMMON_ID_IND, &tp);
+
+ return 0;
+}
+
+/* Handle (VGCS) UPLINK REQUEST ACKNOWLEDGE:
+ *
+ * See 3GPP TS 48.008 ยง3.2.1.58
+ */
+static int bssmap_handle_uplink_rqst_acknowledge(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct tlv_parsed tp;
+
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
+
+ if (conn->vgcs_call.fi)
+ osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_ACK, NULL);
+ return 0;
+}
+
+/* Handle (VGCS) UPLINK REJECT COMMAND message.
+ *
+ * See 3GPP TS 48.008 ยง3.2.1.61
+ */
+static int bssmap_handle_uplink_reject_cmd(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct tlv_parsed tp;
+
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
+
+ if (conn->vgcs_call.fi)
+ osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_REJECT, NULL);
+ return 0;
+}
+
+/* Handle (VGCS) UPLINK RELEASE COMMAND message, MSC indicating an error to us:
+ *
+ * See 3GPP TS 48.008 ยง3.2.1.62
+ */
+static int bssmap_handle_uplink_release_cmd(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct tlv_parsed tp;
+
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
+
+ if (conn->vgcs_call.fi)
+ osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_RELEASE, NULL);
+ return 0;
+}
+
+/* Handle (VGCS) UPLINK SEIZED COMMAND message:
+ *
+ * See 3GPP TS 48.008 ยง3.2.1.63
+ */
+static int bssmap_handle_uplink_seized_cmd(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct tlv_parsed tp;
+
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
+
+ if (conn->vgcs_call.fi)
+ osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_SEIZE, NULL);
+ return 0;
+}
+
+/* Handle VGCS/VBS ADDITIONAL INFO message:
+ *
+ * See 3GPP TS 48.008 ยง3.2.1.78
+ */
+static int bssmap_handle_vgcs_addl_info(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, unsigned int length)
+{
+ struct tlv_parsed tp;
+
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ return -1;
+ }
+
+ LOGPFSML(conn->fi, LOGL_ERROR, "VGCS ADDITIONAL INFO is not supported.\n");
+
+ return 0;
+}
+
static int bssmap_rcvmsg_udt(struct bsc_msc_data *msc,
struct msgb *msg, unsigned int length)
{
int ret = 0;
+ struct rate_ctr *ctrs = msc->msc_ctrs->ctr;
if (length < 1) {
LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
@@ -842,15 +1444,19 @@ static int bssmap_rcvmsg_udt(struct bsc_msc_data *msc,
switch (msg->l4h[0]) {
case BSS_MAP_MSG_RESET_ACKNOWLEDGE:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_UDT_RESET_ACKNOWLEDGE]);
ret = bssmap_handle_reset_ack(msc, msg, length);
break;
case BSS_MAP_MSG_RESET:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_UDT_RESET]);
ret = bssmap_handle_reset(msc, msg, length);
break;
case BSS_MAP_MSG_PAGING:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_UDT_PAGING]);
ret = bssmap_handle_paging(msc, msg, length);
break;
default:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_UDT_UNKNOWN]);
LOGP(DMSC, LOGL_NOTICE, "Received unimplemented BSSMAP UDT %s\n",
gsm0808_bssmap_name(msg->l4h[0]));
break;
@@ -863,6 +1469,7 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn,
struct msgb *msg, unsigned int length)
{
int ret = 0;
+ struct rate_ctr *ctrs = conn->sccp.msc->msc_ctrs->ctr;
if (length < 1) {
LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length);
@@ -874,24 +1481,78 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn,
switch (msg->l4h[0]) {
case BSS_MAP_MSG_CLEAR_CMD:
- osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CLEAR_CMD, msg);
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_CLEAR_CMD]);
+ ret = bssmap_handle_clear_cmd(conn, msg, length);
break;
case BSS_MAP_MSG_CIPHER_MODE_CMD:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_CIPHER_MODE_CMD]);
ret = bssmap_handle_cipher_mode(conn, msg, length);
break;
- case BSS_MAP_MSG_ASSIGMENT_RQST:
+ case BSS_MAP_MSG_ASSIGNMENT_RQST:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_ASSIGNMENT_RQST]);
ret = bssmap_handle_assignm_req(conn, msg, length);
break;
case BSS_MAP_MSG_LCLS_CONNECT_CTRL:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_LCLS_CONNECT_CTRL]);
ret = bssmap_handle_lcls_connect_ctrl(conn, msg, length);
break;
+ case BSS_MAP_MSG_HANDOVER_RQST:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_HANDOVER_RQST]);
+ ret = bssmap_handle_handover_request(conn, msg);
+ break;
case BSS_MAP_MSG_HANDOVER_CMD:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_HANDOVER_CMD]);
ret = bssmap_handle_handover_cmd(conn, msg, length);
break;
case BSS_MAP_MSG_CLASSMARK_RQST:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_CLASSMARK_RQST]);
ret = gsm48_send_rr_classmark_enquiry(conn->lchan);
break;
+ case BSS_MAP_MSG_CONFUSION:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_CONFUSION]);
+ ret = bssmap_handle_confusion(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_COMMON_ID:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_COMMON_ID]);
+ ret = bssmap_handle_common_id(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_PERFORM_LOCATION_RQST:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST]);
+ lcs_loc_req_start(conn, msg);
+ ret = 0;
+ break;
+ case BSS_MAP_MSG_PERFORM_LOCATION_ABORT:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_ABORT]);
+ if (conn->lcs.loc_req) {
+ ret = osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_RX_A_PERFORM_LOCATION_ABORT,
+ msg);
+ } else {
+ LOGP(DMSC, LOGL_ERROR, "Rx BSSMAP Perform Location Abort without ongoing Location Request\n");
+ ret = 0;
+ }
+ break;
+ case BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UPLINK_RQST_ACKNOWLEDGE]);
+ ret = bssmap_handle_uplink_rqst_acknowledge(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_UPLINK_REJECT_CMD:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UPLINK_REJECT_CMD]);
+ ret = bssmap_handle_uplink_reject_cmd(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_UPLINK_RELEASE_CMD:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UPLINK_RELEASE_CMD]);
+ ret = bssmap_handle_uplink_release_cmd(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_UPLINK_SEIZED_CMD:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UPLINK_SEIZED_CMD]);
+ ret = bssmap_handle_uplink_seized_cmd(conn, msg, length);
+ break;
+ case BSS_MAP_MSG_VGCS_ADDL_INFO:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_VGCS_ADDL_INFO]);
+ ret = bssmap_handle_vgcs_addl_info(conn, msg, length);
+ break;
default:
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_UNKNOWN]);
LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n",
gsm0808_bssmap_name(msg->l4h[0]));
break;
@@ -900,21 +1561,14 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn,
return ret;
}
-int bsc_send_welcome_ussd(struct gsm_subscriber_connection *conn)
-{
- bsc_send_ussd_notify(conn, 1, conn->sccp.msc->ussd_welcome_txt);
- bsc_send_ussd_release_complete(conn);
-
- return 0;
-}
-
static int dtap_rcvmsg(struct gsm_subscriber_connection *conn,
struct msgb *msg, unsigned int length)
{
struct dtap_header *header;
struct msgb *gsm48;
uint8_t *data;
- int rc, dtap_rc;
+ int dtap_rc;
+ struct rate_ctr *ctrs;
LOGP(DMSC, LOGL_DEBUG, "Rx MSC DTAP: %s\n",
osmo_hexdump(msg->l3h, length));
@@ -924,20 +1578,24 @@ static int dtap_rcvmsg(struct gsm_subscriber_connection *conn,
return -1;
}
+ ctrs = conn->sccp.msc->msc_ctrs->ctr;
header = (struct dtap_header *) msg->l3h;
if (sizeof(*header) >= length) {
- LOGP(DMSC, LOGL_ERROR, "The DTAP header does not fit. Wanted: %zu got: %u\n", sizeof(*header), length);
- LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length));
- return -1;
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_DTAP_ERROR]);
+ LOGP(DMSC, LOGL_ERROR, "The DTAP header does not fit. Wanted: %zu got: %u, hex: %s\n",
+ sizeof(*header), length, osmo_hexdump(msg->l3h, length));
+ return -1;
}
if (header->length > length - sizeof(*header)) {
- LOGP(DMSC, LOGL_ERROR, "The DTAP l4 information does not fit: header: %u length: %u\n", header->length, length);
- LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length));
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_DTAP_ERROR]);
+ LOGP(DMSC, LOGL_ERROR, "The DTAP l4 information does not fit. Wanted: %u got: %zu, hex: %s\n",
+ header->length, length - sizeof(*header), osmo_hexdump(msg->l3h, length));
return -1;
}
- LOGP(DMSC, LOGL_INFO, "Rx MSC DTAP, SAPI: %u CHAN: %u\n", header->link_id & 0x07, header->link_id & 0xC0);
+ rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_DTAP]);
+ LOGP(DMSC, LOGL_INFO, "Rx MSC DTAP, SAPI: %s CHAN: %u\n", gsm0406_dlci_sapi_name(header->dlci_sapi), header->dlci_cc);
/* forward the data */
gsm48 = gsm48_msgb_alloc_name("GSM 04.08 DTAP RCV");
@@ -951,12 +1609,15 @@ static int dtap_rcvmsg(struct gsm_subscriber_connection *conn,
memcpy(data, msg->l3h + sizeof(*header), length - sizeof(*header));
/* pass it to the filter for extra actions */
- rc = bsc_scan_msc_msg(conn, gsm48);
- /* Store link_id in msgb->cb */
- OBSC_LINKID_CB(gsm48) = header->link_id;
- dtap_rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, gsm48);
- if (rc == BSS_SEND_USSD)
- bsc_send_welcome_ussd(conn);
+ bsc_scan_msc_msg(conn, gsm48);
+
+ /* convert DLCI to RSL link ID, store in msg->cb */
+ OBSC_LINKID_CB(gsm48) = DLCI2RSL_LINK_ID(header->link_id);
+
+ if (conn->vgcs_call.fi)
+ dtap_rc = osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_MSC_DTAP, gsm48);
+ else
+ dtap_rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, gsm48);
return dtap_rc;
}
@@ -990,6 +1651,36 @@ int bsc_handle_udt(struct bsc_msc_data *msc,
return 0;
}
+/* Extract and verify the length information from the BSSMAP header. */
+static unsigned int bssmap_msg_len(struct msgb *msg, unsigned int length,
+ const struct gsm_subscriber_connection *conn)
+{
+ unsigned int expected_len;
+ unsigned int calculated_len;
+ struct bssmap_header *bssmap_header;
+
+ bssmap_header = (struct bssmap_header *)msg->l3h;
+
+ calculated_len = length - sizeof(struct bssmap_header);
+ expected_len = bssmap_header->length;
+
+ /* In case of contradictory length information, decide for the
+ * shorter length */
+ if (calculated_len > expected_len) {
+ LOGPFSML(conn->fi, LOGL_NOTICE,
+ "BSSMAP message contains extra data, expected %u bytes, got %u bytes, truncated\n",
+ expected_len, calculated_len);
+ return expected_len;
+ } else if (calculated_len < expected_len) {
+ LOGPFSML(conn->fi, LOGL_NOTICE,
+ "Short BSSMAP message, expected %u bytes, got %u bytes\n",
+ expected_len, calculated_len);
+ return calculated_len;
+ }
+
+ return expected_len;
+}
+
int bsc_handle_dt(struct gsm_subscriber_connection *conn,
struct msgb *msg, unsigned int len)
{
@@ -1002,7 +1693,7 @@ int bsc_handle_dt(struct gsm_subscriber_connection *conn,
switch (msg->l3h[0]) {
case BSSAP_MSG_BSS_MANAGEMENT:
msg->l4h = &msg->l3h[sizeof(struct bssmap_header)];
- bssmap_rcvmsg_dt1(conn, msg, len - sizeof(struct bssmap_header));
+ bssmap_rcvmsg_dt1(conn, msg, bssmap_msg_len(msg, len, conn));
break;
case BSSAP_MSG_DTAP:
dtap_rcvmsg(conn, msg, len);
@@ -1020,6 +1711,7 @@ int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell
{
int rc;
struct msgb *msg;
+ struct gsm_subscriber_connection *conn = lchan->conn;
struct gsm0808_handover_required params = {
.cause = GSM0808_CAUSE_BETTER_CELL,
.cil = *target_cells,
@@ -1027,12 +1719,21 @@ int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell
.current_channel_type_1 = gsm0808_current_channel_type_1(lchan->type),
};
+ /* Even if fast_return is now allowed locally, we may still want to
+ * signal the Last EUTRAN PLMN Id to the new cell, since destination
+ * config may differ and allow fast return */
+ if (conn->fast_return.last_eutran_plmn_valid) {
+ params.old_bss_to_new_bss_info_present = true;
+ params.old_bss_to_new_bss_info.last_eutran_plmn_id_present = true;
+ params.old_bss_to_new_bss_info.last_eutran_plmn_id = conn->fast_return.last_eutran_plmn;
+ }
+
switch (lchan->type) {
case GSM_LCHAN_TCH_F:
case GSM_LCHAN_TCH_H:
params.speech_version_used_present = true;
params.speech_version_used = gsm0808_permitted_speech(lchan->type,
- lchan->tch_mode);
+ lchan->current_ch_mode_rate.chan_mode);
if (!params.speech_version_used) {
LOG_HO(lchan->conn, LOGL_ERROR, "Cannot encode Speech Version (Used)"
" for BSSMAP Handover Required message\n");
@@ -1045,13 +1746,14 @@ int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell
msg = gsm0808_create_handover_required(&params);
if (!msg) {
- LOG_HO(lchan->conn, LOGL_ERROR, "Cannot compose BSSMAP Handover Required message\n");
+ LOG_HO(conn, LOGL_ERROR, "Cannot compose BSSMAP Handover Required message\n");
return -EINVAL;
}
- rc = gscon_sigtran_send(lchan->conn, msg);
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_REQUIRED));
+ rc = gscon_sigtran_send(conn, msg);
if (rc) {
- LOG_HO(lchan->conn, LOGL_ERROR, "Cannot send BSSMAP Handover Required message\n");
+ LOG_HO(conn, LOGL_ERROR, "Cannot send BSSMAP Handover Required message\n");
return rc;
}
@@ -1064,14 +1766,55 @@ int bsc_tx_bssmap_ho_request_ack(struct gsm_subscriber_connection *conn, struct
{
struct msgb *msg;
struct gsm_lchan *new_lchan = conn->ho.new_lchan;
+ struct sockaddr_storage ss;
+ struct gsm0808_handover_request_ack params = {
+ .l3_info = rr_ho_command->data,
+ .l3_info_len = rr_ho_command->len,
+ .chosen_channel_present = true,
+ .chosen_channel = gsm0808_chosen_channel(new_lchan->type, new_lchan->current_ch_mode_rate.chan_mode),
+ .chosen_encr_alg = ALG_A5_NR_TO_BSSAP(new_lchan->encr.alg_a5_n),
+ .chosen_speech_version = gsm0808_permitted_speech(new_lchan->type,
+ new_lchan->current_ch_mode_rate.chan_mode),
+ };
+
+ if (gscon_is_aoip(conn) && bsc_chan_ind_requires_rtp_stream(new_lchan->activate.info.ch_indctr)) {
+ struct osmo_sockaddr_str to_msc_rtp;
+ const struct mgcp_conn_peer *rtp_info = osmo_mgcpc_ep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc);
+ int rc;
+ int perm_spch;
+ if (!rtp_info) {
+ LOG_HO(conn, LOGL_ERROR,
+ "Handover Request Acknowledge: no RTP address known to send as"
+ " AoIP Transport Layer Address\n");
+ return -EINVAL;
+ }
+ if (osmo_sockaddr_str_from_str(&to_msc_rtp, rtp_info->addr, rtp_info->port)) {
+ LOG_HO(conn, LOGL_ERROR, "Handover Request Acknowledge: cannot encode AoIP Transport Layer\n");
+ return -EINVAL;
+ }
+ if (osmo_sockaddr_str_to_sockaddr(&to_msc_rtp, &ss)) {
+ LOG_HO(conn, LOGL_ERROR, "Handover Request Acknowledge: cannot encode AoIP Transport Layer\n");
+ return -EINVAL;
+ }
+ params.aoip_transport_layer = &ss;
+
+ /* speech_codec_chosen */
+ perm_spch = gsm0808_permitted_speech(new_lchan->type, new_lchan->current_ch_mode_rate.chan_mode);
+ params.speech_codec_chosen_present = true;
+ rc = gsm0808_speech_codec_from_chan_type(&params.speech_codec_chosen, perm_spch);
+ if (rc) {
+ LOG_HO(conn, LOGL_ERROR, "Unable to compose Speech Codec (Chosen)\n");
+ return -EINVAL;
+ }
+ /* Codec list (BSS Supported) */
+ params.more_items = true;
+ gen_bss_supported_codec_list(&params.codec_list_bss_supported, conn->sccp.msc, new_lchan->ts->trx->bts);
+ }
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_RQST_ACKNOWLEDGE));
LOG_HO(conn, LOGL_DEBUG, "Sending BSSMAP Handover Request Acknowledge\n");
- msg = gsm0808_create_handover_request_ack(rr_ho_command->data, rr_ho_command->len,
- gsm0808_chosen_channel(new_lchan->type,
- new_lchan->tch_mode),
- new_lchan->encr.alg_id,
- gsm0808_permitted_speech(new_lchan->type,
- new_lchan->tch_mode));
+ msg = gsm0808_create_handover_request_ack2(&params);
msgb_free(rr_ho_command);
if (!msg)
return -ENOMEM;
@@ -1085,6 +1828,7 @@ int bsc_tx_bssmap_ho_detect(struct gsm_subscriber_connection *conn)
if (!msg)
return -ENOMEM;
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_DETECT));
return osmo_bsc_sigtran_send(conn, msg);
}
@@ -1098,18 +1842,18 @@ enum handover_result bsc_tx_bssmap_ho_complete(struct gsm_subscriber_connection
struct gsm0808_handover_complete params = {
.chosen_encr_alg_present = true,
- .chosen_encr_alg = lchan->encr.alg_id,
+ .chosen_encr_alg = ALG_A5_NR_TO_BSSAP(lchan->encr.alg_a5_n),
.chosen_channel_present = true,
- .chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode),
+ .chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_mode),
.lcls_bss_status_present = (lcls_status != 0xff),
.lcls_bss_status = lcls_status,
};
/* speech_codec_chosen */
- if (ho->new_lchan->activate.info.requires_voice_stream && gscon_is_aoip(conn)) {
- int perm_spch = gsm0808_permitted_speech(lchan->type, lchan->tch_mode);
+ if (bsc_chan_ind_requires_rtp_stream(ho->new_lchan->activate.info.ch_indctr) && gscon_is_aoip(conn)) {
+ int perm_spch = gsm0808_permitted_speech(lchan->type, lchan->current_ch_mode_rate.chan_mode);
params.speech_codec_chosen_present = true;
rc = gsm0808_speech_codec_from_chan_type(&params.speech_codec_chosen, perm_spch);
if (rc) {
@@ -1124,6 +1868,7 @@ enum handover_result bsc_tx_bssmap_ho_complete(struct gsm_subscriber_connection
return HO_RESULT_ERROR;
}
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_COMPLETE));
rc = osmo_bsc_sigtran_send(conn, msg);
if (rc) {
LOG_HO(conn, LOGL_ERROR, "Cannot send BSSMAP Handover Complete message\n");
@@ -1145,8 +1890,186 @@ void bsc_tx_bssmap_ho_failure(struct gsm_subscriber_connection *conn)
return;
}
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_FAILURE));
rc = osmo_bsc_sigtran_send(conn, msg);
if (rc)
LOG_HO(conn, LOGL_ERROR, "Cannot send BSSMAP Handover Failure message (rc=%d %s)\n",
rc, strerror(-rc));
}
+
+/* Send SETUP ACKNOWLEDGE to MSC. */
+void bsc_tx_setup_ack(struct gsm_subscriber_connection *conn, struct gsm0808_vgcs_feature_flags *ff)
+{
+ struct msgb *resp;
+ struct gsm0808_vgcs_vbs_setup_ack sa = {};
+
+ if (ff) {
+ sa.vgcs_feature_flags_present = true;
+ sa.flags = *ff;
+ }
+ resp = gsm0808_create_vgcs_vbs_setup_ack(&sa);
+ OSMO_ASSERT(resp);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_SETUP_ACK));
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+}
+
+/* Send SETUP REFUSE to MSC. */
+void bsc_tx_setup_refuse(struct gsm_subscriber_connection *conn, uint8_t cause)
+{
+ struct msgb *resp;
+ resp = gsm0808_create_vgcs_vbs_setup_refuse(cause);
+
+ OSMO_ASSERT(resp);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_SETUP_REFUSE));
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+}
+
+/* Send ASSIGNMENT FAILURE to MSC. */
+void bsc_tx_vgcs_vbs_assignment_fail(struct gsm_subscriber_connection *conn, uint8_t cause)
+{
+ struct msgb *resp;
+ struct gsm0808_vgcs_vbs_assign_fail af = {
+ .cause = cause,
+ };
+
+ resp = gsm0808_create_vgcs_vbs_assign_fail(&af);
+ OSMO_ASSERT(resp);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGN_FAIL));
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+}
+
+/* Send ASSIGNMENT RESULT to MSC. */
+void bsc_tx_vgcs_vbs_assignment_result(struct gsm_subscriber_connection *conn, struct gsm0808_channel_type *ct,
+ struct gsm0808_cell_id *ci, uint32_t call_id)
+{
+ struct gsm_lchan *lchan = conn->lchan;
+ struct msgb *resp;
+ struct gsm0808_vgcs_vbs_assign_res ar = {
+ .channel_type = *ct,
+ .cell_identifier = *ci,
+ };
+ int perm_spch;
+ uint8_t osmux_cid;
+
+ /* Chosen Channel */
+ ar.chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_mode);
+ if (!ar.chosen_channel) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to compose Chosen Channel for mode=%s type=%s",
+ get_value_string(gsm48_chan_mode_names, lchan->current_ch_mode_rate.chan_mode),
+ gsm_chan_t_name(lchan->type));
+ bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ return;
+ }
+ ar.chosen_channel_present = true;
+
+ /* Generate RTP related fields. */
+ if (gscon_is_aoip(conn)) {
+ /* AoIP Transport Layer Address (BSS) */
+ if (!osmo_mgcpc_ep_ci_get_crcx_info_to_sockaddr(conn->user_plane.mgw_endpoint_ci_msc,
+ &ar.aoip_transport_layer)) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to compose RTP address of MGW -> MSC");
+ bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ return;
+ }
+ ar.aoip_transport_layer_present = true;
+
+ /* Call Identifier */
+ ar.call_id = call_id;
+ ar.call_id_present = true;
+
+ /* Osmux */
+ if (conn->assignment.req.use_osmux) {
+ if (!osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(conn->user_plane.mgw_endpoint_ci_msc,
+ &osmux_cid)) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to compose Osmux CID of MGW -> MSC");
+ bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ return;
+ }
+ }
+
+ /* Extrapolate speech codec from speech mode */
+ perm_spch = gsm0808_permitted_speech(lchan->type, lchan->current_ch_mode_rate.chan_mode);
+ gsm0808_speech_codec_from_chan_type(&ar.codec_msc_chosen, perm_spch);
+ ar.codec_msc_chosen.cfg = conn->lchan->current_ch_mode_rate.s15_s0;
+ ar.codec_present = true;
+ }
+
+ resp = gsm0808_create_vgcs_vbs_assign_res(&ar);
+ OSMO_ASSERT(resp);
+ if (conn->assignment.req.use_osmux)
+ bssap_extend_osmux(resp, osmux_cid);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGN_RESULT));
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+}
+
+/* Send UPLINK REQUEST to MSC. */
+void bsc_tx_uplink_req(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *resp;
+ struct gsm0808_uplink_request ur = {};
+
+ resp = gsm0808_create_uplink_request(&ur);
+ OSMO_ASSERT(resp);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_UPLINK_RQST));
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+}
+
+/* Send UPLINK REQUEST CONFIRMATION to MSC. */
+void bsc_tx_uplink_req_conf(struct gsm_subscriber_connection *conn, struct gsm0808_cell_id *ci, uint8_t *l3_info,
+ uint8_t length)
+{
+ struct msgb *resp;
+ struct gsm0808_uplink_request_cnf ur = {
+ .cell_identifier = *ci,
+ };
+
+ OSMO_ASSERT(length <= LAYER_3_INFORMATION_MAXLEN);
+ if (length) {
+ memcpy(ur.l3.l3, l3_info, length);
+ ur.l3.l3_len = length;
+ }
+ resp = gsm0808_create_uplink_request_cnf(&ur);
+ OSMO_ASSERT(resp);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_UPLINK_RQST_CONFIRMATION));
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+}
+
+/* Send UPLINK APPLICATION DATA to MSC. */
+void bsc_tx_uplink_app_data(struct gsm_subscriber_connection *conn, struct gsm0808_cell_id *ci, uint8_t *l3_info,
+ uint8_t length)
+{
+ struct msgb *resp;
+ struct gsm0808_uplink_app_data ad = {
+ .cell_identifier = *ci,
+ };
+
+ OSMO_ASSERT(length <= LAYER_3_INFORMATION_MAXLEN);
+ memcpy(ad.l3.l3, l3_info, length);
+ ad.l3.l3_len = length;
+ resp = gsm0808_create_uplink_app_data(&ad);
+ OSMO_ASSERT(resp);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_UPLINK_APP_DATA));
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+}
+
+/* Send UPLINK RELEASE INDICATION to MSC. */
+void bsc_tx_uplink_release_ind(struct gsm_subscriber_connection *conn, uint8_t cause)
+{
+ struct msgb *resp;
+ struct gsm0808_uplink_release_ind ri = {
+ .cause = cause,
+ };
+
+ resp = gsm0808_create_uplink_release_ind(&ri);
+ OSMO_ASSERT(resp);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_UPLINK_RELEASE_INDICATION));
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, resp);
+}
diff --git a/src/osmo-bsc/osmo_bsc_ctrl.c b/src/osmo-bsc/osmo_bsc_ctrl.c
deleted file mode 100644
index 80699f877..000000000
--- a/src/osmo-bsc/osmo_bsc_ctrl.c
+++ /dev/null
@@ -1,778 +0,0 @@
-/* (C) 2011 by Daniel Willmann <daniel@totalueberwachung.de>
- * (C) 2011 by Holger Hans Peter Freyther
- * (C) 2011 by On-Waves
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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/ctrl/control_cmd.h>
-#include <osmocom/bsc/ctrl.h>
-#include <osmocom/bsc/debug.h>
-#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/bsc/osmo_bsc.h>
-#include <osmocom/bsc/osmo_bsc_rf.h>
-#include <osmocom/bsc/bsc_msc_data.h>
-#include <osmocom/bsc/signal.h>
-#include <osmocom/bsc/gsm_04_80.h>
-
-#include <osmocom/core/linuxlist.h>
-#include <osmocom/core/signal.h>
-
-#include <osmocom/ctrl/control_if.h>
-
-#include <osmocom/gsm/protocol/ipaccess.h>
-#include <osmocom/gsm/ipa.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <unistd.h>
-
-/* Obtain SS7 application server currently handling given MSC (DPC) */
-static struct osmo_ss7_as *msc_get_ss7_as(struct bsc_msc_data *msc)
-{
- struct osmo_ss7_route *rt;
- struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(msc->a.sccp);
- rt = osmo_ss7_route_lookup(ss7, msc->a.msc_addr.pc);
- if (!rt)
- return NULL;
- return rt->dest.as;
-}
-
-
-/* Encode a CTRL command and send it to the given ASP
- * \param[in] asp ASP through which we shall send the encoded message
- * \param[in] cmd decoded CTRL command to be encoded and sent. Ownership is *NOT*
- * transferred, to permit caller to send the same CMD to several ASPs.
- * Caller must hence free 'cmd' itself.
- * \returns 0 on success; negative on error */
-static int sccplite_asp_ctrl_cmd_send(struct osmo_ss7_asp *asp, struct ctrl_cmd *cmd)
-{
- /* this is basically like libosmoctrl:ctrl_cmd_send(), not for a dedicated
- * CTRL connection but for the CTRL piggy-back on the IPA/SCCPlite link */
- struct msgb *msg;
-
- /* don't attempt to send CTRL on a non-SCCPlite ASP */
- if (asp->cfg.proto != OSMO_SS7_ASP_PROT_IPA)
- return 0;
-
- msg = ctrl_cmd_make(cmd);
- if (!msg)
- return -1;
-
- ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
- ipa_prepend_header(msg, IPAC_PROTO_OSMO);
-
- return osmo_ss7_asp_send(asp, msg);
-}
-
-/* Ownership of 'cmd' is *NOT* transferred, to permit caller to send the same CMD to several ASPs.
- * Caller must hence free 'cmd' itself. */
-static int sccplite_msc_ctrl_cmd_send(struct bsc_msc_data *msc, struct ctrl_cmd *cmd)
-{
- struct osmo_ss7_as *as;
- struct osmo_ss7_asp *asp;
- unsigned int i;
-
- as = msc_get_ss7_as(msc);
- if (!as)
- return -1;
-
- /* don't attempt to send CTRL on a non-SCCPlite AS */
- if (as->cfg.proto != OSMO_SS7_ASP_PROT_IPA)
- return 0;
-
- /* FIXME: unify with xua_as_transmit_msg() and perform proper ASP lookup */
- for (i = 0; i < ARRAY_SIZE(as->cfg.asps); i++) {
- asp = as->cfg.asps[i];
- if (!asp)
- continue;
- /* FIXME: deal with multiple ASPs per AS */
- return sccplite_asp_ctrl_cmd_send(asp, cmd);
- }
- return -1;
-}
-
-/* receive + process a CTRL command from the piggy-back on the IPA/SCCPlite link */
-int bsc_sccplite_rx_ctrl(struct osmo_ss7_asp *asp, struct msgb *msg)
-{
- struct ctrl_cmd *cmd;
- bool parse_failed;
- int rc;
-
- /* caller has already ensured ipaccess_head + ipaccess_head_ext */
- OSMO_ASSERT(msg->l2h);
-
- /* prase raw (ASCII) CTRL command into ctrl_cmd */
- cmd = ctrl_cmd_parse3(asp, msg, &parse_failed);
- OSMO_ASSERT(cmd);
- msgb_free(msg);
- if (cmd->type == CTRL_TYPE_ERROR && parse_failed)
- goto send_reply;
-
- /* handle the CTRL command */
- ctrl_cmd_handle(bsc_gsmnet->ctrl, cmd, bsc_gsmnet);
-
-send_reply:
- rc = sccplite_asp_ctrl_cmd_send(asp, cmd);
- talloc_free(cmd);
- return rc;
-}
-
-
-void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_data *msc_data)
-{
- struct ctrl_cmd *trap;
- struct ctrl_handle *ctrl;
-
- ctrl = msc_data->network->ctrl;
-
- trap = ctrl_cmd_trap(cmd);
- if (!trap) {
-
- LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n");
- return;
- }
-
- ctrl_cmd_send_to_all(ctrl, trap);
- sccplite_msc_ctrl_cmd_send(msc_data, trap);
-
- talloc_free(trap);
-}
-
-CTRL_CMD_DEFINE_RO(msc_connection_status, "connection_status");
-static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data)
-{
- struct bsc_msc_data *msc = (struct bsc_msc_data *)cmd->node;
- struct osmo_ss7_as *as;
- const char *as_state_name;
-
- if (msc == NULL) {
- cmd->reply = "msc not found";
- return CTRL_CMD_ERROR;
- }
- as = msc_get_ss7_as(msc);
- if (!as) {
- cmd->reply = "AS not found for MSC";
- return CTRL_CMD_ERROR;
- }
-
- as_state_name = osmo_fsm_inst_state_name(as->fi);
- if (!strcmp(as_state_name, "AS_ACTIVE"))
- cmd->reply = "connected";
- else
- cmd->reply = "disconnected";
- return CTRL_CMD_REPLY;
-}
-
-/* Backwards compat. */
-CTRL_CMD_DEFINE_RO(msc0_connection_status, "msc_connection_status");
-static int msc_connection_status = 0; /* XXX unused */
-
-static int get_msc0_connection_status(struct ctrl_cmd *cmd, void *data)
-{
- struct bsc_msc_data *msc = osmo_msc_data_find(bsc_gsmnet, 0);
- void *old_node = cmd->node;
- int rc;
-
- cmd->node = msc;
- rc = get_msc_connection_status(cmd, data);
- cmd->node = old_node;
-
- return rc;
-}
-
-static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
-{
- struct ctrl_cmd *cmd;
- struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
-
- if (signal == S_MSC_LOST && msc_connection_status == 1) {
- LOGP(DCTRL, LOGL_DEBUG, "MSC connection lost, sending TRAP.\n");
- msc_connection_status = 0;
- } else if (signal == S_MSC_CONNECTED && msc_connection_status == 0) {
- LOGP(DCTRL, LOGL_DEBUG, "MSC connection (re)established, sending TRAP.\n");
- msc_connection_status = 1;
- } else {
- return 0;
- }
-
- cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
- if (!cmd) {
- LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
- return 0;
- }
-
- cmd->id = "0";
- cmd->variable = "msc_connection_status";
-
- get_msc0_connection_status(cmd, NULL);
-
- ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
-
- talloc_free(cmd);
-
- return 0;
-}
-
-CTRL_CMD_DEFINE_RO(bts_connection_status, "bts_connection_status");
-static int bts_connection_status = 0;
-
-static int get_bts_connection_status(struct ctrl_cmd *cmd, void *data)
-{
- if (bts_connection_status)
- cmd->reply = "connected";
- else
- cmd->reply = "disconnected";
- return CTRL_CMD_REPLY;
-}
-
-static int bts_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
-{
- struct ctrl_cmd *cmd;
- struct gsm_network *gsmnet = (struct gsm_network *)handler_data;
- struct gsm_bts *bts;
- int bts_current_status;
-
- if (signal != S_L_INP_TEI_DN && signal != S_L_INP_TEI_UP) {
- return 0;
- }
-
- bts_current_status = 0;
- /* Check if OML on at least one BTS is up */
- llist_for_each_entry(bts, &gsmnet->bts_list, list) {
- if (bts->oml_link) {
- bts_current_status = 1;
- break;
- }
- }
- if (bts_connection_status == 0 && bts_current_status == 1) {
- LOGP(DCTRL, LOGL_DEBUG, "BTS connection (re)established, sending TRAP.\n");
- } else if (bts_connection_status == 1 && bts_current_status == 0) {
- LOGP(DCTRL, LOGL_DEBUG, "No more BTS connected, sending TRAP.\n");
- } else {
- return 0;
- }
-
- cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
- if (!cmd) {
- LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
- return 0;
- }
-
- bts_connection_status = bts_current_status;
-
- cmd->id = "0";
- cmd->variable = "bts_connection_status";
-
- get_bts_connection_status(cmd, NULL);
-
- ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
-
- talloc_free(cmd);
-
- return 0;
-}
-
-static int get_bts_loc(struct ctrl_cmd *cmd, void *data);
-
-static void generate_location_state_trap(struct gsm_bts *bts, struct bsc_msc_data *msc)
-{
- struct ctrl_cmd *cmd;
- const char *oper, *admin, *policy;
-
- cmd = ctrl_cmd_create(msc, CTRL_TYPE_TRAP);
- if (!cmd) {
- LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n");
- return;
- }
-
- cmd->id = "0";
- cmd->variable = talloc_asprintf(cmd, "bts.%i.location-state", bts->nr);
-
- /* Prepare the location reply */
- cmd->node = bts;
- get_bts_loc(cmd, NULL);
-
- oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts));
- admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
- policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
-
- cmd->reply = talloc_asprintf_append(cmd->reply,
- ",%s,%s,%s,%s,%s",
- oper, admin, policy,
- osmo_mcc_name(bts->network->plmn.mcc),
- osmo_mnc_name(bts->network->plmn.mnc,
- bts->network->plmn.mnc_3_digits));
-
- osmo_bsc_send_trap(cmd, msc);
- talloc_free(cmd);
-}
-
-void bsc_gen_location_state_trap(struct gsm_bts *bts)
-{
- struct bsc_msc_data *msc;
-
- llist_for_each_entry(msc, &bts->network->bsc_data->mscs, entry)
- generate_location_state_trap(bts, msc);
-}
-
-static int location_equal(struct bts_location *a, struct bts_location *b)
-{
- return ((a->tstamp == b->tstamp) && (a->valid == b->valid) && (a->lat == b->lat) &&
- (a->lon == b->lon) && (a->height == b->height));
-}
-
-static void cleanup_locations(struct llist_head *locations)
-{
- struct bts_location *myloc, *tmp;
- int invalpos = 0, i = 0;
-
- LOGP(DCTRL, LOGL_DEBUG, "Checking position list.\n");
- llist_for_each_entry_safe(myloc, tmp, locations, list) {
- i++;
- if (i > 3) {
- LOGP(DCTRL, LOGL_DEBUG, "Deleting old position.\n");
- llist_del(&myloc->list);
- talloc_free(myloc);
- } else if (myloc->valid == BTS_LOC_FIX_INVALID) {
- /* Only capture the newest of subsequent invalid positions */
- invalpos++;
- if (invalpos > 1) {
- LOGP(DCTRL, LOGL_DEBUG, "Deleting subsequent invalid position.\n");
- invalpos--;
- i--;
- llist_del(&myloc->list);
- talloc_free(myloc);
- }
- } else {
- invalpos = 0;
- }
- }
- LOGP(DCTRL, LOGL_DEBUG, "Found %i positions.\n", i);
-}
-
-CTRL_CMD_DEFINE(bts_loc, "location");
-static int get_bts_loc(struct ctrl_cmd *cmd, void *data)
-{
- struct bts_location *curloc;
- struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
- if (!bts) {
- cmd->reply = "bts not found.";
- return CTRL_CMD_ERROR;
- }
-
- if (llist_empty(&bts->loc_list)) {
- cmd->reply = talloc_asprintf(cmd, "0,invalid,0,0,0");
- return CTRL_CMD_REPLY;
- } else {
- curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
- }
-
- cmd->reply = talloc_asprintf(cmd, "%lu,%s,%f,%f,%f", curloc->tstamp,
- get_value_string(bts_loc_fix_names, curloc->valid), curloc->lat, curloc->lon, curloc->height);
- if (!cmd->reply) {
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
- }
-
- return CTRL_CMD_REPLY;
-}
-
-static int set_bts_loc(struct ctrl_cmd *cmd, void *data)
-{
- char *saveptr, *lat, *lon, *height, *tstamp, *valid, *tmp;
- struct bts_location *curloc, *lastloc;
- int ret;
- struct gsm_bts *bts = (struct gsm_bts *) cmd->node;
- if (!bts) {
- cmd->reply = "bts not found.";
- return CTRL_CMD_ERROR;
- }
-
- tmp = talloc_strdup(cmd, cmd->value);
- if (!tmp)
- goto oom;
-
- curloc = talloc_zero(tall_bsc_ctx, struct bts_location);
- if (!curloc) {
- talloc_free(tmp);
- goto oom;
- }
- INIT_LLIST_HEAD(&curloc->list);
-
-
- tstamp = strtok_r(tmp, ",", &saveptr);
- valid = strtok_r(NULL, ",", &saveptr);
- lat = strtok_r(NULL, ",", &saveptr);
- lon = strtok_r(NULL, ",", &saveptr);
- height = strtok_r(NULL, "\0", &saveptr);
-
- curloc->tstamp = atol(tstamp);
- curloc->valid = get_string_value(bts_loc_fix_names, valid);
- curloc->lat = atof(lat);
- curloc->lon = atof(lon);
- curloc->height = atof(height);
- talloc_free(tmp);
-
- lastloc = llist_entry(bts->loc_list.next, struct bts_location, list);
-
- /* Add location to the end of the list */
- llist_add(&curloc->list, &bts->loc_list);
-
- ret = get_bts_loc(cmd, data);
-
- if (!location_equal(curloc, lastloc))
- bsc_gen_location_state_trap(bts);
-
- cleanup_locations(&bts->loc_list);
-
- return ret;
-
-oom:
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
-}
-
-static int verify_bts_loc(struct ctrl_cmd *cmd, const char *value, void *data)
-{
- char *saveptr, *latstr, *lonstr, *heightstr, *tstampstr, *validstr, *tmp;
- time_t tstamp;
- int valid;
- double lat, lon, height __attribute__((unused));
-
- tmp = talloc_strdup(cmd, value);
- if (!tmp)
- return 1;
-
- tstampstr = strtok_r(tmp, ",", &saveptr);
- validstr = strtok_r(NULL, ",", &saveptr);
- latstr = strtok_r(NULL, ",", &saveptr);
- lonstr = strtok_r(NULL, ",", &saveptr);
- heightstr = strtok_r(NULL, "\0", &saveptr);
-
- if ((tstampstr == NULL) || (validstr == NULL) || (latstr == NULL) ||
- (lonstr == NULL) || (heightstr == NULL))
- goto err;
-
- tstamp = atol(tstampstr);
- valid = get_string_value(bts_loc_fix_names, validstr);
- lat = atof(latstr);
- lon = atof(lonstr);
- height = atof(heightstr);
- talloc_free(tmp);
- tmp = NULL;
-
- if (((tstamp == 0) && (valid != BTS_LOC_FIX_INVALID)) || (lat < -90) || (lat > 90) ||
- (lon < -180) || (lon > 180) || (valid < 0)) {
- goto err;
- }
-
- return 0;
-
-err:
- talloc_free(tmp);
- cmd->reply = talloc_strdup(cmd, "The format is <unixtime>,(invalid|fix2d|fix3d),<lat>,<lon>,<height>");
- return 1;
-}
-
-CTRL_CMD_DEFINE(net_timezone, "timezone");
-static int get_net_timezone(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_network *net = (struct gsm_network*)cmd->node;
-
- struct gsm_tz *tz = &net->tz;
- if (tz->override)
- cmd->reply = talloc_asprintf(cmd, "%d,%d,%d",
- tz->hr, tz->mn, tz->dst);
- else
- cmd->reply = talloc_asprintf(cmd, "off");
-
- if (!cmd->reply) {
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
- }
-
- return CTRL_CMD_REPLY;
-}
-
-static int set_net_timezone(struct ctrl_cmd *cmd, void *data)
-{
- char *saveptr, *hourstr, *minstr, *dststr, *tmp = 0;
- int override = 0;
- struct gsm_network *net = (struct gsm_network*)cmd->node;
- struct gsm_tz *tz = &net->tz;
-
- tmp = talloc_strdup(cmd, cmd->value);
- if (!tmp)
- goto oom;
-
- hourstr = strtok_r(tmp, ",", &saveptr);
- minstr = strtok_r(NULL, ",", &saveptr);
- dststr = strtok_r(NULL, ",", &saveptr);
-
- if (hourstr != NULL) {
- override = strcasecmp(hourstr, "off") != 0;
- if (override) {
- tz->hr = atol(hourstr);
- tz->mn = minstr ? atol(minstr) : 0;
- tz->dst = dststr ? atol(dststr) : 0;
- }
- }
-
- tz->override = override;
-
-
- talloc_free(tmp);
- tmp = NULL;
-
- return get_net_timezone(cmd, data);
-
-oom:
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
-}
-
-static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data)
-{
- char *saveptr, *hourstr, *minstr, *dststr, *tmp;
- int override, tz_hours, tz_mins, tz_dst;
-
- tmp = talloc_strdup(cmd, value);
- if (!tmp)
- return 1;
-
- hourstr = strtok_r(tmp, ",", &saveptr);
- minstr = strtok_r(NULL, ",", &saveptr);
- dststr = strtok_r(NULL, ",", &saveptr);
-
- if (hourstr == NULL)
- goto err;
-
- override = strcasecmp(hourstr, "off") != 0;
-
- if (!override) {
- talloc_free(tmp);
- return 0;
- }
-
- if (minstr == NULL || dststr == NULL)
- goto err;
-
- tz_hours = atol(hourstr);
- tz_mins = atol(minstr);
- tz_dst = atol(dststr);
-
- talloc_free(tmp);
- tmp = NULL;
-
- if ((tz_hours < -19) || (tz_hours > 19) ||
- (tz_mins < 0) || (tz_mins >= 60) || (tz_mins % 15 != 0) ||
- (tz_dst < 0) || (tz_dst > 2))
- goto err;
-
- return 0;
-
-err:
- talloc_free(tmp);
- cmd->reply = talloc_strdup(cmd, "The format is <hours>,<mins>,<dst> or 'off' where -19 <= hours <= 19, mins in {0, 15, 30, 45}, and 0 <= dst <= 2");
- return 1;
-}
-
-CTRL_CMD_DEFINE_WO_NOVRF(net_notification, "notification");
-static int set_net_notification(struct ctrl_cmd *cmd, void *data)
-{
- struct ctrl_cmd *trap;
- struct gsm_network *net;
-
- net = cmd->node;
-
- trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
- if (!trap) {
- LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
- goto handled;
- }
-
- trap->id = "0";
- trap->variable = "notification";
- trap->reply = talloc_strdup(trap, cmd->value);
-
- /*
- * This should only be sent to local systems. In the future
- * we might even ask for systems to register to receive
- * the notifications.
- */
- ctrl_cmd_send_to_all(net->ctrl, trap);
- talloc_free(trap);
-
-handled:
- return CTRL_CMD_HANDLED;
-}
-
-CTRL_CMD_DEFINE_WO_NOVRF(net_inform_msc, "inform-msc-v1");
-static int set_net_inform_msc(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_network *net;
- struct bsc_msc_data *msc;
-
- net = cmd->node;
- llist_for_each_entry(msc, &net->bsc_data->mscs, entry) {
- struct ctrl_cmd *trap;
-
- trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
- if (!trap) {
- LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n");
- continue;
- }
-
- trap->id = "0";
- trap->variable = "inform-msc-v1";
- trap->reply = talloc_strdup(trap, cmd->value);
- sccplite_msc_ctrl_cmd_send(msc, trap);
- talloc_free(trap);
- }
-
-
- return CTRL_CMD_HANDLED;
-}
-
-CTRL_CMD_DEFINE_WO(net_ussd_notify, "ussd-notify-v1");
-static int set_net_ussd_notify(struct ctrl_cmd *cmd, void *data)
-{
- struct gsm_subscriber_connection *conn;
- struct gsm_network *net;
- char *saveptr = NULL;
- char *cic_str, *alert_str, *text_str;
- int cic, alert;
-
- /* Verify has done the test for us */
- cic_str = strtok_r(cmd->value, ",", &saveptr);
- alert_str = strtok_r(NULL, ",", &saveptr);
- text_str = strtok_r(NULL, ",", &saveptr);
-
- if (!cic_str || !alert_str || !text_str) {
- cmd->reply = "Programming issue. How did this pass verify?";
- return CTRL_CMD_ERROR;
- }
-
- cmd->reply = "No connection found";
-
- cic = atoi(cic_str);
- alert = atoi(alert_str);
-
- net = cmd->node;
- llist_for_each_entry(conn, &net->subscr_conns, entry) {
- if (conn->user_plane.msc_assigned_cic != cic)
- continue;
-
- /*
- * This is a hack. My E71 does not like to immediately
- * receive a release complete on a TCH. So schedule a
- * release complete to clear any previous attempt. The
- * right thing would be to track invokeId and only send
- * the release complete when we get a returnResultLast
- * for this invoke id.
- */
- bsc_send_ussd_release_complete(conn);
- bsc_send_ussd_notify(conn, alert, text_str);
- cmd->reply = "Found a connection";
- break;
- }
-
- return CTRL_CMD_REPLY;
-}
-
-static int verify_net_ussd_notify(struct ctrl_cmd *cmd, const char *value, void *data)
-{
- char *saveptr = NULL;
- char *inp, *cic, *alert, *text;
-
- OSMO_ASSERT(cmd);
- inp = talloc_strdup(cmd, value);
-
- cic = strtok_r(inp, ",", &saveptr);
- alert = strtok_r(NULL, ",", &saveptr);
- text = strtok_r(NULL, ",", &saveptr);
-
- talloc_free(inp);
- if (!cic || !alert || !text)
- return 1;
- return 0;
-}
-
-static int msc_signal_handler(unsigned int subsys, unsigned int signal,
- void *handler_data, void *signal_data)
-{
- struct msc_signal_data *msc;
- struct gsm_network *net;
- struct gsm_bts *bts;
-
- if (subsys != SS_MSC)
- return 0;
- if (signal != S_MSC_AUTHENTICATED)
- return 0;
-
- msc = signal_data;
-
- net = msc->data->network;
- llist_for_each_entry(bts, &net->bts_list, list)
- generate_location_state_trap(bts, msc->data);
-
- return 0;
-}
-
-int bsc_ctrl_cmds_install(struct gsm_network *net)
-{
- int rc;
-
- rc = bsc_base_ctrl_cmds_install();
- if (rc)
- goto end;
- rc = ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_loc);
- if (rc)
- goto end;
- rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_timezone);
- if (rc)
- goto end;
- rc = ctrl_cmd_install(CTRL_NODE_MSC, &cmd_msc_connection_status);
- if (rc)
- goto end;
- rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_msc0_connection_status);
- if (rc)
- goto end;
- rc = osmo_signal_register_handler(SS_MSC, &msc_connection_status_trap_cb, net);
- if (rc)
- goto end;
- rc = osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL);
- if (rc)
- goto end;
- rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status);
- if (rc)
- goto end;
- rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_notification);
- if (rc)
- goto end;
- rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_inform_msc);
- if (rc)
- goto end;
- rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_ussd_notify);
- if (rc)
- goto end;
- rc = osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net);
-
-end:
- return rc;
-}
diff --git a/src/osmo-bsc/osmo_bsc_filter.c b/src/osmo-bsc/osmo_bsc_filter.c
index 332ba6b83..2b58ccf54 100644
--- a/src/osmo-bsc/osmo_bsc_filter.c
+++ b/src/osmo-bsc/osmo_bsc_filter.c
@@ -21,35 +21,29 @@
#include <osmocom/bsc/osmo_bsc.h>
#include <osmocom/bsc/bsc_msc_data.h>
-#include <osmocom/bsc/gsm_04_80.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/bts.h>
#include <stdlib.h>
-static int send_welcome_ussd(struct gsm_subscriber_connection *conn)
-{
- if (!conn->sccp.msc->ussd_welcome_txt) {
- LOGP(DMSC, LOGL_DEBUG, "No USSD Welcome text defined.\n");
- return 0;
- }
-
- return BSS_SEND_USSD;
-}
-
static int bsc_patch_mm_info(struct gsm_subscriber_connection *conn,
uint8_t *data, unsigned int length)
{
struct tlv_parsed tp;
int parse_res;
- struct gsm_bts *bts = conn_get_bts(conn);
int tzunits;
uint8_t tzbsd = 0;
uint8_t dst = 0;
+ /* Is TZ patching enabled? */
+ struct gsm_tz *tz = &conn->network->tz;
+ if (!tz->override)
+ return 0;
+
parse_res = tlv_parse(&tp, &gsm48_mm_att_tlvdef, data, length, 0, 0);
if (parse_res <= 0 && parse_res != -3)
/* FIXME: -3 means unknown IE error, so this accepts messages
@@ -57,11 +51,6 @@ static int bsc_patch_mm_info(struct gsm_subscriber_connection *conn,
* IE and the message is broken or parsed incompletely. */
return 0;
- /* Is TZ patching enabled? */
- struct gsm_tz *tz = &bts->network->tz;
- if (!tz->override)
- return 0;
-
/* Convert tz.hr and tz.mn to units */
if (tz->hr < 0) {
tzunits = -tz->hr*4;
@@ -103,26 +92,11 @@ static int bsc_patch_mm_info(struct gsm_subscriber_connection *conn,
return 0;
}
-static int has_core_identity(struct bsc_msc_data *msc)
-{
- if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID)
- return 1;
- if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
- return 1;
- if (msc->core_lac != -1)
- return 1;
- if (msc->core_ci != -1)
- return 1;
- return 0;
-}
-
/**
* Messages coming back from the MSC.
*/
int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
{
- struct bsc_msc_data *msc;
- struct gsm48_loc_area_id *lai;
struct gsm48_hdr *gh;
uint8_t pdisc;
uint8_t mtype;
@@ -141,23 +115,27 @@ int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
return 0;
mtype = gsm48_hdr_msg_type(gh);
- msc = conn->sccp.msc;
-
- if (mtype == GSM48_MT_MM_LOC_UPD_ACCEPT) {
- if (has_core_identity(msc)) {
- if (msgb_l3len(msg) >= sizeof(*gh) + sizeof(*lai)) {
- /* overwrite LAI in the message */
- lai = (struct gsm48_loc_area_id *) &gh->data[0];
- gsm48_generate_lai2(lai, bts_lai(conn_get_bts(conn)));
- }
- }
-
- if (conn->new_subscriber)
- return send_welcome_ussd(conn);
- return 0;
- } else if (mtype == GSM48_MT_MM_INFO) {
+ if (mtype == GSM48_MT_MM_INFO) {
bsc_patch_mm_info(conn, &gh->data[0], length);
}
+ if (conn && conn->lchan) {
+ struct rate_ctr_group *bts_ctrs = conn->lchan->ts->trx->bts->bts_ctrs;
+ switch (mtype) {
+ case GSM48_MT_MM_LOC_UPD_ACCEPT:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_LOCATION_UPDATE_ACCEPT));
+ break;
+ case GSM48_MT_MM_LOC_UPD_REJECT:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_LOCATION_UPDATE_REJECT));
+ break;
+ case GSM48_MT_MM_IMSI_DETACH_IND:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_LOCATION_UPDATE_DETACH));
+ break;
+ default:
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_LOCATION_UPDATE_UNKNOWN));
+ break;
+ }
+ }
+
return 0;
}
diff --git a/src/osmo-bsc/osmo_bsc_grace.c b/src/osmo-bsc/osmo_bsc_grace.c
index 2cc3d1a4e..19ffe65aa 100644
--- a/src/osmo-bsc/osmo_bsc_grace.c
+++ b/src/osmo-bsc/osmo_bsc_grace.c
@@ -21,129 +21,15 @@
#include <osmocom/bsc/osmo_bsc_grace.h>
#include <osmocom/bsc/osmo_bsc_rf.h>
#include <osmocom/bsc/bsc_msc_data.h>
-#include <osmocom/bsc/gsm_04_80.h>
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/signal.h>
#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/bts.h>
int bsc_grace_allow_new_connection(struct gsm_network *network, struct gsm_bts *bts)
{
if (bts->excl_from_rf_lock)
return 1;
- return network->bsc_data->rf_ctrl->policy == S_RF_ON;
-}
-
-
-/* Return value is like paging_request_bts():
- * returns 1 on success (one BTS was paged); 0 in case of error (e.g. TRX down) */
-static int locked_paging_bts(struct gsm_bts *bts,
- struct bsc_subscr *subscr,
- int chan_needed,
- struct bsc_msc_data *msc)
-{
- /* Return error if the BTS is not excluded from the lock. */
- if (!bts->excl_from_rf_lock)
- return 0;
-
- /* in case of no lac patching is in place, check the BTS */
- if (msc->core_lac == -1 && subscr->lac != bts->location_area_code)
- return 0;
-
- return paging_request_bts(bts, subscr, chan_needed, msc);
-}
-
-/**
- * Page a subscriber in an MSC.
- * \param[in] rf_policy if not S_RF_ON, page only BTSs which are not excluded from the RF lock
- * \param[in] subscr subscriber we want to page
- * \param[in] chan_needed value of the GSM0808_IE_CHANNEL_NEEDED IE
- * \param[in] msc MSC which has issued this paging
- * \param[in] bts The BTS to issue the paging on
- * \returns 1 if paging was issued to the BTS, 0 if not
- */
-int bsc_grace_paging_request(enum signal_rf rf_policy,
- struct bsc_subscr *subscr,
- int chan_needed,
- struct bsc_msc_data *msc,
- struct gsm_bts *bts)
-{
- if (rf_policy == S_RF_ON)
- return paging_request_bts(bts, subscr, chan_needed, msc);
- return locked_paging_bts(bts, subscr, chan_needed, msc);
-}
-
-static int handle_sub(struct gsm_lchan *lchan, const char *text)
-{
- struct gsm_subscriber_connection *conn;
-
- /* only send it to TCH */
- if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F)
- return -1;
-
- /* only send on the primary channel */
- conn = lchan->conn;
- if (!conn)
- return -1;
-
- if (conn->lchan != lchan)
- return -1;
-
- /* only when active */
- if (lchan->fi->state != LCHAN_ST_ESTABLISHED)
- return -1;
-
- bsc_send_ussd_notify(conn, 0, text);
- bsc_send_ussd_release_complete(conn);
-
- return 0;
-}
-
-/*
- * The place to handle the grace mode. Right now we will send
- * USSD messages to the subscriber, in the future we might start
- * a timer to have different modes for the grace period.
- */
-static int handle_grace(struct gsm_network *network)
-{
- int ts_nr, lchan_nr;
- struct gsm_bts *bts;
- struct gsm_bts_trx *trx;
-
- if (!network->bsc_data->mid_call_txt)
- return 0;
-
- llist_for_each_entry(bts, &network->bts_list, list) {
- llist_for_each_entry(trx, &bts->trx_list, list) {
- for (ts_nr = 0; ts_nr < TRX_NR_TS; ++ts_nr) {
- struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
- for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; ++lchan_nr) {
- handle_sub(&ts->lchan[lchan_nr],
- network->bsc_data->mid_call_txt);
- }
- }
- }
- }
- return 0;
-}
-
-static int handle_rf_signal(unsigned int subsys, unsigned int signal,
- void *handler_data, void *signal_data)
-{
- struct rf_signal_data *sig;
-
- if (subsys != SS_RF)
- return -1;
-
- sig = signal_data;
-
- if (signal == S_RF_GRACE)
- handle_grace(sig->net);
-
- return 0;
-}
-
-static __attribute__((constructor)) void on_dso_load_grace(void)
-{
- osmo_signal_register_handler(SS_RF, handle_rf_signal, NULL);
+ return network->rf_ctrl->policy == S_RF_ON;
}
diff --git a/src/osmo-bsc/osmo_bsc_lcls.c b/src/osmo-bsc/osmo_bsc_lcls.c
index 26b3244ae..eab0be4d1 100644
--- a/src/osmo-bsc/osmo_bsc_lcls.c
+++ b/src/osmo-bsc/osmo_bsc_lcls.c
@@ -29,8 +29,9 @@
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/osmo_bsc_lcls.h>
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
-#include <osmocom/mgcp_client/mgcp_client_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
+#include <osmocom/bsc/lchan.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
struct value_string lcls_event_names[] = {
{ LCLS_EV_UPDATE_CFG_CSC, "UPDATE_CFG_CSC" },
@@ -42,19 +43,25 @@ struct value_string lcls_event_names[] = {
{ 0, NULL }
};
+const struct value_string bsc_lcls_mode_names[] = {
+ { BSC_LCLS_MODE_DISABLED, "disabled" },
+ { BSC_LCLS_MODE_MGW_LOOP, "mgw-loop" },
+ { BSC_LCLS_MODE_BTS_LOOP, "bts-loop" },
+ { 0, NULL }
+};
/***********************************************************************
* Utility functions
***********************************************************************/
-enum gsm0808_lcls_status lcls_get_status(struct gsm_subscriber_connection *conn)
+enum gsm0808_lcls_status lcls_get_status(const struct gsm_subscriber_connection *conn)
{
if (!conn->lcls.fi)
- return 0xff;
+ return GSM0808_LCLS_STS_NA;
switch (conn->lcls.fi->state) {
case ST_NO_LCLS:
- return 0xff;
+ return GSM0808_LCLS_STS_NA;
case ST_NOT_YET_LS:
return GSM0808_LCLS_STS_NOT_YET_LS;
case ST_NOT_POSSIBLE_LS:
@@ -76,7 +83,7 @@ static void lcls_send_notify(struct gsm_subscriber_connection *conn)
enum gsm0808_lcls_status status = lcls_get_status(conn);
struct msgb *msg;
- if (status == 0xff)
+ if (status == GSM0808_LCLS_STS_NA)
return;
LOGPFSM(conn->lcls.fi, "Sending BSSMAP LCLS NOTIFICATION (%s)\n",
@@ -86,7 +93,7 @@ static void lcls_send_notify(struct gsm_subscriber_connection *conn)
}
static struct gsm_subscriber_connection *
-find_conn_with_same_gcr(struct gsm_subscriber_connection *conn_local)
+find_conn_with_same_gcr(const struct gsm_subscriber_connection *conn_local)
{
struct gsm_network *net = conn_local->network;
struct gsm_subscriber_connection *conn_other;
@@ -159,7 +166,7 @@ static int lcls_perform_correlation(struct gsm_subscriber_connection *conn_local
/* Update the connections LCLS configuration and return old/previous configuration.
* \returns (staticallly allocated) old configuration; NULL if new config not supported */
static struct osmo_lcls *update_lcls_cfg_csc(struct gsm_subscriber_connection *conn,
- struct osmo_lcls *new_cfg_csc)
+ const struct osmo_lcls *new_cfg_csc)
{
static struct osmo_lcls old_cfg_csc = { 0 };
old_cfg_csc.config = conn->lcls.config;
@@ -237,13 +244,19 @@ static inline void lcls_rsl(const struct gsm_subscriber_connection *conn, bool e
uint32_t ip = enable ? conn->lcls.other->lchan->abis_ip.bound_ip : lchan->abis_ip.connect_ip;
/* RSL_IE_IPAC_REMOTE_PORT */
uint16_t port = enable ? conn->lcls.other->lchan->abis_ip.bound_port : lchan->abis_ip.connect_port;
+ struct msgb *msg;
if (!conn->lcls.other) {
LOGPFSM(conn->lcls.fi, "%s LCLS: other conn is not available!\n", enable ? "enable" : "disable");
return;
}
- abis_rsl_sendmsg(rsl_make_ipacc_mdcx(lchan, ip, port));
+ msg = rsl_make_ipacc_mdcx(lchan, ip, port);
+ if (!msg) {
+ LOGPFSML(conn->lcls.fi, LOGL_ERROR, "Error encoding IPACC MDCX\n");
+ return;
+ }
+ abis_rsl_sendmsg(msg);
}
static inline bool lcls_check_toggle_allowed(const struct gsm_subscriber_connection *conn, bool enable)
@@ -271,13 +284,13 @@ static inline void lcls_mdcx(const struct gsm_subscriber_connection *conn, struc
{
mgcp_pick_codec(mdcx_info, conn->lchan, false);
- mgw_endpoint_ci_request(conn->user_plane.mgw_endpoint_ci_msc, MGCP_VERB_MDCX, mdcx_info,
+ osmo_mgcpc_ep_ci_request(conn->user_plane.mgw_endpoint_ci_msc, MGCP_VERB_MDCX, mdcx_info,
NULL, 0, 0, NULL);
}
static void lcls_break_local_switching(struct gsm_subscriber_connection *conn)
{
- struct mgcp_conn_peer mdcx_info;
+ struct mgcp_conn_peer mdcx_info = {};
LOGPFSM(conn->lcls.fi, "=== HERE IS WHERE WE DISABLE LCLS(%s)\n",
bsc_lcls_mode_name(conn->sccp.msc->lcls_mode));
@@ -303,7 +316,7 @@ static void lcls_break_local_switching(struct gsm_subscriber_connection *conn)
}
}
-static bool lcls_enable_possible(struct gsm_subscriber_connection *conn)
+static bool lcls_enable_possible(const struct gsm_subscriber_connection *conn)
{
struct gsm_subscriber_connection *other_conn = conn->lcls.other;
OSMO_ASSERT(other_conn);
@@ -339,15 +352,14 @@ static bool lcls_enable_possible(struct gsm_subscriber_connection *conn)
return false;
}
- if (conn->lchan->tch_mode != conn->lcls.other->lchan->tch_mode
+ if (conn->lchan->current_ch_mode_rate.chan_mode != conn->lcls.other->lchan->current_ch_mode_rate.chan_mode
&& conn->sccp.msc->lcls_codec_mismatch_allow == false) {
LOGPFSM(conn->lcls.fi,
"Not enabling LS due to TCH-mode mismatch: %s:%s != %s:%s\n",
gsm_lchan_name(conn->lchan),
- gsm48_chan_mode_name(conn->lchan->tch_mode),
+ gsm48_chan_mode_name(conn->lchan->current_ch_mode_rate.chan_mode),
gsm_lchan_name(conn->lcls.other->lchan),
- gsm48_chan_mode_name(conn->lcls.other->lchan->
- tch_mode));
+ gsm48_chan_mode_name(conn->lcls.other->lchan->current_ch_mode_rate.chan_mode));
return false;
}
@@ -382,9 +394,9 @@ static void lcls_no_lcls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0)
return;
- return;
+ break;
case LCLS_EV_APPLY_CFG_CSC:
- if (conn->lcls.config == 0xff)
+ if (conn->lcls.config == GSM0808_LCLS_CFG_NA)
return;
if (lcls_perform_correlation(conn) != 0) {
/* Correlation leads to no result: Not Possible to LS */
@@ -420,7 +432,7 @@ static void lcls_not_yet_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *d
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0)
return;
- return;
+ break;
case LCLS_EV_APPLY_CFG_CSC:
if (lcls_enable_possible(conn)) {
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0);
@@ -465,7 +477,7 @@ static void lcls_not_possible_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, vo
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0)
return;
- return;
+ break;
case LCLS_EV_APPLY_CFG_CSC:
if (lcls_perform_correlation(conn) != 0) {
/* no correlation result: Remain in NOT_POSSIBLE_LS */
@@ -550,7 +562,7 @@ static void lcls_req_lcls_not_supp_fn(struct osmo_fsm_inst *fi, uint32_t event,
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0)
return;
- //FIXME osmo_fsm_inst_state_chg(fi,
+ //FIXME osmo_fsm_inst_state_chg(fi,
return;
case LCLS_EV_APPLY_CFG_CSC:
if (lcls_perform_correlation(conn) != 0) {
@@ -641,7 +653,7 @@ static void lcls_locally_switched_onenter(struct osmo_fsm_inst *fi, uint32_t pre
return;
}
- other_mgw_info = mgwep_ci_get_rtp_info(conn_other->user_plane.mgw_endpoint_ci_msc);
+ other_mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(conn_other->user_plane.mgw_endpoint_ci_msc);
if (!other_mgw_info) {
LOGPFSML(fi, LOGL_ERROR, "Cannot enable LCLS without RTP port info of MSC-side"
" -- missing CRCX?\n");
@@ -737,7 +749,7 @@ static void lcls_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause
struct gsm_subscriber_connection *conn = fi->priv;
if (conn->lcls.other) {
- /* inform the "other" side that we're dead, so it can disabe LS and send NOTIFY */
+ /* inform the "other" side that we're dead, so it can disable LS and send NOTIFY */
if (conn->lcls.other->fi)
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_DEAD, conn);
conn->lcls.other = NULL;
@@ -864,35 +876,3 @@ struct osmo_fsm lcls_fsm = {
.log_subsys = DLCLS,
.event_names = lcls_event_names,
};
-
-/* Add the LCLS BSS Status IE to a BSSMAP message. We assume this is
- * called on a msgb that was returned by gsm0808_create_ass_compl() */
-static void bssmap_add_lcls_status(struct msgb *msg, enum gsm0808_lcls_status status)
-{
- OSMO_ASSERT(msg->l3h[0] == BSSAP_MSG_BSS_MANAGEMENT);
- OSMO_ASSERT(msg->l3h[2] == BSS_MAP_MSG_ASSIGMENT_COMPLETE ||
- msg->l3h[2] == BSS_MAP_MSG_HANDOVER_RQST_ACKNOWLEDGE ||
- msg->l3h[2] == BSS_MAP_MSG_HANDOVER_COMPLETE ||
- msg->l3h[2] == BSS_MAP_MSG_HANDOVER_PERFORMED);
- OSMO_ASSERT(msgb_tailroom(msg) >= 2);
-
- /* append IE to end of message */
- msgb_tv_put(msg, GSM0808_IE_LCLS_BSS_STATUS, status);
- /* increment the "length" byte in the BSSAP header */
- msg->l3h[1] += 2;
-}
-
-/* Add (append) the LCLS BSS Status IE to a BSSMAP message, if there is any LCLS
- * active on the given \a conn */
-void bssmap_add_lcls_status_if_needed(struct gsm_subscriber_connection *conn, struct msgb *msg)
-{
- enum gsm0808_lcls_status status = lcls_get_status(conn);
- if (status != 0xff) {
- LOGPFSM(conn->fi, "Adding LCLS BSS-Status (%s) to %s\n",
- gsm0808_lcls_status_name(status),
- gsm0808_bssmap_name(msg->l3h[2]));
- bssmap_add_lcls_status(msg, status);
- }
-}
-
-
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
index 08bb40d78..8da319993 100644
--- a/src/osmo-bsc/osmo_bsc_main.c
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -33,11 +33,13 @@
#include <osmocom/bsc/handover_decision_2.h>
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
-#include <osmocom/bsc/mgw_endpoint_fsm.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/lb.h>
+#include <osmocom/bsc/meas_feed.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_if.h>
@@ -53,6 +55,10 @@
#include <osmocom/vty/ports.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/command.h>
+#include <osmocom/vty/cpu_sched_vty.h>
+
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
+#include <osmocom/mgcp_client/mgcp_client_pool.h>
#include <osmocom/abis/abis.h>
#include <osmocom/bsc/abis_om2000.h>
@@ -61,8 +67,12 @@
#include <osmocom/bsc/chan_alloc.h>
#include <osmocom/bsc/e1_config.h>
#include <osmocom/bsc/codec_pref.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bsc_stats.h>
#include <osmocom/mgcp_client/mgcp_client.h>
+#include <osmocom/mgcp_client/mgcp_client_pool.h>
#include <osmocom/sigtran/xua_msg.h>
@@ -78,25 +88,18 @@
#include "../../bscconfig.h"
-struct gsm_network *bsc_gsmnet = 0;
static const char *config_file = "osmo-bsc.cfg";
static const char *rf_ctrl = NULL;
static int daemonize = 0;
-static LLIST_HEAD(access_lists);
-
-struct llist_head *bsc_access_lists(void)
-{
- return &access_lists;
-}
-static void print_usage()
+static void print_usage(void)
{
printf("Usage: osmo-bsc\n");
}
-static void print_help()
+static void print_help(void)
{
- printf(" Some useful help...\n");
+ printf("Some useful options:\n");
printf(" -h --help This text.\n");
printf(" -D --daemonize Fork the process into a background daemon.\n");
printf(" -d --debug option --debug=DRLL:DMM:DRR:DRSL:DNM enable debugging.\n");
@@ -104,16 +107,44 @@ static void print_help()
printf(" -T --timestamp Print a timestamp in the debug output.\n");
printf(" -V --version Print the version of OsmoBSC.\n");
printf(" -c --config-file filename The config file to use.\n");
- printf(" -l --local IP The local address of the MGCP.\n");
printf(" -e --log-level number Set a global loglevel.\n");
printf(" -r --rf-ctl NAME A unix domain socket to listen for cmds.\n");
- printf(" -t --testmode A special mode to provoke failures at the MSC.\n");
+
+ printf("\nVTY reference generation:\n");
+ printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n");
+ printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n");
+}
+
+static void handle_long_options(const char *prog_name, const int long_option)
+{
+ static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
+
+ switch (long_option) {
+ case 1:
+ vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
+ if (vty_ref_mode < 0) {
+ fprintf(stderr, "%s: Unknown VTY reference generation "
+ "mode '%s'\n", prog_name, optarg);
+ exit(2);
+ }
+ break;
+ case 2:
+ fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
+ get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
+ get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
+ vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
+ exit(0);
+ default:
+ fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
+ exit(2);
+ }
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
+ static int long_option = 0;
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"debug", 1, 0, 'd'},
@@ -122,14 +153,14 @@ static void handle_options(int argc, char **argv)
{"disable-color", 0, 0, 's'},
{"timestamp", 0, 0, 'T'},
{"version", 0, 0, 'V' },
- {"local", 1, 0, 'l'},
{"log-level", 1, 0, 'e'},
{"rf-ctl", 1, 0, 'r'},
- {"testmode", 0, 0, 't'},
+ {"vty-ref-mode", 1, &long_option, 1},
+ {"vty-ref-xml", 0, &long_option, 2},
{0, 0, 0, 0}
};
- c = getopt_long(argc, argv, "hd:DsTVc:e:r:t",
+ c = getopt_long(argc, argv, "hd:DsTVc:e:r:",
long_options, &option_index);
if (c == -1)
break;
@@ -139,6 +170,9 @@ static void handle_options(int argc, char **argv)
print_usage();
print_help();
exit(0);
+ case 0:
+ handle_long_options(argv[0], long_option);
+ break;
case 's':
log_set_use_color(osmo_stderr_target, 0);
break;
@@ -165,10 +199,16 @@ static void handle_options(int argc, char **argv)
rf_ctrl = optarg;
break;
default:
- /* ignore */
- break;
+ /* catch unknown options *as well as* missing arguments. */
+ fprintf(stderr, "Error in command line options. Exiting.\n");
+ exit(-1);
}
}
+
+ if (argc > optind) {
+ fprintf(stderr, "Unsupported positional arguments on command line\n");
+ exit(2);
+ }
}
/* Callback function for NACK on the OML NM */
@@ -192,7 +232,7 @@ static int oml_msg_nack(struct nm_nack_signal_data *nack)
return 0;
}
- if (is_ipaccess_bts(nack->bts))
+ if (is_ipa_abisip_bts(nack->bts))
ipaccess_drop_oml_deferred(nack->bts);
return 0;
@@ -215,75 +255,74 @@ static int nm_sig_cb(unsigned int subsys, unsigned int signal,
}
/* Produce a MA as specified in 10.5.2.21 */
-static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts)
+static void generate_ma_for_ts(struct gsm_bts_trx_ts *ts)
{
/* we have three bitvecs: the per-timeslot ARFCNs, the cell chan ARFCNs
* and the MA */
- struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc;
- struct bitvec *ts_arfcn = &ts->hopping.arfcns;
+ const size_t num_cell_arfcns = ts->trx->bts->si_common.cell_chan_num;
+ const struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc;
+ const struct bitvec *ts_arfcn = &ts->hopping.arfcns;
struct bitvec *ma = &ts->hopping.ma;
- unsigned int num_cell_arfcns, bitnum, n_chan;
int i;
/* re-set the MA to all-zero */
- ma->cur_bit = 0;
ts->hopping.ma_len = 0;
memset(ma->data, 0, ma->data_len);
if (!ts->hopping.enabled)
- return 0;
-
- /* count the number of ARFCNs in the cell channel allocation */
- num_cell_arfcns = 0;
- for (i = 0; i < 1024; i++) {
- if (bitvec_get_bit_pos(cell_chan, i))
- num_cell_arfcns++;
- }
+ return;
/* pad it to octet-aligned number of bits */
- ts->hopping.ma_len = num_cell_arfcns / 8;
- if (num_cell_arfcns % 8)
- ts->hopping.ma_len++;
+ ts->hopping.ma_len = OSMO_BYTES_FOR_BITS(num_cell_arfcns);
+ ma->cur_bit = (ts->hopping.ma_len * 8) - 1;
- n_chan = 0;
- for (i = 0; i < 1024; i++) {
+ for (i = 1; i < 1024; i++) {
if (!bitvec_get_bit_pos(cell_chan, i))
continue;
/* set the corresponding bit in the MA */
- bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan;
if (bitvec_get_bit_pos(ts_arfcn, i))
- bitvec_set_bit_pos(ma, bitnum, 1);
+ bitvec_set_bit_pos(ma, ma->cur_bit, 1);
else
- bitvec_set_bit_pos(ma, bitnum, 0);
- n_chan++;
+ bitvec_set_bit_pos(ma, ma->cur_bit, 0);
+ ma->cur_bit--;
}
/* ARFCN 0 is special: It is coded last in the bitmask */
if (bitvec_get_bit_pos(cell_chan, 0)) {
- n_chan++;
/* set the corresponding bit in the MA */
- bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan;
if (bitvec_get_bit_pos(ts_arfcn, 0))
- bitvec_set_bit_pos(ma, bitnum, 1);
+ bitvec_set_bit_pos(ma, ma->cur_bit, 1);
else
- bitvec_set_bit_pos(ma, bitnum, 0);
+ bitvec_set_bit_pos(ma, ma->cur_bit, 0);
}
+}
- return 0;
+static void generate_ma_for_bts(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ unsigned int tn;
+
+ OSMO_ASSERT(bts->si_common.cell_chan_num > 0);
+ OSMO_ASSERT(bts->si_common.cell_chan_num <= 64);
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++)
+ generate_ma_for_ts(&trx->ts[tn]);
+ }
}
static void bootstrap_rsl(struct gsm_bts_trx *trx)
{
+ struct gsm_bts *bts = trx->bts;
unsigned int i;
+ int rc;
- LOGP(DRSL, LOGL_NOTICE, "bootstrapping RSL for BTS/TRX (%u/%u) "
+ LOG_TRX(trx, DRSL, LOGL_NOTICE, "bootstrapping RSL "
"on ARFCN %u using MCC-MNC %s LAC=%u CID=%u BSIC=%u\n",
- trx->bts->nr, trx->nr, trx->arfcn,
- osmo_plmn_name(&bsc_gsmnet->plmn),
- trx->bts->location_area_code,
- trx->bts->cell_identity, trx->bts->bsic);
+ trx->arfcn, osmo_plmn_name(&bsc_gsmnet->plmn),
+ bts->location_area_code, bts->cell_identity, bts->bsic);
- if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) {
+ if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) {
rsl_nokia_si_begin(trx);
}
@@ -292,33 +331,121 @@ static void bootstrap_rsl(struct gsm_bts_trx *trx)
* This ensures that RACH control in system information is configured correctly.
* TRX 0 should be usable and unlocked, otherwise starting ACC ramping is pointless.
*/
- if (trx_is_usable(trx) && trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
+ if (trx_is_usable(trx))
acc_ramp_trigger(&trx->bts->acc_ramp);
- gsm_bts_trx_set_system_infos(trx);
+ if (gsm_bts_trx_set_system_infos(trx) != 0) {
+ LOG_TRX(trx, DRSL, LOGL_ERROR, "Failed to generate System Information\n");
+ return;
+ }
- if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) {
+ if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) {
/* channel unspecific, power reduction in 2 dB steps */
rsl_bs_power_control(trx, 0xFF, trx->max_power_red / 2);
rsl_nokia_si_end(trx);
}
+ if (bts->model->power_ctrl_send_def_params != NULL) {
+ rc = bts->model->power_ctrl_send_def_params(trx);
+ if (rc) {
+ LOG_TRX(trx, DRSL, LOGL_ERROR, "Failed to send default "
+ "MS/BS Power control parameters (rc=%d)\n", rc);
+ /* TODO: should we drop RSL connection here? */
+ }
+ }
+
for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
struct gsm_bts_trx_ts *ts = &trx->ts[i];
- generate_ma_for_ts(ts);
OSMO_ASSERT(ts->fi);
osmo_fsm_inst_dispatch(ts->fi, TS_EV_RSL_READY, NULL);
}
+
+ /* Drop all expired channel requests in the list */
+ abis_rsl_chan_rqd_queue_flush(bts);
+}
+
+struct osmo_timer_list update_connection_stats_timer;
+
+/* Periodically call bsc_update_connection_stats() to keep stat items updated.
+ * It would be nicer to trigger this only when OML or RSL state is seen to flip. I tried hard to find all code paths
+ * that should call this and failed to get accurate results; this trivial timer covers all of them. */
+static void update_connection_stats_cb(void *data)
+{
+ bsc_update_connection_stats(bsc_gsmnet);
+ osmo_timer_setup(&update_connection_stats_timer, update_connection_stats_cb, NULL);
+ osmo_timer_schedule(&update_connection_stats_timer, 1, 0);
+}
+
+static bool nch_position_compatible_with_combined_ccch(const struct gsm_bts *bts)
+{
+ switch (bts->nch.num_blocks) {
+ case 0:
+ /* no NCH enabled, so we are fine */
+ return true;
+ case 1:
+ if (bts->nch.first_block == 0 || bts->nch.first_block == 1)
+ return true;
+ break;
+ case 2:
+ if (bts->nch.first_block == 0)
+ return true;
+ break;
+ default:
+ break;
+ }
+
+ /* anything else is not permitted */
+ return false;
}
-static void all_ts_dispatch_event(struct gsm_bts_trx *trx, uint32_t event)
+static void bootstrap_bts(struct gsm_bts *bts)
{
- int ts_i;
- for (ts_i = 0; ts_i < ARRAY_SIZE(trx->ts); ts_i++) {
- struct gsm_bts_trx_ts *ts = &trx->ts[ts_i];
- if (ts->fi)
- osmo_fsm_inst_dispatch(ts->fi, event, 0);
+ unsigned int n = 0;
+
+ /* Control Channel Description is set from vty/config */
+
+ /* Determine the value of CCCH_CONF. Is TS0/C0 combined? */
+ if (bts->c0->ts[0].pchan_from_config != GSM_PCHAN_CCCH) {
+ bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C;
+
+ /* Limit reserved block to 2 on combined channel according to
+ 3GPP TS 44.018 Table 10.5.2.11.1 */
+ if (bts->si_common.chan_desc.bs_ag_blks_res > 2) {
+ LOGP(DNM, LOGL_NOTICE, "CCCH is combined with SDCCHs, "
+ "reducing BS-AG-BLKS-RES value %d -> 2\n",
+ bts->si_common.chan_desc.bs_ag_blks_res);
+ bts->si_common.chan_desc.bs_ag_blks_res = 2;
+ }
+
+ if (!nch_position_compatible_with_combined_ccch(bts)) {
+ LOG_BTS(bts, DNM, LOGL_ERROR, "CCCH is combined with SDCCHs, but NCH position/size is "
+ "incompatible with that. Please fix your config!\n");
+ }
+ } else { /* Non-combined TS0/C0 configuration */
+ /* There can be additional CCCHs on even timeslot numbers */
+ n += (bts->c0->ts[2].pchan_from_config == GSM_PCHAN_CCCH);
+ n += (bts->c0->ts[4].pchan_from_config == GSM_PCHAN_CCCH);
+ n += (bts->c0->ts[6].pchan_from_config == GSM_PCHAN_CCCH);
+ bts->si_common.chan_desc.ccch_conf = (n << 1);
}
+
+ if (bts->nch.first_block + bts->nch.num_blocks > bts->si_common.chan_desc.bs_ag_blks_res) {
+ LOG_BTS(bts, DNM, LOGL_ERROR, "Position/Number of NCH blocks (%u..%u) exceeds AGCH (%u)."
+ "Please fix your config!\n", bts->nch.first_block,
+ bts->nch.first_block + bts->nch.num_blocks - 1,
+ bts->si_common.chan_desc.bs_ag_blks_res);
+ }
+
+ bts_setup_ramp_init_bts(bts);
+
+ /* ACC ramping is initialized from vty/config */
+
+ /* Initialize the BTS state */
+ gsm_bts_sm_mo_reset(bts->site_mgr);
+
+ /* Generate Mobile Allocation bit-masks for all timeslots.
+ * This needs to be done here, because it's used for TS configuration. */
+ generate_ma_for_bts(bts);
}
/* Callback function to be called every time we receive a signal from INPUT */
@@ -327,13 +454,7 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
{
struct input_signal_data *isd = signal_data;
struct gsm_bts_trx *trx = isd->trx;
- /* N. B: we rely on attribute order when parsing response in abis_nm_rx_get_attr_resp() */
- const uint8_t bts_attr[] = { NM_ATT_MANUF_ID, NM_ATT_SW_CONFIG, };
- const uint8_t trx_attr[] = { NM_ATT_MANUF_STATE, NM_ATT_SW_CONFIG, };
-
- /* we should not request more attributes than we're ready to handle */
- OSMO_ASSERT(sizeof(bts_attr) < MAX_BTS_ATTR);
- OSMO_ASSERT(sizeof(trx_attr) < MAX_BTS_ATTR);
+ int rc;
if (subsys != SS_L_INPUT)
return -EINVAL;
@@ -343,44 +464,43 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
switch (signal) {
case S_L_INP_TEI_UP:
if (isd->link_type == E1INP_SIGN_OML) {
- /* TODO: this is required for the Nokia BTS, hopping is configured
- during OML, other MA is not set. */
- struct gsm_bts_trx *cur_trx;
- /* was static in system_information.c */
- extern int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts);
- uint8_t ca[20];
- /* has to be called before generate_ma_for_ts to
- set bts->si_common.cell_alloc */
- generate_cell_chan_list(ca, trx->bts);
-
- /* Request generic BTS-level attributes */
- abis_nm_get_attr(trx->bts, NM_OC_BTS, 0xFF, 0xFF, 0xFF, bts_attr, sizeof(bts_attr));
-
- llist_for_each_entry(cur_trx, &trx->bts->trx_list, list) {
- int i;
- /* Request TRX-level attributes */
- abis_nm_get_attr(cur_trx->bts, NM_OC_BASEB_TRANSC, 0, cur_trx->nr, 0xFF,
- trx_attr, sizeof(trx_attr));
- for (i = 0; i < ARRAY_SIZE(cur_trx->ts); i++)
- generate_ma_for_ts(&cur_trx->ts[i]);
+ /* Check parameters and apply vty config dependent parameters */
+ rc = gsm_bts_check_cfg(trx->bts);
+ if (rc < 0) {
+ LOGP(DNM, LOGL_ERROR, "(bts=%u) Error in BTS configuration -- cannot bootstrap BTS\n",
+ trx->bts->nr);
+ return rc;
}
+ bootstrap_bts(trx->bts);
}
- if (isd->link_type == E1INP_SIGN_RSL)
+ if (isd->link_type == E1INP_SIGN_RSL) {
+ rc = gsm_bts_check_cfg(trx->bts);
+ if (rc < 0) {
+ LOGP(DNM, LOGL_ERROR, "(bts=%u) Error in BTS configuration -- cannot bootstrap RSL\n",
+ trx->bts->nr);
+ return rc;
+ }
bootstrap_rsl(trx);
+ }
break;
case S_L_INP_TEI_DN:
- LOGP(DLMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", isd->link_type, trx);
+ LOG_TRX(trx, DLMI, LOGL_ERROR, "Lost E1 %s link\n", e1inp_signtype_name(isd->link_type));
if (isd->link_type == E1INP_SIGN_OML) {
- rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_OML_FAIL]);
- all_ts_dispatch_event(trx, TS_EV_OML_DOWN);
+ rate_ctr_inc(rate_ctr_group_get_ctr(trx->bts->bts_ctrs, BTS_CTR_BTS_OML_FAIL));
+ /* ip.access BTS models have a single global A-bis/OML link for all
+ * transceivers, so once it's lost we need to notify them all. */
+ if (is_ipa_abisip_bts(trx->bts))
+ gsm_bts_all_ts_dispatch(trx->bts, TS_EV_OML_DOWN, NULL);
+ else /* Other BTS models (e.g. Ericsson) have per-TRX OML links */
+ gsm_trx_all_ts_dispatch(trx, TS_EV_OML_DOWN, NULL);
} else if (isd->link_type == E1INP_SIGN_RSL) {
- rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(trx->bts->bts_ctrs, BTS_CTR_BTS_RSL_FAIL));
acc_ramp_abort(&trx->bts->acc_ramp);
- all_ts_dispatch_event(trx, TS_EV_RSL_DOWN);
+ gsm_trx_all_ts_dispatch(trx, TS_EV_RSL_DOWN, NULL);
}
- gsm_bts_mo_reset(trx->bts);
+ gsm_bts_sm_mo_reset(trx->bts->site_mgr);
abis_nm_clear_queue(trx->bts);
break;
@@ -391,108 +511,6 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
return 0;
}
-static int bootstrap_bts(struct gsm_bts *bts)
-{
- int i, n;
-
- if (!bts->model)
- return -EFAULT;
-
- if (bts->model->start && !bts->model->started) {
- int ret = bts->model->start(bts->network);
- if (ret < 0)
- return ret;
-
- bts->model->started = true;
- }
-
- /* FIXME: What about secondary TRX of a BTS? What about a BTS that has TRX
- * in different bands? Why is 'band' a parameter of the BTS and not of the TRX? */
- switch (bts->band) {
- case GSM_BAND_1800:
- if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) {
- LOGP(DNM, LOGL_ERROR, "GSM1800 channel must be between 512-885.\n");
- return -EINVAL;
- }
- break;
- case GSM_BAND_1900:
- if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) {
- LOGP(DNM, LOGL_ERROR, "GSM1900 channel must be between 512-810.\n");
- return -EINVAL;
- }
- break;
- case GSM_BAND_900:
- if ((bts->c0->arfcn > 124 && bts->c0->arfcn < 955) ||
- bts->c0->arfcn > 1023) {
- LOGP(DNM, LOGL_ERROR, "GSM900 channel must be between 0-124, 955-1023.\n");
- return -EINVAL;
- }
- break;
- case GSM_BAND_850:
- if (bts->c0->arfcn < 128 || bts->c0->arfcn > 251) {
- LOGP(DNM, LOGL_ERROR, "GSM850 channel must be between 128-251.\n");
- return -EINVAL;
- }
- break;
- default:
- LOGP(DNM, LOGL_ERROR, "Unsupported frequency band.\n");
- return -EINVAL;
- }
-
- /* Control Channel Description is set from vty/config */
-
- /* Set ccch config by looking at ts config */
- for (n=0, i=0; i<8; i++)
- n += bts->c0->ts[i].pchan_is == GSM_PCHAN_CCCH ? 1 : 0;
-
- /* Indicate R99 MSC in SI3 */
- bts->si_common.chan_desc.mscr = 1;
-
- switch (n) {
- case 0:
- bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C;
- /* Limit reserved block to 2 on combined channel according to
- 3GPP TS 44.018 Table 10.5.2.11.1 */
- if (bts->si_common.chan_desc.bs_ag_blks_res > 2) {
- LOGP(DNM, LOGL_NOTICE, "CCCH is combined with SDCCHs, "
- "reducing BS-AG-BLKS-RES value %d -> 2\n",
- bts->si_common.chan_desc.bs_ag_blks_res);
- bts->si_common.chan_desc.bs_ag_blks_res = 2;
- }
- break;
- case 1:
- bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC;
- break;
- case 2:
- bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC;
- break;
- case 3:
- bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC;
- break;
- case 4:
- bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC;
- break;
- default:
- LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n");
- return -EINVAL;
- }
-
- bts->si_common.cell_options.pwrc = 0; /* PWRC not set */
-
- bts->si_common.cell_sel_par.acs = 0;
-
- bts->si_common.ncc_permitted = 0xff;
-
- bts->chan_load_samples_idx = 0;
-
- /* ACC ramping is initialized from vty/config */
-
- /* Initialize the BTS state */
- gsm_bts_mo_reset(bts);
-
- return 0;
-}
-
static int bsc_network_configure(const char *config_file)
{
struct gsm_bts *bts;
@@ -500,13 +518,12 @@ static int bsc_network_configure(const char *config_file)
rc = vty_read_config_file(config_file, NULL);
if (rc < 0) {
- LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s'\n", config_file);
+ LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s' (%s)\n", config_file, strerror(-rc));
return rc;
}
/* start telnet after reading config for vty_get_bind_addr() */
- rc = telnet_init_dynif(tall_bsc_ctx, bsc_gsmnet, vty_get_bind_addr(),
- OSMO_VTY_PORT_NITB_BSC);
+ rc = telnet_init_default(tall_bsc_ctx, bsc_gsmnet, OSMO_VTY_PORT_NITB_BSC);
if (rc < 0)
return rc;
@@ -514,11 +531,12 @@ static int bsc_network_configure(const char *config_file)
osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);
llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
- rc = bootstrap_bts(bts);
+ rc = gsm_bts_check_cfg(bts);
if (rc < 0) {
- LOGP(DNM, LOGL_FATAL, "Error bootstrapping BTS\n");
+ LOGP(DNM, LOGL_FATAL, "(bts=%u) cannot bootstrap BTS, invalid BTS configuration\n", bts->nr);
return rc;
}
+ bootstrap_bts(bts);
rc = e1_reconfig_bts(bts);
if (rc < 0) {
LOGP(DNM, LOGL_FATAL, "Error enabling E1 input driver\n");
@@ -536,6 +554,11 @@ static int bsc_vty_go_parent(struct vty *vty)
vty->node = CONFIG_NODE;
vty->index = NULL;
break;
+ case MGW_NODE:
+ vty->node = GSMNET_NODE;
+ vty->index = bsc_gsmnet;
+ vty->index_sub = NULL;
+ break;
case BTS_NODE:
vty->node = GSMNET_NODE;
{
@@ -545,6 +568,21 @@ static int bsc_vty_go_parent(struct vty *vty)
vty->index_sub = NULL;
}
break;
+ case POWER_CTRL_NODE:
+ vty->node = BTS_NODE;
+ {
+ const struct gsm_power_ctrl_params *cp = vty->index;
+ struct gsm_bts *bts;
+
+ if (cp->dir == GSM_PWR_CTRL_DIR_UL)
+ bts = container_of(cp, struct gsm_bts, ms_power_ctrl);
+ else
+ bts = container_of(cp, struct gsm_bts, bs_power_ctrl);
+
+ vty->index_sub = &bts->description;
+ vty->index = bts;
+ }
+ break;
case TRX_NODE:
vty->node = BTS_NODE;
{
@@ -560,7 +598,6 @@ static int bsc_vty_go_parent(struct vty *vty)
/* set vty->index correctly ! */
struct gsm_bts_trx_ts *ts = vty->index;
vty->index = ts->trx;
- vty->index_sub = &ts->trx->description;
}
break;
case OML_NODE:
@@ -618,38 +655,63 @@ static struct vty_app_info vty_info = {
.copyright =
"Copyright (C) 2008-2018 Harald Welte, Holger Freyther\r\n"
"Contributions by Daniel Willmann, Jan Lรผbbe, Stefan Schmidt\r\n"
- "Dieter Spaar, Andreas Eversberg, Sylvain Munaut, Neels Hofmeyr\r\n\r\n"
+ "Dieter Spaar, Andreas Eversberg, Sylvain Munaut, Neels Hofmeyr\r\n"
+ "Copyright (C) 2013-2022 sysmocom - s.f.m.c. GmbH\r\n"
+ "\r\n"
"License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
"This is free software: you are free to change and redistribute it.\r\n"
"There is NO WARRANTY, to the extent permitted by law.\r\n",
.version = PACKAGE_VERSION,
.go_parent_cb = bsc_vty_go_parent,
.is_config_node = bsc_vty_is_config_node,
+ .usr_attr_desc = {
+ [BSC_VTY_ATTR_RESTART_ABIS_OML_LINK] = \
+ "This command applies on A-bis OML link (re)establishment",
+ [BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK] = \
+ "This command applies on A-bis RSL link (re)establishment",
+ [BSC_VTY_ATTR_NEW_LCHAN] = \
+ "This command applies for newly created lchans",
+ [BSC_VTY_ATTR_VENDOR_SPECIFIC] = \
+ "This command/parameter is BTS vendor specific",
+ },
+ .usr_attr_letters = {
+ [BSC_VTY_ATTR_RESTART_ABIS_OML_LINK] = 'o',
+ [BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK] = 'r',
+ [BSC_VTY_ATTR_NEW_LCHAN] = 'l',
+ [BSC_VTY_ATTR_VENDOR_SPECIFIC] = 'v',
+ },
};
extern int bsc_shutdown_net(struct gsm_network *net);
-static void signal_handler(int signal)
+static void signal_handler(int signum)
{
- fprintf(stdout, "signal %u received\n", signal);
+ fprintf(stdout, "signal %u received\n", signum);
- switch (signal) {
+ switch (signum) {
case SIGINT:
case SIGTERM:
+ /* If SIGTERM was already sent before, just terminate immediately. */
+ if (osmo_select_shutdown_requested())
+ exit(-1);
bsc_shutdown_net(bsc_gsmnet);
osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL);
- sleep(3);
- exit(0);
+ osmo_select_shutdown_request();
break;
case SIGABRT:
- /* in case of abort, we want to obtain a talloc report
- * and then return to the caller, who will abort the process */
- case SIGUSR1:
+ /* in case of abort, we want to obtain a talloc report and
+ * then run default SIGABRT handler, who will generate coredump
+ * and abort the process. abort() should do this for us after we
+ * return, but program wouldn't exit if an external SIGABRT is
+ * received.
+ */
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(tall_bsc_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
break;
- case SIGUSR2:
- if (!bsc_gsmnet->bsc_data)
- return;
+ case SIGUSR1:
+ talloc_report(tall_vty_ctx, stderr);
+ talloc_report_full(tall_bsc_ctx, stderr);
break;
default:
break;
@@ -703,7 +765,7 @@ static const struct log_info_cat osmo_bsc_categories[] = {
.name = "DNM",
.description = "A-bis Network Management / O&M (NM/OML)",
.color = "\033[1;36m",
- .enabled = 1, .loglevel = LOGL_INFO,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DPAG] = {
.name = "DPAG",
@@ -738,11 +800,6 @@ static const struct log_info_cat osmo_bsc_categories[] = {
.description = "Reference Counting",
.enabled = 0, .loglevel = LOGL_NOTICE,
},
- [DNAT] = {
- .name = "DNAT",
- .description = "GSM 08.08 NAT/Multiplexer",
- .enabled = 1, .loglevel = LOGL_NOTICE,
- },
[DCTRL] = {
.name = "DCTRL",
.description = "Control interface",
@@ -751,19 +808,45 @@ static const struct log_info_cat osmo_bsc_categories[] = {
[DFILTER] = {
.name = "DFILTER",
.description = "BSC/NAT IMSI based filtering",
- .enabled = 1, .loglevel = LOGL_DEBUG,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DPCU] = {
.name = "DPCU",
.description = "PCU Interface",
- .enabled = 1, .loglevel = LOGL_DEBUG,
+ .enabled = 1, .loglevel = LOGL_NOTICE,
},
[DLCLS] = {
.name = "DLCLS",
.description = "Local Call, Local Switch",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
-
+ [DCBS] = {
+ .name = "DCBS",
+ .description = "Cell Broadcast System",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DLCS] = {
+ .name = "DLCS",
+ .description = "Location Services",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DASCI] = {
+ .name = "DASCI",
+ .description = "Advanced Speech Call Items (VGCS/VBS)",
+ .color = "\033[1;38m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DRESET] = {
+ .name = "DRESET",
+ .description = "RESET/ACK on A and Lb interfaces",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DLOOP] = {
+ .name = "DLOOP",
+ .description = "Control loops",
+ .color = "\033[0;34m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
};
static int filter_fn(const struct log_context *ctx, struct log_target *tar)
@@ -788,12 +871,49 @@ const struct log_info log_info = {
extern void *tall_paging_ctx;
extern void *tall_fle_ctx;
extern void *tall_tqe_ctx;
-extern void *tall_ctr_ctx;
+
+static int bsc_mgw_setup(void)
+{
+ struct mgcp_client *mgcp_client_single;
+ unsigned int pool_members_initalized;
+
+ /* Initialize MGW pool. This initializes and connects all MGCP clients that are currently configured in
+ * the pool. Adding additional MGCP clients to the pool is possible but the user has to configure and
+ * (re)connect them manually from the VTY. */
+ if (!mgcp_client_pool_empty(bsc_gsmnet->mgw.mgw_pool)) {
+ pool_members_initalized = mgcp_client_pool_connect(bsc_gsmnet->mgw.mgw_pool);
+ if (!pool_members_initalized) {
+ LOGP(DNM, LOGL_ERROR, "MGW pool failed to initialize any pool members\n");
+ return -EINVAL;
+ }
+ LOGP(DNM, LOGL_NOTICE,
+ "MGW pool with %u pool members configured, (ignoring MGW configuration in VTY node 'msc').\n",
+ pool_members_initalized);
+ return 0;
+ }
+
+ /* Initialize and connect a single MGCP client. This MGCP client will appear as the one and only pool
+ * member if there is no MGW pool configured. */
+ LOGP(DNM, LOGL_NOTICE, "No MGW pool configured, using MGW configuration in VTY node 'msc'\n");
+ mgcp_client_single = mgcp_client_init(bsc_gsmnet, bsc_gsmnet->mgw.conf);
+ if (!mgcp_client_single) {
+ LOGP(DNM, LOGL_ERROR, "MGW (single) client initialization failed\n");
+ return -EINVAL;
+ }
+ if (mgcp_client_connect(mgcp_client_single)) {
+ LOGP(DNM, LOGL_ERROR, "MGW (single) connect failed at (%s:%u)\n",
+ bsc_gsmnet->mgw.conf->remote_addr,
+ bsc_gsmnet->mgw.conf->remote_port);
+ return -EINVAL;
+ }
+ mgcp_client_pool_register_single(bsc_gsmnet->mgw.mgw_pool, mgcp_client_single);
+
+ return 0;
+}
int main(int argc, char **argv)
{
struct bsc_msc_data *msc;
- struct osmo_bsc_data *data;
int rc;
tall_bsc_ctx = talloc_named_const(NULL, 1, "osmo-bsc");
@@ -805,12 +925,14 @@ int main(int argc, char **argv)
tall_paging_ctx = talloc_named_const(tall_bsc_ctx, 0, "paging_request");
tall_fle_ctx = talloc_named_const(tall_bsc_ctx, 0, "bs11_file_list_entry");
tall_tqe_ctx = talloc_named_const(tall_bsc_ctx, 0, "subch_txq_entry");
- tall_ctr_ctx = talloc_named_const(tall_bsc_ctx, 0, "counter");
osmo_init_logging2(tall_bsc_ctx, &log_info);
osmo_stats_init(tall_bsc_ctx);
rate_ctr_init(tall_bsc_ctx);
+ osmo_fsm_set_dealloc_ctx(OTC_SELECT);
+ osmo_fsm_log_timeouts(true);
+
/* Allocate global gsm_network struct */
rc = bsc_network_alloc();
if (rc) {
@@ -818,8 +940,8 @@ int main(int argc, char **argv)
exit(1);
}
- bsc_gsmnet->mgw.conf = talloc_zero(bsc_gsmnet, struct mgcp_client_conf);
- mgcp_client_conf_init(bsc_gsmnet->mgw.conf);
+ bsc_gsmnet->mgw.conf = mgcp_client_conf_alloc(bsc_gsmnet);
+ bsc_gsmnet->mgw.mgw_pool = mgcp_client_pool_alloc(bsc_gsmnet);
bts_init();
libosmo_abis_init(tall_bsc_ctx);
@@ -829,13 +951,14 @@ int main(int argc, char **argv)
/* This needs to precede handle_options() */
vty_init(&vty_info);
bsc_vty_init(bsc_gsmnet);
- bsc_msg_acc_lst_vty_init(tall_bsc_ctx, &access_lists, BSC_NODE);
ctrl_vty_init(tall_bsc_ctx);
+ osmo_cpu_sched_vty_init(tall_bsc_ctx);
logging_vty_add_deprecated_subsys(tall_bsc_ctx, "cc");
logging_vty_add_deprecated_subsys(tall_bsc_ctx, "mgcp");
+ logging_vty_add_deprecated_subsys(tall_bsc_ctx, "nat");
- /* Initalize SS7 */
- osmo_ss7_init();
+ /* Initialize SS7 */
+ OSMO_ASSERT(osmo_ss7_init() == 0);
osmo_ss7_vty_init_asp(tall_bsc_ctx);
osmo_sccp_vty_init();
@@ -845,12 +968,11 @@ int main(int argc, char **argv)
/* seed the PRNG */
srand(time(NULL));
- ts_fsm_init();
- lchan_fsm_init();
- bsc_subscr_conn_fsm_init();
- assignment_fsm_init();
- mgw_endpoint_fsm_init(bsc_gsmnet->T_defs);
- handover_fsm_init();
+ lb_init();
+ acc_ramp_global_init();
+ paging_global_init();
+ smscb_global_init();
+ meas_feed_txqueue_max_length_set(MEAS_FEED_TXQUEUE_MAX_LEN_DEFAULT);
/* Read the config */
rc = bsc_network_configure(config_file);
@@ -859,11 +981,14 @@ int main(int argc, char **argv)
exit(1);
}
+ if (neighbors_check_cfg()) {
+ fprintf(stderr, "Errors in neighbor configuration, check the DHO log. exiting.\n");
+ exit(1);
+ }
+
/* start control interface after reading config for
* ctrl_vty_get_bind_addr() */
- bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet,
- ctrl_vty_get_bind_addr(),
- OSMO_CTRL_PORT_NITB_BSC);
+ bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet, OSMO_CTRL_PORT_NITB_BSC);
if (!bsc_gsmnet->ctrl) {
fprintf(stderr, "Failed to init the control interface. Exiting.\n");
exit(1);
@@ -875,43 +1000,59 @@ int main(int argc, char **argv)
exit(1);
}
- data = bsc_gsmnet->bsc_data;
+ if (bsc_gsmnet->neigh_ctrl.addr) {
+ bsc_gsmnet->neigh_ctrl.handle = neighbor_controlif_setup(bsc_gsmnet);
+ if (!bsc_gsmnet->neigh_ctrl.handle) {
+ fprintf(stderr, "Failed to bind Neighbor Resolution Service. Exiting.\n");
+ exit(1);
+ }
+ rc = neighbor_ctrl_cmds_install(bsc_gsmnet);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to install Neighbor Resolution Service commands. Exiting.\n");
+ exit(1);
+ }
+ }
+
if (rf_ctrl)
- osmo_talloc_replace_string(data, &data->rf_ctrl_name, rf_ctrl);
+ osmo_talloc_replace_string(bsc_gsmnet, &bsc_gsmnet->rf_ctrl_name, rf_ctrl);
- data->rf_ctrl = osmo_bsc_rf_create(data->rf_ctrl_name, bsc_gsmnet);
- if (!data->rf_ctrl) {
+ bsc_gsmnet->rf_ctrl = osmo_bsc_rf_create(bsc_gsmnet->rf_ctrl_name, bsc_gsmnet);
+ if (!bsc_gsmnet->rf_ctrl) {
fprintf(stderr, "Failed to create the RF service.\n");
exit(1);
}
- rc = check_codec_pref(&bsc_gsmnet->bsc_data->mscs);
- if (rc < 0)
- LOGP(DMSC, LOGL_ERROR, "Configuration contains mutually exclusive codec settings -- check configuration!\n");
-
- llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) {
- if (osmo_bsc_msc_init(msc) != 0) {
- LOGP(DNAT, LOGL_ERROR, "Failed to start up. Exiting.\n");
+ rc = check_codec_pref(&bsc_gsmnet->mscs);
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Configuration contains mutually exclusive codec settings -- check"
+ " configuration!\n");
+ if (!bsc_gsmnet->allow_unusable_timeslots) {
+ LOGP(DMSC, LOGL_ERROR, "You should really fix that! However, you can prevent OsmoBSC from"
+ " stopping here by setting 'allow-unusable-timeslots' in the 'network'"
+ " section of the config.\n");
exit(1);
}
}
- bsc_gsmnet->mgw.client = mgcp_client_init(bsc_gsmnet, bsc_gsmnet->mgw.conf);
-
- if (mgcp_client_connect(bsc_gsmnet->mgw.client)) {
- LOGP(DNM, LOGL_ERROR, "MGW connect failed at (%s:%u)\n",
- bsc_gsmnet->mgw.conf->remote_addr,
- bsc_gsmnet->mgw.conf->remote_port);
+ if (bsc_mgw_setup() != 0)
exit(1);
+
+ llist_for_each_entry(msc, &bsc_gsmnet->mscs, entry) {
+ if (osmo_bsc_msc_init(msc) != 0) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to start up. Exiting.\n");
+ exit(1);
+ }
}
- if (osmo_bsc_sigtran_init(&bsc_gsmnet->bsc_data->mscs) != 0) {
- LOGP(DNM, LOGL_ERROR, "Failed to initalize sigtran backhaul.\n");
+ if (osmo_bsc_sigtran_init(&bsc_gsmnet->mscs) != 0) {
+ LOGP(DNM, LOGL_ERROR, "Failed to initialize sigtran backhaul.\n");
exit(1);
}
handover_decision_1_init();
hodec2_init(bsc_gsmnet);
+ bsc_cbc_link_restart();
+ lb_start_or_stop();
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
@@ -920,6 +1061,9 @@ int main(int argc, char **argv)
signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
+ update_connection_stats_cb(NULL);
+ chan_counts_sig_init();
+
if (daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
@@ -928,8 +1072,8 @@ int main(int argc, char **argv)
}
}
- while (1) {
- osmo_select_main(0);
+ while (!osmo_select_shutdown_done()) {
+ osmo_select_main_ctx(0);
}
return 0;
diff --git a/src/osmo-bsc/osmo_bsc_mgcp.c b/src/osmo-bsc/osmo_bsc_mgcp.c
new file mode 100644
index 000000000..d7de4d264
--- /dev/null
+++ b/src/osmo-bsc/osmo_bsc_mgcp.c
@@ -0,0 +1,205 @@
+/*
+ * SCCPlite MGCP handling
+ *
+ * (C) 2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
+
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
+
+#include <arpa/inet.h>
+
+/* Determine MSC based on the ASP over which the message was received */
+static struct bsc_msc_data *msc_from_asp(struct osmo_ss7_asp *asp)
+{
+ int msc_nr;
+ const char *asp_name = osmo_ss7_asp_get_name(asp);
+ /* this is rather ugly, as we of course have MTP-level routing between
+ * the local SCCP user (BSC) and the AS/ASPs. However, for the most simple
+ * SCCPlite case, there is a 1:1 mapping between ASP and AS, and using
+ * the libosmo-sigtran "simple client", the names are "as[p]-clnt-msc-%u",
+ * as set in osmo_bsc_sigtran_init() */
+ if (!asp_name || sscanf(asp_name, "asp-clnt-msc-%u", &msc_nr) != 1) {
+ LOGP(DMSC, LOGL_ERROR, "Cannot find to which MSC the ASP '%s' belongs\n", asp_name);
+ return NULL;
+ }
+ return osmo_msc_data_find(bsc_gsmnet, msc_nr);
+}
+
+/* negative on error, zero upon success */
+static int parse_local_endpoint_name(char *buf, size_t buf_len, const char *data)
+{
+ char line[1024];
+ char *epstart, *sep;
+ const char *start = data;
+ char *eol = strpbrk(start, "\r\n");
+
+ if (!eol)
+ return -1;
+
+ if (eol - start > sizeof(line))
+ return -1;
+ memcpy(line, start, eol - start);
+ line[eol - start] = '\0';
+
+ if (!(epstart = strchr(line, ' ')))
+ return -1;
+ epstart++;
+ /* epstart now points to trans */
+
+ if (!(epstart = strchr(epstart, ' ')))
+ return -1;
+ epstart++;
+ /* epstart now points to endpoint */
+ if (!(sep = strchr(epstart, '@')))
+ return -1;
+ if (sep - epstart >= buf_len)
+ return -1;
+
+ *sep = '\0';
+ osmo_strlcpy(buf, epstart, buf_len);
+ return 0;
+}
+
+/* We received an IPA-encapsulated MGCP message from a MSC. Transfers msg ownership. */
+int bsc_sccplite_rx_mgcp(struct osmo_ss7_asp *asp, struct msgb *msg)
+{
+ struct bsc_msc_data *msc;
+ struct gsm_subscriber_connection *conn;
+ char rcv_ep_local_name[1024];
+ struct osmo_sockaddr_str osa_str = {};
+ struct osmo_sockaddr osa = {};
+ socklen_t dest_len;
+ struct mgcp_client *mgcp_cli = NULL;
+ int rc;
+
+ LOGP(DMSC, LOGL_INFO, "%s: Received IPA-encapsulated MGCP: %s\n",
+ osmo_ss7_asp_get_name(asp), msg->l2h);
+
+ msc = msc_from_asp(asp);
+ if (!msc) {
+ rc = 0;
+ goto free_msg_ret;
+ }
+
+ rc = parse_local_endpoint_name(rcv_ep_local_name, sizeof(rcv_ep_local_name), (const char *)msg->l2h);
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "(%s:) Received IPA-encapsulated MGCP: Failed to parse CIC\n",
+ osmo_ss7_asp_get_name(asp));
+ goto free_msg_ret;
+ }
+
+ /* Lookup which conn attached to the MSC holds an MGW endpoint with the
+ * name Endpoint Number as the one provided in the MGCP msg we received
+ * from MSC. Since CIC are unique per MSC, that's the same MGW in the
+ * pool where we have to forward the MGCP message. */
+ llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) {
+ const char *ep_local_name;
+ if (conn->sccp.msc != msc)
+ continue; /* Only conns belonging to this MSC */
+ if (!conn->user_plane.mgw_endpoint)
+ continue;
+ ep_local_name = osmo_mgcpc_ep_local_name(conn->user_plane.mgw_endpoint);
+ LOGPFSMSL(conn->fi, DMSC, LOGL_DEBUG, "ep_local_name='%s' vs rcv_ep_local_name='%s'\n",
+ ep_local_name ? : "(null)", rcv_ep_local_name);
+ if (!ep_local_name)
+ continue;
+ if (strcmp(ep_local_name, rcv_ep_local_name) != 0)
+ continue;
+ mgcp_cli = osmo_mgcpc_ep_client(conn->user_plane.mgw_endpoint);
+ if (!mgcp_cli)
+ continue;
+ break;
+ }
+
+ if (!mgcp_cli) {
+ LOGP(DMSC, LOGL_ERROR, "(%s:) Received IPA-encapsulated MGCP: Failed to find associated MGW\n",
+ osmo_ss7_asp_get_name(asp));
+ rc = 0;
+ goto free_msg_ret;
+ }
+
+ rc = osmo_sockaddr_str_from_str(&osa_str, mgcp_client_remote_addr_str(mgcp_cli),
+ mgcp_client_remote_port(mgcp_cli));
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "(%s:) Received IPA-encapsulated MGCP: Failed to parse MGCP address %s:%u\n",
+ osmo_ss7_asp_get_name(asp), mgcp_client_remote_addr_str(mgcp_cli), mgcp_client_remote_port(mgcp_cli));
+ goto free_msg_ret;
+ }
+
+ LOGP(DMSC, LOGL_NOTICE, "%s: Forwarding IPA-encapsulated MGCP to MGW at " OSMO_SOCKADDR_STR_FMT "\n",
+ osmo_ss7_asp_get_name(asp), OSMO_SOCKADDR_STR_FMT_ARGS_NOT_NULL(&osa_str));
+
+ rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "(%s:) Received IPA-encapsulated MGCP: Failed to parse MGCP address " OSMO_SOCKADDR_STR_FMT "\n",
+ osmo_ss7_asp_get_name(asp), OSMO_SOCKADDR_STR_FMT_ARGS_NOT_NULL(&osa_str));
+ goto free_msg_ret;
+ }
+ dest_len = osmo_sockaddr_size(&osa);
+
+ /* we don't have a write queue here as we simply expect the socket buffers
+ * to be large enough to deal with whatever small/infrequent MGCP messages */
+ rc = sendto(msc->mgcp_ipa.ofd.fd, msgb_l2(msg), msgb_l2len(msg), 0, &osa.u.sa, dest_len);
+
+free_msg_ret:
+ msgb_free(msg);
+ return rc;
+}
+
+/* we received some data on the UDP proxy socket from the MGW. Pass it to MSC via IPA */
+int bsc_sccplite_mgcp_proxy_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct bsc_msc_data *msc = ofd->data;
+ struct msgb *msg;
+ int rc;
+
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ msg = msgb_alloc_headroom(1024, 16, "MGCP->IPA");
+ OSMO_ASSERT(msg);
+ rc = recv(ofd->fd, msg->data, msgb_tailroom(msg), 0);
+ if (rc <= 0) {
+ LOGP(DMSC, LOGL_ERROR, "error receiving data from MGCP<->IPA proxy UDP socket: "
+ "%s\n", strerror(errno));
+ msgb_free(msg);
+ return rc;
+ }
+ msg->l2h = msgb_put(msg, rc);
+ msg->l2h[rc] = '\0';
+ LOGP(DMSC, LOGL_NOTICE, "Received MGCP on UDP proxy socket: %s\n", msg->l2h);
+
+ ipa_prepend_header(msg, IPAC_PROTO_MGCP_OLD);
+ return bsc_sccplite_msc_send(msc, msg);
+}
diff --git a/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c
index 71931e615..24255924f 100644
--- a/src/osmo-bsc/osmo_bsc_msc.c
+++ b/src/osmo-bsc/osmo_bsc_msc.c
@@ -27,26 +27,371 @@
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/ipaccess.h>
#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/osmo_bsc_sigtran.h>
#include <osmocom/bsc/signal.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/socket.h>
#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/gsm23236.h>
#include <osmocom/abis/ipa.h>
+#include <osmocom/mgcp_client/mgcp_client.h>
+#include <osmocom/mgcp_client/mgcp_client_pool.h>
+
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <unistd.h>
-int osmo_bsc_msc_init(struct bsc_msc_data *data)
+static const struct rate_ctr_desc msc_ctr_description[] = {
+ /* Rx message counters (per specific message) */
+ [MSC_CTR_BSSMAP_RX_UDT_RESET_ACKNOWLEDGE] = {
+ "bssmap:rx:udt:reset:ack",
+ "Number of received BSSMAP UDT RESET ACKNOWLEDGE messages"
+ },
+ [MSC_CTR_BSSMAP_RX_UDT_RESET] = {
+ "bssmap:rx:udt:reset:request",
+ "Number of received BSSMAP UDT RESET messages"
+ },
+ [MSC_CTR_BSSMAP_RX_UDT_PAGING] = {
+ "bssmap:rx:udt:paging",
+ "Number of received BSSMAP UDT PAGING messages"
+ },
+ [MSC_CTR_BSSMAP_RX_UDT_UNKNOWN] = {
+ "bssmap:rx:udt:err_unknown",
+ "Number of received BSSMAP unknown UDT messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_CLEAR_CMD] = {
+ "bssmap:rx:dt1:clear:cmd",
+ "Number of received BSSMAP DT1 CLEAR CMD messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_CIPHER_MODE_CMD] = {
+ "bssmap:rx:dt1:cipher_mode:cmd",
+ "Number of received BSSMAP DT1 CIPHER MODE CMD messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_ASSIGNMENT_RQST] = {
+ "bssmap:rx:dt1:assignment:rqst",
+ "Number of received BSSMAP DT1 ASSIGNMENT RQST messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_LCLS_CONNECT_CTRL] = {
+ "bssmap:rx:dt1:lcls_connect_ctrl:cmd",
+ "Number of received BSSMAP DT1 LCLS CONNECT CTRL messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_HANDOVER_RQST] = {
+ "bssmap:rx:dt1:handover:rqst",
+ "Number of received BSSMAP DT1 HANDOVER RQST messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_HANDOVER_CMD] = {
+ "bssmap:rx:dt1:handover:cmd",
+ "Number of received BSSMAP DT1 HANDOVER CMD messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_CLASSMARK_RQST] = {
+ "bssmap:rx:dt1:classmark:rqst",
+ "Number of received BSSMAP DT1 CLASSMARK RQST messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_CONFUSION] = {
+ "bssmap:rx:dt1:confusion",
+ "Number of received BSSMAP DT1 CONFUSION messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_COMMON_ID] = {
+ "bssmap:rx:dt1:common_id",
+ "Number of received BSSMAP DT1 COMMON ID messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_UNKNOWN] = {
+ "bssmap:rx:dt1:err_unknown",
+ "Number of received BSSMAP unknown DT1 messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_DTAP] = {
+ "bssmap:rx:dt1:dtap:good",
+ "Number of received BSSMAP DTAP messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_DTAP_ERROR] = {
+ "bssmap:rx:dt1:dtap:error",
+ "Number of received BSSMAP DTAP messages with errors"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_REQUEST] = {
+ "bssmap:rx:dt1:location:request",
+ "Number of received BSSMAP Perform Location Request messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_PERFORM_LOCATION_ABORT] = {
+ "bssmap:tx:dt1:location:abort",
+ "Number of received BSSMAP Perform Location Abort messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_VGCS_VBS_SETUP] = {
+ "bssmap:rx:dt1:vgcs_vbs_setup",
+ "Number of received BSSMAP DT1 VGCS/VBS SETUP messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_VGCS_VBS_ASSIGN_RQST] = {
+ "bssmap:rx:dt1:vgcs_vbs_assignment:req",
+ "Number of received BSSMAP DT1 VGCS/VBS ASSIGNMENT messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_UPLINK_RQST_ACKNOWLEDGE] = {
+ "bssmap:rx:dt1:uplink_rqst:ack",
+ "Number of received BSSMAP DT1 UPLINK REQUEST ACKNOWLEDGE messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_UPLINK_REJECT_CMD] = {
+ "bssmap:rx:dt1:uplink_reject:cmd",
+ "Number of received BSSMAP DT1 UPLINK REJECT COMMAND messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_UPLINK_RELEASE_CMD] = {
+ "bssmap:rx:dt1:uplink_release:cmd",
+ "Number of received BSSMAP DT1 UPLINK RELEASE COMMAND messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_UPLINK_SEIZED_CMD] = {
+ "bssmap:rx:dt1:uplink_seized:cmd",
+ "Number of received BSSMAP DT1 UPLINK SEIZED COMMAND messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_VGCS_ADDL_INFO] = {
+ "bssmap:rx:dt1:vgcs_addl_info",
+ "Number of received BSSMAP DT1 VGCS/VBS ASSITIONAL INFORMATION messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_VGCS_SMS] = {
+ "bssmap:rx:dt1:vgcs_sms",
+ "Number of received BSSMAP DT1 VGCS SMS messages"
+ },
+ [MSC_CTR_BSSMAP_RX_DT1_NOTIFICATION_DATA] = {
+ "bssmap:rx:dt1:notification_data",
+ "Number of received BSSMAP DT1 NOTIFICATION DATA messages"
+ },
+
+ /* Tx message counters (per message type)
+ *
+ * The counters here follow the logic of the osmo_bsc_sigtran_send() function
+ * which receives DT1 messages from the upper layers and actually sends them to the MSC.
+ * These counters cover all messages passed to the function by the upper layers: */
+ [MSC_CTR_BSSMAP_TX_BSS_MANAGEMENT] = {
+ "bssmap:tx:type:bss_management",
+ "Number of transmitted BSS MANAGEMENT messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DTAP] = {
+ "bssmap:tx:type:dtap",
+ "Number of transmitted DTAP messages"
+ },
+ [MSC_CTR_BSSMAP_TX_UNKNOWN] = {
+ "bssmap:tx:type:err_unknown",
+ "Number of transmitted messages with unknown type (an error in our code?)"
+ },
+ [MSC_CTR_BSSMAP_TX_SHORT] = {
+ "bssmap:tx:type:err_short",
+ "Number of transmitted messages which are too short (an error in our code?)"
+ },
+ /* The next counters are also counted in the osmo_bsc_sigtran_send() function and
+ * sum up to the exactly same number as the counters above but instead of message
+ * classes they split by the result of the sending attempt: */
+ [MSC_CTR_BSSMAP_TX_ERR_CONN_NOT_READY] = {
+ "bssmap:tx:result:err_conn_not_ready",
+ "Number of BSSMAP messages we tried to send when the connection was not ready yet"
+ },
+ [MSC_CTR_BSSMAP_TX_ERR_SEND] = {
+ "bssmap:tx:result:err_send",
+ "Number of socket errors while sending BSSMAP messages"
+ },
+ [MSC_CTR_BSSMAP_TX_SUCCESS] = {
+ "bssmap:tx:result:success",
+ "Number of successfully sent BSSMAP messages"
+ },
+
+ /* Tx message counters (per specific message)
+ *
+ * Theoretically, the DT1 counters should sum up to the same number as the Tx counters
+ * above but since these counters are coming from the upper layers, there might be
+ * some difference if we forget some code path. */
+ [MSC_CTR_BSSMAP_TX_UDT_RESET] = {
+ "bssmap:tx:udt:reset:request",
+ "Number of transmitted BSSMAP UDT RESET messages"
+ },
+ [MSC_CTR_BSSMAP_TX_UDT_RESET_ACK] = {
+ "bssmap:tx:udt:reset:ack",
+ "Number of transmitted BSSMAP UDT RESET ACK messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_CLEAR_RQST] = {
+ "bssmap:tx:dt1:clear:rqst",
+ "Number of transmitted BSSMAP DT1 CLEAR RQSTtx messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_CLEAR_COMPLETE] = {
+ "bssmap:tx:dt1:clear:complete",
+ "Number of transmitted BSSMAP DT1 CLEAR COMPLETE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_FAILURE] = {
+ "bssmap:tx:dt1:assignment:failure",
+ "Number of transmitted BSSMAP DT1 ASSIGNMENT FAILURE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_ASSIGNMENT_COMPLETE] = {
+ "bssmap:tx:dt1:assignment:complete",
+ "Number of transmitted BSSMAP DT1 ASSIGNMENT COMPLETE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_SAPI_N_REJECT] = {
+ "bssmap:tx:dt1:sapi_n:reject",
+ "Number of transmitted BSSMAP DT1 SAPI N REJECT messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_CIPHER_COMPLETE] = {
+ "bssmap:tx:dt1:cipher_mode:complete",
+ "Number of transmitted BSSMAP DT1 CIPHER COMPLETE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_CIPHER_REJECT] = {
+ "bssmap:tx:dt1:cipher_mode:reject",
+ "Number of transmitted BSSMAP DT1 CIPHER REJECT messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_CLASSMARK_UPDATE] = {
+ "bssmap:tx:dt1:classmark:update",
+ "Number of transmitted BSSMAP DT1 CLASSMARK UPDATE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_LCLS_CONNECT_CTRL_ACK] = {
+ "bssmap:tx:dt1:lcls_connect_ctrl:ack",
+ "Number of transmitted BSSMAP DT1 LCLS CONNECT CTRL ACK messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_REQUIRED] = {
+ "bssmap:tx:dt1:handover:required",
+ "Number of transmitted BSSMAP DT1 HANDOVER REQUIRED messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_PERFORMED] = {
+ "bssmap:tx:dt1:handover:performed",
+ "Number of transmitted BSSMAP DT1 HANDOVER PERFORMED messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_RQST_ACKNOWLEDGE] = {
+ "bssmap:tx:dt1:handover:rqst_acknowledge",
+ "Number of transmitted BSSMAP DT1 HANDOVER RQST ACKNOWLEDGE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_DETECT] = {
+ "bssmap:tx:dt1:handover:detect",
+ "Number of transmitted BSSMAP DT1 HANDOVER DETECT messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_COMPLETE] = {
+ "bssmap:tx:dt1:handover:complete",
+ "Number of transmitted BSSMAP DT1 HANDOVER COMPLETE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_HANDOVER_FAILURE] = {
+ "bssmap:tx:dt1:handover:failure",
+ "Number of transmitted BSSMAP DT1 HANDOVER FAILURE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_DTAP] = {
+ "bssmap:tx:dt1:dtap",
+ "Number of transmitted BSSMAP DT1 DTAP messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS] = {
+ "bssmap:tx:dt1:location:response_success",
+ "Number of transmitted BSSMAP Perform Location Response messages containing a location estimate"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE] = {
+ "bssmap:tx:dt1:location:response_failure",
+ "Number of transmitted BSSMAP Perform Location Response messages containing a failure cause"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_SETUP_ACK] = {
+ "bssmap:tx:dt1:vgcs_vbs_setup:ack",
+ "Number of transmitted BSSMAP DT1 VGCS/VBS SETUP ACK messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_SETUP_REFUSE] = {
+ "bssmap:tx:dt1:vgcs_vbs_setup:refuse",
+ "Number of transmitted BSSMAP DT1 VGCS/VBS SETUP REFUSE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGN_RESULT] = {
+ "bssmap:tx:dt1:vgcs_vbs_assignment:res",
+ "Number of transmitted BSSMAP DT1 VGCS/VBS ASSIGNMENT RESULT messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGN_FAIL] = {
+ "bssmap:tx:dt1:vgcs_vbs_assignment:fail",
+ "Number of transmitted BSSMAP DT1 VGCS/VBS ASSIGNMENT FAILURE messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_QUEUING_INDICATION] = {
+ "bssmap:tx:dt1:vgcs_vbs_queuing:ind",
+ "Number of transmitted BSSMAP DT1 VGCS/VBS QUEUING INDICATION messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_UPLINK_RQST] = {
+ "bssmap:tx:dt1:uplink_rqst",
+ "Number of transmitted BSSMAP DT1 UPLINK REQUEST messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_ASSIGNMENT_STATUS] = {
+ "bssmap:tx:dt1:vgcs_vbs_assignment:status",
+ "Number of transmitted BSSMAP DT1 VGCS/VBS ASSIGNMENT STATUS messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_VGCS_VBS_AREA_CELL_INFO] = {
+ "bssmap:tx:dt1:vgcs_vbs_area_cell:info",
+ "Number of transmitted BSSMAP DT1 VGCS/VBS AREA CELL INFO messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_UPLINK_RQST_CONFIRMATION] = {
+ "bssmap:tx:dt1:uplink_rqst:cnf",
+ "Number of transmitted BSSMAP DT1 UPLINK REQUEST CONFIRMATION messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_UPLINK_RELEASE_INDICATION] = {
+ "bssmap:tx:dt1:uplink_release:ind",
+ "Number of transmitted BSSMAP DT1 UPLINK RELEASE INDICATION messages"
+ },
+ [MSC_CTR_BSSMAP_TX_DT1_UPLINK_APP_DATA] = {
+ "bssmap:tx:dt1:uplink_app_data",
+ "Number of transmitted BSSMAP DT1 UPLINK APPLICATION DATA messages"
+ },
+
+ /* Indicators for MSC pool usage */
+ [MSC_CTR_MSCPOOL_SUBSCR_NEW] = {
+ "mscpool:subscr:new",
+ "Complete Layer 3 requests assigned to this MSC by round-robin (no NRI was assigned yet).",
+ },
+ [MSC_CTR_MSCPOOL_SUBSCR_REATTACH] = {
+ "mscpool:subscr:reattach",
+ "Complete Layer 3 requests assigned to this MSC by round-robin because the subscriber indicates a"
+ " NULL-NRI (previously assigned by another MSC).",
+ },
+ [MSC_CTR_MSCPOOL_SUBSCR_KNOWN] = {
+ "mscpool:subscr:known",
+ "Complete Layer 3 requests directed to this MSC because the subscriber indicates an NRI of this MSC.",
+ },
+ [MSC_CTR_MSCPOOL_SUBSCR_PAGED] = {
+ "mscpool:subscr:paged",
+ "Paging Response directed to this MSC because the subscriber was recently paged by this MSC.",
+ },
+ [MSC_CTR_MSCPOOL_SUBSCR_ATTACH_LOST] = {
+ "mscpool:subscr:attach_lost",
+ "A subscriber indicates an NRI value matching this MSC, but the MSC is not connected:"
+ " a re-attach to another MSC (if available) was forced, with possible service failure.",
+ },
+ [MSC_CTR_MSCPOOL_EMERG_FORWARDED] = {
+ "mscpool:emerg:forwarded",
+ "Emergency call requests forwarded to this MSC.",
+ },
+};
+
+static const struct rate_ctr_group_desc msc_ctrg_desc = {
+ "msc",
+ "mobile switching center",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(msc_ctr_description),
+ msc_ctr_description,
+};
+
+static const struct osmo_stat_item_desc msc_stat_desc[] = {
+ [MSC_STAT_MSC_LINKS_ACTIVE] = { "msc_links:active", "Number of active MSC links", "", 16, 0 },
+ [MSC_STAT_MSC_LINKS_TOTAL] = { "msc_links:total", "Number of configured MSC links", "", 16, 0 },
+};
+
+static const struct osmo_stat_item_group_desc msc_statg_desc = {
+ .group_name_prefix = "msc",
+ .group_description = "mobile switching center",
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+ .num_items = ARRAY_SIZE(msc_stat_desc),
+ .item_desc = msc_stat_desc,
+};
+
+int osmo_bsc_msc_init(struct bsc_msc_data *msc)
{
- /* FIXME: This is a leftover from the old architecture that used
- * sccp-lite with osmocom specific authentication. Since we now
- * changed to AoIP the connected status and the authentication
- * status is managed differently. However osmo_bsc_filter.c still
- * needs the flags to be set to one. See also: OS#3112 */
- data->is_authenticated = 1;
+ int rc;
+
+ /* Everything below refers to SCCP-Lite MSC connections only. */
+ if (msc_is_aoip(msc))
+ return 0;
+
+ rc = osmo_sock_init2_ofd(&msc->mgcp_ipa.ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
+ msc->mgcp_ipa.local_addr, msc->mgcp_ipa.local_port,
+ NULL, 0, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ LOGP(DMSC, LOGL_ERROR, "msc %u: Could not create/connect/bind MGCP proxy socket: %d\n",
+ msc->nr, rc);
+ return rc;
+ }
+ LOGP(DMSC, LOGL_INFO, "msc %u: Socket forwarding IPA-encapsulated MGCP messages towards MGW: %s\n",
+ msc->nr, osmo_sock_get_name2(msc->mgcp_ipa.ofd.fd));
return 0;
}
@@ -55,7 +400,7 @@ struct bsc_msc_data *osmo_msc_data_find(struct gsm_network *net, int nr)
{
struct bsc_msc_data *msc_data;
- llist_for_each_entry(msc_data, &net->bsc_data->mscs, entry)
+ llist_for_each_entry(msc_data, &net->mscs, entry)
if (msc_data->nr == nr)
return msc_data;
return NULL;
@@ -64,7 +409,6 @@ struct bsc_msc_data *osmo_msc_data_find(struct gsm_network *net, int nr)
struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr)
{
struct bsc_msc_data *msc_data;
- unsigned int i;
/* check if there is already one */
msc_data = osmo_msc_data_find(net, nr);
@@ -75,7 +419,20 @@ struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr)
if (!msc_data)
return NULL;
- llist_add_tail(&msc_data->entry, &net->bsc_data->mscs);
+ /* init statistics */
+ msc_data->msc_ctrs = rate_ctr_group_alloc(net, &msc_ctrg_desc, nr);
+ if (!msc_data->msc_ctrs) {
+ talloc_free(msc_data);
+ return NULL;
+ }
+ msc_data->msc_statg = osmo_stat_item_group_alloc(net, &msc_statg_desc, nr);
+ if (!msc_data->msc_statg) {
+ rate_ctr_group_free(msc_data->msc_ctrs);
+ talloc_free(msc_data);
+ return NULL;
+ }
+
+ llist_add_tail(&msc_data->entry, &net->mscs);
/* Init back pointer */
msc_data->network = net;
@@ -84,9 +441,6 @@ struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr)
.mcc = GSM_MCC_MNC_INVALID,
.mnc = GSM_MCC_MNC_INVALID,
};
- msc_data->core_ci = -1;
- msc_data->core_lac = -1;
- msc_data->rtp_base = 4000;
msc_data->nr = nr;
msc_data->allow_emerg = 1;
@@ -94,28 +448,47 @@ struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr)
/* Defaults for the audio setup */
msc_data->amr_conf.m5_90 = 1;
+ msc_data->amr_octet_aligned = true;
/* Allow the full set of possible codecs by default */
msc_data->audio_length = 5;
- msc_data->audio_support =
- talloc_zero_array(msc_data, struct gsm_audio_support *,
- msc_data->audio_length);
- for (i = 0; i < msc_data->audio_length; i++) {
- msc_data->audio_support[i] =
- talloc_zero(msc_data->audio_support,
- struct gsm_audio_support);
- }
- msc_data->audio_support[0]->ver = 1;
- msc_data->audio_support[0]->hr = 0;
- msc_data->audio_support[1]->ver = 1;
- msc_data->audio_support[1]->hr = 1;
- msc_data->audio_support[2]->ver = 2;
- msc_data->audio_support[2]->hr = 0;
- msc_data->audio_support[3]->ver = 3;
- msc_data->audio_support[3]->hr = 0;
- msc_data->audio_support[4]->ver = 3;
- msc_data->audio_support[4]->hr = 1;
+ msc_data->audio_support[0].ver = 1;
+ msc_data->audio_support[0].hr = 0;
+ msc_data->audio_support[1].ver = 1;
+ msc_data->audio_support[1].hr = 1;
+ msc_data->audio_support[2].ver = 2;
+ msc_data->audio_support[2].hr = 0;
+ msc_data->audio_support[3].ver = 3;
+ msc_data->audio_support[3].hr = 0;
+ msc_data->audio_support[4].ver = 3;
+ msc_data->audio_support[4].hr = 1;
+
+ osmo_fd_setup(&msc_data->mgcp_ipa.ofd, -1, OSMO_FD_READ, &bsc_sccplite_mgcp_proxy_cb, msc_data, 0);
+ msc_data->mgcp_ipa.local_addr = NULL; /* = INADDR(6)_ANY */
+ msc_data->mgcp_ipa.local_port = 0; /* dynamic */
+
+ msc_data->nri_ranges = osmo_nri_ranges_alloc(msc_data);
+ msc_data->allow_attach = true;
return msc_data;
}
+struct osmo_cell_global_id *cgi_for_msc(struct bsc_msc_data *msc, struct gsm_bts *bts)
+{
+ static struct osmo_cell_global_id cgi;
+
+ if (!bts)
+ return NULL;
+
+ cgi.lai.plmn = msc->network->plmn;
+ if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
+ cgi.lai.plmn.mcc = msc->core_plmn.mcc;
+ if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID) {
+ cgi.lai.plmn.mnc = msc->core_plmn.mnc;
+ cgi.lai.plmn.mnc_3_digits = msc->core_plmn.mnc_3_digits;
+ }
+ cgi.lai.lac = bts->location_area_code;
+ cgi.cell_identity = bts->cell_identity;
+
+ return &cgi;
+}
diff --git a/src/osmo-bsc/osmo_bsc_sigtran.c b/src/osmo-bsc/osmo_bsc_sigtran.c
index 4b2c4aeac..a4d0f2d74 100644
--- a/src/osmo-bsc/osmo_bsc_sigtran.c
+++ b/src/osmo-bsc/osmo_bsc_sigtran.c
@@ -32,66 +32,43 @@
#include <osmocom/bsc/osmo_bsc_grace.h>
#include <osmocom/bsc/osmo_bsc_sigtran.h>
#include <osmocom/bsc/a_reset.h>
-#include <osmocom/bsc/gsm_04_80.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/bssmap_reset.h>
#include <osmocom/mgcp_client/mgcp_common.h>
/* A pointer to a list with all involved MSCs
* (a copy of the pointer location submitted with osmo_bsc_sigtran_init() */
static struct llist_head *msc_list;
-#define RESET_INTERVAL 1 /* sek */
-#define SCCP_MSG_MAXSIZE 1024
-#define CS7_POINTCODE_DEFAULT_OFFSET 2
+#define DEFAULT_ASP_LOCAL_IP "localhost"
+#define DEFAULT_ASP_REMOTE_IP "localhost"
-/* The SCCP stack will not assign connection IDs to us automatically, we
- * will do this ourselves using a counter variable, that counts one up
- * for every new connection */
-static uint32_t conn_id_counter;
-
-/* Helper function to Check if the given connection id is already assigned */
-static struct gsm_subscriber_connection *get_bsc_conn_by_conn_id(int conn_id)
+/* Patch regular BSSMAP RESET to add extra T to announce Osmux support (osmocom extension) */
+static void _gsm0808_extend_announce_osmux(struct msgb *msg)
{
- conn_id &= 0xFFFFFF;
- struct gsm_subscriber_connection *conn;
-
- llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) {
- if (conn->sccp.conn_id == conn_id)
- return conn;
- }
-
- return NULL;
-}
-
-/* Pick a free connection id */
-static int pick_free_conn_id(const struct bsc_msc_data *msc)
-{
- int conn_id = conn_id_counter;
- int i;
-
- for (i = 0; i < 0xFFFFFF; i++) {
- conn_id++;
- conn_id &= 0xFFFFFF;
- if (get_bsc_conn_by_conn_id(conn_id) == false) {
- conn_id_counter = conn_id;
- return conn_id;
- }
- }
-
- return -1;
+ OSMO_ASSERT(msg->l3h[1] == msgb_l3len(msg) - 2); /*TL not in len */
+ msgb_put_u8(msg, GSM0808_IE_OSMO_OSMUX_SUPPORT);
+ msg->l3h[1] = msgb_l3len(msg) - 2;
}
/* Send reset to MSC */
-static void osmo_bsc_sigtran_tx_reset(const struct bsc_msc_data *msc)
+void osmo_bsc_sigtran_tx_reset(const struct bsc_msc_data *msc)
{
struct osmo_ss7_instance *ss7;
struct msgb *msg;
ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
OSMO_ASSERT(ss7);
- LOGP(DMSC, LOGL_NOTICE, "Sending RESET to MSC: %s\n", osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
+ LOGP(DRESET, LOGL_INFO, "Sending RESET to MSC: %s\n", osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
msg = gsm0808_create_reset();
+
+ if (msc_is_aoip(msc) && msc->use_osmux != OSMUX_USAGE_OFF)
+ _gsm0808_extend_announce_osmux(msg);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_UDT_RESET));
osmo_sccp_tx_unitdata_msg(msc->a.sccp_user, &msc->a.bsc_addr,
&msc->a.msc_addr, msg);
}
@@ -105,13 +82,18 @@ void osmo_bsc_sigtran_tx_reset_ack(const struct bsc_msc_data *msc)
ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
OSMO_ASSERT(ss7);
- LOGP(DMSC, LOGL_NOTICE, "Sending RESET ACK to MSC: %s\n", osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
+ LOGP(DRESET, LOGL_NOTICE, "Sending RESET ACK to MSC: %s\n", osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
msg = gsm0808_create_reset_ack();
+
+ if (msc_is_aoip(msc) && msc->use_osmux != OSMUX_USAGE_OFF)
+ _gsm0808_extend_announce_osmux(msg);
+
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_UDT_RESET_ACK));
osmo_sccp_tx_unitdata_msg(msc->a.sccp_user, &msc->a.bsc_addr,
&msc->a.msc_addr, msg);
}
-/* Find an MSC by its sigtran point code */
+/* Find an MSC by its remote SCCP address */
static struct bsc_msc_data *get_msc_by_addr(const struct osmo_sccp_addr *msc_addr)
{
struct osmo_ss7_instance *ss7;
@@ -122,19 +104,33 @@ static struct bsc_msc_data *get_msc_by_addr(const struct osmo_sccp_addr *msc_add
}
ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
- OSMO_ASSERT(ss7);
LOGP(DMSC, LOGL_ERROR, "Unable to find MSC data under address: %s\n", osmo_sccp_addr_name(ss7, msc_addr));
return NULL;
}
-/* Send data to MSC, use the connection id which MSC it is */
+/* Find an MSC by its remote sigtran point code on a given cs7 instance. */
+static struct bsc_msc_data *get_msc_by_pc(struct osmo_ss7_instance *cs7, uint32_t pc)
+{
+ struct bsc_msc_data *msc;
+ llist_for_each_entry(msc, msc_list, entry) {
+ if (msc->a.cs7_instance != cs7->cfg.id)
+ continue;
+ if ((msc->a.msc_addr.presence & OSMO_SCCP_ADDR_T_PC) == 0)
+ continue;
+ if (msc->a.msc_addr.pc == pc)
+ return msc;
+ }
+ return NULL;
+}
+
+/* Received data from MSC, use the connection id which MSC it is */
static int handle_data_from_msc(struct gsm_subscriber_connection *conn, struct msgb *msg)
{
msg->l3h = msgb_l2(msg);
return bsc_handle_dt(conn, msg, msgb_l2len(msg));
}
-/* Sent unitdata to MSC, use the point code to determine which MSC it is */
+/* Received unitdata from MSC, use the point code to determine which MSC it is */
static int handle_unitdata_from_msc(const struct osmo_sccp_addr *msc_addr, struct msgb *msg,
const struct osmo_sccp_user *scu)
{
@@ -157,10 +153,12 @@ static int handle_unitdata_from_msc(const struct osmo_sccp_addr *msc_addr, struc
static int handle_n_connect_from_msc(struct osmo_sccp_user *scu, struct osmo_scu_prim *scu_prim)
{
struct bsc_msc_data *msc = get_msc_by_addr(&scu_prim->u.connect.calling_addr);
+ struct osmo_sccp_instance *sccp = osmo_sccp_get_sccp(scu);
+ struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(sccp);
struct gsm_subscriber_connection *conn;
int rc = 0;
- conn = get_bsc_conn_by_conn_id(scu_prim->u.connect.conn_id);
+ conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.connect.conn_id);
if (conn) {
LOGP(DMSC, LOGL_NOTICE,
"(calling_addr=%s conn_id=%u) N-CONNECT.ind with already used conn_id, ignoring\n",
@@ -180,11 +178,22 @@ static int handle_n_connect_from_msc(struct osmo_sccp_user *scu, struct osmo_scu
goto refuse;
}
+ LOGP(DMSC, LOGL_DEBUG, "(calling_addr=%s conn_id=%u) N-CONNECT.ind from MSC %d\n",
+ osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr),
+ scu_prim->u.connect.conn_id, msc->nr);
+
conn = bsc_subscr_con_allocate(bsc_gsmnet);
if (!conn)
return -ENOMEM;
conn->sccp.msc = msc;
- conn->sccp.conn_id = scu_prim->u.connect.conn_id;
+ conn->sccp.conn.conn_id = scu_prim->u.connect.conn_id;
+ if (bsc_sccp_inst_register_gscon(bsc_sccp, &conn->sccp.conn) < 0) {
+ LOGP(DMSC, LOGL_NOTICE, "(calling_addr=%s conn_id=%u) N-CONNECT.ind failed registering conn\n",
+ osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr), scu_prim->u.connect.conn_id);
+ osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REQUEST, NULL);
+ rc = -ENOENT;
+ goto refuse;
+ }
/* Take actions asked for by the enclosed PDU */
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_IND, scu_prim);
@@ -195,11 +204,101 @@ refuse:
return rc;
}
-/* Callback function, called by the SSCP stack when data arrives */
+static void handle_pcstate_ind(struct osmo_ss7_instance *cs7, const struct osmo_scu_pcstate_param *pcst)
+{
+ struct bsc_msc_data *msc;
+ bool connected;
+ bool disconnected;
+
+ LOGP(DMSC, LOGL_DEBUG, "N-PCSTATE ind: affected_pc=%u sp_status=%s remote_sccp_status=%s\n",
+ pcst->affected_pc, osmo_sccp_sp_status_name(pcst->sp_status),
+ osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status));
+
+ /* If we don't care about that point-code, ignore PCSTATE. */
+ msc = get_msc_by_pc(cs7, pcst->affected_pc);
+ if (!msc)
+ return;
+
+ /* See if this marks the point code to have become available, or to have been lost.
+ *
+ * I want to detect two events:
+ * - connection event (both indicators say PC is reachable).
+ * - disconnection event (at least one indicator says the PC is not reachable).
+ *
+ * There are two separate incoming indicators with various possible values -- the incoming events can be:
+ *
+ * - neither connection nor disconnection indicated -- just indicating congestion
+ * connected == false, disconnected == false --> do nothing.
+ * - both incoming values indicate that we are connected
+ * --> trigger connected
+ * - both indicate we are disconnected
+ * --> trigger disconnected
+ * - one value indicates 'connected', the other indicates 'disconnected'
+ * --> trigger disconnected
+ *
+ * Congestion could imply that we're connected, but it does not indicate that a PC's reachability changed, so no need to
+ * trigger on that.
+ */
+ connected = false;
+ disconnected = false;
+
+ switch (pcst->sp_status) {
+ case OSMO_SCCP_SP_S_ACCESSIBLE:
+ connected = true;
+ break;
+ case OSMO_SCCP_SP_S_INACCESSIBLE:
+ disconnected = true;
+ break;
+ default:
+ case OSMO_SCCP_SP_S_CONGESTED:
+ /* Neither connecting nor disconnecting */
+ break;
+ }
+
+ switch (pcst->remote_sccp_status) {
+ case OSMO_SCCP_REM_SCCP_S_AVAILABLE:
+ if (!disconnected)
+ connected = true;
+ break;
+ case OSMO_SCCP_REM_SCCP_S_UNAVAILABLE_UNKNOWN:
+ case OSMO_SCCP_REM_SCCP_S_UNEQUIPPED:
+ case OSMO_SCCP_REM_SCCP_S_INACCESSIBLE:
+ disconnected = true;
+ connected = false;
+ break;
+ default:
+ case OSMO_SCCP_REM_SCCP_S_CONGESTED:
+ /* Neither connecting nor disconnecting */
+ break;
+ }
+
+ if (disconnected && a_reset_conn_ready(msc)) {
+ LOGP(DMSC, LOGL_NOTICE,
+ "(msc%d) now unreachable: N-PCSTATE ind: pc=%u sp_status=%s remote_sccp_status=%s\n",
+ msc->nr, pcst->affected_pc,
+ osmo_sccp_sp_status_name(pcst->sp_status),
+ osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status));
+ /* A previously usable MSC has disconnected. Kick the BSSMAP back to DISC state. */
+ bssmap_reset_set_disconnected(msc->a.bssmap_reset);
+ } else if (connected && !a_reset_conn_ready(msc)) {
+ LOGP(DMSC, LOGL_NOTICE,
+ "(msc%d) now available: N-PCSTATE ind: pc=%u sp_status=%s remote_sccp_status=%s\n",
+ msc->nr, pcst->affected_pc,
+ osmo_sccp_sp_status_name(pcst->sp_status),
+ osmo_sccp_rem_sccp_status_name(pcst->remote_sccp_status));
+ /* A previously unusable MSC has become reachable. Trigger immediate BSSMAP RESET -- we would resend a
+ * RESET either way, but we might as well do it now to speed up connecting. */
+ bssmap_reset_resend_reset(msc->a.bssmap_reset);
+ }
+}
+
+/* Callback function, called by the SCCP stack when data arrives */
static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
{
struct osmo_scu_prim *scu_prim = (struct osmo_scu_prim *)oph;
struct osmo_sccp_user *scu = _scu;
+ struct osmo_sccp_instance *sccp = osmo_sccp_get_sccp(scu);
+ struct bsc_sccp_inst *bsc_sccp = osmo_sccp_get_priv(sccp);
struct gsm_subscriber_connection *conn;
int rc = 0;
@@ -220,7 +319,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
/* Handle outbound connection confirmation */
DEBUGP(DMSC, "N-CONNECT.cnf(%u, %s)\n", scu_prim->u.connect.conn_id,
osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
- conn = get_bsc_conn_by_conn_id(scu_prim->u.connect.conn_id);
+ conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.connect.conn_id);
if (conn) {
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_CFM, scu_prim);
conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED;
@@ -239,7 +338,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)));
/* Incoming data is a sign of a vital connection */
- conn = get_bsc_conn_by_conn_id(scu_prim->u.data.conn_id);
+ conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.data.conn_id);
if (conn) {
a_reset_conn_success(conn->sccp.msc);
handle_data_from_msc(conn, oph->msg);
@@ -251,7 +350,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
osmo_hexdump(msgb_l2(oph->msg), msgb_l2len(oph->msg)),
scu_prim->u.disconnect.cause);
/* indication of disconnect */
- conn = get_bsc_conn_by_conn_id(scu_prim->u.disconnect.conn_id);
+ conn = bsc_sccp_inst_get_gscon_by_conn_id(bsc_sccp, scu_prim->u.disconnect.conn_id);
if (conn) {
conn->sccp.state = SUBSCR_SCCP_ST_NONE;
if (msgb_l2len(oph->msg) > 0)
@@ -260,6 +359,10 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
}
break;
+ case OSMO_PRIM(OSMO_SCU_PRIM_N_PCSTATE, PRIM_OP_INDICATION):
+ handle_pcstate_ind(osmo_sccp_get_ss7(sccp), &scu_prim->u.pcstate);
+ break;
+
default:
LOGP(DMSC, LOGL_ERROR, "Unhandled SIGTRAN operation %s on primitive %u\n",
get_value_string(osmo_prim_op_names, oph->operation), oph->primitive);
@@ -282,15 +385,15 @@ enum bsc_con osmo_bsc_sigtran_new_conn(struct gsm_subscriber_connection *conn, s
ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
OSMO_ASSERT(ss7);
- LOGP(DMSC, LOGL_NOTICE, "Initializing resources for new SIGTRAN connection to MSC: %s...\n",
- osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
+ LOGP(DMSC, LOGL_INFO, "Initializing resources for new SCCP connection to MSC %d: %s...\n",
+ msc->nr, osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
if (a_reset_conn_ready(msc) == false) {
- LOGP(DMSC, LOGL_ERROR, "MSC is not connected. Dropping.\n");
+ LOGP(DMSC, LOGL_ERROR, "MSC %d is not connected. Dropping.\n", msc->nr);
return BSC_CON_REJECT_NO_LINK;
}
- if (!bsc_grace_allow_new_connection(bts->network, bts)) {
+ if (bts && !bsc_grace_allow_new_connection(bts->network, bts)) {
LOGP(DMSC, LOGL_NOTICE, "BSC in grace period. No new connections.\n");
return BSC_CON_REJECT_RF_GRACE;
}
@@ -301,17 +404,19 @@ enum bsc_con osmo_bsc_sigtran_new_conn(struct gsm_subscriber_connection *conn, s
}
/* Open a new connection oriented sigtran connection */
-int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
+/* Allow test to overwrite it */
+__attribute__((weak)) int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
{
struct osmo_ss7_instance *ss7;
struct bsc_msc_data *msc;
- int conn_id;
+ struct bsc_sccp_inst *bsc_sccp;
+ uint32_t conn_id;
int rc;
OSMO_ASSERT(conn);
OSMO_ASSERT(msg);
OSMO_ASSERT(conn->sccp.msc);
- OSMO_ASSERT(conn->sccp.conn_id == -1);
+ OSMO_ASSERT(conn->sccp.conn.conn_id == SCCP_CONN_ID_UNSET);
msc = conn->sccp.msc;
@@ -320,16 +425,21 @@ int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct ms
return -EINVAL;
}
- conn->sccp.conn_id = conn_id = pick_free_conn_id(msc);
- if (conn->sccp.conn_id < 0) {
+ bsc_sccp = osmo_sccp_get_priv(msc->a.sccp);
+ conn->sccp.conn.conn_id = conn_id = bsc_sccp_inst_next_conn_id(bsc_sccp);
+ if (conn->sccp.conn.conn_id == SCCP_CONN_ID_UNSET) {
LOGP(DMSC, LOGL_ERROR, "Unable to allocate SCCP Connection ID\n");
return -1;
}
- LOGP(DMSC, LOGL_DEBUG, "Allocated new connection id: %d\n", conn->sccp.conn_id);
+ if (bsc_sccp_inst_register_gscon(bsc_sccp, &conn->sccp.conn) < 0) {
+ LOGP(DMSC, LOGL_ERROR, "Unable to register SCCP connection (id=%u)\n", conn->sccp.conn.conn_id);
+ return -1;
+ }
+ LOGP(DMSC, LOGL_DEBUG, "Allocated new connection id: %u\n", conn->sccp.conn.conn_id);
ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
OSMO_ASSERT(ss7);
- LOGP(DMSC, LOGL_NOTICE, "Opening new SIGTRAN connection (id=%i) to MSC: %s\n", conn_id,
- osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
+ LOGP(DMSC, LOGL_INFO, "Opening new SCCP connection (id=%u) to MSC %d: %s\n", conn_id,
+ msc->nr, osmo_sccp_addr_name(ss7, &msc->a.msc_addr));
rc = osmo_sccp_tx_conn_req_msg(msc->a.sccp_user, conn_id, &msc->a.bsc_addr,
&msc->a.msc_addr, msg);
@@ -363,26 +473,32 @@ int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *m
if (msg->len >= 3) {
switch (msg->data[0]) {
case BSSAP_MSG_BSS_MANAGEMENT:
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_BSS_MANAGEMENT));
LOGP(DMSC, LOGL_INFO, "Tx MSC: BSSMAP: %s\n",
gsm0808_bssmap_name(msg->data[2]));
break;
case BSSAP_MSG_DTAP:
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DTAP));
LOGP(DMSC, LOGL_INFO, "Tx MSC: DTAP\n");
break;
default:
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_UNKNOWN));
LOGP(DMSC, LOGL_ERROR, "Tx MSC: unknown message type: 0x%x\n",
msg->data[0]);
}
- } else
+ } else {
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_SHORT));
LOGP(DMSC, LOGL_ERROR, "Tx MSC: message too short: %u\n", msg->len);
+ }
if (a_reset_conn_ready(msc) == false) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_ERR_CONN_NOT_READY));
LOGP(DMSC, LOGL_ERROR, "MSC is not connected. Dropping.\n");
msgb_free(msg);
return -EINVAL;
}
- conn_id = conn->sccp.conn_id;
+ conn_id = conn->sccp.conn.conn_id;
ss7 = osmo_ss7_instance_find(msc->a.cs7_instance);
OSMO_ASSERT(ss7);
@@ -390,43 +506,29 @@ int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *m
conn_id, osmo_sccp_addr_name(ss7, &msc->a.msc_addr), osmo_hexdump(msg->data, msg->len));
rc = osmo_sccp_tx_data_msg(msc->a.sccp_user, conn_id, msg);
+ if (rc >= 0)
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_SUCCESS));
+ else
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_ERR_SEND));
return rc;
}
-/* Send an USSD notification in case we loose the connection to the MSC */
-static void bsc_notify_msc_lost(struct gsm_subscriber_connection *conn)
-{
- /* Check if sccp conn is still present */
- if (!conn)
- return;
-
- /* check for config string */
- if (!conn->sccp.msc->ussd_msc_lost_txt)
- return;
- if (conn->sccp.msc->ussd_msc_lost_txt[0] == '\0')
- return;
-
- /* send USSD notification */
- bsc_send_ussd_notify(conn, 1, conn->sccp.msc->ussd_msc_lost_txt);
- bsc_send_ussd_release_complete(conn);
-}
-
/* Close all open sigtran connections and channels */
-void osmo_bsc_sigtran_reset(const struct bsc_msc_data *msc)
+void osmo_bsc_sigtran_reset(struct bsc_msc_data *msc)
{
struct gsm_subscriber_connection *conn, *conn_temp;
OSMO_ASSERT(msc);
+ /* Drop all ongoing paging requests that this MSC has created on any BTS */
+ paging_flush_network(msc->network, msc);
+
/* Close all open connections */
llist_for_each_entry_safe(conn, conn_temp, &bsc_gsmnet->subscr_conns, entry) {
/* We only may close connections which actually belong to this
* MSC. All other open connections are left untouched */
if (conn->sccp.msc == msc) {
- /* Notify active connection users via USSD that the MSC is down */
- bsc_notify_msc_lost(conn);
-
/* Take down all occopied RF channels */
/* Disconnect all Sigtran connections */
/* Delete subscriber connection */
@@ -435,18 +537,6 @@ void osmo_bsc_sigtran_reset(const struct bsc_msc_data *msc)
}
}
-/* Callback function: Close all open connections */
-static void osmo_bsc_sigtran_reset_cb(const void *priv)
-{
- struct bsc_msc_data *msc = (struct bsc_msc_data*) priv;
-
- /* Shut down all ongoing traffic */
- osmo_bsc_sigtran_reset(msc);
-
- /* Send reset to MSC */
- osmo_bsc_sigtran_tx_reset(msc);
-}
-
/* Default point-code to be used as local address (BSC) */
#define BSC_DEFAULT_PC "0.23.3"
@@ -455,70 +545,28 @@ static void osmo_bsc_sigtran_reset_cb(const void *priv)
static int asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *msg);
-/* Initalize osmo sigtran backhaul */
+/* Initialize osmo sigtran backhaul */
int osmo_bsc_sigtran_init(struct llist_head *mscs)
{
- bool free_attempt_used = false;
- bool fail_on_next_invalid_cfg = false;
-
struct bsc_msc_data *msc;
- char msc_name[32];
uint32_t default_pc;
+ struct osmo_ss7_instance *inst;
+ int create_instance_0_for_msc_nr = -1;
osmo_ss7_register_rx_unknown_cb(&asp_rx_unknown);
OSMO_ASSERT(mscs);
msc_list = mscs;
+ /* Guard against multiple MSCs with identical config */
llist_for_each_entry(msc, msc_list, entry) {
- snprintf(msc_name, sizeof(msc_name), "msc-%u", msc->nr);
- LOGP(DMSC, LOGL_NOTICE, "Initializing SCCP connection to MSC %s\n", msc_name);
-
- /* Check if the VTY could determine a valid CS7 instance,
- * use safe default in case none is set */
- if (msc->a.cs7_instance_valid == false) {
- msc->a.cs7_instance = 0;
- if (fail_on_next_invalid_cfg)
- goto fail_auto_cofiguration;
- free_attempt_used = true;
- }
- LOGP(DMSC, LOGL_NOTICE, "CS7 Instance identifier, A-Interface: %u\n", msc->a.cs7_instance);
-
- /* Pre-Check if there is an ss7 instance present */
- if (osmo_ss7_instance_find(msc->a.cs7_instance) == NULL) {
- if (fail_on_next_invalid_cfg)
- goto fail_auto_cofiguration;
- free_attempt_used = true;
- }
+ struct bsc_msc_data *msc2;
- /* SS7 Protocol stack */
- default_pc = osmo_ss7_pointcode_parse(NULL, BSC_DEFAULT_PC);
- msc->a.sccp =
- osmo_sccp_simple_client_on_ss7_id(msc, msc->a.cs7_instance, msc_name, default_pc,
- msc->a.asp_proto, 0, NULL, 0, NULL);
- if (!msc->a.sccp)
- return -EINVAL;
+ /* An MSC with invalid cs7 instance defaults to cs7 instance 0 */
+ uint32_t msc_inst = (msc->a.cs7_instance_valid ? msc->a.cs7_instance : 0);
- /* In SCCPlite, the MSC side of the MGW endpoint is configured by the MSC. Since we have
- * no way to figure out which CallID ('C:') the MSC will issue in its CRCX command, set
- * an X-Osmo-IGN flag telling osmo-mgw to ignore CallID mismatches for this endpoint.
- * If an explicit VTY command has already indicated whether or not to send X-Osmo-IGN, do
- * not overwrite that setting. */
- if (msc->a.asp_proto == OSMO_SS7_ASP_PROT_IPA
- && !msc->x_osmo_ign_configured)
- msc->x_osmo_ign |= MGCP_X_OSMO_IGN_CALLID;
-
- /* If unset, use default local SCCP address */
- if (!msc->a.bsc_addr.presence)
- osmo_sccp_local_addr_by_instance(&msc->a.bsc_addr, msc->a.sccp,
- OSMO_SCCP_SSN_BSSAP);
-
- if (!osmo_sccp_check_addr(&msc->a.bsc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
- LOGP(DMSC, LOGL_ERROR,
- "(%s) A-interface: invalid local (BSC) SCCP address: %s\n",
- msc_name, osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.bsc_addr));
- return -EINVAL;
- }
+ if (!msc->a.cs7_instance_valid)
+ create_instance_0_for_msc_nr = msc->nr;
/* If unset, use default SCCP address for the MSC */
if (!msc->a.msc_addr.presence)
@@ -526,46 +574,152 @@ int osmo_bsc_sigtran_init(struct llist_head *mscs)
osmo_ss7_pointcode_parse(NULL, MSC_DEFAULT_PC),
OSMO_SCCP_SSN_BSSAP);
- if (!osmo_sccp_check_addr(&msc->a.msc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
- LOGP(DMSC, LOGL_ERROR,
- "(%s) A-interface: invalid remote (MSC) SCCP address: %s\n",
- msc_name, osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.msc_addr));
- return -EINVAL;
+ /* (more optimally, we'd only iterate the remaining other mscs after this msc, but this happens only
+ * during startup, so nevermind that complexity and rather check each pair twice. That also ensures to
+ * compare all MSCs that have no explicit msc_addr set, see osmo_sccp_make_addr_pc_ssn() above.) */
+ llist_for_each_entry(msc2, msc_list, entry) {
+ uint32_t msc2_inst;
+
+ if (msc2 == msc)
+ continue;
+
+ msc2_inst = (msc2->a.cs7_instance_valid ? msc2->a.cs7_instance : 0);
+ if (msc_inst != msc2_inst)
+ continue;
+
+ if (osmo_sccp_addr_cmp(&msc->a.msc_addr, &msc2->a.msc_addr, OSMO_SCCP_ADDR_T_PC) == 0) {
+ LOGP(DMSC, LOGL_ERROR, "'msc %d' and 'msc %d' cannot use the same remote PC"
+ " %s on the same cs7 instance %u\n",
+ msc->nr, msc2->nr, osmo_sccp_addr_dump(&msc->a.msc_addr), msc_inst);
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (create_instance_0_for_msc_nr >= 0 && !osmo_ss7_instance_find(0)) {
+ LOGP(DMSC, LOGL_NOTICE, "To auto-configure msc %d, creating cs7 instance 0 implicitly\n",
+ create_instance_0_for_msc_nr);
+ OSMO_ASSERT(osmo_ss7_instance_find_or_create(tall_bsc_ctx, 0));
+ }
+
+ /* Set up exactly one SCCP user and one ASP+AS per cs7 instance.
+ * Iterate cs7 instance indexes and see for each one whether an MSC is configured for it.
+ * The 'msc' / 'msc-addr' command selects the cs7 instance used for an MSC.
+ */
+ llist_for_each_entry(inst, &osmo_ss7_instances, list) {
+ char inst_name[32];
+ enum osmo_ss7_asp_protocol used_proto = OSMO_SS7_ASP_PROT_NONE;
+ int prev_msc_nr;
+
+ struct osmo_sccp_instance *sccp;
+ struct bsc_sccp_inst *bsc_sccp;
+
+ llist_for_each_entry(msc, msc_list, entry) {
+ /* An MSC with invalid cs7 instance id defaults to cs7 instance 0 */
+ if ((inst->cfg.id != msc->a.cs7_instance)
+ && !(inst->cfg.id == 0 && !msc->a.cs7_instance_valid))
+ continue;
+
+ /* This msc runs on this cs7 inst. Check the asp_proto. */
+ if (used_proto != OSMO_SS7_ASP_PROT_NONE
+ && used_proto != msc->a.asp_proto) {
+ LOGP(DMSC, LOGL_ERROR, "'msc %d' and 'msc %d' with differing ASP protocols"
+ " %s and %s cannot use the same cs7 instance %u\n",
+ prev_msc_nr, msc->nr,
+ osmo_ss7_asp_protocol_name(used_proto),
+ osmo_ss7_asp_protocol_name(msc->a.asp_proto),
+ inst->cfg.id);
+ return -EINVAL;
+ }
+
+ used_proto = msc->a.asp_proto;
+ prev_msc_nr = msc->nr;
+ /* still run through the other MSCs to catch asp_proto mismatches */
}
- LOGP(DMSC, LOGL_NOTICE, "(%s) A-interface: local (BSC) SCCP address: %s\n",
- msc_name, osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.bsc_addr));
- LOGP(DMSC, LOGL_NOTICE, "(%s) A-interface: remote (MSC) SCCP address: %s\n",
- msc_name, osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.msc_addr));
+ if (used_proto == OSMO_SS7_ASP_PROT_NONE) {
+ /* This instance has no MSC associated with it */
+ LOGP(DMSC, LOGL_ERROR, "cs7 instance %u has no MSCs configured to run on it\n", inst->cfg.id);
+ continue;
+ }
+
+ snprintf(inst_name, sizeof(inst_name), "A-%u-%s", inst->cfg.id, osmo_ss7_asp_protocol_name(used_proto));
+ LOGP(DMSC, LOGL_NOTICE, "Initializing SCCP connection for A/%s on cs7 instance %u\n",
+ osmo_ss7_asp_protocol_name(used_proto), inst->cfg.id);
- /* Bind SCCP user */
- msc->a.sccp_user = osmo_sccp_user_bind(msc->a.sccp, msc_name, sccp_sap_up, msc->a.bsc_addr.ssn);
- if (!msc->a.sccp_user)
+ /* SS7 Protocol stack */
+ default_pc = osmo_ss7_pointcode_parse(NULL, BSC_DEFAULT_PC);
+ sccp = osmo_sccp_simple_client_on_ss7_id(tall_bsc_ctx, inst->cfg.id, inst_name,
+ default_pc, used_proto,
+ 0, DEFAULT_ASP_LOCAL_IP,
+ 0, DEFAULT_ASP_REMOTE_IP);
+ if (!sccp)
return -EINVAL;
- /* Start MSC-Reset procedure */
- a_reset_alloc(msc, msc_name, osmo_bsc_sigtran_reset_cb);
-
- /* If we have detected that the SS7 configuration of the MSC we have just initalized
- * was incomplete or completely missing, we can not tolerate another incomplete
- * configuration. The reson for this is that we do only specify exactly one default
- * pointcode pair. We also specify localhost as default IP-Address. If we have wanted
- * to support multiple MSCs with automatic configuration we would be forced to invent
- * a complex ruleset how to allocate the pointcodes and respective IP-Addresses.
- * Furthermore, the situation where a single BSC is connected to multiple MSCs
- * is a very rare situation anyway. In this case we expect the user to experienced
- * enough to create a valid SS7/CS7 VTY configuration that does not lack any
- * components */
- if (free_attempt_used)
- fail_on_next_invalid_cfg = true;
+ bsc_sccp = bsc_sccp_inst_alloc(tall_bsc_ctx);
+ bsc_sccp->sccp = sccp;
+ osmo_sccp_set_priv(sccp, bsc_sccp);
+
+ /* Now that the SCCP client is set up, configure all MSCs on this cs7 instance to use it */
+ llist_for_each_entry(msc, msc_list, entry) {
+ char msc_name[32];
+
+ /* Skip MSCs that don't run on this cs7 instance */
+ if ((inst->cfg.id != msc->a.cs7_instance)
+ && !(inst->cfg.id == 0 && !msc->a.cs7_instance_valid))
+ continue;
+
+ snprintf(msc_name, sizeof(msc_name), "msc-%d", msc->nr);
+
+ msc->a.sccp = sccp;
+
+ /* In SCCPlite, the MSC side of the MGW endpoint is configured by the MSC. Since we have
+ * no way to figure out which CallID ('C:') the MSC will issue in its CRCX command, set
+ * an X-Osmo-IGN flag telling osmo-mgw to ignore CallID mismatches for this endpoint.
+ * If an explicit VTY command has already indicated whether or not to send X-Osmo-IGN, do
+ * not overwrite that setting. */
+ if (msc_is_sccplite(msc) && !msc->x_osmo_ign_configured)
+ msc->x_osmo_ign |= MGCP_X_OSMO_IGN_CALLID;
+
+ /* If unset, use default local SCCP address */
+ if (!msc->a.bsc_addr.presence)
+ osmo_sccp_local_addr_by_instance(&msc->a.bsc_addr, sccp,
+ OSMO_SCCP_SSN_BSSAP);
+
+ if (!osmo_sccp_check_addr(&msc->a.bsc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
+ LOGP(DMSC, LOGL_ERROR,
+ "%s %s: invalid local (BSC) SCCP address: %s\n",
+ inst_name, msc_name, osmo_sccp_inst_addr_name(sccp, &msc->a.bsc_addr));
+ return -EINVAL;
+ }
+
+ if (!osmo_sccp_check_addr(&msc->a.msc_addr, OSMO_SCCP_ADDR_T_SSN | OSMO_SCCP_ADDR_T_PC)) {
+ LOGP(DMSC, LOGL_ERROR,
+ "%s %s: invalid remote (MSC) SCCP address: %s\n",
+ inst_name, msc_name, osmo_sccp_inst_addr_name(sccp, &msc->a.msc_addr));
+ return -EINVAL;
+ }
+
+ LOGP(DMSC, LOGL_NOTICE, "%s %s: local (BSC) SCCP address: %s\n",
+ inst_name, msc_name, osmo_sccp_inst_addr_name(sccp, &msc->a.bsc_addr));
+ LOGP(DMSC, LOGL_NOTICE, "%s %s: remote (MSC) SCCP address: %s\n",
+ inst_name, msc_name, osmo_sccp_inst_addr_name(sccp, &msc->a.msc_addr));
+
+ /* Bind SCCP user. Bind only one user per sccp_instance and bsc_addr. */
+ msc->a.sccp_user = osmo_sccp_user_find(sccp, msc->a.bsc_addr.ssn, msc->a.bsc_addr.pc);
+ LOGP(DMSC, LOGL_NOTICE, "%s %s: %s\n", inst_name, msc_name,
+ msc->a.sccp_user ? "user already bound for this SCCP instance" : "binding SCCP user");
+ if (!msc->a.sccp_user)
+ msc->a.sccp_user = osmo_sccp_user_bind(sccp, msc_name, sccp_sap_up, msc->a.bsc_addr.ssn);
+ if (!msc->a.sccp_user)
+ return -EINVAL;
+
+ /* Start MSC-Reset procedure */
+ a_reset_alloc(msc, msc_name);
+ }
}
return 0;
-
-fail_auto_cofiguration:
- LOGP(DMSC, LOGL_ERROR,
- "A-interface: More than one invalid/inclomplete configuration detected, unable to revover - check config file!\n");
- return -EINVAL;
}
/* this function receives all messages received on an ASP for a PPID / StreamID that
@@ -575,7 +729,7 @@ static int asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *m
struct ipaccess_head *iph;
struct ipaccess_head_ext *iph_ext;
- if (asp->cfg.proto != OSMO_SS7_ASP_PROT_IPA) {
+ if (osmo_ss7_asp_get_proto(asp) != OSMO_SS7_ASP_PROT_IPA) {
msgb_free(msg);
return 0;
}
@@ -593,8 +747,12 @@ static int asp_rx_unknown(struct osmo_ss7_asp *asp, int ppid_mux, struct msgb *m
switch (iph_ext->proto) {
case IPAC_PROTO_EXT_CTRL:
return bsc_sccplite_rx_ctrl(asp, msg);
+ case IPAC_PROTO_EXT_MGCP:
+ return bsc_sccplite_rx_mgcp(asp, msg);
}
break;
+ case IPAC_PROTO_MGCP_OLD:
+ return bsc_sccplite_rx_mgcp(asp, msg);
default:
break;
}
diff --git a/src/osmo-bsc/osmo_bsc_vty.c b/src/osmo-bsc/osmo_bsc_vty.c
deleted file mode 100644
index 6e3d1c190..000000000
--- a/src/osmo-bsc/osmo_bsc_vty.c
+++ /dev/null
@@ -1,1027 +0,0 @@
-/* Osmo BSC VTY Configuration */
-/* (C) 2009-2015 by Holger Hans Peter Freyther
- * (C) 2009-2014 by On-Waves
- * (C) 2018 by Harald Welte <laforge@gnumonks.org>
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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/osmo_bsc.h>
-#include <osmocom/bsc/bsc_msc_data.h>
-#include <osmocom/bsc/vty.h>
-#include <osmocom/bsc/bsc_subscriber.h>
-#include <osmocom/bsc/debug.h>
-#include <osmocom/bsc/bsc_msg_filter.h>
-
-#include <osmocom/core/talloc.h>
-#include <osmocom/gsm/gsm48.h>
-#include <osmocom/vty/logging.h>
-#include <osmocom/mgcp_client/mgcp_client.h>
-
-
-#include <time.h>
-
-
-#define IPA_STR "IP.ACCESS specific\n"
-
-static struct osmo_bsc_data *osmo_bsc_data(struct vty *vty)
-{
- return bsc_gsmnet->bsc_data;
-}
-
-static struct bsc_msc_data *bsc_msc_data(struct vty *vty)
-{
- return vty->index;
-}
-
-static struct cmd_node bsc_node = {
- BSC_NODE,
- "%s(config-bsc)# ",
- 1,
-};
-
-static struct cmd_node msc_node = {
- MSC_NODE,
- "%s(config-msc)# ",
- 1,
-};
-
-DEFUN(cfg_net_msc, cfg_net_msc_cmd,
- "msc [<0-1000>]", "Configure MSC details\n" "MSC connection to configure\n")
-{
- int index = argc == 1 ? atoi(argv[0]) : 0;
- struct bsc_msc_data *msc;
-
- msc = osmo_msc_data_alloc(bsc_gsmnet, index);
- if (!msc) {
- vty_out(vty, "%%Failed to allocate MSC data.%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- vty->index = msc;
- vty->node = MSC_NODE;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc, cfg_net_bsc_cmd,
- "bsc", "Configure BSC\n")
-{
- vty->node = BSC_NODE;
- return CMD_SUCCESS;
-}
-
-static void write_msc_amr_options(struct vty *vty, struct bsc_msc_data *msc)
-{
-#define WRITE_AMR(vty, msc, name, var) \
- vty_out(vty, " amr-config %s %s%s", \
- name, msc->amr_conf.var ? "allowed" : "forbidden", \
- VTY_NEWLINE);
-
- WRITE_AMR(vty, msc, "12_2k", m12_2);
- WRITE_AMR(vty, msc, "10_2k", m10_2);
- WRITE_AMR(vty, msc, "7_95k", m7_95);
- WRITE_AMR(vty, msc, "7_40k", m7_40);
- WRITE_AMR(vty, msc, "6_70k", m6_70);
- WRITE_AMR(vty, msc, "5_90k", m5_90);
- WRITE_AMR(vty, msc, "5_15k", m5_15);
- WRITE_AMR(vty, msc, "4_75k", m4_75);
-#undef WRITE_AMR
-}
-
-static void write_msc(struct vty *vty, struct bsc_msc_data *msc)
-{
- vty_out(vty, "msc %d%s", msc->nr, VTY_NEWLINE);
- if (msc->core_plmn.mnc != GSM_MCC_MNC_INVALID)
- vty_out(vty, " core-mobile-network-code %s%s",
- osmo_mnc_name(msc->core_plmn.mnc, msc->core_plmn.mnc_3_digits), VTY_NEWLINE);
- if (msc->core_plmn.mcc != GSM_MCC_MNC_INVALID)
- vty_out(vty, " core-mobile-country-code %s%s",
- osmo_mcc_name(msc->core_plmn.mcc), VTY_NEWLINE);
- if (msc->core_lac != -1)
- vty_out(vty, " core-location-area-code %d%s",
- msc->core_lac, VTY_NEWLINE);
- if (msc->core_ci != -1)
- vty_out(vty, " core-cell-identity %d%s",
- msc->core_ci, VTY_NEWLINE);
- vty_out(vty, " ip.access rtp-base %d%s", msc->rtp_base, VTY_NEWLINE);
-
- if (msc->ussd_welcome_txt)
- vty_out(vty, " bsc-welcome-text %s%s", msc->ussd_welcome_txt, VTY_NEWLINE);
- else
- vty_out(vty, " no bsc-welcome-text%s", VTY_NEWLINE);
-
- if (msc->ussd_msc_lost_txt && msc->ussd_msc_lost_txt[0])
- vty_out(vty, " bsc-msc-lost-text %s%s", msc->ussd_msc_lost_txt, VTY_NEWLINE);
- else
- vty_out(vty, " no bsc-msc-lost-text%s", VTY_NEWLINE);
-
- if (msc->ussd_grace_txt && msc->ussd_grace_txt[0])
- vty_out(vty, " bsc-grace-text %s%s", msc->ussd_grace_txt, VTY_NEWLINE);
- else
- vty_out(vty, " no bsc-grace-text%s", VTY_NEWLINE);
-
- if (msc->audio_length != 0) {
- int i;
-
- vty_out(vty, " codec-list ");
- for (i = 0; i < msc->audio_length; ++i) {
- if (i != 0)
- vty_out(vty, " ");
-
- if (msc->audio_support[i]->hr)
- vty_out(vty, "hr%.1u", msc->audio_support[i]->ver);
- else
- vty_out(vty, "fr%.1u", msc->audio_support[i]->ver);
- }
- vty_out(vty, "%s", VTY_NEWLINE);
-
- }
-
- vty_out(vty, " type %s%s", msc->type == MSC_CON_TYPE_NORMAL ?
- "normal" : "local", VTY_NEWLINE);
- vty_out(vty, " allow-emergency %s%s", msc->allow_emerg ?
- "allow" : "deny", VTY_NEWLINE);
-
- if (msc->local_pref)
- vty_out(vty, " local-prefix %s%s", msc->local_pref, VTY_NEWLINE);
-
- if (msc->acc_lst_name)
- vty_out(vty, " access-list-name %s%s", msc->acc_lst_name, VTY_NEWLINE);
-
- /* write amr options */
- write_msc_amr_options(vty, msc);
-
- /* write sccp connection configuration */
- if (msc->a.bsc_addr_name) {
- vty_out(vty, " bsc-addr %s%s",
- msc->a.bsc_addr_name, VTY_NEWLINE);
- }
- if (msc->a.msc_addr_name) {
- vty_out(vty, " msc-addr %s%s",
- msc->a.msc_addr_name, VTY_NEWLINE);
- }
- vty_out(vty, " asp-protocol %s%s", osmo_ss7_asp_protocol_name(msc->a.asp_proto), VTY_NEWLINE);
- vty_out(vty, " lcls-mode %s%s", get_value_string(bsc_lcls_mode_names, msc->lcls_mode),
- VTY_NEWLINE);
-
- if (msc->lcls_codec_mismatch_allow)
- vty_out(vty, " lcls-codec-mismatch allowed%s", VTY_NEWLINE);
- else
- vty_out(vty, " lcls-codec-mismatch forbidden%s", VTY_NEWLINE);
-
- /* write MGW configuration */
- mgcp_client_config_write(vty, " ");
-
- if (msc->x_osmo_ign_configured) {
- if (!msc->x_osmo_ign)
- vty_out(vty, " no mgw x-osmo-ign%s", VTY_NEWLINE);
- else
- vty_out(vty, " mgw x-osmo-ign call-id%s", VTY_NEWLINE);
- }
-}
-
-static int config_write_msc(struct vty *vty)
-{
- struct bsc_msc_data *msc;
- struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
-
- llist_for_each_entry(msc, &bsc->mscs, entry)
- write_msc(vty, msc);
-
- return CMD_SUCCESS;
-}
-
-static int config_write_bsc(struct vty *vty)
-{
- struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
-
- vty_out(vty, "bsc%s", VTY_NEWLINE);
- if (bsc->mid_call_txt)
- vty_out(vty, " mid-call-text %s%s", bsc->mid_call_txt, VTY_NEWLINE);
- vty_out(vty, " mid-call-timeout %d%s", bsc->mid_call_timeout, VTY_NEWLINE);
- if (bsc->rf_ctrl_name)
- vty_out(vty, " bsc-rf-socket %s%s",
- bsc->rf_ctrl_name, VTY_NEWLINE);
-
- if (bsc->auto_off_timeout != -1)
- vty_out(vty, " bsc-auto-rf-off %d%s",
- bsc->auto_off_timeout, VTY_NEWLINE);
-
- if (bsc->ussd_no_msc_txt && bsc->ussd_no_msc_txt[0])
- vty_out(vty, " missing-msc-text %s%s", bsc->ussd_no_msc_txt, VTY_NEWLINE);
- else
- vty_out(vty, " no missing-msc-text%s", VTY_NEWLINE);
- if (bsc->acc_lst_name)
- vty_out(vty, " access-list-name %s%s", bsc->acc_lst_name, VTY_NEWLINE);
-
- bsc_msg_acc_lst_write(vty);
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_ncc,
- cfg_net_bsc_ncc_cmd,
- "core-mobile-network-code <1-999>",
- "Use this network code for the core network\n" "MNC value\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- uint16_t mnc;
- bool mnc_3_digits;
-
- if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) {
- vty_out(vty, "%% Error decoding MNC: %s%s", argv[0], VTY_NEWLINE);
- return CMD_WARNING;
- }
- data->core_plmn.mnc = mnc;
- data->core_plmn.mnc_3_digits = mnc_3_digits;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_mcc,
- cfg_net_bsc_mcc_cmd,
- "core-mobile-country-code <1-999>",
- "Use this country code for the core network\n" "MCC value\n")
-{
- uint16_t mcc;
- struct bsc_msc_data *data = bsc_msc_data(vty);
- if (osmo_mcc_from_str(argv[0], &mcc)) {
- vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE);
- return CMD_WARNING;
- }
- data->core_plmn.mcc = mcc;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_lac,
- cfg_net_bsc_lac_cmd,
- "core-location-area-code <0-65535>",
- "Use this location area code for the core network\n" "LAC value\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- data->core_lac = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_ci,
- cfg_net_bsc_ci_cmd,
- "core-cell-identity <0-65535>",
- "Use this cell identity for the core network\n" "CI value\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- data->core_ci = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_rtp_base,
- cfg_net_bsc_rtp_base_cmd,
- "ip.access rtp-base <1-65000>",
- IPA_STR
- "Set the rtp-base port for the RTP stream\n"
- "Port number\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- data->rtp_base = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_codec_list,
- cfg_net_bsc_codec_list_cmd,
- "codec-list .LIST",
- "Set the allowed audio codecs\n"
- "List of audio codecs, e.g. fr3 fr1 hr3\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- int i;
-
- /* free the old list... if it exists */
- if (data->audio_support) {
- talloc_free(data->audio_support);
- data->audio_support = NULL;
- data->audio_length = 0;
- }
-
- /* create a new array */
- data->audio_support =
- talloc_zero_array(osmo_bsc_data(vty), struct gsm_audio_support *, argc);
- data->audio_length = argc;
-
- for (i = 0; i < argc; ++i) {
- /* check for hrX or frX */
- if (strlen(argv[i]) != 3
- || argv[i][1] != 'r'
- || (argv[i][0] != 'h' && argv[i][0] != 'f')
- || argv[i][2] < 0x30
- || argv[i][2] > 0x39)
- goto error;
-
- data->audio_support[i] = talloc_zero(data->audio_support,
- struct gsm_audio_support);
- data->audio_support[i]->ver = atoi(argv[i] + 2);
-
- if (strncmp("hr", argv[i], 2) == 0)
- data->audio_support[i]->hr = 1;
- else if (strncmp("fr", argv[i], 2) == 0)
- data->audio_support[i]->hr = 0;
- }
-
- return CMD_SUCCESS;
-
-error:
- vty_out(vty, "Codec name must be hrX or frX. Was '%s'%s",
- argv[i], VTY_NEWLINE);
- return CMD_ERR_INCOMPLETE;
-}
-
-DEFUN(cfg_net_msc_welcome_ussd,
- cfg_net_msc_welcome_ussd_cmd,
- "bsc-welcome-text .TEXT",
- "Set the USSD notification to be sent\n" "Text to be sent\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- char *str = argv_concat(argv, argc, 0);
- if (!str)
- return CMD_WARNING;
-
- osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_welcome_txt, str);
- talloc_free(str);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_msc_no_welcome_ussd,
- cfg_net_msc_no_welcome_ussd_cmd,
- "no bsc-welcome-text",
- NO_STR "Clear the USSD notification to be sent\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
-
- talloc_free(data->ussd_welcome_txt);
- data->ussd_welcome_txt = NULL;
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_msc_lost_ussd,
- cfg_net_msc_lost_ussd_cmd,
- "bsc-msc-lost-text .TEXT",
- "Set the USSD notification to be sent on MSC connection loss\n" "Text to be sent\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- char *str = argv_concat(argv, argc, 0);
- if (!str)
- return CMD_WARNING;
-
- osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_msc_lost_txt, str);
- talloc_free(str);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_msc_no_lost_ussd,
- cfg_net_msc_no_lost_ussd_cmd,
- "no bsc-msc-lost-text",
- NO_STR "Clear the USSD notification to be sent on MSC connection loss\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
-
- talloc_free(data->ussd_msc_lost_txt);
- data->ussd_msc_lost_txt = 0;
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_msc_grace_ussd,
- cfg_net_msc_grace_ussd_cmd,
- "bsc-grace-text .TEXT",
- "Set the USSD notification to be sent when the MSC has entered the grace period\n" "Text to be sent\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- char *str = argv_concat(argv, argc, 0);
- if (!str)
- return CMD_WARNING;
-
- osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_grace_txt, str);
- talloc_free(str);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_msc_no_grace_ussd,
- cfg_net_msc_no_grace_ussd_cmd,
- "no bsc-grace-text",
- NO_STR "Clear the USSD notification to be sent when the MSC has entered the grace period\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
-
- talloc_free(data->ussd_grace_txt);
- data->ussd_grace_txt = NULL;
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_missing_msc_ussd,
- cfg_net_bsc_missing_msc_ussd_cmd,
- "missing-msc-text .TEXT",
- "Set the USSD notification to be send when a MSC has not been found.\n" "Text to be sent\n")
-{
- struct osmo_bsc_data *data = osmo_bsc_data(vty);
- char *txt = argv_concat(argv, argc, 0);
- if (!txt)
- return CMD_WARNING;
-
- osmo_talloc_replace_string(data, &data->ussd_no_msc_txt, txt);
- talloc_free(txt);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_no_missing_msc_text,
- cfg_net_bsc_no_missing_msc_text_cmd,
- "no missing-msc-text",
- NO_STR "Clear the USSD notification to be send when a MSC has not been found.\n")
-{
- struct osmo_bsc_data *data = osmo_bsc_data(vty);
-
- talloc_free(data->ussd_no_msc_txt);
- data->ussd_no_msc_txt = 0;
-
- return CMD_SUCCESS;
-}
-
-
-DEFUN(cfg_net_msc_type,
- cfg_net_msc_type_cmd,
- "type (normal|local)",
- "Select the MSC type\n"
- "Plain GSM MSC\n" "Special MSC for local call routing\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
-
- if (strcmp(argv[0], "normal") == 0)
- data->type = MSC_CON_TYPE_NORMAL;
- else if (strcmp(argv[0], "local") == 0)
- data->type = MSC_CON_TYPE_LOCAL;
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_msc_emerg,
- cfg_net_msc_emerg_cmd,
- "allow-emergency (allow|deny)",
- "Allow CM ServiceRequests with type emergency\n"
- "Allow\n" "Deny\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- data->allow_emerg = strcmp("allow", argv[0]) == 0;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_msc_local_prefix,
- cfg_net_msc_local_prefix_cmd,
- "local-prefix REGEXP",
- "Prefix for local numbers\n" "REGEXP used\n")
-{
- struct bsc_msc_data *msc = bsc_msc_data(vty);
-
- if (gsm_parse_reg(msc, &msc->local_pref_reg, &msc->local_pref, argc, argv) != 0) {
- vty_out(vty, "%%Failed to parse the regexp: '%s'%s",
- argv[0], VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- return CMD_SUCCESS;
-}
-
-#define AMR_CONF_STR "AMR Multirate Configuration\n"
-#define AMR_COMMAND(name) \
- DEFUN(cfg_net_msc_amr_##name, \
- cfg_net_msc_amr_##name##_cmd, \
- "amr-config " #name "k (allowed|forbidden)", \
- AMR_CONF_STR "Bitrate\n" "Allowed\n" "Forbidden\n") \
-{ \
- struct bsc_msc_data *msc = bsc_msc_data(vty); \
- \
- msc->amr_conf.m##name = strcmp(argv[0], "allowed") == 0; \
- return CMD_SUCCESS; \
-}
-
-AMR_COMMAND(12_2)
-AMR_COMMAND(10_2)
-AMR_COMMAND(7_95)
-AMR_COMMAND(7_40)
-AMR_COMMAND(6_70)
-AMR_COMMAND(5_90)
-AMR_COMMAND(5_15)
-AMR_COMMAND(4_75)
-
-DEFUN(cfg_msc_acc_lst_name,
- cfg_msc_acc_lst_name_cmd,
- "access-list-name NAME",
- "Set the name of the access list to use.\n"
- "The name of the to be used access list.")
-{
- struct bsc_msc_data *msc = bsc_msc_data(vty);
-
- osmo_talloc_replace_string(msc, &msc->acc_lst_name, argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_msc_no_acc_lst_name,
- cfg_msc_no_acc_lst_name_cmd,
- "no access-list-name",
- NO_STR "Remove the access list from the NAT.\n")
-{
- struct bsc_msc_data *msc = bsc_msc_data(vty);
-
- if (msc->acc_lst_name) {
- talloc_free(msc->acc_lst_name);
- msc->acc_lst_name = NULL;
- }
-
- return CMD_SUCCESS;
-}
-
-/* Make sure only standard SSN numbers are used. If no ssn number is
- * configured, silently apply the default SSN */
-static void enforce_standard_ssn(struct vty *vty, struct osmo_sccp_addr *addr)
-{
- if (addr->presence & OSMO_SCCP_ADDR_T_SSN) {
- if (addr->ssn != OSMO_SCCP_SSN_BSSAP)
- vty_out(vty,
- "setting an SSN (%u) different from the standard (%u) is not allowd, will use standard SSN for address: %s%s",
- addr->ssn, OSMO_SCCP_SSN_BSSAP, osmo_sccp_addr_dump(addr), VTY_NEWLINE);
- }
-
- addr->presence |= OSMO_SCCP_ADDR_T_SSN;
- addr->ssn = OSMO_SCCP_SSN_BSSAP;
-}
-
-DEFUN(cfg_msc_cs7_bsc_addr,
- cfg_msc_cs7_bsc_addr_cmd,
- "bsc-addr NAME",
- "Calling Address (local address of this BSC)\n" "SCCP address name\n")
-{
- struct bsc_msc_data *msc = bsc_msc_data(vty);
- const char *bsc_addr_name = argv[0];
- struct osmo_ss7_instance *ss7;
-
- ss7 = osmo_sccp_addr_by_name(&msc->a.bsc_addr, bsc_addr_name);
- if (!ss7) {
- vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", bsc_addr_name, VTY_NEWLINE);
- return CMD_ERR_INCOMPLETE;
- }
-
- /* Prevent mixing addresses from different CS7/SS7 instances */
- if (msc->a.cs7_instance_valid) {
- if (msc->a.cs7_instance != ss7->cfg.id) {
- vty_out(vty,
- "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s",
- bsc_addr_name, VTY_NEWLINE);
- return CMD_ERR_INCOMPLETE;
- }
- }
-
- msc->a.cs7_instance = ss7->cfg.id;
- msc->a.cs7_instance_valid = true;
- enforce_standard_ssn(vty, &msc->a.bsc_addr);
- msc->a.bsc_addr_name = talloc_strdup(msc, bsc_addr_name);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_msc_cs7_msc_addr,
- cfg_msc_cs7_msc_addr_cmd,
- "msc-addr NAME",
- "Called Address (remote address of the MSC)\n" "SCCP address name\n")
-{
- struct bsc_msc_data *msc = bsc_msc_data(vty);
- const char *msc_addr_name = argv[0];
- struct osmo_ss7_instance *ss7;
-
- ss7 = osmo_sccp_addr_by_name(&msc->a.msc_addr, msc_addr_name);
- if (!ss7) {
- vty_out(vty, "Error: No such SCCP addressbook entry: '%s'%s", msc_addr_name, VTY_NEWLINE);
- return CMD_ERR_INCOMPLETE;
- }
-
- /* Prevent mixing addresses from different CS7/SS7 instances */
- if (msc->a.cs7_instance_valid) {
- if (msc->a.cs7_instance != ss7->cfg.id) {
- vty_out(vty,
- "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s",
- msc_addr_name, VTY_NEWLINE);
- return CMD_ERR_INCOMPLETE;
- }
- }
-
- msc->a.cs7_instance = ss7->cfg.id;
- msc->a.cs7_instance_valid = true;
- enforce_standard_ssn(vty, &msc->a.msc_addr);
- msc->a.msc_addr_name = talloc_strdup(msc, msc_addr_name);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_msc_cs7_asp_proto,
- cfg_msc_cs7_asp_proto_cmd,
- "asp-protocol (m3ua|sua|ipa)",
- "A interface protocol to use for this MSC)\n"
- "MTP3 User Adaptation\n"
- "SCCP User Adaptation\n"
- "IPA Multiplex (SCCP Lite)\n")
-{
- struct bsc_msc_data *msc = bsc_msc_data(vty);
-
- msc->a.asp_proto = get_string_value(osmo_ss7_asp_protocol_vals, argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_msc_lcls_mode,
- cfg_net_msc_lcls_mode_cmd,
- "lcls-mode (disabled|mgw-loop|bts-loop)",
- "Configure 3GPP LCLS (Local Call, Local Switch)\n"
- "Disable LCLS for all calls of this MSC\n"
- "Enable LCLS with looping traffic in MGW\n"
- "Enable LCLS with looping traffic between BTS\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
- data->lcls_mode = get_string_value(bsc_lcls_mode_names, argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_msc_lcls_mismtch,
- cfg_net_msc_lcls_mismtch_cmd,
- "lcls-codec-mismatch (allowed|forbidden)",
- "Allow 3GPP LCLS (Local Call, Local Switch) when call legs use different codec/rate\n"
- "Allow LCLS only only for calls that use the same codec/rate on both legs\n"
- "Do not Allow LCLS for calls that use a different codec/rate on both legs\n")
-{
- struct bsc_msc_data *data = bsc_msc_data(vty);
-
- if (strcmp(argv[0], "allowed") == 0)
- data->lcls_codec_mismatch_allow = true;
- else
- data->lcls_codec_mismatch_allow = false;
-
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_msc_mgw_x_osmo_ign,
- cfg_msc_mgw_x_osmo_ign_cmd,
- "mgw x-osmo-ign call-id",
- MGCP_CLIENT_MGW_STR
- "Set a (non-standard) X-Osmo-IGN header in all CRCX messages for RTP streams"
- " associated with this MSC, useful for A/SCCPlite MSCs, since osmo-bsc cannot know"
- " the MSC's chosen CallID. This is enabled by default for A/SCCPlite connections,"
- " disabled by default for all others.\n"
- "Send 'X-Osmo-IGN: C' to ignore CallID mismatches. See OsmoMGW.\n")
-{
- struct bsc_msc_data *msc = bsc_msc_data(vty);
- msc->x_osmo_ign |= MGCP_X_OSMO_IGN_CALLID;
- msc->x_osmo_ign_configured = true;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_msc_no_mgw_x_osmo_ign,
- cfg_msc_no_mgw_x_osmo_ign_cmd,
- "no mgw x-osmo-ign",
- NO_STR
- MGCP_CLIENT_MGW_STR
- "Do not send X-Osmo-IGN MGCP header to this MSC\n")
-{
- struct bsc_msc_data *msc = bsc_msc_data(vty);
- msc->x_osmo_ign = 0;
- msc->x_osmo_ign_configured = true;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_mid_call_text,
- cfg_net_bsc_mid_call_text_cmd,
- "mid-call-text .TEXT",
- "Set the USSD notification sent to running calls when switching from Grace to Off.\n"
- "Text to be sent\n")
-{
- struct osmo_bsc_data *data = osmo_bsc_data(vty);
- char *txt = argv_concat(argv, argc, 0);
- if (!txt)
- return CMD_WARNING;
-
- osmo_talloc_replace_string(data, &data->mid_call_txt, txt);
- talloc_free(txt);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_bsc_mid_call_timeout,
- cfg_net_bsc_mid_call_timeout_cmd,
- "mid-call-timeout NR",
- "Switch from Grace to Off in NR seconds.\n" "Timeout in seconds\n")
-{
- struct osmo_bsc_data *data = osmo_bsc_data(vty);
- data->mid_call_timeout = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_rf_socket,
- cfg_net_rf_socket_cmd,
- "bsc-rf-socket PATH",
- "Set the filename for the RF control interface.\n" "RF Control path\n")
-{
- struct osmo_bsc_data *data = osmo_bsc_data(vty);
-
- osmo_talloc_replace_string(data, &data->rf_ctrl_name, argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_rf_off_time,
- cfg_net_rf_off_time_cmd,
- "bsc-auto-rf-off <1-65000>",
- "Disable RF on MSC Connection\n" "Timeout\n")
-{
- struct osmo_bsc_data *data = osmo_bsc_data(vty);
- data->auto_off_timeout = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_no_rf_off_time,
- cfg_net_no_rf_off_time_cmd,
- "no bsc-auto-rf-off",
- NO_STR "Disable RF on MSC Connection\n")
-{
- struct osmo_bsc_data *data = osmo_bsc_data(vty);
- data->auto_off_timeout = -1;
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bsc_acc_lst_name,
- cfg_bsc_acc_lst_name_cmd,
- "access-list-name NAME",
- "Set the name of the access list to use.\n"
- "The name of the to be used access list.")
-{
- struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
-
- osmo_talloc_replace_string(bsc, &bsc->acc_lst_name, argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_bsc_no_acc_lst_name,
- cfg_bsc_no_acc_lst_name_cmd,
- "no access-list-name",
- NO_STR "Remove the access list from the BSC\n")
-{
- struct osmo_bsc_data *bsc = osmo_bsc_data(vty);
-
- if (bsc->acc_lst_name) {
- talloc_free(bsc->acc_lst_name);
- bsc->acc_lst_name = NULL;
- }
-
- return CMD_SUCCESS;
-}
-
-DEFUN(show_statistics,
- show_statistics_cmd,
- "show statistics",
- SHOW_STR "Statistics about the BSC\n")
-{
- openbsc_vty_print_statistics(vty, bsc_gsmnet);
- return CMD_SUCCESS;
-}
-
-DEFUN(show_mscs,
- show_mscs_cmd,
- "show mscs",
- SHOW_STR "MSC Connections and State\n")
-{
- struct bsc_msc_data *msc;
- llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) {
- vty_out(vty, "%d %s %s ",
- msc->a.cs7_instance,
- osmo_ss7_asp_protocol_name(msc->a.asp_proto),
- osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.bsc_addr));
- vty_out(vty, "%s%s",
- osmo_sccp_inst_addr_name(msc->a.sccp, &msc->a.msc_addr),
- VTY_NEWLINE);
- }
-
- return CMD_SUCCESS;
-}
-
-DEFUN(show_pos,
- show_pos_cmd,
- "show position",
- SHOW_STR "Position information of the BTS\n")
-{
- struct gsm_bts *bts;
- struct bts_location *curloc;
- struct tm time;
- char timestr[50];
-
- llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
- if (llist_empty(&bts->loc_list)) {
- vty_out(vty, "BTS Nr: %d position invalid%s", bts->nr,
- VTY_NEWLINE);
- continue;
- }
- curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
- if (gmtime_r(&curloc->tstamp, &time) == NULL) {
- vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
- VTY_NEWLINE);
- continue;
- }
- if (asctime_r(&time, timestr) == NULL) {
- vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr,
- VTY_NEWLINE);
- continue;
- }
- /* Last character in asctime is \n */
- timestr[strlen(timestr)-1] = 0;
-
- vty_out(vty, "BTS Nr: %d position: %s time: %s%s", bts->nr,
- get_value_string(bts_loc_fix_names, curloc->valid), timestr,
- VTY_NEWLINE);
- vty_out(vty, " lat: %f lon: %f height: %f%s", curloc->lat, curloc->lon,
- curloc->height, VTY_NEWLINE);
- }
- return CMD_SUCCESS;
-}
-
-DEFUN(gen_position_trap,
- gen_position_trap_cmd,
- "generate-location-state-trap <0-255>",
- "Generate location state report\n"
- "BTS to report\n")
-{
- int bts_nr;
- struct gsm_bts *bts;
- struct gsm_network *net = bsc_gsmnet;
-
- bts_nr = atoi(argv[0]);
- if (bts_nr >= net->num_bts) {
- vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
- VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- bts = gsm_bts_num(net, bts_nr);
- bsc_gen_location_state_trap(bts);
- return CMD_SUCCESS;
-}
-
-DEFUN(logging_fltr_imsi,
- logging_fltr_imsi_cmd,
- "logging filter imsi IMSI",
- LOGGING_STR FILTER_STR
- "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
-{
- struct bsc_subscr *bsc_subscr;
- struct log_target *tgt = osmo_log_vty2tgt(vty);
- const char *imsi = argv[0];
-
- if (!tgt)
- return CMD_WARNING;
-
- bsc_subscr = bsc_subscr_find_or_create_by_imsi(bsc_gsmnet->bsc_subscribers, imsi);
-
- if (!bsc_subscr) {
- vty_out(vty, "%%failed to enable logging for subscriber with IMSI(%s)%s",
- imsi, VTY_NEWLINE);
- return CMD_WARNING;
- }
-
- log_set_filter_bsc_subscr(tgt, bsc_subscr);
- /* log_set_filter has grabbed its own reference */
- bsc_subscr_put(bsc_subscr);
-
- return CMD_SUCCESS;
-}
-
-static void dump_one_sub(struct vty *vty, struct bsc_subscr *bsub)
-{
- vty_out(vty, " %15s %08x %5u %d%s", bsub->imsi, bsub->tmsi, bsub->lac, bsub->use_count,
- VTY_NEWLINE);
-}
-
-DEFUN(show_subscr_all,
- show_subscr_all_cmd,
- "show subscriber all",
- SHOW_STR "Display information about subscribers\n" "All Subscribers\n")
-{
- struct bsc_subscr *bsc_subscr;
-
- vty_out(vty, " IMSI TMSI LAC Use%s", VTY_NEWLINE);
- /* " 001010123456789 ffffffff 65534 1" */
-
- llist_for_each_entry(bsc_subscr, bsc_gsmnet->bsc_subscribers, entry)
- dump_one_sub(vty, bsc_subscr);
-
- return CMD_SUCCESS;
-}
-
-#define LEGACY_STR "This command has no effect, it is kept to support legacy config files\n"
-
-DEFUN_DEPRECATED(cfg_net_msc_ping_time, cfg_net_msc_ping_time_cmd,
- "timeout-ping ARG", LEGACY_STR "-\n")
-{
- vty_out(vty, "%% timeout-ping / timeout-pong config is deprecated and has no effect%s",
- VTY_NEWLINE);
- return CMD_WARNING;
-}
-
-ALIAS_DEPRECATED(cfg_net_msc_ping_time, cfg_net_msc_no_ping_time_cmd,
- "no timeout-ping [ARG]", NO_STR LEGACY_STR "-\n");
-
-ALIAS_DEPRECATED(cfg_net_msc_ping_time, cfg_net_msc_pong_time_cmd,
- "timeout-pong ARG", LEGACY_STR "-\n");
-
-DEFUN_DEPRECATED(cfg_net_msc_dest, cfg_net_msc_dest_cmd,
- "dest A.B.C.D <1-65000> <0-255>", LEGACY_STR "-\n" "-\n" "-\n")
-{
- vty_out(vty, "%% dest config is deprecated and has no effect%s", VTY_NEWLINE);
- return CMD_WARNING;
-}
-
-ALIAS_DEPRECATED(cfg_net_msc_dest, cfg_net_msc_no_dest_cmd,
- "no dest A.B.C.D <1-65000> <0-255>", NO_STR LEGACY_STR "-\n" "-\n" "-\n");
-
-int bsc_vty_init_extra(void)
-{
- struct gsm_network *net = bsc_gsmnet;
-
- install_element(CONFIG_NODE, &cfg_net_msc_cmd);
- install_element(CONFIG_NODE, &cfg_net_bsc_cmd);
-
- install_node(&bsc_node, config_write_bsc);
- install_element(BSC_NODE, &cfg_net_bsc_mid_call_text_cmd);
- install_element(BSC_NODE, &cfg_net_bsc_mid_call_timeout_cmd);
- install_element(BSC_NODE, &cfg_net_rf_socket_cmd);
- install_element(BSC_NODE, &cfg_net_rf_off_time_cmd);
- install_element(BSC_NODE, &cfg_net_no_rf_off_time_cmd);
- install_element(BSC_NODE, &cfg_net_bsc_missing_msc_ussd_cmd);
- install_element(BSC_NODE, &cfg_net_bsc_no_missing_msc_text_cmd);
- install_element(BSC_NODE, &cfg_bsc_acc_lst_name_cmd);
- install_element(BSC_NODE, &cfg_bsc_no_acc_lst_name_cmd);
-
- install_node(&msc_node, config_write_msc);
- install_element(MSC_NODE, &cfg_net_bsc_ncc_cmd);
- install_element(MSC_NODE, &cfg_net_bsc_mcc_cmd);
- install_element(MSC_NODE, &cfg_net_bsc_lac_cmd);
- install_element(MSC_NODE, &cfg_net_bsc_ci_cmd);
- install_element(MSC_NODE, &cfg_net_bsc_rtp_base_cmd);
- install_element(MSC_NODE, &cfg_net_bsc_codec_list_cmd);
- install_element(MSC_NODE, &cfg_net_msc_dest_cmd);
- install_element(MSC_NODE, &cfg_net_msc_no_dest_cmd);
- install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd);
- install_element(MSC_NODE, &cfg_net_msc_no_welcome_ussd_cmd);
- install_element(MSC_NODE, &cfg_net_msc_lost_ussd_cmd);
- install_element(MSC_NODE, &cfg_net_msc_no_lost_ussd_cmd);
- install_element(MSC_NODE, &cfg_net_msc_grace_ussd_cmd);
- install_element(MSC_NODE, &cfg_net_msc_no_grace_ussd_cmd);
- install_element(MSC_NODE, &cfg_net_msc_type_cmd);
- install_element(MSC_NODE, &cfg_net_msc_emerg_cmd);
- install_element(MSC_NODE, &cfg_net_msc_local_prefix_cmd);
- install_element(MSC_NODE, &cfg_net_msc_amr_12_2_cmd);
- install_element(MSC_NODE, &cfg_net_msc_amr_10_2_cmd);
- install_element(MSC_NODE, &cfg_net_msc_amr_7_95_cmd);
- install_element(MSC_NODE, &cfg_net_msc_amr_7_40_cmd);
- install_element(MSC_NODE, &cfg_net_msc_amr_6_70_cmd);
- install_element(MSC_NODE, &cfg_net_msc_amr_5_90_cmd);
- install_element(MSC_NODE, &cfg_net_msc_amr_5_15_cmd);
- install_element(MSC_NODE, &cfg_net_msc_amr_4_75_cmd);
- install_element(MSC_NODE, &cfg_net_msc_lcls_mode_cmd);
- install_element(MSC_NODE, &cfg_net_msc_lcls_mismtch_cmd);
- install_element(MSC_NODE, &cfg_msc_acc_lst_name_cmd);
- install_element(MSC_NODE, &cfg_msc_no_acc_lst_name_cmd);
- install_element(MSC_NODE, &cfg_msc_cs7_bsc_addr_cmd);
- install_element(MSC_NODE, &cfg_msc_cs7_msc_addr_cmd);
- install_element(MSC_NODE, &cfg_msc_cs7_asp_proto_cmd);
-
- /* Deprecated: ping time config, kept to support legacy config files. */
- install_element(MSC_NODE, &cfg_net_msc_no_ping_time_cmd);
- install_element(MSC_NODE, &cfg_net_msc_ping_time_cmd);
- install_element(MSC_NODE, &cfg_net_msc_pong_time_cmd);
-
- install_element_ve(&show_statistics_cmd);
- install_element_ve(&show_mscs_cmd);
- install_element_ve(&show_pos_cmd);
- install_element_ve(&logging_fltr_imsi_cmd);
- install_element_ve(&show_subscr_all_cmd);
-
- install_element(ENABLE_NODE, &gen_position_trap_cmd);
-
- install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
-
- mgcp_client_vty_init(net, MSC_NODE, net->mgw.conf);
- install_element(MSC_NODE, &cfg_msc_mgw_x_osmo_ign_cmd);
- install_element(MSC_NODE, &cfg_msc_no_mgw_x_osmo_ign_cmd);
-
- return 0;
-}
diff --git a/src/osmo-bsc/paging.c b/src/osmo-bsc/paging.c
index 2c9d5cd2c..b7842dd52 100644
--- a/src/osmo-bsc/paging.c
+++ b/src/osmo-bsc/paging.c
@@ -39,6 +39,7 @@
#include <assert.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/tdef.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/gsm0502.h>
@@ -51,74 +52,115 @@
#include <osmocom/bsc/chan_alloc.h>
#include <osmocom/bsc/gsm_08_08.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
-#include <osmocom/bsc/gsm_timers.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bsc_stats.h>
void *tall_paging_ctx = NULL;
-#define PAGING_TIMER 0, 500000
+/* How many paging requests to Tx on RSL at max before going back to main loop */
+#define MAX_PAGE_REQ_PER_ITER 10
-/*
- * TODO MSCSPLIT: the paging in libbsc is closely tied to MSC land in that the
- * MSC realm callback functions used to be invoked from the BSC/BTS level. So
- * this entire file needs to be rewired for use with an A interface.
- */
+/* How often to attempt sending new paging requests (initial, not retrans): 250ms */
+static const struct timespec initial_period = {
+ .tv_sec = 0,
+ .tv_nsec = 250 * 1000 * 1000,
+};
+
+/* Minimum period between retransmits of paging req to a subscriber: 500ms */
+static const struct timespec retrans_period = {
+ .tv_sec = 0,
+ .tv_nsec = 500 * 1000 * 1000,
+};
+
+/* If no CCCH Load Ind is received before this time period, the BTS is considered
+ * to have stopped sending CCCH Load Indication, probably due to being under Load
+ * Threshold: */
+#define bts_no_ccch_load_ind_timeout_sec(bts) ((bts)->ccch_load_ind_period * 2)
/*
* Kill one paging request update the internal list...
*/
-static void paging_remove_request(struct gsm_bts_paging_state *paging_bts,
- struct gsm_paging_request *to_be_deleted)
+static void paging_remove_request(struct gsm_paging_request *req)
{
- osmo_timer_del(&to_be_deleted->T3113);
- llist_del(&to_be_deleted->entry);
- bsc_subscr_put(to_be_deleted->bsub);
- talloc_free(to_be_deleted);
+ struct gsm_bts *bts = req->bts;
+ struct gsm_bts_paging_state *bts_pag_st = &bts->paging;
+
+ osmo_timer_del(&req->T3113);
+ llist_del(&req->entry);
+ if (req->attempts == 0) {
+ bts_pag_st->initial_req_list_len--;
+ bts_pag_st->initial_req_pgroup_counts[req->pgroup]--;
+ } else {
+ bts_pag_st->retrans_req_list_len--;
+ }
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_REQ_QUEUE_LENGTH), 1);
+ bsc_subscr_remove_active_paging_request(req->bsub, req);
+ talloc_free(req);
+
+ if (llist_empty(&bts_pag_st->initial_req_list) && llist_empty(&bts_pag_st->retrans_req_list))
+ osmo_timer_del(&bts_pag_st->work_timer);
}
static void page_ms(struct gsm_paging_request *request)
{
- uint8_t mi[128];
- unsigned int mi_len;
unsigned int page_group;
struct gsm_bts *bts = request->bts;
+ struct osmo_mobile_identity mi;
log_set_context(LOG_CTX_BSC_SUBSCR, request->bsub);
- LOGP(DPAG, LOGL_INFO, "(bts=%d) Going to send paging commands: imsi: %s tmsi: "
- "0x%08x for ch. type %d (attempt %d)\n", bts->nr, request->bsub->imsi,
- request->bsub->tmsi, request->chan_type, request->attempts);
-
- if (request->bsub->tmsi == GSM_RESERVED_TMSI)
- mi_len = gsm48_generate_mid_from_imsi(mi, request->bsub->imsi);
- else
- mi_len = gsm48_generate_mid_from_tmsi(mi, request->bsub->tmsi);
+ LOG_PAGING_BTS(request, bts, DPAG, LOGL_INFO,
+ "Going to send paging command for ch. type %d (attempt %d)\n",
+ request->chan_type, request->attempts);
+
+ if (request->bsub->tmsi == GSM_RESERVED_TMSI) {
+ mi = (struct osmo_mobile_identity){
+ .type = GSM_MI_TYPE_IMSI,
+ };
+ OSMO_STRLCPY_ARRAY(mi.imsi, request->bsub->imsi);
+ } else {
+ mi = (struct osmo_mobile_identity){
+ .type = GSM_MI_TYPE_TMSI,
+ .tmsi = request->bsub->tmsi,
+ };
+ }
page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc,
str_to_imsi(request->bsub->imsi));
- rsl_paging_cmd(bts, page_group, mi_len, mi, request->chan_type, false);
+ rsl_paging_cmd(bts, page_group, &mi, request->chan_type, false);
log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
}
+static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts);
+
static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts)
{
- if (llist_empty(&paging_bts->pending_requests))
- return;
-
+ /* paging_handle_pending_requests() will schedule work_timer if work
+ * needs to be partitioned in several iterations. */
if (!osmo_timer_pending(&paging_bts->work_timer))
- osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER);
+ paging_handle_pending_requests(paging_bts);
}
+/* Placeholder to set the value and update the related osmo_stat: */
+static void paging_set_available_slots(struct gsm_bts *bts, uint16_t available_slots)
+{
+ bts->paging.available_slots = available_slots;
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_AVAILABLE_SLOTS), available_slots);
+}
-static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts);
static void paging_give_credit(void *data)
{
- struct gsm_bts_paging_state *paging_bts = data;
-
- LOGP(DPAG, LOGL_NOTICE, "(bts=%d) No PCH LOAD IND, adding 20 slots)\n",
- paging_bts->bts->nr);
- paging_bts->available_slots = 20;
- paging_handle_pending_requests(paging_bts);
+ struct gsm_bts_paging_state *paging_bts_st = data;
+ struct gsm_bts *bts = paging_bts_st->bts;
+ unsigned int load_ind_timeout = bts_no_ccch_load_ind_timeout_sec(bts);
+ uint16_t estimated_slots = paging_estimate_available_slots(bts, load_ind_timeout);
+ LOG_BTS(bts, DPAG, LOGL_INFO,
+ "Timeout waiting for CCCH Load Indication, assuming BTS is below Load Threshold (available_slots %u -> %u)\n",
+ paging_bts_st->available_slots, estimated_slots);
+ paging_set_available_slots(bts, estimated_slots);
+ paging_schedule_if_needed(paging_bts_st);
+ osmo_timer_schedule(&bts->paging.credit_timer, load_ind_timeout, 0);
}
/*! count the number of free channels for given RSL channel type required
@@ -171,63 +213,182 @@ count_tch:
return bts->paging.free_chans_need > count;
}
+static void paging_req_timeout_retrans(struct gsm_paging_request *request, const struct timespec *now)
+{
+ struct gsm_bts_paging_state *bts_pag_st = &request->bts->paging;
+ page_ms(request);
+ paging_set_available_slots(request->bts, bts_pag_st->available_slots - 1);
+
+ if (request->attempts == 0) {
+ /* req is removed from initial_req_list and inserted into retrans_req_list, update list lengths: */
+ bts_pag_st->initial_req_list_len--;
+ bts_pag_st->initial_req_pgroup_counts[request->pgroup]--;
+ bts_pag_st->retrans_req_list_len++;
+ }
+ llist_del(&request->entry);
+ llist_add_tail(&request->entry, &bts_pag_st->retrans_req_list);
+
+ request->last_attempt_ts = *now;
+ request->attempts++;
+}
+
+/* Returns number of paged initial requests (up to max_page_req_per_iter).
+ * Returning work_done=false means the work timer has been scheduled internally and the caller should avoid processing
+ * further requests right now.
+ */
+static unsigned int step_page_initial_reqs(struct gsm_bts_paging_state *bts_pag_st, unsigned int max_page_req_per_iter,
+ const struct timespec *now, bool *work_done)
+{
+ struct gsm_paging_request *request, *request2;
+ unsigned int num_paged = 0;
+
+ llist_for_each_entry_safe(request, request2, &bts_pag_st->initial_req_list, entry) {
+ /* We run out of available slots. Wait until next CCCH Load Ind
+ * arrives or credit_timer triggers to keep processing requests.
+ */
+ if (bts_pag_st->available_slots == 0) {
+ LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_INFO,
+ "Paging delayed: waiting for available slots at BTS\n");
+ *work_done = false;
+ return num_paged;
+ }
+
+ if (num_paged == max_page_req_per_iter) {
+ goto sched_next_iter;
+ }
+
+ /* we need to determine the number of free channels */
+ if (bts_pag_st->free_chans_need != -1 &&
+ can_send_pag_req(request->bts, request->chan_type) != 0) {
+ LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_INFO,
+ "Paging delayed: not enough free channels (<%d)\n",
+ bts_pag_st->free_chans_need);
+ goto sched_next_iter;
+ }
+
+ /* handle the paging request now */
+ paging_req_timeout_retrans(request, now);
+ num_paged++;
+ }
+
+ *work_done = true;
+ return num_paged;
+
+sched_next_iter:
+ LOG_BTS(bts_pag_st->bts, DPAG, LOGL_DEBUG, "Scheduling next batch in %lld.%06lds (available_slots=%u)\n",
+ (long long)initial_period.tv_sec, initial_period.tv_nsec / 1000,
+ bts_pag_st->available_slots);
+ osmo_timer_schedule(&bts_pag_st->work_timer, initial_period.tv_sec, initial_period.tv_nsec / 1000);
+ *work_done = false;
+ return num_paged;
+}
+
+static unsigned int step_page_retrans_reqs(struct gsm_bts_paging_state *bts_pag_st, unsigned int max_page_req_per_iter,
+ const struct timespec *now)
+{
+ struct gsm_paging_request *request, *initial_request;
+ unsigned int num_paged = 0;
+ struct timespec retrans_ts;
+
+ /* do while loop: Try send at most first max_page_req_per_iter paging
+ * requests. Since transmitted requests are re-appended at the end of
+ * the list, we check until we find the first req again, in order to
+ * avoid retransmitting repeated requests until next time paging is
+ * scheduled. */
+ initial_request = llist_first_entry_or_null(&bts_pag_st->retrans_req_list,
+ struct gsm_paging_request, entry);
+ if (!initial_request)
+ return num_paged;
+
+ request = initial_request;
+ do {
+ /* We run out of available slots. Wait until next CCCH Load Ind
+ * arrives or credit_timer triggers to keep processing requests.
+ */
+ if (bts_pag_st->available_slots == 0) {
+ LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_INFO,
+ "Paging delayed: waiting for available slots at BTS\n");
+ return num_paged;
+ }
+
+ /* we need to determine the number of free channels */
+ if (bts_pag_st->free_chans_need != -1 &&
+ can_send_pag_req(request->bts, request->chan_type) != 0) {
+ LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_INFO,
+ "Paging delayed: not enough free channels (<%d)\n",
+ bts_pag_st->free_chans_need);
+ goto sched_next_iter;
+ }
+
+ /* Check if time to retransmit has elapsed. Otherwise, wait until its time to retransmit. */
+ timespecadd(&request->last_attempt_ts, &retrans_period, &retrans_ts);
+ if (timespeccmp(now, &retrans_ts, <)) {
+ struct timespec tdiff;
+ timespecsub(&retrans_ts, now, &tdiff);
+ LOG_PAGING_BTS(request, request->bts, DPAG, LOGL_DEBUG,
+ "Paging delayed: retransmission happens in %lld.%06lds\n",
+ (long long)tdiff.tv_sec, tdiff.tv_nsec / 1000);
+ osmo_timer_schedule(&bts_pag_st->work_timer, tdiff.tv_sec, tdiff.tv_nsec / 1000);
+ return num_paged;
+ }
+
+ if (num_paged >= max_page_req_per_iter)
+ goto sched_next_iter;
+
+ /* handle the paging request now */
+ paging_req_timeout_retrans(request, now);
+ num_paged++;
+
+ request = llist_first_entry(&bts_pag_st->retrans_req_list,
+ struct gsm_paging_request, entry);
+ } while (request != initial_request);
+
+ /* Reaching this code paths means all retrans request have been scheduled (and intial_req_list is empty).
+ * Hence, reeschedule ourselves to now + retrans_period. */
+ osmo_timer_schedule(&bts_pag_st->work_timer, retrans_period.tv_sec, retrans_period.tv_nsec / 1000);
+ return num_paged;
+
+sched_next_iter:
+ LOG_BTS(bts_pag_st->bts, DPAG, LOGL_DEBUG, "Scheduling next batch in %lld.%06lds (available_slots=%u)\n",
+ (long long)initial_period.tv_sec, initial_period.tv_nsec / 1000,
+ bts_pag_st->available_slots);
+ osmo_timer_schedule(&bts_pag_st->work_timer, initial_period.tv_sec, initial_period.tv_nsec / 1000);
+ return num_paged;
+}
+
/*
* This is kicked by the periodic PAGING LOAD Indicator
* coming from abis_rsl.c
*
* We attempt to iterate once over the list of items but
- * only upto available_slots.
+ * only up to available_slots.
*/
static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts)
{
- struct gsm_paging_request *request = NULL;
+ unsigned int num_paged_initial, num_paged_retrans = 0;
+ unsigned int max_page_req_per_iter = MAX_PAGE_REQ_PER_ITER;
+ struct timespec now;
+ bool work_done = false;
/*
* Determine if the pending_requests list is empty and
* return then.
*/
- if (llist_empty(&paging_bts->pending_requests)) {
- /* since the list is empty, no need to reschedule the timer */
+ if (llist_empty(&paging_bts->initial_req_list) &&
+ llist_empty(&paging_bts->retrans_req_list)) {
+ /* since the lists are empty, no need to reschedule the timer */
return;
}
- /*
- * In case the BTS does not provide us with load indication and we
- * ran out of slots, call an autofill routine. It might be that the
- * BTS did not like our paging messages and then we have counted down
- * to zero and we do not get any messages.
- */
- if (paging_bts->available_slots == 0) {
- osmo_timer_setup(&paging_bts->credit_timer, paging_give_credit,
- paging_bts);
- osmo_timer_schedule(&paging_bts->credit_timer, 5, 0);
- return;
- }
-
- request = llist_entry(paging_bts->pending_requests.next,
- struct gsm_paging_request, entry);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+ paging_bts->last_sched_ts = now;
- /* we need to determine the number of free channels */
- if (paging_bts->free_chans_need != -1) {
- if (can_send_pag_req(request->bts, request->chan_type) != 0)
- goto skip_paging;
- }
+ num_paged_initial = step_page_initial_reqs(paging_bts, max_page_req_per_iter, &now, &work_done);
+ if (work_done) /* All work done for initial requests, work on retransmissions now: */
+ num_paged_retrans = step_page_retrans_reqs(paging_bts, max_page_req_per_iter - num_paged_initial, &now);
- /* Skip paging if the bts is down. */
- if (!request->bts->oml_link)
- goto skip_paging;
-
- /* handle the paging request now */
- page_ms(request);
- paging_bts->available_slots--;
- request->attempts++;
-
- /* take the current and add it to the back */
- llist_del(&request->entry);
- llist_add_tail(&request->entry, &paging_bts->pending_requests);
-
-skip_paging:
- osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER);
+ LOG_BTS(paging_bts->bts, DPAG, LOGL_DEBUG, "Paged %u subscribers (%u initial, %u retrans) during last iteration\n",
+ num_paged_initial + num_paged_retrans, num_paged_initial, num_paged_retrans);
}
static void paging_worker(void *data)
@@ -238,36 +399,23 @@ static void paging_worker(void *data)
}
/*! initialize the bts paging state, if it hasn't been initialized yet */
-static void paging_init_if_needed(struct gsm_bts *bts)
+void paging_init(struct gsm_bts *bts)
{
- if (bts->paging.bts)
- return;
-
bts->paging.bts = bts;
-
- /* This should be initialized only once. There is currently no code that sets bts->paging.bts
- * back to NULL, so let's just assert this one instead of graceful handling. */
- OSMO_ASSERT(llist_empty(&bts->paging.pending_requests));
-
- osmo_timer_setup(&bts->paging.work_timer, paging_worker,
- &bts->paging);
-
- /* Large number, until we get a proper message */
- bts->paging.available_slots = 20;
+ bts->paging.free_chans_need = -1;
+ paging_set_available_slots(bts, 0);
+ INIT_LLIST_HEAD(&bts->paging.initial_req_list);
+ INIT_LLIST_HEAD(&bts->paging.retrans_req_list);
+ osmo_timer_setup(&bts->paging.work_timer, paging_worker, &bts->paging);
+ osmo_timer_setup(&bts->paging.credit_timer, paging_give_credit, &bts->paging);
}
-/*! do we have any pending paging requests for given subscriber? */
-static int paging_pending_request(struct gsm_bts_paging_state *bts,
- struct bsc_subscr *bsub)
+/* Called upon the bts struct being freed */
+void paging_destructor(struct gsm_bts *bts)
{
- struct gsm_paging_request *req;
-
- llist_for_each_entry(req, &bts->pending_requests, entry) {
- if (bsub == req->bsub)
- return 1;
- }
-
- return 0;
+ paging_flush_bts(bts, NULL);
+ osmo_timer_del(&bts->paging.credit_timer);
+ osmo_timer_del(&bts->paging.work_timer);
}
/*! Call-back once T3113 (paging timeout) expires for given paging_request */
@@ -277,41 +425,72 @@ static void paging_T3113_expired(void *data)
log_set_context(LOG_CTX_BSC_SUBSCR, req->bsub);
- LOGP(DPAG, LOGL_INFO, "T3113 expired for request %p (%s)\n",
- req, bsc_subscr_name(req->bsub));
+ LOG_PAGING_BTS(req, req->bts, DPAG, LOGL_INFO, "T3113 expired\n");
/* must be destroyed before calling cbfn, to prevent double free */
- rate_ctr_inc(&req->bts->bts_ctrs->ctr[BTS_CTR_PAGING_EXPIRED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(req->bts->bts_ctrs, BTS_CTR_PAGING_EXPIRED));
+
+ /* If last BTS paging times out (active_paging_requests will be
+ * decremented in paging_remove_request below): */
+ if (req->bsub->active_paging_requests_len == 1)
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_PAGING_EXPIRED));
/* destroy it now. Do not access req afterwards */
- paging_remove_request(&req->bts->paging, req);
+ paging_remove_request(req);
log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
}
-#define GSM_FRAME_DURATION_us 4615
-#define GSM51_MFRAME_DURATION_us (51 * GSM_FRAME_DURATION_us) /* 235365 us */
-static unsigned int calculate_timer_3113(struct gsm_bts *bts)
-{
- unsigned int to_us, to;
- struct T_def *d = T_def_get_entry(bts->network->T_defs, 3113);
+#define GSM51_MFRAME_DURATION_us (51 * GSM_TDMA_FN_DURATION_uS) /* 235365 us */
+static unsigned int paging_estimate_delay_us(struct gsm_bts *bts, unsigned int num_reqs,
+ unsigned int num_reqs_same_pgroup);
- if (!bts->T3113_dynamic)
- return d->val;
-
- /* TODO: take into account load of paging group for req->bsub */
+static unsigned int calculate_timer_3113(struct gsm_paging_request *req, unsigned int reqs_before,
+ unsigned int reqs_before_same_pgroup, unsigned int max_dynamic_value)
+{
+ unsigned int to_us, estimated_to, to;
+ struct gsm_bts *bts = req->bts;
+ struct osmo_tdef *d = osmo_tdef_get_entry(bts->network->T_defs, 3113);
+ unsigned int rach_max_trans, rach_tx_integer, bs_pa_mfrms;
+
+ /* Note: d should always contain a valid pointer since all timers,
+ * including 3113 are statically pre-defined in
+ * struct osmo_tdef gsm_network_T_defs. */
+ OSMO_ASSERT(d);
+
+ if (!bts->T3113_dynamic) {
+ to = d->val;
+ goto ret;
+ }
/* MFRMS defines repeat interval of paging messages for MSs that belong
- * to same paging group accross multiple 51 frame multiframes.
- * MAXTRANS defines maximum number of RACH retransmissions.
+ * to same paging group across multiple 51 frame multiframes.
+ * MAXTRANS defines maximum number of RACH retransmissions, spread over
+ * TXINTEGER slots.
*/
- to_us = GSM51_MFRAME_DURATION_us * (bts->si_common.chan_desc.bs_pa_mfrms + 2) *
- bts->si_common.rach_control.max_trans;
+ rach_max_trans = rach_max_trans_raw2val(bts->si_common.rach_control.max_trans);
+ rach_tx_integer = rach_tx_integer_raw2val(bts->si_common.rach_control.tx_integer);
+ bs_pa_mfrms = (bts->si_common.chan_desc.bs_pa_mfrms + 2);
+ to_us = GSM51_MFRAME_DURATION_us * bs_pa_mfrms +
+ GSM_TDMA_FN_DURATION_uS * rach_tx_integer * rach_max_trans;
+
+ /* Now add some extra time based on how many requests need to be transmitted before this one: */
+ to_us += paging_estimate_delay_us(bts, reqs_before, reqs_before_same_pgroup);
/* ceiling in seconds + extra time */
- to = (to_us + 999999) / 1000000 + d->val;
- LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Paging request: T3113 expires in %u seconds\n",
- bts->nr, to);
+ estimated_to = (to_us + 999999) / 1000000 + d->val;
+
+ /* upper bound: see X3113, PAGING_THRESHOLD_X3113_DEFAULT_SEC */
+ if (estimated_to > max_dynamic_value)
+ to = max_dynamic_value;
+ else
+ to = estimated_to;
+
+ LOG_PAGING_BTS(req, bts, DPAG, LOGL_DEBUG,
+ "Paging request: T3113 expires in %u seconds (estimated %u)\n",
+ to, estimated_to);
+ret:
+ osmo_stat_item_set(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_T3113), to);
return to;
}
@@ -321,35 +500,88 @@ static unsigned int calculate_timer_3113(struct gsm_bts *bts)
* \param[in] type type of radio channel we're requirign
* \param[in] msc MSC which has issue this paging
* \returns 0 on success, negative on error */
-static int _paging_request(struct gsm_bts *bts, struct bsc_subscr *bsub, int type,
- struct bsc_msc_data *msc)
+static int _paging_request(const struct bsc_paging_params *params, struct gsm_bts *bts)
{
struct gsm_bts_paging_state *bts_entry = &bts->paging;
struct gsm_paging_request *req;
unsigned int t3113_timeout_s;
+ unsigned int x3113_s = osmo_tdef_get(bts->network->T_defs, -3113, OSMO_TDEF_S, -1);
+ uint8_t pgroup;
+ unsigned int reqs_before, reqs_before_same_pgroup;
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ATTEMPTED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_ATTEMPTED));
- if (paging_pending_request(bts_entry, bsub)) {
- LOGP(DPAG, LOGL_INFO, "(bts=%d) Paging request already pending for %s\n",
- bts->nr, bsc_subscr_name(bsub));
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ALREADY]);
+ /* Find if we already have one for the given subscriber on this BTS: */
+ if (bsc_subscr_find_req_by_bts(params->bsub, bts)) {
+ LOG_PAGING_BTS(params, bts, DPAG, LOGL_INFO, "Paging request already pending for this subscriber\n");
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_ALREADY));
return -EEXIST;
}
- LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Start paging of subscriber %s\n", bts->nr,
- bsc_subscr_name(bsub));
+ /* Don't try to queue more requests than we can realistically handle within X3113 seconds,
+ * see PAGING_THRESHOLD_X3113_DEFAULT_SEC. */
+ if (paging_pending_requests_nr(bts) > paging_estimate_available_slots(bts, x3113_s)) {
+ struct gsm_paging_request *first_retrans_req;
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_OVERLOAD));
+ /* Need to drop a retrans from the queue if possible, in order to make space for the new initial req. */
+ if (bts_entry->retrans_req_list_len == 0) {
+ /* There are no retrans to be replaced by this initial request, discard it. */
+ return -ENOSPC;
+ }
+ first_retrans_req = llist_first_entry(&bts_entry->retrans_req_list, struct gsm_paging_request, entry);
+ paging_remove_request(first_retrans_req);
+ }
+
+ pgroup = gsm0502_calc_paging_group(&bts->si_common.chan_desc, str_to_imsi(params->bsub->imsi));
+ reqs_before = bts_entry->initial_req_list_len;
+ reqs_before_same_pgroup = bts_entry->initial_req_pgroup_counts[pgroup];
+
+ LOG_PAGING_BTS(params, bts, DPAG, LOGL_DEBUG, "Start paging\n");
req = talloc_zero(tall_paging_ctx, struct gsm_paging_request);
OSMO_ASSERT(req);
- req->bsub = bsc_subscr_get(bsub);
+ req->reason = params->reason;
+ req->bsub = params->bsub;
req->bts = bts;
- req->chan_type = type;
- req->msc = msc;
+ req->chan_type = params->chan_needed;
+ req->pgroup = pgroup;
+ req->msc = params->msc;
osmo_timer_setup(&req->T3113, paging_T3113_expired, req);
- t3113_timeout_s = calculate_timer_3113(bts);
+ bsc_subscr_add_active_paging_request(req->bsub, req);
+
+ bts_entry->initial_req_list_len++;
+ bts_entry->initial_req_pgroup_counts[req->pgroup]++;
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_PAGING_REQ_QUEUE_LENGTH), 1);
+ llist_add_tail(&req->entry, &bts_entry->initial_req_list);
+
+ t3113_timeout_s = calculate_timer_3113(req, reqs_before, reqs_before_same_pgroup, x3113_s);
osmo_timer_schedule(&req->T3113, t3113_timeout_s, 0);
- llist_add_tail(&req->entry, &bts_entry->pending_requests);
- paging_schedule_if_needed(bts_entry);
+
+ /* Trigger scheduler if needed: */
+ if (!osmo_timer_pending(&bts_entry->work_timer)) {
+ paging_handle_pending_requests(bts_entry);
+ } else if (bts_entry->initial_req_list_len == 1) {
+ /* Worker timer is armed -> there was already one req before
+ * bts_entry->initial_req_list_len == 1 -> There were no initial requests
+ * in the list, aka the timer is waiting for retransmission,
+ * which is a longer period.
+ * Let's recaculate the time to adapt it to initial_period: */
+ struct timespec now, elapsed, tdiff;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+ timespecsub(&now, &bts_entry->last_sched_ts, &elapsed);
+ if (timespeccmp(&elapsed, &initial_period, <)) {
+ timespecsub(&initial_period, &elapsed, &tdiff);
+ } else {
+ tdiff = (struct timespec){.tv_sec = 0, .tv_nsec = 0 };
+ }
+ LOG_PAGING_BTS(req, req->bts, DPAG, LOGL_DEBUG,
+ "New req arrived: re-scheduling next batch in %lld.%06lds\n",
+ (long long)tdiff.tv_sec, tdiff.tv_nsec / 1000);
+ /* Avoid scheduling timer for short periods, run cb directly: */
+ if (tdiff.tv_sec == 0 && tdiff.tv_nsec < 5000)
+ paging_worker(bts_entry);
+ else
+ osmo_timer_schedule(&bts_entry->work_timer, tdiff.tv_sec, tdiff.tv_nsec / 1000);
+ } /* else: worker is already ongoing submitting initial requests, nothing do be done */
return 0;
}
@@ -360,8 +592,7 @@ static int _paging_request(struct gsm_bts *bts, struct bsc_subscr *bsub, int typ
* \param[in] type type of radio channel we're requirign
* \param[in] msc MSC which has issue this paging
* returns 1 on success; 0 in case of error (e.g. TRX down) */
-int paging_request_bts(struct gsm_bts *bts, struct bsc_subscr *bsub, int type,
- struct bsc_msc_data *msc)
+int paging_request_bts(const struct bsc_paging_params *params, struct gsm_bts *bts)
{
int rc;
@@ -369,134 +600,130 @@ int paging_request_bts(struct gsm_bts *bts, struct bsc_subscr *bsub, int type,
if (!trx_is_usable(bts->c0))
return 0;
- /* maybe it is the first time we use it */
- paging_init_if_needed(bts);
-
/* Trigger paging, pass any error to the caller */
- rc = _paging_request(bts, bsub, type, msc);
+ rc = _paging_request(params, bts);
if (rc < 0)
return 0;
return 1;
}
-/*! Stop paging a given subscriber on a given BTS.
- * If \a conn is non-NULL, we also call the paging call-back function
- * to notify the paging originator that paging has completed.
- * \param[in] bts BTS on which we shall stop paging
- * \param[in] bsub subscriber which we shall stop paging
- * \param[in] conn connection to the subscriber (if any)
- * \param[in] msg message received from subscrbier (if any)
- * \returns 0 if an active paging request was stopped, an error code otherwise. */
-/* we consciously ignore the type of the request here */
-static int _paging_request_stop(struct gsm_bts *bts, struct bsc_subscr *bsub,
- struct gsm_subscriber_connection *conn,
- struct msgb *msg)
+/*! Stop paging on all cells and return the MSC that paged (if any) and all pending paging reasons.
+ * \param[out] returns the MSC that paged the subscriber, if there was a pending request.
+ * \param[out] returns the ORed bitmask of all reasons of pending pagings.
+ * \param[in] bts BTS which has received a paging response
+ * \param[in] bsub subscriber
+ */
+void paging_request_stop(struct bsc_msc_data **msc_p, enum bsc_paging_reason *reasons_p,
+ struct gsm_bts *bts, struct bsc_subscr *bsub)
{
- struct gsm_bts_paging_state *bts_entry = &bts->paging;
- struct gsm_paging_request *req, *req2;
-
- paging_init_if_needed(bts);
+ struct bsc_msc_data *paged_from_msc = NULL;
+ enum bsc_paging_reason reasons = BSC_PAGING_NONE;
+ OSMO_ASSERT(bts);
+ struct gsm_paging_request *req = bsc_subscr_find_req_by_bts(bsub, bts);
+
+ /* Avoid accessing bsub after reaching 0 active_paging_request_len,
+ * since it could be freed during put(): */
+ unsigned remaining = bsub->active_paging_requests_len;
+
+ if (req) {
+ paged_from_msc = req->msc;
+ reasons = req->reason;
+ LOG_PAGING_BTS(req, bts, DPAG, LOGL_DEBUG, "Stop paging\n");
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_RESPONDED));
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->network->bsc_ctrs, BSC_CTR_PAGING_RESPONDED));
+ paging_remove_request(req);
+ remaining--;
+ }
- llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests,
- entry) {
- if (req->bsub == bsub) {
- /* now give up the data structure */
- paging_remove_request(&bts->paging, req);
- LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Stop paging %s\n", bts->nr,
- bsc_subscr_name(bsub));
- return 0;
+ while (remaining > 0) {
+ struct gsm_paging_request *req;
+ req = llist_first_entry(&bsub->active_paging_requests,
+ struct gsm_paging_request, bsub_entry);
+ LOG_PAGING_BTS(req, req->bts, DPAG, LOGL_DEBUG, "Stop paging\n");
+ reasons |= req->reason;
+ if (!paged_from_msc) {
+ /* If this happened, it would be a bit weird: it means there was no Paging Request
+ * pending on the BTS that sent the Paging Response, but there *is* a Paging Request
+ * pending on a different BTS. But why not return an MSC when we found one. */
+ paged_from_msc = req->msc;
}
+ paging_remove_request(req);
+ remaining--;
}
- return -ENOENT;
+ *msc_p = paged_from_msc;
+ *reasons_p = reasons;
}
-/*! Stop paging on all other bts'
- * \param[in] bts_list list of BTSs to iterate
- * \param[in] _bts BTS which has received a paging response
- * \param[in] bsub subscriber
- * \param[in] msgb L3 message that we have received from \a bsub on \a _bts */
-void paging_request_stop(struct llist_head *bts_list,
- struct gsm_bts *_bts, struct bsc_subscr *bsub,
- struct gsm_subscriber_connection *conn,
- struct msgb *msg)
+/* Remove all paging requests, for specific reasons only. */
+void paging_request_cancel(struct bsc_subscr *bsub, enum bsc_paging_reason reasons)
{
- struct gsm_bts *bts;
+ struct gsm_paging_request *req, *req2;
+ OSMO_ASSERT(bsub);
- log_set_context(LOG_CTX_BSC_SUBSCR, bsub);
- conn->bsub = bsc_subscr_get(bsub);
- gscon_update_id(conn);
+ /* Avoid accessing bsub after reaching 0 active_paging_request_len,
+ * since it could be freed during put(): */
+ unsigned remaining = bsub->active_paging_requests_len;
- /* Stop this first and dispatch the request */
- if (_bts) {
- if (_paging_request_stop(_bts, bsub, conn, msg) == 0) {
- rate_ctr_inc(&_bts->bts_ctrs->ctr[BTS_CTR_PAGING_RESPONDED]);
- rate_ctr_inc(&_bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_RESPONDED]);
- }
- }
-
- /* Make sure to cancel this everywhere else */
- llist_for_each_entry(bts, bts_list, list) {
- /* Sort of an optimization. */
- if (bts == _bts)
+ llist_for_each_entry_safe(req, req2, &bsub->active_paging_requests, bsub_entry) {
+ if (!(req->reason & reasons))
+ continue;
+ LOG_PAGING_BTS(req, req->bts, DPAG, LOGL_DEBUG, "Cancel paging reasons=0x%x\n",
+ reasons);
+ if (req->reason & ~reasons) {
+ /* Other reasons are active, simply drop the reasons from func arg: */
+ req->reason &= ~reasons;
continue;
- _paging_request_stop(bts, bsub, NULL, NULL);
+ }
+ /* No reason to keep the paging, remove it: */
+ paging_remove_request(req);
+ remaining--;
+ if (remaining == 0)
+ break;
}
- log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
}
-
/*! Update the BTS paging buffer slots on given BTS */
-void paging_update_buffer_space(struct gsm_bts *bts, uint16_t free_slots)
+static void paging_update_buffer_space(struct gsm_bts *bts, uint16_t free_slots)
{
- paging_init_if_needed(bts);
-
- osmo_timer_del(&bts->paging.credit_timer);
- bts->paging.available_slots = free_slots;
- paging_schedule_if_needed(&bts->paging);
+ LOG_BTS(bts, DPAG, LOGL_DEBUG, "Rx CCCH Load Indication from BTS (available_slots %u -> %u)\n",
+ bts->paging.available_slots, free_slots);
+ paging_set_available_slots(bts, free_slots);
+ /* Re-arm credit_timer if needed */
+ if (trx_is_usable(bts->c0)) {
+ paging_schedule_if_needed(&bts->paging);
+ osmo_timer_schedule(&bts->paging.credit_timer,
+ bts_no_ccch_load_ind_timeout_sec(bts), 0);
+ }
}
/*! Count the number of pending paging requests on given BTS */
-unsigned int paging_pending_requests_nr(struct gsm_bts *bts)
+unsigned int paging_pending_requests_nr(const struct gsm_bts *bts)
{
- unsigned int requests = 0;
- struct gsm_paging_request *req;
-
- paging_init_if_needed(bts);
-
- llist_for_each_entry(req, &bts->paging.pending_requests, entry)
- ++requests;
-
- return requests;
-}
-
-/*! Find any paging data for the given subscriber at the given BTS. */
-struct bsc_msc_data *paging_get_msc(struct gsm_bts *bts, struct bsc_subscr *bsub)
-{
- struct gsm_paging_request *req;
-
- llist_for_each_entry(req, &bts->paging.pending_requests, entry)
- if (req->bsub == bsub)
- return req->msc;
-
- return NULL;
+ return bts->paging.initial_req_list_len + bts->paging.retrans_req_list_len;
}
/*! Flush all paging requests at a given BTS for a given MSC (or NULL if all MSC should be flushed). */
void paging_flush_bts(struct gsm_bts *bts, struct bsc_msc_data *msc)
{
struct gsm_paging_request *req, *req2;
+ int num_cancelled = 0;
+ int i;
- paging_init_if_needed(bts);
+ struct llist_head *lists[] = { &bts->paging.initial_req_list, &bts->paging.retrans_req_list };
- llist_for_each_entry_safe(req, req2, &bts->paging.pending_requests, entry) {
- if (msc && req->msc != msc)
- continue;
- /* now give up the data structure */
- LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Stop paging %s (flush)\n", bts->nr,
- bsc_subscr_name(req->bsub));
- paging_remove_request(&bts->paging, req);
+ for (i = 0; i < ARRAY_SIZE(lists); i++) {
+ llist_for_each_entry_safe(req, req2, lists[i], entry) {
+ if (msc && req->msc != msc)
+ continue;
+ /* now give up the data structure */
+ LOG_PAGING_BTS(req, bts, DPAG, LOGL_DEBUG, "Stop paging (flush)\n");
+ paging_remove_request(req);
+ num_cancelled++;
+ }
}
+
+ rate_ctr_add(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_MSC_FLUSH), num_cancelled);
}
/*! Flush all paging requests issued by \a msc on any BTS in \a net */
@@ -507,3 +734,131 @@ void paging_flush_network(struct gsm_network *net, struct bsc_msc_data *msc)
llist_for_each_entry(bts, &net->bts_list, list)
paging_flush_bts(bts, msc);
}
+
+/* Shim to avoid problems when compiling against libosmocore <= 1.7.0, since
+ * gsm0502_get_n_pag_blocks() was not declared const despite being readonly. Once
+ * osmo-bsc depends on libosmocore > 1.7.0, this shim can be dropped. */
+static inline unsigned int _gsm0502_get_n_pag_blocks(const struct gsm48_control_channel_descr *chan_desc)
+{
+ return gsm0502_get_n_pag_blocks((struct gsm48_control_channel_descr *)chan_desc);
+}
+
+/*! Estimate available_slots credit over a time period, used when below CCCH Load Indication Threshold */
+uint16_t paging_estimate_available_slots(const struct gsm_bts *bts, unsigned int time_span_s)
+{
+ unsigned int n_pag_blocks = _gsm0502_get_n_pag_blocks(&bts->si_common.chan_desc);
+ uint16_t available_slots = n_pag_blocks * time_span_s * 1000000 / GSM51_MFRAME_DURATION_us;
+ LOG_BTS(bts, DPAG, LOGL_DEBUG, "Estimated %u paging available_slots over %u seconds\n",
+ available_slots, time_span_s);
+ return available_slots;
+}
+
+/*! Conservative estimate of time needed by BTS to schedule a number of paging
+ * requests (num_reqs), based on current load at the BSC queue (doesn't take into
+ * account BTs own buffer) */
+static unsigned int paging_estimate_delay_us(struct gsm_bts *bts, unsigned int num_reqs,
+ unsigned int num_reqs_same_pgroup)
+{
+ unsigned int n_pag_blocks, n_mframes, time_us = 0;
+
+ n_pag_blocks = _gsm0502_get_n_pag_blocks(&bts->si_common.chan_desc);
+
+ /* First of all, we need to extend the timeout in relation to the amount
+ * of paging requests in the BSC queue. In here we don't care about the
+ * paging group, because they are mixed in the same queue. If we don't
+ * take this into account, it could happen that if lots of requests are
+ * received at the BSC (from MSC) around the same time, they could time
+ * out in the BSC queue before arriving at the BTS. We already account of
+ * same-paging-group ones further below, so don't take them into account
+ * here: */
+ unsigned int num_reqs_other_groups = num_reqs - num_reqs_same_pgroup;
+ time_us += ((num_reqs_other_groups * GSM51_MFRAME_DURATION_us) + (n_pag_blocks - 1)) / n_pag_blocks;
+
+ /* Now we extend the timeout based on the amount of requests of the same
+ * paging group before the present one: */
+ n_mframes = (num_reqs_same_pgroup + (n_pag_blocks - 1)) / n_pag_blocks;
+ time_us += n_mframes * GSM51_MFRAME_DURATION_us;
+ /* the multiframes are not consecutive for a paging group, let's add the spacing between: */
+ if (n_mframes > 1) {
+ unsigned int bs_pa_mfrms = (bts->si_common.chan_desc.bs_pa_mfrms + 2);
+ time_us += (n_mframes - 1) * bs_pa_mfrms * GSM51_MFRAME_DURATION_us;
+ }
+ return time_us;
+}
+
+/* Callback function to be called every time we receive a signal from NM */
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct nm_running_chg_signal_data *nsd;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ unsigned int load_ind_timeout;
+ uint16_t estimated_slots;
+
+ if (signal != S_NM_RUNNING_CHG)
+ return 0;
+
+ nsd = signal_data;
+ bts = nsd->bts;
+
+ switch (nsd->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ trx = (struct gsm_bts_trx *)nsd->obj;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ trx = gsm_bts_bb_trx_get_trx((struct gsm_bts_bb_trx *)nsd->obj);
+ break;
+ default:
+ return 0;
+ }
+
+ /* We only care about state changes of C0. */
+ if (trx != trx->bts->c0)
+ return 0;
+
+ if (nsd->running) {
+ if (trx_is_usable(trx)) {
+ LOG_BTS(bts, DPAG, LOGL_INFO, "C0 becomes available for paging\n");
+ /* Fill in initial credit */
+ load_ind_timeout = bts_no_ccch_load_ind_timeout_sec(bts);
+ estimated_slots = paging_estimate_available_slots(bts, load_ind_timeout);
+ paging_set_available_slots(bts, estimated_slots);
+ /* Start scheduling credit_timer */
+ osmo_timer_schedule(&bts->paging.credit_timer,
+ bts_no_ccch_load_ind_timeout_sec(bts), 0);
+ /* work_timer will be started when new paging requests arrive. */
+ }
+ } else {
+ /* If credit timer was not pending it means C0 was already unavailable before (rcarrier||bbtransc) */
+ if (osmo_timer_pending(&bts->paging.credit_timer)) {
+ LOG_BTS(bts, DPAG, LOGL_INFO, "C0 becomes unavailable for paging\n");
+ /* Note: flushing will osmo_timer_del(&bts->paging.work_timer) when queue becomes empty */
+ paging_flush_bts(bts, NULL);
+ osmo_timer_del(&bts->paging.credit_timer);
+ }
+ }
+ return 0;
+}
+
+/* Callback function to be called every time we receive a signal from CCCH */
+static int ccch_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct ccch_signal_data *sd;
+
+ if (signal != S_CCCH_PAGING_LOAD)
+ return 0;
+
+ sd = signal_data;
+
+ paging_update_buffer_space(sd->bts, sd->pg_buf_space);
+ return 0;
+}
+
+/* To be called once at startup of the process: */
+void paging_global_init(void)
+{
+ osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
+ osmo_signal_register_handler(SS_CCCH, ccch_sig_cb, NULL);
+}
diff --git a/src/osmo-bsc/pcu_sock.c b/src/osmo-bsc/pcu_sock.c
index b71621dae..7b1aeae68 100644
--- a/src/osmo-bsc/pcu_sock.c
+++ b/src/osmo-bsc/pcu_sock.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -30,12 +26,14 @@
#include <sys/socket.h>
#include <sys/un.h>
+#include <osmocom/core/byteswap.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/logging.h>
#include <osmocom/gsm/l1sap.h>
#include <osmocom/gsm/gsm0502.h>
+#include <osmocom/bsc/abis_nm.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/pcu_if.h>
@@ -44,30 +42,29 @@
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/bts_sm.h>
+#include <osmocom/bsc/timeslot_fsm.h>
-static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg);
-uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx);
-int pcu_direct = 1;
+static int pcu_sock_send(struct gsm_network *net, struct msgb *msg);
static const char *sapi_string[] = {
[PCU_IF_SAPI_RACH] = "RACH",
- [PCU_IF_SAPI_AGCH] = "AGCH",
- [PCU_IF_SAPI_PCH] = "PCH",
[PCU_IF_SAPI_BCCH] = "BCCH",
[PCU_IF_SAPI_PDTCH] = "PDTCH",
[PCU_IF_SAPI_PRACH] = "PRACH",
- [PCU_IF_SAPI_PTCCH] = "PTCCH",
- [PCU_IF_SAPI_AGCH_DT] = "AGCH_DT",
+ [PCU_IF_SAPI_PTCCH] = "PTCCH",
+ [PCU_IF_SAPI_PCH_2] = "PCH_2",
+ [PCU_IF_SAPI_AGCH_2] = "AGCH_2",
};
-/* Check if BTS has a PCU connection */
-static bool pcu_connected(struct gsm_bts *bts)
+bool pcu_connected(const struct gsm_network *net)
{
- struct pcu_sock_state *state = bts->pcu_state;
+ struct pcu_sock_state *state = net->pcu_state;
if (!state)
return false;
- if (state->conn_bfd.fd <= 0)
+ if (state->upqueue.bfd.fd <= 0)
return false;
return true;
}
@@ -94,6 +91,87 @@ struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr)
return msg;
}
+/* Check if the timeslot can be utilized as PDCH now
+ * (PDCH is currently active on BTS) */
+static bool ts_now_usable_as_pdch(const struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan_is) {
+ case GSM_PCHAN_PDCH:
+ /* NOTE: We currently only support Ericsson RBS as a BSC
+ * co-located BTS. This BTS only supports dynamic channels. */
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* Check if it is possible to use the TS as PDCH (not now, but maybe later) */
+static bool ts_usable_as_pdch(const struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan_from_config) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
+ case GSM_PCHAN_PDCH:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* Fill the frequency hopping parameter */
+static void info_ind_fill_fhp(struct gsm_pcu_if_info_trx_ts *ts_info,
+ const struct gsm_bts_trx_ts *ts)
+{
+ ts_info->maio = ts->hopping.maio;
+ ts_info->hsn = ts->hopping.hsn;
+ ts_info->hopping = 0x1;
+
+ memcpy(&ts_info->ma, ts->hopping.ma_data, ts->hopping.ma_len);
+ ts_info->ma_bit_len = ts->hopping.ma_len * 8 - ts->hopping.ma.cur_bit;
+}
+
+/* Fill the TRX parameter */
+static void info_ind_fill_trx(struct gsm_pcu_if_info_trx *trx_info, const struct gsm_bts_trx *trx)
+{
+ unsigned int tn;
+ const struct gsm_bts_trx_ts *ts;
+
+ trx_info->hlayer1 = 0x2342;
+ trx_info->pdch_mask = 0;
+ trx_info->arfcn = trx->arfcn;
+
+ if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED ||
+ trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) {
+ LOG_TRX(trx, DPCU, LOGL_INFO, "unavailable for PCU (op=%s adm=%s)\n",
+ abis_nm_opstate_name(trx->mo.nm_state.operational),
+ abis_nm_admin_name(trx->mo.nm_state.administrative));
+ return;
+ }
+
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ ts = &trx->ts[tn];
+ if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
+ if (!ts_now_usable_as_pdch(ts))
+ continue;
+
+ trx_info->pdch_mask |= (1 << tn);
+ trx_info->ts[tn].tsc =
+ (ts->tsc >= 0) ? ts->tsc : trx->bts->bsic & 7;
+
+ if (ts->hopping.enabled)
+ info_ind_fill_fhp(&trx_info->ts[tn], ts);
+
+ LOG_TRX(trx, DPCU, LOGL_INFO, "PDCH on ts=%u is available (tsc=%u ", ts->nr,
+ trx_info->ts[tn].tsc);
+ if (ts->hopping.enabled)
+ LOGPC(DPCU, LOGL_INFO, "hopping=yes hsn=%u maio=%u ma_bit_len=%u)\n",
+ ts->hopping.hsn, ts->hopping.maio, trx_info->ts[tn].ma_bit_len);
+ else
+ LOGPC(DPCU, LOGL_INFO, "hopping=no arfcn=%u)\n", trx->arfcn);
+ }
+}
+
/* Send BTS properties to the PCU */
static int pcu_tx_info_ind(struct gsm_bts *bts)
{
@@ -101,15 +179,14 @@ static int pcu_tx_info_ind(struct gsm_bts *bts)
struct gsm_pcu_if *pcu_prim;
struct gsm_pcu_if_info_ind *info_ind;
struct gprs_rlc_cfg *rlcc;
- struct gsm_bts_gprs_nsvc *nsvc;
+ struct gsm_bts_sm *bts_sm;
+ struct gsm_gprs_nsvc *nsvc;
struct gsm_bts_trx *trx;
- struct gsm_bts_trx_ts *ts;
- int i, j;
+ int i;
- OSMO_ASSERT(bts);
- OSMO_ASSERT(bts->network);
+ bts_sm = bts->site_mgr;
- LOGP(DPCU, LOGL_INFO, "Sending info for BTS %d\n",bts->nr);
+ LOG_BTS(bts, DPCU, LOGL_INFO, "Sending info for BTS\n");
rlcc = &bts->gprs.cell.rlc_cfg;
@@ -121,9 +198,7 @@ static int pcu_tx_info_ind(struct gsm_bts *bts)
info_ind = &pcu_prim->u.info_ind;
info_ind->version = PCU_IF_VERSION;
info_ind->flags |= PCU_IF_FLAG_ACTIVE;
-
- if (pcu_direct)
- info_ind->flags |= PCU_IF_FLAG_SYSMO;
+ info_ind->flags |= PCU_IF_FLAG_DIRECT_PHY;
/* RAI */
info_ind->mcc = bts->network->plmn.mcc;
@@ -133,11 +208,12 @@ static int pcu_tx_info_ind(struct gsm_bts *bts)
info_ind->rac = bts->gprs.rac;
/* NSE */
- info_ind->nsei = bts->gprs.nse.nsei;
- memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7);
+ info_ind->nsei = bts_sm->gprs.nse.nsei;
+ memcpy(info_ind->nse_timer, bts_sm->gprs.nse.timer, 7);
memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11);
/* cell attributes */
+ info_ind->bsic = bts->bsic;
info_ind->cell_id = bts->cell_identity;
info_ind->repeat_time = rlcc->paging.repeat_time;
info_ind->repeat_count = rlcc->paging.repeat_count;
@@ -179,52 +255,158 @@ static int pcu_tx_info_ind(struct gsm_bts *bts)
if (rlcc->cs_mask & (1 << GPRS_MCS9))
info_ind->flags |= PCU_IF_FLAG_MCS9;
}
-#warning "isn't dl_tbf_ext wrong?: * 10 and no ntohs"
+ /* TODO: isn't dl_tbf_ext wrong?: * 10 and no ntohs */
info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT];
-#warning "isn't ul_tbf_ext wrong?: * 10 and no ntohs"
+ /* TODO: isn't ul_tbf_ext wrong?: * 10 and no ntohs */
info_ind->ul_tbf_ext = rlcc->parameter[T_UL_TBF_EXT];
info_ind->initial_cs = rlcc->initial_cs;
info_ind->initial_mcs = rlcc->initial_mcs;
/* NSVC */
for (i = 0; i < ARRAY_SIZE(info_ind->nsvci); i++) {
- nsvc = &bts->gprs.nsvc[i];
+ nsvc = &bts->site_mgr->gprs.nsvc[i];
+
info_ind->nsvci[i] = nsvc->nsvci;
info_ind->local_port[i] = nsvc->local_port;
- info_ind->remote_port[i] = nsvc->remote_port;
- info_ind->remote_ip[i] = nsvc->remote_ip;
+ switch (nsvc->remote.u.sas.ss_family) {
+ case AF_INET:
+ info_ind->address_type[i] = PCU_IF_ADDR_TYPE_IPV4;
+ info_ind->remote_ip[i].v4 = nsvc->remote.u.sin.sin_addr;
+ info_ind->remote_port[i] = ntohs(nsvc->remote.u.sin.sin_port);
+ break;
+ case AF_INET6:
+ info_ind->address_type[i] = PCU_IF_ADDR_TYPE_IPV6;
+ memcpy(&info_ind->remote_ip[i].v6,
+ &nsvc->remote.u.sin6.sin6_addr,
+ sizeof(struct in6_addr));
+ info_ind->remote_port[i] = ntohs(nsvc->remote.u.sin6.sin6_port);
+ break;
+ default:
+ info_ind->address_type[i] = PCU_IF_ADDR_TYPE_UNSPEC;
+ break;
+ }
}
for (i = 0; i < ARRAY_SIZE(info_ind->trx); i++) {
trx = gsm_bts_trx_num(bts, i);
if (!trx)
continue;
- info_ind->trx[i].hlayer1 = 0x2342;
- info_ind->trx[i].pdch_mask = 0;
- info_ind->trx[i].arfcn = trx->arfcn;
- for (j = 0; j < ARRAY_SIZE(trx->ts); j++) {
- ts = &trx->ts[j];
- if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
- && ts->pchan_is == GSM_PCHAN_PDCH) {
- info_ind->trx[i].pdch_mask |= (1 << j);
- info_ind->trx[i].tsc[j] =
- (ts->tsc >= 0) ? ts->tsc : bts->bsic & 7;
- LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: "
- "available (tsc=%d arfcn=%d)\n",
- trx->nr, ts->nr,
- info_ind->trx[i].tsc[j],
- info_ind->trx[i].arfcn);
- }
+ if (trx->nr >= ARRAY_SIZE(info_ind->trx)) {
+ LOG_TRX(trx, DPCU, LOGL_NOTICE, "PCU interface (version %u) "
+ "cannot handle more than %zu transceivers => skipped\n",
+ PCU_IF_VERSION, ARRAY_SIZE(info_ind->trx));
+ break;
+ }
+ info_ind_fill_trx(&info_ind->trx[trx->nr], trx);
+ }
+
+ switch (bts->type) {
+ case GSM_BTS_TYPE_RBS2000:
+ info_ind->bts_model = PCU_IF_BTS_MODEL_RBS;
+ break;
+ default:
+ info_ind->bts_model = PCU_IF_BTS_MODEL_UNSPEC;
+ }
+
+ return pcu_sock_send(bts->network, msg);
+}
+
+static int pcu_tx_e1_ccu_ind(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct gsm_pcu_if_e1_ccu_ind *e1_ccu_ind;
+ int i;
+
+ if (trx->nr >= PCU_IF_NUM_TRX) {
+ LOG_TRX(trx, DPCU, LOGL_NOTICE, "PCU interface (version %u) "
+ "cannot handle more than %u transceivers => skipped\n",
+ PCU_IF_VERSION, PCU_IF_NUM_TRX);
+ continue;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_bts_trx_ts *ts;
+ struct msgb *msg;
+ int rc;
+
+ ts = &trx->ts[i];
+
+ if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED)
+ continue;
+ if (!ts_usable_as_pdch(ts))
+ continue;
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_E1_CCU_IND, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *)msg->data;
+ e1_ccu_ind = &pcu_prim->u.e1_ccu_ind;
+ e1_ccu_ind->ts_nr = ts->nr;
+ e1_ccu_ind->trx_nr = trx->nr;
+ e1_ccu_ind->e1_nr = ts->e1_link.e1_nr;
+ e1_ccu_ind->e1_ts = ts->e1_link.e1_ts;
+ e1_ccu_ind->e1_ts_ss = ts->e1_link.e1_ts_ss;
+
+ LOG_TRX(trx, DPCU, LOGL_INFO, "Sending E1 CCU info for TS %d\n", e1_ccu_ind->ts_nr);
+ rc = pcu_sock_send(bts->network, msg);
+ if (rc < 0)
+ return -EINVAL;
}
}
- return pcu_sock_send(bts, msg);
+ return 0;
}
-void pcu_info_update(struct gsm_bts *bts)
+/* Allow test to overwrite it */
+__attribute__((weak)) void pcu_info_update(struct gsm_bts *bts)
{
- if (pcu_connected(bts))
- pcu_tx_info_ind(bts);
+ if (pcu_connected(bts->network)) {
+ if (bsc_co_located_pcu(bts)) {
+ /* In cases where the CCU is connected via an E1 line, we transmit the connection parameters for the
+ * PDCH before we announce the other BTS related parameters. */
+ if (is_e1_bts(bts))
+ pcu_tx_e1_ccu_ind(bts);
+ pcu_tx_info_ind(bts);
+ }
+ }
+}
+
+static int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len,
+ int8_t rssi, uint16_t ber10k, int16_t bto, int16_t lqual)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_data *data_ind;
+ struct gsm_bts *bts = ts->trx->bts;
+
+ LOGP(DPCU, LOGL_DEBUG, "Sending data indication: sapi=%s arfcn=%d block=%d data=%s\n",
+ sapi_string[sapi], arfcn, block_nr, osmo_hexdump(data, len));
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_IND, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ data_ind = &pcu_prim->u.data_ind;
+
+ data_ind->sapi = sapi;
+ data_ind->rssi = rssi;
+ data_ind->fn = fn;
+ data_ind->arfcn = arfcn;
+ data_ind->trx_nr = ts->trx->nr;
+ data_ind->ts_nr = ts->nr;
+ data_ind->block_nr = block_nr;
+ data_ind->ber10k = ber10k;
+ data_ind->ta_offs_qbits = bto;
+ data_ind->lqual_cb = lqual;
+ if (len)
+ memcpy(data_ind->data, data, len);
+ data_ind->len = len;
+
+ return pcu_sock_send(bts->network, msg);
}
/* Forward rach indication to PCU */
@@ -236,13 +418,12 @@ int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn,
struct gsm_pcu_if_rach_ind *rach_ind;
/* Bail if no PCU is connected */
- if (!pcu_connected(bts)) {
- LOGP(DRSL, LOGL_ERROR, "BTS %d CHAN RQD(GPRS) but PCU not "
- "connected!\n", bts->nr);
+ if (!pcu_connected(bts->network)) {
+ LOG_BTS(bts, DRSL, LOGL_ERROR, "CHAN RQD(GPRS) but PCU not connected!\n");
return -ENODEV;
}
- LOGP(DPCU, LOGL_INFO, "Sending RACH indication: qta=%d, ra=%d, "
+ LOG_BTS(bts, DPCU, LOGL_INFO, "Sending RACH indication: qta=%d, ra=%d, "
"fn=%d\n", qta, ra, fn);
msg = pcu_msgb_alloc(PCU_IF_MSG_RACH_IND, bts->nr);
@@ -258,68 +439,71 @@ int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn,
rach_ind->is_11bit = is_11bit;
rach_ind->burst_type = burst_type;
- return pcu_sock_send(bts, msg);
+ return pcu_sock_send(bts->network, msg);
}
-/* Confirm the sending of an immediate assignment to the pcu */
-int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli)
+int pcu_tx_data_cnf(struct gsm_bts *bts, uint32_t msg_id, uint8_t sapi)
{
struct msgb *msg;
struct gsm_pcu_if *pcu_prim;
- struct gsm_pcu_if_data_cnf_dt *data_cnf_dt;
+ struct gsm_pcu_if_data_cnf *data_cnf;
- LOGP(DPCU, LOGL_INFO, "Sending PCH confirm with direct TLLI\n");
+ LOGP(DPCU, LOGL_DEBUG, "Sending DATA.cnf: sapi=%s msg_id=%08x\n",
+ sapi_string[sapi], msg_id);
- msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_DT, bts->nr);
+ msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_2, bts->nr);
if (!msg)
return -ENOMEM;
pcu_prim = (struct gsm_pcu_if *) msg->data;
- data_cnf_dt = &pcu_prim->u.data_cnf_dt;
+ data_cnf = &pcu_prim->u.data_cnf2;
- data_cnf_dt->sapi = PCU_IF_SAPI_PCH;
- data_cnf_dt->tlli = tlli;
+ data_cnf->sapi = sapi;
+ data_cnf->msg_id = msg_id;
- return pcu_sock_send(bts, msg);
+ return pcu_sock_send(bts->network, msg);
}
-/* we need to decode the raw RR paging messsage (see PCU code
+/* we need to decode the raw RR paging message (see PCU code
* Encoding::write_paging_request) and extract the mobile identity
* (P-TMSI) from it */
-static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group,
- const uint8_t *raw_rr_msg)
+static int pcu_rx_rr_paging_pch(struct gsm_bts *bts, uint8_t paging_group,
+ const struct gsm_pcu_if_pch *pch)
{
- struct gsm48_paging1 *p1 = (struct gsm48_paging1 *) raw_rr_msg;
+ struct gsm48_paging1 *p1 = (struct gsm48_paging1 *) pch->data;
uint8_t chan_needed;
- unsigned int mi_len;
- uint8_t *mi;
+ struct osmo_mobile_identity mi;
int rc;
switch (p1->msg_type) {
case GSM48_MT_RR_PAG_REQ_1:
chan_needed = (p1->cneed2 << 2) | p1->cneed1;
- mi_len = p1->data[0];
- mi = p1->data+1;
- LOGP(DPCU, LOGL_ERROR, "PCU Sends paging "
- "request type %02x (chan_needed=%02x, mi_len=%u, mi=%s)\n",
- p1->msg_type, chan_needed, mi_len,
- osmo_hexdump_nospc(mi,mi_len));
+ rc = osmo_mobile_identity_decode(&mi, p1->data+1, p1->data[0], false);
+ if (rc) {
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU Sends paging "
+ "request type %02x (chan_needed=%02x): Unable to decode Mobile Identity\n",
+ p1->msg_type, chan_needed);
+ rc = -EINVAL;
+ break;
+ }
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU Sends paging "
+ "request type %02x (chan_needed=%02x, mi=%s)\n",
+ p1->msg_type, chan_needed, osmo_mobile_identity_to_str_c(OTC_SELECT, &mi));
/* NOTE: We will have to add 2 to mi_len and subtract 2 from
* the mi pointer because rsl_paging_cmd() will perform the
* reverse operations. This is because rsl_paging_cmd() is
* normally expected to chop off the element identifier (0xC0)
* and the length field. In our parameter, we do not have
* those fields included. */
- rc = rsl_paging_cmd(bts, paging_group, mi_len+2, mi-2,
- chan_needed, true);
+ rc = rsl_paging_cmd(bts, paging_group, &mi, chan_needed, true);
break;
case GSM48_MT_RR_PAG_REQ_2:
case GSM48_MT_RR_PAG_REQ_3:
- LOGP(DPCU, LOGL_ERROR, "PCU Sends unsupported paging "
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU Sends unsupported paging "
"request type %02x\n", p1->msg_type);
rc = -EINVAL;
break;
default:
- LOGP(DPCU, LOGL_ERROR, "PCU Sends unknown paging "
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU Sends unknown paging "
"request type %02x\n", p1->msg_type);
rc = -EINVAL;
break;
@@ -328,77 +512,76 @@ static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group,
return rc;
}
+static int pcu_rx_rr_imm_ass_pch(struct gsm_bts *bts, uint8_t paging_group,
+ const struct gsm_pcu_if_pch *pch, bool confirm)
+{
+ LOG_BTS(bts, DPCU, LOGL_DEBUG, "PCU Sends immediate assignment via PCH (msg_id=0x%08x, IMSI=%s, Paging group=0x%02x)\n",
+ pch->msg_id, pch->imsi, paging_group);
+
+ /* NOTE: Sending an IMMEDIATE ASSIGNMENT via PCH became necessary with GPRS in order to be able to
+ * assign downlink TBFs directly through the paging channel. However, this method never became part
+ * of the RSL specs. This means that each BTS vendor has to come up with a proprietary method. At
+ * the moment we only support Ericsson RBS here. */
+ if (is_ericsson_bts(bts))
+ return rsl_ericsson_imm_assign_cmd(bts, pch->msg_id, sizeof(pch->data), pch->data, paging_group,
+ confirm);
+
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "BTS model does not support sending immediate assignment via PCH!\n");
+ return -ENOTSUP;
+}
+
static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type,
struct gsm_pcu_if_data *data_req)
{
- struct msgb *msg;
- char imsi_digit_buf[4];
- uint32_t tlli = -1;
uint8_t pag_grp;
int rc = 0;
+ const struct gsm_pcu_if_pch *pch;
+ const struct gsm_pcu_if_agch *agch;
+ const struct gsm48_imm_ass *gsm48_imm_ass;
- LOGP(DPCU, LOGL_DEBUG, "Data request received: sapi=%s arfcn=%d "
+ LOG_BTS(bts, DPCU, LOGL_DEBUG, "Data request received: sapi=%s arfcn=%d "
"block=%d data=%s\n", sapi_string[data_req->sapi],
data_req->arfcn, data_req->block_nr,
osmo_hexdump(data_req->data, data_req->len));
switch (data_req->sapi) {
- case PCU_IF_SAPI_PCH:
- /* the first three bytes are the last three digits of
- * the IMSI, which we need to compute the paging group */
- imsi_digit_buf[0] = data_req->data[0];
- imsi_digit_buf[1] = data_req->data[1];
- imsi_digit_buf[2] = data_req->data[2];
- imsi_digit_buf[3] = '\0';
- LOGP(DPCU, LOGL_DEBUG, "SAPI PCH imsi %s\n", imsi_digit_buf);
- pag_grp = gsm0502_calc_paging_group(&bts->si_common.chan_desc,
- str_to_imsi(imsi_digit_buf));
- pcu_rx_rr_paging(bts, pag_grp, data_req->data+3);
- break;
- case PCU_IF_SAPI_AGCH:
- msg = msgb_alloc(data_req->len, "pcu_agch");
- if (!msg) {
- rc = -ENOMEM;
+ case PCU_IF_SAPI_AGCH_2:
+ if (data_req->len < sizeof(struct gsm_pcu_if_agch)) {
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "Received PCU data request with invalid/small length %d\n",
+ data_req->len);
break;
}
- msg->l3h = msgb_put(msg, data_req->len);
- memcpy(msg->l3h, data_req->data, data_req->len);
- if (rsl_imm_assign_cmd(bts, msg->len, msg->data)) {
- msgb_free(msg);
- rc = -EIO;
- }
- break;
- case PCU_IF_SAPI_AGCH_DT:
- /* DT = direct tlli. A tlli is prefixed */
+ agch = (struct gsm_pcu_if_agch *)data_req->data;
+ if (rsl_imm_assign_cmd(bts, GSM_MACBLOCK_LEN, agch->data))
+ return -EIO;
- if (data_req->len < 5) {
- LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
- "invalid/small length %d\n", data_req->len);
+ /* Send the confirmation immediately. This is as accurate as we can get since from this point on the
+ * BTS hardware is responsible to schedule the sending of the IMMEDIATE ASSIGNMENT */
+ if (agch->confirm)
+ return pcu_tx_data_cnf(bts, agch->msg_id, PCU_IF_SAPI_AGCH_2);
+ break;
+ case PCU_IF_SAPI_PCH_2:
+ if (data_req->len < sizeof(struct gsm_pcu_if_pch)) {
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "Received PCU data request with invalid/small length %d\n",
+ data_req->len);
break;
}
- memcpy(&tlli, data_req->data, 4);
- msg = msgb_alloc(data_req->len - 4, "pcu_agch");
- if (!msg) {
- rc = -ENOMEM;
- break;
- }
- msg->l3h = msgb_put(msg, data_req->len - 4);
- memcpy(msg->l3h, data_req->data + 4, data_req->len - 4);
+ pch = (struct gsm_pcu_if_pch *)data_req->data;
+ pag_grp = gsm0502_calc_paging_group(&bts->si_common.chan_desc, str_to_imsi(pch->imsi));
+ gsm48_imm_ass = (struct gsm48_imm_ass *)pch->data;
- if (bts->type == GSM_BTS_TYPE_RBS2000)
- rc = rsl_ericsson_imm_assign_cmd(bts, tlli, msg->len, msg->data);
- else
- rc = rsl_imm_assign_cmd(bts, msg->len, msg->data);
+ if (gsm48_imm_ass->msg_type == GSM48_MT_RR_IMM_ASS)
+ return pcu_rx_rr_imm_ass_pch(bts, pag_grp, pch, pch->confirm);
- if (rc) {
- msgb_free(msg);
- rc = -EIO;
- }
+ if (pcu_rx_rr_paging_pch(bts, pag_grp, pch))
+ return -EIO;
+ if (pch->confirm)
+ return pcu_tx_data_cnf(bts, pch->msg_id, PCU_IF_SAPI_PCH_2);
break;
default:
- LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "Received PCU data request with "
"unsupported sapi %d\n", data_req->sapi);
rc = -EINVAL;
}
@@ -406,22 +589,123 @@ static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type,
return rc;
}
+static int pcu_tx_si(const struct gsm_bts *bts, enum osmo_sysinfo_type si_type, bool enable)
+{
+ /* the SI is per-BTS so it doesn't matter which TRX we use */
+ struct gsm_bts_trx *trx = bts->c0;
+
+ uint8_t si_buf[GSM_MACBLOCK_LEN];
+ uint8_t len;
+ int rc;
+
+ if (enable) {
+ memcpy(si_buf, GSM_BTS_SI(bts, si_type), GSM_MACBLOCK_LEN);
+ len = GSM_MACBLOCK_LEN;
+ LOG_BTS(bts, DPCU, LOGL_DEBUG, "Updating SI%s to PCU: %s\n",
+ get_value_string(osmo_sitype_strs, si_type),
+ osmo_hexdump_nospc(si_buf, GSM_MACBLOCK_LEN));
+ } else {
+ si_buf[0] = si_type;
+ len = 1;
+
+ /* Note: SI13 is the only system information type that is revked
+ * by just sending a completely empty message. This is due to
+ * historical reasons */
+ if (si_type != SYSINFO_TYPE_13)
+ len = 0;
+
+ LOG_BTS(bts, DPCU, LOGL_DEBUG, "Revoking SI%s from PCU\n",
+ get_value_string(osmo_sitype_strs, si_buf[0]));
+ }
+
+ /* The low-level data like FN, ARFCN etc will be ignored but we have to
+ * set lqual high enough to bypass the check at lower levels */
+ rc = pcu_tx_data_ind(&trx->ts[0], PCU_IF_SAPI_BCCH, 0, 0, 0, si_buf, len,
+ 0, 0, 0, INT16_MAX);
+ if (rc < 0)
+ LOG_BTS(bts, DPCU, LOGL_NOTICE, "Failed to send SI%s to PCU: rc=%d\n",
+ get_value_string(osmo_sitype_strs, si_type), rc);
+
+ return rc;
+}
+
+static int pcu_tx_si_all(struct gsm_bts *bts)
+{
+ const enum osmo_sysinfo_type si_types[] = { SYSINFO_TYPE_1, SYSINFO_TYPE_2, SYSINFO_TYPE_3, SYSINFO_TYPE_13 };
+ unsigned int i;
+ int rc = 0;
+
+ for (i = 0; i < ARRAY_SIZE(si_types); i++) {
+ if (GSM_BTS_HAS_SI(bts, si_types[i])) {
+ rc = pcu_tx_si(bts, si_types[i], true);
+ if (rc < 0)
+ return rc;
+ } else {
+ LOG_BTS(bts, DPCU, LOGL_INFO,
+ "SI%s is not available on PCU connection\n",
+ get_value_string(osmo_sitype_strs, si_types[i]));
+ }
+ }
+
+ return 0;
+}
+
+static int pcu_rx_txt_ind(struct gsm_bts *bts,
+ const struct gsm_pcu_if_txt_ind *txt)
+{
+ int rc;
+
+ switch (txt->type) {
+ case PCU_VERSION:
+ LOG_BTS(bts, DPCU, LOGL_INFO, "OsmoPCU version %s connected\n",
+ txt->text);
+ rc = pcu_tx_si_all(bts);
+ if (rc < 0)
+ return -EINVAL;
+ break;
+ case PCU_OML_ALERT:
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "PCU external alarm: %s\n", txt->text);
+ break;
+ default:
+ LOG_BTS(bts, DPCU, LOGL_ERROR, "Unknown TXT_IND type %u received\n",
+ txt->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define CHECK_IF_MSG_SIZE(prim_len, prim_msg) \
+ do { \
+ size_t _len = PCUIF_HDR_SIZE + sizeof(prim_msg); \
+ if (prim_len < _len) { \
+ LOGP(DPCU, LOGL_ERROR, "Received %zu bytes on PCU Socket, but primitive %s " \
+ "size is %zu, discarding\n", prim_len, #prim_msg, _len); \
+ return -EINVAL; \
+ } \
+ } while (0)
static int pcu_rx(struct gsm_network *net, uint8_t msg_type,
- struct gsm_pcu_if *pcu_prim)
+ struct gsm_pcu_if *pcu_prim, size_t prim_len)
{
int rc = 0;
struct gsm_bts *bts;
- /* FIXME: allow multiple BTS */
- bts = llist_entry(net->bts_list.next, struct gsm_bts, list);
+ bts = gsm_bts_num(net, pcu_prim->bts_nr);
+ if (!bts)
+ return -EINVAL;
switch (msg_type) {
case PCU_IF_MSG_DATA_REQ:
case PCU_IF_MSG_PAG_REQ:
+ CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.data_req);
rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req);
break;
+ case PCU_IF_MSG_TXT_IND:
+ CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.txt_ind);
+ rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind);
+ break;
default:
- LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n",
+ LOGP(DPCU, LOGL_ERROR, "Received unknown PCU msg type %d\n",
msg_type);
rc = -EINVAL;
}
@@ -429,15 +713,18 @@ static int pcu_rx(struct gsm_network *net, uint8_t msg_type,
return rc;
}
+static void pcu_sock_close(struct pcu_sock_state *state);
+
/*
* PCU socket interface
*/
-static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg)
+static int pcu_sock_send(struct gsm_network *net, struct msgb *msg)
{
- struct pcu_sock_state *state = bts->pcu_state;
+ struct pcu_sock_state *state = net->pcu_state;
struct osmo_fd *conn_bfd;
struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data;
+ int rc;
if (!state) {
if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
@@ -446,7 +733,7 @@ static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg)
msgb_free(msg);
return -EINVAL;
}
- conn_bfd = &state->conn_bfd;
+ conn_bfd = &state->upqueue.bfd;
if (conn_bfd->fd <= 0) {
if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, "
@@ -454,31 +741,25 @@ static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg)
msgb_free(msg);
return -EIO;
}
- msgb_enqueue(&state->upqueue, msg);
- conn_bfd->when |= BSC_FD_WRITE;
+ rc = osmo_wqueue_enqueue(&state->upqueue, msg);
+ if (rc < 0) {
+ if (rc == -ENOSPC)
+ LOGP(DPCU, LOGL_NOTICE,
+ "PCU not reacting (more than %u messages waiting). Closing connection\n",
+ state->upqueue.max_length);
+ pcu_sock_close(state);
+ msgb_free(msg);
+ return rc;
+ }
+
return 0;
}
-static void pcu_sock_close(struct pcu_sock_state *state)
+static void pdch_deact_bts(struct gsm_bts *bts)
{
- struct osmo_fd *bfd = &state->conn_bfd;
- struct gsm_bts *bts;
struct gsm_bts_trx *trx;
- struct gsm_bts_trx_ts *ts;
- int i, j;
-
- /* FIXME: allow multiple BTS */
- bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list);
-
- LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n");
-
- close(bfd->fd);
- bfd->fd = -1;
- osmo_fd_unregister(bfd);
-
- /* re-enable the generation of ACCEPT for new connections */
- state->listen_bfd.when |= BSC_FD_READ;
+ int j;
#if 0
/* remove si13, ... */
@@ -487,24 +768,41 @@ static void pcu_sock_close(struct pcu_sock_state *state)
#endif
/* release PDCH */
- for (i = 0; i < 8; i++) {
- trx = gsm_bts_trx_num(bts, i);
- if (!trx)
- break;
- for (j = 0; j < 8; j++) {
- ts = &trx->ts[j];
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (j = 0; j < ARRAY_SIZE(trx->ts); j++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[j];
+ /* BSC co-located PCU applies only to Ericsson RBS, which supports only GSM_PCHAN_OSMO_DYN.
+ * So we need to deact only on this pchan kind. */
if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
- && ts->pchan_is == GSM_PCHAN_PDCH) {
- printf("l1sap_chan_rel(trx,gsm_lchan2chan_nr(ts->lchan));\n");
+ && ts->pchan_on_init == GSM_PCHAN_OSMO_DYN) {
+ ts_pdch_deact(ts);
}
}
}
+}
- /* flush the queue */
- while (!llist_empty(&state->upqueue)) {
- struct msgb *msg = msgb_dequeue(&state->upqueue);
- msgb_free(msg);
+static void pcu_sock_close(struct pcu_sock_state *state)
+{
+ struct osmo_fd *bfd = &state->upqueue.bfd;
+ struct gsm_bts *bts;
+
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n");
+
+ osmo_fd_unregister(bfd);
+ close(bfd->fd);
+ bfd->fd = -1;
+
+ /* re-enable the generation of ACCEPT for new connections */
+ osmo_fd_read_enable(&state->listen_bfd);
+
+ /* Disable all PDCHs on all BTSs that are served by the PCU */
+ llist_for_each_entry(bts, &state->net->bts_list, list) {
+ if (bsc_co_located_pcu(bts))
+ pdch_deact_bts(bts);
}
+
+ /* flush the queue */
+ osmo_wqueue_clear(&state->upqueue);
}
static int pcu_sock_read(struct osmo_fd *bfd)
@@ -514,7 +812,7 @@ static int pcu_sock_read(struct osmo_fd *bfd)
struct msgb *msg;
int rc;
- msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx");
+ msg = msgb_alloc(sizeof(*pcu_prim) + 1000, "pcu_sock_rx");
if (!msg)
return -ENOMEM;
@@ -525,12 +823,21 @@ static int pcu_sock_read(struct osmo_fd *bfd)
goto close;
if (rc < 0) {
- if (errno == EAGAIN)
+ if (errno == EAGAIN) {
+ msgb_free(msg);
return 0;
+ }
goto close;
}
- rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim);
+ if (rc < PCUIF_HDR_SIZE) {
+ LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive hdr size "
+ "is %zu, discarding\n", rc, PCUIF_HDR_SIZE);
+ msgb_free(msg);
+ return 0;
+ }
+
+ rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim, rc);
/* as we always synchronously process the message in pcu_rx() and
* its callbacks, we can free the message here. */
@@ -544,45 +851,22 @@ close:
return -1;
}
-static int pcu_sock_write(struct osmo_fd *bfd)
+static int pcu_sock_write(struct osmo_fd *bfd, struct msgb *msg)
{
struct pcu_sock_state *state = bfd->data;
int rc;
- while (!llist_empty(&state->upqueue)) {
- struct msgb *msg, *msg2;
- struct gsm_pcu_if *pcu_prim;
-
- /* peek at the beginning of the queue */
- msg = llist_entry(state->upqueue.next, struct msgb, list);
- pcu_prim = (struct gsm_pcu_if *)msg->data;
-
- bfd->when &= ~BSC_FD_WRITE;
-
- /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
- if (!msgb_length(msg)) {
- LOGP(DPCU, LOGL_ERROR, "message type (%d) with ZERO "
- "bytes!\n", pcu_prim->msg_type);
- goto dontsend;
- }
-
- /* try to send it over the socket */
- rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
- if (rc == 0)
- goto close;
- if (rc < 0) {
- if (errno == EAGAIN) {
- bfd->when |= BSC_FD_WRITE;
- break;
- }
- goto close;
- }
+ /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
+ OSMO_ASSERT(msgb_length(msg) > 0);
+ /* try to send it over the socket */
+ rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+ if (OSMO_UNLIKELY(rc == 0))
+ goto close;
+ if (OSMO_UNLIKELY(rc < 0)) {
+ if (errno == EAGAIN)
+ return -EAGAIN;
+ return -1;
-dontsend:
- /* _after_ we send it, we can deueue */
- msg2 = msgb_dequeue(&state->upqueue);
- assert(msg == msg2);
- msgb_free(msg);
}
return 0;
@@ -592,54 +876,53 @@ close:
return -1;
}
-static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags)
+static void pdch_act_bts(struct gsm_bts *bts)
{
- int rc = 0;
-
- if (flags & BSC_FD_READ)
- rc = pcu_sock_read(bfd);
- if (rc < 0)
- return rc;
-
- if (flags & BSC_FD_WRITE)
- rc = pcu_sock_write(bfd);
+ struct gsm_bts_trx *trx;
+ int j;
- return rc;
+ /* activate PDCH */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (j = 0; j < ARRAY_SIZE(trx->ts); j++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[j];
+ /* (See comment in pdch_deact_bts above) */
+ if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
+ && ts->pchan_on_init == GSM_PCHAN_OSMO_DYN) {
+ ts_pdch_act(ts);
+ }
+ }
+ }
}
-/* accept connection comming from PCU */
+/* accept connection coming from PCU */
static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags)
{
struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data;
- struct osmo_fd *conn_bfd = &state->conn_bfd;
+ struct osmo_fd *conn_bfd = &state->upqueue.bfd;
struct sockaddr_un un_addr;
+ struct gsm_bts *bts;
socklen_t len;
- int rc;
+ int fd;
len = sizeof(un_addr);
- rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
- if (rc < 0) {
+ fd = accept(bfd->fd, (struct sockaddr *)&un_addr, &len);
+ if (fd < 0) {
LOGP(DPCU, LOGL_ERROR, "Failed to accept a new connection\n");
return -1;
}
if (conn_bfd->fd >= 0) {
- LOGP(DPCU, LOGL_NOTICE, "PCU connects but we already have "
- "another active connection ?!?\n");
+ LOGP(DPCU, LOGL_NOTICE, "PCU connects but we already have another active connection ?!?\n");
/* We already have one PCU connected, this is all we support */
- state->listen_bfd.when &= ~BSC_FD_READ;
- close(rc);
+ osmo_fd_read_disable(&state->listen_bfd);
+ close(fd);
return 0;
}
- conn_bfd->fd = rc;
- conn_bfd->when = BSC_FD_READ;
- conn_bfd->cb = pcu_sock_cb;
- conn_bfd->data = state;
+ osmo_fd_setup(conn_bfd, fd, OSMO_FD_READ, osmo_wqueue_bfd_cb, state, 0);
if (osmo_fd_register(conn_bfd) != 0) {
- LOGP(DPCU, LOGL_ERROR, "Failed to register new connection "
- "fd\n");
+ LOGP(DPCU, LOGL_ERROR, "Failed to register new connection fd\n");
close(conn_bfd->fd);
conn_bfd->fd = -1;
return -1;
@@ -647,11 +930,17 @@ static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags)
LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n");
+ /* Activate all PDCHs on all BTSs that are served by the PCU */
+ llist_for_each_entry(bts, &state->net->bts_list, list) {
+ if (bsc_co_located_pcu(bts))
+ pdch_act_bts(bts);
+ }
+
return 0;
}
/* Open connection to PCU */
-int pcu_sock_init(const char *path, struct gsm_bts *bts)
+int pcu_sock_init(struct gsm_network *net)
{
struct pcu_sock_state *state;
struct osmo_fd *bfd;
@@ -661,24 +950,23 @@ int pcu_sock_init(const char *path, struct gsm_bts *bts)
if (!state)
return -ENOMEM;
- INIT_LLIST_HEAD(&state->upqueue);
- state->net = bts->network;
- state->conn_bfd.fd = -1;
+ osmo_wqueue_init(&state->upqueue, net->pcu_sock_wqueue_len_max);
+ state->upqueue.read_cb = pcu_sock_read;
+ state->upqueue.write_cb = pcu_sock_write;
+ state->upqueue.bfd.fd = -1;
+ state->net = net;
bfd = &state->listen_bfd;
- bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path,
- OSMO_SOCK_F_BIND);
- if (bfd->fd < 0) {
+ rc = osmo_sock_unix_init(SOCK_SEQPACKET, 0, net->pcu_sock_path, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
LOGP(DPCU, LOGL_ERROR, "Could not create unix socket: %s\n",
strerror(errno));
talloc_free(state);
return -1;
}
- bfd->when = BSC_FD_READ;
- bfd->cb = pcu_sock_accept;
- bfd->data = state;
+ osmo_fd_setup(bfd, rc, OSMO_FD_READ, pcu_sock_accept, state, 0);
rc = osmo_fd_register(bfd);
if (rc < 0) {
@@ -689,28 +977,28 @@ int pcu_sock_init(const char *path, struct gsm_bts *bts)
return rc;
}
- LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket: %s\n", path);
+ LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket (PCU IF v%u): %s\n",
+ PCU_IF_VERSION, net->pcu_sock_path);
- bts->pcu_state = state;
+ net->pcu_state = state;
return 0;
}
/* Close connection to PCU */
-void pcu_sock_exit(struct gsm_bts *bts)
+void pcu_sock_exit(struct gsm_network *net)
{
- struct pcu_sock_state *state = bts->pcu_state;
+ struct pcu_sock_state *state = net->pcu_state;
struct osmo_fd *bfd, *conn_bfd;
if (!state)
return;
- conn_bfd = &state->conn_bfd;
+ conn_bfd = &state->upqueue.bfd;
if (conn_bfd->fd > 0)
pcu_sock_close(state);
bfd = &state->listen_bfd;
- close(bfd->fd);
osmo_fd_unregister(bfd);
+ close(bfd->fd);
talloc_free(state);
- bts->pcu_state = NULL;
+ net->pcu_state = NULL;
}
-
diff --git a/src/osmo-bsc/penalty_timers.c b/src/osmo-bsc/penalty_timers.c
index 02cf2468a..124a36255 100644
--- a/src/osmo-bsc/penalty_timers.c
+++ b/src/osmo-bsc/penalty_timers.c
@@ -28,34 +28,22 @@
#include <osmocom/bsc/penalty_timers.h>
#include <osmocom/bsc/gsm_data.h>
-struct penalty_timers {
- struct llist_head timers;
-};
-
-struct penalty_timer {
- struct llist_head entry;
- const void *for_object;
- unsigned int timeout;
-};
-
static unsigned int time_now(void)
{
- time_t now;
- time(&now);
- /* FIXME: use monotonic clock */
- return (unsigned int)now;
-}
-
-struct penalty_timers *penalty_timers_init(void *ctx)
-{
- struct penalty_timers *pt = talloc_zero(ctx, struct penalty_timers);
- if (!pt)
- return NULL;
- INIT_LLIST_HEAD(&pt->timers);
- return pt;
+ struct timespec tp;
+ if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp))
+ return 0;
+ return (unsigned int)tp.tv_sec;
}
-void penalty_timers_add(struct penalty_timers *pt, const void *for_object, int timeout)
+/* Add a penalty timer for a target cell ID.
+ * \param ctx talloc context to allocate new struct penalty_timer from.
+ * \param penalty_timers llist head to add penalty timer to.
+ * \param for_target_cell Which handover target to penalize.
+ * \param timeout Penalty time in seconds.
+ */
+void penalty_timers_add(void *ctx, struct llist_head *penalty_timers,
+ const struct gsm0808_cell_id *for_target_cell, int timeout)
{
struct penalty_timer *timer;
unsigned int now;
@@ -67,9 +55,9 @@ void penalty_timers_add(struct penalty_timers *pt, const void *for_object, int t
then = now + timeout;
- /* timer already running for that BTS? */
- llist_for_each_entry(timer, &pt->timers, entry) {
- if (timer->for_object != for_object)
+ /* timer already running for that target cell? */
+ llist_for_each_entry(timer, penalty_timers, entry) {
+ if (!gsm0808_cell_ids_match(&timer->for_target_cell, for_target_cell, true))
continue;
/* raise, if running timer will timeout earlier or has timed
* out already, otherwise keep later timeout */
@@ -79,24 +67,49 @@ void penalty_timers_add(struct penalty_timers *pt, const void *for_object, int t
}
/* add new timer */
- timer = talloc_zero(pt, struct penalty_timer);
+ timer = talloc_zero(ctx, struct penalty_timer);
if (!timer)
return;
- timer->for_object = for_object;
+ timer->for_target_cell = *for_target_cell;
timer->timeout = then;
- llist_add_tail(&timer->entry, &pt->timers);
+ llist_add_tail(&timer->entry, penalty_timers);
}
-unsigned int penalty_timers_remaining(struct penalty_timers *pt, const void *for_object)
+/* Add a penalty timer for each target cell ID in the given list.
+ * \param ctx talloc context to allocate new struct penalty_timer from.
+ * \param penalty_timers llist head to add penalty timer to.
+ * \param for_target_cells Which handover targets to penalize.
+ * \param timeout Penalty time in seconds.
+ */
+void penalty_timers_add_list(void *ctx, struct llist_head *penalty_timers,
+ const struct gsm0808_cell_id_list2 *for_target_cells, int timeout)
+{
+ int i;
+ for (i = 0; i < for_target_cells->id_list_len; i++) {
+ struct gsm0808_cell_id add = {
+ .id_discr = for_target_cells->id_discr,
+ .id = for_target_cells->id_list[i],
+ };
+ penalty_timers_add(ctx, penalty_timers, &add, timeout);
+ }
+}
+
+/* Return the amount of penalty time in seconds remaining for a target cell.
+ * \param penalty_timers llist head to look up penalty time in.
+ * \param for_target_cell Which handover target to query.
+ * \returns seconds remaining until all penalty time has expired.
+ */
+unsigned int penalty_timers_remaining(struct llist_head *penalty_timers,
+ const struct gsm0808_cell_id *for_target_cell)
{
struct penalty_timer *timer;
unsigned int now = time_now();
unsigned int max_remaining = 0;
- llist_for_each_entry(timer, &pt->timers, entry) {
+ llist_for_each_entry(timer, penalty_timers, entry) {
unsigned int remaining;
- if (timer->for_object != for_object)
+ if (!gsm0808_cell_ids_match(&timer->for_target_cell, for_target_cell, true))
continue;
if (now >= timer->timeout)
continue;
@@ -107,23 +120,39 @@ unsigned int penalty_timers_remaining(struct penalty_timers *pt, const void *for
return max_remaining;
}
-void penalty_timers_clear(struct penalty_timers *pt, const void *for_object)
+/* Return the largest amount of penalty time in seconds remaining for any one of the given target cells.
+ * Call penalty_timers_remaining() for each entry of for_target_cells and return the largest value encountered.
+ * \param penalty_timers llist head to look up penalty time in.
+ * \param for_target_cells Which handover targets to query.
+ * \returns seconds remaining until all penalty time has expired.
+ */
+unsigned int penalty_timers_remaining_list(struct llist_head *penalty_timers,
+ const struct gsm0808_cell_id_list2 *for_target_cells)
+{
+ int i;
+ unsigned int max_remaining = 0;
+ for (i = 0; i < for_target_cells->id_list_len; i++) {
+ unsigned int remaining;
+ struct gsm0808_cell_id query = {
+ .id_discr = for_target_cells->id_discr,
+ .id = for_target_cells->id_list[i],
+ };
+ remaining = penalty_timers_remaining(penalty_timers, &query);
+ max_remaining = OSMO_MAX(max_remaining, remaining);
+ }
+ return max_remaining;
+}
+
+/* Clear penalty timers for one target cell, or completely clear the entire list.
+ * \param penalty_timers llist head to add penalty timer to.
+ * \param for_target_cell Which handover target to clear timers for, or NULL to clear all timers. */
+void penalty_timers_clear(struct llist_head *penalty_timers, const struct gsm0808_cell_id *for_target_cell)
{
struct penalty_timer *timer, *timer2;
- llist_for_each_entry_safe(timer, timer2, &pt->timers, entry) {
- if (for_object && timer->for_object != for_object)
+ llist_for_each_entry_safe(timer, timer2, penalty_timers, entry) {
+ if (for_target_cell && !gsm0808_cell_ids_match(&timer->for_target_cell, for_target_cell, true))
continue;
llist_del(&timer->entry);
talloc_free(timer);
}
}
-
-void penalty_timers_free(struct penalty_timers **pt_p)
-{
- struct penalty_timers *pt = *pt_p;
- if (!pt)
- return;
- penalty_timers_clear(pt, NULL);
- talloc_free(pt);
- *pt_p = NULL;
-}
diff --git a/src/osmo-bsc/power_control.c b/src/osmo-bsc/power_control.c
new file mode 100644
index 000000000..8bf0783bf
--- /dev/null
+++ b/src/osmo-bsc/power_control.c
@@ -0,0 +1,476 @@
+/* MS Power Control Loop L1 */
+
+/* (C) 2014 by Holger Hans Peter Freyther
+ * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/meas_rep.h>
+#include <osmocom/bsc/power_control.h>
+
+/* We don't want to deal with floating point, so we scale up */
+#define EWMA_SCALE_FACTOR 100
+/* EWMA_SCALE_FACTOR/2 = +50: Round to nearest value when downscaling, otherwise floor() is applied. */
+#define EWMA_ROUND_FACTOR (EWMA_SCALE_FACTOR / 2)
+
+/* Base Low-Pass Single-Pole IIR Filter (EWMA) formula:
+ *
+ * Avg[n] = a * Val[n] + (1 - a) * Avg[n - 1]
+ *
+ * where parameter 'a' determines how much weight of the latest measurement value
+ * 'Val[n]' carries vs the weight of the accumulated average 'Avg[n - 1]'. The
+ * value of 'a' is usually a float in range 0 .. 1, so:
+ *
+ * - value 0.5 gives equal weight to both 'Val[n]' and 'Avg[n - 1]';
+ * - value 1.0 means no filtering at all (pass through);
+ * - value 0.0 makes no sense.
+ *
+ * Further optimization:
+ *
+ * Avg[n] = a * Val[n] + Avg[n - 1] - a * Avg[n - 1]
+ * ^^^^^^ ^^^^^^^^^^
+ *
+ * a) this can be implemented in C using '+=' operator:
+ *
+ * Avg += a * Val - a * Avg
+ * Avg += a * (Val - Avg)
+ *
+ * b) everything is scaled up by 100 to avoid floating point stuff:
+ *
+ * Avg100 += A * (Val - Avg)
+ *
+ * where 'Avg100' is 'Avg * 100' and 'A' is 'a * 100'.
+ *
+ * For more details, see:
+ *
+ * https://en.wikipedia.org/wiki/Moving_average
+ * https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter
+ * https://tomroelandts.com/articles/low-pass-single-pole-iir-filter
+ */
+static int do_pf_ewma(const struct gsm_power_ctrl_meas_params *mp,
+ struct gsm_power_ctrl_meas_proc_state *mps,
+ const int Val)
+{
+ const uint8_t A = mp->ewma.alpha;
+ int *Avg100 = &mps->ewma.Avg100;
+
+ /* We don't have 'Avg[n - 1]' if this is the first run */
+ if (mps->meas_num++ == 0) {
+ *Avg100 = Val * EWMA_SCALE_FACTOR;
+ return Val;
+ }
+
+ *Avg100 += A * (Val - (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR);
+ return (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR;
+}
+
+/* Calculate target RxLev value from lower/upper thresholds */
+#define CALC_TARGET(mp) \
+ ((mp).lower_thresh + (mp).upper_thresh) / 2
+
+static int do_avg_algo(const struct gsm_power_ctrl_meas_params *mp,
+ struct gsm_power_ctrl_meas_proc_state *mps,
+ const int val)
+{
+ int val_avg;
+ switch (mp->algo) {
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA:
+ val_avg = do_pf_ewma(mp, mps, val);
+ break;
+ /* TODO: implement other pre-processing methods */
+ case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE:
+ default:
+ /* No filtering (pass through) */
+ val_avg = val;
+ }
+ return val_avg;
+}
+/* Calculate a 'delta' value (for the given MS/BS power control parameters)
+ * to be applied to the current Tx power level to approach the target level. */
+static int calc_delta_rxlev(const struct gsm_power_ctrl_params *params, const uint8_t rxlev)
+{
+ int delta;
+
+ /* Check if RxLev is within the threshold window */
+ if (rxlev >= params->rxlev_meas.lower_thresh &&
+ rxlev <= params->rxlev_meas.upper_thresh)
+ return 0;
+
+ /* How many dBs measured power should be increased (+) or decreased (-)
+ * to reach expected power. */
+ delta = CALC_TARGET(params->rxlev_meas) - rxlev;
+
+ /* Don't ever change more than PWR_{LOWER,RAISE}_MAX_DBM during one loop
+ * iteration, i.e. reduce the speed at which the MS transmit power can
+ * change. A higher value means a lower level (and vice versa) */
+ if (delta > params->inc_step_size_db)
+ delta = params->inc_step_size_db;
+ else if (delta < -params->red_step_size_db)
+ delta = -params->red_step_size_db;
+
+ return delta;
+}
+
+/* Shall we skip current block based on configured interval? */
+static bool ctrl_interval_skip_block(const struct gsm_power_ctrl_params *params,
+ struct lchan_power_ctrl_state *state)
+{
+ /* Power control interval: how many blocks do we skip? */
+ if (state->skip_block_num-- > 0)
+ return true;
+
+ /* Can we be sure if ONE Report is always going to correspond
+ * to ONE SACCH block at the BTS? - If not this is as approximation
+ * but it should not hurt. */
+
+ /* Reset the number of SACCH blocks to be skipped:
+ * ctrl_interval=0 => 0 blocks to skip,
+ * ctrl_interval=1 => 1 blocks to skip,
+ * ctrl_interval=2 => 3 blocks to skip,
+ * so basically ctrl_interval * 2 - 1. */
+ state->skip_block_num = params->ctrl_interval * 2 - 1;
+ return false;
+}
+
+int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, const struct gsm_meas_rep *mr)
+{
+ struct lchan_power_ctrl_state *state = &lchan->ms_power_ctrl;
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct gsm_bts *bts = trx->bts;
+ enum gsm_band band = bts->band;
+ const struct gsm_power_ctrl_params *params = &bts->ms_power_ctrl;
+ int8_t new_power_lvl; /* TS 05.05 power level */
+ int8_t ms_dbm, new_dbm, current_dbm, bsc_max_dbm;
+ uint8_t rxlev_avg;
+ uint8_t ms_power_lvl = ms_pwr_ctl_lvl(band, mr->ms_l1.pwr);
+ int8_t ul_rssi_dbm;
+ bool ignore;
+
+ if (params == NULL)
+ return 0;
+ /* Not doing the power loop here if we are not handling it */
+ if (params->mode != GSM_PWR_CTRL_MODE_DYN_BSC)
+ return 0;
+
+ /* Shall we skip current block based on configured interval? */
+ if (ctrl_interval_skip_block(params, state))
+ return 0;
+
+ /* If DTx is active on Uplink,
+ * use the '-SUB', otherwise '-FULL': */
+ if (mr->flags & MEAS_REP_F_UL_DTX)
+ ul_rssi_dbm = rxlev2dbm(mr->ul.sub.rx_lev);
+ else
+ ul_rssi_dbm = rxlev2dbm(mr->ul.full.rx_lev);
+
+ ms_dbm = ms_pwr_dbm(band, ms_power_lvl);
+ if (ms_dbm < 0) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE,
+ "Failed to calculate dBm for power ctl level %" PRIu8 " on band %s\n",
+ ms_power_lvl, gsm_band_name(band));
+ return 0;
+ }
+
+ bsc_max_dbm = bts->ms_max_power;
+ rxlev_avg = do_avg_algo(&params->rxlev_meas, &state->rxlev_meas_proc, dbm2rxlev(ul_rssi_dbm));
+ new_dbm = ms_dbm + calc_delta_rxlev(params, rxlev_avg);
+
+ /* Make sure new_dbm is never negative. ms_pwr_ctl_lvl() can later on
+ cope with any unsigned dbm value, regardless of band minimal value. */
+ if (new_dbm < 0)
+ new_dbm = 0;
+ /* Don't ask for smaller ms power level than the one set by ms max power for this BTS */
+ if (new_dbm > bsc_max_dbm)
+ new_dbm = bsc_max_dbm;
+
+ new_power_lvl = ms_pwr_ctl_lvl(band, new_dbm);
+ if (new_power_lvl < 0) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE,
+ "Failed to retrieve power level for %" PRId8 " dBm on band %d\n",
+ new_dbm, band);
+ return 0;
+ }
+
+ current_dbm = ms_pwr_dbm(band, lchan->ms_power);
+
+ /* In this Power Control Loop, we infer a new good MS Power Level based
+ * on the previous MS Power Level announced by the MS (not the previous
+ * one we requested!) together with the related computed measurements.
+ * Hence, and since we allow for several good MS Power Levels falling into our
+ * thresholds, we could finally converge into an oscillation loop where
+ * the MS bounces between 2 different correct MS Power levels all the
+ * time, due to the fact that we "accept" and "request back" whatever
+ * good MS Power Level we received from the MS, but at that time the MS
+ * will be transmitting using the previous MS Power Level we
+ * requested, which we will later "accept" and "request back" on next loop
+ * iteration. As a result MS effectively bounces between those 2 MS
+ * Power Levels.
+ * In order to fix this permanent oscillation, if current MS_PWR used/announced
+ * by MS is good ("ms_dbm == new_dbm", hence within thresholds and no change
+ * required) but has higher Tx power than the one we last requested, we ignore
+ * it and keep requesting for one with lower Tx power. This way we converge to
+ * the lowest good Tx power avoiding oscillating over values within thresholds.
+ */
+ ignore = (ms_dbm == new_dbm && ms_dbm > current_dbm);
+
+ if (lchan->ms_power == new_power_lvl || ignore) {
+ LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Keeping MS power at control level %d (%d dBm): "
+ "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm\n",
+ new_power_lvl, ms_dbm, ms_power_lvl, bsc_max_dbm, ul_rssi_dbm, rxlev2dbm(rxlev_avg),
+ rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh));
+ return 0;
+ }
+
+ LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "%s MS power control level %d (%d dBm) => %d (%d dBm): "
+ "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm\n",
+ (new_dbm > current_dbm) ? "Raising" : "Lowering",
+ lchan->ms_power, current_dbm, new_power_lvl, new_dbm, ms_power_lvl,
+ bsc_max_dbm, ul_rssi_dbm, rxlev2dbm(rxlev_avg),
+ rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh));
+
+ lchan_update_ms_power_ctrl_level(lchan, new_dbm);
+
+ return 1;
+
+}
+
+/* Default MS/BS Power Control parameters (see 3GPP TS 45.008, table A.1) */
+const struct gsm_power_ctrl_params power_ctrl_params_def = {
+ /* Static Power Control is the safe default */
+ .mode = GSM_PWR_CTRL_MODE_STATIC,
+
+ /* BS Power reduction value / maximum (in dB) */
+ .bs_power_val_db = 0, /* no attenuation in static mode */
+ .bs_power_max_db = 12, /* up to 12 dB in dynamic mode */
+
+ /* Power increasing/reducing step size */
+ .inc_step_size_db = 4, /* 2, 4, or 6 dB */
+ .red_step_size_db = 2, /* 2 or 4 dB */
+
+ /* RxLev measurement parameters */
+ .rxlev_meas = {
+ .enabled = true,
+ /* Thresholds for RxLev (see 3GPP TS 45.008, A.3.2.1) */
+ .lower_thresh = 32, /* L_RXLEV_XX_P (-78 dBm) */
+ .upper_thresh = 38, /* U_RXLEV_XX_P (-72 dBm) */
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_RXLEV_XX_P */
+ .lower_cmp_p = 10, /* P1 as in 3GPP TS 45.008, A.3.2.1 (case a) */
+ .lower_cmp_n = 12, /* N1 as in 3GPP TS 45.008, A.3.2.1 (case a) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_RXLEV_XX_P */
+ .upper_cmp_p = 19, /* P2 as in 3GPP TS 45.008, A.3.2.1 (case b) */
+ .upper_cmp_n = 20, /* N2 as in 3GPP TS 45.008, A.3.2.1 (case b) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+
+ /* RxQual measurement parameters */
+ .rxqual_meas = {
+ .enabled = true,
+ /* Thresholds for RxQual (see 3GPP TS 45.008, A.3.2.1) */
+ .lower_thresh = 3, /* L_RXQUAL_XX_P (0.8% <= BER < 1.6%) */
+ .upper_thresh = 0, /* U_RXQUAL_XX_P (BER < 0.2%) */
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_RXQUAL_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_RXQUAL_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+
+ /* C/I measurement parameters.
+ * Target C/I retrieved from "GSM/EDGE: Evolution and Performance" Table 10.3.
+ * Set lower and upper so that (lower + upper) / 2 is equal or slightly
+ * above the target.
+ */
+ .ci_fr_meas = { /* FR: Target C/I = 15 dB, Soft blocking threshold = 10 dB */
+ .enabled = false,
+ .lower_thresh = 13,
+ .upper_thresh = 17,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_FR_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_FR_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_hr_meas = { /* HR: Target C/I = 18 dB, Soft blocking threshold = 13 dB */
+ .enabled = false,
+ .lower_thresh = 16,
+ .upper_thresh = 21,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_HR_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_HR_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_amr_fr_meas = { /* AMR-FR: Target C/I = 9 dB, Soft blocking threshold = 4 dB */
+ .enabled = false,
+ .lower_thresh = 7,
+ .upper_thresh = 11,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_AMR_FR_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_AMR_FR_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_amr_hr_meas = { /* AMR-HR: Target C/I = 15 dB, Soft blocking threshold = 10 dB */
+ .enabled = false,
+ .lower_thresh = 13,
+ .upper_thresh = 17,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_AMR_HR_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_AMR_HR_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_sdcch_meas = { /* SDCCH: Target C/I = 14 dB, Soft blocking threshold = 9 dB */
+ .enabled = false,
+ .lower_thresh = 12,
+ .upper_thresh = 16,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_SDCCH_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_SDCCH_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+ .ci_gprs_meas = { /* GPRS: Target C/I = 20 dB, Soft blocking threshold = 15 dB */
+ .enabled = false,
+ .lower_thresh = 18,
+ .upper_thresh = 24,
+
+ /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages
+ * out of LOWER_CMP_N averages are lower than L_CI_GPRS_XX_P */
+ .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */
+ /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages
+ * out of UPPER_CMP_N averages are greater than L_CI_GPRS_XX_P */
+ .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+ .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */
+
+ /* No averaging (filtering) by default */
+ .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE,
+
+ /* Hreqave: the period over which an average is produced */
+ .h_reqave = 4, /* TODO: investigate a reasonable default value */
+ /* Hreqt: the number of averaged results maintained */
+ .h_reqt = 6, /* TODO: investigate a reasonable default value */
+ },
+};
+
+void power_ctrl_params_def_reset(struct gsm_power_ctrl_params *params,
+ enum gsm_power_ctrl_dir dir)
+{
+ *params = power_ctrl_params_def;
+ params->dir = dir;
+
+ /* Trigger loop every N-th SACCH block. See 3GPP TS 45.008 section 4.7.1. */
+ if (dir == GSM_PWR_CTRL_DIR_UL)
+ params->ctrl_interval = 2; /* N=4 (1.92s) */
+ else
+ params->ctrl_interval = 1; /* N=2 (0.960) */
+}
diff --git a/src/osmo-bsc/rest_octets.c b/src/osmo-bsc/rest_octets.c
deleted file mode 100644
index 9f2b4c0ab..000000000
--- a/src/osmo-bsc/rest_octets.c
+++ /dev/null
@@ -1,878 +0,0 @@
-/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface,
- * rest octet handling according to
- * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
-
-/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
- *
- * All Rights Reserved
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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 <string.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <stdbool.h>
-
-#include <osmocom/bsc/debug.h>
-#include <osmocom/bsc/gsm_data.h>
-#include <osmocom/core/bitvec.h>
-#include <osmocom/gsm/bitvec_gsm.h>
-#include <osmocom/bsc/rest_octets.h>
-#include <osmocom/bsc/arfcn_range_encode.h>
-#include <osmocom/bsc/system_information.h>
-
-/* generate SI1 rest octets */
-int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net)
-{
- struct bitvec bv;
-
- memset(&bv, 0, sizeof(bv));
- bv.data = data;
- bv.data_len = 1;
-
- if (nch_pos) {
- bitvec_set_bit(&bv, H);
- bitvec_set_uint(&bv, *nch_pos, 5);
- } else
- bitvec_set_bit(&bv, L);
-
- if (is1800_net)
- bitvec_set_bit(&bv, L);
- else
- bitvec_set_bit(&bv, H);
-
- bitvec_spare_padding(&bv, 6);
- return bv.data_len;
-}
-
-/* Append Repeated E-UTRAN Neighbour Cell to bitvec: see 3GPP TS 44.018 Table 10.5.2.33b.1 */
-static inline bool append_eutran_neib_cell(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget)
-{
- const struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
- unsigned i, skip = 0;
- size_t offset = bts->e_offset;
- int16_t rem = budget - 6; /* account for mandatory stop bit and THRESH_E-UTRAN_high */
- uint8_t earfcn_budget;
-
- if (budget <= 6)
- return false;
-
- OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
-
- /* first we have to properly adjust budget requirements */
- if (e->prio_valid) /* E-UTRAN_PRIORITY: 3GPP TS 45.008*/
- rem -= 4;
- else
- rem--;
-
- if (e->thresh_lo_valid) /* THRESH_E-UTRAN_low: */
- rem -= 6;
- else
- rem--;
-
- if (e->qrxlm_valid) /* E-UTRAN_QRXLEVMIN: */
- rem -= 6;
- else
- rem--;
-
- if (rem < 0)
- return false;
-
- /* now we can proceed with actually adding EARFCNs within adjusted budget limit */
- for (i = 0; i < e->length; i++) {
- if (e->arfcn[i] != OSMO_EARFCN_INVALID) {
- if (skip < offset) {
- skip++; /* ignore EARFCNs added on previous calls */
- } else {
- earfcn_budget = 17; /* compute budget per-EARFCN */
- if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i])
- earfcn_budget++;
- else
- earfcn_budget += 4;
-
- if (rem - earfcn_budget < 0)
- break;
- else {
- bts->e_offset++;
- rem -= earfcn_budget;
-
- if (rem < 0)
- return false;
-
- bitvec_set_bit(bv, 1); /* EARFCN: */
- bitvec_set_uint(bv, e->arfcn[i], 16);
-
- if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i])
- bitvec_set_bit(bv, 0);
- else { /* Measurement Bandwidth: 9.1.54 */
- bitvec_set_bit(bv, 1);
- bitvec_set_uint(bv, e->meas_bw[i], 3);
- }
- }
- }
- }
- }
-
- /* stop bit - end of EARFCN + Measurement Bandwidth sequence */
- bitvec_set_bit(bv, 0);
-
- /* Note: we don't support different EARFCN arrays each with different priority, threshold etc. */
-
- if (e->prio_valid) {
- /* E-UTRAN_PRIORITY: 3GPP TS 45.008*/
- bitvec_set_bit(bv, 1);
- bitvec_set_uint(bv, e->prio, 3);
- } else
- bitvec_set_bit(bv, 0);
-
- /* THRESH_E-UTRAN_high */
- bitvec_set_uint(bv, e->thresh_hi, 5);
-
- if (e->thresh_lo_valid) {
- /* THRESH_E-UTRAN_low: */
- bitvec_set_bit(bv, 1);
- bitvec_set_uint(bv, e->thresh_lo, 5);
- } else
- bitvec_set_bit(bv, 0);
-
- if (e->qrxlm_valid) {
- /* E-UTRAN_QRXLEVMIN: */
- bitvec_set_bit(bv, 1);
- bitvec_set_uint(bv, e->qrxlm, 5);
- } else
- bitvec_set_bit(bv, 0);
-
- return true;
-}
-
-static inline void append_earfcn(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget)
-{
- bool appended;
- unsigned int old = bv->cur_bit; /* save current position to make rollback possible */
- int rem = budget - 25;
- if (rem <= 0)
- return;
-
- OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
-
- /* Additions in Rel-5: */
- bitvec_set_bit(bv, H);
- /* No 3G Additional Measurement Param. Descr. */
- bitvec_set_bit(bv, 0);
- /* No 3G ADDITIONAL MEASUREMENT Param. Descr. 2 */
- bitvec_set_bit(bv, 0);
- /* Additions in Rel-6: */
- bitvec_set_bit(bv, H);
- /* 3G_CCN_ACTIVE */
- bitvec_set_bit(bv, 0);
- /* Additions in Rel-7: */
- bitvec_set_bit(bv, H);
- /* No 700_REPORTING_OFFSET */
- bitvec_set_bit(bv, 0);
- /* No 810_REPORTING_OFFSET */
- bitvec_set_bit(bv, 0);
- /* Additions in Rel-8: */
- bitvec_set_bit(bv, H);
-
- /* Priority and E-UTRAN Parameters Description */
- bitvec_set_bit(bv, 1);
-
- /* No Serving Cell Priority Parameters Descr. */
- bitvec_set_bit(bv, 0);
- /* No 3G Priority Parameters Description */
- bitvec_set_bit(bv, 0);
- /* E-UTRAN Parameters Description */
- bitvec_set_bit(bv, 1);
-
- /* E-UTRAN_CCN_ACTIVE */
- bitvec_set_bit(bv, 0);
- /* E-UTRAN_Start: 9.1.54 */
- bitvec_set_bit(bv, 1);
- /* E-UTRAN_Stop: 9.1.54 */
- bitvec_set_bit(bv, 1);
-
- /* No E-UTRAN Measurement Parameters Descr. */
- bitvec_set_bit(bv, 0);
- /* No GPRS E-UTRAN Measurement Param. Descr. */
- bitvec_set_bit(bv, 0);
-
- /* Note: each of next 3 "repeated" structures might be repeated any
- (0, 1, 2...) times - we only support 1 and 0 */
-
- /* Repeated E-UTRAN Neighbour Cells */
- bitvec_set_bit(bv, 1);
-
- appended = append_eutran_neib_cell(bv, bts, rem);
- if (!appended) { /* appending is impossible within current budget: rollback */
- bv->cur_bit = old;
- return;
- }
-
- /* stop bit - end of Repeated E-UTRAN Neighbour Cells sequence: */
- bitvec_set_bit(bv, 0);
-
- /* Note: following 2 repeated structs are not supported ATM */
- /* stop bit - end of Repeated E-UTRAN Not Allowed Cells sequence: */
- bitvec_set_bit(bv, 0);
- /* stop bit - end of Repeated E-UTRAN PCID to TA mapping sequence: */
- bitvec_set_bit(bv, 0);
-
- /* Priority and E-UTRAN Parameters Description ends here */
- /* No 3G CSG Description */
- bitvec_set_bit(bv, 0);
- /* No E-UTRAN CSG Description */
- bitvec_set_bit(bv, 0);
- /* No Additions in Rel-9: */
- bitvec_set_bit(bv, L);
-}
-
-static inline int f0_helper(int *sc, size_t length, uint8_t *chan_list)
-{
- int w[RANGE_ENC_MAX_ARFCNS] = { 0 };
-
- return range_encode(ARFCN_RANGE_1024, sc, length, w, 0, chan_list);
-}
-
-/* Estimate how many bits it'll take to append single FDD UARFCN */
-static inline int append_utran_fdd_length(uint16_t u, const int *sc, size_t sc_len, size_t length)
-{
- uint8_t chan_list[16] = { 0 };
- int tmp[sc_len], f0;
-
- memcpy(tmp, sc, sizeof(tmp));
-
- f0 = f0_helper(tmp, length, chan_list);
- if (f0 < 0)
- return f0;
-
- return 21 + range1024_p(length);
-}
-
-/* Append single FDD UARFCN */
-static inline int append_utran_fdd(struct bitvec *bv, uint16_t u, int *sc, size_t length)
-{
- uint8_t chan_list[16] = { 0 };
- int f0 = f0_helper(sc, length, chan_list);
-
- if (f0 < 0)
- return f0;
-
- /* Repeated UTRAN FDD Neighbour Cells */
- bitvec_set_bit(bv, 1);
-
- /* FDD-ARFCN */
- bitvec_set_bit(bv, 0);
- bitvec_set_uint(bv, u, 14);
-
- /* FDD_Indic0: parameter value '0000000000' is a member of the set? */
- bitvec_set_bit(bv, f0);
- /* NR_OF_FDD_CELLS */
- bitvec_set_uint(bv, length, 5);
-
- f0 = bv->cur_bit;
- bitvec_add_range1024(bv, (struct gsm48_range_1024 *)chan_list);
- bv->cur_bit = f0 + range1024_p(length);
-
- return 21 + range1024_p(length);
-}
-
-static inline int try_adding_uarfcn(struct bitvec *bv, struct gsm_bts *bts, uint16_t uarfcn,
- uint8_t num_sc, uint8_t start_pos, uint8_t budget)
-{
- int i, k, rc, a[bts->si_common.uarfcn_length];
-
- if (budget < 23)
- return -ENOMEM;
-
- /* copy corresponding Scrambling Codes: range encoder make in-place modifications */
- for (i = start_pos, k = 0; i < num_sc; a[k++] = bts->si_common.data.scramble_list[i++]);
-
- /* estimate bit length requirements */
- rc = append_utran_fdd_length(uarfcn, a, bts->si_common.uarfcn_length, k);
- if (rc < 0)
- return rc; /* range encoder failure */
-
- if (budget - rc <= 0)
- return -ENOMEM; /* we have ran out of budget in current SI2q */
-
- /* compute next offset */
- bts->u_offset += k;
-
- return budget - append_utran_fdd(bv, uarfcn, a, k);
-}
-
-/* Append multiple FDD UARFCNs */
-static inline void append_uarfcns(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget)
-{
- const uint16_t *u = bts->si_common.data.uarfcn_list;
- int i, rem = budget - 7, st = bts->u_offset; /* account for constant bits right away */
- uint16_t cu = u[bts->u_offset]; /* caller ensures that length is positive */
-
- OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
-
- if (budget <= 7)
- return;
-
- /* 3G Neighbour Cell Description */
- bitvec_set_bit(bv, 1);
- /* No Index_Start_3G */
- bitvec_set_bit(bv, 0);
- /* No Absolute_Index_Start_EMR */
- bitvec_set_bit(bv, 0);
-
- /* UTRAN FDD Description */
- bitvec_set_bit(bv, 1);
- /* No Bandwidth_FDD */
- bitvec_set_bit(bv, 0);
-
- for (i = bts->u_offset; i <= bts->si_common.uarfcn_length; i++)
- if (u[i] != cu) { /* we've reached new UARFCN */
- rem = try_adding_uarfcn(bv, bts, cu, i, st, rem);
- if (rem < 0)
- break;
-
- if (i < bts->si_common.uarfcn_length) {
- cu = u[i];
- st = i;
- } else
- break;
- }
-
- /* stop bit - end of Repeated UTRAN FDD Neighbour Cells */
- bitvec_set_bit(bv, 0);
-
- /* UTRAN TDD Description */
- bitvec_set_bit(bv, 0);
-}
-
-/* generate SI2quater rest octets: 3GPP TS 44.018 ยง 10.5.2.33b */
-int rest_octets_si2quater(uint8_t *data, struct gsm_bts *bts)
-{
- int rc;
- struct bitvec bv;
-
- if (bts->si2q_count < bts->si2q_index)
- return -EINVAL;
-
- bv.data = data;
- bv.data_len = 20;
- bitvec_zero(&bv);
-
- /* BA_IND: Set to '0' as that's what we use for SI2xxx type,
- * whereas '1' is used for SI5xxx type messages. The point here
- * is to be able to correlate whether a given MS measurement
- * report was using the neighbor cells advertised in SI2 or in
- * SI5, as those two could very well be different */
- bitvec_set_bit(&bv, 0);
- /* 3G_BA_IND */
- bitvec_set_bit(&bv, 1);
- /* MP_CHANGE_MARK */
- bitvec_set_bit(&bv, 0);
-
- /* SI2quater_INDEX */
- bitvec_set_uint(&bv, bts->si2q_index, 4);
- /* SI2quater_COUNT */
- bitvec_set_uint(&bv, bts->si2q_count, 4);
-
- /* No Measurement_Parameters Description */
- bitvec_set_bit(&bv, 0);
- /* No GPRS_Real Time Difference Description */
- bitvec_set_bit(&bv, 0);
- /* No GPRS_BSIC Description */
- bitvec_set_bit(&bv, 0);
- /* No GPRS_REPORT PRIORITY Description */
- bitvec_set_bit(&bv, 0);
- /* No GPRS_MEASUREMENT_Parameters Description */
- bitvec_set_bit(&bv, 0);
- /* No NC Measurement Parameters */
- bitvec_set_bit(&bv, 0);
- /* No extension (length) */
- bitvec_set_bit(&bv, 0);
-
- rc = SI2Q_MAX_LEN - (bv.cur_bit + 3);
- if (rc > 0 && bts->si_common.uarfcn_length - bts->u_offset > 0)
- append_uarfcns(&bv, bts, rc);
- else /* No 3G Neighbour Cell Description */
- bitvec_set_bit(&bv, 0);
-
- /* No 3G Measurement Parameters Description */
- bitvec_set_bit(&bv, 0);
- /* No GPRS_3G_MEASUREMENT Parameters Descr. */
- bitvec_set_bit(&bv, 0);
-
- rc = SI2Q_MAX_LEN - bv.cur_bit;
- if (rc > 0 && si2q_earfcn_count(&bts->si_common.si2quater_neigh_list) - bts->e_offset > 0)
- append_earfcn(&bv, bts, rc);
- else /* No Additions in Rel-5: */
- bitvec_set_bit(&bv, L);
-
- bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
- return bv.data_len;
-}
-
-/* Append selection parameters to bitvec */
-static void append_selection_params(struct bitvec *bv,
- const struct gsm48_si_selection_params *sp)
-{
- if (sp->present) {
- bitvec_set_bit(bv, H);
- bitvec_set_bit(bv, sp->cbq);
- bitvec_set_uint(bv, sp->cell_resel_off, 6);
- bitvec_set_uint(bv, sp->temp_offs, 3);
- bitvec_set_uint(bv, sp->penalty_time, 5);
- } else
- bitvec_set_bit(bv, L);
-}
-
-/* Append power offset to bitvec */
-static void append_power_offset(struct bitvec *bv,
- const struct gsm48_si_power_offset *po)
-{
- if (po->present) {
- bitvec_set_bit(bv, H);
- bitvec_set_uint(bv, po->power_offset, 2);
- } else
- bitvec_set_bit(bv, L);
-}
-
-/* Append GPRS indicator to bitvec */
-static void append_gprs_ind(struct bitvec *bv,
- const struct gsm48_si3_gprs_ind *gi)
-{
- if (gi->present) {
- bitvec_set_bit(bv, H);
- bitvec_set_uint(bv, gi->ra_colour, 3);
- /* 0 == SI13 in BCCH Norm, 1 == SI13 sent on BCCH Ext */
- bitvec_set_bit(bv, gi->si13_position);
- } else
- bitvec_set_bit(bv, L);
-}
-
-/* Generate SI2ter Rest Octests 3GPP TS 44.018 Table 10.5.2.33a.1 */
-int rest_octets_si2ter(uint8_t *data)
-{
- struct bitvec bv;
-
- memset(&bv, 0, sizeof(bv));
- bv.data = data;
- bv.data_len = 4;
-
- /* No SI2ter_MP_CHANGE_MARK */
- bitvec_set_bit(&bv, L);
-
- bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
-
- return bv.data_len;
-}
-
-/* Generate SI2bis Rest Octests 3GPP TS 44.018 Table 10.5.2.33.1 */
-int rest_octets_si2bis(uint8_t *data)
-{
- struct bitvec bv;
-
- memset(&bv, 0, sizeof(bv));
- bv.data = data;
- bv.data_len = 1;
-
- bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
-
- return bv.data_len;
-}
-
-/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */
-int rest_octets_si3(uint8_t *data, const struct gsm48_si_ro_info *si3)
-{
- struct bitvec bv;
-
- memset(&bv, 0, sizeof(bv));
- bv.data = data;
- bv.data_len = 4;
-
- /* Optional Selection Parameters */
- append_selection_params(&bv, &si3->selection_params);
-
- /* Optional Power Offset */
- append_power_offset(&bv, &si3->power_offset);
-
- /* Do we have a SI2ter on the BCCH? */
- if (si3->si2ter_indicator)
- bitvec_set_bit(&bv, H);
- else
- bitvec_set_bit(&bv, L);
-
- /* Early Classmark Sending Control */
- if (si3->early_cm_ctrl)
- bitvec_set_bit(&bv, H);
- else
- bitvec_set_bit(&bv, L);
-
- /* Do we have a SI Type 9 on the BCCH? */
- if (si3->scheduling.present) {
- bitvec_set_bit(&bv, H);
- bitvec_set_uint(&bv, si3->scheduling.where, 3);
- } else
- bitvec_set_bit(&bv, L);
-
- /* GPRS Indicator */
- append_gprs_ind(&bv, &si3->gprs_ind);
-
- /* 3G Early Classmark Sending Restriction. If H, then controlled by
- * early_cm_ctrl above */
- if (si3->early_cm_restrict_3g)
- bitvec_set_bit(&bv, L);
- else
- bitvec_set_bit(&bv, H);
-
- if (si3->si2quater_indicator) {
- bitvec_set_bit(&bv, H); /* indicator struct present */
- bitvec_set_uint(&bv, 0, 1); /* message is sent on BCCH Norm */
- }
-
- bitvec_spare_padding(&bv, (bv.data_len*8)-1);
- return bv.data_len;
-}
-
-static int append_lsa_params(struct bitvec *bv,
- const struct gsm48_lsa_params *lsa_params)
-{
- /* FIXME */
- return -1;
-}
-
-/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
-int rest_octets_si4(uint8_t *data, const struct gsm48_si_ro_info *si4, int len)
-{
- struct bitvec bv;
-
- memset(&bv, 0, sizeof(bv));
- bv.data = data;
- bv.data_len = len;
-
- /* SI4 Rest Octets O */
- append_selection_params(&bv, &si4->selection_params);
- append_power_offset(&bv, &si4->power_offset);
- append_gprs_ind(&bv, &si4->gprs_ind);
-
- if (0 /* FIXME */) {
- /* H and SI4 Rest Octets S */
- bitvec_set_bit(&bv, H);
-
- /* LSA Parameters */
- if (si4->lsa_params.present) {
- bitvec_set_bit(&bv, H);
- append_lsa_params(&bv, &si4->lsa_params);
- } else
- bitvec_set_bit(&bv, L);
-
- /* Cell Identity */
- if (1) {
- bitvec_set_bit(&bv, H);
- bitvec_set_uint(&bv, si4->cell_id, 16);
- } else
- bitvec_set_bit(&bv, L);
-
- /* LSA ID Information */
- if (0) {
- bitvec_set_bit(&bv, H);
- /* FIXME */
- } else
- bitvec_set_bit(&bv, L);
- } else {
- /* L and break indicator */
- bitvec_set_bit(&bv, L);
- bitvec_set_bit(&bv, si4->break_ind ? H : L);
- }
-
- return bv.data_len;
-}
-
-
-/* GSM 04.18 ETSI TS 101 503 V8.27.0 (2006-05)
-
-<SI6 rest octets> ::=
-{L | H <PCH and NCH info>}
-{L | H <VBS/VGCS options : bit(2)>}
-{ < DTM_support : bit == L > I < DTM_support : bit == H >
-< RAC : bit (8) >
-< MAX_LAPDm : bit (3) > }
-< Band indicator >
-{ L | H < GPRS_MS_TXPWR_MAX_CCH : bit (5) > }
-<implicit spare >;
-*/
-int rest_octets_si6(uint8_t *data, bool is1800_net)
-{
- struct bitvec bv;
-
- memset(&bv, 0, sizeof(bv));
- bv.data = data;
- bv.data_len = 1;
-
- /* no PCH/NCH info */
- bitvec_set_bit(&bv, L);
- /* no VBS/VGCS options */
- bitvec_set_bit(&bv, L);
- /* no DTM_support */
- bitvec_set_bit(&bv, L);
- /* band indicator */
- if (is1800_net)
- bitvec_set_bit(&bv, L);
- else
- bitvec_set_bit(&bv, H);
- /* no GPRS_MS_TXPWR_MAX_CCH */
- bitvec_set_bit(&bv, L);
-
- bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
- return bv.data_len;
-}
-
-/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a:
- < GPRS Mobile Allocation IE > ::=
- < HSN : bit (6) >
- { 0 | 1 < RFL number list : < RFL number list struct > > }
- { 0 < MA_LENGTH : bit (6) >
- < MA_BITMAP: bit (val(MA_LENGTH) + 1) >
- | 1 { 0 | 1 <ARFCN index list : < ARFCN index list struct > > } } ;
-
- < RFL number list struct > :: =
- < RFL_NUMBER : bit (4) >
- { 0 | 1 < RFL number list struct > } ;
- < ARFCN index list struct > ::=
- < ARFCN_INDEX : bit(6) >
- { 0 | 1 < ARFCN index list struct > } ;
- */
-static int append_gprs_mobile_alloc(struct bitvec *bv)
-{
- /* Hopping Sequence Number */
- bitvec_set_uint(bv, 0, 6);
-
- if (0) {
- /* We want to use a RFL number list */
- bitvec_set_bit(bv, 1);
- /* FIXME: RFL number list */
- } else
- bitvec_set_bit(bv, 0);
-
- if (0) {
- /* We want to use a MA_BITMAP */
- bitvec_set_bit(bv, 0);
- /* FIXME: MA_LENGTH, MA_BITMAP, ... */
- } else {
- bitvec_set_bit(bv, 1);
- if (0) {
- /* We want to provide an ARFCN index list */
- bitvec_set_bit(bv, 1);
- /* FIXME */
- } else
- bitvec_set_bit(bv, 0);
- }
- return 0;
-}
-
-static int encode_t3192(unsigned int t3192)
-{
- /* See also 3GPP TS 44.060
- Table 12.24.2: GPRS Cell Options information element details */
- if (t3192 == 0)
- return 3;
- else if (t3192 <= 80)
- return 4;
- else if (t3192 <= 120)
- return 5;
- else if (t3192 <= 160)
- return 6;
- else if (t3192 <= 200)
- return 7;
- else if (t3192 <= 500)
- return 0;
- else if (t3192 <= 1000)
- return 1;
- else if (t3192 <= 1500)
- return 2;
- else
- return -EINVAL;
-}
-
-static int encode_drx_timer(unsigned int drx)
-{
- if (drx == 0)
- return 0;
- else if (drx == 1)
- return 1;
- else if (drx == 2)
- return 2;
- else if (drx <= 4)
- return 3;
- else if (drx <= 8)
- return 4;
- else if (drx <= 16)
- return 5;
- else if (drx <= 32)
- return 6;
- else if (drx <= 64)
- return 7;
- else
- return -EINVAL;
-}
-
-/* GPRS Cell Options as per TS 04.60 Chapter 12.24
- < GPRS Cell Options IE > ::=
- < NMO : bit(2) >
- < T3168 : bit(3) >
- < T3192 : bit(3) >
- < DRX_TIMER_MAX: bit(3) >
- < ACCESS_BURST_TYPE: bit >
- < CONTROL_ACK_TYPE : bit >
- < BS_CV_MAX: bit(4) >
- { 0 | 1 < PAN_DEC : bit(3) >
- < PAN_INC : bit(3) >
- < PAN_MAX : bit(3) >
- { 0 | 1 < Extension Length : bit(6) >
- < bit (val(Extension Length) + 1
- & { < Extension Information > ! { bit ** = <no string> } } ;
- < Extension Information > ::=
- { 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit >
- < BEP_PERIOD : bit(4) > }
- < PFC_FEATURE_MODE : bit >
- < DTM_SUPPORT : bit >
- <BSS_PAGING_COORDINATION: bit >
- <spare bit > ** ;
- */
-static int append_gprs_cell_opt(struct bitvec *bv,
- const struct gprs_cell_options *gco)
-{
- int t3192, drx_timer_max;
-
- t3192 = encode_t3192(gco->t3192);
- if (t3192 < 0)
- return t3192;
-
- drx_timer_max = encode_drx_timer(gco->drx_timer_max);
- if (drx_timer_max < 0)
- return drx_timer_max;
-
- bitvec_set_uint(bv, gco->nmo, 2);
-
- /* See also 3GPP TS 44.060
- Table 12.24.2: GPRS Cell Options information element details */
- bitvec_set_uint(bv, gco->t3168 / 500 - 1, 3);
-
- bitvec_set_uint(bv, t3192, 3);
- bitvec_set_uint(bv, drx_timer_max, 3);
- /* ACCESS_BURST_TYPE: Hard-code 8bit */
- bitvec_set_bit(bv, 0);
- /* CONTROL_ACK_TYPE: */
- bitvec_set_bit(bv, gco->ctrl_ack_type_use_block);
- bitvec_set_uint(bv, gco->bs_cv_max, 4);
-
- if (0) {
- /* hard-code no PAN_{DEC,INC,MAX} */
- bitvec_set_bit(bv, 0);
- } else {
- /* copied from ip.access BSC protocol trace */
- bitvec_set_bit(bv, 1);
- bitvec_set_uint(bv, 1, 3); /* DEC */
- bitvec_set_uint(bv, 1, 3); /* INC */
- bitvec_set_uint(bv, 15, 3); /* MAX */
- }
-
- if (!gco->ext_info_present) {
- /* no extension information */
- bitvec_set_bit(bv, 0);
- } else {
- /* extension information */
- bitvec_set_bit(bv, 1);
- if (!gco->ext_info.egprs_supported) {
- /* 6bit length of extension */
- bitvec_set_uint(bv, (1 + 3)-1, 6);
- /* EGPRS supported in the cell */
- bitvec_set_bit(bv, 0);
- } else {
- /* 6bit length of extension */
- bitvec_set_uint(bv, (1 + 5 + 3)-1, 6);
- /* EGPRS supported in the cell */
- bitvec_set_bit(bv, 1);
-
- /* 1bit EGPRS PACKET CHANNEL REQUEST */
- if (gco->supports_egprs_11bit_rach == 0) {
- bitvec_set_bit(bv,
- gco->ext_info.use_egprs_p_ch_req);
- } else {
- bitvec_set_bit(bv, 0);
- }
-
- /* 4bit BEP PERIOD */
- bitvec_set_uint(bv, gco->ext_info.bep_period, 4);
- }
- bitvec_set_bit(bv, gco->ext_info.pfc_supported);
- bitvec_set_bit(bv, gco->ext_info.dtm_supported);
- bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination);
- }
-
- return 0;
-}
-
-static void append_gprs_pwr_ctrl_pars(struct bitvec *bv,
- const struct gprs_power_ctrl_pars *pcp)
-{
- bitvec_set_uint(bv, pcp->alpha, 4);
- bitvec_set_uint(bv, pcp->t_avg_w, 5);
- bitvec_set_uint(bv, pcp->t_avg_t, 5);
- bitvec_set_uint(bv, pcp->pc_meas_chan, 1);
- bitvec_set_uint(bv, pcp->n_avg_i, 4);
-}
-
-/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */
-int rest_octets_si13(uint8_t *data, const struct gsm48_si13_info *si13)
-{
- struct bitvec bv;
-
- memset(&bv, 0, sizeof(bv));
- bv.data = data;
- bv.data_len = 20;
-
- if (0) {
- /* No rest octets */
- bitvec_set_bit(&bv, L);
- } else {
- bitvec_set_bit(&bv, H);
- bitvec_set_uint(&bv, si13->bcch_change_mark, 3);
- bitvec_set_uint(&bv, si13->si_change_field, 4);
- if (1) {
- bitvec_set_bit(&bv, 0);
- } else {
- bitvec_set_bit(&bv, 1);
- bitvec_set_uint(&bv, si13->bcch_change_mark, 2);
- append_gprs_mobile_alloc(&bv);
- }
- /* PBCCH not present in cell:
- it shall never be indicated according to 3GPP TS 44.018 Table 10.5.2.37b.1 */
- bitvec_set_bit(&bv, 0);
- bitvec_set_uint(&bv, si13->rac, 8);
- bitvec_set_bit(&bv, si13->spgc_ccch_sup);
- bitvec_set_uint(&bv, si13->prio_acc_thr, 3);
- bitvec_set_uint(&bv, si13->net_ctrl_ord, 2);
- append_gprs_cell_opt(&bv, &si13->cell_opts);
- append_gprs_pwr_ctrl_pars(&bv, &si13->pwr_ctrl_pars);
-
- /* 3GPP TS 44.018 Release 6 / 10.5.2.37b */
- bitvec_set_bit(&bv, H); /* added Release 99 */
- /* claim our SGSN is compatible with Release 99, as EDGE and EGPRS
- * was only added in this Release */
- bitvec_set_bit(&bv, 1);
- }
- bitvec_spare_padding(&bv, (bv.data_len*8)-1);
- return bv.data_len;
-}
diff --git a/src/osmo-bsc/smscb.c b/src/osmo-bsc/smscb.c
new file mode 100644
index 000000000..8e1c6348e
--- /dev/null
+++ b/src/osmo-bsc/smscb.c
@@ -0,0 +1,1153 @@
+/* SMSCB (SMS Cell Broadcast) Handling for OsmoBSC */
+/*
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <limits.h>
+
+#include <osmocom/core/stats.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/signal.h>
+
+#include <osmocom/gsm/cbsp.h>
+#include <osmocom/gsm/protocol/gsm_23_041.h>
+#include <osmocom/gsm/protocol/gsm_48_049.h>
+#include <osmocom/gsm/protocol/gsm_03_41.h>
+
+#include <osmocom/netif/stream.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/signal.h>
+
+/*********************************************************************************
+ * Helper Functions
+ *********************************************************************************/
+
+/* replace the old head of an entire list with a new head; effectively moves the entire
+ * list from old to new head */
+static void llist_replace_head(struct llist_head *new, struct llist_head *old)
+{
+ if (llist_empty(old))
+ INIT_LLIST_HEAD(new);
+ else
+ __llist_add(new, old->prev, old->next);
+ INIT_LLIST_HEAD(old);
+}
+
+/* Build a ETWS Primary Notification message as per TS 23.041 9.4.1.3 */
+static int gen_etws_primary_notification(uint8_t *out, uint16_t serial_nr, uint16_t msg_id,
+ uint16_t warn_type, const uint8_t *sec_info)
+{
+ struct gsm341_etws_message *etws = (struct gsm341_etws_message *)out;
+
+ memset(out, 0, ETWS_PRIM_NOTIF_SIZE);
+
+ osmo_store16be(serial_nr, out);
+ etws->msg_id = osmo_htons(msg_id);
+ etws->warning_type = osmo_htons(warn_type);
+ memcpy(etws->data, sec_info, ETWS_PRIM_NOTIF_SIZE - sizeof(*etws));
+
+ return ETWS_PRIM_NOTIF_SIZE;
+}
+
+static void bts_cbch_init_state(struct bts_smscb_chan_state *cstate, struct gsm_bts *bts)
+{
+ cstate->bts = bts;
+ INIT_LLIST_HEAD(&cstate->messages);
+}
+
+void bts_cbch_init(struct gsm_bts *bts)
+{
+ bts_cbch_init_state(&bts->cbch_basic, bts);
+ bts_cbch_init_state(&bts->cbch_extended, bts);
+ osmo_timer_setup(&bts->cbch_timer, &bts_cbch_timer_cb, bts);
+}
+
+/*! Obtain SMSCB Channel State for given BTS (basic or extended CBCH) */
+struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended)
+{
+ struct bts_smscb_chan_state *chan_state;
+
+ if (extended)
+ chan_state = &bts->cbch_extended;
+ else
+ chan_state = &bts->cbch_basic;
+
+ return chan_state;
+}
+
+/* do an ordered list insertion. we keep the list with increasing period, i.e. the most
+ * frequent message first */
+static void __bts_smscb_add(struct bts_smscb_chan_state *cstate, struct bts_smscb_message *new)
+{
+ struct bts_smscb_message *tmp, *tmp2;
+
+ if (llist_empty(&cstate->messages)) {
+ llist_add(&new->list, &cstate->messages);
+ return;
+ }
+
+ llist_for_each_entry_safe(tmp, tmp2, &cstate->messages, list) {
+ if (tmp->input.rep_period > new->input.rep_period) {
+ /* we found the first message with longer period than the new message,
+ * we must insert ourselves before that one */
+ __llist_add(&new->list, tmp->list.prev, &tmp->list);
+ return;
+ }
+ }
+ /* we didn't find any messages with longer period than us, insert us at tail */
+ llist_add_tail(&new->list, &cstate->messages);
+}
+
+/* stringify a SMSCB for logging */
+const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb)
+{
+ static char buf[128];
+ snprintf(buf, sizeof(buf), "MsgId=0x%04x/SerialNr=0x%04x/Pages=%u/Period=%u/NumBcastReq=%u",
+ smscb->input.msg_id, smscb->input.serial_nr, smscb->num_pages,
+ smscb->input.rep_period, smscb->input.num_bcast_req);
+ return buf;
+}
+
+const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate)
+{
+ if (cstate == &cstate->bts->cbch_basic)
+ return "BASIC";
+ else if (cstate == &cstate->bts->cbch_extended)
+ return "EXTENDED";
+ else
+ return "UNKNOWN";
+}
+
+unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate)
+{
+ unsigned int sched_arr_used = 0;
+ unsigned int i;
+
+ if (cstate->sched_arr_size == 0)
+ return 0;
+
+ /* count the number of used slots */
+ for (i = 0; i < cstate->sched_arr_size; i++) {
+ if (cstate->sched_arr[i])
+ sched_arr_used++;
+ }
+
+ OSMO_ASSERT(sched_arr_used <= UINT_MAX/100);
+ return (sched_arr_used * 100) / cstate->sched_arr_size;
+}
+
+unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate)
+{
+ struct bts_smscb_message *smscb;
+ unsigned int page_count = 0;
+
+ llist_for_each_entry(smscb, &cstate->messages, list)
+ page_count += smscb->num_pages;
+
+ return page_count;
+}
+
+
+/*! Obtain the Cell Global Identifier (CGI) of given BTS; returned in static buffer. */
+static struct osmo_cell_global_id *bts_get_cgi(struct gsm_bts *bts)
+{
+ static struct osmo_cell_global_id cgi;
+ cgi.lai.plmn = bts->network->plmn;
+ cgi.lai.lac = bts->location_area_code;
+ cgi.cell_identity = bts->cell_identity;
+ return &cgi;
+}
+
+/* represents the various lists that the BSC can create as part of a response */
+struct response_state {
+ struct osmo_cbsp_cell_list success; /* osmo_cbsp_cell_ent */
+ struct llist_head fail; /* osmo_cbsp_fail_ent */
+ struct osmo_cbsp_num_compl_list num_completed; /* osmo_cbsp_num_compl_ent */
+ struct osmo_cbsp_loading_list loading; /* osmo_cbsp_loading_ent */
+};
+
+/*! per-BTS callback function used by cbsp_per_bts().
+ * \param[in] bts BTS currently being processed
+ * \param[in] dec decoded CBSP message currently being processed
+ * \param r_state response state accumulating cell lists (success/failure/...)
+ * \param priv opaque private data provided by caller of cbsp_per_bts()
+ * \returns 0 on success; negative TS 48.049 cause value on error */
+typedef int bts_cb_fn(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv);
+
+/* append a success for given cell to response state */
+static void append_success(struct response_state *r_state, struct gsm_bts *bts)
+{
+ struct osmo_cbsp_cell_ent *cent = talloc_zero(r_state, struct osmo_cbsp_cell_ent);
+ struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
+
+ LOG_BTS(bts, DCBS, LOGL_INFO, "Success\n");
+
+ OSMO_ASSERT(cent);
+
+ cent->cell_id.global = *cgi;
+ llist_add_tail(&cent->list, &r_state->success.list);
+}
+
+/* append a failure for given cell to response state */
+static void append_fail(struct response_state *r_state, struct gsm_bts *bts, uint8_t cause)
+{
+ struct osmo_cbsp_fail_ent *fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
+ struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
+
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Failure Cause 0x%02x\n", cause);
+
+ OSMO_ASSERT(fent);
+
+ fent->id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ fent->cell_id.global = *cgi;
+ fent->cause = cause;
+ llist_add_tail(&fent->list, &r_state->fail);
+}
+
+/* append a 'number of broadcasts completed' for given cell to response state */
+static void append_bcast_compl(struct response_state *r_state, struct gsm_bts *bts,
+ struct bts_smscb_message *smscb)
+{
+ struct osmo_cbsp_num_compl_ent *cent = talloc_zero(r_state, struct osmo_cbsp_num_compl_ent);
+ struct osmo_cell_global_id *cgi = bts_get_cgi(bts);
+
+ LOG_BTS(bts, DCBS, LOGL_DEBUG, "Number of Broadcasts Completed: %u\n", smscb->bcast_count);
+
+ OSMO_ASSERT(cent);
+
+ r_state->num_completed.id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ cent->cell_id.global = *cgi;
+ if (smscb->bcast_count > INT16_MAX) {
+ cent->num_compl = INT16_MAX;
+ cent->num_bcast_info = 0x01; /* Overflow */
+ } else {
+ cent->num_compl = smscb->bcast_count;
+ cent->num_bcast_info = 0x00;
+ }
+ llist_add_tail(&cent->list, &r_state->num_completed.list);
+}
+
+static bool etws_msg_id_matches(uint16_t a, uint16_t b)
+{
+ /* ETWS messages are identified by the twelve most significant bits of the Message ID */
+ return (a & 0xFFF0) == (b & 0xFFF0);
+}
+
+/*! Iterate over all BTSs, find matching ones, execute command on BTS, add result
+ * to succeeded/failed lists.
+ * \param[in] net GSM network in which we operate
+ * \param[in] caller-allocated Response state structure collecting results
+ * \param[in] cell_list Decoded CBSP cell list describing BTSs to operate on
+ * \param[in] cb_fn Call-back function to call for each matching BTS
+ * \param[in] priv Opqaue private data; passed to cb_fn
+ * */
+static int cbsp_per_bts(struct gsm_network *net, struct response_state *r_state,
+ const struct osmo_cbsp_cell_list *cell_list,
+ bts_cb_fn *cb_fn, const struct osmo_cbsp_decoded *dec, void *priv)
+{
+ struct osmo_cbsp_cell_ent *ent;
+ struct gsm_bts *bts;
+ uint8_t bts_status[net->num_bts];
+ int rc, ret = 0;
+
+ memset(bts_status, 0, sizeof(bts_status));
+ INIT_LLIST_HEAD(&r_state->success.list);
+ INIT_LLIST_HEAD(&r_state->fail);
+ INIT_LLIST_HEAD(&r_state->num_completed.list);
+ INIT_LLIST_HEAD(&r_state->loading.list);
+
+ /* special case as cell_list->list is empty in this case */
+ if (cell_list->id_discr == CELL_IDENT_BSS) {
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ bts_status[bts->nr] = 1;
+ /* call function on this BTS */
+ rc = cb_fn(bts, dec, r_state, priv);
+ if (rc < 0) {
+ append_fail(r_state, bts, -rc);
+ ret = -1;
+ } else
+ append_success(r_state, bts);
+ }
+ } else {
+ /* normal case: iterate over cell list */
+ llist_for_each_entry(ent, &cell_list->list, list) {
+ bool found_at_least_one = false;
+ /* find all matching BTSs for this entry */
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ struct gsm0808_cell_id cell_id = {
+ .id_discr = cell_list->id_discr,
+ .id = ent->cell_id
+ };
+ if (!gsm_bts_matches_cell_id(bts, &cell_id))
+ continue;
+ found_at_least_one = true;
+ /* skip any BTSs which we've already processed */
+ if (bts_status[bts->nr])
+ continue;
+ bts_status[bts->nr] = 1;
+ /* call function on this BTS */
+ rc = cb_fn(bts, dec, r_state, priv);
+ if (rc < 0) {
+ append_fail(r_state, bts, -rc);
+ ret = -1;
+ } else
+ append_success(r_state, bts);
+ }
+ if (!found_at_least_one) {
+ struct osmo_cbsp_fail_ent *fent;
+ LOGP(DCBS, LOGL_NOTICE, "CBSP: Couldn't find a single matching BTS\n");
+ fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent);
+ OSMO_ASSERT(fent);
+ fent->id_discr = cell_list->id_discr;
+ fent->cell_id = ent->cell_id;
+ llist_add_tail(&fent->list, &r_state->fail);
+ ret = -1;
+ }
+ }
+ }
+ return ret;
+}
+
+/*! Find an existing SMSCB message within given BTS.
+ * \param[in] chan_state BTS CBCH channel state
+ * \param[in] msg_id Message Id of to-be-found message
+ * \param[in] serial_nr Serial Number of to-be-found message
+ * \returns SMSCB message if found; NULL otherwise */
+struct bts_smscb_message *bts_find_smscb(struct bts_smscb_chan_state *chan_state,
+ uint16_t msg_id, uint16_t serial_nr)
+{
+ struct bts_smscb_message *smscb;
+
+ llist_for_each_entry(smscb, &chan_state->messages, list) {
+ if (smscb->input.msg_id == msg_id && smscb->input.serial_nr == serial_nr)
+ return smscb;
+ }
+ return NULL;
+}
+
+/*! create a new SMSCB message for specified BTS; don't link it yet.
+ * \param[in] bts BTS for which the SMSCB is to be allocated
+ * \param[in] wrepl CBSP write-replace message
+ * \returns callee-allocated SMSCB message filled with data from wrepl */
+static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts,
+ const struct osmo_cbsp_write_replace *wrepl)
+{
+ struct bts_smscb_message *smscb = talloc_zero(bts, struct bts_smscb_message);
+ struct osmo_cbsp_content *cont;
+ int i;
+
+ if (!smscb)
+ return NULL;
+
+ OSMO_ASSERT(wrepl->is_cbs);
+
+ /* initialize all pages inside the message */
+ for (i = 0; i < ARRAY_SIZE(smscb->page); i++) {
+ struct bts_smscb_page *page = &smscb->page[i];
+ page->nr = i+1; /* page numbers are 1-based */
+ page->msg = smscb;
+ }
+
+ /* initialize "header" part */
+ smscb->input.msg_id = wrepl->msg_id;
+ smscb->input.serial_nr = wrepl->new_serial_nr;
+ smscb->input.category = wrepl->u.cbs.category;
+ smscb->input.rep_period = wrepl->u.cbs.rep_period;
+ smscb->input.num_bcast_req = wrepl->u.cbs.num_bcast_req;
+ smscb->input.dcs = wrepl->u.cbs.dcs;
+ smscb->num_pages = llist_count(&wrepl->u.cbs.msg_content);
+ if (smscb->num_pages > ARRAY_SIZE(smscb->page)) {
+ LOG_BTS(bts, DCBS, LOGL_ERROR, "SMSCB with too many pages (%u > %zu)\n",
+ smscb->num_pages, ARRAY_SIZE(smscb->page));
+ talloc_free(smscb);
+ return NULL;
+ }
+
+ i = 0;
+ llist_for_each_entry(cont, &wrepl->u.cbs.msg_content, list) {
+ struct gsm23041_msg_param_gsm *msg_param;
+ struct bts_smscb_page *page;
+ size_t bytes_used;
+
+ /* we have just ensured a few lines above that this cannot overflow */
+ page = &smscb->page[i++];
+ msg_param = (struct gsm23041_msg_param_gsm *) &page->data[0];
+
+ /* ensure we don't overflow in the memcpy below */
+ osmo_static_assert(sizeof(*page) > sizeof(*msg_param) + sizeof(cont->data), smscb_space);
+
+ /* build 6 byte header according to TS 23.041 9.4.1.2 */
+ osmo_store16be(wrepl->new_serial_nr, &msg_param->serial_nr);
+ osmo_store16be(wrepl->msg_id, &msg_param->message_id);
+ msg_param->dcs = wrepl->u.cbs.dcs;
+ msg_param->page_param.num_pages = smscb->num_pages;
+ msg_param->page_param.page_nr = page->nr;
+
+ OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(cont->data));
+ OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(page->data) - sizeof(*msg_param));
+ /* we must not use cont->user_len as length here, as it would truncate any
+ * possible 7-bit padding at the end. Always copy the whole page */
+ memcpy(&msg_param->content, cont->data, sizeof(cont->data));
+ bytes_used = sizeof(*msg_param) + cont->user_len;
+ /* compute number of valid blocks in page */
+ page->num_blocks = bytes_used / 22;
+ if (bytes_used % 22)
+ page->num_blocks += 1;
+ }
+
+ return smscb;
+}
+
+/*! remove a SMSCB message */
+void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate,
+ const char *reason)
+{
+ struct bts_smscb_page **arr;
+ int rc;
+
+ LOG_BTS(cstate->bts, DCBS, LOGL_INFO, "%s Deleting %s (Reason: %s)\n",
+ bts_smscb_chan_state_name(cstate), bts_smscb_msg2str(smscb), reason);
+ llist_del(&smscb->list);
+
+ /* we must recompute the scheduler array here, as the old one will have pointers
+ * to the pages of the just-to-be-deleted message */
+ rc = bts_smscb_gen_sched_arr(cstate, &arr);
+ if (rc < 0) {
+ LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Cannot generate new CBCH scheduler array after "
+ "removing message %s. WTF?\n", bts_smscb_msg2str(smscb));
+ /* we cannot free the message now, to ensure the page pointers in the old
+ * array are still valid. let's re-add it to keep things sane */
+ __bts_smscb_add(cstate, smscb);
+ } else {
+ /* success */
+ talloc_free(smscb);
+
+ /* replace array with new one */
+ talloc_free(cstate->sched_arr);
+ cstate->sched_arr = arr;
+ cstate->sched_arr_size = rc;
+ cstate->next_idx = 0;
+ }
+}
+
+
+/*********************************************************************************
+ * Transmit of CBSP to CBC
+ *********************************************************************************/
+
+/* transmit a CBSP RESTART message stating all message data was lost for entire BSS */
+int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg)
+{
+ struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESTART);
+
+ if (is_emerg)
+ cbsp->u.restart.bcast_msg_type = 0x01;
+ cbsp->u.restart.recovery_ind = 0x01; /* message data lost */
+ cbsp->u.restart.cell_list.id_discr = CELL_IDENT_BSS;
+
+ return cbsp_tx_decoded(cbc, cbsp);
+}
+
+/* transmit a CBSP RESTART-INDICATION message stating a cell is operative again */
+int cbsp_tx_restart_bts(struct bsc_cbc_link *cbc, bool is_emerg, struct gsm_bts *bts)
+{
+ struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESTART);
+ struct osmo_cbsp_cell_ent cell_ent;
+ struct osmo_cell_global_id *cgi;
+
+ if (is_emerg)
+ cbsp->u.restart.bcast_msg_type = 0x01;
+ cbsp->u.restart.recovery_ind = 0x00; /* message data available */
+ cbsp->u.restart.cell_list.id_discr = CELL_IDENT_WHOLE_GLOBAL;
+
+ cgi = bts_get_cgi(bts);
+ cell_ent.cell_id.global = *cgi;
+ llist_add(&cell_ent.list, &cbsp->u.restart.cell_list.list);
+
+ return cbsp_tx_decoded(cbc, cbsp);
+}
+
+/* transmit a CBSP FAILURE-INDICATION message stating all message data was lost for one cell */
+int cbsp_tx_failure_bts(struct bsc_cbc_link *cbc, bool is_emerg, struct gsm_bts *bts)
+{
+ struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_FAILURE);
+ struct osmo_cbsp_fail_ent fail_ent;
+ struct osmo_cell_global_id *cgi;
+
+ if (is_emerg)
+ cbsp->u.failure.bcast_msg_type = 0x01;
+
+ cgi = bts_get_cgi(bts);
+ fail_ent.id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ fail_ent.cell_id.global = *cgi;
+ fail_ent.cause = OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_OPERATIONAL;
+ llist_add(&fail_ent.list, &cbsp->u.failure.fail_list);
+
+ return cbsp_tx_decoded(cbc, cbsp);
+}
+
+/* transmit a CBSP KEEPALIVE COMPLETE to the CBC */
+static int tx_cbsp_keepalive_compl(struct bsc_cbc_link *cbc)
+{
+ struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KEEP_ALIVE_COMPL);
+ return cbsp_tx_decoded(cbc, cbsp);
+}
+
+/*********************************************************************************
+ * Per-BTS Processing of CBSP from CBC, called via cbsp_per_bts()
+ *********************************************************************************/
+
+static void etws_pn_stop(struct gsm_bts *bts, bool timeout)
+{
+ if (osmo_bts_has_feature(&bts->features, BTS_FEAT_ETWS_PN)) {
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "ETWS PN broadcast via PCH disabled (cause=%s)\n",
+ timeout ? "timeout" : "request");
+ rsl_etws_pn_command(bts, RSL_CHAN_PCH_AGCH, NULL, 0);
+ }
+ bts->etws.active = false;
+ if (!timeout)
+ osmo_timer_del(&bts->etws.timer);
+}
+
+/* timer call-back once ETWS warning period has expired */
+static void etws_pn_cb(void *data)
+{
+ struct gsm_bts *bts = (struct gsm_bts *)data;
+ etws_pn_stop(bts, true);
+}
+
+
+/* the actual "execution" part: Send ETWS to all active lchan in the BTS and via PCH */
+static void bts_send_etws(struct gsm_bts *bts)
+{
+ struct bts_etws_state *bes = &bts->etws;
+ struct gsm_bts_trx *trx;
+ unsigned int count = 0;
+ int i, j;
+
+ /* iterate over all lchan in each TS in each TRX of this BTS */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ for (j = 0; j < ARRAY_SIZE(ts->lchan); j++) {
+ struct gsm_lchan *lchan = &ts->lchan[j];
+ if (!lchan_may_receive_data(lchan))
+ continue;
+ gsm48_send_rr_app_info(lchan, 0x1, 0x0, bes->primary,
+ sizeof(bes->primary));
+ count++;
+ }
+ }
+ }
+
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Sent ETWS Primary Notification via %u dedicated channels\n",
+ count);
+
+ /* Notify BTS of primary ETWS notification via vendor-specific Abis message */
+ if (osmo_bts_has_feature(&bts->features, BTS_FEAT_ETWS_PN)) {
+ rsl_etws_pn_command(bts, RSL_CHAN_PCH_AGCH, bes->primary, sizeof(bes->primary));
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Sent ETWS Primary Notification via common channel\n");
+ } else
+ LOG_BTS(bts, DCBS, LOGL_ERROR, "BTS doesn't support RSL command for ETWS PN\n");
+}
+
+static int etws_primary_to_bts(struct gsm_bts *bts, const struct osmo_cbsp_write_replace *wrepl)
+{
+ struct bts_etws_state *bes = &bts->etws;
+
+ if (bes->active) {
+ /* we were already broadcasting emergency before receiving this WRITE-REPLACE */
+
+ /* If only the New Serial Number IE, and not the Old Serial Number IE, is included in the
+ * WRITE-REPLACE message, then the BSC shall interpret the message as a write request, i.e. a
+ * broadcast request of a new emergency message without replacing an ongoing emergency message
+ * broadcast. */
+ if (!wrepl->old_serial_nr) {
+ /* If a write request is received for a cell where an emergency message broadcast is
+ * currently ongoing, the write request is considered as failed */
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP WRITE rejected due to ongoing emergency "
+ "while no Old Serial Nr IE present in CBSP WRITE\n");
+ return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
+ }
+
+ if (!etws_msg_id_matches(*wrepl->old_serial_nr, bes->input.serial_nr)) {
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP WRITE-REPLACE old_serial 0x%04x doesn't match "
+ "current serial 0x%04x. Is the CBC confused?\n",
+ *wrepl->old_serial_nr, bes->input.serial_nr);
+ /* we allow the WRITE-REPLACE to continue, TS 48.049 doesn't specify how to
+ * handle situations like this */
+ }
+ }
+
+ /* copy over all the data to per-BTS private state */
+ bes->input.msg_id = wrepl->msg_id;
+ bes->input.serial_nr = wrepl->new_serial_nr;
+ bes->input.warn_type = wrepl->u.emergency.warning_type;
+ memcpy(bes->input.sec_info, wrepl->u.emergency.warning_sec_info, sizeof(bes->input.sec_info));
+
+ /* generate the encoded ETWS PN */
+ gen_etws_primary_notification(bes->primary, bes->input.serial_nr, bes->input.msg_id,
+ bes->input.warn_type, bes->input.sec_info);
+
+ bes->active = true;
+
+ bts_send_etws(bts);
+
+ /* start the expiration timer, if any */
+ if (wrepl->u.emergency.warning_period != 0xffffffff) {
+ osmo_timer_schedule(&bts->etws.timer, wrepl->u.emergency.warning_period, 0);
+ } else
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Unlimited ETWS PN broadcast, this breaks "
+ "normal network operation due to PCH blockage\n");
+
+ return 0;
+}
+
+/*! Try to execute a write-replace operation; roll-back if it fails.
+ * \param[in] chan_state BTS CBCH channel state
+ * \param[in] extended_cbch Basic (false) or Extended (true) CBCH
+ * \param[in] new_msg New SMSCB message which should be added
+ * \param[in] exclude_msg Existing SMSCB message that shall be replaced (if possible). Can be NULL
+ * \return 0 on success; negative on error */
+static int bts_try_write_replace(struct bts_smscb_chan_state *chan_state,
+ struct bts_smscb_message *new_msg,
+ struct bts_smscb_message *exclude_msg,
+ struct response_state *r_state)
+{
+ struct bts_smscb_page **arr;
+ int rc;
+
+ if (exclude_msg) {
+ /* temporarily remove from list of SMSCB */
+ llist_del(&exclude_msg->list);
+ }
+ /* temporarily add new_msg to list of SMSCB */
+ __bts_smscb_add(chan_state, new_msg);
+
+ /* attempt to create scheduling array */
+ rc = bts_smscb_gen_sched_arr(chan_state, &arr);
+ if (rc < 0) {
+ /* it didn't work out; we couldn't schedule it */
+ /* remove the new message again */
+ llist_del(&new_msg->list);
+ /* up to the caller to free() it */
+ if (exclude_msg) {
+ /* re-add the temporarily removed message */
+ __bts_smscb_add(chan_state, new_msg);
+ }
+ return -1;
+ }
+
+ /* success! */
+ if (exclude_msg) {
+ LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Replaced MsgId=0x%04x/Serial=0x%04x, "
+ "pages(%u -> %u), period(%u -> %u), num_bcast(%u -> %u)\n",
+ bts_smscb_chan_state_name(chan_state),
+ new_msg->input.msg_id, new_msg->input.serial_nr,
+ exclude_msg->num_pages, new_msg->num_pages,
+ exclude_msg->input.rep_period, new_msg->input.rep_period,
+ exclude_msg->input.num_bcast_req, new_msg->input.num_bcast_req);
+ append_bcast_compl(r_state, chan_state->bts, exclude_msg);
+ talloc_free(exclude_msg);
+ } else
+ LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Added %s\n",
+ bts_smscb_chan_state_name(chan_state), bts_smscb_msg2str(new_msg));
+
+ /* replace array with new one */
+ talloc_free(chan_state->sched_arr);
+ chan_state->sched_arr = arr;
+ chan_state->sched_arr_size = rc;
+ chan_state->next_idx = 0;
+ return 0;
+}
+
+
+static int bts_rx_write_replace(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
+ bool extended_cbch = wrepl->u.cbs.channel_ind;
+ struct bts_smscb_chan_state *chan_state = bts_get_smscb_chan(bts, extended_cbch);
+ struct bts_smscb_message *smscb;
+ int rc;
+
+ if (!wrepl->is_cbs) {
+ return etws_primary_to_bts(bts, wrepl);
+ }
+
+ /* check if cell has a CBCH at all */
+ if (!gsm_bts_get_cbch(bts))
+ return -CBSP_CAUSE_CB_NOT_SUPPORTED;
+
+ /* check for duplicate */
+ if (bts_find_smscb(chan_state, wrepl->msg_id, wrepl->new_serial_nr))
+ return -CBSP_CAUSE_MSG_REF_ALREADY_USED;
+
+ if (!wrepl->old_serial_nr) { /* new message */
+ /* create new message */
+ smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
+ if (!smscb)
+ return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
+ /* check if scheduling permits this additional message */
+ rc = bts_try_write_replace(chan_state, smscb, NULL, r_state);
+ if (rc < 0) {
+ talloc_free(smscb);
+ return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
+ }
+ } else { /* modify / replace existing message */
+ struct bts_smscb_message *smscb_old;
+ /* find existing message */
+ smscb_old = bts_find_smscb(chan_state, wrepl->msg_id, *wrepl->old_serial_nr);
+ if (!smscb_old)
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+ /* create new message */
+ smscb = bts_smscb_msg_from_wrepl(bts, wrepl);
+ if (!smscb)
+ return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED;
+ /* check if scheduling permits this modified message */
+ rc = bts_try_write_replace(chan_state, smscb, smscb_old, r_state);
+ if (rc < 0) {
+ talloc_free(smscb);
+ return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED;
+ }
+ }
+ return 0;
+}
+
+static int bts_rx_kill(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ const struct osmo_cbsp_kill *kill = &dec->u.kill;
+
+ if (kill->channel_ind) {
+ /* KILL for CBS message */
+ struct bts_smscb_chan_state *chan_state;
+ struct bts_smscb_message *smscb;
+ bool extended = false;
+
+ if (*kill->channel_ind == 0x01)
+ extended = true;
+
+ chan_state = bts_get_smscb_chan(bts, extended);
+
+ /* Find message by msg_id + old_serial_nr */
+ smscb = bts_find_smscb(chan_state, kill->msg_id, kill->old_serial_nr);
+ if (!smscb)
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+
+ append_bcast_compl(r_state, chan_state->bts, smscb);
+
+ /* Remove it */
+ bts_smscb_del(smscb, chan_state, "KILL");
+ } else {
+ /* KILL for Emergency */
+ struct bts_etws_state *bes = &bts->etws;
+
+ if (!bes->active) {
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP KILL (Emerg) but no emergency "
+ "broadcast is currently active in this cell\n");
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+ }
+
+ if (kill->msg_id != bes->input.msg_id) {
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP KILL (Emerg) for msg_id 0x%04x, but "
+ "current emergency msg_id is 0x%04x\n", kill->msg_id, bes->input.msg_id);
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+ }
+
+ if (!etws_msg_id_matches(kill->old_serial_nr, bes->input.serial_nr)) {
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP KILL (Emerg) for old_serial_nr 0x%04x, but "
+ "current emergency serial_nr is 0x%04x\n",
+ kill->old_serial_nr, bes->input.serial_nr);
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+ }
+
+ /* stop broadcasting the PN in this BTS */
+ etws_pn_stop(bts, false);
+ }
+ return 0;
+}
+
+static int bts_rx_reset(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ struct bts_smscb_chan_state *chan_state;
+ struct bts_smscb_message *smscb, *smscb2;
+
+ LOG_BTS(bts, DCBS, LOGL_NOTICE, "Rx CBSP RESET: clearing all state; disabling broadcast\n");
+
+ /* remove all SMSCB from CBCH BASIC this BTS */
+ chan_state = bts_get_smscb_chan(bts, false);
+ llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
+ bts_smscb_del(smscb, chan_state, "RESET");
+
+ /* remove all SMSCB from CBCH EXTENDED this BTS */
+ chan_state = bts_get_smscb_chan(bts, true);
+ llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
+ bts_smscb_del(smscb, chan_state, "RESET");
+
+ osmo_timer_del(&bts->etws.timer);
+
+ /* Make sure that broadcast is disabled */
+ rsl_etws_pn_command(bts, RSL_CHAN_PCH_AGCH, NULL, 0);
+ return 0;
+}
+
+static int bts_rx_status_query(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec,
+ struct response_state *r_state, void *priv)
+{
+ const struct osmo_cbsp_msg_status_query *query = &dec->u.msg_status_query;
+ struct bts_smscb_chan_state *chan_state;
+ struct bts_smscb_message *smscb;
+ bool extended = false;
+
+ if (query->channel_ind == 0x01)
+ extended = true;
+ chan_state = bts_get_smscb_chan(bts, extended);
+
+ /* Find message by msg_id + old_serial_nr */
+ smscb = bts_find_smscb(chan_state, query->msg_id, query->old_serial_nr);
+ if (!smscb)
+ return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED;
+
+ append_bcast_compl(r_state, chan_state->bts, smscb);
+
+ return 0;
+}
+
+
+/*********************************************************************************
+ * Receive of CBSP from CBC
+ *********************************************************************************/
+
+static int cbsp_rx_write_replace(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace;
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ enum cbsp_channel_ind channel_ind;
+ int rc;
+
+ LOGP(DCBS, LOGL_INFO, "CBSP Rx WRITE_REPLACE (%s)\n", wrepl->is_cbs ? "CBS" : "EMERGENCY");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.write_replace.cell_list,
+ bts_rx_write_replace, dec, NULL);
+ /* generate response */
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_FAIL);
+ struct osmo_cbsp_write_replace_failure *fail = &resp->u.write_replace_fail;
+ fail->msg_id = wrepl->msg_id;
+ fail->new_serial_nr = wrepl->new_serial_nr;
+ fail->old_serial_nr = wrepl->old_serial_nr;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+ fail->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&fail->cell_list.list, &r_state->success.list);
+ if (wrepl->is_cbs) {
+ channel_ind = wrepl->u.cbs.channel_ind;
+ fail->channel_ind = &channel_ind;
+ }
+ if (wrepl->old_serial_nr) {
+ fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
+ }
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_COMPL);
+ struct osmo_cbsp_write_replace_complete *compl = &resp->u.write_replace_compl;
+ compl->msg_id = wrepl->msg_id;
+ compl->new_serial_nr = wrepl->new_serial_nr;
+ compl->old_serial_nr = wrepl->old_serial_nr;
+ compl->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&compl->cell_list.list, &r_state->success.list);
+ if (wrepl->is_cbs) {
+ channel_ind = wrepl->u.cbs.channel_ind;
+ compl->channel_ind = &channel_ind;
+ }
+ if (wrepl->old_serial_nr) {
+ compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
+ }
+ }
+
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+static int cbsp_rx_keep_alive(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KEEP_ALIVE\n");
+
+ /* FIXME: repetition period */
+ return tx_cbsp_keepalive_compl(cbc);
+}
+
+static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ const struct osmo_cbsp_kill *kill = &dec->u.kill;
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ int rc;
+
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KILL\n");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.kill.cell_list, bts_rx_kill, dec, NULL);
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_FAIL);
+ struct osmo_cbsp_kill_failure *fail = &resp->u.kill_fail;
+ fail->msg_id = kill->msg_id;
+ fail->old_serial_nr = kill->old_serial_nr;
+ fail->channel_ind = kill->channel_ind;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+
+ /* if the KILL relates to CBS, the "Channel Indicator" IE is present */
+ if (kill->channel_ind) {
+ /* only if it was CBS */
+ fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
+ } else {
+ /* only if it was emergency */
+ fail->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&fail->cell_list.list, &r_state->success.list);
+ }
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_COMPL);
+ struct osmo_cbsp_kill_complete *compl = &resp->u.kill_compl;
+ compl->msg_id = kill->msg_id;
+ compl->old_serial_nr = kill->old_serial_nr;
+ compl->channel_ind = kill->channel_ind;
+
+ /* if the KILL relates to CBS, the "Channel Indicator" IE is present */
+ if (kill->channel_ind) {
+ /* only if it was CBS */
+ compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
+ } else {
+ /* only if it was emergency */
+ compl->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&compl->cell_list.list, &r_state->success.list);
+ }
+ }
+
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+static int cbsp_rx_reset(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ int rc;
+
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx RESET\n");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.reset.cell_list, bts_rx_reset, dec, NULL);
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_FAIL);
+ struct osmo_cbsp_reset_failure *fail = &resp->u.reset_fail;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+
+ fail->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&fail->cell_list.list, &r_state->success.list);
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_COMPL);
+ struct osmo_cbsp_reset_complete *compl = &resp->u.reset_compl;
+ if (dec->u.reset.cell_list.id_discr == CELL_IDENT_BSS) {
+ /* replace the list of individual cell identities with CELL_IDENT_BSS */
+ compl->cell_list.id_discr = CELL_IDENT_BSS;
+ /* no need to free success_list entries, hierarchical talloc works */
+ } else {
+ compl->cell_list.id_discr = r_state->success.id_discr;
+ llist_replace_head(&compl->cell_list.list, &r_state->success.list);
+ }
+ }
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+static int cbsp_rx_status_query(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ const struct osmo_cbsp_msg_status_query *query = &dec->u.msg_status_query;
+ struct gsm_network *net = cbc->net;
+ struct response_state *r_state = talloc_zero(cbc, struct response_state);
+ struct osmo_cbsp_decoded *resp;
+ int rc;
+
+ LOGP(DCBS, LOGL_DEBUG, "CBSP Rx MESSAGE STATUS QUERY\n");
+
+ rc = cbsp_per_bts(net, r_state, &dec->u.msg_status_query.cell_list, bts_rx_status_query, dec, NULL);
+ if (rc < 0) {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_MSG_STATUS_QUERY_FAIL);
+ struct osmo_cbsp_msg_status_query_failure *fail = &resp->u.msg_status_query_fail;
+ fail->msg_id = query->msg_id;
+ fail->old_serial_nr = query->old_serial_nr;
+ fail->channel_ind = query->channel_ind;
+ llist_replace_head(&fail->fail_list, &r_state->fail);
+
+ fail->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list);
+ } else {
+ resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_MSG_STATUS_QUERY_COMPL);
+ struct osmo_cbsp_msg_status_query_complete *compl = &resp->u.msg_status_query_compl;
+ compl->msg_id = query->msg_id;
+ compl->old_serial_nr = query->old_serial_nr;
+ compl->channel_ind = query->channel_ind;
+
+ if (dec->u.msg_status_query.cell_list.id_discr == CELL_IDENT_BSS) {
+ /* replace the list of individual cell identities with CELL_IDENT_BSS */
+ compl->num_compl_list.id_discr = CELL_IDENT_BSS;
+ /* no need to free num_completed_list entries, hierarchical talloc works */
+ } else {
+ compl->num_compl_list.id_discr = r_state->num_completed.id_discr;
+ llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list);
+ }
+ }
+ cbsp_tx_decoded(cbc, resp);
+ talloc_free(r_state);
+ return rc;
+}
+
+
+/*! process an incoming, already decoded CBSP message from the CBC.
+ * \param[in] cbc link to the CBC
+ * \param[in] dec decoded CBSP message structure. Ownership not transferred.
+ * \returns 0 on success; negative on error. */
+int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec)
+{
+ int rc = -1;
+
+ switch (dec->msg_type) {
+ case CBSP_MSGT_WRITE_REPLACE: /* create or modify message */
+ rc = cbsp_rx_write_replace(cbc, dec);
+ break;
+ case CBSP_MSGT_KEEP_ALIVE: /* solicit an acknowledgement */
+ rc = cbsp_rx_keep_alive(cbc, dec);
+ break;
+ case CBSP_MSGT_KILL: /* remove message */
+ rc = cbsp_rx_kill(cbc, dec);
+ break;
+ case CBSP_MSGT_RESET: /* stop broadcasting of all messages */
+ rc = cbsp_rx_reset(cbc, dec);
+ break;
+ case CBSP_MSGT_MSG_STATUS_QUERY:
+ rc = cbsp_rx_status_query(cbc, dec);
+ break;
+ case CBSP_MSGT_LOAD_QUERY:
+ case CBSP_MSGT_SET_DRX:
+ LOGP(DCBS, LOGL_ERROR, "Received Unimplemented CBSP Message Type %s",
+ get_value_string(cbsp_msg_type_names, dec->msg_type));
+ /* we should implement those eventually */
+ break;
+ default:
+ LOGP(DCBS, LOGL_ERROR, "Received Unknown/Unexpected CBSP Message Type %s",
+ get_value_string(cbsp_msg_type_names, dec->msg_type));
+ break;
+ }
+ return rc;
+}
+
+/* initialize the ETWS state of a BTS */
+void bts_etws_init(struct gsm_bts *bts)
+{
+ bts->etws.active = false;
+ osmo_timer_setup(&bts->etws.timer, etws_pn_cb, bts);
+}
+
+/* BSC is bootstrapping a BTS; install any currently active ETWS PN */
+static void bts_etws_bootstrap(struct gsm_bts *bts)
+{
+ if (bts->etws.active)
+ bts_send_etws(bts);
+}
+
+/* Callback function to be called every time we receive a signal from NM */
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct nm_running_chg_signal_data *nsd;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+
+ if (signal != S_NM_RUNNING_CHG)
+ return 0;
+
+ nsd = signal_data;
+ bts = nsd->bts;
+
+ switch (nsd->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ trx = (struct gsm_bts_trx *)nsd->obj;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ trx = gsm_bts_bb_trx_get_trx((struct gsm_bts_bb_trx *)nsd->obj);
+ break;
+ default:
+ return 0;
+ }
+
+ struct gsm_lchan *cbch = gsm_bts_get_cbch(bts);
+ if (!cbch)
+ return 0;
+ /* We only care about state changes of TRX holding the CBCH. */
+ if (trx != cbch->ts->trx)
+ return 0;
+
+ if (nsd->running) {
+ if (trx_is_usable(trx)) {
+ LOG_BTS(bts, DCBS, LOGL_INFO, "BTS becomes available for CBCH\n");
+ /* Start CBCH transmit timer if CBCH is present */
+ bts_cbch_timer_schedule(trx->bts);
+ /* Start ETWS/PWS Primary Notification, if active */
+ bts_etws_bootstrap(trx->bts);
+ cbsp_tx_restart_bts(bts->network->cbc, true, bts);
+ cbsp_tx_restart_bts(bts->network->cbc, false, bts);
+ }
+ } else {
+ if (osmo_timer_pending(&bts->cbch_timer)) {
+ /* If timer is ongoing it means CBCH was available */
+ LOG_BTS(bts, DCBS, LOGL_INFO, "BTS becomes unavailable for CBCH\n");
+ osmo_timer_del(&bts->cbch_timer);
+ cbsp_tx_failure_bts(bts->network->cbc, true, bts);
+ cbsp_tx_failure_bts(bts->network->cbc, false, bts);
+ } /* else: CBCH was already unavailable before */
+ }
+ return 0;
+}
+
+/* To be called once at startup of the process: */
+void smscb_global_init(void)
+{
+ osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
+}
diff --git a/src/osmo-bsc/smscb_vty.c b/src/osmo-bsc/smscb_vty.c
new file mode 100644
index 000000000..b13d2db07
--- /dev/null
+++ b/src/osmo-bsc/smscb_vty.c
@@ -0,0 +1,421 @@
+/* CBSP (Cell Broadcast Service Protocol) Handling for OsmoBSC */
+/*
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/debug.h>
+#include <osmocom/bsc/smscb.h>
+#include <osmocom/bsc/bsc_msc_data.h>
+#include <osmocom/bsc/bts.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/cbsp.h>
+
+/*********************************************************************************
+ * cbc
+ *********************************************************************************/
+static struct bsc_cbc_link *vty_cbc_data(struct vty *vty)
+{
+ return bsc_gsmnet->cbc;
+}
+
+DEFUN(cfg_cbc, cfg_cbc_cmd,
+ "cbc", "Configure CBSP Link to Cell Broadcast Centre\n")
+{
+ vty->node = CBC_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_mode, cfg_cbc_mode_cmd,
+ "mode (server|client|disabled)",
+ "Set OsmoBSC as CBSP server or client\n"
+ "CBSP Server: listen for inbound TCP connections from a remote Cell Broadcast Centre\n"
+ "CBSP Client: establish outbound TCP connection to a remote Cell Broadcast Centre\n"
+ "Disable CBSP link\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ cbc->mode = get_string_value(bsc_cbc_link_mode_names, argv[0]);
+ OSMO_ASSERT(cbc->mode >= 0);
+
+ /* Immediately restart/stop CBSP only when coming from a telnet session. The settings from the config file take
+ * effect in osmo_bsc_main.c's invocation of bsc_cbc_link_restart(). */
+ if (vty->type != VTY_FILE)
+ bsc_cbc_link_restart();
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_server, cfg_cbc_server_cmd,
+ "server", "Configure OsmoBSC's CBSP server role\n")
+{
+ vty->node = CBC_SERVER_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_server_local_ip, cfg_cbc_server_local_ip_cmd,
+ "local-ip " VTY_IPV46_CMD,
+ "Set IP Address to listen on for inbound CBSP from a Cell Broadcast Centre\n"
+ "IPv4 address\n" "IPv6 address\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ osmo_sockaddr_str_from_str(&cbc->server.local_addr, argv[0], cbc->server.local_addr.port);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_server_local_port, cfg_cbc_server_local_port_cmd,
+ "local-port <1-65535>",
+ "Set TCP port to listen on for inbound CBSP from a Cell Broadcast Centre\n"
+ "CBSP port number (Default: " OSMO_STRINGIFY_VAL(CBSP_TCP_PORT) ")\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ cbc->server.local_addr.port = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_client, cfg_cbc_client_cmd,
+ "client", "Configure OsmoBSC's CBSP client role\n")
+{
+ vty->node = CBC_CLIENT_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_client_remote_ip, cfg_cbc_client_remote_ip_cmd,
+ "remote-ip " VTY_IPV46_CMD,
+ "Set IP Address of the Cell Broadcast Centre, to establish CBSP link to\n"
+ "IPv4 address\n" "IPv6 address\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ osmo_sockaddr_str_from_str(&cbc->client.remote_addr, argv[0], cbc->client.remote_addr.port);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_client_remote_port, cfg_cbc_client_remote_port_cmd,
+ "remote-port <1-65535>",
+ "Set TCP port of the Cell Broadcast Centre, to establish CBSP link to\n"
+ "CBSP port number (Default: " OSMO_STRINGIFY_VAL(CBSP_TCP_PORT) ")\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ cbc->client.remote_addr.port = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_client_local_ip, cfg_cbc_client_local_ip_cmd,
+ "local-ip " VTY_IPV46_CMD,
+ "Set local bind address for the outbound CBSP link to the Cell Broadcast Centre\n"
+ "IPv4 address\n" "IPv6 address\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ osmo_sockaddr_str_from_str(&cbc->client.local_addr, argv[0], cbc->client.local_addr.port);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_client_local_port, cfg_cbc_client_local_port_cmd,
+ "local-port <1-65535>",
+ "Set local bind port for the outbound CBSP link to the Cell Broadcast Centre\n"
+ "port number\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ cbc->client.local_addr.port = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_client_no_local_ip, cfg_cbc_client_no_local_ip_cmd,
+ "no local-ip",
+ NO_STR "Remove local IP address bind config for the CBSP client mode\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ cbc->client.local_addr = (struct osmo_sockaddr_str){ .port = cbc->client.local_addr.port };
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_cbc_client_no_local_port, cfg_cbc_client_no_local_port_cmd,
+ "no local-port",
+ NO_STR "Remove local TCP port bind config for the CBSP client mode\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ cbc->client.local_addr.port = 0;
+ return CMD_SUCCESS;
+}
+
+static struct cmd_node cbc_node = {
+ CBC_NODE,
+ "%s(config-cbc)# ",
+ 1,
+};
+
+static struct cmd_node cbc_server_node = {
+ CBC_SERVER_NODE,
+ "%s(config-cbc-server)# ",
+ 1,
+};
+
+static struct cmd_node cbc_client_node = {
+ CBC_CLIENT_NODE,
+ "%s(config-cbc-client)# ",
+ 1,
+};
+
+static int config_write_cbc(struct vty *vty)
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+
+ bool default_server_local;
+ bool default_client_remote;
+ bool default_client_local;
+
+ default_server_local = !osmo_sockaddr_str_cmp(&cbc->server.local_addr,
+ &bsc_cbc_default_server_local_addr);
+ default_client_remote = !osmo_sockaddr_str_is_set(&cbc->client.remote_addr);
+ default_client_local = !osmo_sockaddr_str_is_set(&cbc->client.local_addr);
+
+ /* If all reflects default values, skip the 'cbc' section */
+ if (cbc->mode == BSC_CBC_LINK_MODE_DISABLED
+ && default_server_local
+ && default_client_remote && default_client_local)
+ return 0;
+
+ vty_out(vty, "cbc%s", VTY_NEWLINE);
+ vty_out(vty, " mode %s%s", bsc_cbc_link_mode_name(cbc->mode), VTY_NEWLINE);
+
+ if (!default_server_local) {
+ vty_out(vty, " server%s", VTY_NEWLINE);
+
+ if (strcmp(cbc->server.local_addr.ip, bsc_cbc_default_server_local_addr.ip))
+ vty_out(vty, " local-ip %s%s", cbc->server.local_addr.ip, VTY_NEWLINE);
+ if (cbc->server.local_addr.port != bsc_cbc_default_server_local_addr.port)
+ vty_out(vty, " local-port %u%s", cbc->server.local_addr.port, VTY_NEWLINE);
+ }
+
+ if (!(default_client_remote && default_client_local)) {
+ vty_out(vty, " client%s", VTY_NEWLINE);
+
+ if (osmo_sockaddr_str_is_set(&cbc->client.remote_addr)) {
+ vty_out(vty, " remote-ip %s%s", cbc->client.remote_addr.ip, VTY_NEWLINE);
+ if (cbc->client.remote_addr.port != CBSP_TCP_PORT)
+ vty_out(vty, " remote-port %u%s", cbc->client.remote_addr.port, VTY_NEWLINE);
+ }
+
+ if (cbc->client.local_addr.ip[0])
+ vty_out(vty, " local-ip %s%s", cbc->client.local_addr.ip, VTY_NEWLINE);
+ if (cbc->client.local_addr.port)
+ vty_out(vty, " local-port %u%s", cbc->client.local_addr.port, VTY_NEWLINE);
+ }
+
+ return 0;
+}
+
+DEFUN(show_cbc, show_cbc_cmd,
+ "show cbc",
+ SHOW_STR "Display state of CBC / CBSP\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+
+ switch (cbc->mode) {
+ case BSC_CBC_LINK_MODE_DISABLED:
+ vty_out(vty, "CBSP link is disabled%s", VTY_NEWLINE);
+ break;
+
+ case BSC_CBC_LINK_MODE_SERVER:
+ vty_out(vty, "OsmoBSC is configured as CBSP Server on " OSMO_SOCKADDR_STR_FMT "%s",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->server.local_addr), VTY_NEWLINE);
+ vty_out(vty, "CBSP Server Connection: %s%s",
+ cbc->server.sock_name ? cbc->server.sock_name : "Disconnected", VTY_NEWLINE);
+ break;
+
+ case BSC_CBC_LINK_MODE_CLIENT:
+ vty_out(vty, "OsmoBSC is configured as CBSP Client to remote CBC at " OSMO_SOCKADDR_STR_FMT "%s",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&cbc->client.remote_addr), VTY_NEWLINE);
+ vty_out(vty, "CBSP Client Connection: %s%s",
+ cbc->client.sock_name ? cbc->client.sock_name : "Disconnected", VTY_NEWLINE);
+ break;
+ }
+ return CMD_SUCCESS;
+}
+
+/* --- Deprecated 'cbc' commands for backwards compat --- */
+
+DEFUN_DEPRECATED(cfg_cbc_remote_ip, cfg_cbc_remote_ip_cmd,
+ "remote-ip A.B.C.D",
+ "IP Address of the Cell Broadcast Centre\n"
+ "IP Address of the Cell Broadcast Centre\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ vty_out(vty, "%% cbc/remote-ip config is deprecated, instead use cbc/client/remote-ip and cbc/ mode%s",
+ VTY_NEWLINE);
+ osmo_sockaddr_str_from_str(&cbc->client.remote_addr, argv[0], cbc->client.remote_addr.port);
+ cbc->mode = BSC_CBC_LINK_MODE_CLIENT;
+ if (vty->type != VTY_FILE)
+ bsc_cbc_link_restart();
+ return CMD_SUCCESS;
+}
+DEFUN_DEPRECATED(cfg_cbc_no_remote_ip, cfg_cbc_no_remote_ip_cmd,
+ "no remote-ip",
+ NO_STR "Remove IP address of CBC; disables outbound CBSP connections\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ vty_out(vty, "%% cbc/remote-ip config is deprecated, instead use cbc/client/remote-ip and cbc/mode%s",
+ VTY_NEWLINE);
+ if (cbc->mode == BSC_CBC_LINK_MODE_CLIENT) {
+ cbc->mode = BSC_CBC_LINK_MODE_DISABLED;
+ if (vty->type != VTY_FILE)
+ bsc_cbc_link_restart();
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_cbc_remote_port, cfg_cbc_remote_port_cmd,
+ "remote-port <1-65535>",
+ "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n"
+ "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ vty_out(vty, "%% cbc/remote-port config is deprecated, instead use cbc/client/remote-port%s",
+ VTY_NEWLINE);
+ cbc->client.remote_addr.port = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_cbc_listen_port, cfg_cbc_listen_port_cmd,
+ "listen-port <1-65535>",
+ "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n"
+ "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ vty_out(vty, "%% cbc/listen-port config is deprecated, instead use cbc/server/local-port and cbc/mode%s",
+ VTY_NEWLINE);
+ cbc->mode = BSC_CBC_LINK_MODE_SERVER;
+ cbc->server.local_addr.port = atoi(argv[0]);
+ if (vty->type != VTY_FILE)
+ bsc_cbc_link_restart();
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_cbc_no_listen_port, cfg_cbc_no_listen_port_cmd,
+ "no listen-port",
+ NO_STR "Remove CBSP Listen Port; disables inbound CBSP connections\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ vty_out(vty, "%% cbc/listen-port config is deprecated, instead use cbc/server/local-port and cbc/mode%s",
+ VTY_NEWLINE);
+ if (cbc->mode == BSC_CBC_LINK_MODE_SERVER) {
+ cbc->mode = BSC_CBC_LINK_MODE_DISABLED;
+ if (vty->type != VTY_FILE)
+ bsc_cbc_link_restart();
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_cbc_listen_ip, cfg_cbc_listen_ip_cmd,
+ "listen-ip A.B.C.D",
+ "Local IP Address where BSC listens for incoming CBC connections (Default: 127.0.0.1)\n"
+ "Local IP Address where BSC listens for incoming CBC connections\n")
+{
+ struct bsc_cbc_link *cbc = vty_cbc_data(vty);
+ vty_out(vty, "%% cbc/listen-ip config is deprecated, instead use cbc/server/local-ip%s",
+ VTY_NEWLINE);
+ osmo_sockaddr_str_from_str(&cbc->server.local_addr, argv[0], cbc->server.local_addr.port);
+ return CMD_SUCCESS;
+}
+
+void cbc_vty_init(void)
+{
+ install_element_ve(&show_cbc_cmd);
+
+ install_element(CONFIG_NODE, &cfg_cbc_cmd);
+ install_node(&cbc_node, config_write_cbc);
+ install_element(CBC_NODE, &cfg_cbc_mode_cmd);
+
+ install_element(CBC_NODE, &cfg_cbc_server_cmd);
+ install_node(&cbc_server_node, NULL);
+ install_element(CBC_SERVER_NODE, &cfg_cbc_server_local_ip_cmd);
+ install_element(CBC_SERVER_NODE, &cfg_cbc_server_local_port_cmd);
+
+ install_element(CBC_NODE, &cfg_cbc_client_cmd);
+ install_node(&cbc_client_node, NULL);
+ install_element(CBC_CLIENT_NODE, &cfg_cbc_client_remote_ip_cmd);
+ install_element(CBC_CLIENT_NODE, &cfg_cbc_client_remote_port_cmd);
+ install_element(CBC_CLIENT_NODE, &cfg_cbc_client_local_ip_cmd);
+ install_element(CBC_CLIENT_NODE, &cfg_cbc_client_local_port_cmd);
+ install_element(CBC_CLIENT_NODE, &cfg_cbc_client_no_local_ip_cmd);
+ install_element(CBC_CLIENT_NODE, &cfg_cbc_client_no_local_port_cmd);
+
+ /* Deprecated, for backwards compat */
+ install_element(CBC_NODE, &cfg_cbc_remote_ip_cmd);
+ install_element(CBC_NODE, &cfg_cbc_no_remote_ip_cmd);
+ install_element(CBC_NODE, &cfg_cbc_remote_port_cmd);
+ install_element(CBC_NODE, &cfg_cbc_listen_port_cmd);
+ install_element(CBC_NODE, &cfg_cbc_no_listen_port_cmd);
+ install_element(CBC_NODE, &cfg_cbc_listen_ip_cmd);
+}
+
+
+/*********************************************************************************
+ * smscb
+ *********************************************************************************/
+static void vty_dump_smscb_chan_state(struct vty *vty, const struct bts_smscb_chan_state *cs)
+{
+ const struct bts_smscb_message *sm;
+
+ vty_out(vty, "%s CBCH:%s", cs == &cs->bts->cbch_basic ? "BASIC" : "EXTENDED", VTY_NEWLINE);
+
+ vty_out(vty, " MsgId | SerNo | Pg | Category | Perd | #Tx | #Req | DCS%s", VTY_NEWLINE);
+ vty_out(vty, "-------|-------|----|---------------|------|------|------|----%s", VTY_NEWLINE);
+ llist_for_each_entry(sm, &cs->messages, list) {
+ vty_out(vty, " %04x | %04x | %2u | %13s | %4u | %4u | %4u | %02x%s",
+ sm->input.msg_id, sm->input.serial_nr, sm->num_pages,
+ get_value_string(cbsp_category_names, sm->input.category),
+ sm->input.rep_period, sm->bcast_count, sm->input.num_bcast_req,
+ sm->input.dcs, VTY_NEWLINE);
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+DEFUN(bts_show_cbs, bts_show_cbs_cmd,
+ "show bts <0-255> smscb [(basic|extended)]",
+ SHOW_STR "Display information about a BTS\n" "BTS number\n"
+ "SMS Cell Broadcast State\n"
+ "Show only information related to CBCH BASIC\n"
+ "Show only information related to CBCH EXTENDED\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int bts_nr = atoi(argv[0]);
+ struct gsm_bts *bts;
+
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+
+ if (argc < 2 || !strcmp(argv[1], "basic"))
+ vty_dump_smscb_chan_state(vty, &bts->cbch_basic);
+ if (argc < 2 || !strcmp(argv[1], "extended"))
+ vty_dump_smscb_chan_state(vty, &bts->cbch_extended);
+
+ return CMD_SUCCESS;
+}
+
+void smscb_vty_init(void)
+{
+ install_element_ve(&bts_show_cbs_cmd);
+}
diff --git a/src/osmo-bsc/system_information.c b/src/osmo-bsc/system_information.c
index 4709f7fc0..cc2e788d7 100644
--- a/src/osmo-bsc/system_information.c
+++ b/src/osmo-bsc/system_information.c
@@ -31,16 +31,17 @@
#include <osmocom/core/utils.h>
#include <osmocom/gsm/sysinfo.h>
#include <osmocom/gsm/gsm48_ie.h>
+#include <osmocom/gsm/gsm48_rest_octets.h>
#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm48_arfcn_range_encode.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/abis_rsl.h>
-#include <osmocom/bsc/rest_octets.h>
-#include <osmocom/bsc/arfcn_range_encode.h>
#include <osmocom/bsc/gsm_04_08_rr.h>
-#include <osmocom/bsc/acc_ramp.h>
+#include <osmocom/bsc/acc.h>
#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/bts.h>
struct gsm0808_cell_id_list2;
@@ -51,7 +52,7 @@ struct gsm0808_cell_id_list2;
* array. DCS1800 and PCS1900 can not be used at the same time so conserve
* memory and do the below.
*/
-static int band_compatible(const struct gsm_bts *bts, int arfcn)
+int band_compatible(const struct gsm_bts *bts, int arfcn)
{
enum gsm_band band;
@@ -168,7 +169,12 @@ static int make_si2quaters(struct gsm_bts *bts, bool counting)
si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater;
}
- rc = rest_octets_si2quater(si2q->rest_octets, bts);
+ rc = osmo_gsm48_rest_octets_si2quater_encode(si2q->rest_octets, bts->si2q_index,
+ bts->si2q_count, bts->si_common.data.uarfcn_list,
+ &bts->u_offset, bts->si_common.uarfcn_length,
+ bts->si_common.data.scramble_list,
+ &bts->si_common.si2quater_neigh_list,
+ &bts->e_offset);
if (rc < 0)
return rc;
@@ -210,11 +216,40 @@ static inline uint16_t encode_fdd(uint16_t scramble, bool diversity)
return scramble;
}
+int bts_earfcn_del(struct gsm_bts *bts, uint16_t earfcn)
+{
+ struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+ int r;
+
+ r = osmo_earfcn_del(e, earfcn);
+
+ if (r < 0)
+ return r;
+
+ /* If the last earfcn was removed, invlidate common neighbours limitations */
+ if (si2q_earfcn_count(e) == 0) {
+ e->thresh_lo_valid = false;
+ e->qrxlm_valid = false;
+ e->prio_valid = false;
+ }
+
+ return r;
+}
+
int bts_earfcn_add(struct gsm_bts *bts, uint16_t earfcn, uint8_t thresh_hi, uint8_t thresh_lo, uint8_t prio,
uint8_t qrx, uint8_t meas_bw)
{
struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
- int r = osmo_earfcn_add(e, earfcn, (meas_bw < EARFCN_MEAS_BW_INVALID) ? meas_bw : OSMO_EARFCN_MEAS_INVALID);
+ int r;
+
+ /* EARFCN may already exist, so we delete it to avoid duplicates */
+ if (bts_earfcn_del(bts, earfcn) == 0)
+ LOGP(DRR, LOGL_NOTICE, "EARFCN %u is already in the list, modifying\n", earfcn);
+
+ if (meas_bw < EARFCN_MEAS_BW_INVALID)
+ r = osmo_earfcn_add(e, earfcn, meas_bw);
+ else
+ r = osmo_earfcn_add(e, earfcn, OSMO_EARFCN_MEAS_INVALID);
if (r < 0)
return r;
@@ -248,7 +283,7 @@ int bts_earfcn_add(struct gsm_bts *bts, uint16_t earfcn, uint8_t thresh_hi, uint
return r;
}
-/* Scrambling Code as defined in 3GPP TS 25.213 is 9 bit long so number below is unreacheable upper bound */
+/* Scrambling Code as defined in 3GPP TS 25.213 is 9 bit long so number below is unreachable upper bound */
#define SC_BOUND 600
/* Find position for a given UARFCN (take SC into consideration if it's available) in a sorted list
@@ -292,16 +327,21 @@ int bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble, bool
size_t len = bts->si_common.uarfcn_length, i;
uint8_t si2q;
int pos = uarfcn_sc_pos(bts, arfcn, scramble);
- uint16_t scr = diversity ? encode_fdd(scramble, true) : encode_fdd(scramble, false),
+ uint16_t scr = encode_fdd(scramble, diversity),
*ual = bts->si_common.data.uarfcn_list,
*scl = bts->si_common.data.scramble_list;
+ if (pos >= 0) {
+ LOGP(DRR, LOGL_NOTICE,
+ "EARFCN (%u, %u) is already in the list, modifying\n",
+ arfcn, scramble);
+ scl[pos] = scr;
+ return 0;
+ }
+
if (len == MAX_EARFCN_LIST)
return -ENOMEM;
- if (pos >= 0)
- return -EADDRINUSE;
-
/* find the suitable position for arfcn if any */
pos = uarfcn_sc_pos(bts, arfcn, SC_BOUND);
i = (pos < 0) ? len : pos;
@@ -398,7 +438,7 @@ static int freq_list_bmrel_set_arfcn(uint8_t *chan_list, unsigned int arfcn)
/* generate a variable bitmap */
static inline int enc_freq_lst_var_bitmap(uint8_t *chan_list,
- struct bitvec *bv, const struct gsm_bts *bts,
+ const struct bitvec *bv, const struct gsm_bts *bts,
bool bis, bool ter, int min, bool pgsm)
{
int i;
@@ -425,7 +465,7 @@ static inline int enc_freq_lst_var_bitmap(uint8_t *chan_list,
return 0;
}
-int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w,
+int range_encode(enum osmo_gsm48_range r, int *arfcns, int arfcns_used, int *w,
int f0, uint8_t *chan_list)
{
/*
@@ -434,22 +474,22 @@ int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w,
*/
int rc, f0_included;
- range_enc_filter_arfcns(arfcns, arfcns_used, f0, &f0_included);
+ osmo_gsm48_range_enc_filter_arfcns(arfcns, arfcns_used, f0, &f0_included);
- rc = range_enc_arfcns(r, arfcns, arfcns_used, w, 0);
+ rc = osmo_gsm48_range_enc_arfcns(r, arfcns, arfcns_used, w, 0);
if (rc < 0)
return rc;
/* Select the range and the amount of bits needed */
switch (r) {
- case ARFCN_RANGE_128:
- return range_enc_range128(chan_list, f0, w);
- case ARFCN_RANGE_256:
- return range_enc_range256(chan_list, f0, w);
- case ARFCN_RANGE_512:
- return range_enc_range512(chan_list, f0, w);
- case ARFCN_RANGE_1024:
- return range_enc_range1024(chan_list, f0, f0_included, w);
+ case OSMO_GSM48_ARFCN_RANGE_128:
+ return osmo_gsm48_range_enc_128(chan_list, f0, w);
+ case OSMO_GSM48_ARFCN_RANGE_256:
+ return osmo_gsm48_range_enc_256(chan_list, f0, w);
+ case OSMO_GSM48_ARFCN_RANGE_512:
+ return osmo_gsm48_range_enc_512(chan_list, f0, w);
+ case OSMO_GSM48_ARFCN_RANGE_1024:
+ return osmo_gsm48_range_enc_1024(chan_list, f0, f0_included, w);
default:
return -ERANGE;
};
@@ -459,11 +499,11 @@ int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w,
/* generate a frequency list with the range 512 format */
static inline int enc_freq_lst_range(uint8_t *chan_list,
- struct bitvec *bv, const struct gsm_bts *bts,
+ const struct bitvec *bv, const struct gsm_bts *bts,
bool bis, bool ter, bool pgsm)
{
- int arfcns[RANGE_ENC_MAX_ARFCNS];
- int w[RANGE_ENC_MAX_ARFCNS];
+ int arfcns[OSMO_GSM48_RANGE_ENC_MAX_ARFCNS];
+ int w[OSMO_GSM48_RANGE_ENC_MAX_ARFCNS];
int arfcns_used = 0;
int i, range, f0;
@@ -482,8 +522,8 @@ static inline int enc_freq_lst_range(uint8_t *chan_list,
/*
* Check if the given list of ARFCNs can be encoded.
*/
- range = range_enc_determine_range(arfcns, arfcns_used, &f0);
- if (range == ARFCN_RANGE_INVALID)
+ range = osmo_gsm48_range_enc_determine_range(arfcns, arfcns_used, &f0);
+ if (range == OSMO_GSM48_ARFCN_RANGE_INVALID)
return -2;
memset(w, 0, sizeof(w));
@@ -491,34 +531,43 @@ static inline int enc_freq_lst_range(uint8_t *chan_list,
}
/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
-static int bitvec2freq_list(uint8_t *chan_list, struct bitvec *bv,
+static int bitvec2freq_list(uint8_t *chan_list, const struct bitvec *bv,
const struct gsm_bts *bts, bool bis, bool ter)
{
int i, rc, min = -1, max = -1, arfcns = 0;
bool pgsm = false;
memset(chan_list, 0, 16);
- if (bts->band == GSM_BAND_900
- && bts->c0->arfcn >= 1 && bts->c0->arfcn <= 124)
+ /* According to 3GPP TS 44.018, section 10.5.2.1b.2, only ARFCN values
+ * in range 1..124 can be encoded using the 'bit map 0' format. */
+ if (bts->band == GSM_BAND_900)
pgsm = true;
+ /* Check presence of E-GSM ARFCN 0 */
+ if (pgsm && bitvec_get_bit_pos(bv, 0) == ONE)
+ pgsm = false;
+ /* Check presence of R-GSM / E-GSM ARFCNs 955..1023 */
+ for (i = 955; pgsm && i <= 1023; i++) {
+ if (bitvec_get_bit_pos(bv, i) == ONE)
+ pgsm = false;
+ }
+
/* P-GSM-only handsets only support 'bit map 0 format' */
if (!bis && !ter && pgsm) {
chan_list[0] = 0;
- for (i = 0; i < bv->data_len*8; i++) {
- if (i >= 1 && i <= 124
- && bitvec_get_bit_pos(bv, i)) {
- rc = freq_list_bm0_set_arfcn(chan_list, i);
- if (rc < 0)
- return rc;
- }
+ for (i = 1; i <= 124; i++) {
+ if (!bitvec_get_bit_pos(bv, i))
+ continue;
+ rc = freq_list_bm0_set_arfcn(chan_list, i);
+ if (rc < 0)
+ return rc;
}
return 0;
}
for (i = 0; i < bv->data_len*8; i++) {
/* in case of SI2 or SI5 allow all neighbours in same band
- * in case of SI*bis, allow neighbours in same band ouside pgsm
+ * in case of SI*bis, allow neighbours in same band outside pgsm
* in case of SI*ter, allow neighbours in different bands
*/
if (!bitvec_get_bit_pos(bv, i))
@@ -547,7 +596,7 @@ static int bitvec2freq_list(uint8_t *chan_list, struct bitvec *bv,
max = i;
}
- if (max == -1) {
+ if (arfcns == 0) {
/* Empty set, use 'bit map 0 format' */
chan_list[0] = 0;
return 0;
@@ -568,51 +617,63 @@ static int bitvec2freq_list(uint8_t *chan_list, struct bitvec *bv,
return -EINVAL;
}
-/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
-/* static*/ int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts)
+/* (Re)generate Cell Allocation (basically a bit-vector of all cell channels) */
+int generate_cell_chan_alloc(struct gsm_bts *bts)
{
- struct gsm_bts_trx *trx;
- struct bitvec *bv = &bts->si_common.cell_alloc;
-
- /* Zero-initialize the bit-vector */
- memset(bv->data, 0, bv->data_len);
+ const struct gsm_bts_trx *trx;
+ unsigned int chan, chan_num;
+ unsigned int tn;
+
+ /* Temporary Cell Allocation bit-vector */
+ uint8_t ca[1024 / 8] = { 0 };
+ struct bitvec bv = {
+ .data_len = sizeof(ca),
+ .data = &ca[0],
+ };
- /* first we generate a bitvec of all TRX ARFCN's in our BTS */
+ /* Calculate a bit-mask of all assigned ARFCNs */
llist_for_each_entry(trx, &bts->trx_list, list) {
- unsigned int i, j;
/* Always add the TRX's ARFCN */
- bitvec_set_bit_pos(bv, trx->arfcn, 1);
- for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
- struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ bitvec_set_bit_pos(&bv, trx->arfcn, 1);
+ for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
+ const struct gsm_bts_trx_ts *ts = &trx->ts[tn];
/* Add any ARFCNs present in hopping channels */
- for (j = 0; j < 1024; j++) {
- if (bitvec_get_bit_pos(&ts->hopping.arfcns, j))
- bitvec_set_bit_pos(bv, j, 1);
+ for (chan = 0; chan < sizeof(ca) * 8; chan++) {
+ if (bitvec_get_bit_pos(&ts->hopping.arfcns, chan) == ONE)
+ bitvec_set_bit_pos(&bv, chan, ONE);
}
}
}
- /* then we generate a GSM 04.08 frequency list from the bitvec */
- return bitvec2freq_list(chan_list, bv, bts, false, false);
-}
+ /* Calculate the overall number of assigned ARFCNs */
+ for (chan_num = 0, chan = 0; chan < sizeof(ca) * 8; chan++) {
+ if (bitvec_get_bit_pos(&bv, chan) == ONE)
+ chan_num++;
+ }
-struct generate_bcch_chan_list__ni_iter_data {
- struct gsm_bts *bts;
- struct bitvec *bv;
-};
+ /* The Mobile Allocation IE may contain up to 64 bits, so here we
+ * cannot allow more than 64 channels in Cell Allocation. */
+ if (chan_num > 64) {
+ LOGP(DRR, LOGL_ERROR, "Failed to (re)generate Cell Allocation: "
+ "number of channels (%u) exceeds the maximum number of "
+ "ARFCNs in Mobile Allocation (64)\n", chan_num);
+ return -E2BIG;
+ }
-static bool generate_bcch_chan_list__ni_iter_cb(const struct neighbor_ident_key *key,
- const struct gsm0808_cell_id_list2 *val,
- void *cb_data)
-{
- struct generate_bcch_chan_list__ni_iter_data *data = cb_data;
+ /* Commit the new Channel Allocation */
+ memcpy(&bts->si_common.data.cell_alloc[0], ca, sizeof(ca));
+ bts->si_common.cell_chan_num = chan_num;
- if (key->from_bts != NEIGHBOR_IDENT_KEY_ANY_BTS
- && key->from_bts != data->bts->nr)
- return true;
+ return 0;
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts)
+{
+ const struct bitvec *bv = &bts->si_common.cell_alloc;
- bitvec_set_bit_pos(data->bv, key->arfcn, 1);
- return true;
+ /* generate a Frequency List from the Cell Allocation */
+ return bitvec2freq_list(chan_list, bv, bts, false, false);
}
/*! generate a cell channel list as per Section 10.5.2.22 of 04.08
@@ -640,7 +701,7 @@ static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts,
/* Zero-initialize the bit-vector */
memset(bv->data, 0, bv->data_len);
- if (llist_empty(&bts->local_neighbors)) {
+ if (llist_empty(&bts->neighbors)) {
/* There are no explicit neighbors, assume all BTS are. */
llist_for_each_entry(cur_bts, &bts->network->bts_list, list) {
if (cur_bts == bts)
@@ -649,21 +710,23 @@ static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts,
}
} else {
/* Only add explicit neighbor cells */
- struct gsm_bts_ref *neigh;
- llist_for_each_entry(neigh, &bts->local_neighbors, entry) {
- bitvec_set_bit_pos(bv, neigh->bts->c0->arfcn, 1);
+ struct neighbor *n;
+ llist_for_each_entry(n, &bts->neighbors, entry) {
+ if (n->type == NEIGHBOR_TYPE_CELL_ID && n->cell_id.ab_present) {
+ bitvec_set_bit_pos(bv, n->cell_id.ab.arfcn, 1);
+ } else {
+ struct gsm_bts *neigh_bts;
+ if (resolve_local_neighbor(&neigh_bts, bts, n)) {
+ LOGP(DHO, LOGL_ERROR,
+ "Neither local nor remote neighbor: BTS %u -> %s\n",
+ bts->nr, neighbor_to_str_c(OTC_SELECT, n));
+ continue;
+ }
+ if (neigh_bts->c0)
+ bitvec_set_bit_pos(bv, neigh_bts->c0->arfcn, 1);
+ }
}
}
-
- /* Also add neighboring BSS cells' ARFCNs */
- {
- struct generate_bcch_chan_list__ni_iter_data data = {
- .bv = bv,
- .bts = bts,
- };
- neighbor_ident_iter(bts->network->neighbor_bss_cells,
- generate_bcch_chan_list__ni_iter_cb, &data);
- }
}
/* then we generate a GSM 04.08 frequency list from the bitvec */
@@ -689,7 +752,7 @@ static int list_arfcn(uint8_t *chan_list, uint8_t mask, char *text)
struct gsm_sysinfo_freq freq[1024];
memset(freq, 0, sizeof(freq));
- gsm48_decode_freq_list(freq, chan_list, 16, 0xce, 1);
+ gsm48_decode_freq_list(freq, chan_list, 16, mask, 1);
for (i = 0; i < 1024; i++) {
if (freq[i].mask) {
if (!n)
@@ -722,14 +785,25 @@ static int generate_si1(enum osmo_sysinfo_type t, struct gsm_bts *bts)
list_arfcn(si1->cell_channel_description, 0xce, "Serving cell:");
si1->rach_control = bts->si_common.rach_control;
- if (acc_ramp_is_enabled(&bts->acc_ramp))
- acc_ramp_apply(&si1->rach_control, &bts->acc_ramp);
+ acc_mgr_apply_acc(&bts->acc_mgr, &si1->rach_control);
/*
* SI1 Rest Octets (10.5.2.32), contains NCH position and band
* indicator but that is not in the 04.08.
*/
- rc = rest_octets_si1(si1->rest_octets, NULL, is_dcs_net(bts));
+ if (bts->nch.num_blocks) {
+ rc = osmo_gsm48_si1ro_nch_pos_encode(bts->nch.num_blocks, bts->nch.first_block);
+ if (rc < 0) {
+ LOGP(DRR, LOGL_ERROR, "Unable to encode NCH position (num_blocks=%u, first_block=%u)\n",
+ bts->nch.num_blocks, bts->nch.first_block);
+ rc = osmo_gsm48_rest_octets_si1_encode(si1->rest_octets, NULL, is_dcs_net(bts));
+ } else {
+ uint8_t nch_pos = rc;
+ rc = osmo_gsm48_rest_octets_si1_encode(si1->rest_octets, &nch_pos, is_dcs_net(bts));
+ }
+ } else {
+ rc = osmo_gsm48_rest_octets_si1_encode(si1->rest_octets, NULL, is_dcs_net(bts));
+ }
return sizeof(*si1) + rc;
}
@@ -754,12 +828,25 @@ static int generate_si2(enum osmo_sysinfo_type t, struct gsm_bts *bts)
si2->ncc_permitted = bts->si_common.ncc_permitted;
si2->rach_control = bts->si_common.rach_control;
- if (acc_ramp_is_enabled(&bts->acc_ramp))
- acc_ramp_apply(&si2->rach_control, &bts->acc_ramp);
+ acc_mgr_apply_acc(&bts->acc_mgr, &si2->rach_control);
return sizeof(*si2);
}
+/* Generate SI2bis Rest Octests 3GPP TS 44.018 Table 10.5.2.33.1 */
+static int rest_octets_si2bis(uint8_t *data)
+{
+ struct bitvec bv;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = data;
+ bv.data_len = 1;
+
+ bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
+
+ return bv.data_len;
+}
+
static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts)
{
int rc;
@@ -789,8 +876,7 @@ static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts)
bts->si_valid &= ~(1 << SYSINFO_TYPE_2bis);
si2b->rach_control = bts->si_common.rach_control;
- if (acc_ramp_is_enabled(&bts->acc_ramp))
- acc_ramp_apply(&si2b->rach_control, &bts->acc_ramp);
+ acc_mgr_apply_acc(&bts->acc_mgr, &si2b->rach_control);
/* SI2bis Rest Octets as per 3GPP TS 44.018 ยง10.5.2.33 */
rc = rest_octets_si2bis(si2b->rest_octets);
@@ -798,6 +884,24 @@ static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts)
return sizeof(*si2b) + rc;
}
+
+/* Generate SI2ter Rest Octests 3GPP TS 44.018 Table 10.5.2.33a.1 */
+static int rest_octets_si2ter(uint8_t *data)
+{
+ struct bitvec bv;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = data;
+ bv.data_len = 4;
+
+ /* No SI2ter_MP_CHANGE_MARK */
+ bitvec_set_bit(&bv, L);
+
+ bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
+
+ return bv.data_len;
+}
+
static int generate_si2ter(enum osmo_sysinfo_type t, struct gsm_bts *bts)
{
int rc;
@@ -867,7 +971,7 @@ static int generate_si2quater(enum osmo_sysinfo_type t, struct gsm_bts *bts)
return sizeof(*si2q) + rc;
}
-static struct gsm48_si_ro_info si_info = {
+static struct osmo_gsm48_si_ro_info si_info = {
.selection_params = {
.present = 0,
},
@@ -905,14 +1009,17 @@ static int generate_si3(enum osmo_sysinfo_type t, struct gsm_bts *bts)
si3->header.skip_indicator = 0;
si3->header.system_information = GSM48_MT_RR_SYSINFO_3;
+ /* The value in bts->si_common.chan_desc may get out of sync with the actual value
+ * in net->T_defs (e.g. after changing it via the VTY), so we need to sync it here. */
+ bts->si_common.chan_desc.t3212 = osmo_tdef_get(bts->network->T_defs, 3212, OSMO_TDEF_CUSTOM, 0);
+
si3->cell_identity = htons(bts->cell_identity);
gsm48_generate_lai2(&si3->lai, bts_lai(bts));
si3->control_channel_desc = bts->si_common.chan_desc;
si3->cell_options = bts->si_common.cell_options;
si3->cell_sel_par = bts->si_common.cell_sel_par;
si3->rach_control = bts->si_common.rach_control;
- if (acc_ramp_is_enabled(&bts->acc_ramp))
- acc_ramp_apply(&si3->rach_control, &bts->acc_ramp);
+ acc_mgr_apply_acc(&bts->acc_mgr, &si3->rach_control);
/* allow/disallow DTXu */
gsm48_set_dtx(&si3->cell_options, bts->dtxu, bts->dtxu, true);
@@ -937,7 +1044,7 @@ static int generate_si3(enum osmo_sysinfo_type t, struct gsm_bts *bts)
CBQ, CELL_RESELECT_OFFSET, TEMPORARY_OFFSET, PENALTY_TIME
Power Offset, 2ter Indicator, Early Classmark Sending,
Scheduling if and WHERE, GPRS Indicator, SI13 position */
- rc = rest_octets_si3(si3->rest_octets, &si_info);
+ rc = osmo_gsm48_rest_octets_si3_encode(si3->rest_octets, &si_info);
return sizeof(*si3) + rc;
}
@@ -947,7 +1054,7 @@ static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts)
int rc;
struct gsm48_system_information_type_4 *si4 = (struct gsm48_system_information_type_4 *) GSM_BTS_SI(bts, t);
struct gsm_lchan *cbch_lchan;
- uint8_t *restoct = si4->data;
+ uint8_t *tail = si4->data;
/* length of all IEs present except SI4 rest octets and l2_plen */
int l2_plen = sizeof(*si4) - 1;
@@ -961,19 +1068,33 @@ static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts)
gsm48_generate_lai2(&si4->lai, bts_lai(bts));
si4->cell_sel_par = bts->si_common.cell_sel_par;
si4->rach_control = bts->si_common.rach_control;
- if (acc_ramp_is_enabled(&bts->acc_ramp))
- acc_ramp_apply(&si4->rach_control, &bts->acc_ramp);
+ acc_mgr_apply_acc(&bts->acc_mgr, &si4->rach_control);
/* Optional: CBCH Channel Description + CBCH Mobile Allocation */
cbch_lchan = gsm_bts_get_cbch(bts);
if (cbch_lchan) {
+ const struct gsm_bts_trx_ts *ts = cbch_lchan->ts;
struct gsm48_chan_desc cd;
- gsm48_lchan2chan_desc_as_configured(&cd, cbch_lchan);
- tv_fixed_put(si4->data, GSM48_IE_CBCH_CHAN_DESC, 3,
- (uint8_t *) &cd);
- l2_plen += 3 + 1;
- restoct += 3 + 1;
- /* we don't use hopping and thus don't need a CBCH MA */
+
+ /* 10.5.2.5 (TV) CBCH Channel Description IE.
+ * CBCH is never in VAMOS mode, so just pass allow_osmo_cbits == false. */
+ if (gsm48_lchan_and_pchan2chan_desc(&cd, cbch_lchan, cbch_lchan->ts->pchan_from_config,
+ gsm_ts_tsc(cbch_lchan->ts), false))
+ return -EINVAL;
+ tail = tv_fixed_put(tail, GSM48_IE_CBCH_CHAN_DESC,
+ sizeof(cd), (uint8_t *) &cd);
+ l2_plen += 1 + sizeof(cd);
+
+ /* 10.5.2.21 (TLV) CBCH Mobile Allocation IE */
+ if (ts->hopping.enabled) {
+ /* Prevent potential buffer overflow */
+ if (ts->hopping.ma_len > 2)
+ return -ENOMEM;
+ tail = tlv_put(tail, GSM48_IE_CBCH_MOB_AL,
+ ts->hopping.ma_len,
+ ts->hopping.ma_data);
+ l2_plen += 2 + ts->hopping.ma_len;
+ }
}
si4->header.l2_plen = GSM48_LEN2PLEN(l2_plen);
@@ -981,7 +1102,7 @@ static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts)
/* SI4 Rest Octets (10.5.2.35), containing
Optional Power offset, GPRS Indicator,
Cell Identity, LSA ID, Selection Parameter */
- rc = rest_octets_si4(restoct, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - restoct);
+ rc = osmo_gsm48_rest_octets_si4_encode(tail, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - tail);
return l2_plen + 1 + rc;
}
@@ -994,15 +1115,10 @@ static int generate_si5(enum osmo_sysinfo_type t, struct gsm_bts *bts)
memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
- /* ip.access nanoBTS needs l2_plen!! */
- switch (bts->type) {
- case GSM_BTS_TYPE_NANOBTS:
- case GSM_BTS_TYPE_OSMOBTS:
+ /* Abis/IP needs l2_plen!! */
+ if (is_ipa_abisip_bts(bts)) {
*output++ = GSM48_LEN2PLEN(l2_plen);
l2_plen++;
- break;
- default:
- break;
}
si5 = (struct gsm48_system_information_type_5 *) output;
@@ -1030,15 +1146,10 @@ static int generate_si5bis(enum osmo_sysinfo_type t, struct gsm_bts *bts)
memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
- /* ip.access nanoBTS needs l2_plen!! */
- switch (bts->type) {
- case GSM_BTS_TYPE_NANOBTS:
- case GSM_BTS_TYPE_OSMOBTS:
+ /* Abis/IP needs l2_plen!! */
+ if (is_ipa_abisip_bts(bts)) {
*output++ = GSM48_LEN2PLEN(l2_plen);
l2_plen++;
- break;
- default:
- break;
}
si5b = (struct gsm48_system_information_type_5bis *) output;
@@ -1074,15 +1185,10 @@ static int generate_si5ter(enum osmo_sysinfo_type t, struct gsm_bts *bts)
memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
- /* ip.access nanoBTS needs l2_plen!! */
- switch (bts->type) {
- case GSM_BTS_TYPE_NANOBTS:
- case GSM_BTS_TYPE_OSMOBTS:
+ /* Abis/IP needs l2_plen!! */
+ if (is_ipa_abisip_bts(bts)) {
*output++ = GSM48_LEN2PLEN(l2_plen);
l2_plen++;
- break;
- default:
- break;
}
si5t = (struct gsm48_system_information_type_5ter *) output;
@@ -1106,21 +1212,18 @@ static int generate_si5ter(enum osmo_sysinfo_type t, struct gsm_bts *bts)
static int generate_si6(enum osmo_sysinfo_type t, struct gsm_bts *bts)
{
struct gsm48_system_information_type_6 *si6;
+ struct osmo_gsm48_si6_ro_info si6_ro_info;
uint8_t *output = GSM_BTS_SI(bts, t);
int l2_plen = 11;
int rc;
memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+ memset(&si6_ro_info, 0, sizeof(si6_ro_info));
- /* ip.access nanoBTS needs l2_plen!! */
- switch (bts->type) {
- case GSM_BTS_TYPE_NANOBTS:
- case GSM_BTS_TYPE_OSMOBTS:
+ /* Abis/IP needs l2_plen!! */
+ if (is_ipa_abisip_bts(bts)) {
*output++ = GSM48_LEN2PLEN(l2_plen);
l2_plen++;
- break;
- default:
- break;
}
si6 = (struct gsm48_system_information_type_6 *) output;
@@ -1137,50 +1240,280 @@ static int generate_si6(enum osmo_sysinfo_type t, struct gsm_bts *bts)
gsm48_set_dtx(&si6->cell_options, bts->dtxu, bts->dtxu, false);
/* SI6 Rest Octets: 10.5.2.35a: PCH / NCH info, VBS/VGCS options */
- rc = rest_octets_si6(si6->rest_octets, is_dcs_net(bts));
+ si6_ro_info.band_indicator_1900 = !is_dcs_net(bts);
+ rc = osmo_gsm48_rest_octets_si6_encode(si6->rest_octets, &si6_ro_info);
return l2_plen + rc;
}
-static struct gsm48_si13_info si13_default = {
- .cell_opts = {
- .nmo = GPRS_NMO_II,
- .t3168 = 2000,
- .t3192 = 1500,
- .drx_timer_max = 3,
- .bs_cv_max = 15,
- .ctrl_ack_type_use_block = true,
- .ext_info_present = 0,
- .supports_egprs_11bit_rach = 0,
- .ext_info = {
- /* The values below are just guesses ! */
- .egprs_supported = 0,
- .use_egprs_p_ch_req = 1,
- .bep_period = 5,
- .pfc_supported = 0,
- .dtm_supported = 0,
- .bss_paging_coordination = 0,
- },
- },
- .pwr_ctrl_pars = {
- .alpha = 0, /* a = 0.0 */
- .t_avg_w = 16,
- .t_avg_t = 16,
- .pc_meas_chan = 0, /* downling measured on CCCH */
- .n_avg_i = 8,
- },
- .bcch_change_mark = 1,
- .si_change_field = 0,
- .rac = 0, /* needs to be patched */
- .spgc_ccch_sup = 0,
- .net_ctrl_ord = 0,
- .prio_acc_thr = 6,
-};
+static int si10_rest_octets_encode(struct gsm_bts *s_bts, struct bitvec *bv, struct gsm_bts *n_bts, uint8_t index)
+{
+ /* The BA-IND must be equal to the BA-IND in SI5*. */
+ /* <BA ind : bit(1)> */
+ bitvec_set_bit(bv, 1);
+
+ /* Do we have neighbor cells ? */
+ /* { L <spare padding> | H <neighbour information> } */
+ if (!n_bts)
+ return 0;
+ bitvec_set_bit(bv, H);
+
+ /* <first frequency: bit(5)> */
+ bitvec_set_uint(bv, index, 5);
+
+ /* <bsic : bit(6)> */
+ bitvec_set_uint(bv, n_bts->bsic, 6);
+
+ /* We do not provide empty cell information. */
+ /* { H <cell parameters> | L } */
+ bitvec_set_bit(bv, H);
+
+ /* If cell is barred, we do not need further cell info. */
+ /* <cell barred> | L <further cell info> */
+ if (n_bts->si_common.rach_control.cell_bar) {
+ /* H */
+ bitvec_set_bit(bv, H);
+ /* We are done with the first cell info. */
+ return 0;
+ }
+ bitvec_set_bit(bv, L);
+
+ /* If LA is different for serving cell, we need to add CRH. */
+ /* { H <cell reselect hysteresis : bit(3)> | L } */
+ if (s_bts->location_area_code != n_bts->location_area_code) {
+ bitvec_set_bit(bv, H);
+ bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.cell_resel_hyst, 3);
+ } else
+ bitvec_set_bit(bv, L);
+
+ /* <ms txpwr max cch : bit(5)> */
+ bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.ms_txpwr_max_ccch, 5);
+
+ /* <rxlev access min : bit(6)> */
+ bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.rxlev_acc_min, 6);
+
+ /* <cell reselect offset : bit(6)> */
+ if (n_bts->si_common.cell_ro_sel_par.present)
+ bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.cell_resel_off, 6);
+ else
+ bitvec_set_uint(bv, 0, 6);
+
+ /* <temporary offset : bit(3)> */
+ if (n_bts->si_common.cell_ro_sel_par.present)
+ bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.temp_offs, 3);
+ else
+ bitvec_set_uint(bv, 0, 3);
+
+ /* <penalty time : bit(5)> */
+ if (n_bts->si_common.cell_ro_sel_par.present)
+ bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.penalty_time, 5);
+ else
+ bitvec_set_uint(bv, 0, 5);
+
+ /* We are done with the first cell info. */
+ return 0;
+}
+
+static int si10_rest_octets_encode_other(struct gsm_bts *s_bts, struct bitvec *bv, struct gsm_bts *l_bts,
+ struct gsm_bts *n_bts, uint8_t last_index, uint8_t index)
+{
+ int rc;
+
+ /* If the bv buffer would overflow, then the bits are not written. Each bitvec_set_* call will return
+ * an error code. This error is returned with this function. */
+
+ /* { H <info field> } */
+ bitvec_set_bit(bv, H);
+
+ /* How many frequency indices do we skip? */
+ /* <next frequency>** L <differential cell info> */
+ while ((last_index = (last_index + 1) & 0x1f) != index) {
+ /* H */
+ bitvec_set_bit(bv, H);
+ }
+ bitvec_set_bit(bv, L);
+
+ /* If NCC is equal, just send BCC, otherwise send complete BSIC. */
+ /* { H <BCC : bit(3)> | L <bsic : bit(6)> } */
+ if ((l_bts->bsic & 0x38) == (n_bts->bsic & 0x38))
+ bitvec_set_uint(bv, n_bts->bsic & 0x07, 3);
+ else
+ bitvec_set_uint(bv, n_bts->bsic, 6);
+
+ /* We do not provide empty cell information. */
+ /* { H <cell parameters> | L } */
+ bitvec_set_bit(bv, H);
+
+ /* If cell is barred, we do not need further cell info. */
+ /* <cell barred> | L <further cell info> */
+ if (n_bts->si_common.rach_control.cell_bar) {
+ /* H */
+ rc = bitvec_set_bit(bv, H);
+ /* We are done with the other cell info. */
+ return rc;
+ }
+ bitvec_set_bit(bv, L);
+
+ /* If LA is different for serving cell, we need to add CRH. */
+ /* { H <cell reselect hysteresis : bit(3)> | L } */
+ if (s_bts->location_area_code != n_bts->location_area_code) {
+ bitvec_set_bit(bv, H);
+ bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.cell_resel_hyst, 3);
+ } else
+ bitvec_set_bit(bv, L);
+
+ /* { H <ms txpwr max cch : bit(5)> | L } */
+ if (l_bts->si_common.cell_sel_par.ms_txpwr_max_ccch != n_bts->si_common.cell_sel_par.ms_txpwr_max_ccch) {
+ bitvec_set_bit(bv, H);
+ bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.ms_txpwr_max_ccch, 5);
+ } else
+ bitvec_set_bit(bv, L);
+
+ /* { H <rxlev access min : bit(6)> | L } */
+ if (l_bts->si_common.cell_sel_par.rxlev_acc_min != n_bts->si_common.cell_sel_par.rxlev_acc_min) {
+ bitvec_set_bit(bv, H);
+ bitvec_set_uint(bv, n_bts->si_common.cell_sel_par.rxlev_acc_min, 6);
+ } else
+ bitvec_set_bit(bv, L);
+
+ /* { H <cell reselect offset : bit(6)> | L } */
+ if (l_bts->si_common.cell_ro_sel_par.present != n_bts->si_common.cell_ro_sel_par.present ||
+ (n_bts->si_common.cell_ro_sel_par.present &&
+ l_bts->si_common.cell_ro_sel_par.cell_resel_off != n_bts->si_common.cell_ro_sel_par.cell_resel_off)) {
+ bitvec_set_bit(bv, H);
+ if (n_bts->si_common.cell_ro_sel_par.present)
+ bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.cell_resel_off, 6);
+ else
+ bitvec_set_uint(bv, 0, 6);
+ } else
+ bitvec_set_bit(bv, L);
+
+ /* { H <temporary offset : bit(3)> | L } */
+ if (l_bts->si_common.cell_ro_sel_par.present != n_bts->si_common.cell_ro_sel_par.present ||
+ (n_bts->si_common.cell_ro_sel_par.present &&
+ l_bts->si_common.cell_ro_sel_par.temp_offs != n_bts->si_common.cell_ro_sel_par.temp_offs)) {
+ bitvec_set_bit(bv, H);
+ if (n_bts->si_common.cell_ro_sel_par.present)
+ bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.temp_offs, 3);
+ else
+ bitvec_set_uint(bv, 0, 3);
+ } else
+ bitvec_set_bit(bv, L);
+
+ /* { H <penalty time : bit(5)> | L } */
+ if (l_bts->si_common.cell_ro_sel_par.present != n_bts->si_common.cell_ro_sel_par.present ||
+ (n_bts->si_common.cell_ro_sel_par.present &&
+ l_bts->si_common.cell_ro_sel_par.penalty_time != n_bts->si_common.cell_ro_sel_par.penalty_time)) {
+ bitvec_set_bit(bv, H);
+ if (n_bts->si_common.cell_ro_sel_par.present)
+ rc = bitvec_set_uint(bv, n_bts->si_common.cell_ro_sel_par.penalty_time, 5);
+ else
+ rc = bitvec_set_uint(bv, 0, 5);
+ } else
+ rc = bitvec_set_bit(bv, L);
+
+ /* We are done with the other cell info. */
+ return rc;
+}
+
+/* Generate SI 10 and return the number of bits used in the rest octet. */
+int gsm_generate_si10(struct gsm48_system_information_type_10 *si10, size_t len,
+ const struct gsm_subscriber_connection *conn)
+{
+ struct bitvec *nbv;
+ struct gsm_bts *s_bts = conn->lchan->ts->trx->bts, *l_bts = NULL;
+ int i, last_i = -1;
+ bool any_neighbor = false;
+ int rc;
+
+ struct bitvec bv = {
+ .data_len = len - sizeof(*si10),
+ .data = si10->rest_octets,
+ };
+
+ si10->rr_short_pd = 0; /* 3GPP TS 24.007 ยง11.3.2.1 */
+ si10->msg_type = GSM48_MT_RR_SH_SI10;
+ si10->l2_header = 0; /* 3GPP TS 44.006 ยง6.4a */
+
+ /* If we have gernerated SI5 with separate SI5 list, the used frequency indexes refer to it. */
+ if (s_bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP)
+ nbv = &s_bts->si_common.si5_neigh_list;
+ else
+ nbv = &s_bts->si_common.neigh_list;
+
+ /* Get up to 32 possible neighbor frequencies that SI10 can refer to. */
+ for (i = 0; i < 32; i++) {
+ struct gsm_bts *c_bts, *n_bts;
+ struct gsm_subscriber_connection *c;
+ unsigned int save_cur_bit;
+ int16_t arfcn;
+ arfcn = neigh_list_get_arfcn(s_bts, nbv, i);
+ /* End of list */
+ if (arfcn < 0)
+ break;
+ /* Is this neighbour used for this group call? */
+ n_bts = NULL;
+ llist_for_each_entry(c, &conn->vgcs_chan.call->vgcs_call.chan_list, vgcs_chan.list) {
+ if (c == conn)
+ continue;
+ if (!c->lchan)
+ continue;
+ c_bts = c->lchan->ts->trx->bts;
+ if (c_bts->c0->arfcn != arfcn)
+ continue;
+ n_bts = c_bts;
+ break;
+ }
+ if (n_bts) {
+ if (!any_neighbor) {
+ /* First neighbor, so generate rest octets with first cell info. */
+ LOGP(DRR, LOGL_INFO, "SI 10 with cell ID %d.\n", n_bts->cell_identity);
+ rc = si10_rest_octets_encode(s_bts, &bv, n_bts, i);
+ if (rc < 0)
+ return rc;
+ any_neighbor = true;
+ } else {
+ /* Save current position, so we can restore to last position in case of failure. */
+ save_cur_bit = bv.cur_bit;
+ /* Nth neighbor, so add rest octets with differential cell info. */
+ LOGP(DRR, LOGL_INFO, "Append cell ID %d to SI 10.\n", n_bts->cell_identity);
+ OSMO_ASSERT(l_bts && last_i >= 0);
+ rc = si10_rest_octets_encode_other(s_bts, &bv, l_bts, n_bts, last_i, i);
+ if (rc < 0) {
+ LOGP(DRR, LOGL_INFO, "Skip cell ID %d, SI 10 would overflow.\n",
+ n_bts->cell_identity);
+ /* Resore last position. */
+ bv.cur_bit = save_cur_bit;
+ break;
+ }
+ }
+ last_i = i;
+ l_bts = n_bts;
+ }
+ }
+
+ /* If no neighbor exists, generate rest octets without any neighbor info. */
+ if (!any_neighbor) {
+ LOGP(DRR, LOGL_INFO, "SI 10 without any neighbor cell.\n");
+ rc = si10_rest_octets_encode(s_bts, &bv, NULL, 0);
+ if (rc < 0)
+ return rc;
+ }
+
+ /* Do spare padding. We cannot do it earlier, because encoding might corrupt it if differential cell info
+ * does not fit into the message. */
+ while ((bv.cur_bit & 7))
+ bitvec_set_bit(&bv, L);
+ memset(bv.data + bv.cur_bit / 8, GSM_MACBLOCK_PADDING, bv.data_len - bv.cur_bit / 8);
+
+ return len;
+}
static int generate_si13(enum osmo_sysinfo_type t, struct gsm_bts *bts)
{
struct gsm48_system_information_type_13 *si13 =
(struct gsm48_system_information_type_13 *) GSM_BTS_SI(bts, t);
+ struct osmo_gsm48_si13_info si13_info;
int ret;
memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
@@ -1189,18 +1522,68 @@ static int generate_si13(enum osmo_sysinfo_type t, struct gsm_bts *bts)
si13->header.skip_indicator = 0;
si13->header.system_information = GSM48_MT_RR_SYSINFO_13;
- si13_default.rac = bts->gprs.rac;
- si13_default.net_ctrl_ord = bts->gprs.net_ctrl_ord;
+ si13_info = (struct osmo_gsm48_si13_info){
+ .cell_opts = {
+ .nmo = GPRS_NMO_II,
+ .t3168 = 2000,
+ .t3192 = 1500,
+ /* 3GPP TS 45.002 6.5.6:
+ * "On BCCH, the operator should limit DRX_TIMER_MAX [...] to 4 seconds" */
+ .drx_timer_max = 4,
+ .bs_cv_max = 15,
+ .ctrl_ack_type_use_block = bts->gprs.ctrl_ack_type_use_block,
+ .ext_info_present = true,
+ .ext_info = {
+ .egprs_supported = 0, /* overridden below */
+ .use_egprs_p_ch_req = 0, /* overridden below */
+ .bep_period = 5,
+ .pfc_supported = 0,
+ .dtm_supported = 0,
+ .bss_paging_coordination = 0, /* overridden below */
+ .ccn_active = false, /* overridden below */
+ },
+ },
+ .pwr_ctrl_pars = {
+ .alpha = bts->gprs.pwr_ctrl.alpha, /* a = 0.0 */
+ .t_avg_w = 16,
+ .t_avg_t = 16,
+ .pc_meas_chan = 0, /* downling measured on CCCH */
+ .n_avg_i = 8,
+ },
+ .bcch_change_mark = bts->bcch_change_mark, /* Information about the other SIs */
+ .si_change_field = 0,
+ .rac = bts->gprs.rac,
+ .spgc_ccch_sup = 0,
+ .net_ctrl_ord = bts->gprs.net_ctrl_ord,
+ .prio_acc_thr = 6,
+ };
+
+ switch (bts->gprs.mode) {
+ case BTS_GPRS_EGPRS:
+ si13_info.cell_opts.ext_info.egprs_supported = 1;
+ /* Whether EGPRS capable MSs shall use EGPRS PACKET CHANNEL REQUEST */
+ if (bts->gprs.egprs_pkt_chan_request)
+ si13_info.cell_opts.ext_info.use_egprs_p_ch_req = 1;
+ else
+ si13_info.cell_opts.ext_info.use_egprs_p_ch_req = 0;
+ break;
+ case BTS_GPRS_GPRS:
+ case BTS_GPRS_NONE:
+ si13_info.cell_opts.ext_info.egprs_supported = 0;
+ si13_info.cell_opts.ext_info.use_egprs_p_ch_req = 0;
+ break;
+ }
- si13_default.cell_opts.ctrl_ack_type_use_block =
- bts->gprs.ctrl_ack_type_use_block;
+ if (osmo_bts_has_feature(&bts->features, BTS_FEAT_PAGING_COORDINATION))
+ si13_info.cell_opts.ext_info.bss_paging_coordination = 1;
+ else
+ si13_info.cell_opts.ext_info.bss_paging_coordination = 0;
- /* Information about the other SIs */
- si13_default.bcch_change_mark = bts->bcch_change_mark;
- si13_default.cell_opts.supports_egprs_11bit_rach =
- bts->gprs.supports_egprs_11bit_rach;
+ si13_info.cell_opts.ext_info.ccn_active = bts->gprs.ccn.forced_vty ?
+ bts->gprs.ccn.active :
+ osmo_bts_has_feature(&bts->features, BTS_FEAT_CCN);
- ret = rest_octets_si13(si13->rest_octets, &si13_default);
+ ret = osmo_gsm48_rest_octets_si13_encode(si13->rest_octets, &si13_info);
if (ret < 0)
return ret;
@@ -1234,9 +1617,6 @@ int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type)
switch (bts->gprs.mode) {
case BTS_GPRS_EGPRS:
- si13_default.cell_opts.ext_info_present = 1;
- si13_default.cell_opts.ext_info.egprs_supported = 1;
- /* fallthrough */
case BTS_GPRS_GPRS:
si_info.gprs_ind.present = 1;
break;
@@ -1247,7 +1627,7 @@ int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type)
memcpy(&si_info.selection_params,
&bts->si_common.cell_ro_sel_par,
- sizeof(struct gsm48_si_selection_params));
+ sizeof(struct osmo_gsm48_si_selection_params));
gen_si = gen_si_fn[si_type];
if (!gen_si) {
diff --git a/src/osmo-bsc/timeslot_fsm.c b/src/osmo-bsc/timeslot_fsm.c
index 91b6b54ea..9618f9280 100644
--- a/src/osmo-bsc/timeslot_fsm.c
+++ b/src/osmo-bsc/timeslot_fsm.c
@@ -28,6 +28,8 @@
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/pcu_if.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/lchan.h>
static struct osmo_fsm ts_fsm;
@@ -47,11 +49,11 @@ struct gsm_bts_trx_ts *ts_fi_ts(struct osmo_fsm_inst *fi)
static void ts_fsm_update_id(struct gsm_bts_trx_ts *ts)
{
- osmo_fsm_inst_update_id_f(ts->fi, "%u-%u-%u-%s", ts->trx->bts->nr, ts->trx->nr, ts->nr,
- gsm_pchan_id(ts->pchan_on_init));
+ osmo_fsm_inst_update_id_f_sanitize(ts->fi, '_', "%u-%u-%u-%s", ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan_on_init));
}
-void ts_fsm_init()
+static __attribute__((constructor)) void ts_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&ts_fsm) == 0);
}
@@ -62,6 +64,15 @@ void ts_fsm_alloc(struct gsm_bts_trx_ts *ts)
OSMO_ASSERT(ts->trx);
ts->fi = osmo_fsm_inst_alloc(&ts_fsm, ts->trx, ts, LOGL_DEBUG, NULL);
OSMO_ASSERT(ts->fi);
+ ts_fsm_update_id(ts);
+}
+
+void ts_fsm_free(struct gsm_bts_trx_ts *ts)
+{
+ if (ts->fi) {
+ osmo_fsm_inst_free(ts->fi);
+ ts->fi = NULL;
+ }
}
enum lchan_sanity {
@@ -83,7 +94,9 @@ static enum lchan_sanity is_lchan_sane(struct gsm_bts_trx_ts *ts, struct gsm_lch
return LCHAN_IS_READY_TO_GO;
switch (ts->pchan_on_init) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
+ if (lchan->type == GSM_LCHAN_SDCCH)
+ return LCHAN_NEEDS_PCHAN_CHANGE;
if (lchan->type == GSM_LCHAN_TCH_H)
return LCHAN_NEEDS_PCHAN_CHANGE;
/* fall thru */
@@ -110,7 +123,7 @@ static int ts_count_active_lchans(struct gsm_bts_trx_ts *ts)
struct gsm_lchan *lchan;
int count = 0;
- ts_for_each_lchan(lchan, ts) {
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
if (lchan->fi->state == LCHAN_ST_UNUSED)
continue;
count++;
@@ -123,7 +136,7 @@ static void ts_lchans_dispatch(struct gsm_bts_trx_ts *ts, int lchan_state, uint3
{
struct gsm_lchan *lchan;
- ts_for_each_potential_lchan(lchan, ts) {
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
if (lchan_state >= 0
&& !lchan_state_is(lchan, lchan_state))
continue;
@@ -135,7 +148,7 @@ static void ts_terminate_lchan_fsms(struct gsm_bts_trx_ts *ts)
{
struct gsm_lchan *lchan;
- ts_for_each_potential_lchan(lchan, ts) {
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
osmo_fsm_inst_term(lchan->fi, OSMO_FSM_TERM_REQUEST, NULL);
}
}
@@ -144,7 +157,7 @@ static int ts_lchans_waiting(struct gsm_bts_trx_ts *ts)
{
struct gsm_lchan *lchan;
int count = 0;
- ts_for_each_potential_lchan(lchan, ts)
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible)
if (lchan->fi->state == LCHAN_ST_WAIT_TS_READY)
count++;
return count;
@@ -185,17 +198,60 @@ static void ts_fsm_err_ready_to_go_in_pdch(struct osmo_fsm_inst *fi, struct gsm_
osmo_fsm_inst_state_name(fi), gsm_lchan_name(lchan));
}
+void ts_set_pchan_is(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan_is)
+{
+ int i;
+ struct gsm_lchan *lchan;
+ uint8_t max_lchans_possible_vamos;
+
+ ts->pchan_is = pchan_is;
+ ts->max_primary_lchans = pchan_subslots(ts->pchan_is);
+ max_lchans_possible_vamos = pchan_subslots_vamos(ts->pchan_is);
+ LOG_TS(ts, LOGL_DEBUG, "pchan_is=%s max_primary_lchans=%d max_lchans_possible=%d (%u VAMOS)\n",
+ gsm_pchan_name(ts->pchan_is), ts->max_primary_lchans, ts->max_lchans_possible,
+ max_lchans_possible_vamos);
+ switch (ts->pchan_is) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ for (i = 0; i < ts->max_lchans_possible; i++) {
+ lchan = &ts->lchan[i];
+ if (i >= ts->max_primary_lchans &&
+ (i - ts->max_primary_lchans) < (int)max_lchans_possible_vamos)
+ lchan->vamos.is_secondary = true;
+ else
+ lchan->vamos.is_secondary = false;
+ lchan_fsm_update_id(lchan);
+ }
+ break;
+ default:
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
+ lchan->vamos.is_secondary = false;
+ lchan_fsm_update_id(lchan);
+ }
+ break;
+ }
+ chan_counts_ts_update(ts);
+}
+
static void ts_setup_lchans(struct gsm_bts_trx_ts *ts)
{
int i, max_lchans;
+ int max_lchans_vamos;
ts->pchan_on_init = ts->pchan_from_config;
ts_fsm_update_id(ts);
max_lchans = pchan_subslots(ts->pchan_on_init);
- LOG_TS(ts, LOGL_DEBUG, "max lchans: %d\n", max_lchans);
-
- for (i = 0; i < max_lchans; i++) {
+ if (osmo_bts_has_feature(&ts->trx->bts->features, BTS_FEAT_VAMOS))
+ max_lchans_vamos = pchan_subslots_vamos(ts->pchan_on_init);
+ else
+ max_lchans_vamos = 0;
+ LOG_TS(ts, LOGL_DEBUG, "max lchans: %d + %d VAMOS secondaries\n", max_lchans, max_lchans_vamos);
+ ts->max_lchans_possible = max_lchans + max_lchans_vamos;
+ ts->max_primary_lchans = 0;
+
+ OSMO_ASSERT(ts->max_lchans_possible <= TS_MAX_LCHAN);
+ for (i = 0; i < ts->max_lchans_possible; i++) {
/* If we receive more than one Channel OPSTART ACK, don't fail on the second init. */
if (ts->lchan[i].fi)
continue;
@@ -203,18 +259,16 @@ static void ts_setup_lchans(struct gsm_bts_trx_ts *ts)
}
switch (ts->pchan_on_init) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
- ts->pchan_is = GSM_PCHAN_NONE;
+ case GSM_PCHAN_OSMO_DYN:
+ ts_set_pchan_is(ts, GSM_PCHAN_NONE);
break;
case GSM_PCHAN_TCH_F_PDCH:
- ts->pchan_is = GSM_PCHAN_TCH_F;
+ ts_set_pchan_is(ts, GSM_PCHAN_TCH_F);
break;
default:
- ts->pchan_is = ts->pchan_on_init;
+ ts_set_pchan_is(ts, ts->pchan_on_init);
break;
}
-
- LOG_TS(ts, LOGL_DEBUG, "lchans initialized: %d\n", max_lchans);
}
static void ts_fsm_not_initialized(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -263,11 +317,45 @@ static void ts_fsm_not_initialized(struct osmo_fsm_inst *fi, uint32_t event, voi
osmo_fsm_inst_state_chg(fi, TS_ST_UNUSED, 0, 0);
}
-static void ts_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+static void ts_fsm_not_initialized_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ chan_counts_ts_clear(ts);
+}
+
+static void ts_fsm_unused_pdch_act(struct osmo_fsm_inst *fi)
{
struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
struct gsm_bts *bts = ts->trx->bts;
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ LOG_TS(ts, LOGL_DEBUG, "GPRS mode is 'none': not activating PDCH.\n");
+ return;
+ }
+
+ if (!ts->pdch_act_allowed) {
+ LOG_TS(ts, LOGL_DEBUG, "PDCH is disabled for this timeslot,"
+ " either due to a PDCH ACT NACK, or from manual VTY command:"
+ " not activating PDCH. (last error: %s)\n",
+ ts->last_errmsg ? : "-");
+ return;
+ }
+
+ if (bsc_co_located_pcu(bts) && !pcu_connected(bts->network)) {
+ LOG_TS(ts, LOGL_DEBUG, "PCU not connected: not activating PDCH.\n");
+ return;
+ }
+
+ osmo_fsm_inst_state_chg(fi, TS_ST_WAIT_PDCH_ACT, CHAN_ACT_DEACT_TIMEOUT,
+ T_CHAN_ACT_DEACT);
+}
+
+static void ts_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+
+ chan_counts_ts_update(ts);
+
/* We are entering the unused state. There must by definition not be any lchans waiting to be
* activated. */
if (ts_lchans_waiting(ts)) {
@@ -278,21 +366,9 @@ static void ts_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
}
switch (ts->pchan_on_init) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
case GSM_PCHAN_TCH_F_PDCH:
- if (bts->gprs.mode == BTS_GPRS_NONE) {
- LOG_TS(ts, LOGL_DEBUG, "GPRS mode is 'none': not activating PDCH.\n");
- return;
- }
- if (!ts->pdch_act_allowed) {
- LOG_TS(ts, LOGL_DEBUG, "PDCH is disabled for this timeslot,"
- " either due to a PDCH ACT NACK, or from manual VTY command:"
- " not activating PDCH. (last error: %s)\n",
- ts->last_errmsg ? : "-");
- return;
- }
- osmo_fsm_inst_state_chg(fi, TS_ST_WAIT_PDCH_ACT, CHAN_ACT_DEACT_TIMEOUT,
- T_CHAN_ACT_DEACT);
+ ts_fsm_unused_pdch_act(fi);
break;
case GSM_PCHAN_CCCH_SDCCH4_CBCH:
@@ -337,6 +413,10 @@ static void ts_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *data)
/* ignored. */
return;
+ case TS_EV_PDCH_ACT:
+ ts_fsm_unused_pdch_act(fi);
+ return;
+
default:
OSMO_ASSERT(false);
}
@@ -364,6 +444,7 @@ static void ts_fsm_wait_pdch_act_onenter(struct osmo_fsm_inst *fi, uint32_t prev
static void ts_fsm_wait_pdch_act(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ struct rate_ctr_group *bts_ctrs = ts->trx->bts->bts_ctrs;
switch (event) {
case TS_EV_PDCH_ACT_ACK:
@@ -372,9 +453,9 @@ static void ts_fsm_wait_pdch_act(struct osmo_fsm_inst *fi, uint32_t event, void
case TS_EV_PDCH_ACT_NACK:
if (ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH)
- rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_RSL_IPA_NACK));
else
- rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts_ctrs, BTS_CTR_CHAN_ACT_NACK));
ts->pdch_act_allowed = false;
ts_fsm_error(fi, TS_ST_UNUSED, "Received PDCH activation NACK");
return;
@@ -414,10 +495,10 @@ static void ts_fsm_pdch_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
/* Set pchan = PDCH status, but double check. */
switch (ts->pchan_on_init) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
case GSM_PCHAN_TCH_F_PDCH:
case GSM_PCHAN_PDCH:
- ts->pchan_is = GSM_PCHAN_PDCH;
+ ts_set_pchan_is(ts, GSM_PCHAN_PDCH);
break;
default:
ts_fsm_error(fi, TS_ST_BORKEN, "pchan %s is incapable of activating PDCH",
@@ -459,6 +540,10 @@ static void ts_fsm_pdch(struct osmo_fsm_inst *fi, uint32_t event, void *data)
}
}
+ case TS_EV_PDCH_DEACT:
+ ts_fsm_pdch_deact(fi);
+ return;
+
case TS_EV_LCHAN_UNUSED:
/* ignored */
return;
@@ -489,11 +574,11 @@ static void ts_fsm_wait_pdch_deact(struct osmo_fsm_inst *fi, uint32_t event, voi
case TS_EV_PDCH_DEACT_ACK:
/* Remove pchan = PDCH status, but double check. */
switch (ts->pchan_on_init) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
- ts->pchan_is = GSM_PCHAN_NONE;
+ case GSM_PCHAN_OSMO_DYN:
+ ts_set_pchan_is(ts, GSM_PCHAN_NONE);
break;
case GSM_PCHAN_TCH_F_PDCH:
- ts->pchan_is = GSM_PCHAN_TCH_F;
+ ts_set_pchan_is(ts, GSM_PCHAN_TCH_F);
break;
default:
ts_fsm_error(fi, TS_ST_BORKEN, "pchan %s is incapable of deactivating PDCH",
@@ -509,7 +594,7 @@ static void ts_fsm_wait_pdch_deact(struct osmo_fsm_inst *fi, uint32_t event, voi
case TS_EV_PDCH_DEACT_NACK:
if (ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH)
- rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ts->trx->bts->bts_ctrs, BTS_CTR_RSL_IPA_NACK));
/* For Osmocom style dyn TS, there actually is no NACK, since there is no RF Channel
* Release NACK message in RSL. */
ts_fsm_error(fi, TS_ST_BORKEN, "Received PDCH deactivation NACK");
@@ -554,7 +639,7 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
ts->pdch_act_allowed = true;
/* For static TS, check validity. For dyn TS, figure out which PCHAN this should become. */
- ts_for_each_potential_lchan(lchan, ts) {
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
if (lchan_state_is(lchan, LCHAN_ST_UNUSED))
continue;
@@ -571,7 +656,7 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
if (!ok && lchan_state_is(lchan, LCHAN_ST_WAIT_TS_READY)) {
LOG_TS(ts, LOGL_ERROR, "lchan activation of %s is not permitted for %s (%s)\n",
- gsm_lchant_name(lchan->type), gsm_pchan_name(ts->pchan_on_init),
+ gsm_chan_t_name(lchan->type), gsm_pchan_name(ts->pchan_on_init),
gsm_lchan_name(lchan));
lchan_dispatch(lchan, LCHAN_EV_TS_ERROR);
}
@@ -583,7 +668,7 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
activating_type = lchan->type;
else if (activating_type != lchan->type) {
LOG_TS(ts, LOGL_ERROR, "lchan type %s mismatches %s (%s)\n",
- gsm_lchant_name(lchan->type), gsm_lchant_name(activating_type),
+ gsm_chan_t_name(lchan->type), gsm_chan_t_name(activating_type),
gsm_lchan_name(lchan));
lchan_dispatch(lchan, LCHAN_EV_TS_ERROR);
}
@@ -602,7 +687,7 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
break;
default:
- LOG_TS(ts, LOGL_ERROR, "cannot use timeslot as %s\n", gsm_lchant_name(activating_type));
+ LOG_TS(ts, LOGL_ERROR, "cannot use timeslot as %s\n", gsm_chan_t_name(activating_type));
ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_ERROR);
break;
}
@@ -612,10 +697,12 @@ static void ts_fsm_in_use_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
return;
}
+ chan_counts_ts_update(ts);
+
/* Make sure dyn TS pchan_is is updated. For TCH/F_PDCH, there are only PDCH or TCH/F modes, but
- * for Osmocom style TCH/F_TCH/H_PDCH the pchan_is == NONE until an lchan is activated. */
- if (ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH)
- ts->pchan_is = gsm_pchan_by_lchan_type(activating_type);
+ * for Osmocom style TCH/F_TCH/H_SDCCH8_PDCH the pchan_is == NONE until an lchan is activated. */
+ if (ts->pchan_on_init == GSM_PCHAN_OSMO_DYN)
+ ts_set_pchan_is(ts, gsm_pchan_by_lchan_type(activating_type));
ts_lchans_dispatch(ts, LCHAN_ST_WAIT_TS_READY, LCHAN_EV_TS_READY);
}
@@ -654,6 +741,40 @@ static void ts_fsm_in_use(struct osmo_fsm_inst *fi, uint32_t event, void *data)
}
}
+static void ts_fsm_borken_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ struct gsm_bts *bts = ts->trx->bts;
+ enum bts_counter_id ctr;
+ switch (prev_state) {
+ case TS_ST_NOT_INITIALIZED:
+ ctr = BTS_CTR_TS_BORKEN_FROM_NOT_INITIALIZED;
+ break;
+ case TS_ST_UNUSED:
+ ctr = BTS_CTR_TS_BORKEN_FROM_UNUSED;
+ break;
+ case TS_ST_WAIT_PDCH_ACT:
+ ctr = BTS_CTR_TS_BORKEN_FROM_WAIT_PDCH_ACT;
+ break;
+ case TS_ST_PDCH:
+ ctr = BTS_CTR_TS_BORKEN_FROM_PDCH;
+ break;
+ case TS_ST_WAIT_PDCH_DEACT:
+ ctr = BTS_CTR_TS_BORKEN_FROM_WAIT_PDCH_DEACT;
+ break;
+ case TS_ST_IN_USE:
+ ctr = BTS_CTR_TS_BORKEN_FROM_IN_USE;
+ break;
+ case TS_ST_BORKEN:
+ ctr = BTS_CTR_TS_BORKEN_FROM_BORKEN;
+ break;
+ default:
+ ctr = BTS_CTR_TS_BORKEN_FROM_UNKNOWN;
+ }
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, ctr));
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_TS_BORKEN), 1);
+}
+
static void ts_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
@@ -668,6 +789,32 @@ static void ts_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data)
return;
}
+ case TS_EV_PDCH_ACT_ACK:
+ case TS_EV_PDCH_ACT_NACK:
+ {
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ struct gsm_bts *bts = ts->trx->bts;
+ /* Late PDCH activation ACK/NACK is not a crime.
+ * Just process them as normal. */
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_TS_BORKEN_EV_PDCH_ACT_ACK_NACK));
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_TS_BORKEN), 1);
+ ts_fsm_wait_pdch_act(fi, event, data);
+ return;
+ }
+
+ case TS_EV_PDCH_DEACT_ACK:
+ case TS_EV_PDCH_DEACT_NACK:
+ {
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ struct gsm_bts *bts = ts->trx->bts;
+ /* Late PDCH deactivation ACK/NACK is also not a crime.
+ * Just process them as normal. */
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_TS_BORKEN_EV_PDCH_DEACT_ACK_NACK));
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_TS_BORKEN), 1);
+ ts_fsm_wait_pdch_deact(fi, event, data);
+ return;
+ }
+
default:
OSMO_ASSERT(false);
}
@@ -701,7 +848,8 @@ static void ts_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data
osmo_fsm_inst_state_chg(fi, TS_ST_NOT_INITIALIZED, 0, 0);
OSMO_ASSERT(fi->state == TS_ST_NOT_INITIALIZED);
ts_terminate_lchan_fsms(ts);
- ts->pchan_is = ts->pchan_on_init = GSM_PCHAN_NONE;
+ ts->pchan_on_init = GSM_PCHAN_NONE;
+ ts_set_pchan_is(ts, GSM_PCHAN_NONE);
ts_fsm_update_id(ts);
break;
@@ -710,7 +858,7 @@ static void ts_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data
if (fi->state != TS_ST_NOT_INITIALIZED)
osmo_fsm_inst_state_chg(fi, TS_ST_NOT_INITIALIZED, 0, 0);
OSMO_ASSERT(fi->state == TS_ST_NOT_INITIALIZED);
- ts->pchan_is = GSM_PCHAN_NONE;
+ ts_set_pchan_is(ts, GSM_PCHAN_NONE);
ts_lchans_dispatch(ts, -1, LCHAN_EV_TS_ERROR);
break;
@@ -719,11 +867,22 @@ static void ts_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data
}
}
+static void ts_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct gsm_bts_trx_ts *ts = ts_fi_ts(fi);
+ struct gsm_bts *bts = ts->trx->bts;
+ if (ts->fi->state == TS_ST_BORKEN) {
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_TS_BORKEN_EV_TEARDOWN));
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_TS_BORKEN), 1);
+ }
+}
+
#define S(x) (1 << (x))
static const struct osmo_fsm_state ts_fsm_states[] = {
[TS_ST_NOT_INITIALIZED] = {
.name = "NOT_INITIALIZED",
+ .onenter = ts_fsm_not_initialized_onenter,
.action = ts_fsm_not_initialized,
.in_event_mask = 0
| S(TS_EV_OML_READY)
@@ -743,6 +902,7 @@ static const struct osmo_fsm_state ts_fsm_states[] = {
.in_event_mask = 0
| S(TS_EV_LCHAN_REQUESTED)
| S(TS_EV_LCHAN_UNUSED)
+ | S(TS_EV_PDCH_ACT)
,
.out_state_mask = 0
| S(TS_ST_WAIT_PDCH_ACT)
@@ -775,6 +935,7 @@ static const struct osmo_fsm_state ts_fsm_states[] = {
.in_event_mask = 0
| S(TS_EV_LCHAN_REQUESTED)
| S(TS_EV_LCHAN_UNUSED)
+ | S(TS_EV_PDCH_DEACT)
,
.out_state_mask = 0
| S(TS_ST_WAIT_PDCH_DEACT)
@@ -795,7 +956,6 @@ static const struct osmo_fsm_state ts_fsm_states[] = {
.out_state_mask = 0
| S(TS_ST_IN_USE)
| S(TS_ST_UNUSED)
- | S(TS_ST_BORKEN)
| S(TS_ST_NOT_INITIALIZED)
| S(TS_ST_BORKEN)
,
@@ -816,13 +976,21 @@ static const struct osmo_fsm_state ts_fsm_states[] = {
},
[TS_ST_BORKEN] = {
.name = "BORKEN",
+ .onenter = ts_fsm_borken_onenter,
.action = ts_fsm_borken,
.in_event_mask = 0
| S(TS_EV_LCHAN_REQUESTED)
| S(TS_EV_LCHAN_UNUSED)
+ | S(TS_EV_PDCH_ACT_ACK)
+ | S(TS_EV_PDCH_ACT_NACK)
+ | S(TS_EV_PDCH_DEACT_ACK)
+ | S(TS_EV_PDCH_DEACT_NACK)
,
.out_state_mask = 0
+ | S(TS_ST_IN_USE)
+ | S(TS_ST_UNUSED)
| S(TS_ST_NOT_INITIALIZED)
+ | S(TS_ST_PDCH)
,
},
@@ -839,6 +1007,8 @@ static const struct value_string ts_fsm_event_names[] = {
OSMO_VALUE_STRING(TS_EV_PDCH_ACT_NACK),
OSMO_VALUE_STRING(TS_EV_PDCH_DEACT_ACK),
OSMO_VALUE_STRING(TS_EV_PDCH_DEACT_NACK),
+ OSMO_VALUE_STRING(TS_EV_PDCH_DEACT),
+ OSMO_VALUE_STRING(TS_EV_PDCH_ACT),
{}
};
@@ -854,6 +1024,7 @@ static struct osmo_fsm ts_fsm = {
| S(TS_EV_RSL_DOWN)
,
.allstate_action = ts_fsm_allstate,
+ .cleanup = ts_fsm_cleanup,
};
/* Return true if any lchans are waiting for this timeslot to become a specific PCHAN. If target_pchan is
@@ -861,7 +1032,7 @@ static struct osmo_fsm ts_fsm = {
bool ts_is_lchan_waiting_for_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config *target_pchan)
{
struct gsm_lchan *lchan;
- ts_for_each_potential_lchan(lchan, ts) {
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
if (lchan->fi->state == LCHAN_ST_WAIT_TS_READY) {
if (target_pchan)
*target_pchan = gsm_pchan_by_lchan_type(lchan->type);
@@ -902,7 +1073,7 @@ bool ts_is_pchan_switching(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config
* waiting for PDCH DEACT (N)ACK */
if (target_pchan) {
switch (ts->pchan_on_init) {
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
if (target_pchan)
*target_pchan = GSM_PCHAN_NONE;
break;
@@ -925,7 +1096,7 @@ bool ts_is_pchan_switching(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config
/* Does the timeslot's *current* state allow use as this PCHAN kind? If the ts is in switchover, return
* true if the switchover's target PCHAN matches, i.e. an lchan for this pchan kind could be requested
* and will be served after the switch. (Do not check whether any lchans are actually available.) */
-bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan)
+bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan, bool allow_pchan_switch)
{
enum gsm_phys_chan_config target_pchan;
@@ -943,5 +1114,31 @@ bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_
if (ts_is_lchan_waiting_for_pchan(ts, &target_pchan))
return target_pchan == as_pchan;
- return ts_is_capable_of_pchan(ts, as_pchan);
+ if (!ts_is_capable_of_pchan(ts, as_pchan))
+ return false;
+
+ if (!allow_pchan_switch && ts->pchan_is != as_pchan)
+ return false;
+
+ return true;
+}
+
+void ts_pdch_act(struct gsm_bts_trx_ts *ts)
+{
+ /* We send the activation event only when the timeslot is UNUSED. In all other cases the timeslot FSM
+ * will automatically handle the activation at some later point. */
+ if (ts->fi->state != TS_ST_UNUSED)
+ return;
+
+ osmo_fsm_inst_dispatch(ts->fi, TS_EV_PDCH_ACT, NULL);
+}
+
+void ts_pdch_deact(struct gsm_bts_trx_ts *ts)
+{
+ /* We send the deactivation event only when the timeslot is active as PDCH. The timeslot FSM will check
+ * if the PCU is available before activating the PDCH again. */
+ if (ts->fi->state != TS_ST_PDCH)
+ return;
+
+ osmo_fsm_inst_dispatch(ts->fi, TS_EV_PDCH_DEACT, NULL);
}
diff --git a/src/osmo-bsc/vgcs_fsm.c b/src/osmo-bsc/vgcs_fsm.c
new file mode 100644
index 000000000..1f2bbefec
--- /dev/null
+++ b/src/osmo-bsc/vgcs_fsm.c
@@ -0,0 +1,1318 @@
+/* Handle VGCS/VBCS calls. (Voice Group/Broadcast Call Service). */
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * Author: Andreas Eversberg
+ *
+ * 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/>.
+ */
+
+/* The process consists of two state machnes:
+ *
+ * The VGCS call state machine handles the voice group/broadcast call control.
+ * There is one instance for every call. It controls the uplink states of the
+ * call. They will be reported to the MSC or can be changed by the MSC.
+ * One SCCP connection for is associated with the state machine. This is used
+ * to talk to the MSC about state changes.
+ *
+ * The VGCS channel state machine handles the channel states in each cell.
+ * There is one instance for every cell and every call. The instances are
+ * linked to the call state process. It controls the uplink states of the
+ * channel. They will be reported to the call state machine or can be changed
+ * by the call state machine.
+ * One SCCP connection for every cell is associated with the state machine.
+ * It is used to perform VGCS channel assignment.
+ *
+ */
+
+#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
+#include <osmocom/bsc/osmo_bsc.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/vgcs_fsm.h>
+#include <osmocom/bsc/handover_fsm.h>
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_select.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/assignment_fsm.h>
+#include <osmocom/bsc/gsm_08_08.h>
+#include <osmocom/bsc/gsm_04_08_rr.h>
+#include <osmocom/bsc/bts_trx.h>
+#include <osmocom/bsc/bts.h>
+#include <osmocom/bsc/system_information.h>
+
+#define S(x) (1 << (x))
+
+#define LOG_CALL(conn, level, fmt, args...) \
+ LOGP(DASCI, level, \
+ (conn->vgcs_call.sf == GSM0808_SF_VGCS) ? ("VGCS callref %s: " fmt) : ("VBS callref %s: " fmt), \
+ gsm44068_group_id_string(conn->vgcs_call.call_ref), ##args)
+#define LOG_CHAN(conn, level, fmt, args...) \
+ LOGP(DASCI, level, \
+ (conn->vgcs_chan.sf == GSM0808_SF_VGCS) ? ("VGCS callref %s, cell %s: " fmt) \
+ : ("VBS callref %s, cell %s: " fmt), \
+ gsm44068_group_id_string(conn->vgcs_chan.call_ref), conn->vgcs_chan.ci_str, ##args)
+
+const char *gsm44068_group_id_string(uint32_t callref)
+{
+ static char string[9];
+
+ snprintf(string, sizeof(string), "%08u", callref);
+
+ return string;
+}
+
+static struct osmo_fsm vgcs_call_fsm;
+static struct osmo_fsm vgcs_chan_fsm;
+
+static __attribute__((constructor)) void vgcs_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&vgcs_call_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&vgcs_chan_fsm) == 0);
+}
+
+static const struct value_string vgcs_fsm_event_names[] = {
+ OSMO_VALUE_STRING(VGCS_EV_SETUP),
+ OSMO_VALUE_STRING(VGCS_EV_ASSIGN_REQ),
+ OSMO_VALUE_STRING(VGCS_EV_TALKER_DET),
+ OSMO_VALUE_STRING(VGCS_EV_LISTENER_DET),
+ OSMO_VALUE_STRING(VGCS_EV_MSC_ACK),
+ OSMO_VALUE_STRING(VGCS_EV_MSC_REJECT),
+ OSMO_VALUE_STRING(VGCS_EV_MSC_SEIZE),
+ OSMO_VALUE_STRING(VGCS_EV_MSC_RELEASE),
+ OSMO_VALUE_STRING(VGCS_EV_MSC_DTAP),
+ OSMO_VALUE_STRING(VGCS_EV_LCHAN_ACTIVE),
+ OSMO_VALUE_STRING(VGCS_EV_LCHAN_ERROR),
+ OSMO_VALUE_STRING(VGCS_EV_MGW_OK),
+ OSMO_VALUE_STRING(VGCS_EV_MGW_FAIL),
+ OSMO_VALUE_STRING(VGCS_EV_TALKER_EST),
+ OSMO_VALUE_STRING(VGCS_EV_TALKER_DATA),
+ OSMO_VALUE_STRING(VGCS_EV_TALKER_REL),
+ OSMO_VALUE_STRING(VGCS_EV_TALKER_FAIL),
+ OSMO_VALUE_STRING(VGCS_EV_BLOCK),
+ OSMO_VALUE_STRING(VGCS_EV_REJECT),
+ OSMO_VALUE_STRING(VGCS_EV_UNBLOCK),
+ OSMO_VALUE_STRING(VGCS_EV_CLEANUP),
+ OSMO_VALUE_STRING(VGCS_EV_CALLING_ASSIGNED),
+ { }
+};
+
+static struct gsm_subscriber_connection *find_calling_subscr_conn(struct gsm_subscriber_connection *conn)
+{
+ struct gsm_subscriber_connection *c;
+
+ llist_for_each_entry(c, &conn->network->subscr_conns, entry) {
+ if (!c->assignment.fi)
+ continue;
+ if (c->assignment.req.target_lchan != conn->lchan)
+ continue;
+ return c;
+ }
+
+ return NULL;
+}
+
+/*
+ * VGCS call FSM
+ */
+
+/* Add/update SI10. It must be called whenever a channel is activated or failed. */
+static void si10_update(struct gsm_subscriber_connection *conn)
+{
+ struct gsm_subscriber_connection *c;
+ uint8_t si10[SI10_LENGTH];
+ int rc;
+
+ /* Skip SI10 update, if not all channels have been activated or failed. */
+ llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
+ if (c->vgcs_chan.fi->state == VGCS_CHAN_ST_WAIT_EST) {
+ LOG_CALL(conn, LOGL_DEBUG, "There is a channel, not yet active. No SI10 update now.\n");
+ return;
+ }
+ }
+
+ LOG_CALL(conn, LOGL_DEBUG, "New channel(s) added, updating SI10 for all channels.\n");
+
+ /* Go through all channels. */
+ llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
+ /* Skip all channels that failed to activate or have not been aktivated yet.
+ * There shouldn't be any channel in that state now. */
+ if (!c->lchan)
+ continue;
+ /* Encode SI 10 for this channel. Skip, if it fails. */
+ rc = gsm_generate_si10((struct gsm48_system_information_type_10 *)si10, sizeof(si10), c);
+ if (rc < 0)
+ continue;
+ /* Add SI 10 to SACCH of this channel c. */
+ rsl_sacch_info_modify(c->lchan, RSL_SYSTEM_INFO_10, si10, sizeof(si10));
+ }
+}
+
+static void vgcs_call_detach_and_destroy(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct gsm_subscriber_connection *conn = fi->priv, *c;
+ struct msgb *msg;
+
+ /* Flush message queue. */
+ while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue)))
+ msgb_free(msg);
+
+ /* Detach all cell instances. */
+ while (!llist_empty(&conn->vgcs_call.chan_list)) {
+ c = llist_entry(conn->vgcs_call.chan_list.next, struct gsm_subscriber_connection, vgcs_chan.list);
+ c->vgcs_chan.call = NULL;
+ llist_del(&c->vgcs_chan.list);
+ }
+
+ /* No Talker. */
+ conn->vgcs_call.talker = NULL;
+
+ /* Remove pointer of FSM. */
+ conn->vgcs_call.fi = NULL;
+}
+
+static void vgcs_call_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ switch (event) {
+ case VGCS_EV_SETUP:
+ LOG_CALL(conn, LOGL_DEBUG, "VGCS/VBS SETUP from MSC.\n");
+ /* MSC sends VGCS/VBS SETUP for a new call. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0);
+ /* Remove unsupported features. */
+ conn->vgcs_call.ff.tp_ind = 0;
+ conn->vgcs_call.ff.as_ind_circuit = 0;
+ conn->vgcs_call.ff.as_ind_link = 0;
+ conn->vgcs_call.ff.bss_res = 0;
+ conn->vgcs_call.ff.tcp = 0;
+ /* Acknowlege the call. */
+ bsc_tx_setup_ack(conn, &conn->vgcs_call.ff);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_call_fsm_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv, *c;
+ struct handover_rr_detect_data *d = data;
+ struct msgb *msg;
+
+ switch (event) {
+ case VGCS_EV_TALKER_DET:
+ LOG_CALL(conn, LOGL_DEBUG, "Talker detected.\n");
+ /* Talker detected on a channel, call becomes busy. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BUSY, 0, 0);
+ conn->vgcs_call.talker = d->msg->lchan->conn;
+ /* Reset pending states. */
+ while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue)))
+ msgb_free(msg);
+ conn->vgcs_call.msc_ack = false;
+ conn->vgcs_call.talker_rel = false;
+ /* Report busy uplink to the MSC. */
+ bsc_tx_uplink_req(conn);
+ /* Block all other channels. */
+ llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
+ if (c == conn->vgcs_call.talker)
+ continue;
+ osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_BLOCK, NULL);
+ }
+ break;
+ case VGCS_EV_LISTENER_DET:
+ LOG_CALL(conn, LOGL_DEBUG, "Listener detected.\n");
+ // Listener detection not supported.
+ break;
+ case VGCS_EV_MSC_SEIZE:
+ LOG_CALL(conn, LOGL_DEBUG, "MSC seizes all channels.\n");
+ /* MSC seizes call (talker on a different BSS), call becomes blocked. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BLOCKED, 0, 0);
+ /* Block all channels. */
+ llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list)
+ osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_BLOCK, NULL);
+ break;
+ case VGCS_EV_MSC_RELEASE:
+ /* Ignore, because there is no blocked channel in this state. */
+ break;
+ case VGCS_EV_MSC_REJECT:
+ LOG_CALL(conn, LOGL_DEBUG, "MSC rejects talker on uplink.\n");
+ /* Race condition: Talker released before the MSC rejects the talker. Ignore! */
+ break;
+ case VGCS_EV_CLEANUP:
+ LOG_CALL(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
+ osmo_fsm_inst_term(conn->vgcs_call.fi, 0, NULL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+/* Get L3 info from message, if exists. Return the length or otherwise return 0. */
+int l3_data_from_msg(struct msgb *msg, uint8_t **l3_info)
+{
+ struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+
+ /* No space for L3 info */
+ if (msgb_l2len(msg) < sizeof(*rllh) + 3 || rllh->data[0] != RSL_IE_L3_INFO)
+ return 0;
+
+ *l3_info = msg->l3h = &rllh->data[3];
+ return msgb_l3len(msg);
+}
+
+static void vgcs_call_fsm_busy(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv, *c;
+ struct msgb *msg = data;
+ uint8_t cause = (data) ? *(uint8_t *)data : 0;
+ uint8_t *l3_info;
+ int l3_len;
+ int rc;
+
+ switch (event) {
+ case VGCS_EV_TALKER_EST:
+ LOG_CALL(conn, LOGL_DEBUG, "Talker established uplink.\n");
+ /* Talker established L2 connection. Sent L3 info to MSC, if MSC already acked, otherwise enqueue. */
+ if (conn->vgcs_call.msc_ack) {
+ LOG_CALL(conn, LOGL_DEBUG, "Sending establishment messages to MSC.\n");
+ l3_len = l3_data_from_msg(msg, &l3_info);
+ if (conn->vgcs_call.talker)
+ bsc_tx_uplink_req_conf(conn, &conn->vgcs_call.talker->vgcs_chan.ci, l3_info, l3_len);
+ else
+ LOG_CALL(conn, LOGL_ERROR, "Talker establishes, but talker not set, please fix!\n");
+ } else {
+ LOG_CALL(conn, LOGL_DEBUG, "No uplink request ack from MSC yet, queue message.\n");
+ msg = msgb_copy(msg, "Queued Talker establishment");
+ if (msg)
+ msgb_enqueue(&conn->vgcs_call.l3_queue, msg);
+ }
+ break;
+ case VGCS_EV_TALKER_DATA:
+ LOG_CALL(conn, LOGL_DEBUG, "Talker sent data on uplink.\n");
+ /* Talker sends data. Sent L3 info to MSC, if MSC already acked, otherwise enqueue. */
+ if (conn->vgcs_call.msc_ack) {
+ LOG_CALL(conn, LOGL_DEBUG, "Sending data messages to MSC.\n");
+ bsc_dtap(conn, 0, msg);
+ } else {
+ LOG_CALL(conn, LOGL_DEBUG, "No uplink request ack from MSC yet, queue message.\n");
+ msg = msgb_copy(msg, "Queued DTAP");
+ if (msg)
+ msgb_enqueue(&conn->vgcs_call.l3_queue, msg);
+ }
+ break;
+ case VGCS_EV_MSC_DTAP:
+ LOG_CALL(conn, LOGL_DEBUG, "MSC sends DTAP message to talker.\n");
+ if (!conn->vgcs_call.talker) {
+ msgb_free(data);
+ break;
+ }
+ rc = osmo_fsm_inst_dispatch(conn->vgcs_call.talker->vgcs_chan.fi, VGCS_EV_MSC_DTAP, data);
+ if (rc < 0)
+ msgb_free(data);
+ break;
+ case VGCS_EV_TALKER_REL:
+ LOG_CALL(conn, LOGL_DEBUG, "Talker released on uplink.\n");
+ if (!conn->vgcs_call.msc_ack) {
+ LOG_CALL(conn, LOGL_DEBUG, "Talker released before MSC acknowleded or rejected.\n");
+ conn->vgcs_call.talker_rel = true;
+ conn->vgcs_call.talker_cause = cause;
+ break;
+ }
+talker_released:
+ /* Talker released channel, call becomes idle. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0);
+ conn->vgcs_call.talker = NULL;
+ /* Report free uplink to the MSC. */
+ bsc_tx_uplink_release_ind(conn, cause);
+ /* Unblock all other channels. */
+ llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
+ if (c == conn->vgcs_call.talker)
+ continue;
+ osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_UNBLOCK, NULL);
+ }
+ break;
+ case VGCS_EV_MSC_SEIZE:
+ LOG_CALL(conn, LOGL_DEBUG, "MSC seizes all channels. (channels are blocked)\n");
+ /* Race condition: MSC seizes call (talker on a different BSS), call becomes blocked. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BLOCKED, 0, 0);
+ /* Reject talker. (Forward to chan FSM.) */
+ if (conn->vgcs_call.talker) {
+ osmo_fsm_inst_dispatch(conn->vgcs_call.talker->vgcs_chan.fi, VGCS_EV_REJECT, NULL);
+ conn->vgcs_call.talker = NULL;
+ }
+ /* Block all channels. */
+ llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list)
+ osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_BLOCK, NULL);
+ break;
+ case VGCS_EV_MSC_ACK:
+ LOG_CALL(conn, LOGL_DEBUG, "MSC acks talker on uplink.\n");
+ /* MSC acknowledges uplink. Send L3 info to MSC, if talker already established. */
+ conn->vgcs_call.msc_ack = true;
+ /* Send establish message via UPLINK REQUEST CONFIRM, if already received. */
+ msg = msgb_dequeue(&conn->vgcs_call.l3_queue);
+ if (msg) {
+ LOG_CALL(conn, LOGL_DEBUG, "Sending queued establishment messages to MSC.\n");
+ l3_len = l3_data_from_msg(msg, &l3_info);
+ if (conn->vgcs_call.talker)
+ bsc_tx_uplink_req_conf(conn, &conn->vgcs_call.talker->vgcs_chan.ci, l3_info, l3_len);
+ else
+ LOG_CALL(conn, LOGL_ERROR, "MSC acks taker, but talker not set, please fix!\n");
+ msgb_free(msg);
+ }
+ /* Send data messages via UPLINK APPLICATION DATA, if already received. */
+ while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue))) {
+ LOG_CALL(conn, LOGL_DEBUG, "Sending queued DTAP messages to MSC.\n");
+ bsc_dtap(conn, 0, msg);
+ msgb_free(msg);
+ }
+ /* If there is a pending talker release. */
+ if (conn->vgcs_call.talker_rel) {
+ LOG_CALL(conn, LOGL_DEBUG, "Sending queued talker release messages to MSC.\n");
+ cause = conn->vgcs_call.talker_cause;
+ goto talker_released;
+ }
+ break;
+ case VGCS_EV_MSC_REJECT:
+ LOG_CALL(conn, LOGL_DEBUG, "MSC rejects talker on uplink.\n");
+ /* MSC rejects talker, call becomes idle. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0);
+ /* Reject talker. (Forward to chan FSM.) */
+ if (conn->vgcs_call.talker)
+ osmo_fsm_inst_dispatch(conn->vgcs_call.talker->vgcs_chan.fi, VGCS_EV_REJECT, NULL);
+ else
+ LOG_CALL(conn, LOGL_ERROR, "MSC rejects, but talker not set, please fix!\n");
+ conn->vgcs_call.talker = NULL;
+ /* Unblock all other channels. */
+ llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list) {
+ if (c == conn->vgcs_call.talker)
+ continue;
+ osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_UNBLOCK, NULL);
+ }
+ break;
+ case VGCS_EV_CLEANUP:
+ LOG_CALL(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
+ osmo_fsm_inst_term(conn->vgcs_call.fi, 0, NULL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_call_fsm_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv, *c;
+ struct msgb *msg;
+
+ switch (event) {
+ case VGCS_EV_CALLING_ASSIGNED:
+ LOG_CALL(conn, LOGL_DEBUG, "Calling subscriber assigned and now on uplink.\n");
+ /* Talker detected on a channel, call becomes busy. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_BUSY, 0, 0);
+ conn->vgcs_call.talker = data;
+ /* Reset pending states, but imply that MSC acked this uplink session. */
+ while ((msg = msgb_dequeue(&conn->vgcs_call.l3_queue)))
+ msgb_free(msg);
+ conn->vgcs_call.msc_ack = true;
+ break;
+ case VGCS_EV_TALKER_REL:
+ LOG_CALL(conn, LOGL_DEBUG, "Talker released on uplink.\n");
+ /* Talker release was complete. Ignore. */
+ break;
+ case VGCS_EV_MSC_RELEASE:
+ LOG_CALL(conn, LOGL_DEBUG, "MSC releases all channels. (channels are free)\n");
+ /* MSC releases call (no mor talker on a different BSS), call becomes idle */
+ osmo_fsm_inst_state_chg(fi, VGCS_CALL_ST_IDLE, 0, 0);
+ /* Unblock all channels. */
+ llist_for_each_entry(c, &conn->vgcs_call.chan_list, vgcs_chan.list)
+ osmo_fsm_inst_dispatch(c->vgcs_chan.fi, VGCS_EV_UNBLOCK, NULL);
+ break;
+ case VGCS_EV_CLEANUP:
+ LOG_CALL(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
+ osmo_fsm_inst_term(conn->vgcs_call.fi, 0, NULL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static const struct osmo_fsm_state vgcs_call_fsm_states[] = {
+ [VGCS_CALL_ST_NULL] = {
+ .name = "NULL",
+ .in_event_mask = S(VGCS_EV_SETUP),
+ .out_state_mask = S(VGCS_CALL_ST_IDLE),
+ .action = vgcs_call_fsm_null,
+ },
+ [VGCS_CALL_ST_IDLE] = {
+ .name = "IDLE",
+ .in_event_mask = S(VGCS_EV_TALKER_DET) |
+ S(VGCS_EV_MSC_SEIZE) |
+ S(VGCS_EV_MSC_RELEASE) |
+ S(VGCS_EV_MSC_REJECT) |
+ S(VGCS_EV_CLEANUP),
+ .out_state_mask = S(VGCS_CALL_ST_BUSY) |
+ S(VGCS_CALL_ST_BLOCKED) |
+ S(VGCS_CALL_ST_NULL),
+ .action = vgcs_call_fsm_idle,
+ },
+ [VGCS_CALL_ST_BUSY] = {
+ .name = "BUSY",
+ .in_event_mask = S(VGCS_EV_TALKER_EST) |
+ S(VGCS_EV_TALKER_DATA) |
+ S(VGCS_EV_MSC_DTAP) |
+ S(VGCS_EV_TALKER_REL) |
+ S(VGCS_EV_MSC_SEIZE) |
+ S(VGCS_EV_MSC_ACK) |
+ S(VGCS_EV_MSC_REJECT) |
+ S(VGCS_EV_CLEANUP),
+ .out_state_mask = S(VGCS_CALL_ST_IDLE) |
+ S(VGCS_CALL_ST_BLOCKED) |
+ S(VGCS_CALL_ST_NULL),
+ .action = vgcs_call_fsm_busy,
+ },
+ [VGCS_CALL_ST_BLOCKED] = {
+ .name = "BLOCKED",
+ .in_event_mask = S(VGCS_EV_CALLING_ASSIGNED) |
+ S(VGCS_EV_TALKER_REL) |
+ S(VGCS_EV_MSC_RELEASE) |
+ S(VGCS_EV_CLEANUP),
+ .out_state_mask = S(VGCS_CALL_ST_IDLE) |
+ S(VGCS_CALL_ST_BUSY) |
+ S(VGCS_CALL_ST_NULL),
+ .action = vgcs_call_fsm_blocked,
+ },
+};
+
+static struct osmo_fsm vgcs_call_fsm = {
+ .name = "vgcs_call",
+ .states = vgcs_call_fsm_states,
+ .num_states = ARRAY_SIZE(vgcs_call_fsm_states),
+ .log_subsys = DASCI,
+ .event_names = vgcs_fsm_event_names,
+ .cleanup = vgcs_call_detach_and_destroy,
+};
+
+/* Handle VGCS/VBS SETUP message.
+ *
+ * See 3GPP TS 48.008 ยง3.2.1.50
+ */
+int vgcs_vbs_call_start(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ int payload_length = msg->tail - msg->l4h;
+ struct tlv_parsed tp;
+ struct gsm_subscriber_connection *c;
+ struct gsm0808_group_callref *gc = &conn->vgcs_call.gc_ie;
+ int rc;
+ uint8_t cause;
+
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS;
+ goto reject;
+ }
+
+ /* Check for mandatory Group Call Reference. */
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Mandatory group call reference not present.\n");
+ cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ goto reject;
+ }
+
+ /* Decode Group Call Reference. */
+ rc = gsm0808_dec_group_callref(gc, TLVP_VAL(&tp, GSM0808_IE_GROUP_CALL_REFERENCE),
+ TLVP_LEN(&tp, GSM0808_IE_GROUP_CALL_REFERENCE));
+ if (rc < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode group call reference.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+ conn->vgcs_call.sf = gc->sf;
+ conn->vgcs_call.call_ref = (osmo_load32be_ext_2(gc->call_ref_hi, 3) << 3) | gc->call_ref_lo;
+
+ /* Check for duplicated callref. */
+ llist_for_each_entry(c, &conn->network->subscr_conns, entry) {
+ if (!c->vgcs_call.fi)
+ continue;
+ if (c == conn)
+ continue;
+ if (conn->vgcs_call.sf == c->vgcs_call.sf
+ && conn->vgcs_call.call_ref == c->vgcs_call.call_ref) {
+ LOG_CALL(conn, LOGL_ERROR, "A %s call with callref %s already exists.\n",
+ (conn->vgcs_call.sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS",
+ gsm44068_group_id_string(conn->vgcs_call.call_ref));
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+ }
+
+ /* Decode VGCS Feature Flags */
+ if (TLVP_PRESENT(&tp, GSM0808_IE_VGCS_FEATURE_FLAGS)) {
+ rc = gsm0808_dec_vgcs_feature_flags(&conn->vgcs_call.ff,
+ TLVP_VAL(&tp, GSM0808_IE_VGCS_FEATURE_FLAGS),
+ TLVP_LEN(&tp, GSM0808_IE_VGCS_FEATURE_FLAGS));
+ if (rc < 0) {
+ LOG_CALL(conn, LOGL_ERROR, "Unable to decode feature flags.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+ conn->vgcs_call.ff_present = true;
+ }
+
+ /* Create VGCS FSM. */
+ conn->vgcs_call.fi = osmo_fsm_inst_alloc(&vgcs_call_fsm, conn->network, conn, LOGL_DEBUG, NULL);
+ if (!conn->vgcs_call.fi) {
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+
+ /* Init list of cells that are used by the call. */
+ INIT_LLIST_HEAD(&conn->vgcs_call.chan_list);
+
+ /* Init L3 queue. */
+ INIT_LLIST_HEAD(&conn->vgcs_call.l3_queue);
+
+ osmo_fsm_inst_dispatch(conn->vgcs_call.fi, VGCS_EV_SETUP, NULL);
+ return 0;
+reject:
+ bsc_tx_setup_refuse(conn, cause);
+ return -EINVAL;
+}
+
+/*
+ * VGCS chan FSM
+ */
+
+static void vgcs_chan_detach_and_destroy(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ if (conn->vgcs_chan.fi->state != VGCS_CHAN_ST_WAIT_EST) {
+ /* Remove call from notification channel. */
+ if (conn->lchan)
+ rsl_notification_cmd(conn->lchan->ts->trx->bts, NULL, &conn->vgcs_chan.gc_ie, NULL);
+ else
+ LOG_CHAN(conn, LOGL_ERROR, "Unable to remove notification, lchan is already gone.\n");
+ }
+
+ /* Detach from call, if not already. */
+ if (conn->vgcs_chan.call) {
+ llist_del(&conn->vgcs_chan.list);
+ conn->vgcs_chan.call = NULL;
+ }
+
+ /* Remove pointer of FSM. */
+ conn->vgcs_chan.fi = NULL;
+}
+
+static void uplink_released(struct gsm_subscriber_connection *conn)
+{
+ LOG_CHAN(conn, LOGL_DEBUG, "Uplink is now released.\n");
+ /* Go into blocked or free state. */
+ if (conn->vgcs_chan.call && conn->vgcs_chan.call->vgcs_call.fi
+ && conn->vgcs_chan.call->vgcs_call.fi->state == VGCS_CALL_ST_IDLE)
+ osmo_fsm_inst_state_chg(conn->vgcs_chan.fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
+ else
+ osmo_fsm_inst_state_chg(conn->vgcs_chan.fi, VGCS_CHAN_ST_ACTIVE_BLOCKED, 0, 0);
+}
+
+static void vgcs_chan_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ struct lchan_activate_info info;
+
+ switch (event) {
+ case VGCS_EV_ASSIGN_REQ:
+ LOG_CHAN(conn, LOGL_DEBUG, "MSC assigns channel.\n");
+ /* MSC requests channel assignment. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_WAIT_EST, 0, 0);
+ /* Requesting channel from BTS. */
+ info = (struct lchan_activate_info){
+ .activ_for = ACTIVATE_FOR_VGCS_CHANNEL,
+ .for_conn = conn,
+ .chreq_reason = GSM_CHREQ_REASON_OTHER,
+ .ch_mode_rate = conn->vgcs_chan.ch_mode_rate,
+ .ch_indctr = conn->vgcs_chan.ct.ch_indctr,
+ /* TSC is used from TS config. */
+ .encr = conn->vgcs_chan.new_lchan->encr,
+ /* Timing advance of 0 is used until channel is activated for uplink. */
+ .ta_known = true,
+ .ta = 0,
+ };
+ if (conn->vgcs_chan.call->vgcs_call.sf == GSM0808_SF_VGCS)
+ info.type_for = LCHAN_TYPE_FOR_VGCS;
+ else
+ info.type_for = LCHAN_TYPE_FOR_VBS;
+ /* Activate lchan. If an error occurs, this the function call may trigger VGCS_EV_LCHAN_ERROR event.
+ * This means that this must be the last action in this handler. */
+ lchan_activate(conn->vgcs_chan.new_lchan, &info);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_chan_fsm_wait_est(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ const struct mgcp_conn_peer *mgw_info;
+
+ switch (event) {
+ case VGCS_EV_LCHAN_ACTIVE:
+ LOG_CHAN(conn, LOGL_DEBUG, "lchan is active.\n");
+ /* If no MGW is used. */
+ if (!gscon_is_aoip(conn)) {
+ LOG_CHAN(conn, LOGL_DEBUG, "Not connecting MGW endpoint, no AoIP connection.\n");
+ goto no_aoip;
+ }
+ /* Send activation to MGW. */
+ LOG_CHAN(conn, LOGL_DEBUG, "Connecting MGW endpoint to the MSC's RTP port: %s:%u\n",
+ conn->vgcs_chan.msc_rtp_addr, conn->vgcs_chan.msc_rtp_port);
+ /* Connect MGW. The function call may trigger VGCS_EV_MGW_OK event.
+ * This means that this must be the last action in this handler.
+ * If this function fails, VGCS_EV_MGW_FAIL will not trigger. */
+ if (!gscon_connect_mgw_to_msc(conn,
+ conn->vgcs_chan.new_lchan,
+ conn->vgcs_chan.msc_rtp_addr,
+ conn->vgcs_chan.msc_rtp_port,
+ fi,
+ VGCS_EV_MGW_OK,
+ VGCS_EV_MGW_FAIL,
+ NULL,
+ NULL)) {
+ /* Report failure to MSC. */
+ bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ break;
+ }
+ break;
+ case VGCS_EV_LCHAN_ERROR:
+ LOG_CHAN(conn, LOGL_DEBUG, "lchan failed.\n");
+ /* BTS reports failure on channel request. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_NULL, 0, 0);
+ /* Add/update SI10. */
+ if (conn->vgcs_chan.call)
+ si10_update(conn->vgcs_chan.call);
+ /* Report failure to MSC. */
+ bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ break;
+ case VGCS_EV_MGW_OK:
+ LOG_CHAN(conn, LOGL_DEBUG, "MGW endpoint connected.\n");
+ /* MGW reports success. */
+ mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc);
+ if (!mgw_info) {
+ LOG_CHAN(conn, LOGL_ERROR, "Unable to retrieve RTP port info allocated by MGW for"
+ " the MSC side.");
+ /* Report failure to MSC. */
+ bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ break;
+ }
+ LOG_CHAN(conn, LOGL_DEBUG, "MGW's MSC side CI: %s:%u\n", mgw_info->addr, mgw_info->port);
+no_aoip:
+ /* Channel established from BTS. */
+ gscon_change_primary_lchan(conn, conn->vgcs_chan.new_lchan);
+ /* Change state according to call state. */
+ if (conn->vgcs_chan.call && conn->vgcs_chan.call->vgcs_call.fi
+ && conn->vgcs_chan.call->vgcs_call.fi->state == VGCS_CALL_ST_IDLE)
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
+ else
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_BLOCKED, 0, 0);
+ if (conn->vgcs_chan.call) {
+ /* Add call to notification channel. */
+ rsl_notification_cmd(conn->lchan->ts->trx->bts, conn->lchan, &conn->vgcs_chan.gc_ie, NULL);
+ /* Add/update SI10. */
+ si10_update(conn->vgcs_chan.call);
+ }
+ /* Report result to MSC. */
+ bsc_tx_vgcs_vbs_assignment_result(conn, &conn->vgcs_chan.ct, &conn->vgcs_chan.ci,
+ conn->vgcs_chan.call_id);
+ break;
+ case VGCS_EV_MGW_FAIL:
+ LOG_CHAN(conn, LOGL_DEBUG, "MGW endpoint failed.\n");
+ /* MGW reports failure. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_NULL, 0, 0);
+ /* Add/update SI10. */
+ if (conn->vgcs_chan.call)
+ si10_update(conn->vgcs_chan.call);
+ /* Report failure to MSC. */
+ bsc_tx_vgcs_vbs_assignment_fail(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
+ break;
+ case VGCS_EV_CLEANUP:
+ LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
+ /* MSC wants to terminate. */
+ osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
+ break;
+ case VGCS_EV_BLOCK:
+ case VGCS_EV_UNBLOCK:
+ /* Ignore, because channel is not yet ready. */
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_chan_fsm_active_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv, *cc;
+
+ switch (event) {
+ case VGCS_EV_UNBLOCK:
+ LOG_CHAN(conn, LOGL_DEBUG, "Unblocking channel.\n");
+ /* No uplink is used in other cell. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
+ break;
+ case VGCS_EV_TALKER_DET:
+ LOG_CHAN(conn, LOGL_DEBUG, "Talker detected on blocked channel.\n");
+ if (conn->vgcs_chan.call->vgcs_call.sf == GSM0808_SF_VBS)
+ LOG_CHAN(conn, LOGL_ERROR, "Talker detection not allowed on VBS channel.\n");
+ /* Race condition: BTS detected a talker. Waiting for talker to establish or fail. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0);
+ break;
+ case VGCS_EV_TALKER_EST:
+ cc = find_calling_subscr_conn(conn);
+ if (!cc) {
+ LOG_CHAN(conn, LOGL_ERROR, "No assignment requested from MSC!\n");
+ /* Uplink is used while blocked. Waiting for channel to be release. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0);
+ /* Send UPLINK RELEASE to MS. */
+ gsm48_send_uplink_release(conn->lchan, GSM48_RR_CAUSE_NORMAL);
+ /* Go into blocked or free state. */
+ uplink_released(conn);
+ break;
+ }
+ /* Talker is assigning to this channel. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_EST, 0, 0);
+ /* Report talker detection to call state machine. */
+ if (conn->vgcs_chan.call)
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_CALLING_ASSIGNED, conn);
+ /* Repeat notification for the MS that has been assigned. */
+ rsl_notification_cmd(conn->lchan->ts->trx->bts, conn->lchan, &conn->vgcs_chan.gc_ie, NULL);
+ break;
+ case VGCS_EV_CLEANUP:
+ LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
+ /* MSC wants to terminate. */
+ osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_chan_fsm_enter_active_free(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ /* Send UPLINK FREE message to BTS. This hits on every state change (and or timer start). */
+ LOG_CHAN(conn, LOGL_DEBUG, "Sending UPLINK FREE message to channel.\n");
+ gsm48_send_uplink_free(conn->lchan, 0, NULL);
+}
+
+static void vgcs_chan_fsm_active_free(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ switch (event) {
+ case VGCS_EV_BLOCK:
+ LOG_CHAN(conn, LOGL_DEBUG, "Blocking channel.\n");
+ /* Uplink is used in other cell. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_BLOCKED, 0, 0);
+ /* Send UPLINK BUSY to MS. */
+ LOG_CHAN(conn, LOGL_DEBUG, "Sending UPLINK BUSY message to channel.\n");
+ gsm48_send_uplink_busy(conn->lchan);
+ break;
+ case VGCS_EV_TALKER_DET:
+ LOG_CHAN(conn, LOGL_DEBUG, "Talker detected on free channel.\n");
+ /* BTS detected a talker. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_INIT, 0, 0);
+ /* Report talker detection to call state machine. */
+ if (conn->vgcs_chan.call)
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_DET, data);
+ break;
+ case VGCS_EV_CLEANUP:
+ LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
+ /* MSC wants to terminate. */
+ osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_chan_fsm_active_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ uint8_t cause = (data) ? *(uint8_t *)data : 0;
+
+ switch (event) {
+ case VGCS_EV_BLOCK:
+ case VGCS_EV_REJECT:
+ LOG_CHAN(conn, LOGL_DEBUG, "Blocking/rejecting channel.\n");
+ /* Uplink is used in other cell. Waiting for channel to be established and then released. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0);
+ break;
+ case VGCS_EV_TALKER_EST:
+ LOG_CHAN(conn, LOGL_DEBUG, "Talker established uplink.\n");
+ /* Uplink has been established */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_EST, 0, 0);
+ /* Report talker establishment to call state machine. */
+ if (conn->vgcs_chan.call)
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_EST, data);
+ break;
+ case VGCS_EV_TALKER_FAIL:
+ LOG_CHAN(conn, LOGL_NOTICE, "Uplink failed, establishment timeout.\n");
+ /* Release datalink */
+ rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
+ /* fall thru */
+ case VGCS_EV_TALKER_REL:
+ LOG_CHAN(conn, LOGL_DEBUG, "Uplink is now released.\n");
+ /* Uplink establishment failed. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
+ /* Report release indication to call state machine. */
+ if (conn->vgcs_chan.call)
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_REL, &cause);
+ break;
+ case VGCS_EV_CLEANUP:
+ LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
+ /* MSC wants to terminate. */
+ osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_chan_fsm_active_est(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+ uint8_t cause = (data) ? *(uint8_t *)data : 0;
+ struct msgb *msg = data;
+
+ switch (event) {
+ case VGCS_EV_BLOCK:
+ case VGCS_EV_REJECT:
+ LOG_CHAN(conn, LOGL_DEBUG, "Blocking/rejecting channel.\n");
+ /* Uplink is used in other cell. Waiting for channel to be release. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_REL, 0, 0);
+ /* Send UPLINK RELEASE to MS. */
+ gsm48_send_uplink_release(conn->lchan, GSM48_RR_CAUSE_NORMAL);
+ /* Go into blocked or free state. */
+ uplink_released(conn);
+ break;
+ case VGCS_EV_TALKER_DATA:
+ LOG_CHAN(conn, LOGL_DEBUG, "Talker sends data on uplink.\n");
+ if (msg) {
+ struct gsm48_hdr *gh;
+ uint8_t pdisc;
+ uint8_t msg_type;
+ if (msgb_l3len(msg) < sizeof(*gh)) {
+ LOG_LCHAN(msg->lchan, LOGL_ERROR,
+ "Message too short for a GSM48 header (%u)\n", msgb_l3len(msg));
+ break;
+ }
+ gh = msgb_l3(msg);
+ pdisc = gsm48_hdr_pdisc(gh);
+ msg_type = gsm48_hdr_msg_type(gh);
+ if (pdisc == GSM48_PDISC_RR && msg_type == GSM48_MT_RR_UPLINK_RELEASE) {
+ LOG_CHAN(conn, LOGL_DEBUG, "Uplink is released by UPLINK RELEASE message.\n");
+ /* Release datalink */
+ rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
+ /* Talker released the uplink. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
+ /* Report talker release to call state machine. */
+ if (conn->vgcs_chan.call) {
+ cause = GSM0808_CAUSE_CALL_CONTROL;
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_REL,
+ &cause);
+ }
+ break;
+ }
+ if (pdisc == GSM48_PDISC_RR && msg_type == GSM48_MT_RR_ASS_COMPL) {
+ LOG_CHAN(conn, LOGL_DEBUG, "Asssignment complete.\n");
+ struct gsm_subscriber_connection *cc;
+ cc = find_calling_subscr_conn(conn);
+ if (!cc) {
+ LOG_CHAN(conn, LOGL_ERROR, "No assignment requested from MSC!\n");
+ break;
+ }
+ LOG_CHAN(conn, LOGL_DEBUG, "Trigger State machine.\n");
+ osmo_fsm_inst_dispatch(cc->assignment.fi, ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE, msg);
+ break;
+ }
+ }
+ /* Report talker data to call state machine. */
+ if (conn->vgcs_chan.call)
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_DATA, data);
+ break;
+ case VGCS_EV_MSC_DTAP:
+ LOG_CHAN(conn, LOGL_DEBUG, "MSC sends DTAP message to talker.\n");
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, data);
+ break;
+ case VGCS_EV_TALKER_FAIL:
+ LOG_CHAN(conn, LOGL_NOTICE, "Uplink failed after establishment.\n");
+ /* Release datalink */
+ rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
+ /* fall thru */
+ case VGCS_EV_TALKER_REL:
+ LOG_CHAN(conn, LOGL_DEBUG, "Uplink is now released.\n");
+ /* Talker released the uplink. */
+ osmo_fsm_inst_state_chg(fi, VGCS_CHAN_ST_ACTIVE_FREE, 0, 0);
+ /* Report talker release to call state machine. */
+ if (conn->vgcs_chan.call)
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.call->vgcs_call.fi, VGCS_EV_TALKER_REL, &cause);
+ break;
+ case VGCS_EV_CLEANUP:
+ LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
+ /* MSC wants to terminate. */
+ osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void vgcs_chan_fsm_active_rel(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gsm_subscriber_connection *conn = fi->priv;
+
+ switch (event) {
+ case VGCS_EV_BLOCK:
+ case VGCS_EV_REJECT:
+ LOG_CHAN(conn, LOGL_DEBUG, "Blocking/rejecting channel.\n");
+ /* Race condition: Uplink is used in other cell, we are already releasing. */
+ break;
+ case VGCS_EV_TALKER_EST:
+ LOG_CHAN(conn, LOGL_DEBUG, "Talker established uplink, releasing.\n");
+ /* Finally the talker established the connection. Send UPLINK RELEASE to MS. */
+ gsm48_send_uplink_release(conn->lchan, GSM48_RR_CAUSE_NORMAL);
+ /* Release datalink */
+ rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
+ /* fall thru */
+ case VGCS_EV_TALKER_FAIL:
+ /* Release datalink */
+ rsl_release_request(conn->lchan, 0, RSL_REL_LOCAL_END);
+ /* fall thru */
+ case VGCS_EV_TALKER_REL:
+ /* Go into blocked or free state. */
+ uplink_released(conn);
+ break;
+ case VGCS_EV_CLEANUP:
+ LOG_CHAN(conn, LOGL_DEBUG, "SCCP connection clearing.\n");
+ /* MSC wants to terminate. */
+ osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static const struct osmo_fsm_state vgcs_chan_fsm_states[] = {
+ [VGCS_CHAN_ST_NULL] = {
+ .name = "NULL",
+ .in_event_mask = S(VGCS_EV_ASSIGN_REQ),
+ .out_state_mask = S(VGCS_CHAN_ST_WAIT_EST),
+ .action = vgcs_chan_fsm_null,
+ },
+ [VGCS_CHAN_ST_WAIT_EST] = {
+ .name = "WAIT_EST",
+ .in_event_mask = S(VGCS_EV_LCHAN_ACTIVE) |
+ S(VGCS_EV_LCHAN_ERROR) |
+ S(VGCS_EV_MGW_OK) |
+ S(VGCS_EV_MGW_FAIL) |
+ S(VGCS_EV_CLEANUP) |
+ S(VGCS_EV_BLOCK) |
+ S(VGCS_EV_UNBLOCK),
+ .out_state_mask = S(VGCS_CHAN_ST_NULL) |
+ S(VGCS_CHAN_ST_ACTIVE_BLOCKED) |
+ S(VGCS_CHAN_ST_ACTIVE_FREE),
+ .action = vgcs_chan_fsm_wait_est,
+ },
+ [VGCS_CHAN_ST_ACTIVE_BLOCKED] = {
+ .name = "ACTIVE/BLOCKED",
+ .in_event_mask = S(VGCS_EV_UNBLOCK) |
+ S(VGCS_EV_TALKER_DET) |
+ S(VGCS_EV_TALKER_EST) |
+ S(VGCS_EV_CLEANUP),
+ .out_state_mask = S(VGCS_CHAN_ST_NULL) |
+ S(VGCS_CHAN_ST_ACTIVE_EST) |
+ S(VGCS_CHAN_ST_ACTIVE_FREE) |
+ S(VGCS_CHAN_ST_ACTIVE_REL),
+ .action = vgcs_chan_fsm_active_blocked,
+ },
+ [VGCS_CHAN_ST_ACTIVE_FREE] = {
+ .name = "ACTIVE/FREE",
+ .in_event_mask = S(VGCS_EV_BLOCK) |
+ S(VGCS_EV_TALKER_DET) |
+ S(VGCS_EV_CLEANUP),
+ .out_state_mask = S(VGCS_CHAN_ST_NULL) |
+ S(VGCS_CHAN_ST_ACTIVE_BLOCKED) |
+ S(VGCS_CHAN_ST_ACTIVE_INIT) |
+ S(VGCS_CHAN_ST_ACTIVE_FREE),
+ .action = vgcs_chan_fsm_active_free,
+ .onenter = vgcs_chan_fsm_enter_active_free,
+ },
+ [VGCS_CHAN_ST_ACTIVE_INIT] = {
+ .name = "ACTIVE/INIT",
+ .in_event_mask = S(VGCS_EV_BLOCK) |
+ S(VGCS_EV_REJECT) |
+ S(VGCS_EV_TALKER_EST) |
+ S(VGCS_EV_TALKER_FAIL) |
+ S(VGCS_EV_CLEANUP),
+ .out_state_mask = S(VGCS_CHAN_ST_NULL) |
+ S(VGCS_CHAN_ST_ACTIVE_EST) |
+ S(VGCS_CHAN_ST_ACTIVE_REL) |
+ S(VGCS_CHAN_ST_ACTIVE_FREE),
+ .action = vgcs_chan_fsm_active_init,
+ },
+ [VGCS_CHAN_ST_ACTIVE_EST] = {
+ .name = "ACTIVE/ESTABLISHED",
+ .in_event_mask = S(VGCS_EV_BLOCK) |
+ S(VGCS_EV_REJECT) |
+ S(VGCS_EV_TALKER_DATA) |
+ S(VGCS_EV_MSC_DTAP) |
+ S(VGCS_EV_TALKER_REL) |
+ S(VGCS_EV_TALKER_FAIL) |
+ S(VGCS_EV_CLEANUP),
+ .out_state_mask = S(VGCS_CHAN_ST_NULL) |
+ S(VGCS_CHAN_ST_ACTIVE_FREE) |
+ S(VGCS_CHAN_ST_ACTIVE_REL),
+ .action = vgcs_chan_fsm_active_est,
+ },
+ [VGCS_CHAN_ST_ACTIVE_REL] = {
+ .name = "ACTIVE/RELEASE",
+ .in_event_mask = S(VGCS_EV_BLOCK) |
+ S(VGCS_EV_REJECT) |
+ S(VGCS_EV_TALKER_EST) |
+ S(VGCS_EV_TALKER_REL) |
+ S(VGCS_EV_TALKER_FAIL) |
+ S(VGCS_EV_CLEANUP),
+ .out_state_mask = S(VGCS_CHAN_ST_NULL) |
+ S(VGCS_CHAN_ST_ACTIVE_BLOCKED) |
+ S(VGCS_CHAN_ST_ACTIVE_FREE),
+ .action = vgcs_chan_fsm_active_rel,
+ },
+};
+
+static struct osmo_fsm vgcs_chan_fsm = {
+ .name = "vgcs_chan",
+ .states = vgcs_chan_fsm_states,
+ .num_states = ARRAY_SIZE(vgcs_chan_fsm_states),
+ .log_subsys = DASCI,
+ .event_names = vgcs_fsm_event_names,
+ .cleanup = vgcs_chan_detach_and_destroy,
+};
+
+/* Handle VGCS/VBS ASSIGNMENT REQUEST message.
+ *
+ * See 3GPP TS 48.008 ยง3.2.1.53
+ */
+int vgcs_vbs_chan_start(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+ int payload_length = msg->tail - msg->l4h;
+ struct tlv_parsed tp;
+ struct gsm_subscriber_connection *c;
+ struct gsm0808_group_callref *gc = &conn->vgcs_chan.gc_ie;
+ struct assignment_request req = {
+ .aoip = gscon_is_aoip(conn),
+ };
+ uint8_t cause;
+ struct gsm_bts *bts;
+ struct gsm_lchan *lchan = NULL;
+ int rc;
+ int i;
+
+ if (osmo_bssap_tlv_parse(&tp, msg->l4h + 1, payload_length - 1) < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "%s(): tlv_parse() failed\n", __func__);
+ cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS;
+ goto reject;
+ }
+
+ /* Check for mandatory IEs. */
+ if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)
+ || !TLVP_PRESENT(&tp, GSM0808_IE_ASSIGNMENT_REQUIREMENT)
+ || !TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER)
+ || !TLVP_PRESENT(&tp, GSM0808_IE_GROUP_CALL_REFERENCE)) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Mandatory IE not present.\n");
+ cause = GSM0808_CAUSE_INFORMATION_ELEMENT_OR_FIELD_MISSING;
+ goto reject;
+ }
+
+ /* Decode Channel Type element. */
+ rc = gsm0808_dec_channel_type(&conn->vgcs_chan.ct, TLVP_VAL(&tp, GSM0808_IE_CHANNEL_TYPE),
+ TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE));
+ if (rc < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Channel Type.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+
+ /* Only speech is supported. */
+ if (conn->vgcs_chan.ct.ch_indctr != GSM0808_CHAN_SPEECH) {
+ cause = GSM0808_CAUSE_INVALID_MESSAGE_CONTENTS;
+ goto reject;
+ }
+
+ /* Decode Assignment Requirement element. */
+ rc = gsm0808_dec_assign_req(&conn->vgcs_chan.ar, TLVP_VAL(&tp, GSM0808_IE_ASSIGNMENT_REQUIREMENT),
+ TLVP_LEN(&tp, GSM0808_IE_ASSIGNMENT_REQUIREMENT));
+ if (rc < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Assignment Requirement.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+
+ /* Decode Cell Identifier element. */
+ rc = gsm0808_dec_cell_id(&conn->vgcs_chan.ci, TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER),
+ TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER));
+ if (rc < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Cell Identifier.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+ gsm0808_cell_id_u_name(conn->vgcs_chan.ci_str, sizeof(conn->vgcs_chan.ci_str), conn->vgcs_chan.ci.id_discr,
+ &conn->vgcs_chan.ci.id);
+
+ /* Decode Group Call Reference element. */
+ rc = gsm0808_dec_group_callref(gc, TLVP_VAL(&tp, GSM0808_IE_GROUP_CALL_REFERENCE),
+ TLVP_LEN(&tp, GSM0808_IE_GROUP_CALL_REFERENCE));
+ if (rc < 0) {
+ LOGPFSML(conn->fi, LOGL_ERROR, "Unable to decode Group Call Reference.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+ conn->vgcs_chan.sf = gc->sf;
+ conn->vgcs_chan.call_ref = (osmo_load32be_ext_2(gc->call_ref_hi, 3) << 3) | gc->call_ref_lo;
+
+ /* Find BTS from Cell Identity. */
+ bts = gsm_bts_by_cell_id(conn->network, &conn->vgcs_chan.ci, 0);
+ if (!bts) {
+ LOG_CHAN(conn, LOGL_ERROR, "No cell found that matches the given Cell Identifier.\n");
+ cause = GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE;
+ goto reject;
+ }
+
+ /* If Cell Identity is ambiguous. */
+ if (gsm_bts_by_cell_id(conn->network, &conn->vgcs_chan.ci, 1))
+ LOG_CHAN(conn, LOGL_NOTICE, "More thant one cell found that match the given Cell Identifier.\n");
+
+ /* Decode channel related elements.
+ * This must be done after selecting the BTS, because codec selection requires relation to BTS. */
+ rc = bssmap_handle_ass_req_ct_speech(conn, bts, &tp, &conn->vgcs_chan.ct, &req, &cause);
+ if (rc < 0)
+ goto reject;
+
+ /* Store AoIP elements. */
+ osmo_strlcpy(conn->vgcs_chan.msc_rtp_addr, req.msc_rtp_addr, sizeof(conn->vgcs_chan.msc_rtp_addr));
+ conn->vgcs_chan.msc_rtp_port = req.msc_rtp_port;
+ if (TLVP_PRESENT(&tp, GSM0808_IE_CALL_ID)) {
+ /* Decode Call Identifier element. */
+ rc = gsm0808_dec_call_id(&conn->vgcs_chan.call_id, TLVP_VAL(&tp, GSM0808_IE_CALL_ID),
+ TLVP_LEN(&tp, GSM0808_IE_CALL_ID));
+ if (rc < 0) {
+ LOG_CHAN(conn, LOGL_ERROR, "Unable to decode Call Identifier.\n");
+ cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ goto reject;
+ }
+ }
+
+ /* Try to allocate a new lchan in order of preference. */
+ for (i = 0; i < req.n_ch_mode_rate; i++) {
+ lchan = lchan_select_by_chan_mode(bts,
+ req.ch_mode_rate_list[i].chan_mode,
+ req.ch_mode_rate_list[i].chan_rate,
+ SELECT_FOR_VGCS, NULL);
+ if (!lchan)
+ continue;
+ LOG_CHAN(conn, LOGL_DEBUG, "Selected new lchan %s for mode[%d] = %s channel_rate=%d\n",
+ gsm_lchan_name(lchan), i, gsm48_chan_mode_name(req.ch_mode_rate_list[i].chan_mode),
+ req.ch_mode_rate_list[i].chan_rate);
+
+ conn->vgcs_chan.ch_mode_rate = req.ch_mode_rate_list[i];
+ break;
+ }
+ if (!lchan) {
+ LOG_CHAN(conn, LOGL_ERROR, "Requested lchan not available.\n");
+ cause = GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE;
+ goto reject;
+ }
+ conn->vgcs_chan.new_lchan = lchan;
+
+ /* Create VGCS FSM. */
+ conn->vgcs_chan.fi = osmo_fsm_inst_alloc(&vgcs_chan_fsm, conn->network, conn, LOGL_DEBUG, NULL);
+ if (!conn->vgcs_chan.fi)
+ goto reject;
+
+ /* Attach to call control instance, if a call with same callref exists. */
+ llist_for_each_entry(c, &conn->network->subscr_conns, entry) {
+ if (!c->vgcs_call.fi)
+ continue;
+ if (c->vgcs_call.sf == conn->vgcs_chan.sf
+ && c->vgcs_call.call_ref == conn->vgcs_chan.call_ref) {
+ llist_add_tail(&conn->vgcs_chan.list, &c->vgcs_call.chan_list);
+ conn->vgcs_chan.call = c;
+ break;
+ }
+ }
+ if (!conn->vgcs_chan.call) {
+ LOG_CHAN(conn, LOGL_ERROR, "A %s call with callref %s does not exist.\n",
+ (conn->vgcs_chan.sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS",
+ gsm44068_group_id_string(conn->vgcs_chan.call_ref));
+ cause = GSM0808_CAUSE_VGCS_VBS_CALL_NON_EXISTENT;
+ osmo_fsm_inst_term(conn->vgcs_chan.fi, 0, NULL);
+ goto reject;
+ }
+
+ osmo_fsm_inst_dispatch(conn->vgcs_chan.fi, VGCS_EV_ASSIGN_REQ, NULL);
+ return 0;
+reject:
+ bsc_tx_vgcs_vbs_assignment_fail(conn, cause);
+ return -EINVAL;
+}
+
+/* Return lchan of group call that exists in the same BTS. */
+struct gsm_lchan *vgcs_vbs_find_lchan(struct gsm_bts *bts, struct gsm0808_group_callref *gc)
+{
+ struct gsm_subscriber_connection *call = NULL, *c;
+ struct gsm_lchan *lchan = NULL;
+ uint32_t call_ref = (osmo_load32be_ext_2(gc->call_ref_hi, 3) << 3) | gc->call_ref_lo;
+
+ /* Find group call. */
+ llist_for_each_entry(c, &bts->network->subscr_conns, entry) {
+ if (!c->vgcs_call.fi)
+ continue;
+ if (c->vgcs_call.sf == gc->sf
+ && c->vgcs_call.call_ref == call_ref) {
+ call = c;
+ break;
+ }
+ }
+ if (!call) {
+ LOGP(DASCI, LOGL_ERROR, "Cannot assign to channel, %s channel with callref %s does not exist.\n",
+ (gc->sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS", gsm44068_group_id_string(call_ref));
+ return NULL;
+ }
+
+ /* Find channel in same BTS. */
+ llist_for_each_entry(c, &call->vgcs_call.chan_list, vgcs_chan.list) {
+ if (c->lchan && c->lchan->ts->trx->bts == bts)
+ lchan = c->lchan;
+ }
+ if (!call) {
+ LOGP(DASCI, LOGL_ERROR, "Cannot assign to channel, caller's BTS has no %s channel with callref %s.\n",
+ (gc->sf == GSM0808_SF_VGCS) ? "VGCS" : "VBS", gsm44068_group_id_string(call_ref));
+ return NULL;
+ }
+
+ return lchan;
+}
diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am
index 914a6042d..ece993faa 100644
--- a/src/utils/Makefile.am
+++ b/src/utils/Makefile.am
@@ -9,10 +9,10 @@ AM_CFLAGS = \
$(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOGSM_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
+ $(LIBOSMONETIF_CFLAGS) \
+ $(LIBOSMOSIGTRAN_CFLAGS) \
$(COVERAGE_CFLAGS) \
$(SQLITE3_CFLAGS) \
- $(LIBOSMOSIGTRAN_CFLAGS) \
- $(LIBOSMOMGCPCLIENT_CFLAGS) \
$(NULL)
AM_LDFLAGS = \
@@ -28,17 +28,17 @@ bin_PROGRAMS = \
isdnsync \
meas_json \
$(NULL)
-if HAVE_SQLITE3
+if BUILD_MEAS_UDP2DB
bin_PROGRAMS += \
osmo-meas-udp2db \
$(NULL)
-if HAVE_PCAP
+endif
+if BUILD_MEAS_PCAP2DB
bin_PROGRAMS += \
osmo-meas-pcap2db \
$(NULL)
endif
-endif
-if HAVE_LIBCDK
+if BUILD_MEAS_VIS
bin_PROGRAMS += \
meas_vis \
$(NULL)
@@ -49,12 +49,7 @@ bs11_config_SOURCES = \
$(NULL)
bs11_config_LDADD = \
- $(top_builddir)/src/osmo-bsc/abis_nm.o \
- $(top_builddir)/src/osmo-bsc/bts_siemens_bs11.o \
- $(top_builddir)/src/osmo-bsc/e1_config.o \
- $(top_builddir)/src/osmo-bsc/gsm_data.o \
- $(top_builddir)/src/osmo-bsc/gsm_timers.o \
- $(top_builddir)/src/osmo-bsc/net_init.o \
+ $(top_builddir)/src/osmo-bsc/libbsc.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOABIS_LIBS) \
@@ -120,8 +115,7 @@ meas_json_SOURCES = \
$(NULL)
meas_json_LDADD = \
- $(top_builddir)/src/osmo-bsc/gsm_data.o \
- $(top_builddir)/src/osmo-bsc/gsm_timers.o \
+ $(top_builddir)/src/osmo-bsc/libbsc.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOABIS_LIBS) \
@@ -132,4 +126,3 @@ meas_json_CFLAGS = \
$(LIBOSMOGSM_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
$(NULL)
-
diff --git a/src/utils/bs11_config.c b/src/utils/bs11_config.c
index 7fac3e39a..e79507642 100644
--- a/src/utils/bs11_config.c
+++ b/src/utils/bs11_config.c
@@ -39,6 +39,7 @@
#include <osmocom/bsc/debug.h>
#include <osmocom/core/select.h>
#include <osmocom/bsc/rs232.h>
+#include <osmocom/bsc/bts.h>
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
#include <osmocom/abis/abis.h>
@@ -103,7 +104,7 @@ static int create_objects(struct gsm_bts *bts)
abis_nm_bs11_conn_oml_tei(bts, 0, 1, 0xff, TEI_OML);
abis_nm_bs11_set_trx_power(bts->c0, BS11_TRX_POWER_GSM_30mW);
-
+
sleep(1);
abis_nm_bs11_set_trx1_pw(bts, trx1_password);
@@ -136,7 +137,7 @@ static int create_trx1(struct gsm_bts *bts)
abis_nm_bs11_create_object(bts, BS11_OBJ_PA, 1,
sizeof(obj_pa0_attr), obj_pa0_attr);
abis_nm_bs11_set_trx_power(trx, BS11_TRX_POWER_GSM_30mW);
-
+
return 0;
}
@@ -201,7 +202,7 @@ static int swload_cbfn(unsigned int hook, unsigned int event, struct msgb *msg,
break;
case NM_MT_ACTIVATE_SW_ACK:
bs11cfg_state = STATE_NONE;
-
+
break;
case NM_MT_LOAD_SEG_ACK:
percent = abis_nm_software_load_status(g_bts);
@@ -232,6 +233,7 @@ static const struct value_string mbccu_load_names[] = {
{ 3, "Load BTSBBX" },
{ 4, "Load BTSARC" },
{ 5, "Load" },
+ { 8, "Not Equipped" },
{ 0, NULL }
};
@@ -281,7 +283,7 @@ static const char *bts_phase_name(uint8_t phase)
static const char *trx_power_name(uint8_t pwr)
{
switch (pwr) {
- case BS11_TRX_POWER_GSM_2W:
+ case BS11_TRX_POWER_GSM_2W:
return "2W (GSM)";
case BS11_TRX_POWER_GSM_250mW:
return "250mW (GSM)";
@@ -854,6 +856,10 @@ static void handle_options(int argc, char **argv)
if (optind+1 < argc)
value = argv[optind+1];
}
+ if (optind+2 < argc) {
+ fprintf(stderr, "Unsupported positional arguments on command line\n");
+ exit(2);
+ }
}
@@ -968,18 +974,3 @@ int main(int argc, char **argv)
exit(0);
}
-
-/* Stub */
-int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg)
-{
- return 0;
-}
-
-/* Stub */
-int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
-{
- return 0;
-}
-
-void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) {}
-int abis_rsl_rcvmsg(struct msgb *msg) { return 0; }
diff --git a/src/utils/meas_db.c b/src/utils/meas_db.c
index 7233dcda6..2f81524ae 100644
--- a/src/utils/meas_db.c
+++ b/src/utils/meas_db.c
@@ -29,10 +29,11 @@
#include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/bsc/meas_rep.h>
+#include <osmocom/bsc/meas_feed.h>
#include "meas_db.h"
-#define INS_MR "INSERT INTO meas_rep (time, imsi, name, scenario, nr, bs_power, ms_timing_offset, fpc, ms_l1_pwr, ms_l1_ta) VALUES (?,?,?,?,?,?,?,?,?,?)"
+#define INS_MR "INSERT INTO meas_rep (time, bts_nr, trx_nr, ts_nr, ss_nr, lchan_type, pchan_type, imsi, name, scenario, nr, bs_power, ms_timing_offset, fpc, ms_l1_pwr, ms_l1_ta) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
#define INS_UD "INSERT INTO meas_rep_unidir (meas_id, rx_lev_full, rx_lev_sub, rx_qual_full, rx_qual_sub, dtx, uplink) VALUES (?,?,?,?,?,?,?)"
#define UPD_MR "UPDATE meas_rep SET ul_unidir=?, dl_unidir=? WHERE id=?"
@@ -59,8 +60,6 @@ struct meas_db_state {
static int _insert_ud(struct meas_db_state *st, unsigned long meas_id, int dtx,
int uplink, const struct gsm_meas_rep_unidir *ud)
{
- unsigned long rowid;
-
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 1, meas_id));
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_ud, 2,
rxlev2dbm(ud->full.rx_lev)));
@@ -81,53 +80,57 @@ err_io:
}
/* insert a measurement report into the database */
-int meas_db_insert(struct meas_db_state *st, const char *imsi,
- const char *name, unsigned long timestamp,
- const char *scenario,
- const struct gsm_meas_rep *mr)
+int meas_db_insert(struct meas_db_state *st, unsigned long timestamp,
+ const struct meas_feed_meas *mfm)
{
- int rc;
sqlite3_int64 rowid, ul_rowid, dl_rowid;
SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 1, timestamp));
- if (imsi)
- SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 2,
- imsi, -1, SQLITE_STATIC));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 2, mfm->bts_nr));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 3, mfm->trx_nr));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 4, mfm->ts_nr));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 5, mfm->ss_nr));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 6, mfm->lchan_type));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 7, mfm->pchan_type));
+
+ if (mfm->imsi[0] != '\0')
+ SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 8,
+ mfm->imsi, -1, SQLITE_STATIC));
else
- SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 2));
+ SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 8));
- if (name)
- SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 3,
- name, -1, SQLITE_STATIC));
+ if (mfm->name[0] != '\0')
+ SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 9,
+ mfm->name, -1, SQLITE_STATIC));
else
- SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 3));
+ SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 9));
- if (scenario)
- SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 4,
- scenario, -1, SQLITE_STATIC));
+ if (mfm->scenario[0] != '\0')
+ SCK_OK(st->db, sqlite3_bind_text(st->stmt_ins_mr, 10,
+ mfm->scenario, -1, SQLITE_STATIC));
else
- SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 4));
+ SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 10));
- SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 5, mr->nr));
- SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 6, mr->bs_power));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 11, mfm->mr.nr));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 12, mfm->mr.bs_power_db / 2));
- if (mr->flags & MEAS_REP_F_MS_TO)
- SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 7, mr->ms_timing_offset));
+ if (mfm->mr.flags & MEAS_REP_F_MS_TO)
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 13, mfm->mr.ms_timing_offset));
else
- SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 7));
+ SCK_OK(st->db, sqlite3_bind_null(st->stmt_ins_mr, 13));
- if (mr->flags & MEAS_REP_F_FPC)
- SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 8, 1));
+ if (mfm->mr.flags & MEAS_REP_F_FPC)
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 14, 1));
else
- SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 8, 0));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 14, 0));
- if (mr->flags & MEAS_REP_F_MS_L1) {
- SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 9,
- mr->ms_l1.pwr));
- SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 10,
- mr->ms_l1.ta));
+ if (mfm->mr.flags & MEAS_REP_F_MS_L1) {
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 15,
+ mfm->mr.ms_l1.pwr));
+ SCK_OK(st->db, sqlite3_bind_int(st->stmt_ins_mr, 16,
+ mfm->mr.ms_l1.ta));
}
SCK_DONE(st->db, sqlite3_step(st->stmt_ins_mr));
@@ -136,14 +139,14 @@ int meas_db_insert(struct meas_db_state *st, const char *imsi,
rowid = sqlite3_last_insert_rowid(st->db);
/* insert uplink measurement */
- ul_rowid = _insert_ud(st, rowid, mr->flags & MEAS_REP_F_UL_DTX,
- 1, &mr->ul);
+ ul_rowid = _insert_ud(st, rowid, mfm->mr.flags & MEAS_REP_F_UL_DTX,
+ 1, &mfm->mr.ul);
SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 1, ul_rowid));
/* insert downlink measurement, if present */
- if (mr->flags & MEAS_REP_F_DL_VALID) {
- dl_rowid = _insert_ud(st, rowid, mr->flags & MEAS_REP_F_DL_DTX,
- 0, &mr->dl);
+ if (mfm->mr.flags & MEAS_REP_F_DL_VALID) {
+ dl_rowid = _insert_ud(st, rowid, mfm->mr.flags & MEAS_REP_F_DL_DTX,
+ 0, &mfm->mr.dl);
SCK_OK(st->db, sqlite3_bind_int(st->stmt_upd_mr, 2, dl_rowid));
} else
SCK_OK(st->db, sqlite3_bind_null(st->stmt_upd_mr, 2));
@@ -184,6 +187,12 @@ static const char *create_stmts[] = {
"CREATE TABLE IF NOT EXISTS meas_rep ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"time TIMESTAMP,"
+ "bts_nr INTEGER,"
+ "trx_nr INTEGER,"
+ "ts_nr INTEGER,"
+ "ss_nr INTEGER,"
+ "lchan_type INTEGER,"
+ "pchan_type INTEGER,"
"imsi TEXT,"
"name TEXT,"
"scenario TEXT,"
@@ -210,6 +219,12 @@ static const char *create_stmts[] = {
"SELECT "
"meas_rep.id, "
"datetime(time,'unixepoch') AS timestamp, "
+ "bts_nr,"
+ "trx_nr,"
+ "ts_nr,"
+ "ss_nr,"
+ "lchan_type,"
+ "pchan_type,"
"imsi, "
"name, "
"scenario, "
@@ -241,6 +256,12 @@ static const char *create_stmts[] = {
"SELECT "
"id,"
"timestamp,"
+ "bts_nr,"
+ "trx_nr,"
+ "ts_nr,"
+ "ss_nr,"
+ "lchan_type,"
+ "pchan_type,"
"imsi,"
"name,"
"scenario,"
@@ -257,7 +278,7 @@ static const char *create_stmts[] = {
static int check_create_tbl(struct meas_db_state *st)
{
- int i, rc;
+ int i;
for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
SCK_OK(st->db, sqlite3_exec(st->db, create_stmts[i],
diff --git a/src/utils/meas_db.h b/src/utils/meas_db.h
index 889e9022f..8f8a8c679 100644
--- a/src/utils/meas_db.h
+++ b/src/utils/meas_db.h
@@ -9,9 +9,7 @@ void meas_db_close(struct meas_db_state *st);
int meas_db_begin(struct meas_db_state *st);
int meas_db_commit(struct meas_db_state *st);
-int meas_db_insert(struct meas_db_state *st, const char *imsi,
- const char *name, unsigned long timestamp,
- const char *scenario,
- const struct gsm_meas_rep *mr);
+int meas_db_insert(struct meas_db_state *st, unsigned long timestamp,
+ const struct meas_feed_meas *mfm);
#endif
diff --git a/src/utils/meas_json.c b/src/utils/meas_json.c
index 365b450f4..953b114f3 100644
--- a/src/utils/meas_json.c
+++ b/src/utils/meas_json.c
@@ -34,6 +34,7 @@
#include <osmocom/core/socket.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
#include <osmocom/gsm/gsm_utils.h>
@@ -61,7 +62,7 @@ static void print_meas_rep_json(struct gsm_meas_rep *mr)
printf(", \"UL_MEAS\":{");
print_meas_rep_uni_json(&mr->ul);
printf("}");
- printf(", \"BS_POWER\":%d", mr->bs_power);
+ printf(", \"BS_POWER\":%d", mr->bs_power_db / 2);
if (mr->flags & MEAS_REP_F_MS_TO)
printf(", \"MS_TO\":%d", mr->ms_timing_offset);
@@ -98,7 +99,7 @@ static void print_chan_info_json(struct meas_feed_meas *mfm)
{
printf("\"lchan_type\":\"%s\", \"pchan_type\":\"%s\", "
"\"bts_nr\":%d, \"trx_nr\":%d, \"ts_nr\":%d, \"ss_nr\":%d",
- gsm_lchant_name(mfm->lchan_type), gsm_pchan_name(mfm->pchan_type),
+ gsm_chan_t_name(mfm->lchan_type), gsm_pchan_name(mfm->pchan_type),
mfm->bts_nr, mfm->trx_nr, mfm->ts_nr, mfm->ss_nr);
}
@@ -157,7 +158,7 @@ static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
{
int rc;
- if (what & BSC_FD_READ) {
+ if (what & OSMO_FD_READ) {
struct msgb *msg = msgb_alloc(1024, "UDP Rx");
rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
@@ -171,8 +172,21 @@ static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
return 0;
}
+/* default categories */
+static struct log_info_cat default_categories[] = {
+};
+
+static const struct log_info meas_json_log_info = {
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
+
int main(int argc, char **argv)
{
+
+ void *tall_ctx = talloc_named_const(NULL, 0, "meas_json");
+ osmo_init_logging2(tall_ctx, &meas_json_log_info);
+
int rc;
struct osmo_fd udp_ofd;
@@ -187,6 +201,3 @@ int main(int argc, char **argv)
exit(0);
}
-
-void ts_fsm_alloc(struct gsm_bts_trx_ts *ts) {}
-int abis_rsl_rcvmsg(struct msgb *msg) { return 0; }
diff --git a/src/utils/meas_pcap2db.c b/src/utils/meas_pcap2db.c
index db00fae49..eb69d506c 100644
--- a/src/utils/meas_pcap2db.c
+++ b/src/utils/meas_pcap2db.c
@@ -47,21 +47,13 @@ static struct meas_db_state *db;
static void handle_mfm(const struct pcap_pkthdr *h,
const struct meas_feed_meas *mfm)
{
- const char *scenario;
-
- if (strlen(mfm->scenario))
- scenario = mfm->scenario;
- else
- scenario = NULL;
-
- meas_db_insert(db, mfm->imsi, mfm->name, h->ts.tv_sec,
- scenario, &mfm->mr);
+ meas_db_insert(db, h->ts.tv_sec, mfm);
}
static void pcap_cb(u_char *user, const struct pcap_pkthdr *h,
const u_char *bytes)
{
- const char *cur = bytes;
+ const u_char *cur = bytes;
const struct iphdr *ip;
const struct udphdr *udp;
const struct meas_feed_meas *mfm;
diff --git a/src/utils/meas_udp2db.c b/src/utils/meas_udp2db.c
index 34f8385e8..0c97d8bf6 100644
--- a/src/utils/meas_udp2db.c
+++ b/src/utils/meas_udp2db.c
@@ -1,4 +1,4 @@
-/* liesten to meas_feed on UDP and write it to sqlite3 database */
+/* listen to meas_feed on UDP and write it to sqlite3 database */
/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
@@ -47,7 +47,6 @@ static int handle_msg(struct msgb *msg)
{
struct meas_feed_hdr *mfh = (struct meas_feed_hdr *) msgb_data(msg);
struct meas_feed_meas *mfm = (struct meas_feed_meas *) msgb_data(msg);
- const char *scenario;
time_t now = time(NULL);
if (mfh->version != MEAS_FEED_VERSION)
@@ -56,13 +55,7 @@ static int handle_msg(struct msgb *msg)
if (mfh->msg_type != MEAS_FEED_MEAS)
return -EINVAL;
- if (strlen(mfm->scenario))
- scenario = mfm->scenario;
- else
- scenario = NULL;
-
- meas_db_insert(db, mfm->imsi, mfm->name, now,
- scenario, &mfm->mr);
+ meas_db_insert(db, now, mfm);
return 0;
}
@@ -71,7 +64,7 @@ static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
{
int rc;
- if (what & BSC_FD_READ) {
+ if (what & OSMO_FD_READ) {
struct msgb *msg = msgb_alloc(1024, "UDP Rx");
rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
diff --git a/src/utils/meas_vis.c b/src/utils/meas_vis.c
index 851aa03d9..c3ee2a5e2 100644
--- a/src/utils/meas_vis.c
+++ b/src/utils/meas_vis.c
@@ -13,6 +13,8 @@
#include <osmocom/core/msgb.h>
#include <osmocom/core/select.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/application.h>
#include <osmocom/gsm/gsm_utils.h>
@@ -123,7 +125,7 @@ static int udp_fd_cb(struct osmo_fd *ofd, unsigned int what)
{
int rc;
- if (what & BSC_FD_READ) {
+ if (what & OSMO_FD_READ) {
struct msgb *msg = msgb_alloc(1024, "UDP Rx");
rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
@@ -191,7 +193,7 @@ void write_uni(struct ms_state *ms, struct ms_state_uni *msu,
if (dir == DIR_UL) {
pwr = ms->mr.ms_l1.pwr;
} else {
- pwr = ms->mr.bs_power;
+ pwr = ms->mr.bs_power_db / 2;
}
color = A_REVERSE | COLOR_PAIR(lev_col) | ' ';
@@ -203,7 +205,7 @@ void write_uni(struct ms_state *ms, struct ms_state_uni *msu,
snprintf(msu->label, sizeof(msu->label), "</%d>%1d<!%d> %3d %2u %2d %4u",
qual_col, lq->rx_qual, qual_col, pwr,
ms->mr.ms_l1.ta, ms->mr.ms_timing_offset,
- now - msu->last_update);
+ (unsigned int)(now - msu->last_update));
msu->cdk_label = newCDKLabel(g_st.cdkscreen, RIGHT, row,
msu->_lbl, 1, FALSE, FALSE);
}
@@ -258,23 +260,40 @@ const struct value_string col_strs[] = {
{ 0, NULL }
};
+/* default categories */
+static struct log_info_cat default_categories[] = {
+};
+
+static const struct log_info meas_vis_log_info = {
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
+
int main(int argc, char **argv)
{
int rc;
char *header[1];
char *title[1];
+ struct log_target *stderr_target;
+
+ void *tall_ctx = talloc_named_const(NULL, 0, "meas_vis");
+ osmo_init_logging2(tall_ctx, &meas_vis_log_info);
msgb_talloc_ctx_init(NULL, 0);
- printf("sizeof(gsm_meas_rep)=%u\n", sizeof(struct gsm_meas_rep));
- printf("sizeof(meas_feed_meas)=%u\n", sizeof(struct meas_feed_meas));
+ printf("sizeof(gsm_meas_rep)=%zu\n", sizeof(struct gsm_meas_rep));
+ printf("sizeof(meas_feed_meas)=%zu\n", sizeof(struct meas_feed_meas));
+ g_st.udp_ofd.cb = udp_fd_cb;
+ rc = osmo_sock_init_ofd(&g_st.udp_ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
+ if (rc < 0)
+ exit(1);
INIT_LLIST_HEAD(&g_st.ms_list);
g_st.curses_win = initscr();
g_st.cdkscreen = initCDKScreen(g_st.curses_win);
initCDKColor();
- g_st.title = "OpenBSC link quality monitor";
+ g_st.title = "OsmoBSC link quality monitor";
title[0] = g_st.title;
g_st.cdk_title = newCDKLabel(g_st.cdkscreen, CENTER, 0, title, 1, FALSE, FALSE);
@@ -296,11 +315,6 @@ int main(int argc, char **argv)
exit(0);
#endif
- g_st.udp_ofd.cb = udp_fd_cb;
- rc = osmo_sock_init_ofd(&g_st.udp_ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 8888, OSMO_SOCK_F_BIND);
- if (rc < 0)
- exit(1);
-
while (1) {
osmo_select_main(0);
update_sliders();