aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc')
-rw-r--r--src/osmo-bsc/Makefile.am57
-rw-r--r--src/osmo-bsc/a_reset.c12
-rw-r--r--src/osmo-bsc/abis_nm.c418
-rw-r--r--src/osmo-bsc/abis_nm_vty.c5
-rw-r--r--src/osmo-bsc/abis_om2000.c800
-rw-r--r--src/osmo-bsc/abis_om2000_vty.c153
-rw-r--r--src/osmo-bsc/abis_osmo.c226
-rw-r--r--src/osmo-bsc/abis_rsl.c1742
-rw-r--r--src/osmo-bsc/acc.c153
-rw-r--r--src/osmo-bsc/arfcn_range_encode.c340
-rw-r--r--src/osmo-bsc/assignment_fsm.c594
-rw-r--r--src/osmo-bsc/bsc_ctrl.c (renamed from src/osmo-bsc/osmo_bsc_ctrl.c)1033
-rw-r--r--src/osmo-bsc/bsc_ctrl_commands.c501
-rw-r--r--src/osmo-bsc/bsc_ctrl_lookup.c32
-rw-r--r--src/osmo-bsc/bsc_init.c92
-rw-r--r--src/osmo-bsc/bsc_rf_ctrl.c92
-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.c567
-rw-r--r--src/osmo-bsc/bsc_subscriber.c214
-rw-r--r--src/osmo-bsc/bsc_vty.c5077
-rw-r--r--src/osmo-bsc/bssmap_reset.c30
-rw-r--r--src/osmo-bsc/bts.c1073
-rw-r--r--src/osmo-bsc/bts_ctrl.c1580
-rw-r--r--src/osmo-bsc/bts_ericsson_rbs2000.c52
-rw-r--r--src/osmo-bsc/bts_init.c2
-rw-r--r--src/osmo-bsc/bts_ipaccess_nanobts.c594
-rw-r--r--src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c328
-rw-r--r--src/osmo-bsc/bts_nokia_site.c31
-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.c107
-rw-r--r--src/osmo-bsc/bts_sm.c112
-rw-r--r--src/osmo-bsc/bts_sysmobts.c67
-rw-r--r--src/osmo-bsc/bts_trx.c227
-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_vty.c5090
-rw-r--r--src/osmo-bsc/cbch_scheduler.c8
-rw-r--r--src/osmo-bsc/cbsp_link.c414
-rw-r--r--src/osmo-bsc/chan_alloc.c71
-rw-r--r--src/osmo-bsc/chan_counts.c310
-rw-r--r--src/osmo-bsc/codec_pref.c129
-rw-r--r--src/osmo-bsc/data_rate_pref.c165
-rw-r--r--src/osmo-bsc/e1_config.c14
-rw-r--r--src/osmo-bsc/gsm_04_08_rr.c468
-rw-r--r--src/osmo-bsc/gsm_08_08.c132
-rw-r--r--src/osmo-bsc/gsm_data.c462
-rw-r--r--src/osmo-bsc/handover_ctrl.c216
-rw-r--r--src/osmo-bsc/handover_decision.c18
-rw-r--r--src/osmo-bsc/handover_decision_2.c1255
-rw-r--r--src/osmo-bsc/handover_fsm.c291
-rw-r--r--src/osmo-bsc/handover_logic.c111
-rw-r--r--src/osmo-bsc/handover_vty.c2
-rw-r--r--src/osmo-bsc/lb.c182
-rw-r--r--src/osmo-bsc/lchan.c150
-rw-r--r--src/osmo-bsc/lchan_fsm.c989
-rw-r--r--src/osmo-bsc/lchan_rtp_fsm.c149
-rw-r--r--src/osmo-bsc/lchan_select.c446
-rw-r--r--src/osmo-bsc/lcs_loc_req.c74
-rw-r--r--src/osmo-bsc/lcs_ta_req.c30
-rw-r--r--src/osmo-bsc/meas_feed.c102
-rw-r--r--src/osmo-bsc/meas_rep.c79
-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.c860
-rw-r--r--src/osmo-bsc/net_init.c93
-rw-r--r--src/osmo-bsc/nm_bb_transc_fsm.c172
-rw-r--r--src/osmo-bsc/nm_bts_fsm.c162
-rw-r--r--src/osmo-bsc/nm_bts_sm_fsm.c82
-rw-r--r--src/osmo-bsc/nm_channel_fsm.c76
-rw-r--r--src/osmo-bsc/nm_common_fsm.c60
-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.c158
-rw-r--r--src/osmo-bsc/osmo_bsc_bssap.c1105
-rw-r--r--src/osmo-bsc/osmo_bsc_filter.c18
-rw-r--r--src/osmo-bsc/osmo_bsc_lcls.c18
-rw-r--r--src/osmo-bsc/osmo_bsc_main.c472
-rw-r--r--src/osmo-bsc/osmo_bsc_mgcp.c129
-rw-r--r--src/osmo-bsc/osmo_bsc_msc.c354
-rw-r--r--src/osmo-bsc/osmo_bsc_sigtran.c206
-rw-r--r--src/osmo-bsc/paging.c749
-rw-r--r--src/osmo-bsc/pcu_sock.c725
-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.c899
-rw-r--r--src/osmo-bsc/smscb.c423
-rw-r--r--src/osmo-bsc/smscb_vty.c421
-rw-r--r--src/osmo-bsc/system_information.c701
-rw-r--r--src/osmo-bsc/timeslot_fsm.c242
-rw-r--r--src/osmo-bsc/vgcs_fsm.c1318
95 files changed, 29522 insertions, 12938 deletions
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index 573988549..10265235e 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -21,51 +21,63 @@ 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.c \
- arfcn_range_encode.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_data.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 \
@@ -75,20 +87,22 @@ osmo_bsc_SOURCES = \
meas_rep.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 \
@@ -96,15 +110,38 @@ osmo_bsc_SOURCES = \
pcu_sock.c \
penalty_timers.c \
bssmap_reset.c \
- rest_octets.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 = \
+ libbsc.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOVTY_LIBS) \
diff --git a/src/osmo-bsc/a_reset.c b/src/osmo-bsc/a_reset.c
index 23714992c..c2c89e37b 100644
--- a/src/osmo-bsc/a_reset.c
+++ b/src/osmo-bsc/a_reset.c
@@ -25,6 +25,7 @@
#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>
static void a_reset_tx_reset(void *data)
{
@@ -41,16 +42,18 @@ static void a_reset_tx_reset_ack(void *data)
static void a_reset_link_up(void *data)
{
struct bsc_msc_data *msc = data;
- LOGP(DMSC, LOGL_NOTICE, "(msc%d) BSSMAP assocation is up\n", msc->nr);
- osmo_stat_item_inc(msc->msc_statg->items[MSC_STAT_MSC_LINKS_ACTIVE], 1);
+ 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 void a_reset_link_lost(void *data)
{
struct bsc_msc_data *msc = data;
- LOGP(DMSC, LOGL_NOTICE, "(msc%d) BSSMAP assocation is down\n", msc->nr);
- osmo_stat_item_dec(msc->msc_statg->items[MSC_STAT_MSC_LINKS_ACTIVE], 1);
+ 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);
}
@@ -76,6 +79,7 @@ void a_reset_alloc(struct bsc_msc_data *msc, const char *name)
}
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 successfully received a reset acknowledge message */
diff --git a/src/osmo-bsc/abis_nm.c b/src/osmo-bsc/abis_nm.c
index 73dc2d018..afb7abc6b 100644
--- a/src/osmo-bsc/abis_nm.c
+++ b/src/osmo-bsc/abis_nm.c
@@ -49,12 +49,24 @@
#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
+/* 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)
{
if (!bts->model)
@@ -202,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));
@@ -214,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;
}
@@ -236,72 +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));
}
- if ((new_state.administrative != 0 && nm_state->administrative == 0) ||
- new_state.operational != nm_state->operational ||
- new_state.availability != nm_state->availability) {
+ 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 operational state of a given object in our in-memory data
+ /* 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;
+ *nm_state = nsd.new_state;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG, &nsd);
} else {
DEBUGPC(DNM, "(No State change detected)\n");
}
-#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);
- }
-#endif
return 0;
}
@@ -312,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);
- LOGP(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)
@@ -342,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) {
- LOGP(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 {
- LOGP(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;
@@ -367,8 +374,10 @@ struct nm_fail_rep_signal_data *abis_nm_fail_evt_rep_parse(struct msgb *mb, stru
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)
+ 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(&sd->tp, NM_ATT_ADD_TEXT)) {
const uint8_t *val = TLVP_VAL(&sd->tp, NM_ATT_ADD_TEXT);
@@ -522,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;
@@ -555,6 +564,45 @@ static inline const uint8_t *parse_attr_resp_info_unreported(const struct abis_o
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)
{
@@ -565,37 +613,22 @@ 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 "
- "(from %u to %zu bytes)\n", bts->nr, len, sizeof(bts->_features_data));
- len = sizeof(bts->_features_data);
- }
- /* 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);
+ 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));
}
-
- memcpy(bts->_features_data, TLVP_VAL(tp, NM_ATT_MANUF_ID), len);
-
- 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));
- }
+ /* 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 */
@@ -607,6 +640,7 @@ static int parse_attr_resp_info_attr(struct gsm_bts *bts, const struct gsm_bts_t
/* 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: */
@@ -663,23 +697,34 @@ static int parse_attr_resp_info(struct gsm_bts *bts, const struct gsm_bts_trx *t
/* 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;
+ trx = foh->obj_class == NM_OC_BASEB_TRANSC ?
+ gsm_bts_trx_num(bts, foh->obj_inst.trx_nr) : NULL;
+
DEBUGPFOH(DNM, foh, "Get Attributes Response\n");
- 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;
+ }
/* nanoBTS doesn't send Get Attribute Response Info, uses its own format */
if (bts->type != GSM_BTS_TYPE_NANOBTS)
@@ -687,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;
@@ -701,21 +752,30 @@ 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)) {
@@ -752,7 +812,11 @@ 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;
@@ -771,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);
@@ -866,16 +934,16 @@ 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))
@@ -924,9 +992,12 @@ 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))
LOGPC(DNM, LOGL_NOTICE, "CAUSE=%s\n",
abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
@@ -994,7 +1065,7 @@ static int abis_nm_rcvmsg_fom(struct msgb *mb)
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:
@@ -1023,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 (%s)\n", btstype2str(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;
}
@@ -1178,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");
}
}
@@ -1276,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;
}
@@ -1324,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;
}
@@ -1615,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;
@@ -1632,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:
- LOGP(DNM, LOGL_ERROR, "Software Load not properly implemented.\n");
+ LOGPMO(&bts->mo, DNM, LOGL_ERROR, "Software Load not properly implemented.\n");
return -1;
break;
}
@@ -1683,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;
@@ -1780,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);
}
@@ -1804,8 +1869,8 @@ int abis_nm_get_attr(struct gsm_bts *bts, uint8_t obj_class, uint8_t bts_nr, uin
struct msgb *msg;
if (bts->type != GSM_BTS_TYPE_OSMOBTS && bts->type != GSM_BTS_TYPE_NANOBTS) {
- LOGP(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;
}
@@ -1827,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;
- LOG_BTS(bts, DNM, LOGL_DEBUG, "Set BTS Attr\n");
+ 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);
@@ -1875,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;
@@ -1973,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.";
@@ -2005,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 {
@@ -2104,12 +2169,13 @@ int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb)
}
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;
@@ -2117,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);
@@ -2596,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;
@@ -2606,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];
@@ -2816,6 +2878,7 @@ 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);
@@ -2825,11 +2888,14 @@ static int abis_nm_rx_ipacc(struct msgb *msg)
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(sign_link->trx->bts, foh->obj_inst.trx_nr);
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
DEBUGPFOH(DNM, foh, "Rx IPACCESS(0x%02x): %s\n", foh->msg_type,
osmo_hexdump(foh->data, oh->length - sizeof(*foh)));
@@ -2909,17 +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:
- if (!trx)
- goto obj_inst_error;
- signal.trx = trx;
- 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:
- if (!trx)
- goto obj_inst_error;
- signal.trx = trx;
- 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:
@@ -2986,40 +3050,48 @@ static void rsl_connect_timeout(void *data)
LOG_TRX(trx, DRSL, LOGL_NOTICE, "RSL connection request timed out\n");
/* Fake an RSL CONNECT NACK message from the BTS. */
- signal.trx = trx;
- signal.msg_type = NM_MT_IPACC_RSL_CONNECT_NACK;
+ 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){};
+ }
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);
@@ -3034,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);
}
diff --git a/src/osmo-bsc/abis_nm_vty.c b/src/osmo-bsc/abis_nm_vty.c
index fe467fad1..bbd2d157f 100644
--- a/src/osmo-bsc/abis_nm_vty.c
+++ b/src/osmo-bsc/abis_nm_vty.c
@@ -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" \
diff --git a/src/osmo-bsc/abis_om2000.c b/src/osmo-bsc/abis_om2000.c
index a3f689add..69a86b54d 100644
--- a/src/osmo-bsc/abis_om2000.c
+++ b/src/osmo-bsc/abis_om2000.c
@@ -44,9 +44,22 @@
#include <osmocom/bsc/abis_om2000.h>
#include <osmocom/bsc/signal.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,
@@ -55,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;
}
@@ -310,6 +314,7 @@ enum abis_om2k_dei {
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 {
@@ -388,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 },
},
};
@@ -776,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;
@@ -849,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];
@@ -861,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;
@@ -917,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;
@@ -945,17 +953,11 @@ 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)
{
- struct gsm_nm_state *nm_state = mo2nm_state(bts, mo);
- struct gsm_nm_state new_state;
- struct nm_statechg_signal_data nsd;
bool has_enabled_state;
- if (!nm_state)
- return;
-
switch (mo->class) {
case OM2K_MO_CLS_CF:
case OM2K_MO_CLS_TRXC:
@@ -966,61 +968,132 @@ static void update_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo,
break;
}
- new_state = *nm_state;
switch (mo_state) {
case OM2K_MOSTATE_RESET:
- new_state.availability = NM_AVSTATE_POWER_OFF;
- break;
+ return NM_AVSTATE_POWER_OFF;
case OM2K_MOSTATE_STARTED:
- new_state.availability = has_enabled_state ? NM_AVSTATE_OFF_LINE : NM_AVSTATE_OK;
- break;
+ return has_enabled_state ? NM_AVSTATE_OFF_LINE : NM_AVSTATE_OK;
case OM2K_MOSTATE_ENABLED:
- new_state.availability = NM_AVSTATE_OK;
- break;
+ return NM_AVSTATE_OK;
case OM2K_MOSTATE_DISABLED:
- new_state.availability = NM_AVSTATE_POWER_OFF;
- break;
+ return NM_AVSTATE_POWER_OFF;
default:
- new_state.availability = NM_AVSTATE_DEGRADED;
- break;
+ 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 nm_statechg_signal_data nsd;
+ struct gsm_bts_trx *trx;
+
+ 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 = 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;
+
+ trx = gsm_bts_trx_num(bts, mo->inst);
+ if (!trx)
+ return;
- nm_state->operational = new_state.operational;
+ 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)
@@ -1041,8 +1114,8 @@ static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
/* Route through per-TRX OML Link to the appropriate TRX */
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;
@@ -1051,8 +1124,8 @@ static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
/* Route through per-TRX OML Link to the appropriate TRX */
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;
@@ -1066,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;
@@ -1085,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);
@@ -1102,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,
- uint16_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;
@@ -1111,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);
}
@@ -1157,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;
@@ -1168,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 */
@@ -1228,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);
@@ -1286,11 +1351,9 @@ 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);
@@ -1316,17 +1379,14 @@ int abis_om2k_tx_mctr_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.mctr.om2k_mo.addr,
- OM2K_MSGT_MCTR_CONF_REQ);
- DEBUGP(DNM, "Tx MO=%s %s\n",
- om2k_mo_name(&bts->rbs2000.mctr.om2k_mo.addr),
+ 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,
+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;
@@ -1335,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;
@@ -1358,7 +1417,7 @@ int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx)
/* 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, 0x02); /* A */
+ msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, trx->rbs2000.rx_diversity);
return abis_om2k_sendmsg(trx->bts, msg);
}
@@ -1400,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);
@@ -1428,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;
@@ -1438,11 +1494,9 @@ 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: */
@@ -1537,7 +1591,7 @@ 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 */
@@ -1606,7 +1660,7 @@ int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
}
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);
@@ -1620,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,
@@ -1633,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" },
@@ -1665,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)
@@ -1676,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;
}
@@ -1703,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;
}
@@ -1720,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);
}
@@ -1731,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);
@@ -1749,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 directory to WAIT_ENABLE_ACCEPT */
- 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;
#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
@@ -1826,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);
}
@@ -1841,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);
}
}
@@ -1855,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);
}
@@ -1868,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)
@@ -1880,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) |
@@ -1895,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),
@@ -1904,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,
@@ -1912,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,
@@ -1920,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,
@@ -1937,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,
@@ -1945,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,
@@ -1953,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,
@@ -1961,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,
},
@@ -1990,13 +2059,14 @@ 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;
@@ -2006,8 +2076,7 @@ struct osmo_fsm_inst *om2k_mo_fsm_start(struct osmo_fsm_inst *parent,
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;
@@ -2015,38 +2084,38 @@ 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:
@@ -2056,8 +2125,7 @@ int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo,
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:
@@ -2067,24 +2135,20 @@ int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo,
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;
@@ -2098,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,
@@ -2107,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" },
@@ -2130,6 +2198,7 @@ enum om2k_trx_state {
struct om2k_trx_fsm_priv {
struct gsm_bts_trx *trx;
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)
@@ -2137,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)
@@ -2148,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)
@@ -2159,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)
@@ -2171,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);
+ 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(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx,
- &ts->rbs2000.om2k_mo);
+ 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)
@@ -2184,16 +2245,11 @@ 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;
- /* notify TS is ready */
- ts = &otfp->trx->ts[otfp->cur_ts_nr];
- osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL);
-
/* next ? */
if (++otfp->cur_ts_nr < 8) {
/* iterate to the next timeslot */
ts = &otfp->trx->ts[otfp->cur_ts_nr];
- om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx,
- &ts->rbs2000.om2k_mo);
+ om2k_mo_fsm_start(&ts->rbs2000.om2k_mo);
} else {
/* only after all 8 TS */
osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_SEND_SI, 0, 0);
@@ -2212,55 +2268,117 @@ static void om2k_trx_s_send_si(struct osmo_fsm_inst *fi, uint32_t prev_state)
static void om2k_trx_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+ struct om2k_trx_fsm_priv *otfp = fi->priv;
+ 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_SEND_SI),
+ 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_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",
},
};
@@ -2276,41 +2394,68 @@ 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];
+ OSMO_ASSERT(!trx->rbs2000.trx_fi);
+
snprintf(idbuf, sizeof(idbuf), "%u-%u", trx->bts->nr, trx->nr);
- fi = osmo_fsm_inst_alloc_child_id(&om2k_trx_fsm, parent, term_event,
- idbuf);
+ 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,
@@ -2318,11 +2463,14 @@ enum om2k_bts_event {
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" },
@@ -2334,19 +2482,6 @@ static const struct value_string om2k_bts_events[] = {
{ 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_MCTR,
- OM2K_BTS_S_WAIT_TRX_LAPD,
- 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;
@@ -2358,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)
@@ -2372,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)
@@ -2383,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)
@@ -2396,10 +2532,8 @@ 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)
@@ -2411,13 +2545,10 @@ static void om2k_bts_s_wait_is(struct osmo_fsm_inst *fi, uint32_t event, void *d
/* 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(fi, OM2K_BTS_EVT_MCTR_DONE, bts->c0,
- &bts->rbs2000.mctr.om2k_mo);
+ 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);
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX_LAPD, TRX_LAPD_TIMEOUT, 0);
}
}
@@ -2425,8 +2556,7 @@ static void om2k_bts_s_wait_mctr(struct osmo_fsm_inst *fi, uint32_t event, void
{
OSMO_ASSERT(event == OM2K_BTS_EVT_MCTR_DONE);
- osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX_LAPD,
- TRX_LAPD_TIMEOUT, 0);
+ 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)
@@ -2436,11 +2566,10 @@ static void om2k_bts_s_wait_trx_lapd(struct osmo_fsm_inst *fi, uint32_t event, v
OSMO_ASSERT(event == OM2K_BTS_EVT_TRX_LAPD_UP);
- osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX,
- BTS_FSM_TIMEOUT, 0);
+ 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)
@@ -2452,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);
}
@@ -2460,34 +2589,50 @@ 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,
},
@@ -2495,35 +2640,41 @@ static const struct osmo_fsm_state om2k_bts_states[] = {
.in_event_mask = S(OM2K_BTS_EVT_IS_DONE),
.out_state_mask = S(OM2K_BTS_S_ERROR) |
S(OM2K_BTS_S_WAIT_MCTR) |
- S(OM2K_BTS_S_WAIT_TRX_LAPD),
+ 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_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),
+ .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",
},
};
@@ -2546,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
@@ -2587,7 +2751,7 @@ 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);
@@ -2709,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;
@@ -2734,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));
@@ -2743,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);
@@ -2786,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;
}
@@ -2816,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;
}
@@ -2830,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;
}
@@ -2872,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));
}
}
@@ -2890,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);
@@ -2920,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;
@@ -2996,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;
@@ -3027,21 +3177,28 @@ 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);
}
}
@@ -3049,20 +3206,31 @@ void abis_om2k_trx_init(struct gsm_bts_trx *trx)
/* 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);
- om2k_mo_init(&bts->rbs2000.mctr.om2k_mo, OM2K_MO_CLS_MCTR,
- bts->nr, 0xFF, 0); // FIXME: There can be multiple MCTRs ...
+ 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)
diff --git a/src/osmo-bsc/abis_om2000_vty.c b/src/osmo-bsc/abis_om2000_vty.c
index 206fd6b9e..76048071a 100644
--- a/src/osmo-bsc/abis_om2000_vty.c
+++ b/src/osmo-bsc/abis_om2000_vty.c
@@ -63,11 +63,6 @@ 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|tg|ts|tf|is|con|dp|mctr|cf|tx|rx)"
#define OM2K_OBJCLASS_VTY_HELP "TRX Controller\n" \
@@ -81,6 +76,7 @@ static int dummy_config_write(struct vty *v)
"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
@@ -475,10 +471,31 @@ DEFUN_USRATTR(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd,
return CMD_SUCCESS;
}
+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>",
- "Configure OM2K specific parameters\n"
+ OM2K_VTY_HELP
"Configure optional maximum protocol version to negotiate\n"
"Limit OML IWD version\n" "Limit RSL IWD version\n"
"Generation limit\n"
@@ -552,6 +569,33 @@ DEFUN_USRATTR(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",
@@ -631,6 +675,24 @@ 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;
@@ -656,10 +718,86 @@ void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts)
(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);
@@ -683,9 +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 858c683e1..49e8b52c3 100644
--- a/src/osmo-bsc/abis_rsl.c
+++ b/src/osmo-bsc/abis_rsl.c
@@ -44,6 +44,7 @@
#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/core/tdef.h>
@@ -55,8 +56,10 @@
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/bsc/bts.h>
-#define RSL_ALLOC_SIZE 1024
-#define RSL_ALLOC_HEADROOM 128
+#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)
@@ -72,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;
@@ -146,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);
@@ -160,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
@@ -193,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;
@@ -223,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;
@@ -242,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);
}
@@ -251,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);
@@ -262,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);
}
@@ -271,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)
@@ -279,28 +359,31 @@ 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);
- msg->dst = lchan->ts->trx->rsl_link;
+ /* BS Power Control Parameters (if supported by BTS model) */
+ add_power_control_params(msg, RSL_IE_BS_POWER_PARAM, lchan);
+
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan)
{
- struct gsm_bts_trx *trx = lchan->ts->trx;
- struct gsm_bts *bts = trx->bts;
struct abis_rsl_dchan_hdr *dh;
struct msgb *msg;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
LOG_LCHAN(lchan, LOGL_DEBUG, "Tx MS POWER CONTROL (ms_power_lvl=%" PRIu8 ")\n",
lchan->ms_power);
@@ -312,24 +395,23 @@ int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan)
dh->chan_nr = chan_nr;
msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
- /* indicate MS power control to be performed by BTS: */
- if (bts->type == GSM_BTS_TYPE_OSMOBTS)
- msgb_tl_put(msg, RSL_IE_MS_POWER_PARAM);
- /* else: Since IE MS_POWER_PARAM content is operator dependent, it's not
- known if non-osmocom BTS models will support an empty IE, so let's
- better skip sending it unless we know for sure what each expects. */
- msg->dst = 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;
@@ -337,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;
@@ -379,82 +485,96 @@ 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);
+}
- if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR)
+/* 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;
+
+ /* 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 */
@@ -466,10 +586,12 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref)
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),
@@ -478,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",
@@ -486,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 (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),
@@ -525,11 +645,15 @@ int rsl_tx_chan_activ(struct gsm_lchan *lchan, uint8_t act_type, uint8_t ho_ref)
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) {
@@ -541,21 +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);
- /* indicate MS power control to be performed by BTS: */
- if (bts->type == GSM_BTS_TYPE_OSMOBTS)
- msgb_tl_put(msg, RSL_IE_MS_POWER_PARAM);
- /* else: Since IE MS_POWER_PARAM content is operator dependent, it's not
- known if non-osmocom BTS models will support an empty IE, so let's
- better skip sending it unless we know for sure what each expects. */
+ 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;
+ }
+ }
- mr_config_for_bts(lchan, msg);
+ put_rep_acch_cap_ie(lchan, msg);
+ put_top_acch_cap_ie(lchan, &cm, msg);
- msg->dst = 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(&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);
}
@@ -567,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;
- rc = channel_mode_from_lchan(&cm, lchan);
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ return chan_nr;
+
+ rc = channel_mode_from_lchan(&cm, lchan, &lchan->modify.ch_mode_rate, lchan->modify.info.type_for);
if (rc < 0)
return rc;
@@ -582,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;
+ }
+ }
+
+ 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 = lchan->ts->trx->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
return abis_rsl_sendmsg(msg);
}
@@ -601,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);
@@ -623,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);
}
@@ -634,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));
@@ -652,13 +847,17 @@ 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);
}
@@ -667,14 +866,14 @@ 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);
@@ -693,8 +892,47 @@ int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group,
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);
}
@@ -713,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;
@@ -753,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)
@@ -766,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);
}
@@ -784,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);
}
@@ -824,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);
@@ -845,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);
@@ -864,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)
@@ -880,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",
@@ -888,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));
@@ -904,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)
@@ -1011,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");
@@ -1020,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) ||
@@ -1044,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)) {
@@ -1062,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);
@@ -1083,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;
@@ -1097,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);
@@ -1113,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;
@@ -1133,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",
@@ -1158,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 {
@@ -1177,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);
@@ -1190,7 +1707,7 @@ static int abis_rsl_rx_dchan(struct msgb *msg)
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:
@@ -1207,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;
}
@@ -1233,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));
@@ -1241,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);
@@ -1253,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 */
@@ -1269,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;
@@ -1344,25 +1941,29 @@ struct chan_rqd {
/* Handle packet channel rach requests */
static int rsl_rx_pchan_rqd(struct chan_rqd *rqd)
{
- 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 (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",rqd->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;
+
+ 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(rqd->bts, rqd_ta, rqd->ref.ra, fn, is_11bit,
@@ -1374,7 +1975,6 @@ static int rsl_rx_pchan_rqd(struct chan_rqd *rqd)
* requests from the queue to prevent the queue from growing indefinetly. */
static void reduce_rach_dos(struct gsm_bts *bts)
{
- int rlt = gsm_bts_get_radio_link_timeout(bts);
time_t timestamp_current = time(NULL);
struct chan_rqd *rqd;
struct chan_rqd *rqd_tmp;
@@ -1382,9 +1982,9 @@ static void reduce_rach_dos(struct gsm_bts *bts)
/* 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 radio link timeout we drop it. This also means that the
+ /* 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 > rlt) {
+ 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);
@@ -1446,13 +2046,56 @@ static int rsl_rx_chan_rqd(struct msgb *msg)
return -EINVAL;
}
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 */
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, neci=0x%02x, chreq_reason=0x%02x)\n",
- get_value_string(gsm_chreq_descs, rqd->reason), rqd->ref.ra, bts->network->neci, rqd->reason);
+ 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(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL]);
+ 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);
@@ -1479,21 +2122,14 @@ static struct gsm_lchan *get_any_lchan(struct gsm_bts *bts)
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_each_lchan(lchan, ts) {
+ ts_for_n_lchans(lchan, ts, ts->max_primary_lchans) {
if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H) {
- if (bts->chan_alloc_reverse) {
- if (lchan->fi->state == LCHAN_ST_ESTABLISHED)
+ if (lchan->fi->state == LCHAN_ST_ESTABLISHED) {
+ if (!lchan_est || bts->chan_alloc_chan_req_reverse)
lchan_est = lchan;
- else
- lchan_any = lchan;
} else {
- if (lchan->fi->state == LCHAN_ST_ESTABLISHED) {
- if (!lchan_est)
- lchan_est = lchan;
- } else {
- if (!lchan_any)
- lchan_any = lchan;
- }
+ if (!lchan_any || bts->chan_alloc_chan_req_reverse)
+ lchan_any = lchan;
}
}
}
@@ -1518,25 +2154,26 @@ static bool force_free_lchan_for_emergency(struct chan_rqd *rqd)
/* 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)) {
+ 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)) {
+ 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 initate a release on that lchan.
- * This will take a short amount of time. We need to come back and check regulary to see if we managed to
+ /* 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) {
- /* Pick any busy TCH/F or TCH/H lchan and inititate a channel
+ 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 = get_any_lchan(rqd->bts);
- if (!rqd->release_lchan) {
+ 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
@@ -1549,11 +2186,23 @@ static bool force_free_lchan_for_emergency(struct chan_rqd *rqd)
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(rqd->release_lchan), osmo_fsm_inst_state_name(rqd->release_lchan->fi));
-
- lchan_release(rqd->release_lchan, !!(rqd->release_lchan->conn), true, 0);
+ 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 {
- /* BTS is shutting down, give up... */
+ /* if BTS has shut down, give up... */
if (rqd->release_lchan->ts->fi->state == TS_ST_NOT_INITIALIZED)
return false;
@@ -1575,6 +2224,62 @@ static bool force_free_lchan_for_emergency(struct chan_rqd *rqd)
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;
@@ -1588,8 +2293,8 @@ void abis_rsl_chan_rqd_queue_poll(struct gsm_bts *bts)
/* Handle PDCH related rach requests (in case of BSC-co-located-PCU) */
if (rqd->reason == GSM_CHREQ_REASON_PDCH) {
- rsl_rx_pchan_rqd(rqd);
- return;
+ if (rsl_rx_pchan_rqd(rqd) == 0)
+ goto leave;
}
/* Ensure that emergency calls will get priority over regular calls, however releasing
@@ -1610,36 +2315,44 @@ void abis_rsl_chan_rqd_queue_poll(struct gsm_bts *bts)
* - If there is still no channel available, try a TCH/F.
*
*/
- 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 (spec violation by MS!)\n");
- rsl_tx_imm_ass_rej(bts, &rqd->ref);
- llist_del(&rqd->entry);
- talloc_free(rqd);
- return;
- }
- }
-
- /* 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 (rqd->reason != GSM_CHREQ_REASON_EMERG)
- lchan = lchan_select_by_type(bts, GSM_LCHAN_SDCCH);
- if (!lchan) {
- LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD: no resources for %s 0x%x, retrying with %s\n",
- 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) {
- LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD: no resources for %s 0x%x, retrying with %s\n",
- 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) {
- LOG_BTS(bts, DRSL, LOGL_NOTICE, "CHAN RQD: no resources for %s 0x%x\n",
- gsm_lchant_name(lctype), rqd->ref.ra);
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL]);
+ 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);
@@ -1651,27 +2364,65 @@ void abis_rsl_chan_rqd_queue_poll(struct gsm_bts *bts)
OSMO_ASSERT(lchan->rqd_ref);
*(lchan->rqd_ref) = rqd->ref;
- lchan->rqd_ta = rqd->ta;
LOG_LCHAN(lchan, LOGL_DEBUG, "MS: Channel Request: reason=%s ra=0x%02x ta=%d\n",
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);
+
+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 message */
memset(ia, 0, sizeof(*ia));
@@ -1679,11 +2430,23 @@ int rsl_tx_imm_assignment(struct gsm_lchan *lchan)
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 {
@@ -1697,12 +2460,12 @@ int rsl_tx_imm_assignment(struct gsm_lchan *lchan)
rc = rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (uint8_t *) ia);
if (!rc)
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_SUCCESSFUL]);
+ 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;
@@ -1710,22 +2473,20 @@ 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];
@@ -1740,8 +2501,8 @@ static int rsl_rx_ccch_load(struct msgb *msg)
busy_percent = 100;
}
- osmo_stat_item_set(sd.bts->bts_statg->items[BTS_STAT_RACH_BUSY], busy_percent);
- osmo_stat_item_set(sd.bts->bts_statg->items[BTS_STAT_RACH_ACCESS], access_percent);
+ 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);
}
@@ -1765,7 +2526,12 @@ static int rsl_rx_cbch_load(struct msgb *msg)
struct tlv_parsed tp;
uint8_t slot_count;
- rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh));
+ 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;
@@ -1788,7 +2554,7 @@ 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));
@@ -1800,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;
}
@@ -1810,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: ");
@@ -1827,7 +2597,7 @@ static int abis_rsl_rx_cchan(struct msgb *msg)
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(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_DELETE_IND]);
+ 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 */
@@ -1839,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;
}
@@ -1852,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;
}
@@ -1863,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;
}
@@ -1881,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;
@@ -1931,8 +2726,14 @@ 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);
@@ -1947,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,
@@ -1964,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:
@@ -2020,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:
@@ -2105,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.
@@ -2117,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);
}
@@ -2144,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;
}
@@ -2176,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);
}
@@ -2203,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)) {
@@ -2211,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);
@@ -2223,7 +3165,7 @@ 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\n");
@@ -2248,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);
@@ -2261,7 +3208,7 @@ 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\n");
@@ -2276,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));
@@ -2289,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: ");
@@ -2330,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;
}
@@ -2360,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);
}
@@ -2386,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";
@@ -2459,7 +3420,7 @@ 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);
@@ -2480,7 +3441,7 @@ int rsl_etws_pn_command(struct gsm_bts *bts, uint8_t chan_nr, const uint8_t *dat
msgb_tlv_put(msg, RSL_IE_SMSCB_MSG, len, data);
- msg->dst = bts->c0->rsl_link;
+ msg->dst = bts->c0->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
@@ -2506,7 +3467,7 @@ int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number,
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);
}
@@ -2520,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);
}
@@ -2536,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);
}
@@ -2553,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
index 06f96c625..80a35766c 100644
--- a/src/osmo-bsc/acc.c
+++ b/src/osmo-bsc/acc.c
@@ -73,9 +73,8 @@ static void acc_mgr_enable_rotation_cond(struct acc_mgr *acc_mgr)
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 (osmo_timer_pending(&acc_mgr->rotate_timer))
- osmo_timer_del(&acc_mgr->rotate_timer);
+ /* No rotation needed, disable rotation timer (if pending) */
+ osmo_timer_del(&acc_mgr->rotate_timer);
}
}
@@ -411,122 +410,47 @@ static void do_acc_ramping_step(void *data)
}
/* 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)
+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)
+ struct nm_running_chg_signal_data *nsd;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ if (signal != S_NM_RUNNING_CHG)
return 0;
-
- if (nsd->obj_class != NM_OC_RADIO_CARRIER)
+ 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;
-
- trx = nsd->obj;
-
- LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: administrative state %s -> %s\n",
- get_value_string(abis_nm_adm_state_names, nsd->old_state->administrative),
- get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative));
- LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: operational state %s -> %s\n",
- 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)
+ if (trx != trx->bts->c0)
return 0;
- /* RSL must already be up. We cannot send RACH system information to the BTS otherwise. */
- if (trx->rsl_link == NULL) {
- LOG_TRX(trx, DRSL, LOGL_DEBUG,
- "ACC RAMP: ignoring state change because RSL link is down\n");
- 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
- LOG_TRX(trx, DRSL, LOGL_DEBUG,
- "ACC RAMP: ignoring state change because TRX is "
- "transitioning into operational state '%s'\n",
- 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
- LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: ignoring state change "
- "because TRX is not usable\n");
- }
- break;
- case NM_STATE_LOCKED:
- case NM_STATE_SHUTDOWN:
- abort_ramping = true;
- break;
- case NM_STATE_NULL:
- default:
- LOG_TRX(trx, DRSL, LOGL_ERROR, "ACC RAMP: unrecognized administrative state '0x%x' "
- "reported for TRX 0\n", 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
- LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: ignoring state change "
- "because TRX is transitioning into administrative state '%s'\n",
- 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
- LOG_TRX(trx, DRSL, LOGL_DEBUG, "ACC RAMP: ignoring state change "
- "because TRX is in administrative state '%s'\n",
- 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:
- LOG_TRX(trx, DRSL, LOGL_ERROR, "ACC RAMP: unrecognized operational state '0x%x' "
- "reported for TRX 0\n", nsd->new_state->administrative);
- break;
+ 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);
}
-
- if (trigger_ramping)
- acc_ramp_trigger(acc_ramp);
- else if (abort_ramping)
- acc_ramp_abort(acc_ramp);
-
return 0;
}
@@ -548,7 +472,6 @@ void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts)
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);
- osmo_signal_register_handler(SS_NM, acc_ramp_nm_sig_cb, acc_ramp);
}
/*!
@@ -641,8 +564,12 @@ void acc_ramp_trigger(struct acc_ramp *acc_ramp)
*/
void acc_ramp_abort(struct acc_ramp *acc_ramp)
{
- if (osmo_timer_pending(&acc_ramp->step_timer))
- osmo_timer_del(&acc_ramp->step_timer);
+ 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/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 fde028ed9..5e98a28e8 100644
--- a/src/osmo-bsc/assignment_fsm.c
+++ b/src/osmo-bsc/assignment_fsm.c
@@ -32,15 +32,17 @@
#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);
@@ -49,10 +51,10 @@ struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi)
}
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=23042 },
+ [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.
@@ -72,7 +74,7 @@ static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = {
osmo_fsm_inst_state_name(fi), gsm0808_cause_name(cause), ## args); \
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 { \
@@ -80,10 +82,31 @@ static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = {
LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: %s %s\n", \
bsc_ctr_description[BSC_##counter].name, \
bsc_ctr_description[BSC_##counter].description); \
- rate_ctr_inc(&conn->network->bsc_ctrs->ctr[BSC_##counter]); \
- if (bts) \
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_##counter]); \
- } while(0)
+ 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) { \
@@ -94,21 +117,24 @@ static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = {
"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)
+ } 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. */
- osmo_mgcpc_ep_ci_dlcx(conn->assignment.created_ci_for_msc);
+ osmo_mgcpc_ep_ci_dlcx(ci);
}
conn->assignment = (struct assignment_fsm_data){
@@ -118,13 +144,18 @@ 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 {
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_FAILURE]);
- 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. */
@@ -134,7 +165,7 @@ static void on_assignment_failure(struct gsm_subscriber_connection *conn)
}
}
-static void _gsm0808_ass_compl_extend_osmux(struct msgb *msg, uint8_t cid)
+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);
@@ -155,30 +186,40 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn)
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 (!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;
+ 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);
- if (gscon_is_aoip(conn) && conn->assignment.req.use_osmux) {
+ /* 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,
@@ -187,19 +228,33 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn)
}
}
- /* 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->lchan->activate.info.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_compl2(lchan->abis_ip.ass_compl.rr_cause,
chosen_channel,
- lchan->encr.alg_id, perm_spch,
+ ALG_A5_NR_TO_BSSAP(lchan->encr.alg_a5_n), perm_spch,
addr_local_p, sc_ptr, NULL, lcls_get_status(conn));
if (!resp) {
@@ -208,11 +263,11 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn)
return;
}
- if (gscon_is_aoip(conn) && conn->assignment.requires_voice_stream &&
+ if (gscon_is_aoip(conn) && bsc_chan_ind_requires_rtp_stream(conn->assignment.ch_indctr) &&
conn->assignment.req.use_osmux)
- _gsm0808_ass_compl_extend_osmux(resp, osmux_cid);
+ bssap_extend_osmux(resp, osmux_cid);
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_COMPLETE]);
+ 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,
@@ -224,55 +279,77 @@ 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(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));
}
static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct channel_mode_and_rate *ch_mode_rate)
@@ -280,7 +357,7 @@ static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct chann
enum gsm48_chan_mode chan_mode = ch_mode_rate->chan_mode;
enum channel_rate chan_rate = ch_mode_rate->chan_rate;
- switch (chan_mode) {
+ switch (gsm48_chan_mode_to_non_vamos(chan_mode)) {
case GSM48_CMODE_SIGN:
switch (type) {
case GSM_LCHAN_TCH_F: return chan_rate == CH_RATE_FULL;
@@ -315,67 +392,58 @@ static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct chann
}
}
-void assignment_fsm_init()
+static __attribute__((constructor)) void assignment_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&assignment_fsm) == 0);
}
-static int check_requires_voice(bool *requires_voice, enum gsm48_chan_mode chan_mode)
+static int chan_mode_to_ch_indctr(enum gsm48_chan_mode chan_mode)
{
- *requires_voice = false;
-
- switch (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:
- *requires_voice = true;
- break;
+ return GSM0808_CHAN_SPEECH;
case GSM48_CMODE_SIGN:
- *requires_voice = false;
- break;
+ return GSM0808_CHAN_SIGN;
default:
return -EINVAL;
}
-
- return 0;
}
-/* Check if the incoming assignment requests requires a voice stream or not,
- * we will look at the preferred and the alternate channel mode and also make
- * sure that both are consistent. */
-static int check_requires_voice_stream(struct gsm_subscriber_connection *conn)
+/* 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)
{
- bool requires_voice_pref = false, requires_voice_alt;
struct assignment_request *req = &conn->assignment.req;
struct osmo_fsm_inst *fi = conn->fi;
- int i, rc;
-
- /* When the assignment request indicates that there is an alternate
- * rate available (e.g. "Full or Half rate channel, Half rate
- * preferred..."), then both must be either voice or either signalling,
- * a mismatch is not permitted */
+ int i;
+ int rc;
for (i = 0; i < req->n_ch_mode_rate; i++) {
- rc = check_requires_voice(&requires_voice_alt, req->ch_mode_rate[i].chan_mode);
+ 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[i].chan_mode));
+ gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode));
return -EINVAL;
}
- if (i==0)
- requires_voice_pref = requires_voice_alt;
- else if (requires_voice_alt != requires_voice_pref) {
+ if (rc != req->ch_indctr) {
assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP,
- "Inconsistent channel modes: %s != %s",
- gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode),
- gsm48_chan_mode_name(req->ch_mode_rate[i].chan_mode));
+ "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;
}
}
- conn->assignment.requires_voice_stream = requires_voice_pref;
return 0;
}
@@ -391,15 +459,69 @@ static bool reuse_existing_lchan(struct gsm_subscriber_connection *conn)
/* 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[i])) {
- conn->lchan->ch_mode_rate = req->ch_mode_rate[i];
- return true;
- }
+ 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)
{
@@ -409,7 +531,6 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts
[CH_RATE_FULL] = "FR",
};
struct osmo_fsm_inst *fi;
- struct lchan_activate_info info;
int i;
OSMO_ASSERT(conn);
@@ -417,8 +538,6 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts
OSMO_ASSERT(!conn->assignment.fi);
OSMO_ASSERT(!conn->assignment.new_lchan);
- assignment_count(CTR_ASSIGNMENT_ATTEMPTED);
-
fi = osmo_fsm_inst_alloc_child(&assignment_fsm, conn->fi, GSCON_EV_ASSIGNMENT_END);
OSMO_ASSERT(fi);
conn->assignment.fi = fi;
@@ -428,26 +547,28 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts
conn->assignment.req = *req;
req = &conn->assignment.req;
- /* Check if we need a voice stream. If yes, set the appropriate struct
- * members in conn */
- if (check_requires_voice_stream(conn) < 0)
+ assignment_count(CTR_ASSIGNMENT_ATTEMPTED);
+
+ if (check_chan_mode_rate_against_ch_indctr(conn) < 0)
return;
+ conn->assignment.ch_indctr = req->ch_indctr;
- /* There may be an already existing lchan, if yes, try to work with
- * the existing lchan. */
- if (reuse_existing_lchan(conn)) {
+ if (!req->target_lchan && reuse_existing_lchan(conn)) {
+ /* The already existing lchan is suitable for this mode */
+ conn->assignment.new_lchan = NULL;
/* 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->tch_mode == conn->lchan->ch_mode_rate.chan_mode) {
+ if (conn->lchan->current_ch_mode_rate.chan_mode == conn->assignment.selected_ch_mode_rate.chan_mode) {
LOG_ASSIGNMENT(conn, LOGL_DEBUG,
"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(conn->lchan->ch_mode_rate.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) {
@@ -462,39 +583,63 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts
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->lchan->ch_mode_rate.chan_mode),
+ gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode),
gsm_lchan_name(conn->lchan));
- info = (struct lchan_activate_info){
- .activ_for = FOR_ASSIGNMENT,
- .for_conn = conn,
- .chan_mode = conn->lchan->ch_mode_rate.chan_mode,
- .encr = conn->lchan->encr,
- .s15_s0 = conn->lchan->ch_mode_rate.s15_s0,
- .requires_voice_stream = conn->assignment.requires_voice_stream,
- .msc_assigned_cic = req->msc_assigned_cic,
- .re_use_mgw_endpoint_from_lchan = conn->lchan,
- };
-
- osmo_fsm_inst_dispatch(conn->lchan->fi, LCHAN_EV_REQUEST_MODE_MODIFY, &info);
-
- /* Since we opted not to allocate a new lchan, the new lchan is still the old lchan. */
- conn->assignment.new_lchan = conn->lchan;
-
- /* Also we need to skip the RR assignment, so we jump forward and wait for the lchan_fsm until it
- * reaches the established state again. */
- assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED);
-
+ assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED);
return;
}
- /* 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[i].chan_mode, req->ch_mode_rate[i].chan_rate);
- conn->lchan->ch_mode_rate = req->ch_mode_rate[i];
- if (conn->assignment.new_lchan)
+ 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
@@ -503,13 +648,13 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts
assignment_count_result(CTR_ASSIGNMENT_NO_CHANNEL);
assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
"BSSMAP Assignment Command:"
- " No lchan available for: pref=%s:%s / alt1=%s:%s / alt2=%s:%s\n",
- gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode),
- rate_names[req->ch_mode_rate[0].chan_rate],
- req->n_ch_mode_rate >= 1 ? gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode) : "",
- req->n_ch_mode_rate >= 1 ? rate_names[req->ch_mode_rate[0].chan_rate] : "",
- req->n_ch_mode_rate >= 2 ? gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode) : "",
- req->n_ch_mode_rate >= 2 ? rate_names[req->ch_mode_rate[0].chan_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;
}
@@ -517,33 +662,49 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts
assignment_fsm_update_id(conn);
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->lchan->ch_mode_rate.chan_mode),
- rate_names[conn->lchan->ch_mode_rate.chan_rate],
+ 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");
- assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE);
- info = (struct lchan_activate_info){
- .activ_for = FOR_ASSIGNMENT,
+ /* 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 = conn->lchan->ch_mode_rate.chan_mode,
+ .ch_mode_rate = conn->assignment.selected_ch_mode_rate,
.encr = conn->lchan->encr,
- .s15_s0 = conn->lchan->ch_mode_rate.s15_s0,
- .requires_voice_stream = conn->assignment.requires_voice_stream,
+ .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);
@@ -561,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);
@@ -628,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);
@@ -638,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,
@@ -692,19 +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_ESTABLISHED) /* MODE MODIFY */
+ | S(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED)
,
},
[ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = {
@@ -740,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),
@@ -754,9 +976,14 @@ 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:
@@ -765,11 +992,12 @@ void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, vo
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:
@@ -777,7 +1005,7 @@ 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(CTR_ASSIGNMENT_TIMEOUT);
@@ -785,7 +1013,7 @@ int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi)
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/osmo_bsc_ctrl.c b/src/osmo-bsc/bsc_ctrl.c
index 05bc86c2b..aff1d83e6 100644
--- a/src/osmo-bsc/osmo_bsc_ctrl.c
+++ b/src/osmo-bsc/bsc_ctrl.c
@@ -1,6 +1,9 @@
-/* (C) 2011 by Daniel Willmann <daniel@totalueberwachung.de>
- * (C) 2011 by Holger Hans Peter Freyther
+/*
+ * (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
@@ -17,235 +20,461 @@
* 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/bsc/ctrl.h>
-#include <osmocom/bsc/debug.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/osmo_bsc.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/signal.h>
-#include <osmocom/bsc/a_reset.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>
-#include <osmocom/core/linuxlist.h>
-#include <osmocom/core/signal.h>
+static int verify_net_apply_config_file(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ FILE *cfile;
-#include <osmocom/ctrl/control_if.h>
+ if (!cmd->value || cmd->value[0] == '\0')
+ return -1;
-#include <osmocom/gsm/protocol/ipaccess.h>
-#include <osmocom/gsm/ipa.h>
+ cfile = fopen(cmd->value, "r");
+ if (!cfile)
+ return -1;
-#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <unistd.h>
+ fclose(cfile);
-/* 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;
+ return 0;
}
-
-static int _ss7_as_send(struct osmo_ss7_as *as, struct msgb *msg)
+static int set_net_apply_config_file(struct ctrl_cmd *cmd, void *_data)
{
- struct osmo_ss7_asp *asp;
- unsigned int i;
+ 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;
+ }
- /* 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);
+ 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;
}
- msgb_free(msg);
- return -1;
+
+ 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");
-int bsc_sccplite_msc_send(struct bsc_msc_data *msc, struct msgb *msg)
+static int verify_net_write_config_file(struct ctrl_cmd *cmd, const char *value, void *_data)
{
- struct osmo_ss7_as *as;
+ 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;
- as = msc_get_ss7_as(msc);
- if (!as) {
- msgb_free(msg);
+ 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;
+}
- /* don't attempt to send CTRL on a non-SCCPlite AS */
- if (as->cfg.proto != OSMO_SS7_ASP_PROT_IPA)
- return 0;
+static int set_net_apply_config(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+ struct gsm_bts *bts;
- return _ss7_as_send(as, msg);
+ 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;
}
-/* 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)
+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)
{
- /* 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;
+ char *tmp, *saveptr, *mcc, *mnc;
+ int rc = 0;
- /* don't attempt to send CTRL on a non-SCCPlite ASP */
- if (asp->cfg.proto != OSMO_SS7_ASP_PROT_IPA)
- return 0;
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
- msg = ctrl_cmd_make(cmd);
- if (!msg)
- return -1;
+ mcc = strtok_r(tmp, ",", &saveptr);
+ mnc = strtok_r(NULL, ",", &saveptr);
- ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
- ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+ if (osmo_mcc_from_str(mcc, NULL) || osmo_mnc_from_str(mnc, NULL, NULL))
+ rc = -1;
- return osmo_ss7_asp_send(asp, msg);
+ talloc_free(tmp);
+ return rc;
}
-/* 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)
+static int set_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data)
{
- struct msgb *msg;
+ struct gsm_network *net = cmd->node;
+ char *tmp, *saveptr, *mcc_str, *mnc_str;
+ struct osmo_plmn_id plmn;
- msg = ctrl_cmd_make(cmd);
- if (!msg)
- return -1;
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
- ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
- ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+ mcc_str = strtok_r(tmp, ",", &saveptr);
+ mnc_str = strtok_r(NULL, ",", &saveptr);
- return bsc_sccplite_msc_send(msc, msg);
+ 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");
-/* 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)
+static int get_net_rf_lock(struct ctrl_cmd *cmd, void *data)
{
- struct ctrl_cmd *cmd;
- bool parse_failed;
- int rc;
+ struct gsm_network *net = cmd->node;
+ struct gsm_bts *bts;
+ const char *policy_name;
- /* caller has already ensured ipaccess_head + ipaccess_head_ext */
- OSMO_ASSERT(msg->l2h);
+ policy_name = osmo_bsc_rf_get_policy_name(net->rf_ctrl->policy);
- /* 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;
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ struct gsm_bts_trx *trx;
- /* handle the CTRL command */
- ctrl_cmd_handle(bsc_gsmnet->ctrl, cmd, bsc_gsmnet);
+ /* Exclude the BTS from the global lock */
+ if (bts->excl_from_rf_lock)
+ continue;
-send_reply:
- rc = sccplite_asp_ctrl_cmd_send(asp, cmd);
- talloc_free(cmd);
- return rc;
+ 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"
-void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_data *msc_data)
+static int set_net_rf_lock(struct ctrl_cmd *cmd, void *data)
{
- struct ctrl_cmd *trap;
- struct ctrl_handle *ctrl;
+ 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;
+ }
- ctrl = msc_data->network->ctrl;
+ rf = net->rf_ctrl;
- trap = ctrl_cmd_trap(cmd);
- if (!trap) {
+ if (!rf) {
+ cmd->reply = "RF Ctrl is not enabled in the BSC Configuration";
+ return CTRL_CMD_ERROR;
+ }
- LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n");
- return;
+ 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;
}
- ctrl_cmd_send_to_all(ctrl, trap);
- sccplite_msc_ctrl_cmd_send(msc_data, trap);
+ return CTRL_CMD_REPLY;
+}
- talloc_free(trap);
+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");
-CTRL_CMD_DEFINE_RO(msc_connection_status, "connection_status");
-static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data)
+static int get_net_bts_num(struct ctrl_cmd *cmd, void *data)
{
- struct bsc_msc_data *msc = (struct bsc_msc_data *)cmd->node;
+ struct gsm_network *net = cmd->node;
- if (msc == NULL) {
- cmd->reply = "msc not found";
+ 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;
}
- if (a_reset_conn_ready(msc))
- cmd->reply = "connected";
- else
- cmd->reply = "disconnected";
return CTRL_CMD_REPLY;
}
+CTRL_CMD_DEFINE_RO(net_rf_states, "rf_states");
-/* Backwards compat. */
-CTRL_CMD_DEFINE_RO(msc0_connection_status, "msc_connection_status");
-
-static int get_msc0_connection_status(struct ctrl_cmd *cmd, void *data)
+CTRL_CMD_DEFINE(net_timezone, "timezone");
+static int get_net_timezone(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;
+ struct gsm_network *net = (struct gsm_network *)cmd->node;
- cmd->node = msc;
- rc = get_msc_connection_status(cmd, data);
- cmd->node = old_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");
- return rc;
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
}
-static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data)
+static int set_net_timezone(struct ctrl_cmd *cmd, void *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;
+ 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;
- 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;
- }
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
- cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
- if (!cmd) {
- LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
- return 0;
+ 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;
+ }
}
- cmd->id = "0";
- cmd->variable = talloc_asprintf(cmd, "msc.%d.connection_status", msc->nr);
- cmd->node = msc;
+ tz->override = override;
- get_msc_connection_status(cmd, NULL);
- ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+ talloc_free(tmp);
+ tmp = NULL;
- if (msc->nr == 0) {
- /* Backwards compat. */
- cmd->variable = "msc_connection_status";
- ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
+ 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;
}
- talloc_free(cmd);
+ 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");
@@ -307,309 +536,231 @@ static int bts_connection_status_trap_cb(unsigned int subsys, unsigned int signa
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)
+CTRL_CMD_DEFINE_RO(msc_connection_status, "connection_status");
+static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data)
{
- struct ctrl_cmd *cmd;
- const char *oper, *admin, *policy;
+ struct bsc_msc_data *msc = (struct bsc_msc_data *)cmd->node;
- cmd = ctrl_cmd_create(msc, CTRL_TYPE_TRAP);
- if (!cmd) {
- LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n");
- return;
+ if (msc == NULL) {
+ cmd->reply = "msc not found";
+ return CTRL_CMD_ERROR;
}
-
- 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);
+ if (a_reset_conn_ready(msc))
+ cmd->reply = "connected";
+ else
+ cmd->reply = "disconnected";
+ return CTRL_CMD_REPLY;
}
-void bsc_gen_location_state_trap(struct gsm_bts *bts)
-{
- struct bsc_msc_data *msc;
-
- llist_for_each_entry(msc, &bts->network->mscs, entry)
- generate_location_state_trap(bts, msc);
-}
+/* Backwards compat. */
+CTRL_CMD_DEFINE_RO(msc0_connection_status, "msc_connection_status");
-static int location_equal(struct bts_location *a, struct bts_location *b)
+static int get_msc0_connection_status(struct ctrl_cmd *cmd, void *data)
{
- return ((a->tstamp == b->tstamp) && (a->valid == b->valid) && (a->lat == b->lat) &&
- (a->lon == b->lon) && (a->height == b->height));
-}
+ struct bsc_msc_data *msc = osmo_msc_data_find(bsc_gsmnet, 0);
+ void *old_node = cmd->node;
+ int rc;
-static void cleanup_locations(struct llist_head *locations)
-{
- struct bts_location *myloc, *tmp;
- int invalpos = 0, i = 0;
+ cmd->node = msc;
+ rc = get_msc_connection_status(cmd, data);
+ cmd->node = old_node;
- 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);
+ return rc;
}
-CTRL_CMD_DEFINE(bts_loc, "location");
-static int get_bts_loc(struct ctrl_cmd *cmd, void *data)
+static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_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;
- }
+ 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 (llist_empty(&bts->loc_list)) {
- cmd->reply = talloc_asprintf(cmd, "0,invalid,0,0,0");
- return CTRL_CMD_REPLY;
+ 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 {
- curloc = llist_entry(bts->loc_list.next, struct bts_location, list);
+ return 0;
}
- 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;
+ cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP);
+ if (!cmd) {
+ LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n");
+ return 0;
}
- return CTRL_CMD_REPLY;
-}
+ cmd->id = "0";
+ cmd->variable = talloc_asprintf(cmd, "msc.%d.connection_status", msc->nr);
+ cmd->node = msc;
-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;
- }
+ get_msc_connection_status(cmd, NULL);
- tmp = talloc_strdup(cmd, cmd->value);
- if (!tmp)
- goto oom;
+ ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
- curloc = talloc_zero(tall_bsc_ctx, struct bts_location);
- if (!curloc) {
- talloc_free(tmp);
- goto oom;
+ if (msc->nr == 0) {
+ /* Backwards compat. */
+ cmd->variable = "msc_connection_status";
+ ctrl_cmd_send_to_all(gsmnet->ctrl, cmd);
}
- 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);
+ talloc_free(cmd);
- /* Add location to the end of the list */
- llist_add(&curloc->list, &bts->loc_list);
+ return 0;
+}
- ret = get_bts_loc(cmd, data);
+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 (!location_equal(curloc, lastloc))
- bsc_gen_location_state_trap(bts);
+ if (subsys != SS_MSC)
+ return 0;
+ if (signal != S_MSC_AUTHENTICATED)
+ return 0;
- cleanup_locations(&bts->loc_list);
+ msc = signal_data;
- return ret;
+ net = msc->data->network;
+ llist_for_each_entry(bts, &net->bts_list, list)
+ ctrl_generate_bts_location_state_trap(bts, msc->data);
-oom:
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
+ return 0;
}
-static int verify_bts_loc(struct ctrl_cmd *cmd, const char *value, void *data)
+/* Obtain SS7 application server currently handling given MSC (DPC) */
+static struct osmo_ss7_as *msc_get_ss7_as(struct bsc_msc_data *msc)
{
- 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;
+ 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;
+}
- 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;
+static int _ss7_as_send(struct osmo_ss7_as *as, struct msgb *msg)
+{
+ struct osmo_ss7_asp *asp;
+ unsigned int i;
- if (((tstamp == 0) && (valid != BTS_LOC_FIX_INVALID)) || (lat < -90) || (lat > 90) ||
- (lon < -180) || (lon > 180) || (valid < 0)) {
- goto err;
+ /* 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);
}
-
- return 0;
-
-err:
- talloc_free(tmp);
- cmd->reply = talloc_strdup(cmd, "The format is <unixtime>,(invalid|fix2d|fix3d),<lat>,<lon>,<height>");
- return 1;
+ msgb_free(msg);
+ return -1;
}
-CTRL_CMD_DEFINE(net_timezone, "timezone");
-static int get_net_timezone(struct ctrl_cmd *cmd, void *data)
+int bsc_sccplite_msc_send(struct bsc_msc_data *msc, struct msgb *msg)
{
- 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");
+ struct osmo_ss7_as *as;
- if (!cmd->reply) {
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
+ as = msc_get_ss7_as(msc);
+ if (!as) {
+ msgb_free(msg);
+ return -1;
}
- return CTRL_CMD_REPLY;
+ /* 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);
}
-static int set_net_timezone(struct ctrl_cmd *cmd, void *data)
+/* 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)
{
- 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;
+ /* 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;
- tmp = talloc_strdup(cmd, cmd->value);
- if (!tmp)
- goto oom;
+ /* 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;
- hourstr = strtok_r(tmp, ",", &saveptr);
- minstr = strtok_r(NULL, ",", &saveptr);
- dststr = strtok_r(NULL, ",", &saveptr);
+ msg = ctrl_cmd_make(cmd);
+ if (!msg)
+ return -1;
- 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;
- }
- }
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+ ipa_prepend_header(msg, IPAC_PROTO_OSMO);
- tz->override = override;
+ 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;
- talloc_free(tmp);
- tmp = NULL;
+ msg = ctrl_cmd_make(cmd);
+ if (!msg)
+ return -1;
- return get_net_timezone(cmd, data);
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+ ipa_prepend_header(msg, IPAC_PROTO_OSMO);
-oom:
- cmd->reply = "OOM";
- return CTRL_CMD_ERROR;
+ return bsc_sccplite_msc_send(msc, msg);
}
-static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data)
+/* 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)
{
- char *saveptr, *hourstr, *minstr, *dststr, *tmp;
- int override, tz_hours, tz_mins, tz_dst;
+ struct ctrl_cmd *cmd;
+ bool parse_failed;
+ int rc;
- tmp = talloc_strdup(cmd, value);
- if (!tmp)
- return 1;
+ /* caller has already ensured ipaccess_head + ipaccess_head_ext */
+ OSMO_ASSERT(msg->l2h);
- hourstr = strtok_r(tmp, ",", &saveptr);
- minstr = strtok_r(NULL, ",", &saveptr);
- dststr = strtok_r(NULL, ",", &saveptr);
+ /* 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;
- if (hourstr == NULL)
- goto err;
+ /* handle the CTRL command */
+ ctrl_cmd_handle(bsc_gsmnet->ctrl, cmd, bsc_gsmnet);
- override = strcasecmp(hourstr, "off") != 0;
+send_reply:
+ rc = sccplite_asp_ctrl_cmd_send(asp, cmd);
+ talloc_free(cmd);
+ return rc;
+}
- if (!override) {
- talloc_free(tmp);
- return 0;
- }
- if (minstr == NULL || dststr == NULL)
- goto err;
+void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_data *msc_data)
+{
+ struct ctrl_cmd *trap;
+ struct ctrl_handle *ctrl;
- tz_hours = atol(hourstr);
- tz_mins = atol(minstr);
- tz_dst = atol(dststr);
+ ctrl = msc_data->network->ctrl;
- talloc_free(tmp);
- tmp = NULL;
+ trap = ctrl_cmd_trap(cmd);
+ if (!trap) {
- 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;
+ LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n");
+ return;
+ }
- return 0;
+ ctrl_cmd_send_to_all(ctrl, trap);
+ sccplite_msc_ctrl_cmd_send(msc_data, trap);
-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;
+ talloc_free(trap);
}
CTRL_CMD_DEFINE_WO_NOVRF(net_notification, "notification");
@@ -669,63 +820,87 @@ static int set_net_inform_msc(struct ctrl_cmd *cmd, void *data)
return CTRL_CMD_HANDLED;
}
-static int msc_signal_handler(unsigned int subsys, unsigned int signal,
- void *handler_data, void *signal_data)
+/* 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 msc_signal_data *msc;
- struct gsm_network *net;
- struct gsm_bts *bts;
+ struct gsm_network *net = cmd->node;
+ int bts_nr;
+ bool first_bts = true;
+ char *bts_dump;
- if (subsys != SS_MSC)
- return 0;
- if (signal != S_MSC_AUTHENTICATED)
- return 0;
+ cmd->reply = talloc_strdup(cmd, "");
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
- msc = signal_data;
+ 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;
+ }
- net = msc->data->network;
- llist_for_each_entry(bts, &net->bts_list, list)
- generate_location_state_trap(bts, msc->data);
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(net_show_lchan_full, "show-lchan full");
- return 0;
+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();
- 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);
+ rc = bsc_base_ctrl_cmds_install(net);
if (rc)
goto end;
- rc = osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL);
+ rc = bsc_ho_ctrl_cmds_install(net);
if (rc)
goto end;
- rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status);
+ rc = bsc_bts_ctrl_cmds_install();
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 = osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net);
-
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 9383167fb..000000000
--- a/src/osmo-bsc/bsc_ctrl_commands.c
+++ /dev/null
@@ -1,501 +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>
-#include <osmocom/bsc/bts.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, "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");
-
-/* 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, "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 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->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");
-
-/* 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 4328d76eb..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.
*
*/
@@ -47,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;
@@ -93,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;
@@ -115,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 b959c9f4c..c6c3e79a5 100644
--- a/src/osmo-bsc/bsc_init.c
+++ b/src/osmo-bsc/bsc_init.c
@@ -38,6 +38,7 @@
#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>
@@ -46,17 +47,7 @@
#include <limits.h>
#include <stdbool.h>
-static const struct osmo_stat_item_desc bsc_stat_desc[] = {
- { "num_bts:total", "Number of configured BTS for this BSC", "", 16, 0 },
-};
-
-static 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,
-};
+struct gsm_network *bsc_gsmnet;
int bsc_shutdown_net(struct gsm_network *net)
{
@@ -85,6 +76,30 @@ static void update_t3122_chan_load_timer(void *data)
osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0);
}
+static void bsc_store_bts_uptime(void *data)
+{
+ struct gsm_network *net = data;
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &net->bts_list, list)
+ bts_store_uptime(bts);
+
+ /* Keep this timer ticking. */
+ osmo_timer_schedule(&net->bts_store_uptime_timer, BTS_STORE_UPTIME_INTERVAL, 0);
+}
+
+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_store_lchan_durations(bts);
+
+ /* Keep this timer ticking. */
+ 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);
@@ -100,7 +115,6 @@ static struct gsm_network *bsc_network_init(void *ctx)
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);
@@ -118,6 +132,51 @@ static struct gsm_network *bsc_network_init(void *ctx)
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);
@@ -129,6 +188,14 @@ 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;
@@ -137,6 +204,7 @@ static struct gsm_network *bsc_network_init(void *ctx)
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:
diff --git a/src/osmo-bsc/bsc_rf_ctrl.c b/src/osmo-bsc/bsc_rf_ctrl.c
index a845859be..108f7e9ce 100644
--- a/src/osmo-bsc/bsc_rf_ctrl.c
+++ b/src/osmo-bsc/bsc_rf_ctrl.c
@@ -124,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;
@@ -213,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 */
diff --git a/src/osmo-bsc/bsc_sccp.c b/src/osmo-bsc/bsc_sccp.c
index 0cd1dc9f3..323d7fd4f 100644
--- a/src/osmo-bsc/bsc_sccp.c
+++ b/src/osmo-bsc/bsc_sccp.c
@@ -21,44 +21,120 @@
*
*/
+#include <osmocom/core/utils.h>
+
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/lb.h>
-/* We need an unused SCCP conn_id across all SCCP users. */
-int bsc_sccp_inst_next_conn_id(struct osmo_sccp_instance *sccp)
+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)
{
- static uint32_t next_id = 1;
- int i;
-
- /* This looks really suboptimal, but in most cases the static next_id should indicate exactly the next unused
- * conn_id, and we only iterate all conns once to make super sure that it is not already in use. */
-
- for (i = 0; i < 0xFFFFFF; i++) {
- struct gsm_subscriber_connection *conn;
- uint32_t conn_id = next_id;
- bool conn_id_already_used = false;
- next_id = (next_id + 1) & 0xffffff;
-
- llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) {
- if (conn->sccp.msc && conn->sccp.msc->a.sccp == sccp) {
- if (conn_id == conn->sccp.conn_id) {
- conn_id_already_used = true;
- break;
- }
- }
-
- if (bsc_gsmnet->smlc->sccp == sccp
- && conn->lcs.lb.state != SUBSCR_SCCP_ST_NONE) {
- if (conn_id == conn->lcs.lb.conn_id) {
- conn_id_already_used = true;
- break;
- }
- }
+ 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);
- if (!conn_id_already_used)
- return conn_id;
+ 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 -1;
+
+ 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 ed08e86ad..f1f48bc35 100644
--- a/src/osmo-bsc/bsc_subscr_conn_fsm.c
+++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c
@@ -32,6 +32,7 @@
#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>
@@ -45,9 +46,11 @@
#include <osmocom/bsc/assignment_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))
@@ -59,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 */
@@ -66,15 +71,17 @@ 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_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"},
@@ -93,8 +100,10 @@ static const struct value_string gscon_fsm_event_names[] = {
};
struct osmo_tdef_state_timeout conn_fsm_timeouts[32] = {
+ [ST_WAIT_INITIAL_USER_DATA] = { .T = -25 },
[ST_WAIT_CC] = { .T = -3210 },
- [ST_CLEARING] = { .T = -4 },
+ [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.
@@ -137,34 +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;
int rc;
-
- if (conn->rx_clear_command) {
- LOGPFSML(conn->fi, LOGL_DEBUG, "Not sending BSSMAP CLEAR REQUEST, already got CLEAR COMMAND from MSC\n");
- return;
- }
+ struct gsm_subscriber_connection *conn = fi->priv;
+ enum gsm0808_cause cause = conn->clear_cause;
if (!conn->sccp.msc) {
- LOGPFSML(conn->fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message, no MSC for this conn\n");
- return;
+ LOGPFSML(fi, LOGL_ERROR, "Unable to deliver BSSMAP Clear Request message, no MSC for this conn\n");
+ goto nothing_sent;
}
- LOGPFSML(conn->fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST(%s) to MSC\n", gsm0808_cause_name(cause));
+ 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(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CLEAR_RQST]);
+ 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 */
@@ -176,7 +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(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_DTAP]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_DTAP));
gscon_sigtran_send(conn, resp);
}
@@ -193,9 +253,12 @@ 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, enum gsm48_rr_cause cause_rr)
@@ -208,89 +271,149 @@ void gscon_release_lchans(struct gsm_subscriber_connection *conn, bool do_rr_rel
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)];
- bssmap_type = msg->l4h[0];
-
- LOGPFSML(fi, LOGL_DEBUG, "Rx N-CONNECT: %s: %s\n", gsm0808_bssap_name(bs->type),
- gsm0808_bssmap_name(bssmap_type));
+ /* 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:
- break;
+ case BSS_MAP_MSG_VGCS_VBS_SETUP:
+ case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST:
+ return 0;
default:
- LOGPFSML(fi, LOGL_NOTICE, "No support for N-CONNECT: %s: %s\n",
+ LOGPFSML(fi, LOGL_ERROR, "No support for initial BSSMAP: %s: %s\n",
gsm0808_bssap_name(bs->type), gsm0808_bssmap_name(bssmap_type));
- goto refuse;
+ return -EINVAL;
}
+}
- /* First off, 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");
- goto refuse;
- }
+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;
- /* Make sure the conn FSM will osmo_sccp_tx_disconn() on term */
- conn->sccp.state = SUBSCR_SCCP_ST_CONNECTED;
+ /* validate_initial_user_data() must be called before this */
+ OSMO_ASSERT(msgb_l4(msg));
+
+ bs = msgb_l3(msg);
+ bssmap_type = msg->l4h[0];
+
+ /* 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:
- /* Inter-BSC MT Handover Request, another BSS is handovering to us. */
+ 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;
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;
+ 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:
- OSMO_ASSERT(false);
+ 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);
+
+ /* 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;
+ }
}
-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);
+ /* 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)
@@ -299,7 +422,6 @@ 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_MO_COMPL_L3:
@@ -327,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);
+ }
+}
+
+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;
- handle_bssap_n_connect(fi, scu_prim);
+ 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)
@@ -348,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, -4);
return;
default:
OSMO_ASSERT(false);
@@ -373,7 +510,6 @@ static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *da
confirmed connection, then instead simply drop the connection */
LOGPFSML(fi, LOGL_INFO,
"Connection confirmed but lchan was dropped previously, clearing conn\n");
- osmo_fsm_inst_state_chg(conn->fi, ST_CLEARING, 60, -4);
gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
break;
}
@@ -510,6 +646,62 @@ 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. */
@@ -517,39 +709,57 @@ struct osmo_mgcpc_ep *gscon_ensure_mgw_endpoint(struct gsm_subscriber_connection
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 =
osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT,
- conn->network->mgw.client,
+ mgcp_client,
conn->network->mgw.tdefs,
conn->fi->id,
"%x@%s", msc_assigned_cic,
- mgcp_client_endpoint_domain(conn->network->mgw.client));
+ mgcp_client_endpoint_domain(mgcp_client));
LOGPFSML(conn->fi, LOGL_DEBUG, "MGW endpoint name derived from CIC 0x%x: %s\n",
msc_assigned_cic, osmo_mgcpc_ep_name(conn->user_plane.mgw_endpoint));
} else if (gscon_is_aoip(conn)) {
-
- if (is_ipaccess_bts(for_lchan->ts->trx->bts))
+ if (is_ipa_abisip_bts(for_lchan->ts->trx->bts))
/* use dynamic RTPBRIDGE endpoint allocation in MGW */
- epname = mgcp_client_rtpbridge_wildcard(conn->network->mgw.client);
+ epname = mgcp_client_rtpbridge_wildcard(mgcp_client);
else {
- epname = mgcp_client_e1_epname(conn, conn->network->mgw.client, for_lchan->ts->e1_link.e1_nr,
+ 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,
- for_lchan->ts->e1_link.e1_ts_ss*2);
+ i460_bit_offs);
}
conn->user_plane.mgw_endpoint =
- osmo_mgcpc_ep_alloc(conn->fi, GSCON_EV_FORGET_MGW_ENDPOINT,
- conn->network->mgw.client,
- conn->network->mgw.tdefs,
- conn->fi->id,
- "%s", epname);
+ 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;
@@ -584,7 +794,7 @@ 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,
@@ -599,7 +809,7 @@ 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 = osmo_mgcpc_ep_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 endpoint\n");
@@ -617,6 +827,8 @@ bool gscon_connect_mgw_to_msc(struct gsm_subscriber_connection *conn,
LOGPFSML(conn->fi, LOGL_DEBUG,
"MSC side MGW endpoint ci is already configured to %s\n",
osmo_mgcpc_ep_ci_name(ci));
+ /* Immediately dispatch the success event */
+ osmo_fsm_inst_dispatch(notify, event_success, notify_data);
return true;
}
@@ -651,15 +863,25 @@ 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_MO_COMPL_L3) | 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) | S(ST_CLEARING),
+ .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD),
.action = gscon_fsm_wait_cc,
},
[ST_ACTIVE] = {
@@ -669,26 +891,32 @@ static const struct osmo_fsm_state gscon_fsm_states[] = {
| S(GSCON_EV_LCS_LOC_REQ_END)
| S(GSCON_EV_MO_COMPL_L3)
,
- .out_state_mask = S(ST_CLEARING) | S(ST_ASSIGNMENT) |
+ .out_state_mask = S(ST_WAIT_CLEAR_CMD) | S(ST_WAIT_SCCP_RLSD) | S(ST_ASSIGNMENT) |
S(ST_HANDOVER),
.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)
@@ -696,9 +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;
@@ -723,23 +961,23 @@ 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 the conn has no lchan anymore, it was released by the BTS and needs to Clear towards MSC. */
- if (!conn->lchan) {
+ /* 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:
- /* Ensure that the FSM is in ST_CLEARING. */
- osmo_fsm_inst_state_chg(conn->fi, ST_CLEARING, 60, -4);
- /* fall thru, omit an error log if already in ST_CLEARING */
- case ST_CLEARING:
- /* Request a Clear Command from the MSC. */
gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
break;
}
@@ -763,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";
@@ -778,21 +1020,32 @@ void gscon_forget_lchan(struct gsm_subscriber_connection *conn, struct gsm_lchan
osmo_fsm_inst_name(conn->fi), detach_label);
}
- if ((conn->fi && conn->fi->state != ST_CLEARING)
- && !conn->lchan
+ if (!conn->lchan
&& !conn->ho.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);
}
@@ -803,50 +1056,27 @@ void gscon_forget_mgw_endpoint_ci(struct gsm_subscriber_connection *conn, struct
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 gscon_clear_cmd_data *ccd;
- struct osmo_mobile_identity *mi_imsi;
+ const struct tlv_parsed *tp;
+ struct osmo_mobile_identity mi_imsi;
/* Regular allstate event processing */
switch (event) {
case GSCON_EV_A_CLEAR_CMD:
- conn->rx_clear_command = true;
-
- /* 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);
-
OSMO_ASSERT(data);
- ccd = data;
- if (conn->lchan)
- conn->lchan->release.is_csfb = ccd->is_csfb;
- /* MSC tells us to cleanly shut down */
- if (conn->fi->state != ST_CLEARING)
- osmo_fsm_inst_state_chg(fi, ST_CLEARING, 60, -4);
- LOGPFSML(fi, LOGL_DEBUG, "Releasing all lchans (if any) after BSSMAP Clear Command\n");
- gscon_release_lchans(conn, true, bsc_gsm48_rr_cause_from_gsm0808_cause(ccd->cause_0808));
- /* 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 */
- osmo_mgcpc_ep_clear(conn->user_plane.mgw_endpoint);
-
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CLEAR_COMPLETE]);
- gscon_sigtran_send(conn, gsm0808_create_clear_complete());
+ 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:
@@ -859,7 +1089,9 @@ static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *d
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");
@@ -868,14 +1100,26 @@ static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *d
break;
case GSCON_EV_A_COMMON_ID_IND:
OSMO_ASSERT(data);
- mi_imsi = 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,
+ 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);
+ 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;
@@ -891,6 +1135,7 @@ 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);
@@ -899,9 +1144,14 @@ static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cau
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");
@@ -916,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);
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 */
@@ -925,15 +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");
- /* when things go smoothly, the lchan should have been released before FSM instance termination. So if this is
- * necessary it's probably "abnormal". */
- gscon_release_lchans(conn, true, GSM48_RR_CAUSE_ABNORMAL_UNSPEC);
+ 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)
@@ -958,8 +1225,9 @@ static int gscon_timer_cb(struct osmo_fsm_inst *fi)
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;
@@ -987,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);
@@ -1004,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 */
@@ -1089,19 +1360,23 @@ static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, en
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, OBSC_LINKID_CB(msg),
+ 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;
}
}
@@ -1123,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) {
@@ -1139,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, GSM0808_CAUSE_BSS_NOT_EQUIPPED);
+ bsc_sapi_n_reject(conn, RSL_LINK_ID2DLCI(link_id), GSM0808_CAUSE_BSS_NOT_EQUIPPED);
goto failed_to_send;
}
return;
@@ -1154,7 +1428,6 @@ 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");
gscon_bssmap_clear(conn, GSM0808_CAUSE_EQUIPMENT_FAILURE);
- osmo_fsm_inst_state_chg(conn->fi, ST_ACTIVE, 0, 0);
}
void gscon_submit_rsl_dtap(struct gsm_subscriber_connection *conn,
@@ -1174,7 +1447,7 @@ void gscon_update_id(struct gsm_subscriber_connection *conn)
{
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_id,
+ conn->sccp.conn.conn_id,
conn->bsub? "_" : "",
conn->bsub? bsc_subscr_id(conn->bsub) : "");
}
diff --git a/src/osmo-bsc/bsc_subscriber.c b/src/osmo-bsc/bsc_subscriber.c
index 4a4829866..1f69d442d 100644
--- a/src/osmo-bsc/bsc_subscriber.c
+++ b/src/osmo-bsc/bsc_subscriber.c
@@ -30,6 +30,7 @@
#include <osmocom/core/logging.h>
#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/debug.h>
static void bsc_subscr_free(struct bsc_subscr *bsub);
@@ -64,26 +65,40 @@ static int bsub_use_cb(struct osmo_use_count_entry *e, int32_t old_use_count, co
return 0;
}
-static struct bsc_subscr *bsc_subscr_alloc(struct llist_head *list)
+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;
+ 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, list);
+ llist_add_tail(&bsub->entry, &bsubst->bsub_list);
return bsub;
}
-struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list,
+struct bsc_subscr *bsc_subscr_find_by_imsi(struct bsc_subscr_store *bsubst,
const char *imsi,
const char *use_token)
{
@@ -92,7 +107,7 @@ struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list,
if (!imsi || !*imsi)
return NULL;
- llist_for_each_entry(bsub, list, entry) {
+ llist_for_each_entry(bsub, &bsubst->bsub_list, entry) {
if (!strcmp(bsub->imsi, imsi)) {
bsc_subscr_get(bsub, use_token);
return bsub;
@@ -101,17 +116,17 @@ struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list,
return NULL;
}
-struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list,
- uint32_t tmsi,
- const char *use_token)
+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 (tmsi == GSM_RESERVED_TMSI)
+ if (!imei || !*imei)
return NULL;
- llist_for_each_entry(bsub, list, entry) {
- if (bsub->tmsi == tmsi) {
+ llist_for_each_entry(bsub, &bsubst->bsub_list, entry) {
+ if (!strcmp(bsub->imei, imei)) {
bsc_subscr_get(bsub, use_token);
return bsub;
}
@@ -119,19 +134,83 @@ struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list,
return NULL;
}
-struct bsc_subscr *bsc_subscr_find_by_mi(struct llist_head *list, const struct osmo_mobile_identity *mi,
- const char *use_token)
+static struct bsc_subscr *bsc_subscr_find_by_tmsi(struct bsc_subscr_store *bsubst,
+ uint32_t tmsi,
+ const char *use_token)
{
- if (!mi)
- return NULL;
- switch (mi->type) {
- case GSM_MI_TYPE_IMSI:
- return bsc_subscr_find_by_imsi(list, mi->imsi, use_token);
- case GSM_MI_TYPE_TMSI:
- return bsc_subscr_find_by_tmsi(list, mi->tmsi, use_token);
- default:
+ const struct rb_node *node = bsubst->bsub_tree_tmsi.rb_node;
+ struct bsc_subscr *bsub;
+
+ if (tmsi == GSM_RESERVED_TMSI)
return NULL;
+
+ 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)
@@ -141,15 +220,22 @@ 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,
+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, use_token);
+ 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);
@@ -157,32 +243,53 @@ struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list,
return bsub;
}
-struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list,
+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_imei(bsubst, imei, use_token);
+ if (bsub)
+ return bsub;
+ bsub = bsc_subscr_alloc(bsubst);
+ if (!bsub)
+ return NULL;
+ bsc_subscr_set_imei(bsub, imei);
+ bsc_subscr_get(bsub, use_token);
+ return bsub;
+}
+
+struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct bsc_subscr_store *bsubst,
uint32_t tmsi,
const char *use_token)
{
struct bsc_subscr *bsub;
- bsub = bsc_subscr_find_by_tmsi(list, tmsi, use_token);
+ bsub = bsc_subscr_find_by_tmsi(bsubst, tmsi, use_token);
if (bsub)
return bsub;
- bsub = bsc_subscr_alloc(list);
+ bsub = bsc_subscr_alloc(bsubst);
if (!bsub)
return NULL;
- bsub->tmsi = tmsi;
+ 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 llist_head *list, const struct osmo_mobile_identity *mi,
+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(list, mi->imsi, use_token);
+ 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(list, mi->tmsi, use_token);
+ return bsc_subscr_find_or_create_by_tmsi(bsubst, mi->tmsi, use_token);
default:
return NULL;
}
@@ -198,6 +305,8 @@ static int bsc_subscr_name_buf(char *buf, size_t buflen, struct bsc_subscr *bsub
}
if (bsub->imsi[0])
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;
@@ -222,6 +331,11 @@ const char *bsc_subscr_id(struct bsc_subscr *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);
}
@@ -246,3 +360,41 @@ void log_set_filter_bsc_subscr(struct log_target *target,
} 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 e91e01cc7..8690a47c3 100644
--- a/src/osmo-bsc/bsc_vty.c
+++ b/src/osmo-bsc/bsc_vty.c
@@ -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>
@@ -30,53 +32,42 @@
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/misc.h>
#include <osmocom/vty/tdef_vty.h>
-#include <osmocom/gsm/protocol/gsm_04_08.h>
-#include <osmocom/gsm/gsm0502.h>
#include <osmocom/ctrl/control_if.h>
-#include <osmocom/gsm/gsm48.h>
-#include <osmocom/gsm/gsm0808.h>
#include <osmocom/gsm/gsm23236.h>
-#include <osmocom/core/sockaddr_str.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/byteswap.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.h>
#include <osmocom/bsc/meas_feed.h>
-#include <osmocom/bsc/neighbor_ident.h>
-#include <osmocom/bsc/handover.h>
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/bsc/osmo_bsc.h>
#include <osmocom/bsc/bts.h>
-#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.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>
@@ -84,56 +75,6 @@
#define X(x) (1 << x)
-#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 }
-};
-
const struct value_string bts_loc_fix_names[] = {
{ BTS_LOC_FIX_INVALID, "invalid" },
{ BTS_LOC_FIX_2D, "fix2d" },
@@ -141,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)
@@ -179,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),
@@ -192,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;
@@ -268,6 +219,9 @@ static void net_dump_vty(struct vty *vty, struct gsm_network *net)
vty_out(vty, " Last RF Lock Command: %s%s",
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",
@@ -278,271 +232,6 @@ 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_cbch(struct vty *vty, const struct bts_smscb_chan_state *cstate)
-{
- vty_out(vty, " CBCH %s: %u messages, %u pages, %lu-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_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 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);
- 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);
-
- 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",
- 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);
-
- vty_out_stat_item_group(vty, " ", bts->bts_statg);
-
- 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\n")
@@ -568,6 +257,22 @@ DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]",
return CMD_SUCCESS;
}
+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_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
+
+ /* 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);
+ }
+
+ return CMD_SUCCESS;
+}
+
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"
@@ -656,479 +361,6 @@ DEFUN(show_rejected_bts, show_rejected_bts_cmd, "show rejected-bts",
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.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, 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 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 osmo_sockaddr_str remote = {};
- uint16_t port;
-
- 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);
-
- if (osmo_sockaddr_str_from_sockaddr(&remote, &nsvc->remote.u.sas) ||
- remote.af != AF_UNSPEC) {
- vty_out(vty, " gprs nsvc %u remote ip %s%s", i,
- remote.ip, VTY_NEWLINE);
- }
-
- /* Can't use remote.port because it's only valid when family != AF_UNSPEC, but the
- * port can be even configured when the IP isn't */
- port = osmo_htons(nsvc->remote.u.sin.sin_port);
- if (port)
- vty_out(vty, " gprs nsvc %u remote udp port %u%s", i,
- 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);
-}
-
-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);
- }
-
- 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);
-
- 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->ccch_load_ind_thresh != 10)
- vty_out(vty, " ccch load-indication-threshold %u%s",
- bts->ccch_load_ind_thresh, 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);
- 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);
-
- 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(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;
-
- 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);
- }
- }
- 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;
-
- llist_for_each_entry(bts, &gsmnet->bts_list, list)
- config_write_bts_single(v, bts);
-
- return CMD_SUCCESS;
-}
-
static int config_write_net(struct vty *vty)
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
@@ -1169,6 +401,7 @@ static int config_write_net(struct vty *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();
@@ -1179,6 +412,9 @@ 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);
}
if (gsmnet->allow_unusable_timeslots)
@@ -1194,38 +430,16 @@ static int config_write_net(struct vty *vty)
vty_out(vty, "%s", VTY_NEWLINE);
}
- return CMD_SUCCESS;
-}
+ 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);
-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, " 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? "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);
- }
+ 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)
@@ -1284,56 +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 (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_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>]",
@@ -1410,7 +574,7 @@ 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);
@@ -1420,39 +584,6 @@ static void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub)
vty_out(vty, " Use count: %s%s", osmo_use_count_to_str_c(OTC_SELECT, &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");
-}
-
static inline void print_all_trx_ext(struct vty *vty, bool show_connected)
{
struct gsm_network *net = gsmnet_from_vty(vty);
@@ -1481,88 +612,13 @@ 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 *),
bool all)
{
struct gsm_lchan *lchan;
- ts_for_each_lchan(lchan, ts) {
+ 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);
@@ -1701,7 +757,7 @@ DEFUN(show_lchan_summary_all,
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,
+ 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",
@@ -1727,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)
@@ -1743,23 +797,35 @@ 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;
}
@@ -1773,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) {
@@ -1806,7 +871,10 @@ static int ho_or_as(struct vty *vty, const char *argv[], int argc)
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);
}
}
@@ -1816,6 +884,29 @@ static int ho_or_as(struct vty *vty, const char *argv[], int argc)
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"
@@ -1857,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)) {
@@ -1899,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);
+ not_this_bts ? not_this_bts->nr : 255, gsm_chan_t_name(free_type), VTY_NEWLINE);
return NULL;
}
@@ -1931,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,
@@ -1946,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;
@@ -1970,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;
@@ -1983,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);
}
@@ -1991,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);
}
@@ -2098,7 +1187,7 @@ DEFUN_DEPRECATED(cfg_net_dtx,
return CMD_SUCCESS;
}
-#define NRI_STR "Mapping of Network Resource Indicators to this MSC, for MSC pooling\n"
+#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" \
@@ -2115,7 +1204,7 @@ DEFUN_ATTR(cfg_net_nri_bitlen,
"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(NRI_BITLEN_DEFAULT) ")\n",
+ "bit count (default: " OSMO_STRINGIFY_VAL(OSMO_NRI_BITLEN_DEFAULT) ")\n",
CMD_ATTR_IMMEDIATE)
{
struct gsm_network *gsmnet = gsmnet_from_vty(vty);
@@ -2145,7 +1234,7 @@ DEFUN_ATTR(cfg_net_nri_null_add,
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 for this MSC\n"
+ NRI_STR NULL_NRI_STR "Remove NRI value or range from the NRI mapping\n"
NRI_FIRST_LAST_STR,
CMD_ATTR_IMMEDIATE)
{
@@ -2161,2799 +1250,6 @@ DEFUN_ATTR(cfg_net_nri_null_del,
return CMD_SUCCESS;
}
-/* 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, "%% 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);
- osmo_stat_item_inc(gsmnet->bsc_statg->items[BSC_STAT_NUM_BTS_TOTAL], 1);
- } 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 < 0)
- return CMD_WARNING;
-
- return CMD_SUCCESS;
-}
-
-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_ipaccess_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_ipaccess_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>",
- "Set the Location Area Code (LAC) of this BTS\n" "LAC\n")
-{
- struct gsm_bts *bts = vty->index;
- int lac = atoi(argv[0]);
-
- if (lac < 0 || lac > 0xffff) {
- vty_out(vty, "%% LAC %d is not in the valid range (0-65535)%s",
- lac, 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);
- 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_ipaccess_bts(bts)) {
- vty_out(vty, "%% BTS is not of ip.access 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_ipaccess_bts(bts)) {
- vty_out(vty, "%% BTS is not of ip.access 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 (in sec.) between BTS_RESET is sent,\n"
- "and the BTS is being bootstrapped.\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_ipaccess_bts(bts)) {
- vty_out(vty, "%% BTS is not of ip.access 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;
-}
-
-DEFUN_ATTR(cfg_bts_challoc,
- cfg_bts_challoc_cmd,
- "channel allocator (ascending|descending)",
- "Channel Allocator\n" "Channel Allocator\n"
- "Allocate Timeslots and Transceivers in ascending order\n"
- "Allocate Timeslots and Transceivers in descending order\n",
- CMD_ATTR_IMMEDIATE)
-{
- struct gsm_bts *bts = vty->index;
-
- if (!strcmp(argv[0], "ascending"))
- bts->chan_alloc_reverse = 0;
- else
- bts->chan_alloc_reverse = 1;
-
- 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;
-}
-
-#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;
-}
-
-#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_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_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;
-}
-
-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;
-
- gsm_bts_set_radio_link_timeout(bts, atoi(argv[0]));
-
- 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 this BTS%s", 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->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_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->gprs.nsvc[idx].nsvci = atoi(argv[1]);
-
- 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->gprs.nsvc[idx].local_port = atoi(argv[1]);
-
- 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->gprs.nsvc[idx].remote.u.sin.sin_port = htons(atoi(argv[1]));
-
- 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 overriden */
- bts->gprs.nsvc[idx].remote.u.sas.ss_family = remote.af;
- switch (remote.af) {
- case AF_INET:
- osmo_sockaddr_str_to_in_addr(&remote, &bts->gprs.nsvc[idx].remote.u.sin.sin_addr);
- break;
- case AF_INET6:
- osmo_sockaddr_str_to_in6_addr(&remote, &bts->gprs.nsvc[idx].remote.u.sin6.sin6_addr);
- 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->gprs.nse.timer))
- 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_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_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_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_ipaccess_bts(bts) || is_sysmobts_v2(bts)) {
- vty_out(vty, "%% This command is only intended for ipaccess nanoBTS. 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;
- 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_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;
- 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);
- 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;
- case -EADDRINUSE:
- vty_out(vty, "%% Unable to add UARFCN: (%u, %u) is already added%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_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",
- CMD_ATTR_IMMEDIATE)
-{
- struct gsm_bts *bts = vty->index;
- int rc;
-
- 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;
- }
-
- 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_ipaccess_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_ipaccess_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 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 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;
- 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 "Threshold between codec 1 and 2\n"
-#define AMR_HY_HELP_STR "Hysteresis between codec 1 and 2\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)
-{
- 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 AMR_TH_HELP_STR)
-{
- 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 AMR_TH_HELP_STR AMR_TH_HELP_STR)
-{
- 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)
-{
- 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 AMR_HY_HELP_STR)
-{
- 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 AMR_HY_HELP_STR AMR_HY_HELP_STR)
-{
- 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)
-{
- 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 AMR_TH_HELP_STR)
-{
- 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 AMR_TH_HELP_STR AMR_TH_HELP_STR)
-{
- 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)
-{
- 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 AMR_HY_HELP_STR)
-{
- 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 AMR_HY_HELP_STR AMR_HY_HELP_STR)
-{
- get_amr_hy_from_arg(vty, 4, argv, 0);
- return check_amr_config(vty);
-}
-
-#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\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;
-}
-
-#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->index_sub = &trx->description;
- 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;
-
- /* 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 = 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 */
-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) {
- 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;
-
- 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]);
-
- 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;
- 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 seem to support freq. hopping%s", VTY_NEWLINE);
- /* Allow enabling frequency hopping anyway, because the BTS might not have
- * connected yet (thus not sent the feature vector), so we cannot know for
- * sure. Jet print a warning and let it go. */
- }
-
- 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;
- }
-
- bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1);
-
- 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;
- }
-
- bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0);
-
- 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);
-
- 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;
-}
-
int print_counter(struct rate_ctr_group *bsc_ctrs, struct rate_ctr *ctr, const struct rate_ctr_desc *desc, void *data)
{
struct vty *vty = data;
@@ -4994,8 +1290,8 @@ DEFUN(drop_bts,
return CMD_WARNING;
}
- if (!is_ipaccess_bts(bts)) {
- vty_out(vty, "%% This command only works for ipaccess.%s", VTY_NEWLINE);
+ if (!is_ipa_abisip_bts(bts)) {
+ vty_out(vty, "%% This command only works for IPA Abis/IP.%s", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -5041,8 +1337,8 @@ DEFUN(restart_bts, restart_bts_cmd,
return CMD_WARNING;
}
- if (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) {
- vty_out(vty, "%% This command only works for ipaccess nanoBTS.%s",
+ if (!is_ipa_abisip_bts(bts)) {
+ vty_out(vty, "%% This command only works for IPA Abis/IP.%s",
VTY_NEWLINE);
return CMD_WARNING;
}
@@ -5054,7 +1350,8 @@ DEFUN(restart_bts, restart_bts_cmd,
return CMD_SUCCESS;
}
-DEFUN(bts_resend, bts_resend_cmd,
+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")
@@ -5087,8 +1384,91 @@ DEFUN(bts_resend, bts_resend_cmd,
return CMD_SUCCESS;
}
+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")
+{
+ const struct gsm_bts_trx *trx;
+ const struct gsm_bts *bts;
+ int bts_nr = atoi(argv[0]);
+
+ 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 (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 (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;
+ }
-DEFUN(smscb_cmd, smscb_cmd_cmd,
+ 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(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")
+{
+ int bts_nr = atoi(argv[0]);
+ int red = atoi(argv[1]);
+ struct gsm_bts *bts;
+ int rc;
+
+ 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 (red % 2 != 0) {
+ vty_out(vty, "%% Incorrect BCCH power reduction value, "
+ "an even number is expected%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ 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;
+ }
+}
+
+/* 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"
@@ -5152,39 +1532,12 @@ DEFUN(smscb_cmd, smscb_cmd_cmd,
return CMD_WARNING;
}
+ /* 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;
}
-/* 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;
-}
-
DEFUN(pdch_act, pdch_act_cmd,
"bts <0-255> trx <0-255> timeslot <0-7> pdch (activate|deactivate)",
BTS_NR_TRX_TS_STR2
@@ -5201,15 +1554,15 @@ DEFUN(pdch_act, pdch_act_cmd,
return CMD_WARNING;
}
- if (!is_ipaccess_bts(ts->trx->bts)) {
- vty_out(vty, "%% This command only works for ipaccess BTS%s",
+ 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;
}
- if (ts->pchan_on_init != GSM_PCHAN_TCH_F_TCH_H_PDCH
+ 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_PDCH or TCH/F_PDCH%s",
+ 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;
}
@@ -5241,15 +1594,24 @@ DEFUN(pdch_act, pdch_act_cmd,
/* 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 lchan_activate_info info = { };
+ 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 };
if (activate) {
+ if (!codec_str) {
+ vty_out(vty, "%% Error: need a channel type argument to activate%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
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;
+ }
int lchan_t;
if (lchan->fi->state != LCHAN_ST_UNUSED) {
@@ -5262,10 +1624,10 @@ static int lchan_act_single(struct vty *vty, struct gsm_lchan *lchan, const char
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_TCH_F_TCH_H_PDCH && !strcmp(codec_str, "hr"))
+ 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_TCH_F_TCH_H_PDCH)
+ || lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN)
&& !strcmp(codec_str, "fr"))
lchan_t = GSM_LCHAN_TCH_F;
else {
@@ -5276,42 +1638,40 @@ static int lchan_act_single(struct vty *vty, struct gsm_lchan *lchan, const char
}
/* configure the lchan */
- lchan->type = lchan_t;
+ lchan_select_set_type(lchan, lchan_t);
if (!strcmp(codec_str, "hr") || !strcmp(codec_str, "fr")) {
- info = (struct lchan_activate_info) {
- .activ_for = FOR_VTY,
- .chan_mode = GSM48_CMODE_SPEECH_V1,
- .requires_voice_stream = false,
- };
+ info.ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_V1;
} else if (!strcmp(codec_str, "efr")) {
- info = (struct lchan_activate_info) {
- .activ_for = FOR_VTY,
- .chan_mode = GSM48_CMODE_SPEECH_EFR,
- .s15_s0 = amr_modes[amr_mode],
- .requires_voice_stream = false,
- };
+ 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 = (struct lchan_activate_info) {
- .activ_for = FOR_VTY,
- .chan_mode = GSM48_CMODE_SPEECH_AMR,
- .s15_s0 = amr_modes[amr_mode],
- .requires_voice_stream = false,
- };
+ 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 = (struct lchan_activate_info) {
- .activ_for = FOR_VTY,
- .chan_mode = GSM48_CMODE_SIGN,
- .requires_voice_stream = false,
- };
+ info.ch_mode_rate.chan_mode = GSM48_CMODE_SIGN;
} else {
vty_out(vty, "%% Invalid channel mode specified!%s", VTY_NEWLINE);
return CMD_WARNING;
}
+ 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);
+
+ 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);
+ }
+
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);
@@ -5323,7 +1683,8 @@ static int lchan_act_single(struct vty *vty, struct gsm_lchan *lchan, const char
}
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);
+ lchan_release(lchan, !!(lchan->conn), false, 0,
+ gscon_last_eutran_plmn(lchan->conn));
}
return CMD_SUCCESS;
@@ -5340,7 +1701,7 @@ static int lchan_act_trx(struct vty *vty, struct gsm_bts_trx *trx, int activate)
for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
ts = &trx->ts[ts_nr];
- ts_for_each_potential_lchan(lchan, ts) {
+ 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:
@@ -5351,7 +1712,7 @@ static int lchan_act_trx(struct vty *vty, struct gsm_bts_trx *trx, int activate)
break;
case GSM_PCHAN_TCH_F:
case GSM_PCHAN_TCH_F_PDCH:
- case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ case GSM_PCHAN_OSMO_DYN:
codec_str = "fr";
break;
case GSM_PCHAN_TCH_H:
@@ -5364,9 +1725,9 @@ static int lchan_act_trx(struct vty *vty, struct gsm_bts_trx *trx, int activate)
if (codec_str && skip_next == false) {
lchan_act_single(vty, lchan, codec_str, -1, activate);
- /* We use GSM_PCHAN_TCH_F_TCH_H_PDCH slots as TCH_F for this test, so we
+ /* 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_TCH_F_TCH_H_PDCH)
+ if (ts->pchan_on_init == GSM_PCHAN_OSMO_DYN)
skip_next = true;
}
else {
@@ -5379,41 +1740,83 @@ static int lchan_act_trx(struct vty *vty, struct gsm_bts_trx *trx, int activate)
return CMD_SUCCESS;
}
-/* 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|sig) [<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" "Signalling\n" "AMR Mode\n")
+static int lchan_act_deact(struct vty *vty, const char **argv, int argc)
{
struct gsm_bts_trx_ts *ts;
+ struct gsm_bts *bts;
struct gsm_lchan *lchan;
- int ss_nr = atoi(argv[3]);
- const char *act_str = argv[4];
- const char *codec_str = argv[5];
+ 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;
+ if (argc > 5)
+ act_str = argv[5];
if (argc > 6)
- amr_mode = atoi(argv[6]);
+ codec_str = argv[6];
+ if (argc > 7)
+ amr_mode = atoi(argv[7]);
ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
if (!ts)
return CMD_WARNING;
+ 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 = 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;
+ }
+
+ if (vamos)
+ ss_nr += ts->max_primary_lchans;
+
lchan = &ts->lchan[ss_nr];
- if (!strcmp(act_str, "activate"))
+ if (!act_str)
+ activate = 0;
+ else if (!strcmp(act_str, "activate"))
activate = 1;
+ else if (!strcmp(act_str, "activate-vamos"))
+ activate = 2;
else
- activate = 0;
+ return CMD_WARNING;
return lchan_act_single(vty, lchan, codec_str, amr_mode, activate);
}
+/* 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")
+{
+ return lchan_act_deact(vty, argv, argc);
+}
+
+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")
+{
+ return lchan_act_deact(vty, argv, argc);
+}
+
#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"
@@ -5427,9 +1830,7 @@ DEFUN_HIDDEN(lchan_act_bts, lchan_act_all_cmd,
struct gsm_network *net = gsmnet_from_vty(vty);
const char *act_str = argv[0];
int activate;
- int bts_nr;
struct gsm_bts *bts;
- int trx_nr;
struct gsm_bts_trx *trx;
if (!strcmp(act_str, "activate-all-lchan"))
@@ -5437,14 +1838,17 @@ DEFUN_HIDDEN(lchan_act_bts, lchan_act_all_cmd,
else
activate = 0;
- for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
- bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr);
- for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
- trx = gsm_bts_trx_num(bts, trx_nr);
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ llist_for_each_entry(trx, &bts->trx_list, list)
lchan_act_trx(vty, trx, activate);
- }
}
+ 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;
}
@@ -5479,6 +1883,12 @@ DEFUN_HIDDEN(lchan_act_all_bts, lchan_act_all_bts_cmd,
lchan_act_trx(vty, trx, activate);
}
+ 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;
}
@@ -5517,9 +1927,102 @@ DEFUN_HIDDEN(lchan_act_all_trx, lchan_act_all_trx_cmd,
lchan_act_trx(vty, trx, activate);
+ 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(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;
+ 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);
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+ if (ss_nr >= ts->max_primary_lchans) {
+ vty_out(vty, "%% Invalid sub-slot number for this timeslot type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ lchan = &ts->lchan[ss_nr];
+ if (!lchan->fi)
+ return CMD_WARNING;
+
+ 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(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_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;
+
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts)
+ return CMD_WARNING;
+
+ if (ss_nr >= ts->max_primary_lchans) {
+ vty_out(vty, "%% Invalid sub-slot number for this timeslot type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ 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;
+ }
+
+ lchan = &ts->lchan[ss_nr];
+
+ return trigger_vamos_mode_modify(vty, lchan, strcmp(vamos_str, "vamos") == 0, tsc_set, tsc);
+}
+
/* 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,
@@ -5552,7 +2055,7 @@ DEFUN_HIDDEN(lchan_set_borken, lchan_set_borken_cmd,
}
} else {
if (lchan->fi->state == LCHAN_ST_BORKEN) {
- rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_VTY]);
+ 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,
@@ -5585,14 +2088,14 @@ DEFUN(lchan_mdcx, lchan_mdcx_cmd,
lchan = &ts->lchan[ss_nr];
- if (!is_ipaccess_bts(lchan->ts->trx->bts)) {
- vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+ 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 (ss_nr >= pchan_subslots(ts->pchan_is)) {
+ 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), pchan_subslots(ts->pchan_is),
+ ss_nr, gsm_pchan_name(ts->pchan_is), ts->max_primary_lchans,
VTY_NEWLINE);
return CMD_WARNING;
}
@@ -5605,6 +2108,98 @@ DEFUN(lchan_mdcx, lchan_mdcx_cmd,
return CMD_SUCCESS;
}
+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;
+
+ 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 (!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 (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;
+ }
+
+ 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 (from_vamos)
+ from_ss_nr += from_ts->max_primary_lchans;
+ from_lchan = &from_ts->lchan[from_ss_nr];
+
+ if (to_vamos)
+ to_ss_nr += to_ts->max_primary_lchans;
+ to_lchan = &to_ts->lchan[to_ss_nr];
+
+ 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 (!to_lchan->fi) {
+ vty_out(vty, "cannot re-assign, target lchan is not initialized%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ 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;
+ }
+
+ /* 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));
+
+ 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;
+ }
+ return CMD_SUCCESS;
+}
+
DEFUN(ctrl_trap, ctrl_trap_cmd,
"ctrl-interface generate-trap TRAP VALUE",
"Commands related to the CTRL Interface\n"
@@ -5683,12 +2278,13 @@ DEFUN_USRATTR(cfg_net_mnc,
DEFUN_USRATTR(cfg_net_encryption,
cfg_net_encryption_cmd,
X(BSC_VTY_ATTR_NEW_LCHAN),
- "encryption a5 <0-3> [<0-3>] [<0-3>] [<0-3>]",
+ "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_network *gsmnet = gsmnet_from_vty(vty);
@@ -5726,7 +2322,7 @@ DEFUN_ATTR(cfg_net_timezone,
"Timezone offset (45 minutes)\n",
CMD_ATTR_IMMEDIATE)
{
- struct gsm_network *net = vty->index;
+ struct gsm_network *net = gsmnet_from_vty(vty);
int tzhr = atoi(argv[0]);
int tzmn = atoi(argv[1]);
@@ -5750,7 +2346,7 @@ DEFUN_ATTR(cfg_net_timezone_dst,
"DST offset (hours)\n",
CMD_ATTR_IMMEDIATE)
{
- struct gsm_network *net = vty->index;
+ struct gsm_network *net = gsmnet_from_vty(vty);
int tzhr = atoi(argv[0]);
int tzmn = atoi(argv[1]);
int tzdst = atoi(argv[2]);
@@ -5770,22 +2366,23 @@ DEFUN_ATTR(cfg_net_no_timezone,
"Disable network timezone override, use system tz\n",
CMD_ATTR_IMMEDIATE)
{
- struct gsm_network *net = vty->index;
+ struct gsm_network *net = gsmnet_from_vty(vty);
net->tz.override = 0;
return CMD_SUCCESS;
}
-/* FIXME: changing this value would not affect generated System Information */
-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_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_network *net = vty->index;
+ struct gsm_network *net = gsmnet_from_vty(vty);
struct osmo_tdef *d = osmo_tdef_get_entry(net->T_defs, 3212);
OSMO_ASSERT(d);
@@ -5794,15 +2391,16 @@ DEFUN(cfg_net_per_loc_upd, cfg_net_per_loc_upd_cmd,
return CMD_SUCCESS;
}
-/* FIXME: changing this value would not affect generated System Information */
-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")
+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_network *net = vty->index;
+ struct gsm_network *net = gsmnet_from_vty(vty);
struct osmo_tdef *d = osmo_tdef_get_entry(net->T_defs, 3212);
OSMO_ASSERT(d);
@@ -5839,6 +2437,16 @@ DEFUN_ATTR(cfg_net_meas_feed_scenario, cfg_net_meas_feed_scenario_cmd,
return CMD_SUCCESS;
}
+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)
+{
+ meas_feed_txqueue_max_length_set(atoi(argv[0]));
+ return CMD_SUCCESS;
+}
+
static void legacy_timers(struct vty *vty, const char **T_arg)
{
if (!strcmp((*T_arg), "T993111") || !strcmp((*T_arg), "t993111")) {
@@ -5895,6 +2503,63 @@ DEFUN(cfg_net_allow_unusable_timeslots, cfg_net_allow_unusable_timeslots_cmd,
return CMD_SUCCESS;
}
+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)
+{
+ 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_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)
+{
+ 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_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)
+{
+ 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;
+}
+
static struct bsc_msc_data *bsc_msc_data(struct vty *vty)
{
return vty->index;
@@ -5962,7 +2627,7 @@ static void write_msc_amr_options(struct vty *vty, struct bsc_msc_data *msc)
if (msc->amr_octet_aligned)
vty_out(vty, " amr-payload octet-aligned%s", VTY_NEWLINE);
else
- vty_out(vty, " amr-payload bandwith-efficient%s", VTY_NEWLINE);
+ vty_out(vty, " amr-payload bandwidth-efficient%s", VTY_NEWLINE);
}
static void msc_write_nri(struct vty *vty, struct bsc_msc_data *msc, bool verbose);
@@ -5985,10 +2650,10 @@ static void write_msc(struct vty *vty, struct bsc_msc_data *msc)
if (i != 0)
vty_out(vty, " ");
- if (msc->audio_support[i]->hr)
- vty_out(vty, "hr%.1u", msc->audio_support[i]->ver);
+ if (msc->audio_support[i].hr)
+ vty_out(vty, "hr%u", msc->audio_support[i].ver);
else
- vty_out(vty, "fr%.1u", msc->audio_support[i]->ver);
+ vty_out(vty, "fr%u", msc->audio_support[i].ver);
}
vty_out(vty, "%s", VTY_NEWLINE);
@@ -6061,11 +2726,22 @@ static int config_write_bsc(struct vty *vty)
vty_out(vty, " bsc-auto-rf-off %d%s",
bsc_gsmnet->auto_off_timeout, VTY_NEWLINE);
+ if (bsc_gsmnet->bts_setup_ramp.enabled)
+ vty_out(vty, " bts-setup-ramping%s", VTY_NEWLINE);
+
+ 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);
+
+ 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);
+
return CMD_SUCCESS;
}
-DEFUN_ATTR(cfg_net_bsc_ncc,
- cfg_net_bsc_ncc_cmd,
+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)
@@ -6083,8 +2759,8 @@ DEFUN_ATTR(cfg_net_bsc_ncc,
return CMD_SUCCESS;
}
-DEFUN_ATTR(cfg_net_bsc_mcc,
- cfg_net_bsc_mcc_cmd,
+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)
@@ -6099,8 +2775,8 @@ DEFUN_ATTR(cfg_net_bsc_mcc,
return CMD_SUCCESS;
}
-DEFUN_DEPRECATED(cfg_net_bsc_lac,
- cfg_net_bsc_lac_cmd,
+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")
{
@@ -6108,8 +2784,8 @@ DEFUN_DEPRECATED(cfg_net_bsc_lac,
return CMD_SUCCESS;
}
-DEFUN_DEPRECATED(cfg_net_bsc_ci,
- cfg_net_bsc_ci_cmd,
+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")
{
@@ -6117,8 +2793,8 @@ DEFUN_DEPRECATED(cfg_net_bsc_ci,
return CMD_SUCCESS;
}
-DEFUN_DEPRECATED(cfg_net_bsc_rtp_base,
- cfg_net_bsc_rtp_base_cmd,
+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")
{
@@ -6126,53 +2802,64 @@ DEFUN_DEPRECATED(cfg_net_bsc_rtp_base,
return CMD_SUCCESS;
}
-DEFUN_USRATTR(cfg_net_bsc_codec_list,
- cfg_net_bsc_codec_list_cmd,
+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\n"
- "List of audio codecs, e.g. fr3 fr1 hr3\n")
+ "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")
{
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;
- /* free the old list... if it exists */
- if (data->audio_support) {
- talloc_free(data->audio_support);
- data->audio_support = NULL;
- data->audio_length = 0;
+ /* 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;
}
- /* create a new array */
- data->audio_support =
- talloc_zero_array(bsc_gsmnet, struct gsm_audio_support *, argc);
- data->audio_length = argc;
+ /* check all given arguments first */
+ for (i = 0; i < argc; i++) {
+ int j;
+ int ver;
+ arg = argv[i];
- 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;
+ 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;
+
+ ver = atoi(arg + 2);
+ if (ver < 1 || ver > 7)
+ goto invalid_arg;
+ tmp[i].ver = ver;
- data->audio_support[i] = talloc_zero(data->audio_support,
- struct gsm_audio_support);
- data->audio_support[i]->ver = atoi(argv[i] + 2);
+ if (tmp[i].hr == 1 && ver == 2)
+ goto invalid_arg;
- 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;
+ /* 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;
-error:
- vty_out(vty, "Codec name must be hrX or frX. Was '%s'%s",
- argv[i], VTY_NEWLINE);
- return CMD_ERR_INCOMPLETE;
+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;
}
#define LEGACY_STR "This command has no effect, it is kept to support legacy config files\n"
@@ -6481,6 +3168,88 @@ DEFUN_ATTR(cfg_net_no_rf_off_time,
return CMD_SUCCESS;
}
+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)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ bts_setup_ramp_enable(net);
+ return CMD_SUCCESS;
+}
+
+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_network *net = gsmnet_from_vty(vty);
+ bts_setup_ramp_disable(net);
+ return CMD_SUCCESS;
+}
+
+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 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;
+}
+
+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)
+{
+ 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(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 *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 (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(show_statistics,
show_statistics_cmd,
"show statistics",
@@ -6497,13 +3266,19 @@ DEFUN(show_mscs,
{
struct bsc_msc_data *msc;
llist_for_each_entry(msc, &bsc_gsmnet->mscs, entry) {
- vty_out(vty, "%d %s %s ",
+ vty_out(vty, "MSC %d: %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);
+ 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);
+
}
return CMD_SUCCESS;
@@ -6614,7 +3389,7 @@ DEFUN(show_subscr_all,
vty_out(vty, " IMSI TMSI Use%s", VTY_NEWLINE);
/* " 001010123456789 ffffffff 1" */
- llist_for_each_entry(bsc_subscr, bsc_gsmnet->bsc_subscribers, entry)
+ llist_for_each_entry(bsc_subscr, &bsc_gsmnet->bsc_subscribers->bsub_list, entry)
dump_one_sub(vty, bsc_subscr);
return CMD_SUCCESS;
@@ -6647,7 +3422,7 @@ ALIAS_DEPRECATED(cfg_net_msc_dest, cfg_net_msc_no_dest_cmd,
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|bandwith-efficient",
+ "amr-payload (octet-aligned|bandwidth-efficient)",
"Set AMR payload framing mode\n"
"payload fields aligned on octet boundaries\n"
"payload fields packed (AoIP)\n")
@@ -6656,11 +3431,22 @@ DEFUN_USRATTR(cfg_net_msc_amr_octet_align,
if (strcmp(argv[0], "octet-aligned") == 0)
data->amr_octet_aligned = true;
- else if (strcmp(argv[0], "bandwith-efficient") == 0)
+ 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);
+ }
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_ATTR(cfg_msc_nri_add, cfg_msc_nri_add_cmd,
"nri add <0-32767> [<0-32767>]",
@@ -6810,30 +3596,30 @@ DEFUN_HIDDEN(mscpool_roundrobin_next, mscpool_roundrobin_next_cmd,
return CMD_SUCCESS;
}
-int bsc_vty_init(struct gsm_network *network)
+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")
{
- 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);
+ int msc_nr = atoi(argv[0]);
+ struct bsc_msc_data *msc;
+ msc = osmo_msc_data_find(bsc_gsmnet, msc_nr);
+
+ 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;
+}
+
+int bsc_vty_init(struct gsm_network *network)
+{
OSMO_ASSERT(vty_global_gsm_network == NULL);
vty_global_gsm_network = network;
@@ -6852,14 +3638,19 @@ 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);
@@ -6890,167 +3681,26 @@ int bsc_vty_init(struct gsm_network *network)
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_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_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_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_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_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_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_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);
- 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_arfcn_del_all_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);
@@ -7077,14 +3727,18 @@ int bsc_vty_init(struct gsm_network *network)
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_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_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);
@@ -7104,6 +3758,7 @@ int bsc_vty_init(struct gsm_network *network)
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);
@@ -7114,7 +3769,11 @@ int bsc_vty_init(struct gsm_network *network)
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);
@@ -7129,13 +3788,9 @@ int bsc_vty_init(struct gsm_network *network)
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);
- mgcp_client_vty_init(network, MSC_NODE, network->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);
- install_element(MSC_NODE, &cfg_msc_osmux_cmd);
-
return 0;
}
diff --git a/src/osmo-bsc/bssmap_reset.c b/src/osmo-bsc/bssmap_reset.c
index fcf2bab30..018ecbac1 100644
--- a/src/osmo-bsc/bssmap_reset.c
+++ b/src/osmo-bsc/bssmap_reset.c
@@ -47,7 +47,7 @@ static const struct osmo_tdef_state_timeout bssmap_reset_timeouts[32] = {
osmo_tdef_fsm_inst_state_chg(FI, STATE, \
bssmap_reset_timeouts, \
(bsc_gsmnet)->T_defs, \
- 5)
+ -1)
struct bssmap_reset *bssmap_reset_alloc(void *ctx, const char *label, const struct bssmap_reset_cfg *cfg)
{
@@ -70,6 +70,14 @@ struct bssmap_reset *bssmap_reset_alloc(void *ctx, const char *label, const stru
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");
@@ -190,7 +198,7 @@ static int bssmap_reset_fsm_timer_cb(struct osmo_fsm_inst *fi)
static struct osmo_fsm_state bssmap_reset_fsm_states[] = {
[BSSMAP_RESET_ST_DISC] = {
- .name = "DISC",
+ .name = "DISCONNECTED",
.in_event_mask = 0
| S(BSSMAP_RESET_EV_RX_RESET)
| S(BSSMAP_RESET_EV_RX_RESET_ACK)
@@ -205,7 +213,7 @@ static struct osmo_fsm_state bssmap_reset_fsm_states[] = {
.action = bssmap_reset_disc_action,
},
[BSSMAP_RESET_ST_CONN] = {
- .name = "CONN",
+ .name = "CONNECTED",
.in_event_mask = 0
| S(BSSMAP_RESET_EV_RX_RESET)
| S(BSSMAP_RESET_EV_RX_RESET_ACK)
@@ -235,7 +243,21 @@ bool bssmap_reset_is_conn_ready(const struct bssmap_reset *bssmap_reset)
return bssmap_reset->fi->state == BSSMAP_RESET_ST_CONN;
}
-static __attribute__((constructor)) void bssmap_reset_fsm_init()
+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
index aa7ba1dc0..8cc9e9af0 100644
--- a/src/osmo-bsc/bts.c
+++ b/src/osmo-bsc/bts.c
@@ -1,5 +1,5 @@
/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org>
- * (C) 2020 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
*
* All Rights Reserved
*
@@ -24,6 +24,8 @@
#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),
@@ -47,7 +49,7 @@ const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = {
{ BTS_OSMO_LITECELL15, "osmo-bts-lc15" },
{ BTS_OSMO_OCTPHY, "osmo-bts-octphy" },
{ BTS_OSMO_SYSMO, "osmo-bts-sysmo" },
- { BTS_OSMO_TRX, "omso-bts-trx" },
+ { BTS_OSMO_TRX, "osmo-bts-trx" },
{ 0, NULL }
};
@@ -67,7 +69,7 @@ const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE + 1] = {
{ GSM_BTS_TYPE_NANOBTS, "nanobts" },
{ GSM_BTS_TYPE_RBS2000, "rbs2000" },
{ GSM_BTS_TYPE_NOKIA_SITE, "nokia_site" },
- { GSM_BTS_TYPE_OSMOBTS, "sysmobts" },
+ { GSM_BTS_TYPE_OSMOBTS, "osmo-bts" },
{ 0, NULL }
};
@@ -77,7 +79,7 @@ const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1] = {
{ 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" },
+ { GSM_BTS_TYPE_OSMOBTS, "Osmocom Base Transceiver Station" },
{ 0, NULL }
};
@@ -91,12 +93,6 @@ const char *btstype2str(enum gsm_bts_type type)
return get_value_string(bts_type_names, type);
}
-static void bts_init_cbch_state(struct bts_smscb_chan_state *cstate, struct gsm_bts *bts)
-{
- cstate->bts = bts;
- INIT_LLIST_HEAD(&cstate->messages);
-}
-
static LLIST_HEAD(bts_models);
struct gsm_bts_model *bts_model_find(enum gsm_bts_type type)
@@ -121,9 +117,20 @@ int gsm_bts_model_register(struct gsm_bts_model *model)
return 0;
}
-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 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,
@@ -149,14 +156,25 @@ static const struct gprs_rlc_cfg rlc_cfg_default = {
static int gsm_bts_talloc_destructor(struct gsm_bts *bts)
{
- if (bts->site_mgr.mo.fi) {
- osmo_fsm_inst_free(bts->site_mgr.mo.fi);
- bts->site_mgr.mo.fi = NULL;
+ 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;
}
@@ -164,11 +182,9 @@ static int gsm_bts_talloc_destructor(struct gsm_bts *bts)
* 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 *gsm_bts_alloc(struct gsm_network *net, struct gsm_bts_sm *bts_sm, uint8_t bts_num)
{
- struct gsm_bts *bts = talloc_zero(net, struct gsm_bts);
- struct gsm48_multi_rate_conf mr_cfg;
- int i;
+ struct gsm_bts *bts = talloc_zero(bts_sm, struct gsm_bts);
if (!bts)
return NULL;
@@ -182,35 +198,24 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
bts->ms_max_power = 15; /* dBm */
- bts->site_mgr.mo.fi = osmo_fsm_inst_alloc(&nm_bts_sm_fsm, bts, &bts->site_mgr,
- LOGL_INFO, NULL);
- osmo_fsm_inst_update_id_f(bts->site_mgr.mo.fi, "bts_sm");
- gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff);
+ 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);
- 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);
+ /* 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));
- 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));
-
- /* 3GPP TS 08.18, chapter 5.4.1: 0 is reserved for signalling */
- bts->gprs.cell.bvci = 2;
+ 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);
@@ -220,6 +225,51 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
}
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) {
@@ -231,11 +281,11 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
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;
- bts->paging.free_chans_need = -1;
- INIT_LLIST_HEAD(&bts->paging.pending_requests);
+ paging_init(bts);
bts->features.data = &bts->_features_data[0];
bts->features.data_len = sizeof(bts->_features_data);
@@ -254,6 +304,10 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
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;
@@ -274,18 +328,25 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
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->local_neighbors);
+ 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){
@@ -295,70 +356,201 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
};
/* Set reasonable defaults for AMR-FR and AMR-HR rate configuration.
- * (see also 3GPP TS 28.062, Table 7.11.3.1.3-2) */
- mr_cfg = (struct gsm48_multi_rate_conf) {
+ * 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_15 = 0,
.m5_90 = 1,
- .m6_70 = 0,
- .m7_40 = 1,
- .m7_95 = 0,
- .m10_2 = 0,
+ .m7_95 = 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 = 0;
- bts->mr_full.ms_mode[1].mode = 2;
- bts->mr_full.ms_mode[2].mode = 4;
- bts->mr_full.ms_mode[3].mode = 7;
- bts->mr_full.bts_mode[0].mode = 0;
- bts->mr_full.bts_mode[1].mode = 2;
- bts->mr_full.bts_mode[2].mode = 4;
- 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) {
+ static const struct gsm48_multi_rate_conf amr_hr_mr_cfg = {
.m4_75 = 1,
- .m5_15 = 0,
.m5_90 = 1,
- .m6_70 = 0,
- .m7_40 = 1,
- .m7_95 = 0,
- .m10_2 = 0,
- .m12_2 = 0
+ .m6_70 = 1,
+ .m7_95 = 1,
};
- memcpy(bts->mr_half.gsm48_ie, &mr_cfg, sizeof(bts->mr_half.gsm48_ie));
- bts->mr_half.ms_mode[0].mode = 0;
- bts->mr_half.ms_mode[1].mode = 2;
- bts->mr_half.ms_mode[2].mode = 4;
- bts->mr_half.ms_mode[3].mode = 7;
- bts->mr_half.bts_mode[0].mode = 0;
- bts->mr_half.bts_mode[1].mode = 2;
- bts->mr_half.bts_mode[2].mode = 4;
- bts->mr_half.bts_mode[3].mode = 7;
- 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 = 3;
- bts_init_cbch_state(&bts->cbch_basic, bts);
- bts_init_cbch_state(&bts->cbch_extended, bts);
+ 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)
@@ -387,6 +579,10 @@ bool gsm_bts_matches_cell_id(const struct gsm_bts *bts, const struct gsm0808_cel
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;
@@ -403,96 +599,93 @@ bool gsm_bts_matches_cell_id(const struct gsm_bts *bts, const struct gsm0808_cel
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);
}
}
-static struct gsm_bts_ref *gsm_bts_ref_find(const struct llist_head *list, const struct gsm_bts *bts)
+/* 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)
{
- 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;
+ *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,
+ },
+ };
}
-/* 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)
+/* 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 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 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_lchan *lchan = NULL;
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)
- 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;
- }
+ 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 lchan;
+ 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;
- bts->model = model;
+ gsm_set_bts_model(bts, model);
if (model->start && !model->started) {
int ret = model->start(bts->network);
@@ -502,9 +695,22 @@ int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type)
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_NANOBTS:
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;
@@ -522,17 +728,21 @@ int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type 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->model->features, BTS_FEAT_GPRS)) {
+ !osmo_bts_has_feature(&bts->features, BTS_FEAT_GPRS)) {
return 0;
}
if (mode == BTS_GPRS_EGPRS &&
- !osmo_bts_has_feature(&bts->model->features, BTS_FEAT_EGPRS)) {
+ !osmo_bts_has_feature(&bts->features, BTS_FEAT_EGPRS)) {
return 0;
}
@@ -554,22 +764,81 @@ struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num)
return NULL;
}
-unsigned long long bts_uptime(const struct gsm_bts *bts)
+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->uptime || !bts->oml_link) {
- LOGP(DNM, LOGL_ERROR, "BTS %u OML link uptime unavailable\n", bts->nr);
+ if (!bts->updowntime)
return 0;
- }
- if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) {
- LOGP(DNM, LOGL_ERROR, "BTS %u uptime computation failure: %s\n", bts->nr, strerror(errno));
+ 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->uptime);
+ return difftime(tp.tv_sec, bts->updowntime);
}
char *get_model_oml_status(const struct gsm_bts *bts)
@@ -587,10 +856,6 @@ void gsm_bts_mo_reset(struct gsm_bts *bts)
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) {
@@ -617,7 +882,7 @@ 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;
+ bts->depends_on[idx] |= 1U << bit;
}
void bts_depend_clear(struct gsm_bts *bts, int dep)
@@ -625,7 +890,7 @@ 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);
+ bts->depends_on[idx] &= ~(1U << bit);
}
int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other)
@@ -634,17 +899,17 @@ int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other)
depends_calc_index_bit(other->nr, &idx, &bit);
/* Check if there is a depends bit */
- return (base->depends_on[idx] & (1 << bit)) > 0;
+ return (base->depends_on[idx] & (1U << bit)) > 0;
}
-static int bts_is_online(struct gsm_bts *bts)
+static bool bts_is_online(const struct gsm_bts *bts)
{
/* TODO: support E1 BTS too */
- if (!is_ipaccess_bts(bts))
- return 1;
+ if (!is_ipa_abisip_bts(bts))
+ return true;
if (!bts->oml_link)
- return 0;
+ return false;
return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED;
}
@@ -707,19 +972,6 @@ void gsm_bts_all_ts_dispatch(struct gsm_bts *bts, uint32_t ts_ev, void *data)
gsm_trx_all_ts_dispatch(trx, ts_ev, data);
}
-
-/* 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;
-}
-
/* set all system information types for a BTS */
int gsm_bts_set_system_infos(struct gsm_bts *bts)
{
@@ -740,19 +992,153 @@ int gsm_bts_set_system_infos(struct gsm_bts *bts)
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" },
@@ -761,7 +1147,7 @@ const struct rate_ctr_desc bts_ctr_description[] = {
"Received a TEI down on a OML link" },
[BTS_CTR_BTS_RSL_FAIL] = \
{ "rsl_fail",
- "Received a TEI down on a OML link" },
+ "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" },
@@ -795,12 +1181,33 @@ const struct rate_ctr_desc bts_ctr_description[] = {
[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" },
@@ -851,6 +1258,9 @@ const struct rate_ctr_desc bts_ctr_description[] = {
[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" },
@@ -887,25 +1297,78 @@ const struct rate_ctr_desc bts_ctr_description[] = {
[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" },
@@ -952,10 +1415,10 @@ const struct rate_ctr_desc bts_ctr_description[] = {
[BTS_CTR_INTRA_BSC_HO_ATTEMPTED] = \
{ "intra_bsc_ho:attempted",
- "Intra-BSC handover attempts" },
+ "Intra-BSC inter-cell handover attempts" },
[BTS_CTR_INTRA_BSC_HO_COMPLETED] = \
{ "intra_bsc_ho:completed",
- "Intra-BSC handover completed" },
+ "Intra-BSC inter-cell handover completed" },
[BTS_CTR_INTRA_BSC_HO_STOPPED] = \
{ "intra_bsc_ho:stopped",
"Connection ended during HO" },
@@ -970,7 +1433,29 @@ const struct rate_ctr_desc bts_ctr_description[] = {
"Received Handover Fail messages" },
[BTS_CTR_INTRA_BSC_HO_ERROR] = \
{ "intra_bsc_ho:error",
- "Re-assignment failed for other reason" },
+ "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",
@@ -1011,6 +1496,115 @@ const struct rate_ctr_desc bts_ctr_description[] = {
[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 = {
@@ -1022,93 +1616,97 @@ const struct rate_ctr_group_desc bts_ctrg_desc = {
};
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",
- "%", 16, 0 },
+ "%", 60, 0 },
[BTS_STAT_CHAN_CCCH_SDCCH4_USED] = \
{ "chan_ccch_sdcch4:used",
"Number of CCCH+SDCCH4 channels used",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_CCCH_SDCCH4_TOTAL] = \
{ "chan_ccch_sdcch4:total",
"Number of CCCH+SDCCH4 channels total",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_TCH_F_USED] = \
{ "chan_tch_f:used",
"Number of TCH/F channels used",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_TCH_F_TOTAL] = \
{ "chan_tch_f:total",
"Number of TCH/F channels total",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_TCH_H_USED] = \
{ "chan_tch_h:used",
"Number of TCH/H channels used",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_TCH_H_TOTAL] = \
{ "chan_tch_h:total",
"Number of TCH/H channels total",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_SDCCH8_USED] = \
{ "chan_sdcch8:used",
"Number of SDCCH8 channels used",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_SDCCH8_TOTAL] = \
{ "chan_sdcch8:total",
"Number of SDCCH8 channels total",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_TCH_F_PDCH_USED] = \
- { "chan_tch_f_pdch:used",
- "Number of TCH/F_PDCH channels used",
- "", 16, 0 },
+ { "chan_dynamic_ipaccess:used",
+ "Number of DYNAMIC/IPACCESS channels used",
+ "", 60, 0 },
[BTS_STAT_CHAN_TCH_F_PDCH_TOTAL] = \
- { "chan_tch_f_pdch:total",
- "Number of TCH/F_PDCH channels total",
- "", 16, 0 },
+ { "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",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_TOTAL] = \
{ "chan_ccch_sdcch4_cbch:total",
"Number of CCCH+SDCCH4+CBCH channels total",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_SDCCH8_CBCH_USED] = \
{ "chan_sdcch8_cbch:used",
"Number of SDCCH8+CBCH channels used",
- "", 16, 0 },
+ "", 60, 0 },
[BTS_STAT_CHAN_SDCCH8_CBCH_TOTAL] = \
{ "chan_sdcch8_cbch:total",
"Number of SDCCH8+CBCH channels total",
- "", 16, 0 },
- [BTS_STAT_CHAN_TCH_F_TCH_H_PDCH_USED] = \
- { "chan_tch_f_tch_h_pdch:used",
- "Number of TCH/F_TCH/H_PDCH channels used",
- "", 16, 0 },
- [BTS_STAT_CHAN_TCH_F_TCH_H_PDCH_TOTAL] = \
- { "chan_tch_f_tch_h_pdch:total",
- "Number of TCH/F_TCH/H_PDCH channels total",
- "", 16, 0 },
+ "", 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", 16, GSM_T3122_DEFAULT },
+ "s", 60, GSM_T3122_DEFAULT },
[BTS_STAT_RACH_BUSY] = \
{ "rach_busy",
"RACH slots with signal above threshold",
- "%", 16, 0 },
+ "%", 60, 0 },
[BTS_STAT_RACH_ACCESS] = \
{ "rach_access",
"RACH slots with access bursts in them",
- "%", 16, 0 },
+ "%", 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",
+ "Number of RSL links connected (same as num_trx:rsl_connected)",
"", 16, 0 },
[BTS_STAT_LCHAN_BORKEN] = \
{ "lchan_borken",
@@ -1118,6 +1716,26 @@ const struct osmo_stat_item_desc bts_stat_desc[] = {
{ "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 = {
@@ -1127,3 +1745,20 @@ const struct osmo_stat_item_group_desc bts_statg_desc = {
.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 1297b3025..36b378318 100644
--- a/src/osmo-bsc/bts_ericsson_rbs2000.c
+++ b/src/osmo-bsc/bts_ericsson_rbs2000.c
@@ -35,16 +35,8 @@
static void bootstrap_om_bts(struct gsm_bts *bts)
{
- struct gsm_bts_trx *trx;
-
LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
- /* Global init (not bootstrapping) */
- abis_om2k_bts_init(bts);
-
- llist_for_each_entry(trx, &bts->trx_list, list)
- abis_om2k_trx_init(trx);
-
/* TODO: Should we wait for a Failure report? */
om2k_bts_fsm_start(bts);
}
@@ -53,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;
@@ -145,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);
@@ -177,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)
@@ -184,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);
@@ -204,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 577be13f7..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>
@@ -46,11 +49,16 @@
#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)
@@ -66,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.
@@ -118,6 +131,7 @@ struct gsm_bts_model bts_model_nanobts = {
[NM_ATT_IPACC_REVOC_DATE] = { TLV_TYPE_TL16V },
},
},
+ .features_get_reported = false,
};
@@ -126,29 +140,17 @@ 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 (obj_class != NM_OC_BTS &&
- obj_class != NM_OC_BASEB_TRANSC &&
- obj_class != NM_OC_RADIO_CARRIER &&
- obj_class != NM_OC_CHANNEL &&
- evt != S_NM_STATECHG_OPER)
+ if (!is_ipa_abisip_bts(nsd->bts))
return 0;
switch (obj_class) {
@@ -166,7 +168,6 @@ static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd)
break;
case NM_OC_CHANNEL:
ts = obj;
- trx = ts->trx;
osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_RADIO_CARRIER:
@@ -174,64 +175,17 @@ static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd)
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_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);
- abis_nm_opstart(bts, obj_class, bts->bts_nr,
- 0, 0xff);
- }
+ 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 (!osmo_bts_has_feature(&bts->features, BTS_FEAT_IPV6_NSVC) &&
- nsvc->remote.u.sa.sa_family == AF_INET6) {
- LOGP(DLINP, LOGL_ERROR, "BTS %d does not support IPv6 but an IPv6 address was configured!\n", bts->nr);
- 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_chg_adm_state(bts, obj_class, bts->bts_nr,
- nsvc->id, 0xff,
- NM_STATE_UNLOCKED);
- abis_nm_opstart(bts, obj_class, bts->bts_nr,
- nsvc->id, 0xff);
- }
+ osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_STATE_CHG_REP, nsd);
+ break;
default:
break;
}
@@ -241,121 +195,95 @@ 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;
- struct gsm_bts_trx_ts *ts;
+ struct gsm_abis_mo *mo;
+ struct tlv_parsed tp;
- if (!is_ipaccess_bts(bts))
+ if (!is_ipa_abisip_bts(bts))
return 0;
- switch (foh->obj_class) {
- case NM_OC_SITE_MANAGER:
- osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_SW_ACT_REP, NULL);
- break;
- case NM_OC_BTS:
- osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_SW_ACT_REP, NULL);
- break;
- case NM_OC_BASEB_TRANSC:
- if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr)))
- return -EINVAL;
- osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT_REP, NULL);
- break;
- case NM_OC_RADIO_CARRIER:
- if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr)))
- return -EINVAL;
- osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT_REP, NULL);
- break;
- case NM_OC_CHANNEL:
- if (!(ts = abis_nm_get_ts(mb)))
- return -EINVAL;
- osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_SW_ACT_REP, NULL);
- break;
+ 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;
+ }
+
+ 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) };
+
+ /* 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() */
- return;
- if (!ts->fi) {
- LOG_TS(ts, LOGL_ERROR, "Channel OPSTART ACK for uninitialized TS\n");
+ 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;
- }
- osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_OPSTART_ACK, NULL);
- osmo_fsm_inst_dispatch(ts->fi, TS_EV_OML_READY, NULL);
+
+ 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_ack(struct msgb *oml_msg)
+static void nm_rx_opstart_nack(struct msgb *oml_msg)
{
- struct abis_om_fom_hdr *foh = msgb_l3(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_bts_trx *trx;
+ struct gsm_abis_mo *mo;
- switch (foh->obj_class) {
- case NM_OC_SITE_MANAGER:
- osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_OPSTART_ACK, NULL);
- break;
- case NM_OC_BTS:
- osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OPSTART_ACK, NULL);
- break;
- case NM_OC_RADIO_CARRIER:
- if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr)))
- return;
- osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
- break;
- case NM_OC_BASEB_TRANSC:
- if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr)))
- return;
- osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_OPSTART_ACK, NULL);
- break;
- case NM_OC_CHANNEL:
- nm_rx_opstart_ack_chan(oml_msg);
- break;
- default:
- break;
- }
+ 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_opstart_nack(struct msgb *oml_msg)
+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_bts_trx *trx;
- struct gsm_bts_trx_ts *ts;
+ struct gsm_abis_mo *mo;
- switch (foh->obj_class) {
- case NM_OC_SITE_MANAGER:
- osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_OPSTART_NACK, NULL);
- break;
- case NM_OC_BTS:
- osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OPSTART_ACK, NULL);
- break;
- case NM_OC_RADIO_CARRIER:
- if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr)))
- return;
- osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, NULL);
- break;
- case NM_OC_BASEB_TRANSC:
- if (!(trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr)))
- return;
- osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_OPSTART_NACK, NULL);
- break;
- case NM_OC_CHANNEL:
- if (!(ts = abis_nm_get_ts(oml_msg)))
- return;
- osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_OPSTART_NACK, NULL);
- break;
- default:
- break;
- }
+ 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)
@@ -364,6 +292,9 @@ static void nm_rx_set_bts_attr_ack(struct msgb *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;
@@ -377,8 +308,12 @@ 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 = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ struct gsm_bts_trx *trx;
+
+ if (!is_ipa_abisip_bts(bts))
+ return;
+ 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;
@@ -389,8 +324,14 @@ static void nm_rx_set_radio_attr_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 gsm_bts_trx_ts *ts = abis_nm_get_ts(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;
@@ -398,6 +339,91 @@ static void nm_rx_set_chan_attr_ack(struct msgb *oml_msg)
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_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;
+ }
+}
+
/* Callback function to be called every time we receive a signal from NM */
static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
@@ -408,8 +434,7 @@ 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);
@@ -417,6 +442,9 @@ static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal,
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;
@@ -426,6 +454,12 @@ static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal,
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;
}
@@ -461,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 &&
@@ -474,13 +508,13 @@ 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, const char *reason)
{
- if (!trx->rsl_link)
+ if (!trx->rsl_link_primary)
return;
LOG_TRX(trx, DLINP, LOGL_NOTICE, "Dropping RSL link: %s\n", reason);
- e1inp_sign_link_destroy(trx->rsl_link);
- trx->rsl_link = NULL;
- osmo_stat_item_dec(trx->bts->bts_statg->items[BTS_STAT_RSL_CONNECTED], 1);
+ 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);
@@ -492,6 +526,9 @@ void ipaccess_drop_oml(struct gsm_bts *bts, const char *reason)
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);
@@ -502,8 +539,17 @@ void ipaccess_drop_oml(struct gsm_bts *bts, const char *reason)
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;
- osmo_stat_item_dec(bts->bts_statg->items[BTS_STAT_OML_CONNECTED], 1);
+ 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) {
@@ -516,12 +562,22 @@ void ipaccess_drop_oml(struct gsm_bts *bts, const char *reason)
}
}
- osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_OML_DOWN, NULL);
osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OML_DOWN, NULL);
- gsm_bts_all_ts_dispatch(bts, TS_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
@@ -574,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)))
@@ -621,15 +677,24 @@ 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, "%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. */
@@ -644,17 +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(bts->bts_statg->items[BTS_STAT_OML_CONNECTED], 1);
+ 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;
@@ -669,12 +738,12 @@ ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line,
/* 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,
@@ -682,7 +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(bts->bts_statg->items[BTS_STAT_RSL_CONNECTED], 1);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_RSL_CONNECTED), 1);
break;
}
default:
@@ -694,7 +763,7 @@ 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;
@@ -727,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);
@@ -753,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 b7239326c..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
@@ -26,19 +26,92 @@
#include <osmocom/bsc/bts.h>
#include <osmocom/gsm/bts_features.h>
-struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts)
+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 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)
+{
+ 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;
+}
+
+/* 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)
+{
+ 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;
+}
+
+/* 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 */
@@ -51,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 */
+ /* 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) {
@@ -81,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, osmo_tdef_get(bts->network->T_defs, 3105, OSMO_TDEF_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);
@@ -99,111 +177,170 @@ 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);
- switch (bts->gprs.nsvc->remote.u.sa.sa_family) {
+ switch (nsvc->remote.u.sa.sa_family) {
case AF_INET6:
/* all fields are encoded in network byte order */
/* protocol family */
@@ -211,20 +348,20 @@ struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts)
/* padding */
buf[1] = 0x00;
/* local udp port */
- osmo_store16be(bts->gprs.nsvc[0].local_port, &buf[2]);
+ osmo_store16be(nsvc->local_port, &buf[2]);
/* remote udp port */
- memcpy(&buf[4], &bts->gprs.nsvc[0].remote.u.sin6.sin6_port, sizeof(uint16_t));
+ memcpy(&buf[4], &nsvc->remote.u.sin6.sin6_port, sizeof(uint16_t));
/* remote ip address */
- memcpy(&buf[6], &bts->gprs.nsvc[0].remote.u.sin6.sin6_addr, sizeof(struct in6_addr));
+ 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], &bts->gprs.nsvc[0].remote.u.sin.sin_port, sizeof(uint16_t));
+ memcpy(&buf[0], &nsvc->remote.u.sin.sin_port, sizeof(uint16_t));
/* remote ip address */
- memcpy(&buf[2], &bts->gprs.nsvc[0].remote.u.sin.sin_addr, sizeof(struct in_addr));
+ memcpy(&buf[2], &nsvc->remote.u.sin.sin_addr, sizeof(struct in_addr));
/* local udp port */
- osmo_store16be(bts->gprs.nsvc[0].local_port, &buf[6]);
+ osmo_store16be(nsvc->local_port, &buf[6]);
msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_LINK_CFG, 8, buf);
break;
default:
@@ -234,12 +371,15 @@ struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts)
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 2b6f91876..dc8ff1495 100644
--- a/src/osmo-bsc/bts_nokia_site.c
+++ b/src/osmo-bsc/bts_nokia_site.c
@@ -80,6 +80,7 @@ static void bootstrap_om_trx(struct gsm_bts_trx *trx)
static int shutdown_om(struct gsm_bts *bts)
{
+ gsm_bts_stats_reset(bts);
/* TODO !? */
return 0;
}
@@ -203,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:
@@ -797,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;
@@ -806,6 +809,9 @@ 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;
@@ -1332,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) {
@@ -1391,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) {
@@ -1452,7 +1458,7 @@ static void nokia_abis_nm_fake_1221_ok(struct gsm_bts *bts)
struct gsm_bts_trx *trx;
mo_ok(&bts->mo);
- mo_ok(&bts->site_mgr.mo);
+ mo_ok(&bts->site_mgr->mo);
llist_for_each_entry(trx, &bts->trx_list, list) {
int i;
@@ -1758,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);
@@ -1777,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 08694eab0..9e4f09103 100644
--- a/src/osmo-bsc/bts_siemens_bs11.c
+++ b/src/osmo-bsc/bts_siemens_bs11.c
@@ -401,7 +401,7 @@ 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;
switch (ts->pchan_from_config) {
@@ -423,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++)
@@ -484,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);
@@ -552,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;
@@ -603,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);
@@ -618,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 d7d15ebc2..000000000
--- a/src/osmo-bsc/bts_sysmobts.c
+++ /dev/null
@@ -1,67 +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/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_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, 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);
- osmo_bts_set_feature(&model_sysmobts.features, BTS_FEAT_PAGING_COORDINATION);
-
- model_sysmobts.nm_att_tlvdef.def[NM_ATT_OSMO_NS_LINK_CFG].type = TLV_TYPE_TL16V;
-
- return gsm_bts_model_register(&model_sysmobts);
-}
diff --git a/src/osmo-bsc/bts_trx.c b/src/osmo-bsc/bts_trx.c
index 0e5223874..4d2588d5b 100644
--- a/src/osmo-bsc/bts_trx.c
+++ b/src/osmo-bsc/bts_trx.c
@@ -31,6 +31,7 @@
#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)
{
@@ -50,6 +51,7 @@ static int gsm_bts_trx_talloc_destructor(struct gsm_bts_trx *trx)
osmo_fsm_inst_free(ts->mo.fi);
ts->mo.fi = NULL;
}
+ ts_fsm_free(ts);
}
return 0;
}
@@ -67,6 +69,8 @@ struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts)
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);
@@ -104,22 +108,21 @@ struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts)
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);
+ 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;
@@ -146,37 +149,98 @@ struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
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;
- if (cbits == 0x01) {
+ /* 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? */
- } else if ((cbits & 0x1e) == 0x02) {
+ 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);
- } else if ((cbits & 0x1c) == 0x04) {
+ 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);
- } else if ((cbits & 0x18) == 0x08) {
+ 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);
- } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) {
+ 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 !!! */
- } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) {
+ break;
+ case ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH:
lch_idx = 0;
- ok = (ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH);
- } else
+ 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];
}
@@ -200,7 +264,7 @@ void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason)
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 (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;
@@ -223,54 +287,6 @@ void gsm_trx_all_ts_dispatch(struct gsm_bts_trx *trx, uint32_t ts_ev, void *data
}
}
-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;
-}
-
bool trx_has_valid_pchan_config(const struct gsm_bts_trx *trx)
{
bool combined = false;
@@ -310,6 +326,23 @@ bool trx_has_valid_pchan_config(const struct gsm_bts_trx *trx)
}
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) {
@@ -317,6 +350,20 @@ bool trx_has_valid_pchan_config(const struct gsm_bts_trx *trx)
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;
@@ -361,7 +408,7 @@ static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len)
/* set all system information types for a TRX */
int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx)
{
- int i, rc;
+ 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];
@@ -399,42 +446,52 @@ int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx)
/* Second, we generate the selected SI via RSL */
for (n = 0; n < n_si; n++) {
- i = gen_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 << i))) {
+ 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 << i);
- rc = gsm_generate_si(bts, i);
+ bts->si_valid |= (1 << si_type);
+ rc = gsm_generate_si(bts, si_type);
if (rc < 0)
goto err_out;
- si_len[i] = rc;
+ si_len[si_type] = 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;
+ 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++) {
- i = gen_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, i)) {
+ if (!GSM_BTS_HAS_SI(bts, si_type)) {
if (bts->si_unused_send_empty)
- rc = rsl_si(trx, i, 0);
+ 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, i, si_len[i]);
+ rc = rsl_si(trx, si_type, si_len[si_type]);
if (rc < 0)
return rc;
}
@@ -447,6 +504,6 @@ int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx)
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));
+ 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_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
index 802180419..d699e7ede 100644
--- a/src/osmo-bsc/cbch_scheduler.c
+++ b/src/osmo-bsc/cbch_scheduler.c
@@ -60,6 +60,9 @@ static int bts_smscb_sched_add_before(struct bts_smscb_page **sched_arr, int sch
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--;
@@ -132,7 +135,7 @@ int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smsc
}
last_page = rc;
- while (last_page < cstate->sched_arr_size) {
+ 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,
@@ -243,7 +246,7 @@ static void bts_cbch_send_one(struct bts_smscb_chan_state *cstate)
bts_smscb_page_done(cstate, page);
}
-static void bts_cbch_timer(void *data)
+void bts_cbch_timer_cb(void *data)
{
struct gsm_bts *bts = (struct gsm_bts *)data;
@@ -256,7 +259,6 @@ static void bts_cbch_timer(void *data)
/* 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_setup(&bts->cbch_timer, &bts_cbch_timer, bts);
osmo_timer_schedule(&bts->cbch_timer, 1, 882920);
}
diff --git a/src/osmo-bsc/cbsp_link.c b/src/osmo-bsc/cbsp_link.c
index 492679942..9e17ef373 100644
--- a/src/osmo-bsc/cbsp_link.c
+++ b/src/osmo-bsc/cbsp_link.c
@@ -21,7 +21,6 @@
#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>
@@ -55,7 +54,6 @@ const struct osmo_sockaddr_str bsc_cbc_default_server_local_addr = {
static int cbsp_srv_closed_cb(struct osmo_stream_srv *conn)
{
struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn);
- //struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
LOGP(DCBS, LOGL_NOTICE, "CBSP Server lost connection from %s\n", cbc->server.sock_name);
talloc_free(cbc->server.sock_name);
@@ -64,29 +62,28 @@ static int cbsp_srv_closed_cb(struct osmo_stream_srv *conn)
return 0;
}
-static int cbsp_srv_cb(struct osmo_stream_srv *conn)
+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_fd *ofd = osmo_stream_srv_get_ofd(conn);
struct osmo_cbsp_decoded *decoded;
- struct msgb *msg;
- int rc;
- /* READ */
- rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->server.msg);
- if (rc <= 0) {
- if (rc == -EAGAIN || rc == -EINTR) {
- /* more data needs to be read */
+ if (res <= 0) {
+ if (res == -EAGAIN || res == -EINTR) {
+ msgb_free(msg);
return 0;
- } else if (rc == -EPIPE || rc == -ECONNRESET) {
- /* lost connection */
- } else if (rc == 0) {
- /* connection closed */
}
+ /*
+ 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) {
@@ -118,12 +115,15 @@ static int cbsp_srv_link_accept_cb(struct osmo_stream_srv_link *link, int fd)
return -1;
}
- srv = osmo_stream_srv_create(cbc, link, fd, cbsp_srv_cb, cbsp_srv_closed_cb, cbc);
+ 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)
@@ -142,11 +142,10 @@ static int cbsp_srv_link_accept_cb(struct osmo_stream_srv_link *link, int fd)
static int cbsp_client_connect_cb(struct osmo_stream_cli *cli)
{
struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli);
- struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli);
if (cbc->client.sock_name)
talloc_free(cbc->client.sock_name);
- cbc->client.sock_name = osmo_sock_get_name(cbc, ofd->fd);
+ 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);
@@ -166,28 +165,27 @@ static int cbsp_client_disconnect_cb(struct osmo_stream_cli *cli)
return 0;
}
-static int cbsp_client_read_cb(struct osmo_stream_cli *cli)
+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_fd *ofd = osmo_stream_cli_get_ofd(cli);
struct osmo_cbsp_decoded *decoded;
- struct msgb *msg = NULL;
- int rc;
-
- /* READ */
- rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->client.msg);
- if (rc <= 0) {
- if (rc == -EAGAIN || rc == -EINTR) {
- /* more data needs to be read */
+
+ if (res <= 0) {
+ if (res == -EAGAIN || res == -EINTR) {
+ msgb_free(msg);
return 0;
- } else if (rc == -EPIPE || rc == -ECONNRESET) {
- /* lost connection */
- } else if (rc == 0) {
- /* connection closed */
}
+ /*
+ 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) {
@@ -245,7 +243,8 @@ int bsc_cbc_link_restart(void)
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_cb(cbc->client.cli, cbsp_client_read_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);
@@ -301,6 +300,13 @@ 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",
@@ -312,349 +318,7 @@ int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *cbsp)
osmo_stream_cli_send(cbc->client.cli, msg);
else if (cbc->server.srv)
osmo_stream_srv_send(cbc->server.srv, msg);
- else {
- LOGP(DCBS, LOGL_ERROR, "Discarding CBSP Message, link is down: %s\n", msgb_hexdump(msg));
- msgb_free(msg);
- }
talloc_free(cbsp);
return 0;
}
-
-static struct bsc_cbc_link *vty_cbc_data(struct vty *vty)
-{
- return bsc_gsmnet->cbc;
-}
-
-/*********************************************************************************
- * VTY Interface (Configuration + Introspection)
- *********************************************************************************/
-
-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);
-}
diff --git a/src/osmo-bsc/chan_alloc.c b/src/osmo-bsc/chan_alloc.c
index 3569d4eaa..cc8da3daf 100644
--- a/src/osmo-bsc/chan_alloc.c
+++ b/src/osmo-bsc/chan_alloc.c
@@ -44,8 +44,12 @@ 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;
+ /* init per-TRX load counters */
+ memset(ll, 0, sizeof(*ll));
+
/* skip administratively deactivated transceivers */
if (!trx_is_usable(trx))
continue;
@@ -59,38 +63,32 @@ void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
if (!nm_is_running(&ts->mo.nm_state))
continue;
- /* Dynamic timeslots have to be counted separately
- * when not in TCH/F or TCH/H mode because they don't
- * have an lchan's allocated to them. At the same time,
- * dynamic timeslots in NONE and PDCH modes are same
- * as in UNUSED mode from the CS channel load perspective
- * beause they can be switched to TCH mode at any moment.
- * I.e. they are "available" for TCH. */
- if ((ts->pchan_on_init == GSM_PCHAN_TCH_F_TCH_H_PDCH ||
+ /* 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;
}
- /* Count allocated logical channels.
- * Note: A GSM_PCHAN_TCH_F_TCH_H_PDCH can be switched
- * to a single TCH/F or to two TCH/H. So when it's in
- * the TCH/H mode, total number of available channels
- * is 1 more than when it's in the TCH/F mode.
- * I.e. "total" count will fluctuate depending on
- * whether GSM_PCHAN_TCH_F_TCH_H_PDCH timeslot is
- * in TCH/F or TCH/H (or in NONE/PDCH) mode. */
- ts_for_each_lchan(lchan, ts) {
+ 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;
}
@@ -99,6 +97,7 @@ void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts)
case LCHAN_ST_UNUSED:
break;
default:
+ ll->used++;
pl->used++;
break;
}
@@ -129,36 +128,36 @@ static void chan_load_stat_set(enum gsm_phys_chan_config pchan,
case GSM_PCHAN_UNKNOWN:
break;
case GSM_PCHAN_CCCH_SDCCH4:
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_CCCH_SDCCH4_USED], lc->used);
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_CCCH_SDCCH4_TOTAL], lc->total);
+ 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(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_USED], lc->used);
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_TOTAL], lc->total);
+ 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(bts->bts_statg->items[BTS_STAT_CHAN_TCH_H_USED], lc->used);
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_H_TOTAL], lc->total);
+ 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(bts->bts_statg->items[BTS_STAT_CHAN_SDCCH8_USED], lc->used);
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_SDCCH8_TOTAL], lc->total);
+ 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(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_PDCH_USED], lc->used);
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_PDCH_TOTAL], lc->total);
+ 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(bts->bts_statg->items[BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_USED], lc->used);
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_CCCH_SDCCH4_CBCH_TOTAL], lc->total);
+ 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(bts->bts_statg->items[BTS_STAT_CHAN_SDCCH8_CBCH_USED], lc->used);
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_SDCCH8_CBCH_TOTAL], lc->total);
+ 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_TCH_F_TCH_H_PDCH:
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_TCH_H_PDCH_USED], lc->used);
- osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_TCH_F_TCH_H_PDCH_TOTAL], lc->total);
+ 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);
@@ -240,7 +239,7 @@ bts_update_t3122_chan_load(struct gsm_bts *bts)
(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);
@@ -252,5 +251,5 @@ bts_update_t3122_chan_load(struct gsm_bts *bts)
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 58a7867c5..454b00bea 100644
--- a/src/osmo-bsc/codec_pref.c
+++ b/src/osmo-bsc/codec_pref.c
@@ -61,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) {
@@ -88,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) {
@@ -125,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,
@@ -177,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
* 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 */
@@ -217,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;
}
@@ -308,8 +307,8 @@ static int match_amr_s15_s0(struct channel_mode_and_rate *ch_mode_rate, const st
return 0;
}
-/*! Match the codec preferences from local config with a received codec preferences IEs received from the
- * MSC and the BTS' codec configuration.
+/*! 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).
@@ -336,7 +335,7 @@ int match_codec_pref(struct channel_mode_and_rate *ch_mode_rate,
* 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);
@@ -376,7 +375,7 @@ int match_codec_pref(struct channel_mode_and_rate *ch_mode_rate,
break;
}
- /* Exit without result, in case no match can be deteched */
+ /* Exit without result, in case no match can be detected */
if (!match) {
ch_mode_rate->chan_mode = GSM48_CMODE_SIGN;
ch_mode_rate->chan_rate = CH_RATE_SDCCH;
@@ -407,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 */
@@ -422,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++;
}
@@ -482,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 9ccbbfd70..dbea3e9e0 100644
--- a/src/osmo-bsc/e1_config.c
+++ b/src/osmo-bsc/e1_config.c
@@ -90,7 +90,7 @@ 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) {
LOG_TRX(trx, DLINP, LOGL_ERROR, "TRX OML link creation failed\n");
return -ENOMEM;
@@ -100,14 +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) {
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]);
@@ -196,8 +196,8 @@ int e1_reconfig_bts(struct gsm_bts *bts)
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 a44812618..194432125 100644
--- a/src/osmo-bsc/gsm_04_08_rr.c
+++ b/src/osmo-bsc/gsm_04_08_rr.c
@@ -46,17 +46,26 @@
#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;
@@ -232,11 +241,11 @@ 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)
@@ -244,7 +253,7 @@ static void mr_config_for_ms(struct gsm_lchan *lchan, struct msgb *msg)
#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 TF 44.018 version 15.3.0 Table 10.5.2.1e.1. This only generates the "value"
+ * 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)
{
@@ -299,12 +308,15 @@ static int generate_cell_sel_ind_after_rel(uint8_t *out, unsigned int out_len, c
}
}
+#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;
@@ -313,10 +325,10 @@ int gsm48_send_rr_release(struct gsm_lchan *lchan)
cause = msgb_put(msg, 1);
cause[0] = lchan->release.rr_cause;
- if (lchan->release.is_csfb) {
+ 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 "
@@ -325,11 +337,21 @@ int gsm48_send_rr_release(struct gsm_lchan *lchan)
msgb_tlv_put(msg, GSM48_IE_CELL_SEL_IND_AFTER_REL, len, buf);
}
- DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d RR-Cause: 0x%x '%s'\n",
- lchan->nr, lchan->type, lchan->release.rr_cause, rr_cause_name(lchan->release.rr_cause));
+ 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,
@@ -358,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);
}
@@ -368,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;
@@ -397,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)
{
@@ -413,15 +434,17 @@ 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 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 inconsistent config (duplicate modes).\n");
return -EINVAL;
@@ -482,52 +505,91 @@ 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;
si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1);
@@ -536,9 +598,8 @@ struct msgb *gsm48_make_ho_cmd(struct gsm_lchan *new_lchan, uint8_t power_comman
GSM48_HOCMD_CCHDESC_LEN,
si1->cell_channel_description);
}
- /* FIXME: optional bits for type of synchronization? */
- msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->tch_mode);
+ 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) {
@@ -547,35 +608,45 @@ struct msgb *gsm48_make_ho_cmd(struct gsm_lchan *new_lchan, uint8_t power_comman
new_lchan->ts->hopping.ma_data);
}
+ /* (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;
@@ -587,26 +658,45 @@ 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;
/* Cell Channel Description (freq. hopping), TV (see 3GPP TS 44.018, 10.5.2.1b) */
- if (lchan->ts->hopping.enabled) {
- uint8_t *chan_desc = msgb_put(msg, 1 + 16); /* tag + fixed length */
- generate_cell_chan_list(chan_desc + 1, dest_lchan->ts->trx->bts);
- chan_desc[0] = GSM48_IE_CELL_CH_DESC;
+ 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 (freq. hopping), TLV (see 3GPP TS 44.018, 10.5.2.21) */
- if (lchan->ts->hopping.enabled) {
- msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len,
- lchan->ts->hopping.ma_data);
+ 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);
}
@@ -636,29 +726,107 @@ int gsm48_send_rr_app_info(struct gsm_lchan *lchan, uint8_t apdu_id, uint8_t apd
/* 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)
{
struct gsm48_hdr *gh = msgb_l3(msg);
@@ -668,33 +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++;
}
- return 0;
+ /* 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++;
+ }
+
+ /* 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)
@@ -702,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;
@@ -727,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;
@@ -739,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;
@@ -747,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;
@@ -755,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;
@@ -763,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;
@@ -771,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 */
@@ -946,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)
@@ -974,11 +1204,20 @@ static void dispatch_dtap(struct gsm_subscriber_connection *conn,
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",
@@ -992,15 +1231,18 @@ static void dispatch_dtap(struct gsm_subscriber_connection *conn,
* 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_NOTICE, "MS attempts EMERGENCY SETUP although EMERGENCY CALLS"
- " are not allowed in sysinfo (spec violation by MS!)\n");
- lchan_release(msg->lchan, true, true, GSM48_RR_CAUSE_PREMPTIVE_REL);
+ 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_NOTICE, "MS attempts EMERGENCY SETUP, but EMERGENCY CALLS are"
- " denied on this BSC (check BTS config!)\n");
- lchan_release(msg->lchan, true, true, GSM48_RR_CAUSE_PREMPTIVE_REL);
+ 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;
}
}
diff --git a/src/osmo-bsc/gsm_08_08.c b/src/osmo-bsc/gsm_08_08.c
index cd8b77f79..692e35cea 100644
--- a/src/osmo-bsc/gsm_08_08.c
+++ b/src/osmo-bsc/gsm_08_08.c
@@ -26,6 +26,7 @@
#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_08_rr.h>
#include <osmocom/bsc/a_reset.h>
@@ -34,6 +35,7 @@
#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>
@@ -69,21 +71,23 @@ void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn,
{
int rc;
struct msgb *resp;
+ struct gsm_bts *bts;
if (!msc_connected(conn))
return;
- LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT (dlci=0x%02x, cause='%s')\n",
- dlci, gsm0808_cause_name(cause));
+ 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(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_SAPI_N_REJECT]);
+ 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;
@@ -92,30 +96,13 @@ 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);
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CIPHER_COMPLETE]);
+ 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 bool is_cm_service_for_emerg(struct msgb *msg)
{
struct gsm48_service_request *cm;
@@ -195,7 +182,7 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn,
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;
- uint8_t round_robin_next_nr;
+ unsigned int round_robin_next_nr;
int16_t nri_v = -1;
bool is_null_nri = false;
@@ -232,7 +219,7 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn,
if (nri_matches_msc) {
LOG_NRI(LOGL_DEBUG, "matches msc %d, but this MSC is currently not connected\n",
msc->nr);
- rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_ATTACH_LOST]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_ATTACH_LOST));
}
continue;
}
@@ -244,10 +231,10 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn,
msc->nr);
} else {
LOG_NRI(LOGL_DEBUG, "matches msc %d\n", msc->nr);
- rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_KNOWN]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_KNOWN));
if (is_emerg) {
- rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_MSCPOOL_EMERG_FORWARDED]);
- rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_MSCPOOL_EMERG_FORWARDED]);
+ 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;
}
@@ -282,9 +269,9 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn,
* them are usable -- wrap to the start. */
msc_target = msc_round_robin_next ? : msc_round_robin_first;
if (!msc_target) {
- rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_MSCPOOL_SUBSCR_NO_MSC]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_SUBSCR_NO_MSC));
if (is_emerg)
- rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_MSCPOOL_EMERG_LOST]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_MSCPOOL_EMERG_LOST));
return NULL;
}
@@ -292,13 +279,13 @@ static struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn,
osmo_mobile_identity_to_str_c(OTC_SELECT, mi), msc_target->nr);
if (is_null_nri)
- rate_ctr_inc(&msc_target->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_REATTACH]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc_target->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_REATTACH));
else
- rate_ctr_inc(&msc_target->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_NEW]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc_target->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_NEW));
if (is_emerg) {
- rate_ctr_inc(&msc_target->msc_ctrs->ctr[MSC_CTR_MSCPOOL_EMERG_FORWARDED]);
- rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_MSCPOOL_EMERG_FORWARDED]);
+ 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));
}
/* An MSC was picked by round-robin, so update the next round-robin nr to pick */
@@ -364,6 +351,19 @@ static void parse_powercap(struct gsm_subscriber_connection *conn, struct msgb *
conn_update_ms_power_class(conn, rc8);
}
+static struct gsm_subscriber_connection *bsc_conn_by_bsub(const struct bsc_subscr *bsub)
+{
+ struct gsm_subscriber_connection *conn;
+ if (!bsub)
+ return NULL;
+
+ llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) {
+ if (conn->bsub == bsub)
+ return conn;
+ }
+ return NULL;
+}
+
/*! MS->MSC: New MM context with L3 payload. */
int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_channel)
{
@@ -402,11 +402,10 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan
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 current
- * state of our ttcn3 tests does send invalid Layer 3 Info in some tests and expects osmo-bsc to not
- * care about that. So, changing the behavior to rejecting on missing MI causes test failure and, if at
- * all, should happen in a separate patch.
- * See e.g. BSC_Tests.TC_chan_rel_rll_rel_ind: "dt := * f_est_dchan('23'O, 23, '00010203040506'O);"
+ /* 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__);
@@ -414,8 +413,13 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan
/* 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. */
- if (bsub)
+ * 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) {
@@ -448,7 +452,10 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan
paged_from_msc = NULL;
paging_reasons = BSC_PAGING_NONE;
if (pdisc == GSM48_PDISC_RR && mtype == GSM48_MT_RR_PAG_RESP) {
- paging_request_stop(&paged_from_msc, &paging_reasons, bts, conn->bsub);
+ /* 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. */
@@ -456,12 +463,12 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan
"%s Unsolicited Paging Response, possibly an MT-CSFB call.\n",
osmo_mobile_identity_to_str_c(OTC_SELECT, &mi));
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_NO_ACTIVE_PAGING]);
- rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_PAGING_NO_ACTIVE_PAGING]);
+ 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(&paged_from_msc->msc_ctrs->ctr[MSC_CTR_MSCPOOL_SUBSCR_PAGED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(paged_from_msc->msc_ctrs, MSC_CTR_MSCPOOL_SUBSCR_PAGED));
} else {
LOG_COMPL_L3(pdisc, mtype, LOGL_DEBUG,
"%s matches earlier Paging from msc %d, but this MSC is not connected\n",
@@ -540,20 +547,12 @@ int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_chan
early_exit:
if (release_lchan)
- lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(lchan, true, true, RSL_ERR_EQUIPMENT_FAIL,
+ gscon_last_eutran_plmn(conn));
log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
return rc;
}
-/* Data Link Connection Identifier (DLCI) is defined in 3GPP TS 48.006, section 9.3.2.
- * .... .SSS - SAPI value used on the radio link;
- * CC.. .... - control channel identification:
- * 00.. .... - indicates that the control channel is not further specified,
- * 10.. .... - represents the FACCH or the SDCCH,
- * 11.. .... - represents the SACCH,
- * other values are reserved. */
-#define RSL_LINK_ID2DLCI(link_id) \
- (link_id & 0x40 ? 0xc0 : 0x80) | (link_id & 0x07)
/*! MS->BSC/MSC: Um L3 message. */
void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
@@ -602,12 +601,33 @@ void bsc_cm_update(struct gsm_subscriber_connection *conn,
}
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(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CLASSMARK_UPDATE]);
+ 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 22616f37d..b11607e3f 100644
--- a/src/osmo-bsc/gsm_data.c
+++ b/src/osmo-bsc/gsm_data.c
@@ -62,16 +62,13 @@ void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr,
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;
@@ -110,19 +107,21 @@ struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_typ
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);
@@ -170,7 +169,7 @@ void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts,
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" },
@@ -185,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,
@@ -218,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);
@@ -236,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" },
@@ -332,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 */
@@ -474,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:
@@ -520,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;
}
@@ -544,11 +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);
+ 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;
+ }
+ 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;
+}
+
+int gsm_lchan2chan_nr(const struct gsm_lchan *lchan, bool allow_osmo_cbits)
+{
+ return gsm_lchan_and_pchan2chan_nr(lchan, lchan->ts->pchan_is, allow_osmo_cbits);
}
static const uint8_t subslots_per_pchan[] = {
@@ -562,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))
@@ -575,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) {
@@ -597,17 +616,18 @@ struct gsm_bts *conn_get_bts(struct gsm_subscriber_connection *conn) {
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;
@@ -615,21 +635,30 @@ 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)
{
- cd->chan_nr = gsm_lchan2chan_nr(lchan);
- _chan_desc_fill_tail(cd, lchan);
+ 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;
}
-/* 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)
+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_pchan2chan_nr(lchan->ts->pchan_from_config, lchan->ts->nr, lchan->nr);
- _chan_desc_fill_tail(cd, lchan);
+ return gsm48_lchan_and_pchan2chan_desc(cd, lchan, lchan->ts->pchan_is, tsc, allow_osmo_cbits);
}
uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts)
@@ -643,7 +672,7 @@ uint8_t gsm_ts_tsc(const struct gsm_bts_trx_ts *ts)
bool nm_is_running(const struct gsm_nm_state *s) {
if (s->operational != NM_OPSTATE_ENABLED)
return false;
- if ((s->availability != NM_AVSTATE_OK) && (s->availability != 0xff))
+ if (s->availability != NM_AVSTATE_OK)
return false;
if (s->administrative != NM_STATE_UNLOCKED)
return false;
@@ -675,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
@@ -686,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)
{
@@ -699,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;
@@ -771,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;
@@ -817,10 +866,8 @@ bool ts_is_capable_of_lchant(struct gsm_bts_trx_ts *ts, enum gsm_chan_t type)
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;
@@ -839,6 +886,11 @@ bool ts_is_usable(const struct gsm_bts_trx_ts *ts)
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);
@@ -850,65 +902,28 @@ void conn_update_ms_power_class(struct gsm_subscriber_connection *conn, uint8_t
lchan_update_ms_power_ctrl_level(conn->lchan, bts->ms_max_power);
}
-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;
+const struct value_string lchan_activate_mode_names[] = {
+ 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),
+ {}
+};
- if (send_pwr_ctrl_msg)
- rsl_chan_ms_power_ctrl(lchan);
-}
+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 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),
+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),
{}
};
@@ -964,3 +979,50 @@ enum gsm48_rr_cause bsc_gsm48_rr_cause_from_rsl_cause(uint8_t c)
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;
+ }
+}
+
+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/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 7eb8f31e0..2fb466cbe 100644
--- a/src/osmo-bsc/handover_decision.c
+++ b/src/osmo-bsc/handover_decision.c
@@ -201,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,
},
@@ -215,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;
@@ -232,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);
@@ -257,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 c818dbbde..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>
@@ -38,6 +41,8 @@
#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)
@@ -48,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)
@@ -59,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)
@@ -71,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
@@ -98,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 {
@@ -136,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)
@@ -174,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)
{
@@ -235,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)
{
@@ -364,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.
@@ -416,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;
@@ -534,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;
}
}
@@ -544,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;
@@ -572,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 */
@@ -593,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 */
@@ -687,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;
}
@@ -737,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;
}
@@ -776,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;
@@ -791,7 +950,7 @@ 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);
@@ -812,59 +971,134 @@ static int trigger_ho(struct ho_candidate *c, uint8_t requirements)
" less-or-equal congestion"))
/* verbosely log about a handover candidate */
-static inline void debug_candidate(struct ho_candidate *candidate,
- int8_t rxlev, int tchf_count, int tchh_count)
+static inline void debug_candidate(struct ho_candidate *candidate)
{
- struct gsm_lchan *lchan = candidate->lchan;
-
#define HO_CANDIDATE_FMT(tchx, TCHX) "TCH/" #TCHX "={free %d (want %d), " REQUIREMENTS_FMT "}"
#define HO_CANDIDATE_ARGS(tchx, TCHX) \
- tch##tchx##_count, ho_get_hodec2_tch##tchx##_min_slots(candidate->bts->ho), \
+ candidate->target.free_tch##tchx, candidate->target.min_free_tch##tchx, \
REQUIREMENTS_ARGS(candidate->requirements, TCHX)
- 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->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->cil)
- LOGPHOLCHANTOREMOTE(lchan, candidate->cil, LOGL_DEBUG,
- "RX level %d -> %d\n",
- rxlev2dbm(rxlev), rxlev2dbm(candidate->avg));
+ 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->bts == lchan->ts->trx->bts)
- LOGPHOLCHANTOBTS(lchan, candidate->bts, LOGL_DEBUG,
- "RX level %d; "
+ 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, 0, tchf_count, tchh_count);
+ debug_candidate(&c);
if (!c.requirements)
return;
@@ -876,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 */
@@ -910,9 +1139,9 @@ static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_mea
}
find_handover_target_cell(&neighbor_bts, &neighbor_cil,
- lchan->conn, &ni, false);
+ lchan->conn, &target_ab, false);
- 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;
@@ -928,51 +1157,51 @@ 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;
@@ -983,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);
- /* calculate 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 measurement report (yet) */
- if (av_rxlev < 0) {
+ 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);
}
}
}
@@ -1077,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;
@@ -1098,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. */
@@ -1117,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];
@@ -1136,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);
}
@@ -1150,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];
@@ -1169,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);
}
@@ -1190,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;
@@ -1207,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
@@ -1276,14 +1523,8 @@ static void on_measurement_report(struct gsm_meas_rep *mr)
}
/* get average levels. if not enough 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));
+ 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;
@@ -1291,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);
@@ -1323,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;
}
@@ -1358,10 +1602,123 @@ 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;
+}
+
/*
* Handover/assignment check after timer timeout:
*
@@ -1419,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;
@@ -1511,70 +1864,27 @@ static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int
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].avg));
+ 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
@@ -1598,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
@@ -1746,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 */
@@ -1843,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;
@@ -1870,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)
@@ -1903,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;
@@ -1913,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. */
@@ -1941,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 573f249a1..24766a5ea 100644
--- a/src/osmo-bsc/handover_fsm.c
+++ b/src/osmo-bsc/handover_fsm.c
@@ -45,6 +45,8 @@
#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) \
@@ -59,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) \
@@ -88,8 +90,8 @@
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
@@ -102,15 +104,18 @@
bts_ctr_description[counter].name, \
bts_ctr_description[counter].description); \
if (bts) \
- rate_ctr_inc(&bts->bts_ctrs->ctr[counter]); \
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, counter)); \
else \
- rate_ctr_inc(&conn->network->bts_unknown_ctrs->ctr[counter]); \
- } while(0)
+ 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)
+ ho_count_bsc(BSC_##counter); \
+ ho_count_bts(bts, BTS_##counter); \
+ } while (0)
static uint8_t g_next_ho_ref = 1;
@@ -134,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),
@@ -146,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) {
@@ -163,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));
@@ -188,11 +193,11 @@ struct gsm_subscriber_connection *ho_fi_conn(struct osmo_fsm_inst *fi)
}
static const struct osmo_tdef_state_timeout ho_fsm_timeouts[32] = {
- [HO_ST_WAIT_LCHAN_ACTIVE] = { .T = 23042 },
- [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .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_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 },
};
@@ -211,15 +216,15 @@ static const struct osmo_tdef_state_timeout ho_fsm_timeouts[32] = {
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);
@@ -227,12 +232,9 @@ void handover_request(struct handover_out_req *req)
conn = req->old_lchan->conn;
OSMO_ASSERT(conn && conn->fi);
- /* Make sure the handover target neighbor_ident_key contains the correct source bts nr */
- req->target_nik.from_bts = req->old_lchan->ts->trx->bts->nr;
-
/* 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(). */
- osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_START, req);
+ 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.
@@ -267,7 +269,7 @@ 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)
@@ -275,7 +277,8 @@ static void handover_reset(struct gsm_subscriber_connection *conn)
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) {
@@ -290,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);
}
@@ -315,10 +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 neighbor_ident_key *search_for = &req->target_nik;
+ const struct cell_ab *search_for = &req->target_cell_ab;
struct handover *ho = &conn->ho;
struct gsm_bts *local_target_cell = NULL;
- const struct gsm0808_cell_id_list2 *remote_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");
@@ -335,9 +338,9 @@ void handover_start(struct handover_out_req *req)
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;
- if (find_handover_target_cell(&local_target_cell, &remote_target_cell,
+ if (find_handover_target_cell(&local_target_cell, &remote_target_cells,
conn, search_for, true)) {
handover_end(conn, HO_RESULT_ERROR);
return;
@@ -349,8 +352,8 @@ void handover_start(struct handover_out_req *req)
return;
}
- if (remote_target_cell) {
- handover_start_inter_bsc_out(conn, remote_target_cell);
+ if (remote_target_cells.id_list_len) {
+ handover_start_inter_bsc_out(conn, &remote_target_cells);
return;
}
@@ -375,8 +378,12 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn)
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) {
ho_count(bts, CTR_INTRA_CELL_HO_ATTEMPTED);
@@ -384,12 +391,13 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn)
} else {
ho_count(bts, CTR_INTRA_BSC_HO_ATTEMPTED);
ho_fsm_update_id(fi, "intraBSC");
+ 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));
@@ -397,20 +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,
+ .ch_mode_rate = conn->lchan->current_ch_mode_rate,
.encr = conn->lchan->encr,
- .requires_voice_stream = conn->lchan->mgw_endpoint_ci_bts ? true : false,
+ .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,
- .s15_s0 = conn->lchan->activate.info.s15_s0,
};
+ 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)
@@ -422,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"
@@ -452,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)) {
@@ -481,9 +533,10 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
req->chosen_encr_alg);
}
- LOG_HO(conn, LOGL_DEBUG, "Handover Request encryption info: chosen=A5/%u key=%s\n",
- (req->chosen_encr_alg ? : 1) - 1, req->ei.key_len?
- osmo_hexdump_nospc(req->ei.key, req->ei.key_len) : "none");
+ 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;
@@ -551,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,
@@ -578,6 +640,7 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
int match_idx;
struct osmo_fsm_inst *fi;
struct channel_mode_and_rate ch_mode_rate = {};
+ int chosen_a5_n;
handover_fsm_alloc(conn);
@@ -593,7 +656,7 @@ 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;
}
@@ -625,7 +688,10 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
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, ch_mode_rate.chan_mode, ch_mode_rate.chan_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;
@@ -658,26 +724,37 @@ 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 = ch_mode_rate.chan_mode,
- .s15_s0 = ch_mode_rate.s15_s0,
- .requires_voice_stream = chan_mode_is_tch(ch_mode_rate.chan_mode),
+ .ch_mode_rate = ch_mode_rate,
+ .ch_indctr = req->ct.ch_indctr,
.msc_assigned_cic = req->msc_assigned_cic,
};
- if (req->chosen_encr_alg) {
- info.encr.alg_id = req->chosen_encr_alg;
- if (info.encr.alg_id > 1 && !req->ei.key_len) {
- ho_fail(HO_RESULT_ERROR, "Chosen Encryption Algorithm (Serving) reflects A5/%u"
- " but there is no key (Encryption Information)", info.encr.alg_id - 1);
+ /* 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;
}
}
- if (req->ei.key_len) {
+ /* 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\n",
+ ho_fail(HO_RESULT_ERROR, "Encryption Information IE key length is too large: %u",
req->ei.key_len);
return;
}
@@ -685,9 +762,39 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
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);
}
+/* 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) \
{ \
@@ -713,6 +820,7 @@ FUNC_RESULT_COUNTER(BSC, INTRA_CELL_HO)
FUNC_RESULT_COUNTER(BSC, INTRA_BSC_HO)
FUNC_RESULT_COUNTER(BSC, 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_BSC_INTER_BSC_HO_OUT(enum handover_result result) {
switch (result) {
case HO_RESULT_OK:
@@ -748,8 +856,10 @@ static int result_counter_bsc(enum handover_scope scope, enum handover_result re
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:
@@ -782,6 +892,9 @@ static int result_counter_bts(enum handover_scope scope, enum handover_result re
}
}
+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;
@@ -807,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;
@@ -815,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->lchan->ch_mode_rate.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;
}
@@ -839,7 +953,7 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn)
return;
}
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_PERFORMED]);
+ 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");
@@ -877,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);
@@ -903,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;
}
@@ -933,13 +1048,30 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
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);
@@ -950,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;
}
@@ -985,7 +1117,7 @@ static void ho_fsm_wait_lchan_active(struct osmo_fsm_inst *fi, uint32_t event, v
* 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 (ho->new_lchan->activate.info.requires_voice_stream
+ 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);
@@ -1079,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) {
diff --git a/src/osmo-bsc/handover_logic.c b/src/osmo-bsc/handover_logic.c
index ade330d46..1e1b6c319 100644
--- a/src/osmo-bsc/handover_logic.c
+++ b/src/osmo-bsc/handover_logic.c
@@ -112,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)
@@ -126,7 +126,7 @@ int bts_handover_count(struct gsm_bts *bts, int ho_scopes)
return count;
}
-/* Find out a handover target cell for the given neighbor_ident_key,
+/* 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
@@ -138,22 +138,20 @@ int bts_handover_count(struct gsm_bts *bts, int ho_scopes)
* to be found.
*/
int find_handover_target_cell(struct gsm_bts **local_target_cell_p,
- const struct gsm0808_cell_id_list2 **remote_target_cell_p,
- struct gsm_subscriber_connection *conn, const struct neighbor_ident_key *search_for,
+ struct gsm0808_cell_id_list2 *remote_target_cells,
+ struct gsm_subscriber_connection *conn,
+ const struct cell_ab *search_for,
bool log_errors)
{
struct gsm_network *net = conn->network;
- struct gsm_bts *from_bts;
struct gsm_bts *local_target_cell = NULL;
- const struct gsm0808_cell_id_list2 *remote_target_cell = NULL;
- struct gsm_bts_ref *neigh;
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 (remote_target_cell_p)
- *remote_target_cell_p = NULL;
if (!search_for) {
if (log_errors)
@@ -161,7 +159,6 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p,
return -EINVAL;
}
- from_bts = gsm_bts_num(net, search_for->from_bts);
if (!from_bts) {
if (log_errors)
LOG_HO(conn, LOGL_ERROR, "Handover without source cell\n");
@@ -174,12 +171,11 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p,
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",
- neighbor_ident_key_name(search_for));
+ cell_ab_to_str_c(OTC_SELECT, search_for));
return -EINVAL;
}
- if (llist_empty(&from_bts->local_neighbors)
- && !neighbor_ident_bts_entry_exists(from_bts->nr)) {
+ 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;
@@ -192,15 +188,16 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p,
for (i = 0; i < 2; i++) {
bool exact_match = !i;
llist_for_each_entry(bts, &net->bts_list, list) {
- struct neighbor_ident_key bts_key = *bts_ident_key(bts);
- if (neighbor_ident_key_match(&bts_key, search_for, exact_match)) {
+ 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",
- neighbor_ident_key_name(search_for),
+ cell_ab_to_str_c(OTC_SELECT, search_for),
local_target_cell->nr, bts->nr);
return -EINVAL;
}
@@ -214,7 +211,7 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p,
if (!local_target_cell) {
if (log_errors)
LOG_HO(conn, LOGL_ERROR, "Cannot Handover, no cell matches %s\n",
- neighbor_ident_key_name(search_for));
+ cell_ab_to_str_c(OTC_SELECT, search_for));
return -EINVAL;
}
@@ -222,14 +219,14 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p,
if (log_errors)
LOG_HO(conn, LOGL_ERROR,
"Cannot start re-assignment, Assignment disabled for this cell (%s)\n",
- neighbor_ident_key_name(search_for));
+ 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",
- neighbor_ident_key_name(search_for));
+ cell_ab_to_str_c(OTC_SELECT, search_for));
return -EINVAL;
}
@@ -243,96 +240,64 @@ int find_handover_target_cell(struct gsm_bts **local_target_cell_p,
LOG_HO(conn, LOGL_DEBUG, "There are explicit neighbors configured for this cell\n");
- /* Iterate explicit local neighbor cells */
- llist_for_each_entry(neigh, &from_bts->local_neighbors, entry) {
- struct gsm_bts *neigh_bts = neigh->bts;
- struct neighbor_ident_key neigh_bts_key = *bts_ident_key(neigh_bts);
- neigh_bts_key.from_bts = from_bts->nr;
-
- LOG_HO(conn, LOGL_DEBUG, "Local neighbor %s\n", neighbor_ident_key_name(&neigh_bts_key));
-
- if (!neighbor_ident_key_match(&neigh_bts_key, search_for, true)) {
- LOG_HO(conn, LOGL_DEBUG, "Doesn't match %s\n", neighbor_ident_key_name(search_for));
- continue;
- }
-
- if (local_target_cell) {
- if (log_errors)
- LOG_HO(conn, LOGL_ERROR,
- "NEIGHBOR CONFIGURATION ERROR: Multiple BTS match %s (BTS %d and BTS %d)."
- " Aborting Handover because of ambiguous network topology.\n",
- neighbor_ident_key_name(search_for), local_target_cell->nr, neigh_bts->nr);
- return -EINVAL;
- }
-
- local_target_cell = neigh_bts;
+ 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;
}
- /* Any matching remote-BSS neighbor cell? */
- remote_target_cell = neighbor_ident_get(net->neighbor_bss_cells, search_for);
+ /* 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_cell)
- LOG_HO(conn, LOGL_DEBUG, "Found remote target cell %s\n",
- gsm0808_cell_id_list_name(remote_target_cell));
+ 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_cell) {
+ 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 %s"
- " (BTS %d and remote %s)."
+ 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",
- neighbor_ident_key_name(search_for), local_target_cell->nr,
- gsm0808_cell_id_list_name(remote_target_cell));
+ 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 (%s)\n",
- neighbor_ident_key_name(search_for));
+ "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_cell)
+ || remote_target_cells->id_list_len)
&& !ho_active) {
if (log_errors)
LOG_HO(conn, LOGL_ERROR,
- "Cannot start Handover, Handover disabled for this cell (%s)\n",
- neighbor_ident_key_name(search_for));
+ "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_cell) {
- if (remote_target_cell_p)
- *remote_target_cell_p = remote_target_cell;
+ if (remote_target_cells->id_list_len)
return 0;
- }
if (log_errors)
LOG_HO(conn, LOGL_ERROR, "Cannot handover %s: neighbor unknown\n",
- neighbor_ident_key_name(search_for));
+ cell_ab_to_str_c(OTC_SELECT, search_for));
return -ENODEV;
}
-struct neighbor_ident_key *bts_ident_key(const struct gsm_bts *bts)
-{
- static struct neighbor_ident_key key;
- key = (struct neighbor_ident_key){
- .from_bts = NEIGHBOR_IDENT_KEY_ANY_BTS,
- .arfcn = bts->c0->arfcn,
- .bsic = bts->bsic,
- };
- return &key;
-}
-
static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
diff --git a/src/osmo-bsc/handover_vty.c b/src/osmo-bsc/handover_vty.c
index e5435f53d..afc12d9d1 100644
--- a/src/osmo-bsc/handover_vty.c
+++ b/src/osmo-bsc/handover_vty.c
@@ -170,7 +170,7 @@ 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);
diff --git a/src/osmo-bsc/lb.c b/src/osmo-bsc/lb.c
index 654f26144..511a54528 100644
--- a/src/osmo-bsc/lb.c
+++ b/src/osmo-bsc/lb.c
@@ -31,22 +31,10 @@
#include <osmocom/bsc/osmo_bsc_sigtran.h>
#include <osmocom/bsc/lcs_loc_req.h>
#include <osmocom/bsc/bssmap_reset.h>
-
-static struct gsm_subscriber_connection *get_bsc_conn_by_lb_conn_id(int conn_id)
-{
- struct gsm_subscriber_connection *conn;
-
- llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) {
- if (conn->lcs.lb.state != SUBSCR_SCCP_ST_NONE
- && conn->lcs.lb.conn_id == conn_id)
- return conn;
- }
-
- return NULL;
-}
+#include <osmocom/bsc/gsm_data.h>
/* Send reset to SMLC */
-int bssmap_le_tx_reset()
+int bssmap_le_tx_reset(void)
{
struct osmo_ss7_instance *ss7;
struct msgb *msg;
@@ -58,18 +46,23 @@ int bssmap_le_tx_reset()
},
};
+ 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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_UDT_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()
+int bssmap_le_tx_reset_ack(void)
{
struct osmo_ss7_instance *ss7;
struct msgb *msg;
@@ -85,7 +78,7 @@ int bssmap_le_tx_reset_ack()
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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_UDT_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);
}
@@ -95,8 +88,8 @@ static int handle_unitdata_from_smlc(const struct osmo_sccp_addr *smlc_addr, str
{
struct osmo_ss7_instance *ss7;
struct bssap_le_pdu bssap_le;
- struct osmo_bssap_le_err *err;
- struct rate_ctr *ctr = bsc_gsmnet->smlc->ctrs->ctr;
+ 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);
@@ -104,13 +97,13 @@ static int handle_unitdata_from_smlc(const struct osmo_sccp_addr *smlc_addr, str
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(&ctr[SMLC_CTR_BSSMAP_LE_RX_UNKNOWN_PEER]);
+ 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(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG));
return -EINVAL;
}
@@ -121,17 +114,17 @@ static int handle_unitdata_from_smlc(const struct osmo_sccp_addr *smlc_addr, str
switch (bssap_le.bssmap_le.msg_type) {
case BSSMAP_LE_MSGT_RESET:
- rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_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(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_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(&ctr[SMLC_CTR_BSSMAP_LE_RX_UDT_ERR_INVALID_MSG]);
+ 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;
@@ -142,6 +135,8 @@ 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;
@@ -164,7 +159,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
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 = get_bsc_conn_by_lb_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) {
conn->lcs.lb.state = SUBSCR_SCCP_ST_CONNECTED;
if (msgb_l2len(oph->msg) > 0) {
@@ -180,7 +175,7 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
/* Handle incoming connection oriented data */
DEBUGP(DLCS, "N-DATA.ind(%u)\n", scu_prim->u.data.conn_id);
- conn = get_bsc_conn_by_lb_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) {
LOGP(DLCS, LOGL_ERROR, "N-DATA.ind(%u) for unknown conn_id\n", scu_prim->u.data.conn_id);
rc = -EINVAL;
@@ -198,12 +193,14 @@ 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_lb_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) {
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);
@@ -212,8 +209,9 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
break;
default:
- LOGP(DLCS, LOGL_ERROR, "Unhandled SIGTRAN operation %s on primitive %u\n",
- get_value_string(osmo_prim_op_names, oph->operation), oph->primitive);
+ 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;
}
@@ -224,8 +222,9 @@ static int sccp_sap_up(struct osmo_prim_hdr *oph, void *_scu)
static int lb_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg)
{
struct osmo_ss7_instance *ss7;
- int conn_id;
+ 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);
@@ -236,35 +235,52 @@ static int lb_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg
return -EINVAL;
}
- conn_id = bsc_sccp_inst_next_conn_id(bsc_gsmnet->smlc->sccp);
- if (conn_id < 0) {
+ 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_id = conn_id;
+ 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=%i) to SMLC: %s\n", conn_id,
+ 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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_SUCCESS]);
- else
- rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_ERR_SEND]);
- if (rc >= 0)
+ 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_sccp_tx_disconn(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn_id, &bsc_gsmnet->smlc->bsc_addr, 0);
+ 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 */
@@ -297,11 +313,11 @@ int lb_send(struct gsm_subscriber_connection *conn, const struct bssap_le_pdu *b
}
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_id, msg);
+ rc = osmo_sccp_tx_data_msg(bsc_gsmnet->smlc->sccp_user, conn->lcs.lb.conn.conn_id, msg);
if (rc >= 0)
- rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_SUCCESS]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_SUCCESS));
else
- rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_ERR_SEND]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_ERR_SEND));
count_tx:
if (rc < 0)
@@ -309,24 +325,24 @@ count_tx:
switch (bssap_le->bssmap_le.msg_type) {
case BSSMAP_LE_MSGT_PERFORM_LOC_REQ:
- rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_REQUEST]);
+ 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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_PERFORM_LOCATION_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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_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(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_ABORT]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->smlc->ctrs, SMLC_CTR_BSSMAP_LE_TX_DT1_BSSLAP_ABORT));
break;
default:
break;
@@ -347,7 +363,7 @@ count_tx:
#define DEFAULT_ASP_LOCAL_IP "localhost"
#define DEFAULT_ASP_REMOTE_IP "localhost"
-void lb_cancel_all()
+void lb_cancel_all(void)
{
struct gsm_subscriber_connection *conn;
llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry)
@@ -379,7 +395,7 @@ void lb_reset_tx_reset_ack(void *data)
bssmap_le_tx_reset_ack();
}
-static void lb_start_reset_fsm()
+static void lb_start_reset_fsm(void)
{
struct bssmap_reset_cfg cfg = {
.conn_cfm_failure_threshold = 3,
@@ -399,7 +415,13 @@ static void lb_start_reset_fsm()
bsc_gsmnet->smlc->bssmap_reset = bssmap_reset_alloc(bsc_gsmnet, "Lb", &cfg);
}
-static int lb_start()
+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;
@@ -407,6 +429,7 @@ static int lb_start()
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)
@@ -440,6 +463,9 @@ static int lb_start()
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)
@@ -473,7 +499,7 @@ static int lb_start()
return 0;
}
-static int lb_stop()
+static int lb_stop(void)
{
/* Not set up? */
if (!bsc_gsmnet->smlc->sccp_user)
@@ -482,12 +508,13 @@ static int lb_stop()
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()
+int lb_start_or_stop(void)
{
int rc;
if (bsc_gsmnet->smlc->enable) {
@@ -522,7 +549,7 @@ int lb_start_or_stop()
static void smlc_vty_init(void);
-int lb_init()
+int lb_init(void)
{
OSMO_ASSERT(!bsc_gsmnet->smlc);
bsc_gsmnet->smlc = talloc_zero(bsc_gsmnet, struct smlc_config);
@@ -587,6 +614,28 @@ static void enforce_ssn(struct vty *vty, struct osmo_sccp_addr *addr, enum osmo_
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",
@@ -601,17 +650,9 @@ DEFUN(cfg_smlc_cs7_bsc_addr,
return CMD_ERR_INCOMPLETE;
}
- /* Prevent mixing addresses from different CS7 instances */
- if (bsc_gsmnet->smlc->cs7_instance_valid
- && bsc_gsmnet->smlc->cs7_instance != ss7->cfg.id) {
- vty_out(vty,
- "Error: SCCP addressbook entry from mismatching CS7 instance: '%s'%s",
- bsc_addr_name, VTY_NEWLINE);
+ if (!smlc_set_cs7_instance(vty, "smlc / bsc-addr", bsc_addr_name, ss7))
return CMD_WARNING;
- }
- bsc_gsmnet->smlc->cs7_instance = ss7->cfg.id;
- bsc_gsmnet->smlc->cs7_instance_valid = true;
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;
@@ -631,18 +672,9 @@ DEFUN(cfg_smlc_cs7_smlc_addr,
return CMD_ERR_INCOMPLETE;
}
- /* Prevent mixing addresses from different CS7/SS7 instances */
- if (bsc_gsmnet->smlc->cs7_instance_valid) {
- if (bsc_gsmnet->smlc->cs7_instance != ss7->cfg.id) {
- vty_out(vty,
- "Error: SCCP addressbook entry from mismatching CS7 instance: '%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;
- bsc_gsmnet->smlc->cs7_instance = ss7->cfg.id;
- bsc_gsmnet->smlc->cs7_instance_valid = true;
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;
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 e97c1baeb..55875f0b4 100644
--- a/src/osmo-bsc/lchan_fsm.c
+++ b/src/osmo-bsc/lchan_fsm.c
@@ -35,11 +35,15 @@
#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;
@@ -66,30 +70,71 @@ bool lchan_may_receive_data(struct gsm_lchan *lchan)
}
}
-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;
+}
+
+static void lchan_on_mode_modify_success(struct gsm_lchan *lchan)
+{
+ lchan->modify.concluded = true;
+
+ switch (lchan->modify.info.modify_for) {
- if (fmt) {
- va_start(ap, fmt);
- lchan->last_error = talloc_vasprintf(lchan->ts->trx, fmt, ap);
- va_end(ap);
+ case MODIFY_FOR_ASSIGNMENT:
+ osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_MODIFIED, lchan);
+ break;
- LOG_LCHAN(lchan, LOGL_ERROR, "%s\n", lchan->last_error);
+ 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;
+
+ 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;
- if (last_error_was)
- talloc_free(last_error_was);
+ 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)
{
@@ -99,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",
@@ -111,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) {
@@ -136,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");
@@ -154,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,
@@ -182,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);
@@ -202,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));
@@ -210,13 +325,18 @@ static void lchan_on_fully_established(struct gsm_lchan *lchan)
}
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=3111 },
- [LCHAN_ST_WAIT_AFTER_ERROR] = { .T=-3111 },
+ [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.
@@ -238,7 +358,7 @@ struct osmo_tdef_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); \
@@ -247,10 +367,10 @@ struct osmo_tdef_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[34] = {
+uint32_t lchan_fsm_on_error[32] = {
[LCHAN_ST_UNUSED] = LCHAN_ST_UNUSED,
[LCHAN_ST_WAIT_TS_READY] = LCHAN_ST_UNUSED,
[LCHAN_ST_WAIT_ACTIV_ACK] = LCHAN_ST_BORKEN,
@@ -261,8 +381,10 @@ uint32_t lchan_fsm_on_error[34] = {
[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_BORKEN,
- [LCHAN_ST_WAIT_RSL_CHAN_MODE_MODIFY_ACK] = 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)
@@ -273,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;
@@ -281,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");
@@ -298,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");
@@ -320,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;
}
@@ -340,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_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)
{
- 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);
+ 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);
@@ -368,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
@@ -381,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);
@@ -407,6 +592,10 @@ static void lchan_reset(struct gsm_lchan *lchan)
.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,
};
}
@@ -415,6 +604,7 @@ static void lchan_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta
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
@@ -422,6 +612,12 @@ static void lchan_fsm_unused_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta
abis_rsl_chan_rqd_queue_poll(bts);
}
+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)
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
@@ -434,101 +630,87 @@ static void lchan_fsm_wait_after_error_onenter(struct osmo_fsm_inst *fi, uint32_
}
/* Configure the multirate setting on this channel. */
-static int lchan_mr_config(struct gsm_lchan *lchan, uint16_t s15_s0)
+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)
{
- struct gsm48_multi_rate_conf mr_conf;
- bool full_rate = (lchan->type == GSM_LCHAN_TCH_F);
- struct gsm_bts *bts = lchan->ts->trx->bts;
- struct amr_multirate_conf *mr;
int rc;
- int rc_rate;
- struct gsm48_multi_rate_conf mr_conf_filtered;
- const struct gsm48_multi_rate_conf *mr_conf_bts;
+ 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, s15_s0) < 0) {
- LOG_LCHAN(lchan, LOGL_ERROR,
+ 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;
}
/* Do not include 12.2 kbps rate when S1 is set. */
- if (lchan->type == GSM_LCHAN_TCH_H && (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. */
- mr_conf.m12_2 = 0;
- }
-
- /* 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;
-
- if (lchan->activate.info.activ_for == FOR_VTY)
- /* If the channel is activated manually from VTY, then there is no
- * conn attached to the lchan, also no MSC is involved. Since this
- * option is for debugging and the codec choice is an intentional
- * decision by the VTY user, we do not filter the mr_conf. */
- memcpy(&mr_conf_filtered, &mr_conf, sizeof(mr_conf_filtered));
- else {
- /* 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, &lchan->conn->sccp.msc->amr_conf, &mr_conf);
- if (rc_rate < 0) {
- LOG_LCHAN(lchan, LOGL_ERROR,
- "can not encode multirate configuration (invalid amr rate setting, MSC)\n");
- return -EINVAL;
- }
+ 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;
}
- /* 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;
- }
-
- /* Ensure that the resulting filtered conf is coherent with the
- * configuration that is set for the BTS and the specified rate.
- * if the channel activation was triggerd by the VTY, do not
- * filter anything (see also comment above) */
- if (lchan->activate.info.activ_for != FOR_VTY) {
- 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");
+ 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;
}
- lchan->s15_s0 = s15_s0;
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;
@@ -540,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;
@@ -553,6 +741,46 @@ 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);
@@ -560,68 +788,63 @@ static void lchan_fsm_wait_ts_ready_onenter(struct osmo_fsm_inst *fi, uint32_t p
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;
+ int ms_power_dbm = bts->ms_max_power;
+ bool requires_rtp_stream;
if (lchan->release.requested) {
lchan_fail("Release requested while activating");
return;
}
+ chan_counts_ts_update(lchan->ts);
+
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) {
- ms_power_dbm = ms_pwr_dbm(bts->band, old_lchan->ms_power);
- lchan_update_ms_power_ctrl_level(lchan, ms_power_dbm >= 0 ? ms_power_dbm : bts->ms_max_power);
- lchan->bs_power = old_lchan->bs_power;
- lchan->rqd_ta = old_lchan->rqd_ta;
- } else {
- lchan_update_ms_power_ctrl_level(lchan, bts->ms_max_power);
- /* Upon last entering the UNUSED state, 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) {
- if (lchan_mr_config(lchan, info->s15_s0) < 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 encr-alg=A5/%u ck=%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 ?
+ 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),
- (lchan->activate.info.encr.alg_id ? : 1)-1,
+ 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");
@@ -630,8 +853,20 @@ static void lchan_fsm_wait_ts_ready_onenter(struct osmo_fsm_inst *fi, uint32_t p
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)
@@ -652,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;
@@ -675,24 +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);
@@ -751,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;
@@ -762,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. */
@@ -825,16 +1095,19 @@ 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 (lchan->activate.info.requires_voice_stream)
+ else if (requires_rtp_stream)
lchan_rtp_fsm_start(lchan);
- /* When activating a channel for VTY, skip waiting for activity from
- * lchan_rtp_fsm, but only if no voice stream is required. */
- if (lchan->activate.info.activ_for == FOR_VTY &&
- !lchan->activate.info.requires_voice_stream) {
+ /* 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);
}
}
@@ -842,16 +1115,24 @@ static void lchan_fsm_wait_rll_rtp_establish_onenter(struct osmo_fsm_inst *fi, u
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;
@@ -863,7 +1144,7 @@ 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;
@@ -876,7 +1157,7 @@ static void lchan_fsm_wait_rll_rtp_establish(struct osmo_fsm_inst *fi, uint32_t
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->activate.info.chan_mode);
+ 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)
@@ -888,7 +1169,7 @@ static void lchan_fsm_wait_rr_chan_mode_modify_ack(struct osmo_fsm_inst *fi, uin
return;
case LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR:
- lchan_fail("Failed to change channel mode on the MS side: %s in state %s\n",
+ 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;
@@ -905,7 +1186,7 @@ static void lchan_fsm_wait_rsl_chan_mode_modify_ack_onenter(struct osmo_fsm_inst
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\n",
+ lchan_fail("Failed to send rsl message to change the channel mode on the BTS side: state %s",
osmo_fsm_inst_state_name(fi));
}
}
@@ -916,14 +1197,37 @@ static void lchan_fsm_wait_rsl_chan_mode_modify_ack(struct osmo_fsm_inst *fi, ui
switch (event) {
case LCHAN_EV_RSL_CHAN_MODE_MODIFY_ACK:
- if (lchan->activate.info.requires_voice_stream)
+ /* 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
+ } 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\n",
+ 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;
@@ -996,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 */
@@ -1009,8 +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_activate_info *info;
+ 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:
@@ -1020,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;
@@ -1032,50 +1349,59 @@ 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);
- /* FIXME: Add missing implementation to handle an already existing RTP voice stream on MODE MODIFY.
- * there may be transitions from VOICE to SIGNALLING and also from VOICE to VOICE with a different
- * codec. */
- if (lchan->fi_rtp) {
- lchan_fail("MODE MODIFY not implemented when RTP voice stream is already active (VOICE => SIGNALLING, VOICE/CODEC_A => VOICE/CODEC_B)\n");
+ 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;
}
- info = data;
- lchan->activate.info = *info;
- use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);
-
- if (info->chan_mode == GSM48_CMODE_SPEECH_AMR) {
- if (lchan_mr_config(lchan, info->s15_s0) < 0) {
- lchan_fail("Can not generate multirate configuration IE\n");
+ 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 voice=%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 ?
+ "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_lchant_name(lchan->type),
- gsm48_chan_mode_name(lchan->tch_mode),
- (lchan->activate.info.encr.alg_id ? : 1) - 1,
- lchan->activate.info.encr.key_len ? osmo_hexdump_nospc(lchan->activate.info.encr.key,
- lchan->activate.info.encr.key_len) : "none");
-
- /* While the mode is changed the lchan is virtually "not activated", at least
- * from the FSM implementations perspective */
- lchan->activate.concluded = false;
+ gsm_chan_t_name(lchan->type),
+ gsm48_chan_mode_name(lchan->modify.ch_mode_rate.chan_mode),
+ lchan->modify.tsc_set, lchan->modify.tsc);
- /* Initiate mode modification, start with the MS side (RR) */
lchan_fsm_state_chg(LCHAN_ST_WAIT_RR_CHAN_MODE_MODIFY_ACK);
return;
@@ -1101,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);
@@ -1186,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);
@@ -1212,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);
}
@@ -1220,6 +1560,7 @@ 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:
@@ -1243,23 +1584,25 @@ static void lchan_fsm_borken_onenter(struct osmo_fsm_inst *fi, uint32_t prev_sta
default:
ctr = BTS_CTR_LCHAN_BORKEN_FROM_UNKNOWN;
}
- rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[ctr]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, ctr));
if (prev_state != LCHAN_ST_BORKEN)
- osmo_stat_item_inc(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1);
+ 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(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_ACK]);
- osmo_stat_item_dec(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1);
+ 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);
@@ -1268,33 +1611,24 @@ static void lchan_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *dat
case LCHAN_EV_RSL_CHAN_ACTIV_NACK:
/* A late Chan Activ NACK? Ok then, unused. */
- rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_CHAN_ACTIV_NACK]);
- osmo_stat_item_dec(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1);
+ 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(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_RF_CHAN_REL_ACK]);
- osmo_stat_item_dec(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1);
+ 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:
@@ -1302,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[] = {
@@ -1320,6 +1719,7 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
},
[LCHAN_ST_CBCH] = {
.name = "CBCH",
+ .onenter = lchan_fsm_cbch_onenter,
.out_state_mask = 0
| S(LCHAN_ST_UNUSED)
,
@@ -1382,8 +1782,9 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
| S(LCHAN_EV_RR_CHAN_MODE_MODIFY_ERROR)
,
.out_state_mask = 0
- | S(LCHAN_ST_BORKEN)
| 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] = {
@@ -1395,8 +1796,10 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
| S(LCHAN_EV_RSL_CHAN_MODE_MODIFY_NACK)
,
.out_state_mask = 0
- | S(LCHAN_ST_BORKEN)
+ | 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] = {
@@ -1452,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
@@ -1480,11 +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)
,
},
};
@@ -1507,6 +1938,7 @@ static const struct value_string lchan_fsm_event_names[] = {
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),
{}
};
@@ -1515,8 +1947,15 @@ static void lchan_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event,
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
@@ -1550,19 +1989,31 @@ static 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->release.rr_cause = bsc_gsm48_rr_cause_from_rsl_cause(lchan->release.rsl_error_cause);
- lchan_fail("Timeout");
+ 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)
@@ -1574,6 +2025,10 @@ void lchan_release(struct gsm_lchan *lchan, bool do_rr_release,
lchan->release.in_error = err;
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. */
@@ -1616,8 +2071,8 @@ static void lchan_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause
{
struct gsm_lchan *lchan = lchan_fi_lchan(fi);
if (lchan->fi->state == LCHAN_ST_BORKEN) {
- rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_LCHAN_BORKEN_EV_TEARDOWN]);
- osmo_stat_item_dec(lchan->ts->trx->bts->bts_statg->items[BTS_STAT_LCHAN_BORKEN], 1);
+ 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) {
diff --git a/src/osmo-bsc/lchan_rtp_fsm.c b/src/osmo-bsc/lchan_rtp_fsm.c
index cd195d021..e8384c604 100644
--- a/src/osmo-bsc/lchan_rtp_fsm.c
+++ b/src/osmo-bsc/lchan_rtp_fsm.c
@@ -31,6 +31,7 @@
#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;
@@ -43,10 +44,10 @@ struct gsm_lchan *lchan_rtp_fi_lchan(struct osmo_fsm_inst *fi)
}
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 },
+ [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.
@@ -63,13 +64,13 @@ struct osmo_tdef_state_timeout lchan_rtp_fsm_timeouts[32] = {
#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);
}
@@ -137,11 +138,12 @@ 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 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 = {};
+ struct mgcp_conn_peer crcx_info;
- if (!is_ipaccess_bts(lchan->ts->trx->bts)) {
+ 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;
@@ -162,14 +164,36 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_available_onenter(struct osmo_fsm_in
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);
+ /* 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);
@@ -178,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",
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;
@@ -251,7 +290,7 @@ 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_state_chg(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED);
@@ -268,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;
@@ -411,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);
@@ -449,7 +516,7 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter(struct osmo_fsm_i
return;
}
- if (!is_ipaccess_bts(lchan->ts->trx->bts)) {
+ 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;
@@ -476,7 +543,7 @@ static void lchan_rtp_fsm_wait_mgw_endpoint_configured(struct osmo_fsm_inst *fi,
switch (event) {
case LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED:
- 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_MDCX_ACK);
else {
lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_READY);
@@ -522,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);
}
@@ -537,7 +610,7 @@ static void lchan_rtp_fsm_rollback_onenter(struct osmo_fsm_inst *fi, uint32_t pr
return;
}
- if (is_ipaccess_bts(lchan->ts->trx->bts))
+ 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);
@@ -704,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)
@@ -796,7 +870,13 @@ static struct osmo_fsm lchan_rtp_fsm = {
/* 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 (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 CODEC_CLEARMODE;
+
case GSM48_CMODE_SPEECH_V1:
if (full_rate)
return CODEC_GSM_8000_1;
@@ -834,14 +914,14 @@ static int chan_mode_to_mgcp_bss_pt(enum mgcp_codecs codec)
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,
+ 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_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));
verb_info->codecs_len = 0;
return;
}
@@ -853,8 +933,8 @@ void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *l
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;
+ verb_info->ptmap[0].pt = custom_pt;
+ verb_info->ptmap_len = 1;
}
/* AMR requires additional parameters to be set up (framing mode) */
@@ -866,7 +946,7 @@ void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *l
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 bandwith-efficient
+ * 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;
@@ -875,3 +955,8 @@ void mgcp_pick_codec(struct mgcp_conn_peer *verb_info, const struct gsm_lchan *l
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 6d3caaced..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>
@@ -30,148 +32,340 @@
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/bts.h>
-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)
+struct lchan_select_ts_list {
+ struct gsm_bts_trx_ts **list;
+ unsigned int num;
+};
+
+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;
- }
+ for (unsigned int tn = 0; tn < ts_list->num; tn++) {
+ struct gsm_bts_trx_ts *ts = ts_list->list[tn];
+ int lchans_as_pchan;
- 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 (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, enum channel_rate chan_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:
switch (chan_rate) {
- case CH_RATE_SDCCH: type = GSM_LCHAN_SDCCH; break;
- case CH_RATE_HALF: type = GSM_LCHAN_TCH_H; break;
- case CH_RATE_FULL: type = GSM_LCHAN_TCH_F; break;
- default: return NULL;
+ 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;
}
- break;
case GSM48_CMODE_SPEECH_EFR:
- /* EFR works over FR channels only */
+ 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 NULL;
+ return GSM_LCHAN_NONE;
/* fall through */
case GSM48_CMODE_SPEECH_V1:
case GSM48_CMODE_SPEECH_AMR:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
switch (chan_rate) {
- case CH_RATE_HALF: type = GSM_LCHAN_TCH_H; break;
- case CH_RATE_FULL: type = GSM_LCHAN_TCH_F; break;
- default: return NULL;
+ case CH_RATE_HALF:
+ return GSM_LCHAN_TCH_H;
+ case CH_RATE_FULL:
+ return GSM_LCHAN_TCH_F;
+ default:
+ return GSM_LCHAN_NONE;
}
- break;
default:
- return NULL;
+ return GSM_LCHAN_NONE;
+ }
+}
+
+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;
+}
+
+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));
+ }
}
- return lchan_select_by_type(bts, type);
+ 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)
+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;
- LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_avail_by_type(%s)\n", gsm_lchant_name(type));
+ 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;
+
+ /* 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;
@@ -183,71 +377,79 @@ struct gsm_lchan *lchan_avail_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);
+ 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;
- }
+ if (!lchan)
+ lchan = lc_dyn_find(&ts_list, GSM_PCHAN_OSMO_DYN,
+ GSM_PCHAN_TCH_H, log);
break;
default:
LOG_BTS(bts, DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", 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)
+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;
- lchan = lchan_avail_by_type(bts, type);
+ 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));
- LOG_BTS(bts, DRLL, LOGL_DEBUG, "lchan_select_by_type(%s)\n", gsm_lchant_name(type));
+ lchan = lchan_avail_by_type(bts, type, reason, ctx, true);
- if (lchan) {
- lchan->type = type;
- LOG_LCHAN(lchan, LOGL_INFO, "Selected\n");
- } else
- LOG_BTS(bts, DRLL, LOGL_NOTICE, "Failed to select %s channel\n",
- gsm_lchant_name(type));
+ 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
index ca5c7b93f..bb0c5e273 100644
--- a/src/osmo-bsc/lcs_loc_req.c
+++ b/src/osmo-bsc/lcs_loc_req.c
@@ -81,7 +81,7 @@ static const struct osmo_tdef_state_timeout lcs_loc_req_fsm_timeouts[32] = {
.cause_val = cause, \
}; \
lcs_loc_req_fsm_state_chg(lcs_loc_req->fi, LCS_LOC_REQ_ST_FAILED); \
- } while(0)
+ } while (0)
static struct lcs_loc_req *lcs_loc_req_alloc(struct osmo_fsm_inst *parent_fi, uint32_t parent_event_term)
{
@@ -104,13 +104,13 @@ static bool parse_bssmap_perf_loc_req(struct lcs_loc_req *lcs_loc_req, struct ms
{
struct tlv_parsed tp_arr[1];
struct tlv_parsed *tp = &tp_arr[0];
- struct tlv_p_entry *e;
+ 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)
+ } 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)
@@ -127,6 +127,29 @@ static bool parse_bssmap_perf_loc_req(struct lcs_loc_req *lcs_loc_req, struct ms
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)
@@ -139,8 +162,6 @@ static bool parse_bssmap_perf_loc_req(struct lcs_loc_req *lcs_loc_req, struct ms
PARSE_ERR("Failed to parse IMEI IE");
}
- // FIXME LCS QoS IE is mandatory for requesting the location
-
/* A lot of IEs remain ignored... */
return true;
@@ -190,7 +211,7 @@ static int handle_bssmap_le_conn_oriented_info(struct lcs_loc_req *lcs_loc_req,
{
switch (bssmap_le->conn_oriented_info.apdu.msg_type) {
case BSSLAP_MSGT_TA_REQUEST:
- rate_ctr_inc(&bsc_gsmnet->smlc->ctrs->ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_BSSLAP_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);
@@ -205,8 +226,8 @@ int lcs_loc_req_rx_bssmap_le(struct gsm_subscriber_connection *conn, struct msgb
{
struct lcs_loc_req *lcs_loc_req = conn->lcs.loc_req;
struct bssap_le_pdu bssap_le;
- struct osmo_bssap_le_err *err;
- struct rate_ctr *ctr = bsc_gsmnet->smlc->ctrs->ctr;
+ 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,
@@ -216,13 +237,13 @@ int lcs_loc_req_rx_bssmap_le(struct gsm_subscriber_connection *conn, struct msgb
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(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG]);
+ 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(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_ERR_INVALID_MSG));
return -ENOTSUP;
}
@@ -231,9 +252,9 @@ int lcs_loc_req_rx_bssmap_le(struct gsm_subscriber_connection *conn, struct msgb
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(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS));
else
- rate_ctr_inc(&ctr[SMLC_CTR_BSSMAP_LE_RX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE]);
+ 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);
@@ -300,6 +321,17 @@ static void lcs_loc_req_wait_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t
.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,
},
},
};
@@ -313,7 +345,7 @@ static void lcs_loc_req_wait_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t
plr.bssmap_le.perform_loc_req.apdu = (struct bsslap_pdu){
.msg_type = BSSLAP_MSGT_TA_LAYER3,
.ta_layer3 = {
- .ta = lchan->rqd_ta,
+ .ta = lchan->last_ta,
},
};
} else {
@@ -365,11 +397,14 @@ static void lcs_loc_req_handover_performed(struct lcs_loc_req *lcs_loc_req)
.msg_type = BSSLAP_MSGT_RESET,
.reset = {
.cell_id = lchan->ts->trx->bts->cell_identity,
- .ta = lchan->rqd_ta,
+ .ta = lchan->last_ta,
.cause = BSSLAP_CAUSE_INTRA_BSS_HO,
},
};
- gsm48_lchan2chan_desc(&apdu->reset.chan_desc, lchan);
+ 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);
@@ -457,9 +492,7 @@ static void lcs_loc_req_got_loc_resp_onenter(struct osmo_fsm_inst *fi, uint32_t
LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
"Failed to send Perform Location Response (A-interface)\n");
else
- rate_ctr_inc(&lcs_loc_req->conn->sccp.msc->msc_ctrs->ctr[
- plr.location_estimate_present ? MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_SUCCESS
- : MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE]);
+ 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);
}
@@ -481,7 +514,8 @@ static void lcs_loc_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_s
};
/* If we're paging this subscriber for LCS, stop paging. */
- paging_request_cancel(lcs_loc_req->conn->bsub, BSC_PAGING_FOR_LCS);
+ 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)
@@ -498,7 +532,7 @@ static void lcs_loc_req_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_s
LOG_LCS_LOC_REQ(lcs_loc_req, LOGL_ERROR,
"Failed to send BSSMAP Perform Location Response (A-interface)\n");
else
- rate_ctr_inc(&lcs_loc_req->conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_PERFORM_LOCATION_RESPONSE_FAILURE]);
+ 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);
}
diff --git a/src/osmo-bsc/lcs_ta_req.c b/src/osmo-bsc/lcs_ta_req.c
index 97d6eb5c8..a1fa87301 100644
--- a/src/osmo-bsc/lcs_ta_req.c
+++ b/src/osmo-bsc/lcs_ta_req.c
@@ -56,14 +56,14 @@ static const struct osmo_tdef_state_timeout lcs_ta_req_fsm_timeouts[32] = {
osmo_tdef_fsm_inst_state_chg(FI, STATE, \
lcs_ta_req_fsm_timeouts, \
(bsc_gsmnet)->T_defs, \
- 5)
+ -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)
+ } while (0)
static struct osmo_fsm lcs_ta_req_fsm;
@@ -89,7 +89,7 @@ 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 anoter TA Request FSM, this TA Request is still active\n");
+ "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);
@@ -122,17 +122,6 @@ void lcs_ta_req_wait_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
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);
-
/* Do we already have an active lchan with knowledge of TA? */
lchan = loc_req->conn->lchan;
if (lchan) {
@@ -147,6 +136,17 @@ void lcs_ta_req_wait_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
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");
@@ -210,7 +210,7 @@ void lcs_ta_req_got_ta_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
.msg_type = BSSLAP_MSGT_TA_RESPONSE,
.ta_response = {
.cell_id = lchan->ts->trx->bts->cell_identity,
- .ta = lchan->rqd_ta,
+ .ta = lchan->last_ta,
},
},
},
diff --git a/src/osmo-bsc/meas_feed.c b/src/osmo-bsc/meas_feed.c
index 889f6efc3..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>
@@ -20,15 +20,17 @@
#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)
{
@@ -36,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");
@@ -48,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;
@@ -83,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);
@@ -108,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 &= ~OSMO_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 &= ~OSMO_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;
}
@@ -175,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/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 7feed2a27..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>
@@ -32,43 +35,6 @@
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/bts.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;
-}
-
#define NEIGHBOR_ADD_CMD "neighbor "
#define NEIGHBOR_DEL_CMD "no neighbor "
#define NEIGHBOR_DOC "Manage local and remote-BSS neighbor cells\n"
@@ -76,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)
+static int neighbor_ident_vty_parse_lac(struct vty *vty, struct gsm0808_cell_id *cell_id, 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 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];
@@ -141,379 +94,388 @@ 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;
- }
- if (!bts) {
- vty_out(vty, "%% Error: cannot add local BTS neighbor, no BTS on this node%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
- 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;
- }
- 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);
- return CMD_SUCCESS;
-}
+ *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];
-static int del_local_bts(struct vty *vty, struct gsm_bts *neigh)
-{
- 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;
- }
- 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;
+ 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 (rc == 0)
- vty_out(vty, "%% BTS %u is no neighbor of BTS %u%s",
- neigh->nr, bts->nr, VTY_NEWLINE);
- return CMD_SUCCESS;
-}
-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));
-}
+ 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;
+ }
-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)));
+ cgi_ps->rai.lac.lac = atoi(lac);
+ cgi_ps->rai.rac = atoi(rac);
+ cgi_ps->cell_identity = atoi(ci);
+ return 0;
}
-DEFUN(cfg_neighbor_add_lac_ci, cfg_neighbor_add_lac_ci_cmd,
- NEIGHBOR_ADD_CMD LAC_CI_PARAMS,
- NEIGHBOR_ADD_DOC LAC_CI_DOC)
+void neighbor_ident_vty_parse_arfcn_bsic(struct cell_ab *ab, const char **argv)
{
- return add_local_bts(vty, bts_by_cell_id(vty, neighbor_ident_vty_parse_lac_ci(vty, argv)));
-}
-
-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)));
-}
+ const char *arfcn_str = argv[0];
+ const char *bsic_str = argv[1];
-bool neighbor_ident_key_matches_bts(const struct neighbor_ident_key *key, struct gsm_bts *bts)
-{
- if (!bts || !key)
- return false;
- return key->arfcn == bts->c0->arfcn
- && (key->bsic == BSIC_ANY || key->bsic == bts->bsic);
+ *ab = (struct cell_ab){
+ .arfcn = atoi(arfcn_str),
+ .bsic = (!strcmp(bsic_str, "any")) ? BSIC_ANY : atoi(bsic_str),
+ };
}
-static int add_remote_or_local_bts(struct vty *vty, const struct gsm0808_cell_id *cell_id,
- const struct neighbor_ident_key *key)
+#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)
{
- 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 *neighbor;
- 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;
- }
+ 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;
+ }
- /* 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? */
+ /* 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;
}
- return add_local_bts(vty, local_neigh);
}
- /* 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);
- return CMD_WARNING;
- }
+ neighbor = talloc_zero(bts, struct neighbor);
+ *neighbor = *n;
+ llist_add_tail(&neighbor->entry, &bts->neighbors);
+ return CMD_SUCCESS;
+}
- /* 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);
+/* 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)
+{
+ struct neighbor *neighbor;
- 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;
+ 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:
- reason = "";
- break;
+ continue;
}
- vty_out(vty, "%% Error adding neighbor-BSS Cell Identifier %s%s%s",
- gsm0808_cell_id_name(cell_id), reason, VTY_NEWLINE);
- return CMD_WARNING;
+ llist_del(&neighbor->entry);
+ talloc_free(neighbor);
+ return CMD_SUCCESS;
}
- 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;
+ 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_by_key(struct vty *vty, const struct neighbor_ident_key *key)
+static int del_neighbor_by_cell_ab(struct vty *vty, const struct cell_ab *cell_ab)
{
- int removed = 0;
- int rc;
struct gsm_bts *bts = vty->index;
- struct gsm_bts_ref *neigh, *safe;
+ struct neighbor *neighbor, *safe;
+ struct gsm_bts *neighbor_bts;
+ struct cell_ab neighbor_ab;
+ int count = 0;
- 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);
- return CMD_WARNING;
- }
+ 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;
- /* 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))
+ 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;
- 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 ++;
+ llist_del(&neighbor->entry);
+ talloc_free(neighbor);
+ count++;
}
+ 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)
+{
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_BTS_NR,
+ .bts_nr = atoi(argv[0]),
+ };
+ 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_lac, cfg_neighbor_add_lac_cmd,
+ NEIGHBOR_ADD_CMD LAC_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_DOC)
+{
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
+ };
+ if (neighbor_ident_vty_parse_lac(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- }
- return CMD_SUCCESS;
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
}
-struct nil_match_bts_data {
- int bts_nr;
- const struct neighbor_ident_key *found;
-};
+DEFUN(cfg_neighbor_add_lac_ci, cfg_neighbor_add_lac_ci_cmd,
+ NEIGHBOR_ADD_CMD LAC_CI_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_CI_DOC)
+{
+ 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);
+}
-static bool nil_match_bts(const struct neighbor_ident_key *key,
- const struct gsm0808_cell_id_list2 *val,
- void *cb_data)
+DEFUN(cfg_neighbor_add_cgi, cfg_neighbor_add_cgi_cmd,
+ NEIGHBOR_ADD_CMD CGI_PARAMS,
+ NEIGHBOR_ADD_DOC CGI_DOC)
{
- struct nil_match_bts_data *d = cb_data;
- if (key->from_bts == d->bts_nr) {
- d->found = key;
- return false;
- }
- return true;
+ 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_bts_entry_exists(uint8_t from_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)
{
- struct nil_match_bts_data d = {
- .bts_nr = from_bts,
+ struct neighbor n = {
+ .type = NEIGHBOR_TYPE_CELL_ID,
};
- neighbor_ident_iter(g_neighbor_cells, nil_match_bts, &d);
- return (bool)d.found;
+ 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 neighbor_del_all(struct vty *vty)
{
- int rc;
- int removed = 0;
struct gsm_bts *bts = vty->index;
-
+ struct neighbor *n;
OSMO_ASSERT((vty->node == BTS_NODE) && bts);
- /* Remove all local neighbors and print to VTY for the user to know what changed */
- while (1) {
- struct gsm_bts_ref *neigh = llist_first_entry_or_null(&bts->local_neighbors, struct gsm_bts_ref, entry);
- struct gsm_bts *neigh_bts;
- if (!neigh)
- break;
-
- neigh_bts = neigh->bts;
- OSMO_ASSERT(neigh_bts);
-
- /* It would be more efficient to just llist_del() the gsm_bts_ref directly, but for the sake of
- * safe/sane API use and against code dup, rather invoke the central gsm_bts_local_neighbor_del()
- * function intended for this task. */
- 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;
- } else {
- vty_out(vty, "%% Error while removing local neighbor bts %u to bts %u, aborted%s",
- bts->nr, neigh_bts->nr, VTY_NEWLINE);
- return CMD_WARNING;
- }
+ if (llist_empty(&bts->neighbors)) {
+ vty_out(vty, "%% No neighbors configured%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
}
- /* Remove all remote-BSS neighbors */
- while (1) {
- struct neighbor_ident_key k;
- struct nil_match_bts_data d = {
- .bts_nr = bts->nr,
- };
- neighbor_ident_iter(g_neighbor_cells, nil_match_bts, &d);
- if (!d.found)
- break;
- k = *d.found;
- if (neighbor_ident_del(g_neighbor_cells, &k)) {
- vty_out(vty, "%% Removed remote BSS neighbor %s%s",
- neighbor_ident_key_name(&k), VTY_NEWLINE);
- removed++;
- } else {
- vty_out(vty, "%% Error while removing remote BSS neighbor %s, aborted%s",
- neighbor_ident_key_name(&k), VTY_NEWLINE);
- return CMD_WARNING;
- }
+ /* 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);
}
-
- if (!removed)
- vty_out(vty, "%% No neighbors configured%s", VTY_NEWLINE);
return CMD_SUCCESS;
}
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)
+ NEIGHBOR_ADD_CMD LAC_PARAMS " " CELL_AB_VTY_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_DOC CELL_AB_VTY_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))
+ 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;
- return add_remote_or_local_bts(vty, cell_id, &nik);
+ neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + LAC_ARGC);
+ return neighbor_ident_add_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)
+ NEIGHBOR_ADD_CMD LAC_CI_PARAMS " " CELL_AB_VTY_PARAMS,
+ NEIGHBOR_ADD_DOC LAC_CI_DOC CELL_AB_VTY_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,
+ .cell_id.ab_present = true,
+ };
+ if (neighbor_ident_vty_parse_lac_ci(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- return add_remote_or_local_bts(vty, cell_id, &nik);
+ neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + LAC_CI_ARGC);
+ return neighbor_ident_add_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)
+ NEIGHBOR_ADD_CMD CGI_PARAMS " " CELL_AB_VTY_PARAMS,
+ NEIGHBOR_ADD_DOC CGI_DOC CELL_AB_VTY_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,
+ .cell_id.ab_present = true,
+ };
+ if (neighbor_ident_vty_parse_cgi(vty, &n.cell_id.id, argv))
return CMD_WARNING;
- if (!neighbor_ident_vty_parse_key_params(vty, argv + 4, &nik))
+ neighbor_ident_vty_parse_arfcn_bsic(&n.cell_id.ab, argv + CGI_ARGC);
+ return neighbor_ident_add_neighbor(vty, vty->index, &n);
+}
+
+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 add_remote_or_local_bts(vty, cell_id, &nik);
+ 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_del_bts_nr, cfg_neighbor_del_bts_nr_cmd,
NEIGHBOR_DEL_CMD LOCAL_BTS_PARAMS,
NEIGHBOR_DEL_DOC LOCAL_BTS_DOC)
{
- return del_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_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)
+DEFUN(cfg_neighbor_del_lac, cfg_neighbor_del_lac_cmd,
+ NEIGHBOR_DEL_CMD LAC_PARAMS,
+ NEIGHBOR_DEL_DOC LAC_DOC)
{
- struct neighbor_ident_key key;
+ 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_del_neighbor(vty, vty->index, &n);
+}
- if (!neighbor_ident_vty_parse_key_params(vty, argv, &key))
+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 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_del_neighbor(vty, vty->index, &n);
+}
- return del_by_key(vty, &key);
+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 neighbor_ident_del_neighbor(vty, vty->index, &n);
+}
+
+DEFUN(cfg_neighbor_del_cgi_ps, cfg_neighbor_del_cgi_ps_cmd,
+ NEIGHBOR_DEL_CMD CGI_PS_PARAMS,
+ NEIGHBOR_DEL_DOC CGI_PS_DOC)
+{
+ 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 CELL_AB_VTY_PARAMS,
+ NEIGHBOR_DEL_DOC CELL_AB_VTY_DOC)
+{
+ struct cell_ab ab;
+ neighbor_ident_vty_parse_arfcn_bsic(&ab, argv);
+ return del_neighbor_by_cell_ab(vty, &ab);
}
DEFUN(cfg_neighbor_del_all, cfg_neighbor_del_all_cmd,
@@ -525,107 +487,107 @@ DEFUN(cfg_neighbor_del_all, cfg_neighbor_del_all_cmd,
return neighbor_del_all(vty);
}
-struct write_neighbor_ident_entry_data {
- struct vty *vty;
- const char *indent;
- struct gsm_bts *bts;
-};
+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;
+}
+
+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],
@@ -633,43 +595,57 @@ 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 6cbb40cdb..e5d3fad28 100644
--- a/src/osmo-bsc/net_init.c
+++ b/src/osmo-bsc/net_init.c
@@ -25,41 +25,71 @@
#include <osmocom/bsc/handover_cfg.h>
#include <osmocom/bsc/chan_alloc.h>
#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/bts_setup_ramp.h>
+#include <osmocom/bsc/paging.h>
static struct osmo_tdef gsm_network_T_defs[] = {
- { .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=100, .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=-3111, .default_val=4, .desc="Wait time after lchan was released in error (should be T3111 + 2s)" },
- { .T=-3210, .default_val=20, .desc="After L3 Complete, wait for MSC to confirm" },
+ { .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" },
+ { .T = -2427, .default_val = 5, .desc = "timeout for MGCP response from MGW" },
{}
};
@@ -90,8 +120,7 @@ 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;
@@ -105,5 +134,7 @@ struct gsm_network *gsm_network_init(void *ctx)
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
index c29f53d3c..a781cb1dc 100644
--- a/src/osmo-bsc/nm_bb_transc_fsm.c
+++ b/src/osmo-bsc/nm_bb_transc_fsm.c
@@ -1,6 +1,6 @@
/* NM BaseBand Transceiver FSM */
-/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* All Rights Reserved
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -32,6 +32,7 @@
#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>
@@ -40,6 +41,18 @@
#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
//////////////////////////
@@ -48,21 +61,30 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3
{
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;
- struct gsm_nm_state *new_state;
+ 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;
+ 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);
@@ -84,10 +106,36 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event
}
}
-static void configure_loop(struct gsm_bts_bb_trx *bb_transc, struct gsm_nm_state *state, bool allow_opstart)
+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 (state->administrative != NM_STATE_UNLOCKED && !bb_transc->mo.adm_unlock_sent) {
+
+ 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
@@ -98,14 +146,22 @@ static void configure_loop(struct gsm_bts_bb_trx *bb_transc, struct gsm_nm_state
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.opstart_sent) {
+ 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);
- /* 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);
- }
+ }
}
static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
@@ -113,7 +169,7 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_
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) {
+ 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;
}
@@ -123,13 +179,31 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_
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;
- struct gsm_nm_state *new_state;
+ 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;
+ 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);
@@ -150,6 +224,9 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
default:
return;
}
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, false);
+ break;
default:
OSMO_ASSERT(0);
}
@@ -159,8 +236,6 @@ static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t p
{
struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv;
- /* Warning: In here we may be acessing an state older than new_state
- from prev (syncrhonous) FSM state */
configure_loop(bb_transc, &bb_transc->mo.nm_state, true);
}
@@ -169,12 +244,29 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
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;
- struct gsm_nm_state *new_state;
+ 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;
+ 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;
@@ -187,7 +279,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
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) {
+ 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
@@ -203,6 +295,9 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
default:
return;
}
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(bb_transc, &bb_transc->mo.nm_state, true);
+ break;
default:
OSMO_ASSERT(0);
}
@@ -214,31 +309,41 @@ static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state
/* 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.opstart_sent = 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_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;
- struct gsm_nm_state *new_state;
+ 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;
+ 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:
@@ -260,7 +365,11 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
bb_transc->mo.opstart_sent = false;
break;
case NM_EV_OML_DOWN:
- nm_bb_transc_fsm_state_chg(fi, NM_BB_TRANSC_ST_OP_DISABLED_NOTINSTALLED);
+ 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);
@@ -271,7 +380,8 @@ 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_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) |
@@ -282,7 +392,12 @@ static struct osmo_fsm_state nm_bb_transc_fsm_states[] = {
},
[NM_BB_TRANSC_ST_OP_DISABLED_DEPENDENCY] = {
.in_event_mask =
- X(NM_EV_STATE_CHG_REP),
+ 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) |
@@ -293,7 +408,12 @@ static struct osmo_fsm_state nm_bb_transc_fsm_states[] = {
},
[NM_BB_TRANSC_ST_OP_DISABLED_OFFLINE] = {
.in_event_mask =
- X(NM_EV_STATE_CHG_REP),
+ 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) |
diff --git a/src/osmo-bsc/nm_bts_fsm.c b/src/osmo-bsc/nm_bts_fsm.c
index 5f47fdcc1..aaccea0bf 100644
--- a/src/osmo-bsc/nm_bts_fsm.c
+++ b/src/osmo-bsc/nm_bts_fsm.c
@@ -1,6 +1,6 @@
/* NM BTS FSM */
-/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* All Rights Reserved
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -48,6 +48,9 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3
{
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;
@@ -56,15 +59,19 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3
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;
- struct gsm_nm_state *new_state;
+ 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;
+ 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);
@@ -86,30 +93,78 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event
}
}
-static void configure_loop(struct gsm_bts *bts, struct gsm_nm_state *state, bool allow_opstart) {
+static void configure_loop(struct gsm_bts *bts, const struct gsm_nm_state *state, bool allow_opstart)
+{
struct msgb *msgb;
- if (!bts->mo.set_attr_sent && !bts->mo.set_attr_ack_received) {
+ 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_attr_bts_get(bts);
+ msgb = nanobts_gen_set_bts_attr(bts);
abis_nm_set_bts_attr(bts, msgb->data, msgb->len);
msgb_free(msgb);
}
- if (state->administrative != NM_STATE_UNLOCKED && !bts->mo.adm_unlock_sent) {
+ 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);
- }
- }
+ 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)
@@ -119,7 +174,7 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_
/* 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) {
+ if (bts->site_mgr->peer_has_no_avstate_offline) {
nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_OFFLINE);
return;
}
@@ -130,9 +185,16 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
{
struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
struct nm_statechg_signal_data *nsd;
- struct gsm_nm_state *new_state;
+ 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;
@@ -140,7 +202,7 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
return;
case NM_EV_STATE_CHG_REP:
nsd = (struct nm_statechg_signal_data *)data;
- new_state = nsd->new_state;
+ 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);
@@ -161,6 +223,9 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
default:
return;
}
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(bts, &bts->mo.nm_state, false);
+ break;
default:
OSMO_ASSERT(0);
}
@@ -170,8 +235,6 @@ static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t p
{
struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
- /* Warning: In here we may be acessing an state older than new_state
- from prev (syncrhonous) FSM state */
configure_loop(bts, &bts->mo.nm_state, true);
}
@@ -179,9 +242,16 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
{
struct gsm_bts *bts = (struct gsm_bts *)fi->priv;
struct nm_statechg_signal_data *nsd;
- struct gsm_nm_state *new_state;
+ 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;
@@ -189,7 +259,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
return;
case NM_EV_STATE_CHG_REP:
nsd = (struct nm_statechg_signal_data *)data;
- new_state = nsd->new_state;
+ new_state = &nsd->new_state;
if (new_state->operational == NM_OPSTATE_ENABLED) {
nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_ENABLED);
return;
@@ -202,7 +272,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
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) {
+ 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
@@ -218,6 +288,9 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
default:
return;
}
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(bts, &bts->mo.nm_state, true);
+ break;
default:
OSMO_ASSERT(0);
}
@@ -231,19 +304,28 @@ static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state
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;
- struct gsm_nm_state *new_state;
+ 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;
+ new_state = &nsd->new_state;
if (new_state->operational == NM_OPSTATE_ENABLED)
return;
switch (new_state->availability) { /* operational = DISABLED */
@@ -277,7 +359,8 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
bts->mo.opstart_sent = false;
break;
case NM_EV_OML_DOWN:
- nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_NOTINSTALLED);
+ 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);
@@ -288,7 +371,8 @@ 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_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) |
@@ -299,8 +383,11 @@ static struct osmo_fsm_state nm_bts_fsm_states[] = {
},
[NM_BTS_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_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) |
@@ -311,8 +398,11 @@ static struct osmo_fsm_state nm_bts_fsm_states[] = {
},
[NM_BTS_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_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) |
@@ -322,15 +412,15 @@ static struct osmo_fsm_state nm_bts_fsm_states[] = {
.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,
+ .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,
},
};
diff --git a/src/osmo-bsc/nm_bts_sm_fsm.c b/src/osmo-bsc/nm_bts_sm_fsm.c
index ce9e15b0e..24a1ec3c6 100644
--- a/src/osmo-bsc/nm_bts_sm_fsm.c
+++ b/src/osmo-bsc/nm_bts_sm_fsm.c
@@ -1,6 +1,6 @@
/* NM BTS Site Manager FSM */
-/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* All Rights Reserved
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -49,6 +49,7 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3
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;
}
@@ -57,14 +58,17 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event
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;
- struct gsm_nm_state *new_state;
+ 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;
+ 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
@@ -78,8 +82,8 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event
"have your .cfg with 'type nanobts'. Otherwise, you probably "
"are using an old osmo-bts; automatically adjusting OML "
"behavior to be backward-compatible.\n");
- bts->site_mgr.peer_has_no_avstate_offline = true;
}
+ site_mgr->peer_has_no_avstate_offline = true;
nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_ENABLED);
return;
}
@@ -99,15 +103,41 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event
}
}
+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;
- struct gsm_nm_state *new_state;
+ 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;
+ 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;
@@ -132,22 +162,24 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
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;
- struct gsm_bts *bts = gsm_bts_sm_get_bts(site_mgr);
- if (!site_mgr->mo.opstart_sent) {
- site_mgr->mo.opstart_sent = true;
- abis_nm_opstart(bts, NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff);
- }
+
+ 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;
- struct gsm_nm_state *new_state;
+ 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;
+ 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;
@@ -166,19 +198,23 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
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;
- struct gsm_nm_state *new_state;
+ 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;
+ new_state = &nsd->new_state;
if (new_state->operational == NM_OPSTATE_ENABLED)
return;
switch (new_state->availability) { /* operational = DISABLED */
@@ -212,7 +248,8 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
site_mgr->mo.opstart_sent = false;
break;
case NM_EV_OML_DOWN:
- nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED);
+ 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);
@@ -223,7 +260,8 @@ 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_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) |
@@ -234,7 +272,9 @@ static struct osmo_fsm_state nm_bts_sm_fsm_states[] = {
},
[NM_BTS_SM_ST_OP_DISABLED_DEPENDENCY] = {
.in_event_mask =
- X(NM_EV_STATE_CHG_REP),
+ 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) |
@@ -244,7 +284,9 @@ static struct osmo_fsm_state nm_bts_sm_fsm_states[] = {
},
[NM_BTS_SM_ST_OP_DISABLED_OFFLINE] = {
.in_event_mask =
- X(NM_EV_STATE_CHG_REP),
+ 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) |
diff --git a/src/osmo-bsc/nm_channel_fsm.c b/src/osmo-bsc/nm_channel_fsm.c
index 676c471e6..2f389d7ac 100644
--- a/src/osmo-bsc/nm_channel_fsm.c
+++ b/src/osmo-bsc/nm_channel_fsm.c
@@ -1,6 +1,6 @@
/* NM Radio Channel FSM */
-/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* All Rights Reserved
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -35,6 +35,7 @@
#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))
@@ -58,14 +59,15 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3
static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct nm_statechg_signal_data *nsd;
- struct gsm_nm_state *new_state;
+ 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;
+ 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);
@@ -87,24 +89,19 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event
}
}
-static void configure_loop(struct gsm_bts_trx_ts *ts, struct gsm_nm_state *state, bool allow_opstart)
+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) {
- /* FIXME: using this here makes crazy lots of .o
- dependencies be fulled in, ending up in
- osmo_bsc_main.o which conficts due to containing its
- own main() */
- LOGPFSML(ts->mo.fi, LOGL_ERROR,
- "FIXME: Here OML link should br dropped, "
- "something is wrong in your setup!\n");
- //ipaccess_drop_oml_deferred(trx->bts);
- }
+ 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) {
@@ -125,7 +122,7 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_
{
struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
- if (ts->trx->bts->site_mgr.peer_has_no_avstate_offline) {
+ if (ts->trx->bts->site_mgr->peer_has_no_avstate_offline) {
nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE);
return;
}
@@ -136,9 +133,12 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
{
struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
struct nm_statechg_signal_data *nsd;
- struct gsm_nm_state *new_state;
+ 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;
@@ -146,7 +146,7 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
return;
case NM_EV_STATE_CHG_REP:
nsd = (struct nm_statechg_signal_data *)data;
- new_state = nsd->new_state;
+ 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);
@@ -167,6 +167,9 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
default:
return;
}
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(ts, &ts->mo.nm_state, false);
+ break;
default:
OSMO_ASSERT(0);
}
@@ -176,8 +179,6 @@ static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t p
{
struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
- /* Warning: In here we may be acessing an state older than new_state
- from prev (syncrhonous) FSM state */
configure_loop(ts, &ts->mo.nm_state, true);
}
@@ -185,9 +186,12 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
{
struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv;
struct nm_statechg_signal_data *nsd;
- struct gsm_nm_state *new_state;
+ 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;
@@ -195,7 +199,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
return;
case NM_EV_STATE_CHG_REP:
nsd = (struct nm_statechg_signal_data *)data;
- new_state = nsd->new_state;
+ new_state = &nsd->new_state;
if (new_state->operational == NM_OPSTATE_ENABLED) {
nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED);
return;
@@ -208,7 +212,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
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) {
+ 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
@@ -224,6 +228,9 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
default:
return;
}
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(ts, &ts->mo.nm_state, true);
+ break;
default:
OSMO_ASSERT(0);
}
@@ -239,17 +246,19 @@ static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state
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;
- struct gsm_nm_state *new_state;
+ 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;
+ new_state = &nsd->new_state;
if (new_state->operational == NM_OPSTATE_ENABLED)
return;
switch (new_state->availability) { /* operational = DISABLED */
@@ -283,7 +292,10 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
ts->mo.opstart_sent = false;
break;
case NM_EV_OML_DOWN:
- nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_NOTINSTALLED);
+ 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);
@@ -294,9 +306,9 @@ 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_STATE_CHG_REP) |
+ 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_DISABLED_OFFLINE) |
X(NM_CHAN_ST_OP_ENABLED),
@@ -306,8 +318,10 @@ static struct osmo_fsm_state nm_chan_fsm_states[] = {
},
[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_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) |
@@ -318,8 +332,10 @@ static struct osmo_fsm_state nm_chan_fsm_states[] = {
},
[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_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) |
diff --git a/src/osmo-bsc/nm_common_fsm.c b/src/osmo-bsc/nm_common_fsm.c
index 42c01e0b1..7719f0672 100644
--- a/src/osmo-bsc/nm_common_fsm.c
+++ b/src/osmo-bsc/nm_common_fsm.c
@@ -1,6 +1,6 @@
/* NM FSM, common bits */
-/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* All Rights Reserved
@@ -13,22 +13,78 @@
* 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.
+ * 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
index 8702ebeec..f5d7c270f 100644
--- a/src/osmo-bsc/nm_rcarrier_fsm.c
+++ b/src/osmo-bsc/nm_rcarrier_fsm.c
@@ -1,6 +1,6 @@
/* NM Radio Carrier FSM */
-/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
*
* All Rights Reserved
@@ -13,7 +13,7 @@
* 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.
+ * 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/>.
@@ -40,6 +40,16 @@
#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
//////////////////////////
@@ -48,6 +58,9 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3
{
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;
@@ -56,15 +69,19 @@ static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint3
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;
- struct gsm_nm_state *new_state;
+ 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;
+ 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);
@@ -86,13 +103,38 @@ static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event
}
}
-static void configure_loop(struct gsm_bts_trx *trx, struct gsm_nm_state *state, bool allow_opstart)
+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_attr_radio_get(trx->bts, trx);
+ msgb = nanobts_gen_set_radio_attr(trx->bts, trx);
abis_nm_set_radio_attr(trx, msgb->data, msgb->len);
msgb_free(msgb);
}
@@ -120,7 +162,7 @@ static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_
* 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 aboev estated bug, this code needs to be
+ * 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) {
@@ -134,9 +176,18 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
{
struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
struct nm_statechg_signal_data *nsd;
- struct gsm_nm_state *new_state;
+ 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;
@@ -144,7 +195,7 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
return;
case NM_EV_STATE_CHG_REP:
nsd = (struct nm_statechg_signal_data *)data;
- new_state = nsd->new_state;
+ 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);
@@ -165,6 +216,9 @@ static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event,
default:
return;
}
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(trx, &trx->mo.nm_state, false);
+ break;
default:
OSMO_ASSERT(0);
}
@@ -174,8 +228,6 @@ static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t p
{
struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
- /* Warning: In here we may be acessing an state older than new_state
- from prev (syncrhonous) FSM state */
configure_loop(trx, &trx->mo.nm_state, true);
}
@@ -183,9 +235,18 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
{
struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv;
struct nm_statechg_signal_data *nsd;
- struct gsm_nm_state *new_state;
+ 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;
@@ -193,7 +254,7 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
return;
case NM_EV_STATE_CHG_REP:
nsd = (struct nm_statechg_signal_data *)data;
- new_state = nsd->new_state;
+ new_state = &nsd->new_state;
if (new_state->operational == NM_OPSTATE_ENABLED) {
nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_ENABLED);
return;
@@ -213,6 +274,9 @@ static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, voi
default:
return;
}
+ case NM_EV_SETUP_RAMP_READY:
+ configure_loop(trx, &trx->mo.nm_state, true);
+ break;
default:
OSMO_ASSERT(0);
}
@@ -228,29 +292,54 @@ static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state
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;
- struct gsm_nm_state *new_state;
+ 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)
+ 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:
@@ -278,7 +367,11 @@ static void st_op_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
trx->mo.force_rf_lock ? NM_STATE_LOCKED : NM_STATE_UNLOCKED);
break;
case NM_EV_OML_DOWN:
- nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED);
+ 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);
@@ -289,7 +382,8 @@ 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_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) |
@@ -300,8 +394,11 @@ static struct osmo_fsm_state nm_rcarrier_fsm_states[] = {
},
[NM_RCARRIER_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_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) |
@@ -312,8 +409,11 @@ static struct osmo_fsm_state nm_rcarrier_fsm_states[] = {
},
[NM_RCARRIER_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_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) |
@@ -323,15 +423,15 @@ static struct osmo_fsm_state nm_rcarrier_fsm_states[] = {
.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,
+ .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,
},
};
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
index f168b6501..2a9805444 100644
--- a/src/osmo-bsc/osmo_bsc_bssap.c
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -29,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_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>
@@ -46,6 +49,8 @@
#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
@@ -62,7 +67,7 @@ static void update_msc_osmux_support(struct bsc_msc_data *msc,
int rc;
bool old_value = msc->remote_supports_osmux;
- rc = tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0);
+ 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");
@@ -113,8 +118,7 @@ static int bssmap_handle_reset(struct bsc_msc_data *msc,
/* 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(const struct bsc_paging_params *params, struct gsm_bts *bts, uint32_t lac)
+static void page_subscriber(const struct bsc_paging_params *params, struct gsm_bts *bts, uint32_t lac)
{
int ret;
@@ -131,16 +135,14 @@ page_subscriber(const struct bsc_paging_params *params, struct gsm_bts *bts, uin
"Paging request failed, or repeated paging on LAC %u\n", lac);
}
-static void
-page_all_bts(const struct bsc_paging_params *params)
+static void page_all_bts(const struct bsc_paging_params *params)
{
struct gsm_bts *bts;
llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list)
page_subscriber(params, bts, GSM_LAC_RESERVED_ALL_BTS);
}
-static void
-page_cgi(const struct bsc_paging_params *params)
+static void page_cgi(const struct bsc_paging_params *params)
{
int i;
for (i = 0; i < params->cil.id_list_len; i++) {
@@ -169,8 +171,7 @@ page_cgi(const struct bsc_paging_params *params)
}
}
-static void
-page_lac_and_ci(const struct bsc_paging_params *params)
+static void page_lac_and_ci(const struct bsc_paging_params *params)
{
int i;
@@ -192,8 +193,7 @@ page_lac_and_ci(const struct bsc_paging_params *params)
}
}
-static void
-page_ci(const struct bsc_paging_params *params)
+static void page_ci(const struct bsc_paging_params *params)
{
int i;
@@ -213,8 +213,7 @@ page_ci(const struct bsc_paging_params *params)
}
}
-static void
-page_lai_and_lac(const struct bsc_paging_params *params)
+static void page_lai_and_lac(const struct bsc_paging_params *params)
{
int i;
@@ -241,8 +240,7 @@ page_lai_and_lac(const struct bsc_paging_params *params)
}
}
-static void
-page_lac(const struct bsc_paging_params *params)
+static void page_lac(const struct bsc_paging_params *params)
{
int i;
@@ -276,7 +274,10 @@ static int bssmap_handle_paging(struct bsc_msc_data *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)) {
@@ -343,7 +344,7 @@ static int bssmap_handle_paging(struct bsc_msc_data *msc,
int bsc_paging_start(struct bsc_paging_params *params)
{
- rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_PAGING_ATTEMPTED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_PAGING_ATTEMPTED));
if (!params->bsub) {
params->bsub = bsc_subscr_find_or_create_by_imsi(bsc_gsmnet->bsc_subscribers, params->imsi.imsi,
@@ -353,8 +354,12 @@ int bsc_paging_start(struct bsc_paging_params *params)
return -EINVAL;
}
}
- if (params->tmsi != GSM_RESERVED_TMSI)
- params->bsub->tmsi = params->tmsi;
+ 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) {
@@ -399,68 +404,52 @@ int bsc_paging_start(struct bsc_paging_params *params)
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)
-{
- if (cipher > 0 && key == NULL) {
- LOGP(DRSL, LOGL_ERROR, "%s: Need to have an encryption key.\n",
- bsc_subscr_name(conn->bsub));
- return -1;
- }
-
- if (len > MAX_A5_KEY_LEN) {
- LOGP(DRSL, LOGL_ERROR, "%s: The key is too long: %d\n",
- bsc_subscr_name(conn->bsub), len);
- 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);
-
- 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);
- }
-
- return gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv);
-}
-
static int bssmap_handle_clear_cmd(struct gsm_subscriber_connection *conn,
struct msgb *msg, unsigned int length)
{
struct tlv_parsed tp;
- struct gscon_clear_cmd_data ccd = {
- .is_csfb = false,
- };
+ enum gsm0808_cause cause_0808;
- 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;
+ }
- ccd.cause_0808 = gsm0808_get_cause(&tp);
- if (ccd.cause_0808 < 0) {
+ 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. */
- ccd.cause_0808 = GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE;
+ cause_0808 = GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE;
}
- if (TLVP_PRESENT(&tp, GSM0808_IE_CSFB_INDICATION))
- ccd.is_csfb = true;
+ 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");
+ }
- osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CLEAR_CMD, &ccd);
+ osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CLEAR_CMD, &cause_0808);
return 0;
}
@@ -486,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;
}
@@ -500,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;
@@ -534,9 +528,6 @@ 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, bsc_gsmnet->a5_encryption_mask);
if (chosen_cipher < 0) {
LOGP(DMSC, LOGL_ERROR, "Reject: no overlapping A5 ciphers between BSC (0x%02x) "
@@ -545,13 +536,51 @@ static int bssmap_handle_cipher_mode(struct gsm_subscriber_connection *conn,
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:
@@ -561,7 +590,7 @@ reject:
return -1;
}
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_CIPHER_REJECT]);
+ 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;
}
@@ -613,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);
@@ -642,25 +669,85 @@ 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(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_LCLS_CONNECT_CTRL_ACK]);
+ 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 codec rate depending on the available
+/* 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_codecs(struct assignment_request *req, struct gsm0808_channel_type *ct,
- struct gsm_subscriber_connection *conn)
+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;
- struct gsm_bts *bts = conn_get_bts(conn);
if (!bts) {
LOGP(DMSC, LOGL_ERROR, "No lchan, cannot select codecs\n");
@@ -671,34 +758,34 @@ static int select_codecs(struct assignment_request *req, struct gsm0808_channel_
switch (ct->ch_rate_type) {
case GSM0808_SPEECH_FULL_BM:
- rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts,
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
RATE_PREF_FR);
- nc += (rc == 0);
+ nc += (rc == 0) ? 1 : 0;
break;
case GSM0808_SPEECH_HALF_LM:
- rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts,
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
RATE_PREF_HR);
- nc += (rc == 0);
+ 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[nc], ct, &conn->codec_list, msc, bts,
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
RATE_PREF_FR);
- nc += (rc == 0);
- rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts,
+ 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);
+ 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[nc], ct, &conn->codec_list, msc, bts,
+ rc = match_codec_pref(&req->ch_mode_rate_list[nc], ct, &conn->codec_list, msc, bts,
RATE_PREF_HR);
- nc += (rc == 0);
- rc = match_codec_pref(&req->ch_mode_rate[nc], ct, &conn->codec_list, msc, bts,
+ 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);
+ nc += (rc == 0) ? 1 : 0;
break;
default:
rc = -EINVAL;
@@ -718,8 +805,8 @@ static int select_codecs(struct assignment_request *req, struct gsm0808_channel_
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[i].chan_rate == CH_RATE_FULL ? "full rate" : "half rate",
- get_value_string(gsm48_chan_mode_names, req->ch_mode_rate[i].chan_mode),
+ 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));
}
@@ -734,49 +821,263 @@ static int select_sign_chan(struct assignment_request *req, struct gsm0808_chann
switch (ct->ch_rate_type) {
case GSM0808_SIGN_ANY:
- req->ch_mode_rate[nc++].chan_rate = CH_RATE_SDCCH;
- req->ch_mode_rate[nc++].chan_rate = CH_RATE_HALF;
- req->ch_mode_rate[nc++].chan_rate = CH_RATE_FULL;
+ 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[nc++].chan_rate = CH_RATE_SDCCH;
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_SDCCH;
break;
case GSM0808_SIGN_SDCCH_FULL_BM:
- req->ch_mode_rate[nc++].chan_rate = CH_RATE_SDCCH;
- req->ch_mode_rate[nc++].chan_rate = CH_RATE_FULL;
+ 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[nc++].chan_rate = CH_RATE_SDCCH;
- req->ch_mode_rate[nc++].chan_rate = CH_RATE_HALF;
+ 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[nc++].chan_rate = CH_RATE_FULL;
+ req->ch_mode_rate_list[nc++].chan_rate = CH_RATE_FULL;
break;
case GSM0808_SIGN_HALF_LM:
- req->ch_mode_rate[nc++].chan_rate = CH_RATE_HALF;
+ 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[nc++].chan_rate = CH_RATE_FULL;
- req->ch_mode_rate[nc++].chan_rate = CH_RATE_HALF;
+ 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[nc++].chan_rate = CH_RATE_HALF;
- req->ch_mode_rate[nc++].chan_rate = CH_RATE_FULL;
+ 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[i].chan_mode = GSM48_CMODE_SIGN;
+ 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.
*
@@ -787,12 +1088,8 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn,
{
struct msgb *resp;
struct tlv_parsed tp;
- uint16_t cic = 0;
- bool aoip = false;
- bool use_osmux = false;
- uint8_t osmux_cid = 0;
- struct sockaddr_storage rtp_addr;
struct gsm0808_channel_type ct;
+ struct gsm0808_group_callref gc;
uint8_t cause;
int rc;
struct assignment_request req = {};
@@ -803,9 +1100,10 @@ static int bssmap_handle_assignm_req(struct gsm_subscriber_connection *conn,
return -1;
}
- 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)) {
@@ -823,151 +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;
- 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;
- goto reject;
- }
- 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;
- goto reject;
- }
- } else {
- 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;
- goto reject;
- } else if (conn->sccp.msc->use_osmux == OSMUX_USAGE_ON)
- LOGP(DMSC, LOGL_NOTICE, "MSC not using Osmux but we have Osmux enabled.\n");
+ /* 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;
}
- /* 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;
- }
- }
+ /* 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);
- 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;
- }
+ return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_ASSIGNMENT_START, &req);
+ }
- req = (struct assignment_request){
- .aoip = aoip,
- .msc_assigned_cic = cic,
- .use_osmux = use_osmux,
- .osmux_cid = osmux_cid,
- };
+ bssmap_handle_ass_req_lcls(conn, &tp);
- /* Match codec information from the assignment command against the
- * local preferences of the BSC and BTS */
- rc = select_codecs(&req, &ct, conn);
- if (rc < 0) {
- cause = GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_UNAVAIL;
+ /* 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;
- }
-
- 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;
- }
- }
break;
case GSM0808_CHAN_SIGN:
- req = (struct assignment_request){
- .aoip = aoip,
- };
-
- rc = select_sign_chan(&req, &ct);
- if (rc < 0) {
- cause = GSM0808_CAUSE_INCORRECT_VALUE;
+ 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(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_FAILURE]);
+ 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
@@ -988,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)) {
@@ -1026,7 +1269,10 @@ static int bssmap_handle_confusion(struct gsm_subscriber_connection *conn,
enum gsm0808_cause_class cause_class;
struct gsm0808_diagnostics *diag;
- osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1);
+ 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)) {
@@ -1068,9 +1314,11 @@ static int bssmap_handle_common_id(struct gsm_subscriber_connection *conn,
struct msgb *msg, unsigned int length)
{
struct tlv_parsed tp;
- struct osmo_mobile_identity mi_imsi;
- osmo_bssap_tlv_parse(&tp, msg->l4h + 1, length - 1);
+ 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)) {
@@ -1080,13 +1328,102 @@ static int bssmap_handle_common_id(struct gsm_subscriber_connection *conn,
return -EINVAL;
}
- 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(conn->fi, LOGL_ERROR, "CommonID: could not parse IMSI\n");
- 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;
}
- osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_COMMON_ID_IND, &mi_imsi);
+ LOGPFSML(conn->fi, LOGL_ERROR, "VGCS ADDITIONAL INFO is not supported.\n");
return 0;
}
@@ -1151,14 +1488,18 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn,
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:
- rate_ctr_inc(&ctrs[MSC_CTR_BSSMAP_RX_DT1_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);
@@ -1190,6 +1531,26 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn,
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",
@@ -1200,16 +1561,6 @@ static int bssmap_rcvmsg_dt1(struct gsm_subscriber_connection *conn,
return ret;
}
-/* RSL Link Identifier is defined in 3GPP TS 3GPP TS 48.058, section 9.3.2.
- * .... .SSS - SAPI value used on the radio link;
- * ...P P... - priority for SAPI0 messages;
- * CC.. .... - control channel identification:
- * 00.. .... - main signalling channel (FACCH or SDCCH),
- * 01.. .... - SACCH,
- * other values are reserved. */
-#define DLCI2RSL_LINK_ID(dlci) \
- ((dlci & 0xc0) == 0xc0 ? 0x40 : 0x00) | (dlci & 0x07)
-
static int dtap_rcvmsg(struct gsm_subscriber_connection *conn,
struct msgb *msg, unsigned int length)
{
@@ -1263,7 +1614,10 @@ static int dtap_rcvmsg(struct gsm_subscriber_connection *conn,
/* convert DLCI to RSL link ID, store in msg->cb */
OBSC_LINKID_CB(gsm48) = DLCI2RSL_LINK_ID(header->link_id);
- dtap_rc = osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MT_DTAP, gsm48);
+ 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;
}
@@ -1357,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,
@@ -1364,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");
@@ -1382,14 +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;
}
- rate_ctr_inc(&lchan->conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_REQUIRED]);
- 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;
}
@@ -1407,14 +1771,17 @@ int bsc_tx_bssmap_ho_request_ack(struct gsm_subscriber_connection *conn, struct
.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->tch_mode),
- .chosen_encr_alg = new_lchan->encr.alg_id,
- .chosen_speech_version = gsm0808_permitted_speech(new_lchan->type, new_lchan->tch_mode),
+ .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)) {
+ 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"
@@ -1430,9 +1797,22 @@ int bsc_tx_bssmap_ho_request_ack(struct gsm_subscriber_connection *conn, struct
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(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_RQST_ACKNOWLEDGE]);
+ 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_ack2(&params);
msgb_free(rr_ho_command);
@@ -1448,7 +1828,7 @@ int bsc_tx_bssmap_ho_detect(struct gsm_subscriber_connection *conn)
if (!msg)
return -ENOMEM;
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_DETECT]);
+ 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);
}
@@ -1462,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) {
@@ -1488,7 +1868,7 @@ enum handover_result bsc_tx_bssmap_ho_complete(struct gsm_subscriber_connection
return HO_RESULT_ERROR;
}
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_COMPLETE]);
+ 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");
@@ -1510,9 +1890,186 @@ void bsc_tx_bssmap_ho_failure(struct gsm_subscriber_connection *conn)
return;
}
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_FAILURE]);
+ 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_filter.c b/src/osmo-bsc/osmo_bsc_filter.c
index 497b3e4de..2b58ccf54 100644
--- a/src/osmo-bsc/osmo_bsc_filter.c
+++ b/src/osmo-bsc/osmo_bsc_filter.c
@@ -119,5 +119,23 @@ int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg)
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_lcls.c b/src/osmo-bsc/osmo_bsc_lcls.c
index 4ab04419d..eab0be4d1 100644
--- a/src/osmo-bsc/osmo_bsc_lcls.c
+++ b/src/osmo-bsc/osmo_bsc_lcls.c
@@ -30,6 +30,7 @@
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/osmo_bsc_lcls.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[] = {
@@ -243,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)
@@ -345,15 +352,14 @@ static bool lcls_enable_possible(const 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;
}
@@ -556,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) {
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
index aa45bf07f..8da319993 100644
--- a/src/osmo-bsc/osmo_bsc_main.c
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -39,6 +39,7 @@
#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>
@@ -57,6 +58,7 @@
#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>
@@ -67,8 +69,10 @@
#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>
@@ -84,17 +88,16 @@
#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 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 options:\n");
printf(" -h --help This text.\n");
@@ -104,10 +107,8 @@ 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");
@@ -152,16 +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;
@@ -233,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;
@@ -256,14 +255,14 @@ 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 */
+ 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;
int i;
/* re-set the MA to all-zero */
@@ -271,14 +270,7 @@ static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts)
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 = OSMO_BYTES_FOR_BITS(num_cell_arfcns);
@@ -303,21 +295,34 @@ static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts)
else
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;
LOG_TRX(trx, DRSL, LOGL_NOTICE, "bootstrapping RSL "
"on ARFCN %u using MCC-MNC %s LAC=%u CID=%u BSIC=%u\n",
trx->arfcn, osmo_plmn_name(&bsc_gsmnet->plmn),
- trx->bts->location_area_code,
- trx->bts->cell_identity, trx->bts->bsic);
+ 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);
}
@@ -334,170 +339,71 @@ static void bootstrap_rsl(struct gsm_bts_trx *trx)
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);
}
- /* Start CBCH transmit timer if CBCH is present */
- if (trx->nr == 0 && gsm_bts_get_cbch(trx->bts))
- bts_cbch_timer_schedule(trx->bts);
-
/* Drop all expired channel requests in the list */
- abis_rsl_chan_rqd_queue_flush(trx->bts);
+ abis_rsl_chan_rqd_queue_flush(bts);
}
-static void all_ts_dispatch_event(struct gsm_bts_trx *trx, uint32_t event)
+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)
{
- 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);
- }
+ 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);
}
-/* 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)
+static bool nch_position_compatible_with_combined_ccch(const struct gsm_bts *bts)
{
- 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);
-
- if (subsys != SS_L_INPUT)
- return -EINVAL;
-
- LOGP(DLMI, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__,
- get_value_string(e1inp_signal_names, 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;
- 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]);
- }
- }
- if (isd->link_type == E1INP_SIGN_RSL)
- bootstrap_rsl(trx);
+ 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 S_L_INP_TEI_DN:
- 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);
- } else if (isd->link_type == E1INP_SIGN_RSL) {
- rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL]);
- acc_ramp_abort(&trx->bts->acc_ramp);
- all_ts_dispatch_event(trx, TS_EV_RSL_DOWN);
- if (trx->nr == 0)
- osmo_timer_del(&trx->bts->cbch_timer);
- }
-
- gsm_bts_mo_reset(trx->bts);
-
- abis_nm_clear_queue(trx->bts);
+ case 2:
+ if (bts->nch.first_block == 0)
+ return true;
break;
default:
break;
}
- return 0;
+ /* anything else is not permitted */
+ return false;
}
-static int bootstrap_bts(struct gsm_bts *bts)
+static void bootstrap_bts(struct gsm_bts *bts)
{
- struct gsm_bts_trx *trx;
unsigned int n = 0;
- 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;
- }
-
- /* 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;
- }
- }
-
/* Control Channel Description is set from vty/config */
- /* Indicate R99 MSC in SI3 */
- bts->si_common.chan_desc.mscr = 1;
-
/* 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;
@@ -510,6 +416,11 @@ static int bootstrap_bts(struct gsm_bts *bts)
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);
@@ -518,18 +429,84 @@ static int bootstrap_bts(struct gsm_bts *bts)
bts->si_common.chan_desc.ccch_conf = (n << 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;
+ 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->chan_load_samples_idx = 0;
+ bts_setup_ramp_init_bts(bts);
/* ACC ramping is initialized from vty/config */
/* Initialize the BTS state */
- gsm_bts_mo_reset(bts);
+ 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 */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct input_signal_data *isd = signal_data;
+ struct gsm_bts_trx *trx = isd->trx;
+ int rc;
+
+ if (subsys != SS_L_INPUT)
+ return -EINVAL;
+
+ LOGP(DLMI, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__,
+ get_value_string(e1inp_signal_names, signal));
+ switch (signal) {
+ case S_L_INP_TEI_UP:
+ if (isd->link_type == E1INP_SIGN_OML) {
+ /* 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) {
+ 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:
+ 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(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(rate_ctr_group_get_ctr(trx->bts->bts_ctrs, BTS_CTR_BTS_RSL_FAIL));
+ acc_ramp_abort(&trx->bts->acc_ramp);
+ gsm_trx_all_ts_dispatch(trx, TS_EV_RSL_DOWN, NULL);
+ }
+
+ gsm_bts_sm_mo_reset(trx->bts->site_mgr);
+
+ abis_nm_clear_queue(trx->bts);
+ break;
+ default:
+ break;
+ }
return 0;
}
@@ -541,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;
@@ -555,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");
@@ -577,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;
{
@@ -586,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;
{
@@ -601,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:
@@ -659,7 +655,9 @@ 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",
@@ -673,30 +671,44 @@ static struct vty_app_info vty_info = {
"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 */
+ /* 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 SIGUSR1:
talloc_report(tall_vty_ctx, stderr);
talloc_report_full(tall_bsc_ctx, stderr);
@@ -818,11 +830,23 @@ static const struct log_info_cat osmo_bsc_categories[] = {
.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)
@@ -847,7 +871,45 @@ 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)
{
@@ -863,13 +925,13 @@ 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();
@@ -878,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);
@@ -906,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();
- 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);
@@ -920,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);
@@ -936,6 +1000,19 @@ int main(int argc, char **argv)
exit(1);
}
+ 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(bsc_gsmnet, &bsc_gsmnet->rf_ctrl_name, rf_ctrl);
@@ -957,6 +1034,9 @@ int main(int argc, char **argv)
}
}
+ 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");
@@ -964,15 +1044,6 @@ int main(int argc, char **argv)
}
}
- 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);
- exit(1);
- }
-
if (osmo_bsc_sigtran_init(&bsc_gsmnet->mscs) != 0) {
LOGP(DNM, LOGL_ERROR, "Failed to initialize sigtran backhaul.\n");
exit(1);
@@ -990,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) {
@@ -998,7 +1072,7 @@ int main(int argc, char **argv)
}
}
- while (1) {
+ while (!osmo_select_shutdown_done()) {
osmo_select_main_ctx(0);
}
diff --git a/src/osmo-bsc/osmo_bsc_mgcp.c b/src/osmo-bsc/osmo_bsc_mgcp.c
index 90a73e509..d7de4d264 100644
--- a/src/osmo-bsc/osmo_bsc_mgcp.c
+++ b/src/osmo-bsc/osmo_bsc_mgcp.c
@@ -2,6 +2,7 @@
* 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
@@ -19,6 +20,15 @@
*
*/
+#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>
@@ -33,33 +43,136 @@
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 (sscanf(asp->cfg.name, "asp-clnt-msc-%u", &msc_nr) != 1) {
- LOGP(DMSC, LOGL_ERROR, "Cannot find to which MSC the ASP %s belongs\n", asp->cfg.name);
+ 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_NOTICE, "%s: Received IPA-encapsulated MGCP: %s\n", asp->cfg.name, msg->l2h);
+ 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) {
- /* 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 = send(msc->mgcp_ipa.ofd.fd, msgb_l2(msg), msgb_l2len(msg), 0);
- } else
+ 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;
}
diff --git a/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c
index 772bea588..24255924f 100644
--- a/src/osmo-bsc/osmo_bsc_msc.c
+++ b/src/osmo-bsc/osmo_bsc_msc.c
@@ -40,6 +40,7 @@
#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>
@@ -47,67 +48,281 @@
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_ASSIGMENT_RQST] = {"bssmap:rx:dt1:assignment:rqst", "Number of received BSSMAP DT1 ASSIGMENT 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_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_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?)"},
+ [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"},
+ [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_ASSIGMENT_FAILURE] = {"bssmap:tx:dt1:assigment:failure", "Number of transmitted BSSMAP DT1 ASSIGMENT FAILURE messages"},
- [MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_COMPLETE] = {"bssmap:tx:dt1:assigment:complete", "Number of transmitted BSSMAP DT1 ASSIGMENT 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_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] = {
@@ -147,8 +362,8 @@ static const struct rate_ctr_group_desc msc_ctrg_desc = {
};
static const struct osmo_stat_item_desc msc_stat_desc[] = {
- { "msc_links:active", "Number of active MSC links", "", 16, 0 },
- { "msc_links:total", "Number of configured MSC links", "", 16, 0 },
+ [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 = {
@@ -161,24 +376,22 @@ static const struct osmo_stat_item_group_desc msc_statg_desc = {
int osmo_bsc_msc_init(struct bsc_msc_data *msc)
{
- struct gsm_network *net = msc->network;
- uint16_t mgw_port;
int rc;
- if (net->mgw.conf->remote_port >= 0)
- mgw_port = net->mgw.conf->remote_port;
- else
- mgw_port = MGCP_CLIENT_REMOTE_PORT_DEFAULT;
+ /* 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,
- net->mgw.conf->remote_addr, mgw_port,
- OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
+ 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;
}
@@ -196,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);
@@ -240,24 +452,16 @@ struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr)
/* 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 */
diff --git a/src/osmo-bsc/osmo_bsc_sigtran.c b/src/osmo-bsc/osmo_bsc_sigtran.c
index 2cb769470..a4d0f2d74 100644
--- a/src/osmo-bsc/osmo_bsc_sigtran.c
+++ b/src/osmo-bsc/osmo_bsc_sigtran.c
@@ -36,6 +36,7 @@
#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
@@ -45,33 +46,6 @@ static struct llist_head *msc_list;
#define DEFAULT_ASP_LOCAL_IP "localhost"
#define DEFAULT_ASP_REMOTE_IP "localhost"
-/* 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)
-{
- 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;
-}
-
-struct gsm_subscriber_connection *bsc_conn_by_bsub(struct bsc_subscr *bsub)
-{
- struct gsm_subscriber_connection *conn;
- if (!bsub)
- return NULL;
-
- llist_for_each_entry(conn, &bsc_gsmnet->subscr_conns, entry) {
- if (conn->bsub == bsub)
- return conn;
- }
- return NULL;
-}
-
/* Patch regular BSSMAP RESET to add extra T to announce Osmux support (osmocom extension) */
static void _gsm0808_extend_announce_osmux(struct msgb *msg)
{
@@ -94,7 +68,7 @@ void osmo_bsc_sigtran_tx_reset(const struct bsc_msc_data *msc)
if (msc_is_aoip(msc) && msc->use_osmux != OSMUX_USAGE_OFF)
_gsm0808_extend_announce_osmux(msg);
- rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_UDT_RESET]);
+ 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);
}
@@ -114,12 +88,12 @@ void osmo_bsc_sigtran_tx_reset_ack(const struct bsc_msc_data *msc)
if (msc_is_aoip(msc) && msc->use_osmux != OSMUX_USAGE_OFF)
_gsm0808_extend_announce_osmux(msg);
- rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_UDT_RESET_ACK]);
+ 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;
@@ -134,6 +108,21 @@ static struct bsc_msc_data *get_msc_by_addr(const struct osmo_sccp_addr *msc_add
return NULL;
}
+/* 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)
{
@@ -164,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",
@@ -195,7 +186,14 @@ static int handle_n_connect_from_msc(struct osmo_sccp_user *scu, struct osmo_scu
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);
@@ -206,11 +204,101 @@ refuse:
return rc;
}
+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;
@@ -231,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;
@@ -250,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);
@@ -262,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)
@@ -271,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);
@@ -312,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;
@@ -331,15 +425,20 @@ int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct ms
return -EINVAL;
}
- conn->sccp.conn_id = conn_id = bsc_sccp_inst_next_conn_id(conn->sccp.msc->a.sccp);
- 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_INFO, "Opening new SCCP connection (id=%i) to MSC %d: %s\n", conn_id,
+ 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,
@@ -374,32 +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(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_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(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_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(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_UNKNOWN]);
+ 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 {
- rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_SHORT]);
+ 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(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_ERR_CONN_NOT_READY]);
+ 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);
@@ -408,9 +507,9 @@ int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *m
rc = osmo_sccp_tx_data_msg(msc->a.sccp_user, conn_id, msg);
if (rc >= 0)
- rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_SUCCESS]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_SUCCESS));
else
- rate_ctr_inc(&msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_ERR_SEND]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(msc->msc_ctrs, MSC_CTR_BSSMAP_TX_ERR_SEND));
return rc;
}
@@ -513,6 +612,7 @@ int osmo_bsc_sigtran_init(struct llist_head *mscs)
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 */
@@ -556,6 +656,10 @@ int osmo_bsc_sigtran_init(struct llist_head *mscs)
if (!sccp)
return -EINVAL;
+ 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];
@@ -625,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;
}
diff --git a/src/osmo-bsc/paging.c b/src/osmo-bsc/paging.c
index 15aca00ea..b7842dd52 100644
--- a/src/osmo-bsc/paging.c
+++ b/src/osmo-bsc/paging.c
@@ -54,21 +54,52 @@
#include <osmocom/bsc/gsm_04_08_rr.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
+
+/* 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, BSUB_USE_PAGING_REQUEST);
- 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)
@@ -79,9 +110,9 @@ static void page_ms(struct gsm_paging_request *request)
log_set_context(LOG_CTX_BSC_SUBSCR, request->bsub);
- LOG_BTS(bts, DPAG, LOGL_INFO, "Going to send paging commands: %s"
- " for ch. type %d (attempt %d)\n", bsc_subscr_name(request->bsub),
- request->chan_type, request->attempts);
+ 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){
@@ -101,24 +132,35 @@ static void page_ms(struct gsm_paging_request *request)
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;
-
- LOG_BTS(paging_bts->bts, DPAG, LOGL_NOTICE, "No PCH LOAD IND, adding 20 slots)\n");
- 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);
-
- /* 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;
- }
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+ paging_bts->last_sched_ts = now;
- /* Skip paging if the bts is down. */
- if (!request->bts->oml_link)
- 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);
- /* 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,45 +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)
+#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);
+
+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, to;
+ 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)
- return d->val;
-
- /* TODO: take into account load of paging group for req->bsub */
+ if (!bts->T3113_dynamic) {
+ to = d->val;
+ goto ret;
+ }
/* MFRMS defines repeat interval of paging messages for MSs that belong
* to same paging group across multiple 51 frame multiframes.
- * MAXTRANS defines maximum number of RACH retransmissions.
+ * 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;
- LOG_BTS(bts, DPAG, LOGL_DEBUG, "Paging request: T3113 expires in %u seconds\n", 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;
}
@@ -330,29 +505,83 @@ static int _paging_request(const struct bsc_paging_params *params, struct gsm_bt
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, params->bsub)) {
+ /* 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(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ALREADY]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_CTR_PAGING_ALREADY));
return -EEXIST;
}
+ /* 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->reason = params->reason;
req->bsub = params->bsub;
- bsc_subscr_get(req->bsub, BSUB_USE_PAGING_REQUEST);
req->bts = bts;
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;
}
@@ -371,9 +600,6 @@ int paging_request_bts(const struct bsc_paging_params *params, struct gsm_bts *b
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(params, bts);
if (rc < 0)
@@ -381,128 +607,100 @@ int paging_request_bts(const struct bsc_paging_params *params, struct gsm_bts *b
return 1;
}
-/*! Stop paging a given subscriber on a given BTS.
- * \param[out] returns the MSC that paged the subscriber, if any.
- * \param[out] returns the reason for a pending paging, if any.
- * \param[in] bts BTS which has received a paging response.
- * \param[in] bsub subscriber.
- * \returns number of pending pagings.
- */
-static int paging_request_stop_bts(struct bsc_msc_data **msc_p, enum bsc_paging_reason *reason_p,
- struct gsm_bts *bts, struct bsc_subscr *bsub)
-{
- struct gsm_bts_paging_state *bts_entry = &bts->paging;
- struct gsm_paging_request *req, *req2;
-
- *msc_p = NULL;
- *reason_p = BSC_PAGING_NONE;
-
- paging_init_if_needed(bts);
-
- llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests,
- entry) {
- if (req->bsub != bsub)
- continue;
- *msc_p = req->msc;
- *reason_p = req->reason;
- LOG_BTS(bts, DPAG, LOGL_DEBUG, "Stop paging %s\n", bsc_subscr_name(bsub));
- paging_remove_request(&bts->paging, req);
- return 1;
- }
-
- return 0;
-}
-
/*! 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
- * \returns number of pending pagings.
*/
-int paging_request_stop(struct bsc_msc_data **msc_p, enum bsc_paging_reason *reasons_p,
+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 *bts_i;
- struct bsc_msc_data *paged_from_msc;
- int count;
- enum bsc_paging_reason reasons;
+ struct bsc_msc_data *paged_from_msc = NULL;
+ enum bsc_paging_reason reasons = BSC_PAGING_NONE;
OSMO_ASSERT(bts);
-
- count = paging_request_stop_bts(&paged_from_msc, &reasons, bts, bsub);
- if (paged_from_msc) {
- count++;
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_RESPONDED]);
- rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_RESPONDED]);
+ 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(bts_i, &bsc_gsmnet->bts_list, list) {
- struct bsc_msc_data *paged_from_msc2;
- enum bsc_paging_reason reason2;
- count += paging_request_stop_bts(&paged_from_msc2, &reason2, bts_i, bsub);
- if (paged_from_msc2) {
- reasons |= reason2;
- 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 Reponse, but there *is* a Paging Request
- * pending on a different BTS. But why not return an MSC when we found one. */
- paged_from_msc = paged_from_msc2;
- }
+ 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--;
}
*msc_p = paged_from_msc;
*reasons_p = reasons;
-
- return count;
}
/* Remove all paging requests, for specific reasons only. */
-int paging_request_cancel(struct bsc_subscr *bsub, enum bsc_paging_reason reasons)
+void paging_request_cancel(struct bsc_subscr *bsub, enum bsc_paging_reason reasons)
{
- struct gsm_bts *bts;
- int count = 0;
-
- llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
- struct gsm_paging_request *req, *req2;
+ struct gsm_paging_request *req, *req2;
+ OSMO_ASSERT(bsub);
- paging_init_if_needed(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;
- llist_for_each_entry_safe(req, req2, &bts->paging.pending_requests, entry) {
- if (req->bsub != bsub)
- continue;
- if (!(req->reason & reasons))
- continue;
- LOG_BTS(bts, DPAG, LOGL_DEBUG, "Cancel paging %s\n", bsc_subscr_name(bsub));
- paging_remove_request(&bts->paging, req);
- count++;
+ 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;
}
+ /* No reason to keep the paging, remove it: */
+ paging_remove_request(req);
+ remaining--;
+ if (remaining == 0)
+ break;
}
- return count;
}
/*! 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;
+ 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). */
@@ -510,19 +708,22 @@ 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 */
- LOG_BTS(bts, DPAG, LOGL_DEBUG, "Stop paging %s (flush)\n", bsc_subscr_name(req->bsub));
- paging_remove_request(&bts->paging, req);
- num_cancelled++;
+ 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(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_MSC_FLUSH], 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 */
@@ -533,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 d59df33bb..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>
@@ -37,6 +33,7 @@
#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>
@@ -46,30 +43,28 @@
#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;
}
@@ -96,6 +91,33 @@ 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)
@@ -108,6 +130,48 @@ static void info_ind_fill_fhp(struct gsm_pcu_if_info_trx_ts *ts_info,
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)
{
@@ -115,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, tn;
+ 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;
@@ -135,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;
@@ -147,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;
@@ -193,16 +255,16 @@ 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;
@@ -210,14 +272,14 @@ static int pcu_tx_info_ind(struct gsm_bts *bts)
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] = nsvc->remote.u.sin.sin_port;
+ 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] = nsvc->remote.u.sin6.sin6_port;
+ info_ind->remote_port[i] = ntohs(nsvc->remote.u.sin6.sin6_port);
break;
default:
info_ind->address_type[i] = PCU_IF_ADDR_TYPE_UNSPEC;
@@ -229,39 +291,122 @@ static int pcu_tx_info_ind(struct gsm_bts *bts)
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 (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) {
- ts = &trx->ts[tn];
- if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED ||
- ts->pchan_is != GSM_PCHAN_PDCH)
- continue;
+ 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);
+ }
- info_ind->trx[i].pdch_mask |= (1 << tn);
- info_ind->trx[i].ts[tn].tsc =
- (ts->tsc >= 0) ? ts->tsc : bts->bsic & 7;
+ 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;
+ }
- if (ts->hopping.enabled)
- info_ind_fill_fhp(&info_ind->trx[i].ts[tn], ts);
+ 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;
- LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: PDCH is available "
- "(tsc=%u ", trx->nr, ts->nr, info_ind->trx[i].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->ts[tn].hopping.ma.data_len - trx->ts[tn].hopping.ma.cur_bit);
- else
- LOGPC(DPCU, LOGL_INFO, "hopping=no arfcn=%u)\n", trx->arfcn);
+ 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 */
@@ -273,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);
@@ -295,37 +439,37 @@ 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 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;
struct osmo_mobile_identity mi;
int rc;
@@ -335,14 +479,14 @@ static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group,
chan_needed = (p1->cneed2 << 2) | p1->cneed1;
rc = osmo_mobile_identity_decode(&mi, p1->data+1, p1->data[0], false);
if (rc) {
- LOGP(DPCU, LOGL_ERROR, "PCU Sends paging "
- "request type %02x (chan_needed=%02x): Unable to decode Mobile Identity\n",
+ 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;
}
- LOGP(DPCU, LOGL_ERROR, "PCU Sends paging "
- "request type %02x (chan_needed=%02x, mi=%s)\n",
+ 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
@@ -354,12 +498,12 @@ static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group,
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;
@@ -368,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;
}
@@ -446,20 +589,121 @@ 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 unknown PCU msg type %d\n",
msg_type);
@@ -469,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)
@@ -486,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, "
@@ -494,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 |= OSMO_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 |= OSMO_FD_READ;
+ int j;
#if 0
/* remove si13, ... */
@@ -527,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)
@@ -554,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;
@@ -565,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. */
@@ -584,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 &= ~OSMO_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 |= OSMO_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;
@@ -632,51 +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 & OSMO_FD_READ)
- rc = pcu_sock_read(bfd);
- if (rc < 0)
- return rc;
-
- if (flags & OSMO_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 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 &= ~OSMO_FD_READ;
- close(rc);
+ osmo_fd_read_disable(&state->listen_bfd);
+ close(fd);
return 0;
}
- osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, pcu_sock_cb, state, 0);
+ 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;
@@ -684,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;
@@ -698,13 +950,15 @@ 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;
- rc = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND);
+ 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));
@@ -723,27 +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 7307cb888..000000000
--- a/src/osmo-bsc/rest_octets.c
+++ /dev/null
@@ -1,899 +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>
-#include <osmocom/bsc/bts.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 = ((int)budget) - 40;
- 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);
-
- /* budget: 10 bits used above */
-
- /* Serving Cell Priority Parameters Descr. is Present,
- * see also: 3GPP TS 44.018, Table 10.5.2.33b.1 */
- bitvec_set_bit(bv, 1);
-
- /* GERAN_PRIORITY */
- bitvec_set_uint(bv, 0, 3);
-
- /* THRESH_Priority_Search */
- bitvec_set_uint(bv, 0, 4);
-
- /* THRESH_GSM_low */
- bitvec_set_uint(bv, 0, 4);
-
- /* H_PRIO */
- bitvec_set_uint(bv, 0, 2);
-
- /* T_Reselection */
- bitvec_set_uint(bv, 0, 2);
-
- /* budget: 26 bits used above */
-
- /* 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);
-
- /* budget: 34 bits used above */
-
- appended = append_eutran_neib_cell(bv, bts, rem);
- if (!appended) { /* appending is impossible within current budget: rollback */
- bv->cur_bit = old;
- return;
- }
-
- /* budget: further 6 bits used below, totalling 40 bits */
-
- /* 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 (inverted logic) */
- bitvec_set_bit(bv, !gco->ext_info.use_egprs_p_ch_req);
-
- /* 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
index b34780b6d..8e1c6348e 100644
--- a/src/osmo-bsc/smscb.c
+++ b/src/osmo-bsc/smscb.c
@@ -22,10 +22,12 @@
#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>
@@ -37,11 +39,11 @@
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/smscb.h>
-#include <osmocom/bsc/vty.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
@@ -58,8 +60,6 @@ static void llist_replace_head(struct llist_head *new, struct llist_head *old)
INIT_LLIST_HEAD(old);
}
-#define ETWS_PRIM_NOTIF_SIZE 56
-
/* 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)
@@ -71,13 +71,24 @@ static int gen_etws_primary_notification(uint8_t *out, uint16_t serial_nr, uint1
osmo_store16be(serial_nr, out);
etws->msg_id = osmo_htons(msg_id);
etws->warning_type = osmo_htons(warn_type);
-
- if (sec_info)
- memcpy(etws->data, sec_info, ETWS_PRIM_NOTIF_SIZE - sizeof(*etws));
+ 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)
{
@@ -110,6 +121,8 @@ static void __bts_smscb_add(struct bts_smscb_chan_state *cstate, struct bts_smsc
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 */
@@ -242,6 +255,12 @@ static void append_bcast_compl(struct response_state *r_state, struct gsm_bts *b
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
@@ -366,7 +385,7 @@ static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts,
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 > %lu)\n",
+ 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;
@@ -382,6 +401,9 @@ static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts,
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);
@@ -391,7 +413,9 @@ static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts,
OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(cont->data));
OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(page->data) - sizeof(*msg_param));
- memcpy(&msg_param->content, cont->data, cont->user_len);
+ /* 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;
@@ -452,6 +476,44 @@ int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg)
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)
{
@@ -463,25 +525,34 @@ static int tx_cbsp_keepalive_compl(struct bsc_cbc_link *cbc)
* 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;
- LOG_BTS(bts, DCBS, LOGL_NOTICE, "ETWS PN Timeout; disabling broadcast via PCH\n");
- rsl_etws_pn_command(bts, RSL_CHAN_PCH_AGCH, NULL, 0);
+ etws_pn_stop(bts, true);
}
-static void etws_primary_to_bts(struct gsm_bts *bts, const struct osmo_cbsp_write_replace *wrepl)
+
+/* 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)
{
- uint8_t etws_primary[ETWS_PRIM_NOTIF_SIZE];
+ struct bts_etws_state *bes = &bts->etws;
struct gsm_bts_trx *trx;
unsigned int count = 0;
int i, j;
- gen_etws_primary_notification(etws_primary, wrepl->new_serial_nr, wrepl->msg_id,
- wrepl->u.emergency.warning_type,
- wrepl->u.emergency.warning_sec_info);
-
/* 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++) {
@@ -490,8 +561,8 @@ static void etws_primary_to_bts(struct gsm_bts *bts, const struct osmo_cbsp_writ
struct gsm_lchan *lchan = &ts->lchan[j];
if (!lchan_may_receive_data(lchan))
continue;
- gsm48_send_rr_app_info(lchan, 0x1, 0x0, etws_primary,
- sizeof(etws_primary));
+ gsm48_send_rr_app_info(lchan, 0x1, 0x0, bes->primary,
+ sizeof(bes->primary));
count++;
}
}
@@ -502,18 +573,64 @@ static void etws_primary_to_bts(struct gsm_bts *bts, const struct osmo_cbsp_writ
/* 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, etws_primary, sizeof(etws_primary));
+ 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");
- if (wrepl->u.emergency.warning_period != 0xffffffff) {
- osmo_timer_setup(&bts->etws_timer, etws_pn_cb, bts);
- 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");
} 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
@@ -583,8 +700,7 @@ static int bts_rx_write_replace(struct gsm_bts *bts, const struct osmo_cbsp_deco
int rc;
if (!wrepl->is_cbs) {
- etws_primary_to_bts(bts, wrepl);
- return 0;
+ return etws_primary_to_bts(bts, wrepl);
}
/* check if cell has a CBCH at all */
@@ -630,21 +746,53 @@ 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;
- struct bts_smscb_chan_state *chan_state;
- struct bts_smscb_message *smscb;
- bool extended = false;
- if (kill->channel_ind && *kill->channel_ind == 0x01)
- extended = true;
- chan_state = bts_get_smscb_chan(bts, extended);
+ if (kill->channel_ind) {
+ /* KILL for CBS message */
+ struct bts_smscb_chan_state *chan_state;
+ struct bts_smscb_message *smscb;
+ bool extended = false;
- /* 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;
+ 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;
+ }
- /* Remove it */
- bts_smscb_del(smscb, chan_state, "KILL");
+ /* stop broadcasting the PN in this BTS */
+ etws_pn_stop(bts, false);
+ }
return 0;
}
@@ -666,13 +814,36 @@ static int bts_rx_reset(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec
llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list)
bts_smscb_del(smscb, chan_state, "RESET");
- osmo_timer_del(&bts->etws_timer);
+ 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
*********************************************************************************/
@@ -758,11 +929,16 @@ static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded
fail->channel_ind = kill->channel_ind;
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);
-
- 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);
+ /* 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;
@@ -770,11 +946,16 @@ static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded
compl->old_serial_nr = kill->old_serial_nr;
compl->channel_ind = kill->channel_ind;
- compl->cell_list.id_discr = r_state->success.id_discr;
- llist_replace_head(&compl->cell_list.list, &r_state->success.list);
-
- 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);
+ /* 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);
@@ -816,6 +997,48 @@ static int cbsp_rx_reset(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decode
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
@@ -838,8 +1061,10 @@ int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *de
case CBSP_MSGT_RESET: /* stop broadcasting of all messages */
rc = cbsp_rx_reset(cbc, dec);
break;
- case CBSP_MSGT_LOAD_QUERY:
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));
@@ -853,54 +1078,76 @@ int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *de
return rc;
}
-/*********************************************************************************
- * VTY Interface (Introspection)
- *********************************************************************************/
-
-static void vty_dump_smscb_chan_state(struct vty *vty, const struct bts_smscb_chan_state *cs)
+/* initialize the ETWS state of a BTS */
+void bts_etws_init(struct gsm_bts *bts)
{
- const struct bts_smscb_message *sm;
-
- vty_out(vty, "%s CBCH:%s", cs == &cs->bts->cbch_basic ? "BASIC" : "EXTENDED", VTY_NEWLINE);
+ bts->etws.active = false;
+ osmo_timer_setup(&bts->etws.timer, etws_pn_cb, bts);
+}
- 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);
+/* 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);
}
-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")
+/* 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 gsm_network *net = gsmnet_from_vty(vty);
- int bts_nr = atoi(argv[0]);
+ 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;
- if (bts_nr >= net->num_bts) {
- vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE);
- return CMD_WARNING;
+ 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;
}
- 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);
+ 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;
- return CMD_SUCCESS;
+ 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;
}
-void smscb_vty_init(void)
+/* To be called once at startup of the process: */
+void smscb_global_init(void)
{
- install_element_ve(&bts_show_cbs_cmd);
+ 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 b9699899f..cc2e788d7 100644
--- a/src/osmo-bsc/system_information.c
+++ b/src/osmo-bsc/system_information.c
@@ -31,13 +31,13 @@
#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.h>
#include <osmocom/bsc/neighbor_ident.h>
@@ -52,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;
@@ -169,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;
@@ -211,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;
@@ -293,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;
@@ -426,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)
{
/*
@@ -435,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;
};
@@ -463,8 +502,8 @@ static inline int enc_freq_lst_range(uint8_t *chan_list,
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;
@@ -483,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));
@@ -499,20 +538,29 @@ static int bitvec2freq_list(uint8_t *chan_list, const struct bitvec *bv,
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;
}
@@ -548,7 +596,7 @@ static int bitvec2freq_list(uint8_t *chan_list, const struct bitvec *bv,
max = i;
}
- if (max == -1) {
+ if (arfcns == 0) {
/* Empty set, use 'bit map 0 format' */
chan_list[0] = 0;
return 0;
@@ -569,51 +617,63 @@ static int bitvec2freq_list(uint8_t *chan_list, const struct bitvec *bv,
return -EINVAL;
}
-/* 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)
+/* (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;
+}
- bitvec_set_bit_pos(data->bv, key->arfcn, 1);
- return true;
+/* 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;
+
+ /* 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
@@ -641,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)
@@ -650,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 */
@@ -690,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)
@@ -729,7 +791,19 @@ static int generate_si1(enum osmo_sysinfo_type t, struct gsm_bts *bts)
* 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;
}
@@ -759,6 +833,20 @@ static int generate_si2(enum osmo_sysinfo_type t, struct gsm_bts *bts)
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;
@@ -796,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;
@@ -865,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,
},
@@ -903,6 +1009,10 @@ 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;
@@ -934,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;
}
@@ -966,8 +1076,11 @@ static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts)
const struct gsm_bts_trx_ts *ts = cbch_lchan->ts;
struct gsm48_chan_desc cd;
- /* 10.5.2.5 (TV) CBCH Channel Description IE */
- gsm48_lchan2chan_desc_as_configured(&cd, cbch_lchan);
+ /* 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);
@@ -989,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(tail, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - tail);
+ 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;
}
@@ -1002,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;
@@ -1038,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;
@@ -1082,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;
@@ -1114,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;
@@ -1145,48 +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,
- .ext_info = {
- .egprs_supported = 0, /* overridden in gsm_generate_si() */
- .use_egprs_p_ch_req = 0, /* overridden in generate_si13() */
- .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);
@@ -1195,27 +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_default.cell_opts.ctrl_ack_type_use_block =
- bts->gprs.ctrl_ack_type_use_block;
-
- /* Information about the other SIs */
- si13_default.bcch_change_mark = bts->bcch_change_mark;
+ 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,
+ };
- /* Whether EGPRS capable MSs shall use EGPRS PACKET CHANNEL REQUEST */
- if (bts->gprs.egprs_pkt_chan_request)
- si13_default.cell_opts.ext_info.use_egprs_p_ch_req = 1;
- else
- si13_default.cell_opts.ext_info.use_egprs_p_ch_req = 0;
+ 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;
+ }
if (osmo_bts_has_feature(&bts->features, BTS_FEAT_PAGING_COORDINATION))
- si13_default.cell_opts.ext_info.bss_paging_coordination = 1;
+ si13_info.cell_opts.ext_info.bss_paging_coordination = 1;
else
- si13_default.cell_opts.ext_info.bss_paging_coordination = 0;
+ si13_info.cell_opts.ext_info.bss_paging_coordination = 0;
+
+ 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;
@@ -1249,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;
@@ -1262,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 106e6a1fa..9618f9280 100644
--- a/src/osmo-bsc/timeslot_fsm.c
+++ b/src/osmo-bsc/timeslot_fsm.c
@@ -29,6 +29,7 @@
#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;
@@ -48,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);
}
@@ -66,6 +67,14 @@ void ts_fsm_alloc(struct gsm_bts_trx_ts *ts)
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 {
LCHAN_IS_INSANE = -1,
LCHAN_IS_READY_TO_GO,
@@ -85,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 */
@@ -112,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++;
@@ -125,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;
@@ -137,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);
}
}
@@ -146,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;
@@ -187,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;
@@ -205,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)
@@ -265,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)) {
@@ -280,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:
@@ -339,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);
}
@@ -366,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:
@@ -374,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;
@@ -416,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",
@@ -461,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;
@@ -491,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",
@@ -511,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");
@@ -556,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;
@@ -573,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);
}
@@ -585,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);
}
@@ -604,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;
}
@@ -614,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);
}
@@ -659,6 +744,7 @@ 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:
@@ -685,8 +771,8 @@ static void ts_fsm_borken_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
default:
ctr = BTS_CTR_TS_BORKEN_FROM_UNKNOWN;
}
- rate_ctr_inc(&ts->trx->bts->bts_ctrs->ctr[ctr]);
- osmo_stat_item_inc(ts->trx->bts->bts_statg->items[BTS_STAT_TS_BORKEN], 1);
+ 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)
@@ -710,8 +796,8 @@ static void ts_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data)
struct gsm_bts *bts = ts->trx->bts;
/* Late PDCH activation ACK/NACK is not a crime.
* Just process them as normal. */
- rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_TS_BORKEN_EV_PDCH_ACT_ACK_NACK]);
- osmo_stat_item_dec(bts->bts_statg->items[BTS_STAT_TS_BORKEN], 1);
+ 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;
}
@@ -723,8 +809,8 @@ static void ts_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *data)
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(&bts->bts_ctrs->ctr[BTS_CTR_TS_BORKEN_EV_PDCH_DEACT_ACK_NACK]);
- osmo_stat_item_dec(bts->bts_statg->items[BTS_STAT_TS_BORKEN], 1);
+ 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;
}
@@ -762,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;
@@ -771,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;
@@ -783,9 +870,10 @@ 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(&ts->trx->bts->bts_ctrs->ctr[BTS_CTR_TS_BORKEN_EV_TEARDOWN]);
- osmo_stat_item_dec(ts->trx->bts->bts_statg->items[BTS_STAT_TS_BORKEN], 1);
+ 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);
}
}
@@ -794,6 +882,7 @@ static void ts_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause ca
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)
@@ -813,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)
@@ -845,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)
@@ -865,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)
,
@@ -917,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),
{}
};
@@ -940,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);
@@ -981,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;
@@ -1004,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;
@@ -1022,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;
+}