aboutsummaryrefslogtreecommitdiffstats
path: root/src/libbsc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libbsc')
-rw-r--r--src/libbsc/Makefile.am57
-rw-r--r--src/libbsc/abis_nm.c2917
-rw-r--r--src/libbsc/abis_nm_ipaccess.c87
-rw-r--r--src/libbsc/abis_nm_vty.c191
-rw-r--r--src/libbsc/abis_om2000.c2776
-rw-r--r--src/libbsc/abis_om2000_vty.c609
-rw-r--r--src/libbsc/abis_rsl.c2927
-rw-r--r--src/libbsc/arfcn_range_encode.c330
-rw-r--r--src/libbsc/bsc_api.c894
-rw-r--r--src/libbsc/bsc_ctrl_commands.c459
-rw-r--r--src/libbsc/bsc_ctrl_lookup.c107
-rw-r--r--src/libbsc/bsc_dyn_ts.c77
-rw-r--r--src/libbsc/bsc_init.c552
-rw-r--r--src/libbsc/bsc_msc.c320
-rw-r--r--src/libbsc/bsc_rf_ctrl.c534
-rw-r--r--src/libbsc/bsc_rll.c139
-rw-r--r--src/libbsc/bsc_subscriber.c168
-rw-r--r--src/libbsc/bsc_vty.c4304
-rw-r--r--src/libbsc/bts_ericsson_rbs2000.c204
-rw-r--r--src/libbsc/bts_init.c30
-rw-r--r--src/libbsc/bts_ipaccess_nanobts.c520
-rw-r--r--src/libbsc/bts_ipaccess_nanobts_omlattr.c240
-rw-r--r--src/libbsc/bts_nokia_site.c1739
-rw-r--r--src/libbsc/bts_siemens_bs11.c602
-rw-r--r--src/libbsc/bts_sysmobts.c60
-rw-r--r--src/libbsc/bts_unknown.c40
-rw-r--r--src/libbsc/chan_alloc.c543
-rw-r--r--src/libbsc/e1_config.c297
-rw-r--r--src/libbsc/gsm_04_08_utils.c687
-rw-r--r--src/libbsc/gsm_04_80_utils.c40
-rw-r--r--src/libbsc/handover_decision.c304
-rw-r--r--src/libbsc/handover_logic.c378
-rw-r--r--src/libbsc/meas_proc.c84
-rw-r--r--src/libbsc/meas_rep.c115
-rw-r--r--src/libbsc/net_init.c69
-rw-r--r--src/libbsc/paging.c449
-rw-r--r--src/libbsc/pcu_sock.c742
-rw-r--r--src/libbsc/rest_octets.c860
-rw-r--r--src/libbsc/system_information.c1169
39 files changed, 26620 insertions, 0 deletions
diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am
new file mode 100644
index 000000000..e78bde624
--- /dev/null
+++ b/src/libbsc/Makefile.am
@@ -0,0 +1,57 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+noinst_LIBRARIES = \
+ libbsc.a \
+ $(NULL)
+
+libbsc_a_SOURCES = \
+ abis_nm.c \
+ abis_nm_vty.c \
+ abis_om2000.c \
+ abis_om2000_vty.c \
+ abis_rsl.c \
+ bsc_rll.c \
+ bsc_subscriber.c \
+ paging.c \
+ bts_ericsson_rbs2000.c \
+ bts_ipaccess_nanobts.c \
+ bts_siemens_bs11.c \
+ bts_nokia_site.c \
+ bts_unknown.c \
+ bts_sysmobts.c \
+ chan_alloc.c \
+ handover_decision.c \
+ handover_logic.c \
+ meas_rep.c \
+ pcu_sock.c \
+ rest_octets.c \
+ system_information.c \
+ e1_config.c \
+ bsc_api.c \
+ bsc_msc.c bsc_vty.c \
+ gsm_04_08_utils.c \
+ gsm_04_80_utils.c \
+ bsc_init.c \
+ bts_init.c \
+ bsc_rf_ctrl.c \
+ arfcn_range_encode.c \
+ bsc_ctrl_commands.c \
+ bsc_ctrl_lookup.c \
+ net_init.c \
+ bsc_dyn_ts.c \
+ bts_ipaccess_nanobts_omlattr.c \
+ $(NULL)
+
diff --git a/src/libbsc/abis_nm.c b/src/libbsc/abis_nm.c
new file mode 100644
index 000000000..019d03952
--- /dev/null
+++ b/src/libbsc/abis_nm.c
@@ -0,0 +1,2917 @@
+/* GSM Network Management (OML) messages on the A-bis interface
+ * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */
+
+/* (C) 2008-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 <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <time.h>
+#include <limits.h>
+
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/misdn.h>
+#include <openbsc/signal.h>
+#include <osmocom/abis/e1_input.h>
+
+#define OM_ALLOC_SIZE 1024
+#define OM_HEADROOM_SIZE 128
+#define IPACC_SEGMENT_SIZE 245
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const uint8_t *buf, int len)
+{
+ if (!bts->model)
+ return -EIO;
+ return tlv_parse(tp, &bts->model->nm_att_tlvdef, buf, len, 0, 0);
+}
+
+static int is_in_arr(enum abis_nm_msgtype mt, const enum abis_nm_msgtype *arr, int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ if (arr[i] == mt)
+ return 1;
+ }
+
+ return 0;
+}
+
+#if 0
+/* is this msgtype the usual ACK/NACK type ? */
+static int is_ack_nack(enum abis_nm_msgtype mt)
+{
+ return !is_in_arr(mt, no_ack_nack, ARRAY_SIZE(no_ack_nack));
+}
+#endif
+
+/* is this msgtype a report ? */
+static int is_report(enum abis_nm_msgtype mt)
+{
+ return is_in_arr(mt, abis_nm_reports, ARRAY_SIZE(abis_nm_reports));
+}
+
+#define MT_ACK(x) (x+1)
+#define MT_NACK(x) (x+2)
+
+static void fill_om_hdr(struct abis_om_hdr *oh, uint8_t len)
+{
+ oh->mdisc = ABIS_OM_MDISC_FOM;
+ oh->placement = ABIS_OM_PLACEMENT_ONLY;
+ oh->sequence = 0;
+ oh->length = len;
+}
+
+static struct abis_om_fom_hdr *fill_om_fom_hdr(struct abis_om_hdr *oh, uint8_t len,
+ uint8_t msg_type, uint8_t obj_class,
+ uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr)
+{
+ struct abis_om_fom_hdr *foh =
+ (struct abis_om_fom_hdr *) oh->data;
+
+ fill_om_hdr(oh, len+sizeof(*foh));
+ foh->msg_type = msg_type;
+ foh->obj_class = obj_class;
+ foh->obj_inst.bts_nr = bts_nr;
+ foh->obj_inst.trx_nr = trx_nr;
+ foh->obj_inst.ts_nr = ts_nr;
+ return foh;
+}
+
+static struct msgb *nm_msgb_alloc(void)
+{
+ return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+ "OML");
+}
+
+int _abis_nm_sendmsg(struct msgb *msg)
+{
+ msg->l2h = msg->data;
+
+ if (!msg->dst) {
+ LOGP(DNM, LOGL_ERROR, "%s: msg->dst == NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ return abis_sendmsg(msg);
+}
+
+/* Send a OML NM Message from BSC to BTS */
+static int abis_nm_queue_msg(struct gsm_bts *bts, struct msgb *msg)
+{
+ msg->dst = bts->oml_link;
+
+ /* queue OML messages */
+ if (llist_empty(&bts->abis_queue) && !bts->abis_nm_pend) {
+ bts->abis_nm_pend = OBSC_NM_W_ACK_CB(msg);
+ return _abis_nm_sendmsg(msg);
+ } else {
+ msgb_enqueue(&bts->abis_queue, msg);
+ return 0;
+ }
+
+}
+
+int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+ OBSC_NM_W_ACK_CB(msg) = 1;
+ return abis_nm_queue_msg(bts, msg);
+}
+
+static int abis_nm_sendmsg_direct(struct gsm_bts *bts, struct msgb *msg)
+{
+ OBSC_NM_W_ACK_CB(msg) = 0;
+ return abis_nm_queue_msg(bts, msg);
+}
+
+static int abis_nm_rcvmsg_sw(struct msgb *mb);
+
+int nm_is_running(struct gsm_nm_state *s) {
+ return (s->operational == NM_OPSTATE_ENABLED) && (
+ (s->availability == NM_AVSTATE_OK) ||
+ (s->availability == 0xff)
+ );
+}
+
+/* Update the administrative state of a given object in our in-memory data
+ * structures and send an event to the higher layer */
+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 nm_statechg_signal_data nsd;
+
+ memset(&nsd, 0, sizeof(nsd));
+
+ nsd.obj = gsm_objclass2obj(bts, obj_class, obj_inst);
+ if (!nsd.obj)
+ return -EINVAL;
+ nm_state = gsm_objclass2nmstate(bts, obj_class, obj_inst);
+ 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.obj_inst = obj_inst;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd);
+
+ nm_state->administrative = adm_state;
+
+ return 0;
+}
+
+static int abis_nm_rx_statechg_rep(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 = sign_link->trx->bts;
+ struct tlv_parsed tp;
+ struct gsm_nm_state *nm_state, new_state;
+
+ DEBUGPC(DNM, "STATE CHG: ");
+
+ memset(&new_state, 0, sizeof(new_state));
+
+ nm_state = gsm_objclass2nmstate(bts, foh->obj_class, &foh->obj_inst);
+ if (!nm_state) {
+ DEBUGPC(DNM, "unknown object class\n");
+ return -EINVAL;
+ }
+
+ new_state = *nm_state;
+
+ 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);
+ DEBUGPC(DNM, "OP_STATE=%s ",
+ abis_nm_opstate_name(new_state.operational));
+ }
+ if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) {
+ if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0)
+ new_state.availability = 0xff;
+ else
+ 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);
+ } else
+ new_state.availability = 0xff;
+ if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) {
+ 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));
+ }
+ DEBUGPC(DNM, "\n");
+
+ if ((new_state.administrative != 0 && nm_state->administrative == 0) ||
+ new_state.operational != nm_state->operational ||
+ new_state.availability != nm_state->availability) {
+ /* Update the operational state of a given object in our in-memory data
+ * structures and send an event to the higher layer */
+ struct nm_statechg_signal_data nsd;
+ nsd.obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst);
+ nsd.obj_class = foh->obj_class;
+ nsd.old_state = nm_state;
+ nsd.new_state = &new_state;
+ nsd.obj_inst = &foh->obj_inst;
+ nsd.bts = bts;
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG_OPER, &nsd);
+ nm_state->operational = new_state.operational;
+ nm_state->availability = new_state.availability;
+ if (nm_state->administrative == 0)
+ nm_state->administrative = new_state.administrative;
+ }
+#if 0
+ if (op_state == 1) {
+ /* try to enable objects that are disabled */
+ abis_nm_opstart(bts, foh->obj_class,
+ foh->obj_inst.bts_nr,
+ foh->obj_inst.trx_nr,
+ foh->obj_inst.ts_nr);
+ }
+#endif
+ return 0;
+}
+
+static inline void log_oml_fail_rep(const struct gsm_bts *bts, const char *type,
+ const char *severity, const uint8_t *p_val,
+ const char *text)
+{
+ enum abis_nm_pcause_type pcause = p_val[0];
+ enum abis_mm_event_causes cause = osmo_load16be(p_val + 1);
+
+ LOGPC(DNM, LOGL_ERROR, "BTS %u: Failure Event Report: ", bts->nr);
+ if (type)
+ LOGPC(DNM, LOGL_ERROR, "Type=%s, ", type);
+ if (severity)
+ LOGPC(DNM, LOGL_ERROR, "Severity=%s, ", severity);
+
+ LOGPC(DNM, LOGL_ERROR, "Probable cause=%s: ",
+ get_value_string(abis_nm_pcause_type_names, pcause));
+
+ if (pcause == NM_PCAUSE_T_MANUF)
+ LOGPC(DNM, LOGL_ERROR, "%s, ",
+ get_value_string(abis_mm_event_cause_names, cause));
+ else
+ LOGPC(DNM, LOGL_ERROR, "%02X %02X ", p_val[1], p_val[2]);
+
+ if (text) {
+ LOGPC(DNM, LOGL_ERROR, "Additional Text=%s. ", text);
+ }
+
+ LOGPC(DNM, LOGL_ERROR, "\n");
+}
+
+static inline void handle_manufact_report(struct gsm_bts *bts, const uint8_t *p_val, const char *type,
+ const char *severity, const char *text)
+{
+ enum abis_mm_event_causes cause = osmo_load16be(p_val + 1);
+
+ switch (cause) {
+ case OSMO_EVT_PCU_VERS:
+ if (text) {
+ LOGPC(DNM, LOGL_NOTICE, "BTS %u reported connected PCU version %s\n", bts->nr, text);
+ osmo_strlcpy(bts->pcu_version, text, sizeof(bts->pcu_version));
+ } else {
+ LOGPC(DNM, LOGL_ERROR, "BTS %u reported PCU disconnection.\n", bts->nr);
+ bts->pcu_version[0] = '\0';
+ }
+ break;
+ default:
+ log_oml_fail_rep(bts, type, severity, p_val, text);
+ };
+}
+
+static int rx_fail_evt_rep(struct msgb *mb, struct gsm_bts *bts)
+{
+ struct abis_om_hdr *oh = msgb_l2(mb);
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ struct e1inp_sign_link *sign_link = mb->dst;
+ struct tlv_parsed tp;
+ int rc = 0;
+ const uint8_t *p_val = NULL;
+ char *p_text = NULL;
+ const char *e_type = NULL, *severity = NULL;
+
+ abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data,
+ oh->length-sizeof(*foh));
+
+ if (TLVP_PRESENT(&tp, NM_ATT_ADD_TEXT)) {
+ p_val = TLVP_VAL(&tp, NM_ATT_ADD_TEXT);
+ p_text = talloc_strndup(tall_bsc_ctx, (const char *) p_val,
+ TLVP_LEN(&tp, NM_ATT_ADD_TEXT));
+ }
+
+ if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE))
+ e_type = abis_nm_event_type_name(*TLVP_VAL(&tp,
+ NM_ATT_EVENT_TYPE));
+
+ if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY))
+ severity = abis_nm_severity_name(*TLVP_VAL(&tp,
+ NM_ATT_SEVERITY));
+
+ if (TLVP_PRESENT(&tp, NM_ATT_PROB_CAUSE)) {
+ p_val = TLVP_VAL(&tp, NM_ATT_PROB_CAUSE);
+
+ switch (p_val[0]) {
+ case NM_PCAUSE_T_MANUF:
+ handle_manufact_report(bts, p_val, e_type, severity,
+ p_text);
+ break;
+ default:
+ log_oml_fail_rep(bts, e_type, severity, p_val, p_text);
+ };
+ } else {
+ LOGPC(DNM, LOGL_ERROR, "BTS%u: Failure Event Report without "
+ "Probable Cause?!\n", bts->nr);
+ rc = -EINVAL;
+ }
+
+ if (p_text)
+ talloc_free(p_text);
+
+ return rc;
+}
+
+static int abis_nm_rcvmsg_report(struct msgb *mb, struct gsm_bts *bts)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ uint8_t mt = foh->msg_type;
+
+ abis_nm_debugp_foh(DNM, foh);
+
+ //nmh->cfg->report_cb(mb, foh);
+
+ switch (mt) {
+ case NM_MT_STATECHG_EVENT_REP:
+ return abis_nm_rx_statechg_rep(mb);
+ break;
+ case NM_MT_SW_ACTIVATED_REP:
+ DEBUGPC(DNM, "Software Activated Report\n");
+ osmo_signal_dispatch(SS_NM, S_NM_SW_ACTIV_REP, mb);
+ break;
+ case NM_MT_FAILURE_EVENT_REP:
+ rx_fail_evt_rep(mb, bts);
+ osmo_signal_dispatch(SS_NM, S_NM_FAIL_REP, mb);
+ break;
+ case NM_MT_TEST_REP:
+ DEBUGPC(DNM, "Test Report\n");
+ osmo_signal_dispatch(SS_NM, S_NM_TEST_REP, mb);
+ break;
+ default:
+ DEBUGPC(DNM, "reporting NM MT 0x%02x\n", mt);
+ break;
+
+ };
+
+ return 0;
+}
+
+/* Activate the specified software into the BTS */
+static int ipacc_sw_activate(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1,
+ uint8_t i2, const struct abis_nm_sw_desc *sw_desc)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint16_t len = abis_nm_sw_desc_len(sw_desc, true);
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, obj_class, i0, i1, i2);
+ abis_nm_put_sw_desc(msg, sw_desc, true);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_select_newest_sw(const struct abis_nm_sw_desc *sw_descr,
+ const size_t size)
+{
+ int res = 0;
+ int i;
+
+ for (i = 1; i < size; ++i) {
+ if (memcmp(sw_descr[res].file_version, sw_descr[i].file_version,
+ OSMO_MIN(sw_descr[i].file_version_len,
+ sw_descr[res].file_version_len)) < 0) {
+ res = i;
+ }
+ }
+
+ return res;
+}
+
+static inline bool handle_attr(const struct gsm_bts *bts, enum bts_attribute id, uint8_t *val, uint8_t len)
+{
+ switch (id) {
+ case BTS_TYPE_VARIANT:
+ LOGP(DNM, LOGL_NOTICE, "BTS%u reported variant: %s\n", bts->nr, val);
+ break;
+ case BTS_SUB_MODEL:
+ LOGP(DNM, LOGL_NOTICE, "BTS%u reported submodel: %s\n", bts->nr, val);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+/* Parse Attribute Response Info - return pointer to the actual content */
+static inline uint8_t *parse_attr_resp_info_unreported(uint8_t bts_nr, uint8_t *ari, uint16_t ari_len, uint16_t *out_len)
+{
+ uint8_t num_unreported = ari[0], i;
+
+ DEBUGP(DNM, "BTS%u Get Attributes Response Info: %u bytes total with %u unreported attributes\n",
+ bts_nr, ari_len, num_unreported);
+
+ /* +1 because we have to account for number of unreported attributes, prefixing the list: */
+ for (i = 0; i < num_unreported; i++)
+ LOGP(DNM, LOGL_ERROR, "BTS%u Attribute %s is unreported\n",
+ bts_nr, get_value_string(abis_nm_att_names, ari[i + 1]));
+
+ /* the data starts right after the list of unreported attributes + space for length of that list */
+ *out_len = ari_len - (num_unreported + 2);
+
+ 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 inline uint8_t *parse_attr_resp_info_manuf_id(struct gsm_bts *bts, uint8_t *data, uint16_t *data_len)
+{
+ struct tlv_parsed tp;
+ uint16_t m_id_len = 0;
+ uint8_t adjust = 0, i;
+
+ abis_nm_tlv_parse(&tp, bts, data, *data_len);
+ if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_ID, 2)) {
+ m_id_len = TLVP_LEN(&tp, NM_ATT_MANUF_ID);
+
+ /* log potential BTS feature vector overflow */
+ if (m_id_len > sizeof(bts->_features_data))
+ LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: feature vector is truncated to %u bytes\n",
+ bts->nr, MAX_BTS_FEATURES/8);
+
+ /* check that max. expected BTS attribute is above given feature vector length */
+ if (m_id_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, m_id_len);
+
+ memcpy(bts->_features_data, TLVP_VAL(&tp, NM_ATT_MANUF_ID), sizeof(bts->_features_data));
+ adjust = m_id_len + 3; /* adjust for parsed TL16V struct */
+
+ for (i = 0; i < _NUM_BTS_FEAT; i++)
+ if (gsm_bts_has_feature(bts, i) != gsm_btsmodel_has_feature(bts->model, 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(gsm_bts_features_descs, i),
+ gsm_bts_has_feature(bts, i), gsm_btsmodel_has_feature(bts->model, i));
+ }
+
+ *data_len -= adjust;
+
+ return data + adjust;
+}
+
+/* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.28 Manufacturer Dependent State */
+static inline uint8_t *parse_attr_resp_info_manuf_state(const struct gsm_bts_trx *trx, uint8_t *data, uint16_t *data_len)
+{
+ struct tlv_parsed tp;
+ const uint8_t *power;
+ uint8_t adjust = 0;
+
+ if (!trx) /* this attribute does not make sense on BTS level, only on TRX level */
+ return data;
+
+ abis_nm_tlv_parse(&tp, trx->bts, data, *data_len);
+ if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_STATE, 1)) {
+ power = TLVP_VAL(&tp, NM_ATT_MANUF_STATE);
+ LOGP(DNM, LOGL_NOTICE, "%s Get Attributes Response: nominal power is %u\n", gsm_trx_name(trx), *power);
+ adjust = 2; /* adjust for parsed TV struct */
+ }
+
+ *data_len -= adjust;
+
+ return data + adjust;
+}
+
+/* Handle 3GPP TS 52.021 §9.4.64 Get Attribute Response Info */
+static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *trx)
+{
+ 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 tlv_parsed tp;
+ uint8_t *data, i;
+ uint16_t data_len;
+ int rc;
+ struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR];
+
+ abis_nm_debugp_foh(DNM, foh);
+
+ DEBUGPC(DNM, "Get Attributes Response for BTS%u\n", bts->nr);
+
+ abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+ if (!TLVP_PRES_LEN(&tp, NM_ATT_GET_ARI, 1)) {
+ LOGP(DNM, LOGL_ERROR, "BTS%u: Get Attributes Response without Response Info?!\n", bts->nr);
+ return -EINVAL;
+ }
+
+ data = parse_attr_resp_info_unreported(bts->nr, TLVP_VAL(&tp, NM_ATT_GET_ARI), TLVP_LEN(&tp, NM_ATT_GET_ARI),
+ &data_len);
+
+ data = parse_attr_resp_info_manuf_state(trx, data, &data_len);
+ data = parse_attr_resp_info_manuf_id(bts, data, &data_len);
+
+ /* after parsing manufacturer-specific attributes there's list of replies in form of sw-conf structure: */
+ rc = abis_nm_get_sw_conf(data, data_len, &sw_descr[0], ARRAY_SIZE(sw_descr));
+ if (rc > 0) {
+ for (i = 0; i < rc; i++) {
+ if (!handle_attr(bts, str2btsattr((const char *)sw_descr[i].file_id),
+ sw_descr[i].file_version, sw_descr[i].file_version_len))
+ LOGP(DNM, LOGL_NOTICE, "BTS%u: ARI reported sw[%d/%d]: %s is %s\n",
+ bts->nr, i, rc, sw_descr[i].file_id, sw_descr[i].file_version);
+ }
+ } else
+ LOGP(DNM, LOGL_ERROR, "BTS%u: failed to parse SW-Config part of Get Attribute Response Info: %s\n",
+ bts->nr, strerror(-rc));
+
+ return 0;
+}
+
+/* 3GPP TS 52.021 §6.2.5 */
+static int abis_nm_rx_sw_act_req(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 tlv_parsed tp;
+ const uint8_t *sw_config;
+ int ret, sw_config_len, len;
+ struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR];
+
+ abis_nm_debugp_foh(DNM, foh);
+
+ DEBUGPC(DNM, "SW Activate Request: ");
+
+ DEBUGP(DNM, "Software Activate Request, ACKing and Activating\n");
+
+ 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));
+ if (ret != 0) {
+ LOGP(DNM, LOGL_ERROR,
+ "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));
+ 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)) {
+ LOGP(DNM, LOGL_ERROR,
+ "SW config not found! Can't continue.\n");
+ return -EINVAL;
+ } else {
+ DEBUGP(DNM, "Found SW config: %s\n", osmo_hexdump(sw_config, sw_config_len));
+ }
+
+ /* Parse up to two sw descriptions from the data */
+ len = abis_nm_get_sw_conf(sw_config, sw_config_len, &sw_descr[0],
+ ARRAY_SIZE(sw_descr));
+ if (len <= 0) {
+ LOGP(DNM, LOGL_ERROR, "Failed to parse SW Config.\n");
+ return -EINVAL;
+ }
+
+ ret = abis_nm_select_newest_sw(&sw_descr[0], len);
+ DEBUGP(DNM, "Selected sw description %d of %d\n", ret, len);
+
+ return ipacc_sw_activate(sign_link->trx->bts, foh->obj_class,
+ foh->obj_inst.bts_nr,
+ foh->obj_inst.trx_nr,
+ foh->obj_inst.ts_nr,
+ &sw_descr[ret]);
+}
+
+/* Receive a CHANGE_ADM_STATE_ACK, parse the TLV and update local state */
+static int abis_nm_rx_chg_adm_state_ack(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 tlv_parsed tp;
+ uint8_t adm_state;
+
+ abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
+ if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE))
+ return -EINVAL;
+
+ adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE);
+
+ return update_admstate(sign_link->trx->bts, foh->obj_class, &foh->obj_inst, adm_state);
+}
+
+static int abis_nm_rx_lmt_event(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 tlv_parsed tp;
+
+ DEBUGP(DNM, "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);
+ DEBUGPC(DNM, "LOG%s ", onoff ? "ON" : "OFF");
+ }
+ if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) &&
+ TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) >= 1) {
+ uint8_t level = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV);
+ DEBUGPC(DNM, "Level=%u ", level);
+ }
+ if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_NAME) &&
+ TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_NAME) >= 1) {
+ char *name = (char *) TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_NAME);
+ DEBUGPC(DNM, "Username=%s ", name);
+ }
+ DEBUGPC(DNM, "\n");
+ /* FIXME: parse LMT LOGON TIME */
+ return 0;
+}
+
+void abis_nm_queue_send_next(struct gsm_bts *bts)
+{
+ int wait = 0;
+ struct msgb *msg;
+ /* the queue is empty */
+ while (!llist_empty(&bts->abis_queue)) {
+ msg = msgb_dequeue(&bts->abis_queue);
+ wait = OBSC_NM_W_ACK_CB(msg);
+ _abis_nm_sendmsg(msg);
+
+ if (wait)
+ break;
+ }
+
+ bts->abis_nm_pend = wait;
+}
+
+/* Receive a OML NM Message from BTS */
+static int abis_nm_rcvmsg_fom(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;
+ uint8_t mt = foh->msg_type;
+ /* sign_link might get deleted via osmo_signal_dispatch -> save bts */
+ struct gsm_bts *bts = sign_link->trx->bts;
+ int ret = 0;
+
+ /* check for unsolicited message */
+ if (is_report(mt))
+ return abis_nm_rcvmsg_report(mb, bts);
+
+ if (is_in_arr(mt, abis_nm_sw_load_msgs, ARRAY_SIZE(abis_nm_sw_load_msgs)))
+ return abis_nm_rcvmsg_sw(mb);
+
+ if (is_in_arr(mt, abis_nm_nacks, ARRAY_SIZE(abis_nm_nacks))) {
+ struct nm_nack_signal_data nack_data;
+ struct tlv_parsed tp;
+
+ abis_nm_debugp_foh(DNM, foh);
+
+ DEBUGPC(DNM, "%s NACK ", abis_nm_nack_name(mt));
+
+ abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh));
+ if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+ DEBUGPC(DNM, "CAUSE=%s\n",
+ abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+ else
+ DEBUGPC(DNM, "\n");
+
+ nack_data.msg = mb;
+ nack_data.mt = mt;
+ nack_data.bts = bts;
+ osmo_signal_dispatch(SS_NM, S_NM_NACK, &nack_data);
+ abis_nm_queue_send_next(bts);
+ return 0;
+ }
+#if 0
+ /* check if last message is to be acked */
+ if (is_ack_nack(nmh->last_msgtype)) {
+ if (mt == MT_ACK(nmh->last_msgtype)) {
+ DEBUGP(DNM, "received ACK (0x%x)\n", foh->msg_type);
+ /* we got our ACK, continue sending the next msg */
+ } else if (mt == MT_NACK(nmh->last_msgtype)) {
+ /* we got a NACK, signal this to the caller */
+ DEBUGP(DNM, "received NACK (0x%x)\n", foh->msg_type);
+ /* FIXME: somehow signal this to the caller */
+ } else {
+ /* really strange things happen */
+ return -EINVAL;
+ }
+ }
+#endif
+
+ switch (mt) {
+ case NM_MT_CHG_ADM_STATE_ACK:
+ ret = abis_nm_rx_chg_adm_state_ack(mb);
+ break;
+ case NM_MT_SW_ACT_REQ:
+ ret = abis_nm_rx_sw_act_req(mb);
+ break;
+ case NM_MT_BS11_LMT_SESSION:
+ ret = abis_nm_rx_lmt_event(mb);
+ break;
+ case NM_MT_OPSTART_ACK:
+ abis_nm_debugp_foh(DNM, foh);
+ DEBUGPC(DNM, "Opstart ACK\n");
+ break;
+ case NM_MT_SET_CHAN_ATTR_ACK:
+ abis_nm_debugp_foh(DNM, foh);
+ DEBUGPC(DNM, "Set Channel Attributes ACK\n");
+ break;
+ case NM_MT_SET_RADIO_ATTR_ACK:
+ abis_nm_debugp_foh(DNM, foh);
+ DEBUGPC(DNM, "Set Radio Carrier Attributes ACK\n");
+ break;
+ case NM_MT_CONN_MDROP_LINK_ACK:
+ abis_nm_debugp_foh(DNM, foh);
+ DEBUGPC(DNM, "CONN MDROP LINK ACK\n");
+ break;
+ case NM_MT_IPACC_RESTART_ACK:
+ osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_ACK, NULL);
+ break;
+ case NM_MT_IPACC_RESTART_NACK:
+ osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_NACK, NULL);
+ break;
+ case NM_MT_SET_BTS_ATTR_ACK:
+ 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));
+ break;
+ default:
+ abis_nm_debugp_foh(DNM, foh);
+ LOGPC(DNM, LOGL_ERROR, "Unhandled message %s\n",
+ get_value_string(abis_nm_msgtype_names, mt));
+ }
+
+ abis_nm_queue_send_next(bts);
+ return ret;
+}
+
+static int abis_nm_rx_ipacc(struct msgb *mb);
+
+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;
+
+ 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);
+ break;
+ default:
+ LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this "
+ "BTS type (%u)\n", bts_type);
+ rc = 0;
+ break;
+ }
+
+ return rc;
+}
+
+/* High-Level API */
+/* Entry-point where L2 OML from BTS enters the NM code */
+int abis_nm_rcvmsg(struct msgb *msg)
+{
+ struct abis_om_hdr *oh = msgb_l2(msg);
+ int rc = 0;
+
+ /* Various consistency checks */
+ if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
+ LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
+ oh->placement);
+ if (oh->placement != ABIS_OM_PLACEMENT_FIRST) {
+ rc = -EINVAL;
+ goto err;
+ }
+ }
+ if (oh->sequence != 0) {
+ LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
+ oh->sequence);
+ rc = -EINVAL;
+ goto err;
+ }
+#if 0
+ unsigned int l2_len = msg->tail - (uint8_t *)msgb_l2(msg);
+ unsigned int hlen = sizeof(*oh) + sizeof(struct abis_om_fom_hdr);
+ if (oh->length + hlen > l2_len) {
+ LOGP(DNM, LOGL_ERROR, "ABIS OML truncated message (%u > %u)\n",
+ oh->length + sizeof(*oh), l2_len);
+ return -EINVAL;
+ }
+ if (oh->length + hlen < l2_len)
+ LOGP(DNM, LOGL_ERROR, "ABIS OML message with extra trailer?!? (oh->len=%d, sizeof_oh=%d l2_len=%d\n", oh->length, sizeof(*oh), l2_len);
+#endif
+ msg->l3h = (unsigned char *)oh + sizeof(*oh);
+
+ switch (oh->mdisc) {
+ case ABIS_OM_MDISC_FOM:
+ rc = abis_nm_rcvmsg_fom(msg);
+ break;
+ case ABIS_OM_MDISC_MANUF:
+ rc = abis_nm_rcvmsg_manuf(msg);
+ break;
+ case ABIS_OM_MDISC_MMI:
+ case ABIS_OM_MDISC_TRAU:
+ LOGP(DNM, LOGL_ERROR, "unimplemented ABIS OML message discriminator 0x%x\n",
+ oh->mdisc);
+ break;
+ default:
+ LOGP(DNM, LOGL_ERROR, "unknown ABIS OML message discriminator 0x%x\n",
+ oh->mdisc);
+ rc = -EINVAL;
+ break;
+ }
+err:
+ msgb_free(msg);
+ return rc;
+}
+
+#if 0
+/* initialized all resources */
+struct abis_nm_h *abis_nm_init(struct abis_nm_cfg *cfg)
+{
+ struct abis_nm_h *nmh;
+
+ nmh = malloc(sizeof(*nmh));
+ if (!nmh)
+ return NULL;
+
+ nmh->cfg = cfg;
+
+ return nmh;
+}
+
+/* free all resources */
+void abis_nm_fini(struct abis_nm_h *nmh)
+{
+ free(nmh);
+}
+#endif
+
+/* Here we are trying to define a high-level API that can be used by
+ * the actual BSC implementation. However, the architecture is currently
+ * still under design. Ideally the calls to this API would be synchronous,
+ * while the underlying stack behind the APi runs in a traditional select
+ * based state machine.
+ */
+
+/* 6.2 Software Load: */
+enum sw_state {
+ SW_STATE_NONE,
+ SW_STATE_WAIT_INITACK,
+ SW_STATE_WAIT_SEGACK,
+ SW_STATE_WAIT_ENDACK,
+ SW_STATE_WAIT_ACTACK,
+ SW_STATE_ERROR,
+};
+
+struct abis_nm_sw {
+ struct gsm_bts *bts;
+ int trx_nr;
+ gsm_cbfn *cbfn;
+ void *cb_data;
+ int forced;
+
+ /* this will become part of the SW LOAD INITIATE */
+ uint8_t obj_class;
+ uint8_t obj_instance[3];
+
+ uint8_t file_id[255];
+ uint8_t file_id_len;
+
+ uint8_t file_version[255];
+ uint8_t file_version_len;
+
+ uint8_t window_size;
+ uint8_t seg_in_window;
+
+ int fd;
+ FILE *stream;
+ enum sw_state state;
+ int last_seg;
+};
+
+static struct abis_nm_sw g_sw;
+
+static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg)
+{
+ if (sw->bts->type == GSM_BTS_TYPE_NANOBTS) {
+ msgb_v_put(msg, NM_ATT_SW_DESCR);
+ msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+ msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+ sw->file_version);
+ } else if (sw->bts->type == GSM_BTS_TYPE_BS11) {
+ msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+ 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");
+ }
+}
+
+/* 6.2.1 / 8.3.1: Load Data Initiate */
+static int sw_load_init(struct abis_nm_sw *sw)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t len = 3*2 + sw->file_id_len + sw->file_version_len;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, len, NM_MT_LOAD_INIT, sw->obj_class,
+ sw->obj_instance[0], sw->obj_instance[1],
+ sw->obj_instance[2]);
+
+ sw_add_file_id_and_ver(sw, msg);
+ msgb_tv_put(msg, NM_ATT_WINDOW_SIZE, sw->window_size);
+
+ return abis_nm_sendmsg(sw->bts, msg);
+}
+
+static int is_last_line(FILE *stream)
+{
+ char next_seg_buf[256];
+ long pos;
+
+ /* check if we're sending the last line */
+ pos = ftell(stream);
+
+ /* Did ftell fail? Then we are at the end for sure */
+ if (pos < 0)
+ return 1;
+
+ if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) {
+ int rc = fseek(stream, pos, SEEK_SET);
+ if (rc < 0)
+ return rc;
+ return 1;
+ }
+
+ fseek(stream, pos, SEEK_SET);
+ return 0;
+}
+
+/* 6.2.2 / 8.3.2 Load Data Segment */
+static int sw_load_segment(struct abis_nm_sw *sw)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ char seg_buf[256];
+ char *line_buf = seg_buf+2;
+ unsigned char *tlv;
+ int len;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+
+ switch (sw->bts->type) {
+ case GSM_BTS_TYPE_BS11:
+ if (fgets(line_buf, sizeof(seg_buf)-2, sw->stream) == NULL) {
+ perror("fgets reading segment");
+ return -EINVAL;
+ }
+ seg_buf[0] = 0x00;
+
+ /* check if we're sending the last line */
+ sw->last_seg = is_last_line(sw->stream);
+ if (sw->last_seg)
+ seg_buf[1] = 0;
+ else
+ seg_buf[1] = 1 + sw->seg_in_window++;
+
+ len = strlen(line_buf) + 2;
+ tlv = msgb_put(msg, TLV_GROSS_LEN(len));
+ tlv_put(tlv, NM_ATT_BS11_FILE_DATA, len, (uint8_t *)seg_buf);
+ /* BS11 wants CR + LF in excess of the TLV length !?! */
+ tlv[1] -= 2;
+
+ /* we only now know the exact length for the OM hdr */
+ len = strlen(line_buf)+2;
+ break;
+ case GSM_BTS_TYPE_NANOBTS: {
+ osmo_static_assert(sizeof(seg_buf) >= IPACC_SEGMENT_SIZE, buffer_big_enough);
+ len = read(sw->fd, &seg_buf, IPACC_SEGMENT_SIZE);
+ if (len < 0) {
+ perror("read failed");
+ return -EINVAL;
+ }
+
+ if (len != IPACC_SEGMENT_SIZE)
+ sw->last_seg = 1;
+
+ ++sw->seg_in_window;
+ msgb_tl16v_put(msg, NM_ATT_IPACC_FILE_DATA, len, (const uint8_t *) seg_buf);
+ len += 3;
+ break;
+ }
+ default:
+ LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n");
+ /* FIXME: Other BTS types */
+ return -1;
+ }
+
+ fill_om_fom_hdr(oh, len, NM_MT_LOAD_SEG, sw->obj_class,
+ sw->obj_instance[0], sw->obj_instance[1],
+ sw->obj_instance[2]);
+
+ return abis_nm_sendmsg_direct(sw->bts, msg);
+}
+
+/* 6.2.4 / 8.3.4 Load Data End */
+static int sw_load_end(struct abis_nm_sw *sw)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, len, NM_MT_LOAD_END, sw->obj_class,
+ sw->obj_instance[0], sw->obj_instance[1],
+ sw->obj_instance[2]);
+
+ sw_add_file_id_and_ver(sw, msg);
+ return abis_nm_sendmsg(sw->bts, msg);
+}
+
+/* Activate the specified software into the BTS */
+static int sw_activate(struct abis_nm_sw *sw)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, sw->obj_class,
+ sw->obj_instance[0], sw->obj_instance[1],
+ sw->obj_instance[2]);
+
+ /* FIXME: this is BS11 specific format */
+ msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id);
+ msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len,
+ sw->file_version);
+
+ 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)
+{
+ 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");
+ return -1;
+ }
+
+ if (strncmp(firmware_header.magic, " SDP", 4) != 0) {
+ LOGP(DNM, LOGL_ERROR, "The magic number1 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");
+ return -1;
+ }
+
+
+ if (fstat(sw->fd, &stat) == -1) {
+ LOGP(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");
+ 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.\n"
+ "There might be checksums in the file that are not\n"
+ "verified and incomplete firmware might be flashed.\n"
+ "There is absolutely no WARRANTY that flashing will\n"
+ "work.\n");
+ return 0;
+}
+
+static int sw_open_file(struct abis_nm_sw *sw, const char *fname)
+{
+ char file_id[12+1];
+ char file_version[80+1];
+ int rc;
+
+ sw->fd = open(fname, O_RDONLY);
+ if (sw->fd < 0)
+ return sw->fd;
+
+ switch (sw->bts->type) {
+ case GSM_BTS_TYPE_BS11:
+ sw->stream = fdopen(sw->fd, "r");
+ if (!sw->stream) {
+ perror("fdopen");
+ return -1;
+ }
+ /* read first line and parse file ID and VERSION */
+ rc = fscanf(sw->stream, "@(#)%12s:%80s\r\n",
+ file_id, file_version);
+ if (rc != 2) {
+ perror("parsing header line of software file");
+ return -1;
+ }
+ strcpy((char *)sw->file_id, file_id);
+ sw->file_id_len = strlen(file_id);
+ strcpy((char *)sw->file_version, file_version);
+ sw->file_version_len = strlen(file_version);
+ /* rewind to start of file */
+ rewind(sw->stream);
+ break;
+ case GSM_BTS_TYPE_NANOBTS:
+ /* TODO: extract that from the filename or content */
+ rc = parse_sdp_header(sw);
+ if (rc < 0) {
+ fprintf(stderr, "Could not parse the ipaccess SDP header\n");
+ return -1;
+ }
+
+ strcpy((char *)sw->file_id, "id");
+ sw->file_id_len = 3;
+ strcpy((char *)sw->file_version, "version");
+ sw->file_version_len = 8;
+ break;
+ default:
+ /* We don't know how to treat them yet */
+ close(sw->fd);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void sw_close_file(struct abis_nm_sw *sw)
+{
+ switch (sw->bts->type) {
+ case GSM_BTS_TYPE_BS11:
+ fclose(sw->stream);
+ break;
+ default:
+ close(sw->fd);
+ break;
+ }
+}
+
+/* Fill the window */
+static int sw_fill_window(struct abis_nm_sw *sw)
+{
+ int rc;
+
+ while (sw->seg_in_window < sw->window_size) {
+ rc = sw_load_segment(sw);
+ if (rc < 0)
+ return rc;
+ if (sw->last_seg)
+ break;
+ }
+ return 0;
+}
+
+/* callback function from abis_nm_rcvmsg() handler */
+static int abis_nm_rcvmsg_sw(struct msgb *mb)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(mb);
+ struct e1inp_sign_link *sign_link = mb->dst;
+ int rc = -1;
+ struct abis_nm_sw *sw = &g_sw;
+ enum sw_state old_state = sw->state;
+
+ //DEBUGP(DNM, "state %u, NM MT 0x%02x\n", sw->state, foh->msg_type);
+
+ switch (sw->state) {
+ case SW_STATE_WAIT_INITACK:
+ switch (foh->msg_type) {
+ case NM_MT_LOAD_INIT_ACK:
+ /* fill window with segments */
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_LOAD_INIT_ACK, mb,
+ sw->cb_data, NULL);
+ rc = sw_fill_window(sw);
+ sw->state = SW_STATE_WAIT_SEGACK;
+ abis_nm_queue_send_next(sign_link->trx->bts);
+ break;
+ case NM_MT_LOAD_INIT_NACK:
+ if (sw->forced) {
+ DEBUGP(DNM, "FORCED: Ignoring Software Load "
+ "Init NACK\n");
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_LOAD_INIT_ACK, mb,
+ sw->cb_data, NULL);
+ rc = sw_fill_window(sw);
+ sw->state = SW_STATE_WAIT_SEGACK;
+ } else {
+ DEBUGP(DNM, "Software Load Init NACK\n");
+ /* FIXME: cause */
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_LOAD_INIT_NACK, mb,
+ sw->cb_data, NULL);
+ sw->state = SW_STATE_ERROR;
+ }
+ abis_nm_queue_send_next(sign_link->trx->bts);
+ break;
+ }
+ break;
+ case SW_STATE_WAIT_SEGACK:
+ switch (foh->msg_type) {
+ case NM_MT_LOAD_SEG_ACK:
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_LOAD_SEG_ACK, mb,
+ sw->cb_data, NULL);
+ sw->seg_in_window = 0;
+ if (!sw->last_seg) {
+ /* fill window with more segments */
+ rc = sw_fill_window(sw);
+ sw->state = SW_STATE_WAIT_SEGACK;
+ } else {
+ /* end the transfer */
+ sw->state = SW_STATE_WAIT_ENDACK;
+ rc = sw_load_end(sw);
+ }
+ abis_nm_queue_send_next(sign_link->trx->bts);
+ break;
+ case NM_MT_LOAD_ABORT:
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_LOAD_ABORT, mb,
+ sw->cb_data, NULL);
+ break;
+ }
+ break;
+ case SW_STATE_WAIT_ENDACK:
+ switch (foh->msg_type) {
+ case NM_MT_LOAD_END_ACK:
+ sw_close_file(sw);
+ DEBUGP(DNM, "Software Load End (BTS %u)\n",
+ sw->bts->nr);
+ sw->state = SW_STATE_NONE;
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_LOAD_END_ACK, mb,
+ sw->cb_data, NULL);
+ rc = 0;
+ abis_nm_queue_send_next(sign_link->trx->bts);
+ break;
+ case NM_MT_LOAD_END_NACK:
+ if (sw->forced) {
+ DEBUGP(DNM, "FORCED: Ignoring Software Load"
+ "End NACK\n");
+ sw->state = SW_STATE_NONE;
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_LOAD_END_ACK, mb,
+ sw->cb_data, NULL);
+ } else {
+ DEBUGP(DNM, "Software Load End NACK\n");
+ /* FIXME: cause */
+ sw->state = SW_STATE_ERROR;
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_LOAD_END_NACK, mb,
+ sw->cb_data, NULL);
+ }
+ abis_nm_queue_send_next(sign_link->trx->bts);
+ break;
+ }
+ case SW_STATE_WAIT_ACTACK:
+ switch (foh->msg_type) {
+ case NM_MT_ACTIVATE_SW_ACK:
+ /* we're done */
+ DEBUGP(DNM, "Activate Software DONE!\n");
+ sw->state = SW_STATE_NONE;
+ rc = 0;
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_ACTIVATE_SW_ACK, mb,
+ sw->cb_data, NULL);
+ abis_nm_queue_send_next(sign_link->trx->bts);
+ break;
+ case NM_MT_ACTIVATE_SW_NACK:
+ DEBUGP(DNM, "Activate Software NACK\n");
+ /* FIXME: cause */
+ sw->state = SW_STATE_ERROR;
+ if (sw->cbfn)
+ sw->cbfn(GSM_HOOK_NM_SWLOAD,
+ NM_MT_ACTIVATE_SW_NACK, mb,
+ sw->cb_data, NULL);
+ abis_nm_queue_send_next(sign_link->trx->bts);
+ break;
+ }
+ case SW_STATE_NONE:
+ switch (foh->msg_type) {
+ case NM_MT_ACTIVATE_SW_ACK:
+ rc = 0;
+ break;
+ }
+ break;
+ case SW_STATE_ERROR:
+ break;
+ }
+
+ if (rc)
+ DEBUGP(DNM, "unexpected NM MT 0x%02x in state %u -> %u\n",
+ foh->msg_type, old_state, sw->state);
+
+ return rc;
+}
+
+/* Load the specified software into the BTS */
+int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname,
+ uint8_t win_size, int forced,
+ gsm_cbfn *cbfn, void *cb_data)
+{
+ struct abis_nm_sw *sw = &g_sw;
+ int rc;
+
+ DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n",
+ bts->nr, fname);
+
+ if (sw->state != SW_STATE_NONE)
+ return -EBUSY;
+
+ sw->bts = bts;
+ sw->trx_nr = trx_nr;
+
+ switch (bts->type) {
+ case GSM_BTS_TYPE_BS11:
+ sw->obj_class = NM_OC_SITE_MANAGER;
+ sw->obj_instance[0] = 0xff;
+ sw->obj_instance[1] = 0xff;
+ sw->obj_instance[2] = 0xff;
+ break;
+ case GSM_BTS_TYPE_NANOBTS:
+ sw->obj_class = NM_OC_BASEB_TRANSC;
+ sw->obj_instance[0] = sw->bts->nr;
+ sw->obj_instance[1] = sw->trx_nr;
+ sw->obj_instance[2] = 0xff;
+ break;
+ case GSM_BTS_TYPE_UNKNOWN:
+ default:
+ LOGPC(DNM, LOGL_ERROR, "Software Load not properly implemented.\n");
+ return -1;
+ break;
+ }
+ sw->window_size = win_size;
+ sw->state = SW_STATE_WAIT_INITACK;
+ sw->cbfn = cbfn;
+ sw->cb_data = cb_data;
+ sw->forced = forced;
+
+ rc = sw_open_file(sw, fname);
+ if (rc < 0) {
+ sw->state = SW_STATE_NONE;
+ return rc;
+ }
+
+ return sw_load_init(sw);
+}
+
+int abis_nm_software_load_status(struct gsm_bts *bts)
+{
+ struct abis_nm_sw *sw = &g_sw;
+ struct stat st;
+ int rc, percent;
+
+ rc = fstat(sw->fd, &st);
+ if (rc < 0) {
+ perror("ERROR during stat");
+ return rc;
+ }
+
+ if (sw->stream)
+ percent = (ftell(sw->stream) * 100) / st.st_size;
+ else
+ percent = (lseek(sw->fd, 0, SEEK_CUR) * 100) / st.st_size;
+ return percent;
+}
+
+/* Activate the specified software into the BTS */
+int abis_nm_software_activate(struct gsm_bts *bts, const char *fname,
+ gsm_cbfn *cbfn, void *cb_data)
+{
+ struct abis_nm_sw *sw = &g_sw;
+ int rc;
+
+ DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n",
+ bts->nr, fname);
+
+ if (sw->state != SW_STATE_NONE)
+ return -EBUSY;
+
+ sw->bts = bts;
+ sw->obj_class = NM_OC_SITE_MANAGER;
+ sw->obj_instance[0] = 0xff;
+ sw->obj_instance[1] = 0xff;
+ sw->obj_instance[2] = 0xff;
+ sw->state = SW_STATE_WAIT_ACTACK;
+ sw->cbfn = cbfn;
+ sw->cb_data = cb_data;
+
+ /* Open the file in order to fill some sw struct members */
+ rc = sw_open_file(sw, fname);
+ if (rc < 0) {
+ sw->state = SW_STATE_NONE;
+ return rc;
+ }
+ sw_close_file(sw);
+
+ return sw_activate(sw);
+}
+
+static void fill_nm_channel(struct abis_nm_channel *ch, uint8_t bts_port,
+ uint8_t ts_nr, uint8_t subslot_nr)
+{
+ ch->attrib = NM_ATT_ABIS_CHANNEL;
+ ch->bts_port = bts_port;
+ ch->timeslot = ts_nr;
+ ch->subslot = subslot_nr;
+}
+
+int abis_nm_establish_tei(struct gsm_bts *bts, uint8_t trx_nr,
+ uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot,
+ uint8_t tei)
+{
+ struct abis_om_hdr *oh;
+ struct abis_nm_channel *ch;
+ uint8_t len = sizeof(*ch) + 2;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, len, NM_MT_ESTABLISH_TEI, NM_OC_RADIO_CARRIER,
+ bts->bts_nr, trx_nr, 0xff);
+
+ msgb_tv_put(msg, NM_ATT_TEI, tei);
+
+ ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+ fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* connect signalling of one (BTS,TRX) to a particular timeslot on the E1 */
+int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx,
+ uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot)
+{
+ struct gsm_bts *bts = trx->bts;
+ struct abis_om_hdr *oh;
+ struct abis_nm_channel *ch;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_SIGN,
+ NM_OC_RADIO_CARRIER, bts->bts_nr, trx->nr, 0xff);
+
+ ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+ fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+#if 0
+int abis_nm_disc_terr_sign(struct abis_nm_h *h, struct abis_om_obj_inst *inst,
+ struct abis_nm_abis_channel *chan)
+{
+}
+#endif
+
+int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts,
+ uint8_t e1_port, uint8_t e1_timeslot,
+ uint8_t e1_subslot)
+{
+ struct gsm_bts *bts = ts->trx->bts;
+ struct abis_om_hdr *oh;
+ struct abis_nm_channel *ch;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_TRAF,
+ NM_OC_CHANNEL, bts->bts_nr, ts->trx->nr, ts->nr);
+
+ 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);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+#if 0
+int abis_nm_disc_terr_traf(struct abis_nm_h *h, struct abis_om_obj_inst *inst,
+ struct abis_nm_abis_channel *chan,
+ uint8_t subchan)
+{
+}
+#endif
+
+/* 3GPP TS 52.021 § 8.11.1 */
+int abis_nm_get_attr(struct gsm_bts *bts, uint8_t obj_class, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+ const uint8_t *attr, uint8_t attr_len)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ DEBUGP(DNM, "Get Attr (bts=%d)\n", bts->nr);
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, attr_len, NM_MT_GET_ATTR, obj_class,
+ bts_nr, trx_nr, ts_nr);
+ msgb_tl16v_put(msg, NM_ATT_LIST_REQ_ATTR, attr_len, attr);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.6.1 */
+int abis_nm_set_bts_attr(struct gsm_bts *bts, uint8_t *attr, int attr_len)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t *cur;
+
+ DEBUGP(DNM, "Set BTS Attr (bts=%d)\n", bts->nr);
+
+ 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);
+ cur = msgb_put(msg, attr_len);
+ memcpy(cur, attr, attr_len);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.6.2 */
+int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, uint8_t *attr, int attr_len)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t *cur;
+
+ DEBUGP(DNM, "Set TRX Attr (bts=%d,trx=%d)\n", trx->bts->nr, trx->nr);
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, attr_len, NM_MT_SET_RADIO_ATTR, NM_OC_RADIO_CARRIER,
+ trx->bts->bts_nr, trx->nr, 0xff);
+ cur = msgb_put(msg, attr_len);
+ memcpy(cur, attr, attr_len);
+
+ return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_update_max_power_red(struct gsm_bts_trx *trx)
+{
+ uint8_t attr[] = { NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2 };
+ return abis_nm_set_radio_attr(trx, attr, ARRAY_SIZE(attr));
+}
+
+static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb,
+ const char **reason)
+{
+ int i;
+
+ *reason = "Reason unknown";
+
+ /* As it turns out, the BS-11 has some very peculiar restrictions
+ * on the channel combinations it allows */
+ switch (ts->trx->bts->type) {
+ case GSM_BTS_TYPE_BS11:
+ switch (chan_comb) {
+ case NM_CHANC_TCHHalf:
+ case NM_CHANC_TCHHalf2:
+ case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH:
+ /* not supported */
+ *reason = "TCH/H is not supported.";
+ return -EINVAL;
+ case NM_CHANC_SDCCH:
+ /* only one SDCCH/8 per TRX */
+ for (i = 0; i < TRX_NR_TS; i++) {
+ if (i == ts->nr)
+ continue;
+ if (ts->trx->ts[i].nm_chan_comb ==
+ NM_CHANC_SDCCH) {
+ *reason = "Only one SDCCH/8 per TRX allowed.";
+ return -EINVAL;
+ }
+ }
+ /* not allowed for TS0 of BCCH-TRX */
+ if (ts->trx == ts->trx->bts->c0 &&
+ ts->nr == 0) {
+ *reason = "SDCCH/8 must be on TS0.";
+ return -EINVAL;
+ }
+
+ /* not on the same TRX that has a BCCH+SDCCH4
+ * combination */
+ if (ts->trx != ts->trx->bts->c0 &&
+ (ts->trx->ts[0].nm_chan_comb == 5 ||
+ ts->trx->ts[0].nm_chan_comb == 8)) {
+ *reason = "SDCCH/8 and BCCH must be on the same TRX.";
+ return -EINVAL;
+ }
+ break;
+ case NM_CHANC_mainBCCH:
+ case NM_CHANC_BCCHComb:
+ /* allowed only for TS0 of C0 */
+ if (ts->trx != ts->trx->bts->c0 || ts->nr != 0) {
+ *reason = "Main BCCH must be on TS0.";
+ return -EINVAL;
+ }
+ break;
+ case NM_CHANC_BCCH:
+ /* allowed only for TS 2/4/6 of C0 */
+ if (ts->trx != ts->trx->bts->c0) {
+ *reason = "BCCH must be on C0.";
+ return -EINVAL;
+ }
+ if (ts->nr != 2 && ts->nr != 4 && ts->nr != 6) {
+ *reason = "BCCH must be on TS 2/4/6.";
+ return -EINVAL;
+ }
+ break;
+ case 8: /* this is not like 08.58, but in fact
+ * FCCH+SCH+BCCH+CCCH+SDCCH/4+SACCH/C4+CBCH */
+ /* FIXME: only one CBCH allowed per cell */
+ break;
+ }
+ break;
+ case GSM_BTS_TYPE_NANOBTS:
+ switch (ts->nr) {
+ case 0:
+ if (ts->trx->nr == 0) {
+ /* only on TRX0 */
+ switch (chan_comb) {
+ case NM_CHANC_BCCH:
+ case NM_CHANC_mainBCCH:
+ case NM_CHANC_BCCHComb:
+ return 0;
+ break;
+ default:
+ *reason = "TS0 of TRX0 must carry a BCCH.";
+ return -EINVAL;
+ }
+ } else {
+ switch (chan_comb) {
+ case NM_CHANC_TCHFull:
+ case NM_CHANC_TCHHalf:
+ case NM_CHANC_IPAC_TCHFull_TCHHalf:
+ return 0;
+ default:
+ *reason = "TS0 must carry a TCH/F or TCH/H.";
+ return -EINVAL;
+ }
+ }
+ break;
+ case 1:
+ if (ts->trx->nr == 0) {
+ switch (chan_comb) {
+ case NM_CHANC_SDCCH_CBCH:
+ if (ts->trx->ts[0].nm_chan_comb ==
+ NM_CHANC_mainBCCH)
+ return 0;
+ *reason = "TS0 must be the main BCCH for CBCH.";
+ return -EINVAL;
+ case NM_CHANC_SDCCH:
+ case NM_CHANC_TCHFull:
+ case NM_CHANC_TCHHalf:
+ case NM_CHANC_IPAC_TCHFull_TCHHalf:
+ case NM_CHANC_IPAC_TCHFull_PDCH:
+ case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH:
+ return 0;
+ default:
+ *reason = "TS1 must carry a CBCH, SDCCH or TCH.";
+ return -EINVAL;
+ }
+ } else {
+ switch (chan_comb) {
+ case NM_CHANC_SDCCH:
+ case NM_CHANC_TCHFull:
+ case NM_CHANC_TCHHalf:
+ case NM_CHANC_IPAC_TCHFull_TCHHalf:
+ return 0;
+ default:
+ *reason = "TS1 must carry a SDCCH or TCH.";
+ return -EINVAL;
+ }
+ }
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ switch (chan_comb) {
+ case NM_CHANC_TCHFull:
+ case NM_CHANC_TCHHalf:
+ case NM_CHANC_IPAC_TCHFull_TCHHalf:
+ return 0;
+ case NM_CHANC_IPAC_PDCH:
+ case NM_CHANC_IPAC_TCHFull_PDCH:
+ case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH:
+ if (ts->trx->nr == 0)
+ return 0;
+ else {
+ *reason = "PDCH must be on TRX0.";
+ return -EINVAL;
+ }
+ }
+ break;
+ }
+ *reason = "Unknown combination";
+ return -EINVAL;
+ case GSM_BTS_TYPE_OSMOBTS:
+ /* no known restrictions */
+ return 0;
+ default:
+ /* unknown BTS type */
+ return 0;
+ }
+ return 0;
+}
+
+/* Chapter 8.6.3 */
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb)
+{
+ struct gsm_bts *bts = ts->trx->bts;
+ struct abis_om_hdr *oh;
+ uint8_t zero = 0x00;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t len = 2 + 2;
+ const char *reason = NULL;
+
+ if (bts->type == GSM_BTS_TYPE_BS11)
+ len += 4 + 2 + 2 + 3;
+
+ DEBUGP(DNM, "Set Chan Attr %s\n", gsm_ts_name(ts));
+ if (verify_chan_comb(ts, chan_comb, &reason) < 0) {
+ msgb_free(msg);
+ LOGP(DNM, LOGL_ERROR,
+ "Invalid Channel Combination %d on %s. Reason: %s\n",
+ chan_comb, gsm_ts_name(ts), reason);
+ return -EINVAL;
+ }
+ ts->nm_chan_comb = chan_comb;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, len, NM_MT_SET_CHAN_ATTR,
+ NM_OC_CHANNEL, bts->bts_nr,
+ ts->trx->nr, ts->nr);
+ msgb_tv_put(msg, NM_ATT_CHAN_COMB, chan_comb);
+ if (ts->hopping.enabled) {
+ unsigned int i;
+ uint8_t *len;
+
+ msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn);
+ msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio);
+
+ /* build the ARFCN list */
+ msgb_put_u8(msg, NM_ATT_ARFCN_LIST);
+ len = msgb_put(msg, 1);
+ *len = 0;
+ for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+ if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) {
+ msgb_put_u16(msg, i);
+ /* At least BS-11 wants a TLV16 here */
+ if (bts->type == GSM_BTS_TYPE_BS11)
+ *len += 1;
+ else
+ *len += sizeof(uint16_t);
+ }
+ }
+ }
+ msgb_tv_put(msg, NM_ATT_TSC, gsm_ts_tsc(ts)); /* training sequence */
+ if (bts->type == GSM_BTS_TYPE_BS11)
+ msgb_tlv_put(msg, 0x59, 1, &zero);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_sw_act_req_ack(struct gsm_bts *bts, uint8_t obj_class, uint8_t i1,
+ uint8_t i2, uint8_t i3, int nack, uint8_t *attr, int att_len)
+{
+ 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;
+
+ if (nack) {
+ len += 2;
+ msgtype = NM_MT_SW_ACT_REQ_NACK;
+ }
+
+ 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);
+
+ if (attr) {
+ uint8_t *ptr = msgb_put(msg, att_len);
+ memcpy(ptr, attr, att_len);
+ }
+ if (nack)
+ msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP);
+
+ return abis_nm_sendmsg_direct(bts, msg);
+}
+
+int abis_nm_raw_msg(struct gsm_bts *bts, int len, uint8_t *rawmsg)
+{
+ struct msgb *msg = nm_msgb_alloc();
+ struct abis_om_hdr *oh;
+ uint8_t *data;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh));
+ fill_om_hdr(oh, len);
+ data = msgb_put(msg, len);
+ memcpy(data, rawmsg, len);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* Siemens specific commands */
+static int __simple_cmd(struct gsm_bts *bts, uint8_t msg_type)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 0, msg_type, NM_OC_SITE_MANAGER,
+ 0xff, 0xff, 0xff);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.9.2 */
+int abis_nm_opstart(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, uint8_t i2)
+{
+ struct abis_om_hdr *oh;
+ struct abis_om_fom_hdr *foh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ foh = fill_om_fom_hdr(oh, 0, NM_MT_OPSTART, obj_class, i0, i1, i2);
+
+ abis_nm_debugp_foh(DNM, foh);
+ DEBUGPC(DNM, "Sending OPSTART\n");
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.8.5 */
+int abis_nm_chg_adm_state(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0,
+ uint8_t i1, uint8_t i2, enum abis_nm_adm_state adm_state)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 2, NM_MT_CHG_ADM_STATE, obj_class, i0, i1, i2);
+ msgb_tv_put(msg, NM_ATT_ADM_STATE, adm_state);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_conn_mdrop_link(struct gsm_bts *bts, uint8_t e1_port0, uint8_t ts0,
+ uint8_t e1_port1, uint8_t ts1)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t *attr;
+
+ DEBUGP(DNM, "CONNECT MDROP LINK E1=(%u,%u) -> E1=(%u, %u)\n",
+ e1_port0, ts0, e1_port1, ts1);
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 6, NM_MT_CONN_MDROP_LINK,
+ NM_OC_SITE_MANAGER, 0x00, 0x00, 0x00);
+
+ attr = msgb_put(msg, 3);
+ attr[0] = NM_ATT_MDROP_LINK;
+ attr[1] = e1_port0;
+ attr[2] = ts0;
+
+ attr = msgb_put(msg, 3);
+ attr[0] = NM_ATT_MDROP_NEXT;
+ attr[1] = e1_port1;
+ attr[2] = ts1;
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* Chapter 8.7.1 */
+int abis_nm_perform_test(struct gsm_bts *bts, uint8_t obj_class,
+ uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+ uint8_t test_nr, uint8_t auton_report, struct msgb *msg)
+{
+ struct abis_om_hdr *oh;
+
+ DEBUGP(DNM, "PEFORM TEST %s\n", abis_nm_test_name(test_nr));
+
+ if (!msg)
+ msg = nm_msgb_alloc();
+
+ msgb_tv_push(msg, NM_ATT_AUTON_REPORT, auton_report);
+ msgb_tv_push(msg, NM_ATT_TEST_NO, test_nr);
+ oh = (struct abis_om_hdr *) msgb_push(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, msgb_l3len(msg), NM_MT_PERF_TEST,
+ obj_class, bts_nr, trx_nr, ts_nr);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_event_reports(struct gsm_bts *bts, int on)
+{
+ if (on == 0)
+ return __simple_cmd(bts, NM_MT_STOP_EVENT_REP);
+ else
+ return __simple_cmd(bts, NM_MT_REST_EVENT_REP);
+}
+
+/* Siemens (or BS-11) specific commands */
+
+int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect)
+{
+ if (reconnect == 0)
+ return __simple_cmd(bts, NM_MT_BS11_DISCONNECT);
+ else
+ return __simple_cmd(bts, NM_MT_BS11_RECONNECT);
+}
+
+int abis_nm_bs11_restart(struct gsm_bts *bts)
+{
+ return __simple_cmd(bts, NM_MT_BS11_RESTART);
+}
+
+
+struct bs11_date_time {
+ uint16_t year;
+ uint8_t month;
+ uint8_t day;
+ uint8_t hour;
+ uint8_t min;
+ uint8_t sec;
+} __attribute__((packed));
+
+
+void get_bs11_date_time(struct bs11_date_time *aet)
+{
+ time_t t;
+ struct tm *tm;
+
+ t = time(NULL);
+ tm = localtime(&t);
+ aet->sec = tm->tm_sec;
+ aet->min = tm->tm_min;
+ aet->hour = tm->tm_hour;
+ aet->day = tm->tm_mday;
+ aet->month = tm->tm_mon;
+ aet->year = htons(1900 + tm->tm_year);
+}
+
+int abis_nm_bs11_reset_resource(struct gsm_bts *bts)
+{
+ return __simple_cmd(bts, NM_MT_BS11_RESET_RESOURCE);
+}
+
+int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin)
+{
+ if (begin)
+ return __simple_cmd(bts, NM_MT_BS11_BEGIN_DB_TX);
+ else
+ return __simple_cmd(bts, NM_MT_BS11_END_DB_TX);
+}
+
+int abis_nm_bs11_create_object(struct gsm_bts *bts,
+ enum abis_bs11_objtype type, uint8_t idx,
+ uint8_t attr_len, const uint8_t *attr)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t *cur;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, attr_len, NM_MT_BS11_CREATE_OBJ,
+ NM_OC_BS11, type, 0, idx);
+ cur = msgb_put(msg, attr_len);
+ memcpy(cur, attr, attr_len);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_delete_object(struct gsm_bts *bts,
+ enum abis_bs11_objtype type, uint8_t idx)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ,
+ NM_OC_BS11, type, 0, idx);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, uint8_t idx)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t zero = 0x00;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 3, NM_MT_BS11_CREATE_OBJ,
+ NM_OC_BS11_ENVABTSE, 0, idx, 0xff);
+ msgb_tlv_put(msg, 0x99, 1, &zero);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_create_bport(struct gsm_bts *bts, uint8_t idx)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 0, NM_MT_BS11_CREATE_OBJ, NM_OC_BS11_BPORT,
+ idx, 0xff, 0xff);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_delete_bport(struct gsm_bts *bts, uint8_t idx)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, NM_OC_BS11_BPORT,
+ idx, 0xff, 0xff);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+static const uint8_t sm_attr[] = { NM_ATT_TEI, NM_ATT_ABIS_CHANNEL };
+int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 2+sizeof(sm_attr), NM_MT_GET_ATTR, NM_OC_SITE_MANAGER,
+ 0xff, 0xff, 0xff);
+ msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(sm_attr), sm_attr);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* like abis_nm_conn_terr_traf + set_tei */
+int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, uint8_t e1_port,
+ uint8_t e1_timeslot, uint8_t e1_subslot,
+ uint8_t tei)
+{
+ struct abis_om_hdr *oh;
+ struct abis_nm_channel *ch;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, sizeof(*ch)+2, NM_MT_BS11_SET_ATTR,
+ NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff);
+
+ ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch));
+ fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot);
+ msgb_tv_put(msg, NM_ATT_TEI, tei);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, uint8_t level)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR,
+ NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr);
+ msgb_tlv_put(msg, NM_ATT_BS11_TXPWR, 1, &level);
+
+ return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t attr = NM_ATT_BS11_TXPWR;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+ NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr);
+ msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr);
+
+ return abis_nm_sendmsg(trx->bts, msg);
+}
+
+int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t attr[] = { NM_ATT_BS11_PLL_MODE };
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+ NM_OC_BS11, BS11_OBJ_LI, 0x00, 0x00);
+ msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_cclk(struct gsm_bts *bts)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t attr[] = { NM_ATT_BS11_CCLK_ACCURACY,
+ NM_ATT_BS11_CCLK_TYPE };
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+ NM_OC_BS11, BS11_OBJ_CCLK, 0x00, 0x00);
+ msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr);
+
+ return abis_nm_sendmsg(bts, msg);
+
+}
+
+//static const uint8_t bs11_logon_c7[] = { 0x07, 0xd9, 0x01, 0x11, 0x0d, 0x10, 0x20 };
+
+int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on)
+{
+ return abis_nm_bs11_logon(bts, 0x02, "FACTORY", on);
+}
+
+int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on)
+{
+ return abis_nm_bs11_logon(bts, 0x03, "FIELD ", on);
+}
+
+int abis_nm_bs11_logon(struct gsm_bts *bts, uint8_t level, const char *name, int on)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ struct bs11_date_time bdt;
+
+ get_bs11_date_time(&bdt);
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ if (on) {
+ uint8_t len = 3*2 + sizeof(bdt)
+ + 1 + strlen(name);
+ fill_om_fom_hdr(oh, len, NM_MT_BS11_LMT_LOGON,
+ NM_OC_BS11_BTSE, 0xff, 0xff, 0xff);
+ msgb_tlv_put(msg, NM_ATT_BS11_LMT_LOGIN_TIME,
+ sizeof(bdt), (uint8_t *) &bdt);
+ msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_ACC_LEV,
+ 1, &level);
+ msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_NAME,
+ strlen(name), (uint8_t *)name);
+ } else {
+ fill_om_fom_hdr(oh, 0, NM_MT_BS11_LMT_LOGOFF,
+ NM_OC_BS11_BTSE, 0xff, 0xff, 0xff);
+ }
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg;
+
+ if (strlen(password) != 10)
+ return -EINVAL;
+
+ msg = nm_msgb_alloc();
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 2+strlen(password), NM_MT_BS11_SET_ATTR,
+ NM_OC_BS11, BS11_OBJ_TRX1, 0x00, 0x00);
+ msgb_tlv_put(msg, NM_ATT_BS11_PASSWORD, 10, (const uint8_t *)password);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* change the BS-11 PLL Mode to either locked (E1 derived) or standalone */
+int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg;
+ uint8_t tlv_value;
+
+ msg = nm_msgb_alloc();
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11,
+ BS11_OBJ_LI, 0x00, 0x00);
+
+ if (locked)
+ tlv_value = BS11_LI_PLL_LOCKED;
+ else
+ tlv_value = BS11_LI_PLL_STANDALONE;
+
+ msgb_tlv_put(msg, NM_ATT_BS11_PLL_MODE, 1, &tlv_value);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* Set the calibration value of the PLL (work value/set value)
+ * It depends on the login which one is changed */
+int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg;
+ uint8_t tlv_value[2];
+
+ msg = nm_msgb_alloc();
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11,
+ BS11_OBJ_TRX1, 0x00, 0x00);
+
+ tlv_value[0] = value>>8;
+ tlv_value[1] = value&0xff;
+
+ msgb_tlv_put(msg, NM_ATT_BS11_PLL, 2, tlv_value);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_state(struct gsm_bts *bts)
+{
+ return __simple_cmd(bts, NM_MT_BS11_GET_STATE);
+}
+
+/* BS11 SWL */
+
+void *tall_fle_ctx;
+
+struct abis_nm_bs11_sw {
+ struct gsm_bts *bts;
+ char swl_fname[PATH_MAX];
+ uint8_t win_size;
+ int forced;
+ struct llist_head file_list;
+ gsm_cbfn *user_cb; /* specified by the user */
+};
+static struct abis_nm_bs11_sw _g_bs11_sw, *g_bs11_sw = &_g_bs11_sw;
+
+struct file_list_entry {
+ struct llist_head list;
+ char fname[PATH_MAX];
+};
+
+struct file_list_entry *fl_dequeue(struct llist_head *queue)
+{
+ struct llist_head *lh;
+
+ if (llist_empty(queue))
+ return NULL;
+
+ lh = queue->next;
+ llist_del(lh);
+
+ return llist_entry(lh, struct file_list_entry, list);
+}
+
+static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw)
+{
+ char linebuf[255];
+ struct llist_head *lh, *lh2;
+ FILE *swl;
+ int rc = 0;
+
+ swl = fopen(bs11_sw->swl_fname, "r");
+ if (!swl)
+ 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 (fgets(linebuf, sizeof(linebuf), swl)) {
+ char file_id[12+1];
+ char file_version[80+1];
+ struct file_list_entry *fle;
+ static char dir[PATH_MAX];
+
+ if (strlen(linebuf) < 4)
+ continue;
+
+ rc = sscanf(linebuf+4, "%12s:%80s\r\n", file_id, file_version);
+ if (rc < 0) {
+ perror("ERR parsing SWL file");
+ rc = -EINVAL;
+ goto out;
+ }
+ if (rc < 2)
+ continue;
+
+ fle = talloc_zero(tall_fle_ctx, struct file_list_entry);
+ if (!fle) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ /* construct new filename */
+ osmo_strlcpy(dir, bs11_sw->swl_fname, sizeof(dir));
+ strncat(fle->fname, dirname(dir), sizeof(fle->fname) - 1);
+ strcat(fle->fname, "/");
+ strncat(fle->fname, file_id, sizeof(fle->fname) - 1 -strlen(fle->fname));
+
+ llist_add_tail(&fle->list, &bs11_sw->file_list);
+ }
+
+out:
+ fclose(swl);
+ return rc;
+}
+
+/* bs11 swload specific callback, passed to abis_nm core swload */
+static int bs11_swload_cbfn(unsigned int hook, unsigned int event,
+ struct msgb *msg, void *data, void *param)
+{
+ struct abis_nm_bs11_sw *bs11_sw = data;
+ struct file_list_entry *fle;
+ int rc = 0;
+
+ switch (event) {
+ case NM_MT_LOAD_END_ACK:
+ fle = fl_dequeue(&bs11_sw->file_list);
+ if (fle) {
+ /* start download the next file of our file list */
+ rc = abis_nm_software_load(bs11_sw->bts, 0xff, fle->fname,
+ bs11_sw->win_size,
+ bs11_sw->forced,
+ &bs11_swload_cbfn, bs11_sw);
+ talloc_free(fle);
+ } else {
+ /* activate the SWL */
+ rc = abis_nm_software_activate(bs11_sw->bts,
+ bs11_sw->swl_fname,
+ bs11_swload_cbfn,
+ bs11_sw);
+ }
+ break;
+ case NM_MT_LOAD_SEG_ACK:
+ case NM_MT_LOAD_END_NACK:
+ case NM_MT_LOAD_INIT_ACK:
+ case NM_MT_LOAD_INIT_NACK:
+ case NM_MT_ACTIVATE_SW_NACK:
+ case NM_MT_ACTIVATE_SW_ACK:
+ default:
+ /* fallthrough to the user callback */
+ if (bs11_sw->user_cb)
+ rc = bs11_sw->user_cb(hook, event, msg, NULL, NULL);
+ break;
+ }
+
+ return rc;
+}
+
+/* Siemens provides a SWL file that is a mere listing of all the other
+ * files that are part of a software release. We need to upload first
+ * the list file, and then each file that is listed in the list file */
+int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname,
+ uint8_t win_size, int forced, gsm_cbfn *cbfn)
+{
+ struct abis_nm_bs11_sw *bs11_sw = g_bs11_sw;
+ struct file_list_entry *fle;
+ int rc = 0;
+
+ INIT_LLIST_HEAD(&bs11_sw->file_list);
+ bs11_sw->bts = bts;
+ bs11_sw->win_size = win_size;
+ bs11_sw->user_cb = cbfn;
+ bs11_sw->forced = forced;
+
+ osmo_strlcpy(bs11_sw->swl_fname, fname, sizeof(bs11_sw->swl_fname));
+ rc = bs11_read_swl_file(bs11_sw);
+ if (rc < 0)
+ return rc;
+
+ /* dequeue next item in file list */
+ fle = fl_dequeue(&bs11_sw->file_list);
+ if (!fle)
+ return -EINVAL;
+
+ /* start download the next file of our file list */
+ rc = abis_nm_software_load(bts, 0xff, fle->fname, win_size, forced,
+ bs11_swload_cbfn, bs11_sw);
+ talloc_free(fle);
+ return rc;
+}
+
+#if 0
+static uint8_t req_attr_btse[] = {
+ NM_ATT_ADM_STATE, NM_ATT_BS11_LMT_LOGON_SESSION,
+ NM_ATT_BS11_LMT_LOGIN_TIME, NM_ATT_BS11_LMT_USER_ACC_LEV,
+ NM_ATT_BS11_LMT_USER_NAME,
+
+ 0xaf, NM_ATT_BS11_RX_OFFSET, NM_ATT_BS11_VENDOR_NAME,
+
+ NM_ATT_BS11_SW_LOAD_INTENDED, NM_ATT_BS11_SW_LOAD_SAFETY,
+
+ NM_ATT_BS11_SW_LOAD_STORED };
+
+static uint8_t req_attr_btsm[] = {
+ NM_ATT_ABIS_CHANNEL, NM_ATT_TEI, NM_ATT_BS11_ABIS_EXT_TIME,
+ NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xce, NM_ATT_FILE_ID,
+ NM_ATT_FILE_VERSION, NM_ATT_OPER_STATE, 0xe8, NM_ATT_BS11_ALL_TEST_CATG,
+ NM_ATT_SW_DESCR, NM_ATT_GET_ARI };
+#endif
+
+static uint8_t req_attr[] = {
+ NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xa8, NM_ATT_OPER_STATE,
+ 0xd5, 0xa1, NM_ATT_BS11_ESN_FW_CODE_NO, NM_ATT_BS11_ESN_HW_CODE_NO,
+ 0x42, NM_ATT_BS11_ESN_PCB_SERIAL, NM_ATT_BS11_PLL };
+
+int abis_nm_bs11_get_serno(struct gsm_bts *bts)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ /* SiemensHW CCTRL object */
+ fill_om_fom_hdr(oh, 2+sizeof(req_attr), NM_MT_GET_ATTR, NM_OC_BS11,
+ 0x03, 0x00, 0x00);
+ msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(req_attr), req_attr);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_ext_time(struct gsm_bts *bts)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ struct bs11_date_time aet;
+
+ get_bs11_date_time(&aet);
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ /* SiemensHW CCTRL object */
+ fill_om_fom_hdr(oh, 2+sizeof(aet), NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER,
+ 0xff, 0xff, 0xff);
+ msgb_tlv_put(msg, NM_ATT_BS11_ABIS_EXT_TIME, sizeof(aet), (uint8_t *) &aet);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, uint8_t bport)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ uint8_t attr = NM_ATT_BS11_LINE_CFG;
+
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR,
+ NM_OC_BS11_BPORT, bport, 0xff, 0x02);
+ msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, uint8_t bport, enum abis_bs11_line_cfg line_cfg)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+ struct bs11_date_time aet;
+
+ get_bs11_date_time(&aet);
+ oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE);
+ fill_om_fom_hdr(oh, 2, NM_MT_BS11_SET_ATTR, NM_OC_BS11_BPORT,
+ bport, 0xff, 0x02);
+ msgb_tv_put(msg, NM_ATT_BS11_LINE_CFG, line_cfg);
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* ip.access nanoBTS specific commands */
+static const char ipaccess_magic[] = "com.ipaccess";
+
+
+static int abis_nm_rx_ipacc(struct msgb *msg)
+{
+ struct in_addr addr;
+ struct abis_om_hdr *oh = msgb_l2(msg);
+ struct abis_om_fom_hdr *foh;
+ uint8_t idstrlen = oh->data[0];
+ struct tlv_parsed tp;
+ struct ipacc_ack_signal_data signal;
+ struct e1inp_sign_link *sign_link = msg->dst;
+
+ if (strncmp((char *)&oh->data[1], ipaccess_magic, idstrlen)) {
+ LOGP(DNM, LOGL_ERROR, "id string is not com.ipaccess !?!\n");
+ return -EINVAL;
+ }
+
+ foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen);
+ abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+ abis_nm_debugp_foh(DNM, foh);
+
+ DEBUGPC(DNM, "IPACCESS(0x%02x): ", foh->msg_type);
+
+ switch (foh->msg_type) {
+ case NM_MT_IPACC_RSL_CONNECT_ACK:
+ DEBUGPC(DNM, "RSL CONNECT ACK ");
+ if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP)) {
+ memcpy(&addr,
+ TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP), sizeof(addr));
+
+ DEBUGPC(DNM, "IP=%s ", inet_ntoa(addr));
+ }
+ if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP_PORT))
+ DEBUGPC(DNM, "PORT=%u ",
+ ntohs(*((uint16_t *)
+ TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP_PORT))));
+ if (TLVP_PRESENT(&tp, NM_ATT_IPACC_STREAM_ID))
+ DEBUGPC(DNM, "STREAM=0x%02x ",
+ *TLVP_VAL(&tp, NM_ATT_IPACC_STREAM_ID));
+ DEBUGPC(DNM, "\n");
+ break;
+ case NM_MT_IPACC_RSL_CONNECT_NACK:
+ LOGP(DNM, LOGL_ERROR, "RSL CONNECT NACK ");
+ if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+ LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+ abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+ else
+ LOGPC(DNM, LOGL_ERROR, "\n");
+ break;
+ case NM_MT_IPACC_SET_NVATTR_ACK:
+ DEBUGPC(DNM, "SET NVATTR ACK\n");
+ /* FIXME: decode and show the actual attributes */
+ break;
+ case NM_MT_IPACC_SET_NVATTR_NACK:
+ LOGP(DNM, LOGL_ERROR, "SET NVATTR NACK ");
+ if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+ LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+ abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+ else
+ LOGPC(DNM, LOGL_ERROR, "\n");
+ break;
+ case NM_MT_IPACC_GET_NVATTR_ACK:
+ DEBUGPC(DNM, "GET NVATTR ACK\n");
+ /* FIXME: decode and show the actual attributes */
+ break;
+ case NM_MT_IPACC_GET_NVATTR_NACK:
+ LOGPC(DNM, LOGL_ERROR, "GET NVATTR NACK ");
+ if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+ LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+ abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+ else
+ LOGPC(DNM, LOGL_ERROR, "\n");
+ break;
+ case NM_MT_IPACC_SET_ATTR_ACK:
+ DEBUGPC(DNM, "SET ATTR ACK\n");
+ break;
+ case NM_MT_IPACC_SET_ATTR_NACK:
+ LOGPC(DNM, LOGL_ERROR, "SET ATTR NACK ");
+ if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+ LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n",
+ abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+ else
+ LOGPC(DNM, LOGL_ERROR, "\n");
+ break;
+ default:
+ DEBUGPC(DNM, "unknown\n");
+ break;
+ }
+
+ /* signal handling */
+ switch (foh->msg_type) {
+ case NM_MT_IPACC_RSL_CONNECT_NACK:
+ case NM_MT_IPACC_SET_NVATTR_NACK:
+ case NM_MT_IPACC_GET_NVATTR_NACK:
+ signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr);
+ signal.msg_type = foh->msg_type;
+ osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal);
+ break;
+ case NM_MT_IPACC_SET_NVATTR_ACK:
+ signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr);
+ signal.msg_type = foh->msg_type;
+ osmo_signal_dispatch(SS_NM, S_NM_IPACC_ACK, &signal);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* send an ip-access manufacturer specific message */
+int abis_nm_ipaccess_msg(struct gsm_bts *bts, uint8_t msg_type,
+ uint8_t obj_class, uint8_t bts_nr,
+ uint8_t trx_nr, uint8_t ts_nr,
+ uint8_t *attr, int attr_len)
+{
+ struct msgb *msg = nm_msgb_alloc();
+ struct abis_om_hdr *oh;
+ struct abis_om_fom_hdr *foh;
+ uint8_t *data;
+
+ /* construct the 12.21 OM header, observe the erroneous length */
+ oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh));
+ fill_om_hdr(oh, sizeof(*foh) + attr_len);
+ oh->mdisc = ABIS_OM_MDISC_MANUF;
+
+ /* add the ip.access magic */
+ data = msgb_put(msg, sizeof(ipaccess_magic)+1);
+ *data++ = sizeof(ipaccess_magic);
+ memcpy(data, ipaccess_magic, sizeof(ipaccess_magic));
+
+ /* fill the 12.21 FOM header */
+ foh = (struct abis_om_fom_hdr *) msgb_put(msg, sizeof(*foh));
+ foh->msg_type = msg_type;
+ foh->obj_class = obj_class;
+ foh->obj_inst.bts_nr = bts_nr;
+ foh->obj_inst.trx_nr = trx_nr;
+ foh->obj_inst.ts_nr = ts_nr;
+
+ if (attr && attr_len) {
+ data = msgb_put(msg, attr_len);
+ memcpy(data, attr, attr_len);
+ }
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* set some attributes in NVRAM */
+int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, uint8_t *attr,
+ int attr_len)
+{
+ return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_SET_NVATTR,
+ NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, attr,
+ attr_len);
+}
+
+int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx,
+ uint32_t ip, uint16_t port, uint8_t stream)
+{
+ 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);
+
+ ia.s_addr = htonl(ip);
+ attr[1] = stream;
+ attr[3] = port >> 8;
+ attr[4] = port & 0xff;
+ *(uint32_t *)(attr+6) = ia.s_addr;
+
+ /* if ip == 0, we use the default IP */
+ if (ip == 0)
+ attr_len -= 5;
+
+ DEBUGP(DNM, "ip.access RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+ inet_ntoa(ia), port, stream);
+
+ return 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);
+}
+
+/* restart / reboot an ip.access nanoBTS */
+int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx)
+{
+ struct abis_om_hdr *oh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ 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);
+
+ return abis_nm_sendmsg_direct(trx->bts, msg);
+}
+
+int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, uint8_t obj_class,
+ uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr,
+ uint8_t *attr, uint8_t attr_len)
+{
+ return abis_nm_ipaccess_msg(bts, NM_MT_IPACC_SET_ATTR,
+ obj_class, bts_nr, trx_nr, ts_nr,
+ attr, attr_len);
+}
+
+void abis_nm_ipaccess_cgi(uint8_t *buf, struct gsm_bts *bts)
+{
+ /* we simply reuse the GSM48 function and overwrite the RAC
+ * with the Cell ID */
+ gsm48_ra_id_by_bts(buf, bts);
+ *((uint16_t *)(buf + 5)) = htons(bts->cell_identity);
+}
+
+void gsm_trx_lock_rf(struct gsm_bts_trx *trx, int locked)
+{
+ int new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+
+ trx->mo.nm_state.administrative = new_state;
+ if (!trx->bts || !trx->bts->oml_link)
+ return;
+
+ abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER,
+ trx->bts->bts_nr, trx->nr, 0xff,
+ new_state);
+}
+
+static const struct value_string ipacc_testres_names[] = {
+ { NM_IPACC_TESTRES_SUCCESS, "SUCCESS" },
+ { NM_IPACC_TESTRES_TIMEOUT, "TIMEOUT" },
+ { NM_IPACC_TESTRES_NO_CHANS, "NO CHANNELS" },
+ { NM_IPACC_TESTRES_PARTIAL, "PARTIAL" },
+ { NM_IPACC_TESTRES_STOPPED, "STOPPED" },
+ { 0, NULL }
+};
+
+const char *ipacc_testres_name(uint8_t res)
+{
+ return get_value_string(ipacc_testres_names, res);
+}
+
+void ipac_parse_cgi(struct cell_global_id *cid, const uint8_t *buf)
+{
+ cid->mcc = (buf[0] & 0xf) * 100;
+ cid->mcc += (buf[0] >> 4) * 10;
+ cid->mcc += (buf[1] & 0xf) * 1;
+
+ if (buf[1] >> 4 == 0xf) {
+ cid->mnc = (buf[2] & 0xf) * 10;
+ cid->mnc += (buf[2] >> 4) * 1;
+ } else {
+ cid->mnc = (buf[2] & 0xf) * 100;
+ cid->mnc += (buf[2] >> 4) * 10;
+ cid->mnc += (buf[1] >> 4) * 1;
+ }
+
+ cid->lac = ntohs(*((uint16_t *)&buf[3]));
+ cid->ci = ntohs(*((uint16_t *)&buf[5]));
+}
+
+/* parse BCCH information IEI from wire format to struct ipac_bcch_info */
+int ipac_parse_bcch_info(struct ipac_bcch_info *binf, uint8_t *buf)
+{
+ uint8_t *cur = buf;
+ uint16_t len __attribute__((unused));
+
+ memset(binf, 0, sizeof(*binf));
+
+ if (cur[0] != NM_IPAC_EIE_BCCH_INFO)
+ return -EINVAL;
+ cur++;
+
+ len = ntohs(*(uint16_t *)cur);
+ cur += 2;
+
+ binf->info_type = ntohs(*(uint16_t *)cur);
+ cur += 2;
+
+ if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL)
+ binf->freq_qual = *cur >> 2;
+
+ binf->arfcn = (*cur++ & 3) << 8;
+ binf->arfcn |= *cur++;
+
+ if (binf->info_type & IPAC_BINF_RXLEV)
+ binf->rx_lev = *cur & 0x3f;
+ cur++;
+
+ if (binf->info_type & IPAC_BINF_RXQUAL)
+ binf->rx_qual = *cur & 0x7;
+ cur++;
+
+ if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL)
+ binf->freq_err = ntohs(*(uint16_t *)cur);
+ cur += 2;
+
+ if (binf->info_type & IPAC_BINF_FRAME_OFFSET)
+ binf->frame_offset = ntohs(*(uint16_t *)cur);
+ cur += 2;
+
+ if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET)
+ binf->frame_nr_offset = ntohl(*(uint32_t *)cur);
+ cur += 4;
+
+#if 0
+ /* Somehow this is not set correctly */
+ if (binf->info_type & IPAC_BINF_BSIC)
+#endif
+ binf->bsic = *cur & 0x3f;
+ cur++;
+
+ ipac_parse_cgi(&binf->cgi, cur);
+ cur += 7;
+
+ if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2) {
+ memcpy(binf->ba_list_si2, cur, sizeof(binf->ba_list_si2));
+ cur += sizeof(binf->ba_list_si2);
+ }
+
+ if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2bis) {
+ memcpy(binf->ba_list_si2bis, cur,
+ sizeof(binf->ba_list_si2bis));
+ cur += sizeof(binf->ba_list_si2bis);
+ }
+
+ if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2ter) {
+ memcpy(binf->ba_list_si2ter, cur,
+ sizeof(binf->ba_list_si2ter));
+ cur += sizeof(binf->ba_list_si2ter);
+ }
+
+ return 0;
+}
+
+void abis_nm_clear_queue(struct gsm_bts *bts)
+{
+ struct msgb *msg;
+
+ while (!llist_empty(&bts->abis_queue)) {
+ msg = msgb_dequeue(&bts->abis_queue);
+ msgb_free(msg);
+ }
+
+ bts->abis_nm_pend = 0;
+}
diff --git a/src/libbsc/abis_nm_ipaccess.c b/src/libbsc/abis_nm_ipaccess.c
new file mode 100644
index 000000000..b8225383a
--- /dev/null
+++ b/src/libbsc/abis_nm_ipaccess.c
@@ -0,0 +1,87 @@
+/* GSM Network Management (OML) messages on the A-bis interface
+ * Extensions for the ip.access A-bis over IP protocol*/
+
+/* (C) 2008-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/>.
+ *
+ */
+
+/* A list of all the 'embedded' attributes of ip.access */
+enum ipa_embedded_att {
+ IPA_ATT_ARFCN_WHITELIST = 0x01,
+ IPA_ATT_ARFCN_BLACKLIST = 0x02,
+ IPA_ATT_FREQ_ERR_LIST = 0x03,
+ IPA_ATT_CHAN_USAGE_LIST = 0x04,
+ IPA_ATT_BCCH_INF_TYPE = 0x05,
+ IPA_ATT_BCCH_INF = 0x06,
+ IPA_ATT_CONFIG = 0x07,
+ IPA_ATT_RESULT_DETAILS = 0x08,
+ IPA_ATT_RXLEV_THRESH = 0x09,
+ IPA_ATT_FREQ_SYNC_OPT = 0x0a,
+ IPA_ATT_MAC_ADDR = 0x0b,
+ IPA_ATT_HW_SW_COMPAT_NR = 0x0c,
+ IPA_ATT_MANUF_SER_NR = 0x0d,
+ IPA_ATT_OEM_ID = 0x0e,
+ IPA_ATT_DATETIME_MANUF = 0x0f,
+ IPA_ATT_DATETIME_CALIB = 0x10,
+ IPA_ATT_BEACON_INF = 0x11,
+ IPA_ATT_FREQ_ERR = 0x12,
+ IPA_ATT_SNMP_COMM_STRING = 0x13,
+ IPA_ATT_SNMP_TRAP_ADDR = 0x14,
+ IPA_ATT_SNMP_TRAP_PORT = 0x15,
+ IPA_ATT_SNMP_MAN_ADDR = 0x16,
+ IPA_ATT_SNMP_SYS_CONTACT = 0x17,
+ IPA_ATT_FACTORY_ID = 0x18,
+ IPA_ATT_FACTORY_SERIAL = 0x19,
+ IPA_ATT_LOGGED_EVT_IND = 0x1a,
+ IPA_ATT_LOCAL_ADD_TEXT = 0x1b,
+ IPA_ATT_FREQ_BANDS = 0x1c,
+ IPA_ATT_MAX_TA = 0x1d,
+ IPA_ATT_CIPH_ALG = 0x1e,
+ IPA_ATT_CHAN_TYPES = 0x1f,
+ IPA_ATT_CHAN_MODES = 0x20,
+ IPA_ATT_GPRS_CODING_SCHEMES = 0x21,
+ IPA_ATT_RTP_FEATURES = 0x22,
+ IPA_ATT_RSL_FEATURES = 0x23,
+ IPA_ATT_BTS_HW_CLASS = 0x24,
+ IPA_ATT_BTS_ID = 0x25,
+ IPA_ATT_BCAST_L2_MSG = 0x26,
+};
+
+/* append an ip.access channel list to the given msgb */
+static int ipa_chan_list_append(struct msgb *msg, uint8_t ie,
+ uint16_t *arfcns, int arfcn_count)
+{
+ int i;
+ uint8_t *u8;
+ uint16_t *u16;
+
+ /* tag */
+ u8 = msgb_push(msg, 1);
+ *u8 = ie;
+
+ /* length in octets */
+ u16 = msgb_push(msg, 2);
+ *u16 = htons(arfcn_count * 2);
+
+ for (i = 0; i < arfcn_count; i++) {
+ u16 = msgb_push(msg, 2);
+ *u16 = htons(arfcns[i]);
+ }
+
+ return 0;
+}
diff --git a/src/libbsc/abis_nm_vty.c b/src/libbsc/abis_nm_vty.c
new file mode 100644
index 000000000..6ec0a4a21
--- /dev/null
+++ b/src/libbsc/abis_nm_vty.c
@@ -0,0 +1,191 @@
+/* VTY interface for A-bis OML (Netowrk Management) */
+
+/* (C) 2009-2010 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 <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/gsm/abis_nm.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+extern struct gsm_network *bsc_gsmnet;
+
+static struct cmd_node oml_node = {
+ OML_NODE,
+ "%s(oml)# ",
+ 1,
+};
+
+struct oml_node_state {
+ struct gsm_bts *bts;
+ uint8_t obj_class;
+ 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" \
+ "BTS Object\n" \
+ "Radio Carrier Object\n" \
+ "Baseband Transceiver Object\n" \
+ "Channel (Timeslot) Object\n" \
+ "Adjacent Object (Siemens)\n" \
+ "Handover Object (Siemens)\n" \
+ "Power Control Object (Siemens)\n" \
+ "BTSE Object (Siemens)\n" \
+ "Rack Object (Siemens)\n" \
+ "Test Object (Siemens)\n" \
+ "ENVABTSE Object (Siemens)\n" \
+ "BPORT Object (Siemens)\n" \
+ "GPRS NSE Object (ip.access/osmo-bts)\n" \
+ "GPRS Cell Object (ip.acecss/osmo-bts)\n" \
+ "GPRS NSVC Object (ip.acecss/osmo-bts)\n" \
+ "SIEMENSHW Object (Siemens)\n"
+
+
+DEFUN(oml_class_inst, oml_class_inst_cmd,
+ "bts <0-255> oml class " NM_OBJCLASS_VTY
+ " instance <0-255> <0-255> <0-255>",
+ "BTS related commands\n" "BTS Number\n"
+ "Manipulate the OML managed objects\n"
+ "Object Class\n" NM_OBJCLASS_VTY_HELP
+ "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n")
+{
+ struct gsm_bts *bts;
+ struct oml_node_state *oms;
+ 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;
+ }
+
+ oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+ if (!oms)
+ return CMD_WARNING;
+
+ oms->bts = bts;
+ oms->obj_class = get_string_value(abis_nm_obj_class_names, argv[1]);
+ oms->obj_inst[0] = atoi(argv[2]);
+ oms->obj_inst[1] = atoi(argv[3]);
+ oms->obj_inst[2] = atoi(argv[4]);
+
+ vty->index = oms;
+ vty->node = OML_NODE;
+
+ return CMD_SUCCESS;
+
+}
+
+DEFUN(oml_classnum_inst, oml_classnum_inst_cmd,
+ "bts <0-255> oml class <0-255> instance <0-255> <0-255> <0-255>",
+ "BTS related commands\n" "BTS Number\n"
+ "Manipulate the OML managed objects\n"
+ "Object Class\n" "Object Class\n"
+ "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n")
+{
+ struct gsm_bts *bts;
+ struct oml_node_state *oms;
+ 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;
+ }
+
+ oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+ if (!oms)
+ return CMD_WARNING;
+
+ oms->bts = bts;
+ oms->obj_class = atoi(argv[1]);
+ oms->obj_inst[0] = atoi(argv[2]);
+ oms->obj_inst[1] = atoi(argv[3]);
+ oms->obj_inst[2] = atoi(argv[4]);
+
+ vty->index = oms;
+ vty->node = OML_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(oml_chg_adm_state, oml_chg_adm_state_cmd,
+ "change-adm-state (locked|unlocked|shutdown|null)",
+ "Change the Administrative State\n"
+ "Locked\n" "Unlocked\n" "Shutdown\n" "NULL\n")
+{
+ struct oml_node_state *oms = vty->index;
+ enum abis_nm_adm_state state;
+
+ state = get_string_value(abis_nm_adm_state_names, argv[0]);
+
+ abis_nm_chg_adm_state(oms->bts, oms->obj_class, oms->obj_inst[0],
+ oms->obj_inst[1], oms->obj_inst[2], state);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(oml_opstart, oml_opstart_cmd,
+ "opstart", "Send an OPSTART message to the object")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_nm_opstart(oms->bts, oms->obj_class, oms->obj_inst[0],
+ oms->obj_inst[1], oms->obj_inst[2]);
+
+ return CMD_SUCCESS;
+}
+
+int abis_nm_vty_init(void)
+{
+ install_element(ENABLE_NODE, &oml_class_inst_cmd);
+ install_element(ENABLE_NODE, &oml_classnum_inst_cmd);
+ install_node(&oml_node, dummy_config_write);
+
+ vty_install_default(OML_NODE);
+ install_element(OML_NODE, &oml_chg_adm_state_cmd);
+ install_element(OML_NODE, &oml_opstart_cmd);
+
+ return 0;
+}
diff --git a/src/libbsc/abis_om2000.c b/src/libbsc/abis_om2000.c
new file mode 100644
index 000000000..82a14b269
--- /dev/null
+++ b/src/libbsc/abis_om2000.c
@@ -0,0 +1,2776 @@
+/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface
+ * implemented based on protocol trace analysis, no formal documentation */
+
+/* (C) 2010-2011,2016 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 <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/fsm.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/signal.h>
+#include <osmocom/abis/e1_input.h>
+
+/* FIXME: move to libosmocore */
+struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm,
+ struct osmo_fsm_inst *parent,
+ uint32_t parent_term_event,
+ const char *id)
+{
+ 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);
+ 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);
+
+ return fi;
+}
+
+
+#define OM_ALLOC_SIZE 1024
+#define OM_HEADROOM_SIZE 128
+
+#define OM2K_TIMEOUT 10
+#define TRX_FSM_TIMEOUT 60
+#define BTS_FSM_TIMEOUT 60
+
+/* use following functions from abis_nm.c:
+ * om2k_msgb_alloc()
+ * abis_om2k_sendmsg()
+ */
+
+struct abis_om2k_hdr {
+ struct abis_om_hdr om;
+ uint16_t msg_type;
+ struct abis_om2k_mo mo;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+enum abis_om2k_msgtype {
+ OM2K_MSGT_ABORT_SP_CMD = 0x0000,
+ OM2K_MSGT_ABORT_SP_COMPL = 0x0002,
+ OM2K_MSGT_ALARM_REP_ACK = 0x0004,
+ OM2K_MSGT_ALARM_REP_NACK = 0x0005,
+ OM2K_MSGT_ALARM_REP = 0x0006,
+ OM2K_MSGT_ALARM_STATUS_REQ = 0x0008,
+ OM2K_MSGT_ALARM_STATUS_REQ_ACK = 0x000a,
+ OM2K_MSGT_ALARM_STATUS_REQ_REJ = 0x000b,
+ OM2K_MSGT_ALARM_STATUS_RES_ACK = 0x000c,
+ OM2K_MSGT_ALARM_STATUS_RES_NACK = 0x000d,
+ OM2K_MSGT_ALARM_STATUS_RES = 0x000e,
+ OM2K_MSGT_CAL_TIME_RESP = 0x0010,
+ OM2K_MSGT_CAL_TIME_REJ = 0x0011,
+ OM2K_MSGT_CAL_TIME_REQ = 0x0012,
+
+ OM2K_MSGT_CON_CONF_REQ = 0x0014,
+ OM2K_MSGT_CON_CONF_REQ_ACK = 0x0016,
+ OM2K_MSGT_CON_CONF_REQ_REJ = 0x0017,
+ OM2K_MSGT_CON_CONF_RES_ACK = 0x0018,
+ OM2K_MSGT_CON_CONF_RES_NACK = 0x0019,
+ OM2K_MSGT_CON_CONF_RES = 0x001a,
+
+ OM2K_MSGT_CONNECT_CMD = 0x001c,
+ OM2K_MSGT_CONNECT_COMPL = 0x001e,
+ OM2K_MSGT_CONNECT_REJ = 0x001f,
+
+ OM2K_MSGT_DISABLE_REQ = 0x0028,
+ OM2K_MSGT_DISABLE_REQ_ACK = 0x002a,
+ OM2K_MSGT_DISABLE_REQ_REJ = 0x002b,
+ OM2K_MSGT_DISABLE_RES_ACK = 0x002c,
+ OM2K_MSGT_DISABLE_RES_NACK = 0x002d,
+ OM2K_MSGT_DISABLE_RES = 0x002e,
+ OM2K_MSGT_DISCONNECT_CMD = 0x0030,
+ OM2K_MSGT_DISCONNECT_COMPL = 0x0032,
+ OM2K_MSGT_DISCONNECT_REJ = 0x0033,
+ OM2K_MSGT_ENABLE_REQ = 0x0034,
+ OM2K_MSGT_ENABLE_REQ_ACK = 0x0036,
+ OM2K_MSGT_ENABLE_REQ_REJ = 0x0037,
+ OM2K_MSGT_ENABLE_RES_ACK = 0x0038,
+ OM2K_MSGT_ENABLE_RES_NACK = 0x0039,
+ OM2K_MSGT_ENABLE_RES = 0x003a,
+
+ OM2K_MSGT_FAULT_REP_ACK = 0x0040,
+ OM2K_MSGT_FAULT_REP_NACK = 0x0041,
+ OM2K_MSGT_FAULT_REP = 0x0042,
+
+ OM2K_MSGT_IS_CONF_REQ = 0x0060,
+ OM2K_MSGT_IS_CONF_REQ_ACK = 0x0062,
+ OM2K_MSGT_IS_CONF_REQ_REJ = 0x0063,
+ OM2K_MSGT_IS_CONF_RES_ACK = 0x0064,
+ OM2K_MSGT_IS_CONF_RES_NACK = 0x0065,
+ OM2K_MSGT_IS_CONF_RES = 0x0066,
+
+ OM2K_MSGT_OP_INFO = 0x0074,
+ OM2K_MSGT_OP_INFO_ACK = 0x0076,
+ OM2K_MSGT_OP_INFO_REJ = 0x0077,
+ OM2K_MSGT_RESET_CMD = 0x0078,
+ OM2K_MSGT_RESET_COMPL = 0x007a,
+ OM2K_MSGT_RESET_REJ = 0x007b,
+ OM2K_MSGT_RX_CONF_REQ = 0x007c,
+ OM2K_MSGT_RX_CONF_REQ_ACK = 0x007e,
+ OM2K_MSGT_RX_CONF_REQ_REJ = 0x007f,
+ OM2K_MSGT_RX_CONF_RES_ACK = 0x0080,
+ OM2K_MSGT_RX_CONF_RES_NACK = 0x0081,
+ OM2K_MSGT_RX_CONF_RES = 0x0082,
+ OM2K_MSGT_START_REQ = 0x0084,
+ OM2K_MSGT_START_REQ_ACK = 0x0086,
+ OM2K_MSGT_START_REQ_REJ = 0x0087,
+ OM2K_MSGT_START_RES_ACK = 0x0088,
+ OM2K_MSGT_START_RES_NACK = 0x0089,
+ OM2K_MSGT_START_RES = 0x008a,
+ OM2K_MSGT_STATUS_REQ = 0x008c,
+ OM2K_MSGT_STATUS_RESP = 0x008e,
+ OM2K_MSGT_STATUS_REJ = 0x008f,
+
+ OM2K_MSGT_TEST_REQ = 0x0094,
+ OM2K_MSGT_TEST_REQ_ACK = 0x0096,
+ OM2K_MSGT_TEST_REQ_REJ = 0x0097,
+ OM2K_MSGT_TEST_RES_ACK = 0x0098,
+ OM2K_MSGT_TEST_RES_NACK = 0x0099,
+ OM2K_MSGT_TEST_RES = 0x009a,
+
+ OM2K_MSGT_TF_CONF_REQ = 0x00a0,
+ OM2K_MSGT_TF_CONF_REQ_ACK = 0x00a2,
+ OM2K_MSGT_TF_CONF_REQ_REJ = 0x00a3,
+ OM2K_MSGT_TF_CONF_RES_ACK = 0x00a4,
+ OM2K_MSGT_TF_CONF_RES_NACK = 0x00a5,
+ OM2K_MSGT_TF_CONF_RES = 0x00a6,
+ OM2K_MSGT_TS_CONF_REQ = 0x00a8,
+ OM2K_MSGT_TS_CONF_REQ_ACK = 0x00aa,
+ OM2K_MSGT_TS_CONF_REQ_REJ = 0x00ab,
+ OM2K_MSGT_TS_CONF_RES_ACK = 0x00ac,
+ OM2K_MSGT_TS_CONF_RES_NACK = 0x00ad,
+ OM2K_MSGT_TS_CONF_RES = 0x00ae,
+ OM2K_MSGT_TX_CONF_REQ = 0x00b0,
+ OM2K_MSGT_TX_CONF_REQ_ACK = 0x00b2,
+ OM2K_MSGT_TX_CONF_REQ_REJ = 0x00b3,
+ OM2K_MSGT_TX_CONF_RES_ACK = 0x00b4,
+ OM2K_MSGT_TX_CONF_RES_NACK = 0x00b5,
+ OM2K_MSGT_TX_CONF_RES = 0x00b6,
+
+ OM2K_MSGT_CAPA_REQ = 0x00e8,
+ OM2K_MSGT_CAPA_REQ_ACK = 0x00ea,
+ OM2K_MSGT_CAPA_REQ_REJ = 0x00eb,
+ OM2K_MSGT_CAPA_RES = 0x00ee,
+ OM2K_MSGT_CAPA_RES_ACK = 0x00ec,
+ OM2K_MSGT_CAPA_RES_NACK = 0x00ed,
+
+ OM2K_MSGT_NEGOT_REQ_ACK = 0x0104,
+ OM2K_MSGT_NEGOT_REQ_NACK = 0x0105,
+ OM2K_MSGT_NEGOT_REQ = 0x0106,
+};
+
+enum abis_om2k_dei {
+ OM2K_DEI_ACCORDANCE_IND = 0x00,
+ OM2K_DEI_BCC = 0x06,
+ OM2K_DEI_BS_AG_BKS_RES = 0x07,
+ OM2K_DEI_BSIC = 0x09,
+ OM2K_DEI_BA_PA_MFRMS = 0x0a,
+ OM2K_DEI_CBCH_INDICATOR = 0x0b,
+ OM2K_DEI_CCCH_OPTIONS = 0x0c,
+ OM2K_DEI_CAL_TIME = 0x0d,
+ OM2K_DEI_COMBINATION = 0x0f,
+ OM2K_DEI_CON_CONN_LIST = 0x10,
+ OM2K_DEI_DRX_DEV_MAX = 0x12,
+ OM2K_DEI_END_LIST_NR = 0x13,
+ OM2K_DEI_EXT_COND_MAP_1 = 0x14,
+ OM2K_DEI_EXT_COND_MAP_2 = 0x15,
+ OM2K_DEI_FILLING_MARKER = 0x1c,
+ OM2K_DEI_FN_OFFSET = 0x1d,
+ OM2K_DEI_FREQ_LIST = 0x1e,
+ OM2K_DEI_FREQ_SPEC_RX = 0x1f,
+ OM2K_DEI_FREQ_SPEC_TX = 0x20,
+ OM2K_DEI_HSN = 0x21,
+ OM2K_DEI_ICM_INDICATOR = 0x22,
+ OM2K_DEI_INT_FAULT_MAP_1A = 0x23,
+ OM2K_DEI_INT_FAULT_MAP_1B = 0x24,
+ OM2K_DEI_INT_FAULT_MAP_2A = 0x25,
+ OM2K_DEI_INT_FAULT_MAP_2A_EXT = 0x26,
+ OM2K_DEI_IS_CONN_LIST = 0x27,
+ OM2K_DEI_LIST_NR = 0x28,
+ OM2K_DEI_LOCAL_ACCESS = 0x2a,
+ OM2K_DEI_MAIO = 0x2b,
+ OM2K_DEI_MO_STATE = 0x2c,
+ OM2K_DEI_NY1 = 0x2d,
+ OM2K_DEI_OP_INFO = 0x2e,
+ OM2K_DEI_POWER = 0x2f,
+ OM2K_DEI_REASON_CODE = 0x32,
+ OM2K_DEI_RX_DIVERSITY = 0x33,
+ OM2K_DEI_REPL_UNIT_MAP = 0x34,
+ OM2K_DEI_RESULT_CODE = 0x35,
+ OM2K_DEI_T3105 = 0x38,
+ OM2K_DEI_TF_MODE = 0x3a,
+ OM2K_DEI_TS_NR = 0x3c,
+ OM2K_DEI_TSC = 0x3d,
+ OM2K_DEI_BTS_VERSION = 0x40,
+ OM2K_DEI_OML_IWD_VERSION = 0x41,
+ OM2K_DEI_RSL_IWD_VERSION = 0x42,
+ OM2K_DEI_OML_FUNC_MAP_1 = 0x43,
+ OM2K_DEI_OML_FUNC_MAP_2 = 0x44,
+ OM2K_DEI_RSL_FUNC_MAP_1 = 0x45,
+ OM2K_DEI_RSL_FUNC_MAP_2 = 0x46,
+ OM2K_DEI_EXT_RANGE = 0x47,
+ OM2K_DEI_REQ_IND = 0x48,
+ OM2K_DEI_REPL_UNIT_MAP_EXT = 0x50,
+ OM2K_DEI_ICM_BOUND_PARAMS = 0x74,
+ OM2K_DEI_LSC = 0x79,
+ OM2K_DEI_LSC_FILT_TIME = 0x7a,
+ OM2K_DEI_CALL_SUPV_TIME = 0x7b,
+ OM2K_DEI_ICM_CHAN_RATE = 0x7e,
+ OM2K_DEI_HW_INFO_SIG = 0x84,
+ OM2K_DEI_TF_SYNC_SRC = 0x86,
+ OM2K_DEI_TTA = 0x87,
+ OM2K_DEI_CAPA_SIG = 0x8a,
+ OM2K_DEI_NEGOT_REC1 = 0x90,
+ OM2K_DEI_NEGOT_REC2 = 0x91,
+ OM2K_DEI_ENCR_ALG = 0x92,
+ OM2K_DEI_INTERF_REJ_COMB = 0x94,
+ OM2K_DEI_FS_OFFSET = 0x98,
+ OM2K_DEI_EXT_COND_MAP_2_EXT = 0x9c,
+ OM2K_DEI_TSS_MO_STATE = 0x9d,
+};
+
+const struct tlv_definition om2k_att_tlvdef = {
+ .def = {
+ [OM2K_DEI_ACCORDANCE_IND] = { TLV_TYPE_TV },
+ [OM2K_DEI_BCC] = { TLV_TYPE_TV },
+ [OM2K_DEI_BS_AG_BKS_RES] = { TLV_TYPE_TV },
+ [OM2K_DEI_BSIC] = { TLV_TYPE_TV },
+ [OM2K_DEI_BA_PA_MFRMS] = { TLV_TYPE_TV },
+ [OM2K_DEI_CBCH_INDICATOR] = { TLV_TYPE_TV },
+ [OM2K_DEI_INT_FAULT_MAP_1A] = { TLV_TYPE_FIXED, 6 },
+ [OM2K_DEI_INT_FAULT_MAP_1B] = { TLV_TYPE_FIXED, 6 },
+ [OM2K_DEI_INT_FAULT_MAP_2A] = { TLV_TYPE_FIXED, 6 },
+ [OM2K_DEI_INT_FAULT_MAP_2A_EXT]={ TLV_TYPE_FIXED, 6 },
+ [OM2K_DEI_CCCH_OPTIONS] = { TLV_TYPE_TV },
+ [OM2K_DEI_CAL_TIME] = { TLV_TYPE_FIXED, 6 },
+ [OM2K_DEI_COMBINATION] = { TLV_TYPE_TV },
+ [OM2K_DEI_CON_CONN_LIST] = { TLV_TYPE_TLV },
+ [OM2K_DEI_DRX_DEV_MAX] = { TLV_TYPE_TV },
+ [OM2K_DEI_END_LIST_NR] = { TLV_TYPE_TV },
+ [OM2K_DEI_EXT_COND_MAP_1] = { TLV_TYPE_FIXED, 2 },
+ [OM2K_DEI_EXT_COND_MAP_2] = { TLV_TYPE_FIXED, 2 },
+ [OM2K_DEI_FILLING_MARKER] = { TLV_TYPE_TV },
+ [OM2K_DEI_FN_OFFSET] = { TLV_TYPE_FIXED, 2 },
+ [OM2K_DEI_FREQ_LIST] = { TLV_TYPE_TLV },
+ [OM2K_DEI_FREQ_SPEC_RX] = { TLV_TYPE_FIXED, 2 },
+ [OM2K_DEI_FREQ_SPEC_TX] = { TLV_TYPE_FIXED, 2 },
+ [OM2K_DEI_HSN] = { TLV_TYPE_TV },
+ [OM2K_DEI_ICM_INDICATOR] = { TLV_TYPE_TV },
+ [OM2K_DEI_IS_CONN_LIST] = { TLV_TYPE_TLV },
+ [OM2K_DEI_LIST_NR] = { TLV_TYPE_TV },
+ [OM2K_DEI_LOCAL_ACCESS] = { TLV_TYPE_TV },
+ [OM2K_DEI_MAIO] = { TLV_TYPE_TV },
+ [OM2K_DEI_MO_STATE] = { TLV_TYPE_TV },
+ [OM2K_DEI_NY1] = { TLV_TYPE_TV },
+ [OM2K_DEI_OP_INFO] = { TLV_TYPE_TV },
+ [OM2K_DEI_POWER] = { TLV_TYPE_TV },
+ [OM2K_DEI_REASON_CODE] = { TLV_TYPE_TV },
+ [OM2K_DEI_RX_DIVERSITY] = { TLV_TYPE_TV },
+ [OM2K_DEI_RESULT_CODE] = { TLV_TYPE_TV },
+ [OM2K_DEI_T3105] = { TLV_TYPE_TV },
+ [OM2K_DEI_TF_MODE] = { TLV_TYPE_TV },
+ [OM2K_DEI_TS_NR] = { TLV_TYPE_TV },
+ [OM2K_DEI_TSC] = { TLV_TYPE_TV },
+ [OM2K_DEI_BTS_VERSION] = { TLV_TYPE_FIXED, 12 },
+ [OM2K_DEI_OML_IWD_VERSION] = { TLV_TYPE_FIXED, 6 },
+ [OM2K_DEI_RSL_IWD_VERSION] = { TLV_TYPE_FIXED, 6 },
+ [OM2K_DEI_OML_FUNC_MAP_1] = { TLV_TYPE_TLV },
+ [OM2K_DEI_OML_FUNC_MAP_2] = { TLV_TYPE_TLV },
+ [OM2K_DEI_RSL_FUNC_MAP_1] = { TLV_TYPE_TLV },
+ [OM2K_DEI_RSL_FUNC_MAP_2] = { TLV_TYPE_TLV },
+ [OM2K_DEI_EXT_RANGE] = { TLV_TYPE_TV },
+ [OM2K_DEI_REQ_IND] = { TLV_TYPE_TV },
+ [OM2K_DEI_REPL_UNIT_MAP] = { TLV_TYPE_FIXED, 6 },
+ [OM2K_DEI_REPL_UNIT_MAP_EXT] = {TLV_TYPE_FIXED, 6},
+ [OM2K_DEI_ICM_BOUND_PARAMS] = { TLV_TYPE_FIXED, 5 },
+ [OM2K_DEI_LSC] = { TLV_TYPE_TV },
+ [OM2K_DEI_LSC_FILT_TIME] = { TLV_TYPE_TV },
+ [OM2K_DEI_CALL_SUPV_TIME] = { TLV_TYPE_TV },
+ [OM2K_DEI_ICM_CHAN_RATE] = { TLV_TYPE_TV },
+ [OM2K_DEI_HW_INFO_SIG] = { TLV_TYPE_FIXED, 2 },
+ [OM2K_DEI_TF_SYNC_SRC] = { TLV_TYPE_TV },
+ [OM2K_DEI_TTA] = { TLV_TYPE_TV },
+ [OM2K_DEI_CAPA_SIG] = { TLV_TYPE_FIXED, 2 },
+ [OM2K_DEI_NEGOT_REC1] = { TLV_TYPE_TLV },
+ [OM2K_DEI_NEGOT_REC2] = { TLV_TYPE_TLV },
+ [OM2K_DEI_ENCR_ALG] = { TLV_TYPE_TV },
+ [OM2K_DEI_INTERF_REJ_COMB] = { TLV_TYPE_TV },
+ [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 },
+ },
+};
+
+static const struct value_string om2k_msgcode_vals[] = {
+ { 0x0000, "Abort SP Command" },
+ { 0x0002, "Abort SP Complete" },
+ { 0x0004, "Alarm Report ACK" },
+ { 0x0005, "Alarm Report NACK" },
+ { 0x0006, "Alarm Report" },
+ { 0x0008, "Alarm Status Request" },
+ { 0x000a, "Alarm Status Request Accept" },
+ { 0x000b, "Alarm Status Request Reject" },
+ { 0x000c, "Alarm Status Result ACK" },
+ { 0x000d, "Alarm Status Result NACK" },
+ { 0x000e, "Alarm Status Result" },
+ { 0x0010, "Calendar Time Response" },
+ { 0x0011, "Calendar Time Reject" },
+ { 0x0012, "Calendar Time Request" },
+ { 0x0014, "CON Configuration Request" },
+ { 0x0016, "CON Configuration Request Accept" },
+ { 0x0017, "CON Configuration Request Reject" },
+ { 0x0018, "CON Configuration Result ACK" },
+ { 0x0019, "CON Configuration Result NACK" },
+ { 0x001a, "CON Configuration Result" },
+ { 0x001c, "Connect Command" },
+ { 0x001e, "Connect Complete" },
+ { 0x001f, "Connect Reject" },
+ { 0x0028, "Disable Request" },
+ { 0x002a, "Disable Request Accept" },
+ { 0x002b, "Disable Request Reject" },
+ { 0x002c, "Disable Result ACK" },
+ { 0x002d, "Disable Result NACK" },
+ { 0x002e, "Disable Result" },
+ { 0x0030, "Disconnect Command" },
+ { 0x0032, "Disconnect Complete" },
+ { 0x0033, "Disconnect Reject" },
+ { 0x0034, "Enable Request" },
+ { 0x0036, "Enable Request Accept" },
+ { 0x0037, "Enable Request Reject" },
+ { 0x0038, "Enable Result ACK" },
+ { 0x0039, "Enable Result NACK" },
+ { 0x003a, "Enable Result" },
+ { 0x003c, "Escape Downlink Normal" },
+ { 0x003d, "Escape Downlink NACK" },
+ { 0x003e, "Escape Uplink Normal" },
+ { 0x003f, "Escape Uplink NACK" },
+ { 0x0040, "Fault Report ACK" },
+ { 0x0041, "Fault Report NACK" },
+ { 0x0042, "Fault Report" },
+ { 0x0044, "File Package End Command" },
+ { 0x0046, "File Package End Result" },
+ { 0x0047, "File Package End Reject" },
+ { 0x0048, "File Relation Request" },
+ { 0x004a, "File Relation Response" },
+ { 0x004b, "File Relation Request Reject" },
+ { 0x004c, "File Segment Transfer" },
+ { 0x004e, "File Segment Transfer Complete" },
+ { 0x004f, "File Segment Transfer Reject" },
+ { 0x0050, "HW Information Request" },
+ { 0x0052, "HW Information Request Accept" },
+ { 0x0053, "HW Information Request Reject" },
+ { 0x0054, "HW Information Result ACK" },
+ { 0x0055, "HW Information Result NACK" },
+ { 0x0056, "HW Information Result" },
+ { 0x0060, "IS Configuration Request" },
+ { 0x0062, "IS Configuration Request Accept" },
+ { 0x0063, "IS Configuration Request Reject" },
+ { 0x0064, "IS Configuration Result ACK" },
+ { 0x0065, "IS Configuration Result NACK" },
+ { 0x0066, "IS Configuration Result" },
+ { 0x0068, "Load Data End" },
+ { 0x006a, "Load Data End Result" },
+ { 0x006b, "Load Data End Reject" },
+ { 0x006c, "Load Data Init" },
+ { 0x006e, "Load Data Init Accept" },
+ { 0x006f, "Load Data Init Reject" },
+ { 0x0070, "Loop Control Command" },
+ { 0x0072, "Loop Control Complete" },
+ { 0x0073, "Loop Control Reject" },
+ { 0x0074, "Operational Information" },
+ { 0x0076, "Operational Information Accept" },
+ { 0x0077, "Operational Information Reject" },
+ { 0x0078, "Reset Command" },
+ { 0x007a, "Reset Complete" },
+ { 0x007b, "Reset Reject" },
+ { 0x007c, "RX Configuration Request" },
+ { 0x007e, "RX Configuration Request Accept" },
+ { 0x007f, "RX Configuration Request Reject" },
+ { 0x0080, "RX Configuration Result ACK" },
+ { 0x0081, "RX Configuration Result NACK" },
+ { 0x0082, "RX Configuration Result" },
+ { 0x0084, "Start Request" },
+ { 0x0086, "Start Request Accept" },
+ { 0x0087, "Start Request Reject" },
+ { 0x0088, "Start Result ACK" },
+ { 0x0089, "Start Result NACK" },
+ { 0x008a, "Start Result" },
+ { 0x008c, "Status Request" },
+ { 0x008e, "Status Response" },
+ { 0x008f, "Status Reject" },
+ { 0x0094, "Test Request" },
+ { 0x0096, "Test Request Accept" },
+ { 0x0097, "Test Request Reject" },
+ { 0x0098, "Test Result ACK" },
+ { 0x0099, "Test Result NACK" },
+ { 0x009a, "Test Result" },
+ { 0x00a0, "TF Configuration Request" },
+ { 0x00a2, "TF Configuration Request Accept" },
+ { 0x00a3, "TF Configuration Request Reject" },
+ { 0x00a4, "TF Configuration Result ACK" },
+ { 0x00a5, "TF Configuration Result NACK" },
+ { 0x00a6, "TF Configuration Result" },
+ { 0x00a8, "TS Configuration Request" },
+ { 0x00aa, "TS Configuration Request Accept" },
+ { 0x00ab, "TS Configuration Request Reject" },
+ { 0x00ac, "TS Configuration Result ACK" },
+ { 0x00ad, "TS Configuration Result NACK" },
+ { 0x00ae, "TS Configuration Result" },
+ { 0x00b0, "TX Configuration Request" },
+ { 0x00b2, "TX Configuration Request Accept" },
+ { 0x00b3, "TX Configuration Request Reject" },
+ { 0x00b4, "TX Configuration Result ACK" },
+ { 0x00b5, "TX Configuration Result NACK" },
+ { 0x00b6, "TX Configuration Result" },
+ { 0x00bc, "DIP Alarm Report ACK" },
+ { 0x00bd, "DIP Alarm Report NACK" },
+ { 0x00be, "DIP Alarm Report" },
+ { 0x00c0, "DIP Alarm Status Request" },
+ { 0x00c2, "DIP Alarm Status Response" },
+ { 0x00c3, "DIP Alarm Status Reject" },
+ { 0x00c4, "DIP Quality Report I ACK" },
+ { 0x00c5, "DIP Quality Report I NACK" },
+ { 0x00c6, "DIP Quality Report I" },
+ { 0x00c8, "DIP Quality Report II ACK" },
+ { 0x00c9, "DIP Quality Report II NACK" },
+ { 0x00ca, "DIP Quality Report II" },
+ { 0x00dc, "DP Configuration Request" },
+ { 0x00de, "DP Configuration Request Accept" },
+ { 0x00df, "DP Configuration Request Reject" },
+ { 0x00e0, "DP Configuration Result ACK" },
+ { 0x00e1, "DP Configuration Result NACK" },
+ { 0x00e2, "DP Configuration Result" },
+ { 0x00e4, "Capabilities HW Info Report ACK" },
+ { 0x00e5, "Capabilities HW Info Report NACK" },
+ { 0x00e6, "Capabilities HW Info Report" },
+ { 0x00e8, "Capabilities Request" },
+ { 0x00ea, "Capabilities Request Accept" },
+ { 0x00eb, "Capabilities Request Reject" },
+ { 0x00ec, "Capabilities Result ACK" },
+ { 0x00ed, "Capabilities Result NACK" },
+ { 0x00ee, "Capabilities Result" },
+ { 0x00f0, "FM Configuration Request" },
+ { 0x00f2, "FM Configuration Request Accept" },
+ { 0x00f3, "FM Configuration Request Reject" },
+ { 0x00f4, "FM Configuration Result ACK" },
+ { 0x00f5, "FM Configuration Result NACK" },
+ { 0x00f6, "FM Configuration Result" },
+ { 0x00f8, "FM Report Request" },
+ { 0x00fa, "FM Report Response" },
+ { 0x00fb, "FM Report Reject" },
+ { 0x00fc, "FM Start Command" },
+ { 0x00fe, "FM Start Complete" },
+ { 0x00ff, "FM Start Reject" },
+ { 0x0100, "FM Stop Command" },
+ { 0x0102, "FM Stop Complete" },
+ { 0x0103, "FM Stop Reject" },
+ { 0x0104, "Negotiation Request ACK" },
+ { 0x0105, "Negotiation Request NACK" },
+ { 0x0106, "Negotiation Request" },
+ { 0x0108, "BTS Initiated Request ACK" },
+ { 0x0109, "BTS Initiated Request NACK" },
+ { 0x010a, "BTS Initiated Request" },
+ { 0x010c, "Radio Channels Release Command" },
+ { 0x010e, "Radio Channels Release Complete" },
+ { 0x010f, "Radio Channels Release Reject" },
+ { 0x0118, "Feature Control Command" },
+ { 0x011a, "Feature Control Complete" },
+ { 0x011b, "Feature Control Reject" },
+
+ { 0, NULL }
+};
+
+/* TS 12.21 Section 9.4: Attributes */
+static const struct value_string om2k_attr_vals[] = {
+ { 0x00, "Accordance indication" },
+ { 0x01, "Alarm Id" },
+ { 0x02, "Alarm Data" },
+ { 0x03, "Alarm Severity" },
+ { 0x04, "Alarm Status" },
+ { 0x05, "Alarm Status Type" },
+ { 0x06, "BCC" },
+ { 0x07, "BS_AG_BKS_RES" },
+ { 0x09, "BSIC" },
+ { 0x0a, "BA_PA_MFRMS" },
+ { 0x0b, "CBCH Indicator" },
+ { 0x0c, "CCCH Options" },
+ { 0x0d, "Calendar Time" },
+ { 0x0f, "Channel Combination" },
+ { 0x10, "CON Connection List" },
+ { 0x11, "Data End Indication" },
+ { 0x12, "DRX_DEV_MAX" },
+ { 0x13, "End List Number" },
+ { 0x14, "External Condition Map Class 1" },
+ { 0x15, "External Condition Map Class 2" },
+ { 0x16, "File Relation Indication" },
+ { 0x17, "File Revision" },
+ { 0x18, "File Segment Data" },
+ { 0x19, "File Segment Length" },
+ { 0x1a, "File Segment Sequence Number" },
+ { 0x1b, "File Size" },
+ { 0x1c, "Filling Marker" },
+ { 0x1d, "FN Offset" },
+ { 0x1e, "Frequency List" },
+ { 0x1f, "Frequency Specifier RX" },
+ { 0x20, "Frequency Specifier TX" },
+ { 0x21, "HSN" },
+ { 0x22, "ICM Indicator" },
+ { 0x23, "Internal Fault Map Class 1A" },
+ { 0x24, "Internal Fault Map Class 1B" },
+ { 0x25, "Internal Fault Map Class 2A" },
+ { 0x26, "Internal Fault Map Class 2A Extension" },
+ { 0x27, "IS Connection List" },
+ { 0x28, "List Number" },
+ { 0x29, "File Package State Indication" },
+ { 0x2a, "Local Access State" },
+ { 0x2b, "MAIO" },
+ { 0x2c, "MO State" },
+ { 0x2d, "Ny1" },
+ { 0x2e, "Operational Information" },
+ { 0x2f, "Power" },
+ { 0x30, "RU Position Data" },
+ { 0x31, "Protocol Error" },
+ { 0x32, "Reason Code" },
+ { 0x33, "Receiver Diversity" },
+ { 0x34, "Replacement Unit Map" },
+ { 0x35, "Result Code" },
+ { 0x36, "RU Revision Data" },
+ { 0x38, "T3105" },
+ { 0x39, "Test Loop Setting" },
+ { 0x3a, "TF Mode" },
+ { 0x3b, "TF Compensation Value" },
+ { 0x3c, "Time Slot Number" },
+ { 0x3d, "TSC" },
+ { 0x3e, "RU Logical Id" },
+ { 0x3f, "RU Serial Number Data" },
+ { 0x40, "BTS Version" },
+ { 0x41, "OML IWD Version" },
+ { 0x42, "RWL IWD Version" },
+ { 0x43, "OML Function Map 1" },
+ { 0x44, "OML Function Map 2" },
+ { 0x45, "RSL Function Map 1" },
+ { 0x46, "RSL Function Map 2" },
+ { 0x47, "Extended Range Indicator" },
+ { 0x48, "Request Indicators" },
+ { 0x49, "DIP Alarm Condition Map" },
+ { 0x4a, "ES Incoming" },
+ { 0x4b, "ES Outgoing" },
+ { 0x4e, "SES Incoming" },
+ { 0x4f, "SES Outgoing" },
+ { 0x50, "Replacement Unit Map Extension" },
+ { 0x52, "UAS Incoming" },
+ { 0x53, "UAS Outgoing" },
+ { 0x58, "DF Incoming" },
+ { 0x5a, "DF Outgoing" },
+ { 0x5c, "SF" },
+ { 0x60, "S Bits Setting" },
+ { 0x61, "CRC-4 Use Option" },
+ { 0x62, "T Parameter" },
+ { 0x63, "N Parameter" },
+ { 0x64, "N1 Parameter" },
+ { 0x65, "N3 Parameter" },
+ { 0x66, "N4 Parameter" },
+ { 0x67, "P Parameter" },
+ { 0x68, "Q Parameter" },
+ { 0x69, "BI_Q1" },
+ { 0x6a, "BI_Q2" },
+ { 0x74, "ICM Boundary Parameters" },
+ { 0x77, "AFT" },
+ { 0x78, "AFT RAI" },
+ { 0x79, "Link Supervision Control" },
+ { 0x7a, "Link Supervision Filtering Time" },
+ { 0x7b, "Call Supervision Time" },
+ { 0x7c, "Interval Length UAS Incoming" },
+ { 0x7d, "Interval Length UAS Outgoing" },
+ { 0x7e, "ICM Channel Rate" },
+ { 0x7f, "Attribute Identifier" },
+ { 0x80, "FM Frequency List" },
+ { 0x81, "FM Frequency Report" },
+ { 0x82, "FM Percentile" },
+ { 0x83, "FM Clear Indication" },
+ { 0x84, "HW Info Signature" },
+ { 0x85, "MO Record" },
+ { 0x86, "TF Synchronisation Source" },
+ { 0x87, "TTA" },
+ { 0x88, "End Segment Number" },
+ { 0x89, "Segment Number" },
+ { 0x8a, "Capabilities Signature" },
+ { 0x8c, "File Relation List" },
+ { 0x90, "Negotiation Record I" },
+ { 0x91, "Negotiation Record II" },
+ { 0x92, "Encryption Algorithm" },
+ { 0x94, "Interference Rejection Combining" },
+ { 0x95, "Dedication Information" },
+ { 0x97, "Feature Code" },
+ { 0x98, "FS Offset" },
+ { 0x99, "ESB Timeslot" },
+ { 0x9a, "Master TG Instance" },
+ { 0x9b, "Master TX Chain Delay" },
+ { 0x9c, "External Condition Class 2 Extension" },
+ { 0x9d, "TSs MO State" },
+ { 0, NULL }
+};
+
+const struct value_string om2k_mo_class_short_vals[] = {
+ { 0x01, "TRXC" },
+ { 0x03, "TS" },
+ { 0x04, "TF" },
+ { 0x05, "IS" },
+ { 0x06, "CON" },
+ { 0x07, "DP" },
+ { 0x0a, "CF" },
+ { 0x0b, "TX" },
+ { 0x0c, "RX" },
+ { 0, NULL }
+};
+
+const struct value_string om2k_result_strings[] = {
+ { 0x02, "Wrong state or out of sequence" },
+ { 0x03, "File error" },
+ { 0x04, "Fault, unspecified" },
+ { 0x05, "Tuning fault" },
+ { 0x06, "Protocol error" },
+ { 0x07, "MO not connected" },
+ { 0x08, "Parameter error" },
+ { 0x09, "Optional function not supported" },
+ { 0x0a, "Local access state LOCALLY DISCONNECTED" },
+ { 0, NULL }
+};
+
+const struct value_string om2k_accordance_strings[] = {
+ { 0x00, "Data according to request" },
+ { 0x01, "Data not according to request" },
+ { 0x02, "Inconsistent MO data" },
+ { 0x03, "Capability constraint violation" },
+ { 0, NULL }
+};
+
+const struct value_string om2k_mostate_vals[] = {
+ { 0x00, "RESET" },
+ { 0x01, "STARTED" },
+ { 0x02, "ENABLED" },
+ { 0x03, "DISABLED" },
+ { 0, NULL }
+};
+
+/* entire decoded OM2K message (header + parsed TLV) */
+struct om2k_decoded_msg {
+ struct abis_om2k_hdr o2h;
+ uint16_t msg_type;
+ struct tlv_parsed tp;
+};
+
+/* resolve the OM2000 Managed Object by BTS + MO Address */
+static struct om2k_mo *
+get_om2k_mo(struct gsm_bts *bts, const struct abis_om2k_mo *abis_mo)
+{
+ struct om2k_mo *mo = NULL;
+ struct gsm_bts_trx *trx;
+
+ switch (abis_mo->class) {
+ case OM2K_MO_CLS_CF:
+ mo = &bts->rbs2000.cf.om2k_mo;
+ break;
+ case OM2K_MO_CLS_CON:
+ mo = &bts->rbs2000.con.om2k_mo;
+ break;
+ case OM2K_MO_CLS_IS:
+ mo = &bts->rbs2000.is.om2k_mo;
+ break;
+ case OM2K_MO_CLS_TF:
+ mo = &bts->rbs2000.tf.om2k_mo;
+ break;
+
+ case OM2K_MO_CLS_TRXC:
+ trx = gsm_bts_trx_num(bts, abis_mo->inst);
+ if (!trx)
+ return NULL;
+ mo = &trx->rbs2000.trxc.om2k_mo;
+ break;
+ case OM2K_MO_CLS_TX:
+ trx = gsm_bts_trx_num(bts, abis_mo->inst);
+ if (!trx)
+ return NULL;
+ mo = &trx->rbs2000.tx.om2k_mo;
+ break;
+ case OM2K_MO_CLS_RX:
+ trx = gsm_bts_trx_num(bts, abis_mo->inst);
+ if (!trx)
+ return NULL;
+ mo = &trx->rbs2000.rx.om2k_mo;
+ break;
+ case OM2K_MO_CLS_TS:
+ trx = gsm_bts_trx_num(bts, abis_mo->assoc_so);
+ if (!trx)
+ return NULL;
+ if (abis_mo->inst >= ARRAY_SIZE(trx->ts))
+ return NULL;
+ mo = &trx->ts[abis_mo->inst].rbs2000.om2k_mo;
+ break;
+ default:
+ return NULL;
+ };
+
+ return mo;
+}
+
+static struct msgb *om2k_msgb_alloc(void)
+{
+ return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+ "OM2000");
+}
+
+static int abis_om2k_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len)
+{
+ return tlv_parse(tp, &om2k_att_tlvdef, buf, len, 0, 0);
+}
+
+static int abis_om2k_msg_tlv_parse(struct tlv_parsed *tp, struct abis_om2k_hdr *oh)
+{
+ return abis_om2k_tlv_parse(tp, oh->data, oh->om.length - 6);
+}
+
+/* decode/parse the message */
+static int om2k_decode_msg(struct om2k_decoded_msg *odm, struct msgb *msg)
+{
+ struct abis_om2k_hdr *o2h = msgb_l2(msg);
+ odm->msg_type = ntohs(o2h->msg_type);
+ odm->o2h = *o2h;
+ return abis_om2k_msg_tlv_parse(&odm->tp, o2h);
+}
+
+static char *om2k_mo_name(const struct abis_om2k_mo *mo)
+{
+ static char mo_buf[64];
+
+ memset(mo_buf, 0, sizeof(mo_buf));
+ snprintf(mo_buf, sizeof(mo_buf), "%s/%02x/%02x/%02x",
+ get_value_string(om2k_mo_class_short_vals, mo->class),
+ mo->bts, mo->assoc_so, mo->inst);
+ return mo_buf;
+}
+
+/* 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)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_nm_state *nm_state = NULL;
+
+ switch (mo->class) {
+ case OM2K_MO_CLS_TRXC:
+ trx = gsm_bts_trx_num(bts, mo->inst);
+ if (!trx)
+ return NULL;
+ nm_state = &trx->mo.nm_state;
+ break;
+ case OM2K_MO_CLS_TS:
+ trx = gsm_bts_trx_num(bts, mo->assoc_so);
+ if (!trx)
+ return NULL;
+ if (mo->inst >= ARRAY_SIZE(trx->ts))
+ return NULL;
+ nm_state = &trx->ts[mo->inst].mo.nm_state;
+ break;
+ case OM2K_MO_CLS_TF:
+ nm_state = &bts->rbs2000.tf.mo.nm_state;
+ break;
+ case OM2K_MO_CLS_IS:
+ nm_state = &bts->rbs2000.is.mo.nm_state;
+ break;
+ case OM2K_MO_CLS_CON:
+ nm_state = &bts->rbs2000.con.mo.nm_state;
+ break;
+ case OM2K_MO_CLS_DP:
+ nm_state = &bts->rbs2000.con.mo.nm_state;
+ break;
+ case OM2K_MO_CLS_CF:
+ nm_state = &bts->mo.nm_state;
+ break;
+ case OM2K_MO_CLS_TX:
+ trx = gsm_bts_trx_num(bts, mo->inst);
+ if (!trx)
+ return NULL;
+ /* FIXME */
+ break;
+ case OM2K_MO_CLS_RX:
+ trx = gsm_bts_trx_num(bts, mo->inst);
+ if (!trx)
+ return NULL;
+ /* FIXME */
+ break;
+ }
+
+ return nm_state;
+}
+
+static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo)
+{
+ struct gsm_bts_trx *trx;
+
+ switch (mo->class) {
+ case OM2K_MO_CLS_TX:
+ case OM2K_MO_CLS_RX:
+ case OM2K_MO_CLS_TRXC:
+ return gsm_bts_trx_num(bts, mo->inst);
+ case OM2K_MO_CLS_TS:
+ trx = gsm_bts_trx_num(bts, mo->assoc_so);
+ if (!trx)
+ return NULL;
+ if (mo->inst >= ARRAY_SIZE(trx->ts))
+ return NULL;
+ return &trx->ts[mo->inst];
+ case OM2K_MO_CLS_TF:
+ case OM2K_MO_CLS_IS:
+ case OM2K_MO_CLS_CON:
+ case OM2K_MO_CLS_DP:
+ case OM2K_MO_CLS_CF:
+ return bts;
+ }
+
+ return NULL;
+}
+
+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;
+ /* NOTICE: 12.21 Availability state values != OM2000 */
+ new_state.availability = mo_state;
+
+ memset(&nsd, 0, sizeof(nsd));
+
+ nsd.bts = bts;
+ nsd.obj = mo2obj(bts, mo);
+ nsd.old_state = nm_state;
+ nsd.new_state = &new_state;
+ nsd.om2k_mo = mo;
+
+ osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd);
+
+ nm_state->availability = new_state.availability;
+}
+
+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 gsm_nm_state new_state;
+
+ if (!nm_state)
+ return;
+
+ new_state = *nm_state;
+ switch (op_state) {
+ case 1:
+ new_state.operational = NM_OPSTATE_ENABLED;
+ break;
+ case 0:
+ new_state.operational = NM_OPSTATE_DISABLED;
+ break;
+ default:
+ new_state.operational = NM_OPSTATE_NULL;
+ break;
+ }
+
+ nm_state->operational = new_state.operational;
+}
+
+static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om2k_hdr *o2h;
+ struct gsm_bts_trx *trx;
+
+ msg->l2h = msg->data;
+ o2h = (struct abis_om2k_hdr *) msg->l2h;
+
+ /* Compute the length in the OML header */
+ o2h->om.length = 6 + msgb_l2len(msg)-sizeof(*o2h);
+
+ switch (o2h->mo.class) {
+ case OM2K_MO_CLS_TRXC:
+ case OM2K_MO_CLS_TX:
+ case OM2K_MO_CLS_RX:
+ /* Route through per-TRX OML Link to the appropriate TRX */
+ trx = gsm_bts_trx_by_nr(bts, o2h->mo.inst);
+ if (!trx) {
+ LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to "
+ "non-existing TRX\n", om2k_mo_name(&o2h->mo));
+ return -ENODEV;
+ }
+ msg->dst = trx->oml_link;
+ break;
+ case OM2K_MO_CLS_TS:
+ /* Route through per-TRX OML Link to the appropriate TRX */
+ trx = gsm_bts_trx_by_nr(bts, o2h->mo.assoc_so);
+ if (!trx) {
+ LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to "
+ "non-existing TRX\n", om2k_mo_name(&o2h->mo));
+ return -ENODEV;
+ }
+ msg->dst = trx->oml_link;
+ break;
+ default:
+ /* Route through the IXU/DXU OML Link */
+ msg->dst = bts->oml_link;
+ break;
+ }
+
+ 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)
+{
+ o2h->om.mdisc = ABIS_OM_MDISC_FOM;
+ o2h->om.placement = ABIS_OM_PLACEMENT_ONLY;
+ o2h->om.sequence = 0;
+ /* We fill o2h->om.length later during om2k_sendmsg() */
+ o2h->msg_type = htons(msg_type);
+ memcpy(&o2h->mo, mo, sizeof(o2h->mo));
+}
+
+static int abis_om2k_cal_time_resp(struct gsm_bts *bts)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+ time_t tm_t;
+ 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);
+
+ tm_t = time(NULL);
+ tm = localtime(&tm_t);
+
+ msgb_put_u8(msg, OM2K_DEI_CAL_TIME);
+ msgb_put_u8(msg, tm->tm_year % 100);
+ msgb_put_u8(msg, tm->tm_mon + 1);
+ msgb_put_u8(msg, tm->tm_mday);
+ msgb_put_u8(msg, tm->tm_hour);
+ msgb_put_u8(msg, tm->tm_min);
+ msgb_put_u8(msg, tm->tm_sec);
+
+ return abis_om2k_sendmsg(bts, msg);
+}
+
+static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+ uint8_t msg_type)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+
+ o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+ fill_om2k_hdr(o2k, mo, msg_type);
+
+ DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+ get_value_string(om2k_msgcode_vals, msg_type));
+
+ return abis_om2k_sendmsg(bts, msg);
+}
+
+int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+ return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_RESET_CMD);
+}
+
+int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+ return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_START_REQ);
+}
+
+int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+ return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_STATUS_REQ);
+}
+
+int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+ return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CONNECT_CMD);
+}
+
+int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+ return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISCONNECT_CMD);
+}
+
+int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+ return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_TEST_REQ);
+}
+
+int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+ return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_ENABLE_REQ);
+}
+
+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)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+
+ o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+ fill_om2k_hdr(o2k, mo, OM2K_MSGT_OP_INFO);
+
+ msgb_tv_put(msg, OM2K_DEI_OP_INFO, operational);
+
+ DEBUGP(DNM, "Tx MO=%s %s\n", 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 */
+ update_op_state(bts, mo, operational);
+
+ return abis_om2k_sendmsg(bts, msg);
+}
+
+int abis_om2k_tx_cap_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo)
+{
+ return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CAPA_REQ);
+}
+
+static void om2k_fill_is_conn_grp(struct om2k_is_conn_grp *grp, uint16_t icp1,
+ uint16_t icp2, uint8_t cont_idx)
+{
+ grp->icp1 = htons(icp1);
+ grp->icp2 = htons(icp2);
+ grp->cont_idx = cont_idx;
+}
+
+int abis_om2k_tx_is_conf_req(struct gsm_bts *bts)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+ struct is_conn_group *grp;
+ unsigned int num_grps = 0, i = 0;
+ struct om2k_is_conn_grp *cg;
+
+ /* count number of groups in linked list */
+ llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list)
+ num_grps++;
+
+ if (!num_grps)
+ return -EINVAL;
+
+ /* allocate buffer for oml group array */
+ cg = talloc_zero_array(bts, struct om2k_is_conn_grp, num_grps);
+
+ /* fill array with data from linked list */
+ llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list)
+ 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);
+
+ 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);
+
+ talloc_free(cg);
+
+ DEBUGP(DNM, "Tx MO=%s %s\n",
+ 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);
+}
+
+int abis_om2k_tx_con_conf_req(struct gsm_bts *bts)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+ struct con_group *grp;
+ unsigned int num_grps = 0;
+
+ /* count number of groups in linked list */
+ llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list)
+ num_grps++;
+
+ if (!num_grps)
+ return -EINVAL;
+
+ /* first build the value part of the OM2K_DEI_CON_CONN_LIST DEI */
+ msgb_put_u8(msg, num_grps);
+ llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list) {
+ struct con_path *cp;
+ unsigned int num_paths = 0;
+ llist_for_each_entry(cp, &grp->paths, list)
+ num_paths++;
+ msgb_put_u8(msg, num_paths);
+ llist_for_each_entry(cp, &grp->paths, list) {
+ struct om2k_con_path *om2k_cp;
+ om2k_cp = (struct om2k_con_path *) msgb_put(msg, sizeof(*om2k_cp));
+ om2k_cp->ccp = htons(cp->ccp);
+ om2k_cp->ci = cp->ci;
+ om2k_cp->tag = cp->tag;
+ om2k_cp->tei = cp->tei;
+ }
+ }
+ msgb_push_u8(msg, msgb_length(msg));
+ msgb_push_u8(msg, OM2K_DEI_CON_CONN_LIST);
+
+ /* pre-pend the list number DEIs */
+ msgb_tv_push(msg, OM2K_DEI_END_LIST_NR, 1);
+ msgb_tv_push(msg, OM2K_DEI_LIST_NR, 1);
+
+ /* 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);
+
+ DEBUGP(DNM, "Tx MO=%s %s\n",
+ om2k_mo_name(&bts->rbs2000.con.om2k_mo.addr),
+ get_value_string(om2k_msgcode_vals, OM2K_MSGT_CON_CONF_REQ));
+
+ return abis_om2k_sendmsg(bts, msg);
+}
+
+static void om2k_trx_to_mo(struct abis_om2k_mo *mo,
+ const struct gsm_bts_trx *trx,
+ enum abis_om2k_mo_cls cls)
+{
+ mo->class = cls;
+ mo->bts = 0;
+ mo->inst = trx->nr;
+ mo->assoc_so = 255;
+}
+
+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;
+ mo->inst = ts->nr;
+ mo->assoc_so = ts->trx->nr;
+}
+
+/* Configure a Receiver MO */
+int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+ struct abis_om2k_mo mo;
+
+ om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_RX);
+
+ o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+ fill_om2k_hdr(o2k, &mo, OM2K_MSGT_RX_CONF_REQ);
+
+ msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, trx->arfcn);
+ msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */
+
+ return abis_om2k_sendmsg(trx->bts, msg);
+}
+
+/* Configure a Transmitter MO */
+int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+ struct abis_om2k_mo mo;
+
+ om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_TX);
+
+ o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+ fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TX_CONF_REQ);
+
+ msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_TX, trx->arfcn);
+ msgb_tv_put(msg, OM2K_DEI_POWER, trx->nominal_power-trx->max_power_red);
+ msgb_tv_put(msg, OM2K_DEI_FILLING_MARKER, 0); /* Filling enabled */
+ msgb_tv_put(msg, OM2K_DEI_BCC, trx->bts->bsic & 0x7);
+ /* Dedication Information is optional */
+
+ return abis_om2k_sendmsg(trx->bts, msg);
+}
+
+enum abis_om2k_tf_mode {
+ OM2K_TF_MODE_MASTER = 0x00,
+ OM2K_TF_MODE_STANDALONE = 0x01,
+ OM2K_TF_MODE_SLAVE = 0x02,
+ OM2K_TF_MODE_UNDEFINED = 0xff,
+};
+
+static const uint8_t fs_offset_undef[5] = { 0xff, 0xff, 0xff, 0xff, 0xff };
+
+int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+
+ o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+ fill_om2k_hdr(o2k, &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);
+
+ DEBUGP(DNM, "Tx MO=%s %s\n",
+ 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);
+}
+
+static uint8_t pchan2comb(enum gsm_phys_chan_config pchan)
+{
+ switch (pchan) {
+ case GSM_PCHAN_CCCH:
+ return 4;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ return 5;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ return 3;
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_PDCH:
+ case GSM_PCHAN_TCH_F_PDCH:
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return 8;
+ default:
+ return 0;
+ }
+}
+
+static uint8_t ts2comb(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case 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));
+ /* If we allowed initialization of TCH/F_PDCH, it would fail
+ * when we try to send the ip.access specific RSL PDCH Act
+ * message for it. Rather fail completely right now: */
+ return 0;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return pchan2comb(GSM_PCHAN_TCH_F);
+ default:
+ return pchan2comb(ts->pchan);
+ }
+}
+
+static int put_freq_list(uint8_t *buf, uint16_t arfcn)
+{
+ buf[0] = 0x00; /* TX/RX address */
+ buf[1] = (arfcn >> 8);
+ buf[2] = (arfcn & 0xff);
+
+ return 3;
+}
+
+/* Compute a frequency list in OM2000 fomrmat */
+static int om2k_gen_freq_list(uint8_t *list, struct gsm_bts_trx_ts *ts)
+{
+ uint8_t *cur = list;
+ int len;
+
+ if (ts->hopping.enabled) {
+ unsigned int i;
+ for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+ if (bitvec_get_bit_pos(&ts->hopping.arfcns, i))
+ cur += put_freq_list(cur, i);
+ }
+ } else
+ cur += put_freq_list(cur, ts->trx->arfcn);
+
+ len = cur - list;
+
+ return len;
+}
+
+const uint8_t icm_bound_params[] = { 0x02, 0x06, 0x0c, 0x16, 0x06 };
+
+int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+ struct abis_om2k_mo mo;
+ uint8_t freq_list[64*3]; /* BA max size: 64 ARFCN */
+ int freq_list_len;
+
+ om2k_ts_to_mo(&mo, ts);
+
+ memset(freq_list, 0, sizeof(freq_list));
+ freq_list_len = om2k_gen_freq_list(freq_list, ts);
+ if (freq_list_len < 0)
+ return freq_list_len;
+
+ o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+ fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TS_CONF_REQ);
+
+ msgb_tv_put(msg, OM2K_DEI_COMBINATION, ts2comb(ts));
+ msgb_tv_put(msg, OM2K_DEI_TS_NR, ts->nr);
+ msgb_tlv_put(msg, OM2K_DEI_FREQ_LIST, freq_list_len, freq_list);
+ 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_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0);
+ msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */
+ /* Optional: Interference Rejection Combining */
+ msgb_tv_put(msg, OM2K_DEI_INTERF_REJ_COMB, 0x00);
+ switch (ts->pchan) {
+ case GSM_PCHAN_CCCH:
+ msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06);
+ msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01);
+ msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05);
+ /* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */
+ msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01);
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10);
+ msgb_tv_put(msg, OM2K_DEI_NY1, 35);
+ msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06);
+ msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0);
+ msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts));
+ msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01);
+ msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0);
+ msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05);
+ /* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */
+ msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01);
+ msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS,
+ sizeof(icm_bound_params), icm_bound_params);
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10);
+ msgb_tv_put(msg, OM2K_DEI_NY1, 35);
+ msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0);
+ msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts));
+ /* Disable RF RESOURCE INDICATION on idle channels */
+ msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0);
+ msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS,
+ sizeof(icm_bound_params), icm_bound_params);
+ break;
+ default:
+ msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10);
+ msgb_tv_put(msg, OM2K_DEI_NY1, 35);
+ msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts));
+ /* Disable RF RESOURCE INDICATION on idle channels */
+ msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0);
+ msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS,
+ sizeof(icm_bound_params), icm_bound_params);
+ msgb_tv_put(msg, OM2K_DEI_TTA, 10); /* Timer for Time Alignment */
+ if (ts->pchan == GSM_PCHAN_TCH_H)
+ msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 1); /* TCH/H */
+ else
+ msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 0); /* TCH/F */
+ msgb_tv_put(msg, OM2K_DEI_LSC, 1); /* enabled */
+ msgb_tv_put(msg, OM2K_DEI_LSC_FILT_TIME, 10); /* units of 100ms */
+ msgb_tv_put(msg, OM2K_DEI_CALL_SUPV_TIME, 8);
+ msgb_tv_put(msg, OM2K_DEI_ENCR_ALG, 0x00);
+ /* Not sure what those below mean */
+ msgb_tv_put(msg, 0x9e, 0x00);
+ msgb_tv_put(msg, 0x9f, 0x37);
+ msgb_tv_put(msg, 0xa0, 0x01);
+ break;
+ }
+
+ DEBUGP(DNM, "Tx MO=%s %s\n",
+ om2k_mo_name(&mo),
+ get_value_string(om2k_msgcode_vals, OM2K_MSGT_TS_CONF_REQ));
+
+ return abis_om2k_sendmsg(ts->trx->bts, msg);
+}
+
+
+/***********************************************************************
+ * OM2000 Managed Object (MO) FSM
+ ***********************************************************************/
+
+#define S(x) (1 << (x))
+
+enum om2k_event_name {
+ OM2K_MO_EVT_START,
+ OM2K_MO_EVT_RX_CONN_COMPL,
+ OM2K_MO_EVT_RX_RESET_COMPL,
+ OM2K_MO_EVT_RX_START_REQ_ACCEPT,
+ OM2K_MO_EVT_RX_START_RES,
+ OM2K_MO_EVT_RX_CFG_REQ_ACCEPT,
+ OM2K_MO_EVT_RX_CFG_RES,
+ OM2K_MO_EVT_RX_ENA_REQ_ACCEPT,
+ OM2K_MO_EVT_RX_ENA_RES,
+ OM2K_MO_EVT_RX_OPINFO_ACC,
+};
+
+static const struct value_string om2k_event_names[] = {
+ { OM2K_MO_EVT_START, "START" },
+ { 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" },
+ { OM2K_MO_EVT_RX_START_RES, "RX-START-RESULT" },
+ { OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, "RX-CFG-REQ-ACCEPT" },
+ { OM2K_MO_EVT_RX_CFG_RES, "RX-CFG-RESULT" },
+ { OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, "RX-ENABLE-REQ-ACCEPT" },
+ { OM2K_MO_EVT_RX_ENA_RES, "RX-ENABLE-RESULT" },
+ { OM2K_MO_EVT_RX_OPINFO_ACC, "RX-OPINFO-ACCEPT" },
+ { 0, NULL }
+};
+
+enum om2k_mo_fsm_state {
+ OM2K_ST_INIT,
+ OM2K_ST_WAIT_CONN_COMPL,
+ OM2K_ST_WAIT_RES_COMPL,
+ OM2K_ST_WAIT_START_ACCEPT,
+ OM2K_ST_WAIT_START_RES,
+ OM2K_ST_WAIT_CFG_ACCEPT,
+ OM2K_ST_WAIT_CFG_RES,
+ OM2K_ST_WAIT_ENABLE_ACCEPT,
+ OM2K_ST_WAIT_ENABLE_RES,
+ OM2K_ST_WAIT_OPINFO_ACCEPT,
+ OM2K_ST_DONE,
+ OM2K_ST_ERROR,
+};
+
+struct om2k_mo_fsm_priv {
+ struct gsm_bts_trx *trx;
+ struct om2k_mo *mo;
+ uint8_t ts_nr;
+};
+
+static void om2k_mo_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_mo_fsm_priv *omfp = fi->priv;
+
+ OSMO_ASSERT(event == OM2K_MO_EVT_START);
+
+ 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);
+ 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);
+ 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);
+ abis_om2k_tx_connect_cmd(omfp->trx->bts, &omfp->mo->addr);
+ break;
+ }
+}
+
+static void om2k_mo_st_wait_conn_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_mo_fsm_priv *omfp = fi->priv;
+
+ switch (omfp->mo->addr.class) {
+#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);
+ 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);
+ abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr);
+ break;
+ }
+}
+
+static void om2k_mo_st_wait_res_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_mo_fsm_priv *omfp = fi->priv;
+
+ 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);
+}
+
+static void om2k_mo_st_wait_start_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_decoded_msg *omd = data;
+
+ switch (omd->msg_type) {
+ case OM2K_MSGT_START_REQ_ACK:
+ 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);
+ break;
+ }
+}
+
+static void om2k_mo_st_wait_start_res(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_mo_fsm_priv *omfp = fi->priv;
+ struct gsm_bts_trx_ts *ts;
+
+ switch (omfp->mo->addr.class) {
+ 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);
+ abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1);
+ return;
+ case OM2K_MO_CLS_DP:
+ /* Transition directoy to WAIT_ENABLE_ACCEPT */
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT,
+ OM2K_TIMEOUT, 0);
+ 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);
+ abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr);
+ return;
+#endif
+ }
+
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_ACCEPT,
+ OM2K_TIMEOUT, 0);
+ switch (omfp->mo->addr.class) {
+ case OM2K_MO_CLS_TF:
+ abis_om2k_tx_tf_conf_req(omfp->trx->bts);
+ break;
+ case OM2K_MO_CLS_IS:
+ abis_om2k_tx_is_conf_req(omfp->trx->bts);
+ break;
+ case OM2K_MO_CLS_CON:
+ abis_om2k_tx_con_conf_req(omfp->trx->bts);
+ break;
+ case OM2K_MO_CLS_TX:
+ abis_om2k_tx_tx_conf_req(omfp->trx);
+ break;
+ case OM2K_MO_CLS_RX:
+ abis_om2k_tx_rx_conf_req(omfp->trx);
+ break;
+ case OM2K_MO_CLS_TS:
+ ts = mo2obj(omfp->trx->bts, &omfp->mo->addr);
+ abis_om2k_tx_ts_conf_req(ts);
+ break;
+ }
+}
+
+static void om2k_mo_st_wait_cfg_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_mo_fsm_priv *omfp = fi->priv;
+ uint32_t timeout = OM2K_TIMEOUT;
+
+ if (omfp->mo->addr.class == OM2K_MO_CLS_TF)
+ timeout = 600;
+
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_RES, timeout, 0);
+}
+
+static void om2k_mo_st_wait_cfg_res(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_mo_fsm_priv *omfp = fi->priv;
+ struct om2k_decoded_msg *omd = data;
+ uint8_t accordance;
+
+ if (!TLVP_PRESENT(&omd->tp, OM2K_DEI_ACCORDANCE_IND)) {
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
+ return;
+ }
+ accordance = *TLVP_VAL(&omd->tp, OM2K_DEI_ACCORDANCE_IND);
+
+ if (accordance != 0) {
+ /* accordance not OK */
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
+ return;
+ }
+
+ 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);
+}
+
+static void om2k_mo_st_wait_enable_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_mo_fsm_priv *omfp = fi->priv;
+ struct om2k_decoded_msg *omd = data;
+
+ switch (omd->msg_type) {
+ case OM2K_MSGT_ENABLE_REQ_REJ:
+ 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)
+ 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);
+ }
+}
+
+static void om2k_mo_st_wait_enable_res(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_mo_fsm_priv *omfp = fi->priv;
+ //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);
+ abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1);
+}
+
+static void om2k_mo_st_wait_opinfo_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_mo_fsm_priv *omfp = fi->priv;
+
+ /* if we have just received opinfo accept for the timeslot,
+ * start dynamic TCH switching procedures */
+ if (omfp->mo->addr.class == OM2K_MO_CLS_TS) {
+ struct gsm_bts_trx_ts *ts;
+ ts = mo2obj(omfp->trx->bts, &omfp->mo->addr);
+ dyn_ts_init(ts);
+ }
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_DONE, 0, 0);
+}
+
+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);
+}
+
+static void om2k_mo_s_error_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_ERROR, NULL);
+}
+
+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_ERROR) |
+ S(OM2K_ST_WAIT_CONN_COMPL) |
+ S(OM2K_ST_WAIT_START_ACCEPT) |
+ S(OM2K_ST_WAIT_RES_COMPL),
+ .action = om2k_mo_st_init,
+ },
+ [OM2K_ST_WAIT_CONN_COMPL] = {
+ .name = "WAIT-CONN-COMPL",
+ .in_event_mask = S(OM2K_MO_EVT_RX_CONN_COMPL),
+ .out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_ERROR) |
+ S(OM2K_ST_WAIT_START_ACCEPT) |
+ S(OM2K_ST_WAIT_RES_COMPL),
+ .action = om2k_mo_st_wait_conn_compl,
+ },
+ [OM2K_ST_WAIT_RES_COMPL] = {
+ .name = "WAIT-RES-COMPL",
+ .in_event_mask = S(OM2K_MO_EVT_RX_RESET_COMPL),
+ .out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_ERROR) |
+ S(OM2K_ST_WAIT_START_ACCEPT),
+ .action = om2k_mo_st_wait_res_compl,
+ },
+ [OM2K_ST_WAIT_START_ACCEPT] = {
+ .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_ERROR) |
+ S(OM2K_ST_WAIT_START_RES),
+ .action =om2k_mo_st_wait_start_accept,
+ },
+ [OM2K_ST_WAIT_START_RES] = {
+ .name = "WAIT-START-RES",
+ .in_event_mask = S(OM2K_MO_EVT_RX_START_RES),
+ .out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_ERROR) |
+ S(OM2K_ST_WAIT_CFG_ACCEPT) |
+ S(OM2K_ST_WAIT_OPINFO_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_ERROR) |
+ S(OM2K_ST_WAIT_CFG_RES),
+ .action = om2k_mo_st_wait_cfg_accept,
+ },
+ [OM2K_ST_WAIT_CFG_RES] = {
+ .name = "WAIT-CFG-RES",
+ .in_event_mask = S(OM2K_MO_EVT_RX_CFG_RES),
+ .out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_ERROR) |
+ S(OM2K_ST_WAIT_ENABLE_ACCEPT),
+ .action = om2k_mo_st_wait_cfg_res,
+ },
+ [OM2K_ST_WAIT_ENABLE_ACCEPT] = {
+ .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_ERROR) |
+ S(OM2K_ST_WAIT_ENABLE_RES),
+ .action = om2k_mo_st_wait_enable_accept,
+ },
+ [OM2K_ST_WAIT_ENABLE_RES] = {
+ .name = "WAIT-ENABLE-RES",
+ .in_event_mask = S(OM2K_MO_EVT_RX_ENA_RES),
+ .out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_ERROR) |
+ S(OM2K_ST_WAIT_OPINFO_ACCEPT),
+ .action = om2k_mo_st_wait_enable_res,
+ },
+ [OM2K_ST_WAIT_OPINFO_ACCEPT] = {
+ .name = "WAIT-OPINFO-ACCEPT",
+ .in_event_mask = S(OM2K_MO_EVT_RX_OPINFO_ACC),
+ .out_state_mask = S(OM2K_ST_DONE) |
+ S(OM2K_ST_ERROR),
+ .action = om2k_mo_st_wait_opinfo_accept,
+ },
+ [OM2K_ST_DONE] = {
+ .name = "DONE",
+ .in_event_mask = 0,
+ .out_state_mask = 0,
+ .onenter = om2k_mo_s_done_onenter,
+ },
+ [OM2K_ST_ERROR] = {
+ .name = "ERROR",
+ .in_event_mask = 0,
+ .out_state_mask = 0,
+ .onenter = om2k_mo_s_error_onenter,
+ },
+
+};
+
+static int om2k_mo_timer_cb(struct osmo_fsm_inst *fi)
+{
+ osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0);
+ return 0;
+}
+
+static struct osmo_fsm om2k_mo_fsm = {
+ .name = "OM2000-MO",
+ .states = om2k_is_states,
+ .num_states = ARRAY_SIZE(om2k_is_states),
+ .log_subsys = DNM,
+ .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)
+{
+ struct osmo_fsm_inst *fi;
+ struct om2k_mo_fsm_priv *omfp;
+ char idbuf[64];
+
+ snprintf(idbuf, sizeof(idbuf), "%s-%s", parent->id,
+ om2k_mo_name(&mo->addr));
+
+ fi = osmo_fsm_inst_alloc_child_id(&om2k_mo_fsm, parent,
+ term_event, idbuf);
+ if (!fi)
+ return NULL;
+
+ mo->fsm = fi;
+ omfp = talloc_zero(fi, struct om2k_mo_fsm_priv);
+ omfp->mo = mo;
+ omfp->trx = trx;
+ fi->priv = omfp;
+
+ osmo_fsm_inst_dispatch(fi, OM2K_MO_EVT_START, NULL);
+
+ return fi;
+}
+
+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);
+ break;
+
+ case OM2K_MSGT_RESET_COMPL:
+ case OM2K_MSGT_RESET_REJ:
+ 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);
+ break;
+
+ case OM2K_MSGT_START_RES:
+ osmo_fsm_inst_dispatch(mo->fsm,
+ OM2K_MO_EVT_RX_START_RES, odm);
+ break;
+
+ case OM2K_MSGT_CON_CONF_REQ_ACK:
+ case OM2K_MSGT_IS_CONF_REQ_ACK:
+ case OM2K_MSGT_RX_CONF_REQ_ACK:
+ case OM2K_MSGT_TF_CONF_REQ_ACK:
+ case OM2K_MSGT_TS_CONF_REQ_ACK:
+ case OM2K_MSGT_TX_CONF_REQ_ACK:
+ osmo_fsm_inst_dispatch(mo->fsm,
+ OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, odm);
+ break;
+
+ case OM2K_MSGT_CON_CONF_RES:
+ case OM2K_MSGT_IS_CONF_RES:
+ case OM2K_MSGT_RX_CONF_RES:
+ case OM2K_MSGT_TF_CONF_RES:
+ case OM2K_MSGT_TS_CONF_RES:
+ case OM2K_MSGT_TX_CONF_RES:
+ osmo_fsm_inst_dispatch(mo->fsm,
+ OM2K_MO_EVT_RX_CFG_RES, odm);
+ 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);
+ break;
+ case OM2K_MSGT_ENABLE_RES:
+ 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);
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+/***********************************************************************
+ * OM2000 TRX Finite State Machine, initializes TRXC and all siblings
+ ***********************************************************************/
+
+enum om2k_trx_event {
+ OM2K_TRX_EVT_START,
+ OM2K_TRX_EVT_TRXC_DONE,
+ OM2K_TRX_EVT_TX_DONE,
+ OM2K_TRX_EVT_RX_DONE,
+ OM2K_TRX_EVT_TS_DONE,
+ OM2K_TRX_EVT_STOP,
+};
+
+static struct value_string om2k_trx_events[] = {
+ { OM2K_TRX_EVT_START, "START" },
+ { OM2K_TRX_EVT_TRXC_DONE, "TRXC-DONE" },
+ { OM2K_TRX_EVT_TX_DONE, "TX-DONE" },
+ { OM2K_TRX_EVT_RX_DONE, "RX-DONE" },
+ { OM2K_TRX_EVT_TS_DONE, "TS-DONE" },
+ { OM2K_TRX_EVT_STOP, "STOP" },
+ { 0, NULL }
+};
+
+enum om2k_trx_state {
+ OM2K_TRX_S_INIT,
+ OM2K_TRX_S_WAIT_TRXC,
+ OM2K_TRX_S_WAIT_TX,
+ OM2K_TRX_S_WAIT_RX,
+ OM2K_TRX_S_WAIT_TS,
+ OM2K_TRX_S_DONE,
+ OM2K_TRX_S_ERROR
+};
+
+struct om2k_trx_fsm_priv {
+ struct gsm_bts_trx *trx;
+ uint8_t next_ts_nr;
+};
+
+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);
+}
+
+static void om2k_trx_s_wait_trxc(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ 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);
+}
+
+static void om2k_trx_s_wait_tx(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ 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);
+}
+
+static void om2k_trx_s_wait_rx(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_trx_fsm_priv *otfp = fi->priv;
+ struct gsm_bts_trx_ts *ts;
+
+ /* Initialize Timeslots after TX */
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TS,
+ TRX_FSM_TIMEOUT, 0);
+ otfp->next_ts_nr = 0;
+ ts = &otfp->trx->ts[otfp->next_ts_nr++];
+ om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx,
+ &ts->rbs2000.om2k_mo);
+}
+
+static void om2k_trx_s_wait_ts(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_trx_fsm_priv *otfp = fi->priv;
+ struct gsm_bts_trx_ts *ts;
+
+ if (otfp->next_ts_nr < 8) {
+ /* iterate to the next timeslot */
+ ts = &otfp->trx->ts[otfp->next_ts_nr++];
+ om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx,
+ &ts->rbs2000.om2k_mo);
+ } else {
+ /* only after all 8 TS */
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_DONE, 0, 0);
+ }
+}
+
+static void om2k_trx_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct om2k_trx_fsm_priv *otfp = fi->priv;
+ gsm_bts_trx_set_system_infos(otfp->trx);
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+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),
+ .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),
+ .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),
+ .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),
+ .name = "WAIT-RX",
+ .action = om2k_trx_s_wait_rx,
+ },
+ [OM2K_TRX_S_WAIT_TS] = {
+ .in_event_mask = S(OM2K_TRX_EVT_TS_DONE),
+ .out_state_mask = S(OM2K_TRX_S_ERROR) |
+ S(OM2K_TRX_S_DONE),
+ .name = "WAIT-TS",
+ .action = om2k_trx_s_wait_ts,
+ },
+ [OM2K_TRX_S_DONE] = {
+ .name = "DONE",
+ .onenter = om2k_trx_s_done_onenter,
+ },
+ [OM2K_TRX_S_ERROR] = {
+ .name = "ERROR",
+ },
+};
+
+static int om2k_trx_timer_cb(struct osmo_fsm_inst *fi)
+{
+ osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_ERROR, 0, 0);
+ return 0;
+}
+
+static struct osmo_fsm om2k_trx_fsm = {
+ .name = "OM2000-TRX",
+ .states = om2k_trx_states,
+ .num_states = ARRAY_SIZE(om2k_trx_states),
+ .log_subsys = DNM,
+ .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)
+{
+ struct osmo_fsm_inst *fi;
+ struct om2k_trx_fsm_priv *otfp;
+ char idbuf[32];
+
+ snprintf(idbuf, sizeof(idbuf), "%u/%u", trx->bts->nr, trx->nr);
+
+ fi = osmo_fsm_inst_alloc_child_id(&om2k_trx_fsm, parent, term_event,
+ idbuf);
+ if (!fi)
+ return NULL;
+
+ otfp = talloc_zero(fi, struct om2k_trx_fsm_priv);
+ otfp->trx = trx;
+ fi->priv = otfp;
+
+ osmo_fsm_inst_dispatch(fi, OM2K_TRX_EVT_START, NULL);
+
+ return fi;
+}
+
+
+/***********************************************************************
+ * OM2000 BTS Finite State Machine, initializes CF and all siblings
+ ***********************************************************************/
+
+enum om2k_bts_event {
+ OM2K_BTS_EVT_START,
+ OM2K_BTS_EVT_CF_DONE,
+ OM2K_BTS_EVT_IS_DONE,
+ OM2K_BTS_EVT_CON_DONE,
+ OM2K_BTS_EVT_TF_DONE,
+ OM2K_BTS_EVT_TRX_DONE,
+ OM2K_BTS_EVT_STOP,
+};
+
+static const struct value_string om2k_bts_events[] = {
+ { OM2K_BTS_EVT_START, "START" },
+ { OM2K_BTS_EVT_CF_DONE, "CF-DONE" },
+ { OM2K_BTS_EVT_IS_DONE, "IS-DONE" },
+ { OM2K_BTS_EVT_CON_DONE, "CON-DONE" },
+ { OM2K_BTS_EVT_TF_DONE, "TF-DONE" },
+ { OM2K_BTS_EVT_TRX_DONE, "TRX-DONE" },
+ { OM2K_BTS_EVT_STOP, "STOP" },
+ { 0, NULL }
+};
+
+enum om2k_bts_state {
+ OM2K_BTS_S_INIT,
+ OM2K_BTS_S_WAIT_CF,
+ OM2K_BTS_S_WAIT_IS,
+ OM2K_BTS_S_WAIT_CON,
+ OM2K_BTS_S_WAIT_TF,
+ OM2K_BTS_S_WAIT_TRX,
+ OM2K_BTS_S_DONE,
+ OM2K_BTS_S_ERROR,
+};
+
+struct om2k_bts_fsm_priv {
+ struct gsm_bts *bts;
+ uint8_t next_trx_nr;
+};
+
+static void om2k_bts_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_bts_fsm_priv *obfp = fi->priv;
+ 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);
+}
+
+static void om2k_bts_s_wait_cf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_bts_fsm_priv *obfp = fi->priv;
+ struct gsm_bts *bts = obfp->bts;
+
+ 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);
+}
+
+static void om2k_bts_s_wait_tf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_bts_fsm_priv *obfp = fi->priv;
+ struct gsm_bts *bts = obfp->bts;
+
+ 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);
+}
+
+static void om2k_bts_s_wait_con(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_bts_fsm_priv *obfp = fi->priv;
+ struct gsm_bts *bts = obfp->bts;
+
+ 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);
+}
+
+static void om2k_bts_s_wait_is(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_bts_fsm_priv *obfp = fi->priv;
+ struct gsm_bts_trx *trx;
+
+ OSMO_ASSERT(event == OM2K_BTS_EVT_IS_DONE);
+
+ 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);
+}
+
+static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct om2k_bts_fsm_priv *obfp = fi->priv;
+
+ OSMO_ASSERT(event == OM2K_BTS_EVT_TRX_DONE);
+
+ 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);
+ } else {
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_DONE, 0, 0);
+ }
+}
+
+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 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),
+ .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),
+ .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),
+ .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),
+ .name = "WAIT-CON",
+ .action = om2k_bts_s_wait_con,
+ },
+ [OM2K_BTS_S_WAIT_IS] = {
+ .in_event_mask = S(OM2K_BTS_EVT_IS_DONE),
+ .out_state_mask = S(OM2K_BTS_S_ERROR) |
+ S(OM2K_BTS_S_WAIT_TRX),
+ .name = "WAIT-IS",
+ .action = om2k_bts_s_wait_is,
+ },
+ [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),
+ .name = "WAIT-TRX",
+ .action = om2k_bts_s_wait_trx,
+ },
+ [OM2K_BTS_S_DONE] = {
+ .name = "DONE",
+ .onenter = om2k_bts_s_done_onenter,
+ },
+ [OM2K_BTS_S_ERROR] = {
+ .name = "ERROR",
+ },
+};
+
+static int om2k_bts_timer_cb(struct osmo_fsm_inst *fi)
+{
+ osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_ERROR, 0, 0);
+ return 0;
+}
+
+static struct osmo_fsm om2k_bts_fsm = {
+ .name = "OM2000-BTS",
+ .states = om2k_bts_states,
+ .num_states = ARRAY_SIZE(om2k_bts_states),
+ .log_subsys = DNM,
+ .event_names = om2k_bts_events,
+ .timer_cb = om2k_bts_timer_cb,
+};
+
+struct osmo_fsm_inst *
+om2k_bts_fsm_start(struct gsm_bts *bts)
+{
+ struct osmo_fsm_inst *fi;
+ struct om2k_bts_fsm_priv *obfp;
+ char idbuf[16];
+
+ snprintf(idbuf, sizeof(idbuf), "%u", bts->nr);
+
+ 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;
+}
+
+
+/***********************************************************************
+ * OM2000 Negotiation
+ ***********************************************************************/
+
+static int abis_om2k_tx_negot_req_ack(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+ uint8_t *data, unsigned int len)
+{
+ struct msgb *msg = om2k_msgb_alloc();
+ struct abis_om2k_hdr *o2k;
+
+ o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k));
+ fill_om2k_hdr(o2k, mo, OM2K_MSGT_NEGOT_REQ_ACK);
+
+ msgb_tlv_put(msg, OM2K_DEI_NEGOT_REC2, len, data);
+
+ DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo),
+ get_value_string(om2k_msgcode_vals, OM2K_MSGT_NEGOT_REQ_ACK));
+
+ return abis_om2k_sendmsg(bts, msg);
+}
+
+struct iwd_version {
+ uint8_t gen_char[3+1];
+ uint8_t rev_char[3+1];
+};
+
+struct iwd_type {
+ uint8_t num_vers;
+ struct iwd_version v[8];
+};
+
+static int om2k_rx_negot_req(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst;
+ struct abis_om2k_hdr *o2h = msgb_l2(msg);
+ struct iwd_type iwd_types[16];
+ uint8_t num_iwd_types = o2h->data[2];
+ uint8_t *cur = o2h->data+3;
+ unsigned int i, v;
+
+ uint8_t out_buf[1024];
+ uint8_t *out_cur = out_buf+1;
+ uint8_t out_num_types = 0;
+
+ memset(iwd_types, 0, sizeof(iwd_types));
+
+ /* Parse the RBS-supported IWD versions into iwd_types array */
+ for (i = 0; i < num_iwd_types; i++) {
+ uint8_t num_versions = *cur++;
+ uint8_t iwd_type = *cur++;
+
+ iwd_types[iwd_type].num_vers = num_versions;
+
+ for (v = 0; v < num_versions; v++) {
+ struct iwd_version *iwd_v = &iwd_types[iwd_type].v[v];
+
+ memcpy(iwd_v->gen_char, cur, 3);
+ cur += 3;
+ memcpy(iwd_v->rev_char, cur, 3);
+ cur += 3;
+
+ DEBUGP(DNM, "\tIWD Type %u Gen %s Rev %s\n", iwd_type,
+ iwd_v->gen_char, iwd_v->rev_char);
+ }
+ }
+
+ /* Select the last version for each IWD type */
+ for (i = 0; i < ARRAY_SIZE(iwd_types); i++) {
+ struct iwd_type *type = &iwd_types[i];
+ struct iwd_version *last_v;
+
+ if (type->num_vers == 0)
+ continue;
+
+ out_num_types++;
+
+ last_v = &type->v[type->num_vers-1];
+
+ *out_cur++ = i;
+ memcpy(out_cur, last_v->gen_char, 3);
+ out_cur += 3;
+ memcpy(out_cur, last_v->rev_char, 3);
+ out_cur += 3;
+ }
+
+ out_buf[0] = out_num_types;
+
+ return abis_om2k_tx_negot_req_ack(sign_link->trx->bts, &o2h->mo, out_buf, out_cur - out_buf);
+}
+
+
+/***********************************************************************
+ * OM2000 Receive Message Handler
+ ***********************************************************************/
+
+static int om2k_rx_nack(struct msgb *msg)
+{
+ struct abis_om2k_hdr *o2h = msgb_l2(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),
+ 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));
+
+ 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)));
+ LOGPC(DNM, LOGL_ERROR, "\n");
+
+ return 0;
+}
+
+static int process_mo_state(struct gsm_bts *bts, struct om2k_decoded_msg *odm)
+{
+ uint8_t mo_state;
+
+ if (!TLVP_PRESENT(&odm->tp, OM2K_DEI_MO_STATE))
+ 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),
+ get_value_string(om2k_msgcode_vals, odm->msg_type),
+ get_value_string(om2k_mostate_vals, mo_state));
+
+ /* Throw error message in case we see an enable rsponse that does
+ * 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));
+ }
+
+ update_mo_state(bts, &odm->o2h.mo, mo_state);
+
+ return 0;
+}
+
+/* Display fault report bits (helper function of display_fault_maps()) */
+static bool display_fault_bits(const uint8_t *vect, uint16_t len,
+ uint8_t dei, const struct abis_om2k_mo *mo)
+{
+ uint16_t i;
+ int k;
+ bool faults_present = false;
+ int first = 1;
+ char string[255];
+
+ /* Check if errors are present at all */
+ for (i = 0; i < len; i++)
+ if (vect[i])
+ faults_present = true;
+ if (!faults_present)
+ return false;
+
+ sprintf(string, "Fault Report: %s (",
+ get_value_string(om2k_attr_vals, dei));
+
+ for (i = 0; i < len; i++) {
+ for (k = 0; k < 8; k++) {
+ if ((vect[i] >> k) & 1) {
+ if (!first)
+ sprintf(string + strlen(string), ",");
+ sprintf(string + strlen(string), "%d", k + i*8);
+ first = 0;
+ }
+ }
+ }
+
+ sprintf(string + strlen(string), ")\n");
+ DEBUGP(DNM, "Rx MO=%s %s", om2k_mo_name(mo), string);
+
+ return true;
+}
+
+/* Display fault report maps */
+static void display_fault_maps(const uint8_t *src, unsigned int src_len,
+ const struct abis_om2k_mo *mo)
+{
+ uint8_t tag;
+ uint16_t tag_len;
+ const uint8_t *val;
+ int src_pos = 0;
+ int rc;
+ int tlv_count = 0;
+ uint16_t msg_code;
+ bool faults_present = false;
+
+ /* Chop off header */
+ src+=4;
+ src_len-=4;
+
+ /* Check message type */
+ msg_code = (*src & 0xff) << 8;
+ src++;
+ src_len--;
+ msg_code |= (*src & 0xff);
+ 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));
+ return;
+ }
+
+ /* Chop off mo-interface */
+ src += 4;
+ src_len -= 4;
+
+ /* Iterate over each TLV element */
+ while (1) {
+
+ /* 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));
+ return;
+ }
+
+ /* Parse TLV field */
+ 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));
+ return;
+ }
+
+ switch (tag) {
+ case OM2K_DEI_INT_FAULT_MAP_1A:
+ case OM2K_DEI_INT_FAULT_MAP_1B:
+ case OM2K_DEI_INT_FAULT_MAP_2A:
+ case OM2K_DEI_EXT_COND_MAP_1:
+ case OM2K_DEI_EXT_COND_MAP_2:
+ case OM2K_DEI_REPL_UNIT_MAP:
+ case OM2K_DEI_INT_FAULT_MAP_2A_EXT:
+ case OM2K_DEI_EXT_COND_MAP_2_EXT:
+ case OM2K_DEI_REPL_UNIT_MAP_EXT:
+ faults_present |= display_fault_bits(val, tag_len,
+ tag, mo);
+ break;
+ }
+
+ /* Stop when no further TLV elements can be expected */
+ if (src_len - src_pos < 2)
+ break;
+
+ tlv_count++;
+ }
+
+ if (!faults_present) {
+ DEBUGP(DNM, "Rx MO=%s Fault Report: All faults ceased!\n",
+ om2k_mo_name(mo));
+ }
+}
+
+int abis_om2k_rcvmsg(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct abis_om2k_hdr *o2h = msgb_l2(msg);
+ struct abis_om_hdr *oh = &o2h->om;
+ uint16_t msg_type = ntohs(o2h->msg_type);
+ struct om2k_decoded_msg odm;
+ struct om2k_mo *mo;
+ int rc = 0;
+
+ /* Various consistency checks */
+ if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
+ LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
+ oh->placement);
+ 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);
+ 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);
+ 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)));
+
+ om2k_decode_msg(&odm, msg);
+
+ process_mo_state(bts, &odm);
+
+ switch (msg_type) {
+ case OM2K_MSGT_CAL_TIME_REQ:
+ rc = abis_om2k_cal_time_resp(bts);
+ break;
+ 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;
+ case OM2K_MSGT_NEGOT_REQ:
+ rc = om2k_rx_negot_req(msg);
+ break;
+ case OM2K_MSGT_START_RES:
+ /* common processing here */
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_START_RES_ACK);
+ /* below we dispatch into MO */
+ break;
+ case OM2K_MSGT_IS_CONF_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_IS_CONF_RES_ACK);
+ break;
+ case OM2K_MSGT_CON_CONF_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CON_CONF_RES_ACK);
+ break;
+ case OM2K_MSGT_TX_CONF_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TX_CONF_RES_ACK);
+ break;
+ case OM2K_MSGT_RX_CONF_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RX_CONF_RES_ACK);
+ break;
+ case OM2K_MSGT_TS_CONF_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TS_CONF_RES_ACK);
+ break;
+ case OM2K_MSGT_TF_CONF_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TF_CONF_RES_ACK);
+ break;
+ case OM2K_MSGT_ENABLE_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_ENABLE_RES_ACK);
+ break;
+ case OM2K_MSGT_DISABLE_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_DISABLE_RES_ACK);
+ break;
+ case OM2K_MSGT_TEST_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TEST_RES_ACK);
+ break;
+ case OM2K_MSGT_CAPA_RES:
+ rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CAPA_RES_ACK);
+ break;
+ /* ERrors */
+ case OM2K_MSGT_START_REQ_REJ:
+ case OM2K_MSGT_CONNECT_REJ:
+ case OM2K_MSGT_OP_INFO_REJ:
+ case OM2K_MSGT_DISCONNECT_REJ:
+ case OM2K_MSGT_TEST_REQ_REJ:
+ case OM2K_MSGT_CON_CONF_REQ_REJ:
+ case OM2K_MSGT_IS_CONF_REQ_REJ:
+ case OM2K_MSGT_TX_CONF_REQ_REJ:
+ case OM2K_MSGT_RX_CONF_REQ_REJ:
+ case OM2K_MSGT_TS_CONF_REQ_REJ:
+ case OM2K_MSGT_TF_CONF_REQ_REJ:
+ case OM2K_MSGT_ENABLE_REQ_REJ:
+ case OM2K_MSGT_ALARM_STATUS_REQ_REJ:
+ case OM2K_MSGT_DISABLE_REQ_REJ:
+ rc = om2k_rx_nack(msg);
+ break;
+ }
+
+ /* Resolve the MO for this message */
+ 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;
+ }
+ 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;
+ }
+
+ /* Dispatch message to that MO */
+ om2k_mo_fsm_recvmsg(bts, mo, &odm);
+
+ 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)
+{
+ mo->addr.class = class;
+ mo->addr.bts = bts_nr;
+ mo->addr.assoc_so = assoc_so;
+ mo->addr.inst = inst;
+}
+
+/* initialize the OM2K_MO members of gsm_bts_trx and its timeslots */
+void abis_om2k_trx_init(struct gsm_bts_trx *trx)
+{
+ struct gsm_bts *bts = trx->bts;
+ 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);
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ om2k_mo_init(&trx->ts[i].rbs2000.om2k_mo, OM2K_MO_CLS_TS,
+ bts->nr, trx->nr, i);
+ }
+}
+
+/* initialize the OM2K_MO members of gsm_bts */
+void abis_om2k_bts_init(struct gsm_bts *bts)
+{
+ 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);
+}
+
+static __attribute__((constructor)) void abis_om2k_init(void)
+{
+ osmo_fsm_register(&om2k_mo_fsm);
+ osmo_fsm_register(&om2k_bts_fsm);
+ osmo_fsm_register(&om2k_trx_fsm);
+}
diff --git a/src/libbsc/abis_om2000_vty.c b/src/libbsc/abis_om2000_vty.c
new file mode 100644
index 000000000..a6bc4c78c
--- /dev/null
+++ b/src/libbsc/abis_om2000_vty.c
@@ -0,0 +1,609 @@
+/* VTY interface for A-bis OM2000 */
+
+/* (C) 2010-2011 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 <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/vty.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+extern struct gsm_network *bsc_gsmnet;
+
+static struct cmd_node om2k_node = {
+ OM2K_NODE,
+ "%s(om2k)# ",
+ 1,
+};
+
+static struct cmd_node om2k_con_group_node = {
+ OM2K_CON_GROUP_NODE,
+ "%s(om2k-con-group)# ",
+ 1,
+};
+
+struct con_group;
+
+struct oml_node_state {
+ struct gsm_bts *bts;
+ struct abis_om2k_mo mo;
+ struct con_group *cg;
+};
+
+static int dummy_config_write(struct vty *v)
+{
+ return CMD_SUCCESS;
+}
+
+/* FIXME: auto-generate those strings from the value_string lists */
+#define OM2K_OBJCLASS_VTY "(trxc|ts|tf|is|con|dp|cf|tx|rx)"
+#define OM2K_OBJCLASS_VTY_HELP "TRX Controller\n" \
+ "Timeslot\n" \
+ "Timing Function\n" \
+ "Interface Switch\n" \
+ "Abis Concentrator\n" \
+ "Digital Path\n" \
+ "Central Function\n" \
+ "Transmitter\n" \
+ "Receiver\n"
+
+DEFUN(om2k_class_inst, om2k_class_inst_cmd,
+ "bts <0-255> om2000 class " OM2K_OBJCLASS_VTY
+ " <0-255> <0-255> <0-255>",
+ "BTS related commands\n" "BTS Number\n"
+ "Manipulate the OM2000 managed objects\n"
+ "Object Class\n" OM2K_OBJCLASS_VTY_HELP
+ "BTS Number\n" "Associated SO Instance\n" "Instance Number\n")
+{
+ struct gsm_bts *bts;
+ struct oml_node_state *oms;
+ 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->type != GSM_BTS_TYPE_RBS2000) {
+ vty_out(vty, "%% BTS %d not an Ericsson RBS%s",
+ bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+ if (!oms)
+ return CMD_WARNING;
+
+ oms->bts = bts;
+ oms->mo.class = get_string_value(om2k_mo_class_short_vals, argv[1]);
+ oms->mo.bts = atoi(argv[2]);
+ oms->mo.assoc_so = atoi(argv[3]);
+ oms->mo.inst = atoi(argv[4]);
+
+ vty->index = oms;
+ vty->node = OM2K_NODE;
+
+ return CMD_SUCCESS;
+
+}
+
+DEFUN(om2k_classnum_inst, om2k_classnum_inst_cmd,
+ "bts <0-255> om2000 class <0-255> <0-255> <0-255> <0-255>",
+ "BTS related commands\n" "BTS Number\n"
+ "Manipulate the OML managed objects\n"
+ "Object Class\n" "Object Class\n"
+ "BTS Number\n" "Associated SO Instance\n" "Instance Number\n")
+{
+ struct gsm_bts *bts;
+ struct oml_node_state *oms;
+ 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;
+ }
+
+ oms = talloc_zero(tall_bsc_ctx, struct oml_node_state);
+ if (!oms)
+ return CMD_WARNING;
+
+ oms->bts = bts;
+ oms->mo.class = atoi(argv[1]);
+ oms->mo.bts = atoi(argv[2]);
+ oms->mo.assoc_so = atoi(argv[3]);
+ oms->mo.inst = atoi(argv[4]);
+
+ vty->index = oms;
+ vty->node = OM2K_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_reset, om2k_reset_cmd,
+ "reset-command",
+ "Reset the MO\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_om2k_tx_reset_cmd(oms->bts, &oms->mo);
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_start, om2k_start_cmd,
+ "start-request",
+ "Start the MO\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_om2k_tx_start_req(oms->bts, &oms->mo);
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_status, om2k_status_cmd,
+ "status-request",
+ "Get the MO Status\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_om2k_tx_status_req(oms->bts, &oms->mo);
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_connect, om2k_connect_cmd,
+ "connect-command",
+ "Connect the MO\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_om2k_tx_connect_cmd(oms->bts, &oms->mo);
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_disconnect, om2k_disconnect_cmd,
+ "disconnect-command",
+ "Disconnect the MO\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_om2k_tx_disconnect_cmd(oms->bts, &oms->mo);
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_enable, om2k_enable_cmd,
+ "enable-request",
+ "Enable the MO\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_om2k_tx_enable_req(oms->bts, &oms->mo);
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_disable, om2k_disable_cmd,
+ "disable-request",
+ "Disable the MO\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_om2k_tx_disable_req(oms->bts, &oms->mo);
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_op_info, om2k_op_info_cmd,
+ "operational-info <0-1>",
+ "Set operational information\n"
+ "Set operational info to 0 or 1\n")
+{
+ struct oml_node_state *oms = vty->index;
+ int oper = atoi(argv[0]);
+
+ abis_om2k_tx_op_info(oms->bts, &oms->mo, oper);
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_test, om2k_test_cmd,
+ "test-request",
+ "Test the MO\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_om2k_tx_test_req(oms->bts, &oms->mo);
+ return CMD_SUCCESS;
+}
+
+DEFUN(om2k_cap_req, om2k_cap_req_cmd,
+ "capabilities-request",
+ "Request MO capabilities\n")
+{
+ struct oml_node_state *oms = vty->index;
+
+ abis_om2k_tx_cap_req(oms->bts, &oms->mo);
+ return CMD_SUCCESS;
+}
+
+static struct con_group *con_group_find_or_create(struct gsm_bts *bts, uint8_t cg)
+{
+ struct con_group *ent;
+
+ llist_for_each_entry(ent, &bts->rbs2000.con.conn_groups, list) {
+ if (ent->cg == cg)
+ return ent;
+ }
+
+ ent = talloc_zero(bts, struct con_group);
+ ent->bts = bts;
+ ent->cg = cg;
+ INIT_LLIST_HEAD(&ent->paths);
+ llist_add_tail(&ent->list, &bts->rbs2000.con.conn_groups);
+
+ return ent;
+}
+
+static int con_group_del(struct gsm_bts *bts, uint8_t cg_id)
+{
+ struct con_group *cg, *cg2;
+
+ llist_for_each_entry_safe(cg, cg2, &bts->rbs2000.con.conn_groups, list) {
+ if (cg->cg == cg_id) {
+ llist_del(&cg->list);
+ talloc_free(cg);
+ return 0;
+ };
+ }
+ return -ENOENT;
+}
+
+static void con_group_add_path(struct con_group *cg, uint16_t ccp,
+ uint8_t ci, uint8_t tag, uint8_t tei)
+{
+ struct con_path *cp = talloc_zero(cg, struct con_path);
+
+ cp->ccp = ccp;
+ cp->ci = ci;
+ cp->tag = tag;
+ cp->tei = tei;
+ llist_add(&cp->list, &cg->paths);
+}
+
+static int con_group_del_path(struct con_group *cg, uint16_t ccp,
+ uint8_t ci, uint8_t tag, uint8_t tei)
+{
+ struct con_path *cp, *cp2;
+ llist_for_each_entry_safe(cp, cp2, &cg->paths, list) {
+ if (cp->ccp == ccp && cp->ci == ci && cp->tag == tag &&
+ cp->tei == tei) {
+ llist_del(&cp->list);
+ talloc_free(cp);
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+DEFUN(cfg_om2k_con_group, cfg_om2k_con_group_cmd,
+ "con-connection-group <1-31>",
+ "Configure a CON (Concentrator) Connection Group\n"
+ "CON Connection Group Number\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct con_group *cg;
+ uint8_t cgid = atoi(argv[0]);
+
+ if (bts->type != GSM_BTS_TYPE_RBS2000) {
+ vty_out(vty, "%% CON MO only exists in RBS2000%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ cg = con_group_find_or_create(bts, cgid);
+ if (!cg) {
+ vty_out(vty, "%% Cannot create CON Group%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty->node = OM2K_CON_GROUP_NODE;
+ vty->index = cg;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(del_om2k_con_group, del_om2k_con_group_cmd,
+ "del-connection-group <1-31>",
+ "Delete a CON (Concentrator) Connection Group\n"
+ "CON Connection Group Number\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int rc;
+ uint8_t cgid = atoi(argv[0]);
+
+ if (bts->type != GSM_BTS_TYPE_RBS2000) {
+ vty_out(vty, "%% CON MO only exists in RBS2000%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = con_group_del(bts, cgid);
+ if (rc != 0) {
+ vty_out(vty, "%% Cannot delete CON Group%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+#define CON_PATH_HELP "CON Path (In/Out)\n" \
+ "Add CON Path to Concentration Group\n" \
+ "Delete CON Path from Concentration Group\n" \
+ "CON Conection Point\n" \
+ "Contiguity Index\n" \
+
+DEFUN(cfg_om2k_con_path_dec, cfg_om2k_con_path_dec_cmd,
+ "con-path (add|del) <0-2047> <0-255> deconcentrated <0-63>",
+ CON_PATH_HELP "De-concentrated in/outlet\n" "TEI Value\n")
+{
+ struct con_group *cg = vty->index;
+ uint16_t ccp = atoi(argv[1]);
+ uint8_t ci = atoi(argv[2]);
+ uint8_t tei = atoi(argv[3]);
+
+ if (!strcmp(argv[0], "add"))
+ con_group_add_path(cg, ccp, ci, 0, tei);
+ else {
+ if (con_group_del_path(cg, ccp, ci, 0, tei) < 0) {
+ vty_out(vty, "%% No matching CON Path%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_om2k_con_path_conc, cfg_om2k_con_path_conc_cmd,
+ "con-path (add|del) <0-2047> <0-255> concentrated <1-16>",
+ CON_PATH_HELP "Concentrated in/outlet\n" "Tag Number\n")
+{
+ struct con_group *cg = vty->index;
+ uint16_t ccp = atoi(argv[1]);
+ uint8_t ci = atoi(argv[2]);
+ uint8_t tag = atoi(argv[3]);
+
+ if (!strcmp(argv[0], "add"))
+ con_group_add_path(cg, ccp, ci, tag, 0xff);
+ else {
+ if (con_group_del_path(cg, ccp, ci, tag, 0xff) < 0) {
+ vty_out(vty, "%% No matching CON list entry%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd,
+ "abis-lower-transport (single-timeslot|super-channel)",
+ "Configure thee Abis Lower Transport\n"
+ "Single Timeslot (classic Abis)\n"
+ "SuperChannel (Packet Abis)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct con_group *cg;
+
+ 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], "super-channel"))
+ bts->rbs2000.use_superchannel = 1;
+ else
+ bts->rbs2000.use_superchannel = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd,
+ "is-connection-list (add|del) <0-2047> <0-2047> <0-255>",
+ "Interface Switch Connection List\n"
+ "Add to IS list\n" "Delete from IS list\n"
+ "ICP1\n" "ICP2\n" "Contiguity Index\n")
+{
+ struct gsm_bts *bts = vty->index;
+ uint16_t icp1 = atoi(argv[1]);
+ uint16_t icp2 = atoi(argv[2]);
+ uint8_t ci = atoi(argv[3]);
+ struct is_conn_group *grp, *grp2;
+
+ if (bts->type != GSM_BTS_TYPE_RBS2000) {
+ vty_out(vty, "%% IS MO only exists in RBS2000%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "add")) {
+ grp = talloc_zero(bts, struct is_conn_group);
+ grp->icp1 = icp1;
+ grp->icp2 = icp2;
+ grp->ci = ci;
+ llist_add_tail(&grp->list, &bts->rbs2000.is.conn_groups);
+ } else {
+ llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.is.conn_groups, list) {
+ if (grp->icp1 == icp1 && grp->icp2 == icp2
+ && grp->ci == ci) {
+ llist_del(&grp->list);
+ talloc_free(grp);
+ return CMD_SUCCESS;
+ }
+ }
+ vty_out(vty, "%% No matching IS Conn Group found!%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(om2k_conf_req, om2k_conf_req_cmd,
+ "configuration-request",
+ "Send the configuration request for current MO\n")
+{
+ struct oml_node_state *oms = vty->index;
+ struct gsm_bts *bts = oms->bts;
+ struct gsm_bts_trx *trx = NULL;
+ struct gsm_bts_trx_ts *ts = NULL;
+
+ switch (oms->mo.class) {
+ case OM2K_MO_CLS_IS:
+ abis_om2k_tx_is_conf_req(bts);
+ break;
+ case OM2K_MO_CLS_TS:
+ trx = gsm_bts_trx_by_nr(bts, oms->mo.assoc_so);
+ if (!trx) {
+ vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr,
+ oms->mo.assoc_so, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (oms->mo.inst >= ARRAY_SIZE(trx->ts)) {
+ vty_out(vty, "%% Timeslot %u out of range%s",
+ oms->mo.inst, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ ts = &trx->ts[oms->mo.inst];
+ abis_om2k_tx_ts_conf_req(ts);
+ break;
+ case OM2K_MO_CLS_RX:
+ case OM2K_MO_CLS_TX:
+ case OM2K_MO_CLS_TRXC:
+ trx = gsm_bts_trx_by_nr(bts, oms->mo.inst);
+ if (!trx) {
+ vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr,
+ oms->mo.inst, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ switch (oms->mo.class) {
+ case OM2K_MO_CLS_RX:
+ abis_om2k_tx_rx_conf_req(trx);
+ break;
+ case OM2K_MO_CLS_TX:
+ abis_om2k_tx_tx_conf_req(trx);
+ break;
+ default:
+ break;
+ }
+ break;
+ case OM2K_MO_CLS_TF:
+ abis_om2k_tx_tf_conf_req(bts);
+ break;
+ default:
+ vty_out(vty, "%% Don't know how to configure MO%s",
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void dump_con_group(struct vty *vty, struct con_group *cg)
+{
+ struct con_path *cp;
+
+ llist_for_each_entry(cp, &cg->paths, list) {
+ vty_out(vty, " con-path add %u %u ", cp->ccp, cp->ci);
+ if (cp->tei == 0xff) {
+ vty_out(vty, "concentrated %u%s", cp->tag,
+ VTY_NEWLINE);
+ } else {
+ vty_out(vty, "deconcentrated %u%s", cp->tei,
+ VTY_NEWLINE);
+ }
+ }
+}
+
+void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+ struct is_conn_group *igrp;
+ struct con_group *cgrp;
+
+ llist_for_each_entry(igrp, &bts->rbs2000.is.conn_groups, list)
+ vty_out(vty, " is-connection-list add %u %u %u%s",
+ igrp->icp1, igrp->icp2, igrp->ci, VTY_NEWLINE);
+
+ llist_for_each_entry(cgrp, &bts->rbs2000.con.conn_groups, list) {
+ vty_out(vty, " con-connection-group %u%s", cgrp->cg,
+ VTY_NEWLINE);
+ dump_con_group(vty, cgrp);
+ }
+ if (bts->rbs2000.use_superchannel)
+ vty_out(vty, " abis-lower-transport super-channel%s",
+ VTY_NEWLINE);
+}
+
+int abis_om2k_vty_init(void)
+{
+ install_element(ENABLE_NODE, &om2k_class_inst_cmd);
+ install_element(ENABLE_NODE, &om2k_classnum_inst_cmd);
+ install_node(&om2k_node, dummy_config_write);
+
+ vty_install_default(OM2K_NODE);
+ install_element(OM2K_NODE, &om2k_reset_cmd);
+ install_element(OM2K_NODE, &om2k_start_cmd);
+ install_element(OM2K_NODE, &om2k_status_cmd);
+ install_element(OM2K_NODE, &om2k_connect_cmd);
+ install_element(OM2K_NODE, &om2k_disconnect_cmd);
+ install_element(OM2K_NODE, &om2k_enable_cmd);
+ install_element(OM2K_NODE, &om2k_disable_cmd);
+ install_element(OM2K_NODE, &om2k_op_info_cmd);
+ install_element(OM2K_NODE, &om2k_test_cmd);
+ install_element(OM2K_NODE, &om2k_cap_req_cmd);
+ install_element(OM2K_NODE, &om2k_conf_req_cmd);
+
+ install_node(&om2k_con_group_node, dummy_config_write);
+ vty_install_default(OM2K_CON_GROUP_NODE);
+ install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_dec_cmd);
+ install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_conc_cmd);
+
+ install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd);
+ install_element(BTS_NODE, &cfg_bts_alt_mode_cmd);
+ install_element(BTS_NODE, &cfg_om2k_con_group_cmd);
+ install_element(BTS_NODE, &del_om2k_con_group_cmd);
+
+ return 0;
+}
diff --git a/src/libbsc/abis_rsl.c b/src/libbsc/abis_rsl.c
new file mode 100644
index 000000000..6ae790f6c
--- /dev/null
+++ b/src/libbsc/abis_rsl.c
@@ -0,0 +1,2927 @@
+/* GSM Radio Signalling Link messages on the A-bis interface
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012 by Holger Hans Peter Freyther
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/debug.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/rtp_proxy.h>
+#include <openbsc/gsm_subscriber.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/pcu_if.h>
+
+#define RSL_ALLOC_SIZE 1024
+#define RSL_ALLOC_HEADROOM 128
+
+enum sacch_deact {
+ SACCH_NONE,
+ SACCH_DEACTIVATE,
+};
+
+static int rsl_send_imm_assignment(struct gsm_lchan *lchan);
+static void error_timeout_cb(void *data);
+static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts);
+static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc);
+static void dyn_ts_switchover_complete(struct gsm_lchan *lchan);
+
+static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan,
+ struct gsm_meas_rep *resp)
+{
+ struct lchan_signal_data sig;
+ sig.lchan = lchan;
+ sig.mr = resp;
+ osmo_signal_dispatch(SS_LCHAN, sig_no, &sig);
+}
+
+static void do_lchan_free(struct gsm_lchan *lchan)
+{
+ /* We start the error timer to make the channel available again */
+ if (lchan->state == LCHAN_S_REL_ERR) {
+ osmo_timer_setup(&lchan->error_timer, error_timeout_cb, lchan);
+ osmo_timer_schedule(&lchan->error_timer,
+ lchan->ts->trx->bts->network->T3111 + 2, 0);
+ } else {
+ rsl_lchan_set_state(lchan, LCHAN_S_NONE);
+ }
+ lchan_free(lchan);
+}
+
+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) {
+ case GSM48_CMODE_SPEECH_AMR:
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_AMR_H]);
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_V1_HR]);
+ break;
+ default:
+ break;
+ }
+ } else if (lchan->type == GSM_LCHAN_TCH_F) {
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_AMR:
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_AMR_F]);
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_V1_FR]);
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CODEC_EFR]);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static uint8_t mdisc_by_msgtype(uint8_t msg_type)
+{
+ /* mask off the transparent bit ? */
+ msg_type &= 0xfe;
+
+ if ((msg_type & 0xf0) == 0x00)
+ return ABIS_RSL_MDISC_RLL;
+ if ((msg_type & 0xf0) == 0x10) {
+ if (msg_type >= 0x19 && msg_type <= 0x22)
+ return ABIS_RSL_MDISC_TRX;
+ else
+ return ABIS_RSL_MDISC_COM_CHAN;
+ }
+ if ((msg_type & 0xe0) == 0x20)
+ return ABIS_RSL_MDISC_DED_CHAN;
+
+ return ABIS_RSL_MDISC_LOC;
+}
+
+static inline void init_dchan_hdr(struct abis_rsl_dchan_hdr *dh,
+ uint8_t msg_type)
+{
+ dh->c.msg_discr = mdisc_by_msgtype(msg_type);
+ dh->c.msg_type = msg_type;
+ dh->ie_chan = RSL_IE_CHAN_NR;
+}
+
+/* call rsl_lchan_lookup and set the log context */
+static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ const char *log_name)
+{
+ int rc;
+ struct gsm_lchan *lchan = rsl_lchan_lookup(trx, chan_nr, &rc);
+
+ if (!lchan) {
+ LOGP(DRSL, LOGL_ERROR, "%sunknown chan_nr=0x%02x\n",
+ log_name, chan_nr);
+ return NULL;
+ }
+
+ if (rc < 0)
+ LOGP(DRSL, LOGL_ERROR, "%s %smismatching chan_nr=0x%02x\n",
+ gsm_ts_and_pchan_name(lchan->ts), log_name, chan_nr);
+
+ if (lchan->conn)
+ log_set_context(LOG_CTX_VLR_SUBSCR, lchan->conn->subscr);
+
+ return lchan;
+}
+
+/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */
+uint64_t str_to_imsi(const char *imsi_str)
+{
+ uint64_t ret;
+
+ ret = strtoull(imsi_str, NULL, 10);
+
+ return ret;
+}
+
+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);
+
+ if (len < GSM_MACBLOCK_LEN)
+ memset(out+len, 0x2b, GSM_MACBLOCK_LEN - len);
+}
+
+/* Chapter 9.3.7: Encryption Information */
+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;
+}
+
+static void print_rsl_cause(int lvl, const uint8_t *cause_v, uint8_t cause_len)
+{
+ int i;
+
+ LOGPC(DRSL, lvl, "CAUSE=0x%02x(%s) ",
+ cause_v[0], rsl_err_name(cause_v[0]));
+ for (i = 1; i < cause_len-1; i++)
+ LOGPC(DRSL, lvl, "%02x ", cause_v[i]);
+}
+
+static void lchan_act_tmr_cb(void *data)
+{
+ struct gsm_lchan *lchan = data;
+
+ rsl_lchan_mark_broken(lchan, "activation timeout");
+ lchan_free(lchan);
+}
+
+static void lchan_deact_tmr_cb(void *data)
+{
+ struct gsm_lchan *lchan = data;
+
+ rsl_lchan_mark_broken(lchan, "de-activation timeout");
+ lchan_free(lchan);
+}
+
+
+/* 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)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ const struct gsm_bts *bts = trx->bts;
+ struct msgb *msg = rsl_msgb_alloc();
+ uint8_t type = osmo_sitype2rsl(si_type);
+
+ if (bts->c0 != trx)
+ LOGP(DRR, LOGL_ERROR, "Attempting to set BCCH SI%s on wrong BTS%u/TRX%u\n",
+ get_value_string(osmo_sitype_strs, si_type), bts->nr, trx->nr);
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof*dh);
+ init_dchan_hdr(dh, RSL_MT_BCCH_INFO);
+ dh->chan_nr = RSL_CHAN_BCCH;
+
+ if (trx->bts->type == GSM_BTS_TYPE_RBS2000
+ && type == RSL_SYSTEM_INFO_13) {
+ /* Ericsson proprietary encoding of SI13 */
+ msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, RSL_ERIC_SYSTEM_INFO_13);
+ msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data);
+ msgb_tv_put(msg, RSL_IE_ERIC_BCCH_MAPPING, 0x00);
+ } else {
+ /* Normal encoding */
+ msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+ msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data);
+ }
+
+ msg->dst = trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type,
+ const uint8_t *data, int len)
+{
+ struct abis_rsl_common_hdr *ch;
+ struct msgb *msg = rsl_msgb_alloc();
+
+ ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+ ch->msg_discr = ABIS_RSL_MDISC_TRX;
+ ch->msg_type = RSL_MT_SACCH_FILL;
+
+ msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+ msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data);
+
+ msg->dst = trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+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);
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ init_dchan_hdr(dh, RSL_MT_SACCH_INFO_MODIFY);
+ dh->chan_nr = chan_nr;
+
+ msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+ msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data);
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+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);
+
+ db = abs(db);
+ if (db > 30)
+ return -EINVAL;
+
+ msg = rsl_msgb_alloc();
+
+ lchan->bs_power = db/2;
+ if (fpc)
+ lchan->bs_power |= 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);
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ int ctl_lvl;
+
+ ctl_lvl = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, dbm);
+ if (ctl_lvl < 0)
+ return ctl_lvl;
+
+ msg = rsl_msgb_alloc();
+
+ lchan->ms_power = ctl_lvl;
+
+ if (fpc)
+ lchan->ms_power |= 0x20;
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ init_dchan_hdr(dh, RSL_MT_MS_POWER_CONTROL);
+ dh->chan_nr = chan_nr;
+
+ msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm,
+ struct gsm_lchan *lchan)
+{
+ 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;
+ if (lchan->ts->trx->bts->dtxd)
+ 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");
+
+ 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;
+ break;
+ case GSM_LCHAN_TCH_H:
+ 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));
+ return -EINVAL;
+ }
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ cm->chan_rate = 0;
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ cm->chan_rate = RSL_CMOD_SP_GSM1;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ cm->chan_rate = RSL_CMOD_SP_GSM2;
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ cm->chan_rate = RSL_CMOD_SP_GSM3;
+ break;
+ 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;
+ }
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "unsupported lchan->tch_mode %u\n",
+ lchan->tch_mode);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void mr_config_for_bts(struct gsm_lchan *lchan, struct msgb *msg)
+{
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+ msgb_tlv_put(msg, RSL_IE_MR_CONFIG, lchan->mr_bts_lv[0],
+ lchan->mr_bts_lv + 1);
+}
+
+static enum gsm_phys_chan_config pchan_for_lchant(enum gsm_chan_t type)
+{
+ switch (type) {
+ case GSM_LCHAN_TCH_F:
+ return GSM_PCHAN_TCH_F;
+ case GSM_LCHAN_TCH_H:
+ return GSM_PCHAN_TCH_H;
+ case GSM_LCHAN_NONE:
+ case GSM_LCHAN_PDTCH:
+ /* TODO: so far lchan->type is NONE in PDCH mode. PDTCH is only
+ * used in osmo-bts. Maybe set PDTCH and drop the NONE case
+ * here. */
+ return GSM_PCHAN_PDCH;
+ default:
+ return GSM_PCHAN_UNKNOWN;
+ }
+}
+
+/*! Tx simplified channel activation message for non-standard PDCH type. */
+static int rsl_chan_activate_lchan_as_pdch(struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ struct abis_rsl_dchan_hdr *dh;
+
+ /* This might be called after release of the second lchan of a TCH/H,
+ * but PDCH activation must always happen on the first lchan. Make sure
+ * the calling code passes the correct lchan. */
+ OSMO_ASSERT(lchan == lchan->ts->lchan);
+
+ rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+ 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_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_PDCH);
+
+ msgb_tv_put(msg, RSL_IE_ACT_TYPE, RSL_ACT_OSMO_PDCH);
+
+ if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_RBS2000 &&
+ lchan->ts->trx->bts->rbs2000.use_superchannel) {
+ const uint8_t eric_pgsl_tmr[] = { 30, 1 };
+ msgb_tv_fixed_put(msg, RSL_IE_ERIC_PGSL_TIMERS,
+ sizeof(eric_pgsl_tmr), eric_pgsl_tmr);
+ }
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.1 */
+int rsl_chan_activate_lchan(struct gsm_lchan *lchan, uint8_t act_type,
+ uint8_t ho_ref)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *msg;
+ int rc;
+ uint8_t *len;
+ uint8_t ta;
+
+ struct rsl_ie_chan_mode cm;
+ struct gsm48_chan_desc cd;
+
+ /* If a TCH_F/PDCH TS is in PDCH mode, deactivate PDCH first. */
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH
+ && (lchan->ts->flags & TS_F_PDCH_ACTIVE)) {
+ /* store activation type and handover reference */
+ lchan->dyn.act_type = act_type;
+ lchan->dyn.ho_ref = ho_ref;
+ return rsl_ipacc_pdch_activate(lchan->ts, 0);
+ }
+
+ /*
+ * If necessary, release PDCH on dynamic TS. Note that sending a
+ * release here is only necessary when in PDCH mode; for TCH types, an
+ * RSL RF Chan Release is initiated by the BTS when a voice call ends,
+ * so when we reach this, it will already be released. If a dyn TS is
+ * in PDCH mode, it is still active and we need to initiate a release
+ * from the BSC side here.
+ *
+ * If pchan_is != pchan_want, the PDCH has already been taken down and
+ * the switchover now needs to enable the TCH lchan.
+ *
+ * To switch a dyn TS between TCH/H and TCH/F, it is sufficient to send
+ * a chan activ with the new lchan type, because it will already be
+ * released.
+ */
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH
+ && lchan->ts->dyn.pchan_is == lchan->ts->dyn.pchan_want) {
+ enum gsm_phys_chan_config pchan_want;
+ pchan_want = pchan_for_lchant(lchan->type);
+ if (lchan->ts->dyn.pchan_is != pchan_want) {
+ /*
+ * Make sure to record on lchan[0] so that we'll find
+ * it after the PDCH release.
+ */
+ struct gsm_lchan *lchan0 = lchan->ts->lchan;
+ lchan0->dyn.act_type = act_type,
+ lchan0->dyn.ho_ref = ho_ref;
+ lchan0->dyn.rqd_ref = lchan->rqd_ref;
+ lchan0->dyn.rqd_ta = lchan->rqd_ta;
+ lchan->rqd_ref = NULL;
+ lchan->rqd_ta = 0;
+ DEBUGP(DRSL, "%s saved rqd_ref=%p ta=%u\n",
+ gsm_lchan_name(lchan0), lchan0->rqd_ref,
+ lchan0->rqd_ta);
+ return dyn_ts_switchover_start(lchan->ts, pchan_want);
+ }
+ }
+
+ DEBUGP(DRSL, "%s Tx RSL Channel Activate with act_type=%s\n",
+ gsm_ts_and_pchan_name(lchan->ts),
+ rsl_act_type_name(act_type));
+
+ if (act_type == RSL_ACT_OSMO_PDCH) {
+ if (lchan->ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s PDCH channel activation only allowed on %s\n",
+ gsm_ts_and_pchan_name(lchan->ts),
+ gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH));
+ return -EINVAL;
+ }
+ return rsl_chan_activate_lchan_as_pdch(lchan);
+ }
+
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && lchan->ts->dyn.pchan_want == GSM_PCHAN_PDCH) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Expected PDCH activation kind\n",
+ gsm_ts_and_pchan_name(lchan->ts));
+ return -EINVAL;
+ }
+
+ rc = channel_mode_from_lchan(&cm, lchan);
+ if (rc < 0) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Cannot find channel mode from lchan type\n",
+ gsm_ts_and_pchan_name(lchan->ts));
+ return rc;
+ }
+
+ rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+ ta = lchan->rqd_ta;
+
+ /* BS11 requires TA shifted by 2 bits */
+ if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11)
+ ta <<= 2;
+
+ memset(&cd, 0, sizeof(cd));
+ gsm48_lchan2chan_desc(&cd, lchan);
+
+ msg = rsl_msgb_alloc();
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV);
+
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ dh->chan_nr = gsm_lchan_as_pchan2chan_nr(
+ lchan, lchan->ts->dyn.pchan_want);
+ else
+ dh->chan_nr = gsm_lchan2chan_nr(lchan);
+
+ msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type);
+ msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
+ (uint8_t *) &cm);
+
+ /*
+ * The Channel Identification is needed for Phase1 phones
+ * and it contains the GSM48 Channel Description and the
+ * Mobile Allocation. The GSM 08.58 asks for the Mobile
+ * Allocation to have a length of zero. We are using the
+ * msgb_l3len to calculate the length of both messages.
+ */
+ msgb_v_put(msg, RSL_IE_CHAN_IDENT);
+ len = msgb_put(msg, 1);
+ msgb_tv_fixed_put(msg, GSM48_IE_CHANDESC_2, sizeof(cd), (const uint8_t *) &cd);
+
+ if (lchan->ts->hopping.enabled)
+ msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len,
+ lchan->ts->hopping.ma_data);
+ else
+ msgb_tlv_put(msg, GSM48_IE_MA_AFTER, 0, NULL);
+
+ /* update the calculated size */
+ msg->l3h = len + 1;
+ *len = msgb_l3len(msg);
+
+ if (lchan->encr.alg_id > RSL_ENC_ALG_A5(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);
+ }
+
+ switch (act_type) {
+ case RSL_ACT_INTER_ASYNC:
+ case RSL_ACT_INTER_SYNC:
+ msgb_tv_put(msg, RSL_IE_HANDO_REF, ho_ref);
+ break;
+ default:
+ break;
+ }
+
+ msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power);
+ msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power);
+ msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta);
+ mr_config_for_bts(lchan, msg);
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.9: Modify channel mode on BTS side */
+int rsl_chan_mode_modify_req(struct gsm_lchan *lchan)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *msg;
+ int rc;
+
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ struct rsl_ie_chan_mode cm;
+
+ rc = channel_mode_from_lchan(&cm, lchan);
+ if (rc < 0)
+ return rc;
+
+ msg = rsl_msgb_alloc();
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ init_dchan_hdr(dh, RSL_MT_MODE_MODIFY_REQ);
+ dh->chan_nr = chan_nr;
+
+ msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
+ (uint8_t *) &cm);
+
+ if (lchan->encr.alg_id > RSL_ENC_ALG_A5(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);
+ }
+
+ mr_config_for_bts(lchan, msg);
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.6: Send the encryption command with given L3 info */
+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;
+
+ /* First push the L3 IE tag and length */
+ msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len);
+
+ /* then the link identifier (SAPI0, main sign link) */
+ msgb_tv_push(msg, RSL_IE_LINK_IDENT, 0);
+
+ /* then encryption information */
+ rc = build_encr_info(encr_info, lchan);
+ if (rc <= 0)
+ return rc;
+ msgb_tlv_push(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+
+ /* and finally the DCHAN header */
+ dh = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dh));
+ init_dchan_hdr(dh, RSL_MT_ENCR_CMD);
+ dh->chan_nr = chan_nr;
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/* Chapter 8.4.5 / 4.6: Deactivate the SACCH after 04.08 RR CHAN RELEASE */
+int rsl_deact_sacch(struct gsm_lchan *lchan)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *msg = rsl_msgb_alloc();
+
+ 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);
+
+ msg->lchan = lchan;
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan));
+
+ return abis_rsl_sendmsg(msg);
+}
+
+static bool dyn_ts_should_switch_to_pdch(struct gsm_bts_trx_ts *ts)
+{
+ int ss;
+
+ if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ return false;
+
+ if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE)
+ return false;
+
+ /* Already in PDCH mode? */
+ if (ts->dyn.pchan_is == GSM_PCHAN_PDCH)
+ return false;
+
+ /* See if all lchans are released. */
+ for (ss = 0; ss < ts_subslots(ts); ss++) {
+ struct gsm_lchan *lc = &ts->lchan[ss];
+ if (lc->state != LCHAN_S_NONE) {
+ DEBUGP(DRSL, "%s lchan %u still in use"
+ " (type=%s,state=%s)\n",
+ gsm_ts_and_pchan_name(ts), lc->nr,
+ gsm_lchant_name(lc->type),
+ gsm_lchans_name(lc->state));
+ /* An lchan is still used. */
+ return false;
+ }
+ }
+
+ /* All channels are released, go to PDCH mode. */
+ DEBUGP(DRSL, "%s back to PDCH\n",
+ gsm_ts_and_pchan_name(ts));
+ return true;
+}
+
+static void error_timeout_cb(void *data)
+{
+ struct gsm_lchan *lchan = data;
+ if (lchan->state != LCHAN_S_REL_ERR) {
+ LOGP(DRSL, LOGL_ERROR, "%s error timeout but not in error state: %d\n",
+ gsm_lchan_name(lchan), lchan->state);
+ return;
+ }
+
+ /* go back to the none state */
+ LOGP(DRSL, LOGL_INFO, "%s is back in operation.\n", gsm_lchan_name(lchan));
+ rsl_lchan_set_state(lchan, LCHAN_S_NONE);
+
+ /* Put PDCH channel back into PDCH mode, if GPRS is enabled */
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH
+ && lchan->ts->trx->bts->gprs.mode != BTS_GPRS_NONE)
+ rsl_ipacc_pdch_activate(lchan->ts, 1);
+
+ if (dyn_ts_should_switch_to_pdch(lchan->ts))
+ dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH);
+}
+
+static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan);
+
+/* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */
+static int rsl_rf_chan_release(struct gsm_lchan *lchan, int error,
+ enum sacch_deact deact_sacch)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *msg;
+ int rc;
+
+ /* Stop timers that should lead to a channel release */
+ osmo_timer_del(&lchan->T3109);
+
+ if (lchan->state == LCHAN_S_REL_ERR) {
+ LOGP(DRSL, LOGL_NOTICE, "%s is in error state, not sending release.\n",
+ gsm_lchan_name(lchan));
+ return -1;
+ }
+
+ 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);
+
+ msg->lchan = lchan;
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ if (error)
+ DEBUGP(DRSL, "%s RF Channel Release due to error: %d\n",
+ gsm_lchan_name(lchan), error);
+ else
+ DEBUGP(DRSL, "%s RF Channel Release\n", gsm_lchan_name(lchan));
+
+ if (error) {
+ /*
+ * FIXME: GSM 04.08 gives us two options for the abnormal
+ * chanel release. This can be either like in the non-existent
+ * sub-lcuase 3.5.1 or for the main signalling link deactivate
+ * the SACCH, start timer T3109 and consider the channel as
+ * released.
+ *
+ * This code is doing the later for all raido links and not
+ * only the main link. Right now all SAPIs are released on the
+ * local end, the SACCH will be de-activated and right now the
+ * T3111 will be started. First T3109 should be started and then
+ * the T3111.
+ *
+ * TODO: Move this out of the function.
+ */
+
+ /*
+ * sacch de-activate and "local end release"
+ */
+ if (deact_sacch == SACCH_DEACTIVATE)
+ rsl_deact_sacch(lchan);
+ rsl_release_sapis_from(lchan, 0, RSL_REL_LOCAL_END);
+
+ /*
+ * TODO: start T3109 now.
+ */
+ rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR);
+ }
+
+ /* Start another timer or assume the BTS sends a ACK/NACK? */
+ osmo_timer_setup(&lchan->act_timer, lchan_deact_tmr_cb, lchan);
+ osmo_timer_schedule(&lchan->act_timer, 4, 0);
+
+ rc = abis_rsl_sendmsg(msg);
+
+ /* BTS will respond by RF CHAN REL ACK */
+ return rc;
+}
+
+/*
+ * Special handling for channel releases in the error case.
+ */
+static int rsl_rf_chan_release_err(struct gsm_lchan *lchan)
+{
+ enum sacch_deact sacch_deact;
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return 0;
+ switch (ts_pchan(lchan->ts)) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ sacch_deact = SACCH_DEACTIVATE;
+ break;
+ default:
+ sacch_deact = SACCH_NONE;
+ break;
+ }
+ return rsl_rf_chan_release(lchan, 1, sacch_deact);
+}
+
+static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan)
+{
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+
+ DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", gsm_lchan_name(lchan));
+
+ /* Stop all pending timers */
+ osmo_timer_del(&lchan->act_timer);
+ osmo_timer_del(&lchan->T3111);
+
+ /*
+ * The BTS didn't respond within the timeout to our channel
+ * release request and we have marked the channel as broken.
+ * Now we do receive an ACK and let's be conservative. If it
+ * is a sysmoBTS we know that only one RF Channel Release ACK
+ * will be sent. So let's "repair" the channel.
+ */
+ if (lchan->state == LCHAN_S_BROKEN) {
+ 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);
+ if (dyn_ts_should_switch_to_pdch(lchan->ts))
+ dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH);
+ return 0;
+ }
+
+ if (lchan->state != LCHAN_S_REL_REQ && lchan->state != LCHAN_S_REL_ERR)
+ LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n",
+ gsm_lchan_name(lchan),
+ gsm_lchans_name(lchan->state));
+
+ do_lchan_free(lchan);
+
+ /*
+ * Check Osmocom RSL CHAN ACT style dynamic TCH/F_TCH/H_PDCH TS for pending
+ * transitions in these cases:
+ *
+ * a) after PDCH was released due to switchover request, activate TCH.
+ * BSC initiated this switchover, so dyn.pchan_is != pchan_want and
+ * lchan->type has been set to the desired GSM_LCHAN_*.
+ *
+ * b) Voice call ended and a TCH is released. If the TS is now unused,
+ * switch to PDCH. Here still dyn.pchan_is == dyn.pchan_want because
+ * we're only just notified and may decide to switch to PDCH now.
+ */
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ DEBUGP(DRSL, "%s Rx RSL Channel Release ack for lchan %u\n",
+ gsm_ts_and_pchan_name(ts), lchan->nr);
+
+ /* (a) */
+ if (ts->dyn.pchan_is != ts->dyn.pchan_want)
+ return dyn_ts_switchover_continue(ts);
+
+ /* (b) */
+ if (dyn_ts_should_switch_to_pdch(ts))
+ return dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH);
+ }
+
+ /*
+ * Put a dynamic TCH/F_PDCH channel back to PDCH mode iff it was
+ * released successfully. If in error, the PDCH ACT will follow after
+ * T3111 in error_timeout_cb().
+ *
+ * Any state other than LCHAN_S_REL_ERR became LCHAN_S_NONE after above
+ * do_lchan_free(). Assert this, because that's what ensures a PDCH ACT
+ * on a TCH/F_PDCH TS in all cases.
+ *
+ * If GPRS is disabled, always skip the PDCH ACT.
+ */
+ OSMO_ASSERT(lchan->state == LCHAN_S_NONE
+ || lchan->state == LCHAN_S_REL_ERR);
+ if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE)
+ return 0;
+ if (ts->pchan == GSM_PCHAN_TCH_F_PDCH
+ && lchan->state == LCHAN_S_NONE)
+ return rsl_ipacc_pdch_activate(ts, 1);
+ return 0;
+}
+
+int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, uint8_t len,
+ uint8_t *ms_ident, uint8_t chan_needed, bool is_gprs)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *msg = rsl_msgb_alloc();
+
+ 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;
+
+ msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group);
+ msgb_tlv_put(msg, RSL_IE_MS_IDENTITY, len-2, ms_ident+2);
+ msgb_tv_put(msg, RSL_IE_CHAN_NEEDED, chan_needed);
+
+ /* Ericsson wants to have this IE in case a paging message
+ * relates to packet paging */
+ 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;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+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 *msg = rsl_msgb_alloc();
+ struct abis_rsl_dchan_hdr *dh;
+ uint8_t buf[GSM_MACBLOCK_LEN];
+
+ 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;
+
+ switch (bts->type) {
+ case GSM_BTS_TYPE_BS11:
+ msgb_tlv_put(msg, RSL_IE_IMM_ASS_INFO, len, val);
+ break;
+ default:
+ /* If phase 2, construct a FULL_IMM_ASS_INFO */
+ pad_macblock(buf, val, len);
+ msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, GSM_MACBLOCK_LEN,
+ buf);
+ break;
+ }
+
+ msg->dst = bts->c0->rsl_link;
+ return msg;
+}
+
+/* Chapter 8.5.6 */
+int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val)
+{
+ struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val);
+ if (!msg)
+ return 1;
+ 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)
+{
+ struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val);
+ if (!msg)
+ return 1;
+
+ /* 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);
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/* 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 abis_rsl_dchan_hdr *dh;
+
+ 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);
+ 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;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+
+/* Send "DATA REQUEST" message with given L3 Info payload */
+/* Chapter 8.3.1 */
+int rsl_data_request(struct msgb *msg, uint8_t link_id)
+{
+ if (msg->lchan == NULL) {
+ LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n");
+ return -EINVAL;
+ }
+
+ rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, gsm_lchan2chan_nr(msg->lchan),
+ link_id, 1);
+
+ msg->dst = msg->lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/* Send "ESTABLISH REQUEST" message with given L3 Info payload */
+/* Chapter 8.3.1 */
+int rsl_establish_request(struct gsm_lchan *lchan, uint8_t link_id)
+{
+ struct msgb *msg;
+
+ msg = rsl_rll_simple(RSL_MT_EST_REQ, gsm_lchan2chan_nr(lchan),
+ link_id, 0);
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ DEBUGP(DRLL, "%s RSL RLL ESTABLISH REQ (link_id=0x%02x)\n",
+ gsm_lchan_name(lchan), link_id);
+
+ return abis_rsl_sendmsg(msg);
+}
+
+static void rsl_handle_release(struct gsm_lchan *lchan);
+
+/* Special work handler to handle missing RSL_MT_REL_CONF message from
+ * Nokia InSite BTS */
+static void lchan_rel_work_cb(void *data)
+{
+ struct gsm_lchan *lchan = data;
+ int sapi;
+
+ for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) {
+ if (lchan->sapis[sapi] == LCHAN_SAPI_REL)
+ lchan->sapis[sapi] = LCHAN_SAPI_UNUSED;
+ }
+ rsl_handle_release(lchan);
+}
+
+/* Chapter 8.3.7 Request the release of multiframe mode of RLL connection.
+ This is what higher layers should call. The BTS then responds with
+ RELEASE CONFIRM, which we in turn use to trigger RSL CHANNEL RELEASE,
+ which in turn is acknowledged by RSL CHANNEL RELEASE ACK, which calls
+ lchan_free() */
+int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id,
+ enum rsl_rel_mode release_mode)
+{
+
+ struct msgb *msg;
+
+ msg = rsl_rll_simple(RSL_MT_REL_REQ, gsm_lchan2chan_nr(lchan),
+ link_id, 0);
+ /* 0 is normal release, 1 is local end */
+ msgb_tv_put(msg, RSL_IE_RELEASE_MODE, release_mode);
+
+ /* FIXME: start some timer in case we don't receive a REL ACK ? */
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ DEBUGP(DRLL, "%s RSL RLL RELEASE REQ (link_id=0x%02x, reason=%u)\n",
+ gsm_lchan_name(lchan), link_id, release_mode);
+
+ abis_rsl_sendmsg(msg);
+
+ /* Do not wait for Nokia BTS to send the confirm. */
+ if (is_nokia_bts(lchan->ts->trx->bts)
+ && lchan->ts->trx->bts->nokia.no_loc_rel_cnf
+ && release_mode == RSL_REL_LOCAL_END) {
+ DEBUGP(DRLL, "Scheduling release, becasuse Nokia InSite BTS does not send a RELease CONFirm.\n");
+ lchan->sapis[link_id & 0x7] = LCHAN_SAPI_REL;
+ osmo_timer_setup(&lchan->rel_work, lchan_rel_work_cb, lchan);
+ osmo_timer_schedule(&lchan->rel_work, 0, 0);
+ }
+
+ return 0;
+}
+
+int rsl_lchan_mark_broken(struct gsm_lchan *lchan, const char *reason)
+{
+ LOGP(DRSL, LOGL_ERROR, "%s %s lchan broken: %s\n",
+ gsm_lchan_name(lchan), gsm_lchant_name(lchan->type), reason);
+ rsl_lchan_set_state(lchan, LCHAN_S_BROKEN);
+ lchan->broken_reason = reason;
+ return 0;
+}
+
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int state)
+{
+ DEBUGP(DRSL, "%s state %s -> %s\n",
+ gsm_lchan_name(lchan), gsm_lchans_name(lchan->state),
+ gsm_lchans_name(state));
+ lchan->state = state;
+ return 0;
+}
+
+/* Chapter 8.4.2: Channel Activate Acknowledge */
+static int rsl_rx_chan_act_ack(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+ struct gsm_lchan *lchan = msg->lchan;
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+
+ /* BTS has confirmed channel activation, we now need
+ * to assign the activated channel to the MS */
+ if (rslh->ie_chan != RSL_IE_CHAN_NR)
+ return -EINVAL;
+
+ osmo_timer_del(&lchan->act_timer);
+
+ if (lchan->state == LCHAN_S_BROKEN) {
+ int do_release = is_sysmobts_v2(ts->trx->bts);
+ LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK for broken channel. %s\n",
+ gsm_lchan_name(lchan),
+ do_release ? "Releasing it" : "Keeping it broken");
+ if (do_release) {
+ talloc_free(lchan->rqd_ref);
+ lchan->rqd_ref = NULL;
+ lchan->rqd_ta = 0;
+ rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE);
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ /*
+ * lchan_act_tmr_cb() already called
+ * lchan_free() and cleared the lchan->type, so
+ * calling dyn_ts_switchover_complete() here
+ * would not have the desired effect of
+ * mimicking an activated lchan that we can
+ * release. Instead hack the dyn ts state to
+ * make sure that rsl_rx_rf_chan_rel_ack() will
+ * switch back to PDCH, i.e. have pchan_is ==
+ * pchan_want, both != GSM_PCHAN_PDCH:
+ */
+ ts->dyn.pchan_is = GSM_PCHAN_NONE;
+ ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ }
+ rsl_rf_chan_release(msg->lchan, 0, SACCH_NONE);
+ }
+ return 0;
+ }
+
+ if (lchan->state != LCHAN_S_ACT_REQ)
+ LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n",
+ gsm_lchan_name(lchan),
+ gsm_lchans_name(lchan->state));
+ rsl_lchan_set_state(lchan, LCHAN_S_ACTIVE);
+
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ dyn_ts_switchover_complete(lchan);
+
+ if (lchan->rqd_ref) {
+ rsl_send_imm_assignment(lchan);
+ talloc_free(lchan->rqd_ref);
+ lchan->rqd_ref = NULL;
+ lchan->rqd_ta = 0;
+ }
+
+ send_lchan_signal(S_LCHAN_ACTIVATE_ACK, lchan, NULL);
+
+ /* Update bts attributes inside the PCU */
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH ||
+ ts->pchan == GSM_PCHAN_TCH_F_PDCH ||
+ ts->pchan == GSM_PCHAN_PDCH)
+ pcu_info_update(ts->trx->bts);
+
+ return 0;
+}
+
+/* Chapter 8.4.3: Channel Activate NACK */
+static int rsl_rx_chan_act_nack(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+ struct tlv_parsed tp;
+
+ osmo_timer_del(&msg->lchan->act_timer);
+
+ if (msg->lchan->state == LCHAN_S_BROKEN) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s CHANNEL ACTIVATE NACK for broken channel.\n",
+ gsm_lchan_name(msg->lchan));
+ return -1;
+ }
+
+ LOGP(DRSL, LOGL_ERROR, "%s CHANNEL ACTIVATE NACK ",
+ gsm_lchan_name(msg->lchan));
+
+ /* BTS has rejected channel activation ?!? */
+ if (dh->ie_chan != RSL_IE_CHAN_NR)
+ return -EINVAL;
+
+ rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+ if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) {
+ const uint8_t *cause = TLVP_VAL(&tp, RSL_IE_CAUSE);
+ print_rsl_cause(LOGL_ERROR, cause,
+ TLVP_LEN(&tp, RSL_IE_CAUSE));
+ msg->lchan->error_cause = *cause;
+ if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC) {
+ rsl_lchan_mark_broken(msg->lchan, "NACK on activation");
+ } else
+ rsl_rf_chan_release(msg->lchan, 1, SACCH_DEACTIVATE);
+
+ } else {
+ rsl_lchan_mark_broken(msg->lchan, "NACK on activation no IE");
+ }
+
+ LOGPC(DRSL, LOGL_ERROR, "\n");
+
+ send_lchan_signal(S_LCHAN_ACTIVATE_NACK, msg->lchan, NULL);
+ return 0;
+}
+
+/* Chapter 8.4.4: Connection Failure Indication */
+static int rsl_rx_conn_fail(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+ struct tlv_parsed tp;
+
+ LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL: RELEASING state %s ",
+ gsm_lchan_name(msg->lchan),
+ gsm_lchans_name(msg->lchan->state));
+
+ rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+ if (TLVP_PRESENT(&tp, RSL_IE_CAUSE))
+ print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE),
+ TLVP_LEN(&tp, RSL_IE_CAUSE));
+
+ LOGPC(DRSL, LOGL_NOTICE, "\n");
+ rate_ctr_inc(&msg->lchan->ts->trx->bts->network->bsc_ctrs->ctr[BSC_CTR_CHAN_RF_FAIL]);
+ return rsl_rf_chan_release_err(msg->lchan);
+}
+
+static void print_meas_rep_uni(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);
+}
+
+static void print_meas_rep(struct gsm_lchan *lchan, struct gsm_meas_rep *mr)
+{
+ int i;
+ const char *name = "";
+
+ if (lchan && lchan->conn) {
+ if (lchan->conn->bsub)
+ name = bsc_subscr_name(lchan->conn->bsub);
+ else if (lchan->conn->subscr)
+ name = lchan->conn->subscr->imsi;
+ else
+ name = lchan->name;
+ }
+
+ DEBUGP(DMEAS, "[%s] MEASUREMENT RESULT NR=%d ", name, mr->nr);
+
+ if (mr->flags & MEAS_REP_F_DL_DTX)
+ DEBUGPC(DMEAS, "DTXd ");
+
+ print_meas_rep_uni(&mr->ul, "ul");
+ DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power);
+
+ if (mr->flags & MEAS_REP_F_MS_TO)
+ DEBUGPC(DMEAS, "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);
+ }
+
+ if (mr->flags & MEAS_REP_F_UL_DTX)
+ DEBUGPC(DMEAS, "DTXu ");
+ if (mr->flags & MEAS_REP_F_BA1)
+ DEBUGPC(DMEAS, "BA1 ");
+ if (!(mr->flags & MEAS_REP_F_DL_VALID))
+ DEBUGPC(DMEAS, "NOT VALID ");
+ else
+ print_meas_rep_uni(&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));
+ }
+}
+
+static int rsl_rx_meas_res(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+ struct tlv_parsed tp;
+ struct gsm_meas_rep *mr = lchan_next_meas_rep(msg->lchan);
+ uint8_t len;
+ const uint8_t *val;
+ int rc;
+
+ /* check if this channel is actually active */
+ /* FIXME: maybe this check should be way more generic/centralized */
+ if (msg->lchan->state != LCHAN_S_ACTIVE) {
+ LOGP(DRSL, LOGL_DEBUG, "%s: MEAS RES for inactive channel\n",
+ gsm_lchan_name(msg->lchan));
+ return 0;
+ }
+
+ memset(mr, 0, sizeof(*mr));
+ mr->lchan = msg->lchan;
+
+ rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) ||
+ !TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) ||
+ !TLVP_PRESENT(&tp, RSL_IE_BS_POWER))
+ return -EIO;
+
+ /* Mandatory Parts */
+ mr->nr = *TLVP_VAL(&tp, RSL_IE_MEAS_RES_NR);
+
+ len = TLVP_LEN(&tp, RSL_IE_UPLINK_MEAS);
+ val = TLVP_VAL(&tp, RSL_IE_UPLINK_MEAS);
+ if (len >= 3) {
+ if (val[0] & 0x40)
+ mr->flags |= MEAS_REP_F_DL_DTX;
+ mr->ul.full.rx_lev = val[0] & 0x3f;
+ mr->ul.sub.rx_lev = val[1] & 0x3f;
+ mr->ul.full.rx_qual = val[2]>>3 & 0x7;
+ mr->ul.sub.rx_qual = val[2] & 0x7;
+ }
+
+ mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER);
+
+ /* Optional Parts */
+ if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET)) {
+ /* According to 3GPP TS 48.058 § MS Timing Offset = Timing Offset field - 63 */
+ mr->ms_timing_offset = *TLVP_VAL(&tp, RSL_IE_MS_TIMING_OFFSET) - 63;
+ mr->flags |= MEAS_REP_F_MS_TO;
+ }
+
+ if (TLVP_PRESENT(&tp, RSL_IE_L1_INFO)) {
+ struct e1inp_sign_link *sign_link = msg->dst;
+
+ val = TLVP_VAL(&tp, RSL_IE_L1_INFO);
+ mr->flags |= MEAS_REP_F_MS_L1;
+ mr->ms_l1.pwr = ms_pwr_dbm(sign_link->trx->bts->band, val[0] >> 3);
+ 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 */
+ if (msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11
+ || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE)
+ mr->ms_l1.ta >>= 2;
+ }
+ if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+ msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO);
+ rc = gsm48_parse_meas_rep(mr, msg);
+ if (rc < 0)
+ return rc;
+ }
+
+ print_meas_rep(msg->lchan, mr);
+
+ send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr);
+
+ return 0;
+}
+
+/* Chapter 8.4.7 */
+static int rsl_rx_hando_det(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+ struct tlv_parsed tp;
+
+ DEBUGP(DRSL, "%s HANDOVER DETECT ", gsm_lchan_name(msg->lchan));
+
+ rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh));
+
+ if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY))
+ DEBUGPC(DRSL, "access delay = %u\n",
+ *TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY));
+ else
+ DEBUGPC(DRSL, "\n");
+
+ send_lchan_signal(S_LCHAN_HANDOVER_DETECT, msg->lchan, NULL);
+
+ return 0;
+}
+
+static bool lchan_may_change_pdch(struct gsm_lchan *lchan, bool pdch_act)
+{
+ struct gsm_bts_trx_ts *ts;
+
+ OSMO_ASSERT(lchan);
+
+ ts = lchan->ts;
+ OSMO_ASSERT(ts);
+ OSMO_ASSERT(ts->trx);
+ OSMO_ASSERT(ts->trx->bts);
+
+ if (lchan->ts->pchan != GSM_PCHAN_TCH_F_PDCH) {
+ LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK"
+ " for channel that is no TCH/F_PDCH\n",
+ gsm_lchan_name(lchan),
+ gsm_pchan_name(ts->pchan),
+ pdch_act? "ACT" : "DEACT");
+ return false;
+ }
+
+ if (lchan->state != LCHAN_S_NONE) {
+ LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK"
+ " in unexpected state: %s\n",
+ gsm_lchan_name(lchan),
+ gsm_pchan_name(ts->pchan),
+ pdch_act? "ACT" : "DEACT",
+ gsm_lchans_name(lchan->state));
+ return false;
+ }
+ return true;
+}
+
+static int rsl_rx_pdch_act_ack(struct msgb *msg)
+{
+ if (!lchan_may_change_pdch(msg->lchan, true))
+ return -EINVAL;
+
+ msg->lchan->ts->flags |= TS_F_PDCH_ACTIVE;
+ msg->lchan->ts->flags &= ~TS_F_PDCH_ACT_PENDING;
+
+ return 0;
+}
+
+static int rsl_rx_pdch_deact_ack(struct msgb *msg)
+{
+ if (!lchan_may_change_pdch(msg->lchan, false))
+ return -EINVAL;
+
+ msg->lchan->ts->flags &= ~TS_F_PDCH_ACTIVE;
+ msg->lchan->ts->flags &= ~TS_F_PDCH_DEACT_PENDING;
+
+ rsl_chan_activate_lchan(msg->lchan, msg->lchan->dyn.act_type,
+ msg->lchan->dyn.ho_ref);
+
+ return 0;
+}
+
+static int abis_rsl_rx_dchan(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+ int rc = 0;
+ char *ts_name;
+ struct e1inp_sign_link *sign_link = msg->dst;
+
+ msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr,
+ "Abis RSL rx DCHAN: ");
+ if (!msg->lchan)
+ return -1;
+ ts_name = gsm_lchan_name(msg->lchan);
+
+ switch (rslh->c.msg_type) {
+ case RSL_MT_CHAN_ACTIV_ACK:
+ DEBUGP(DRSL, "%s CHANNEL ACTIVATE ACK\n", ts_name);
+ rc = rsl_rx_chan_act_ack(msg);
+ count_codecs(sign_link->trx->bts, msg->lchan);
+ break;
+ case RSL_MT_CHAN_ACTIV_NACK:
+ rc = rsl_rx_chan_act_nack(msg);
+ break;
+ case RSL_MT_CONN_FAIL:
+ rc = rsl_rx_conn_fail(msg);
+ break;
+ case RSL_MT_MEAS_RES:
+ rc = rsl_rx_meas_res(msg);
+ break;
+ case RSL_MT_HANDO_DET:
+ rc = rsl_rx_hando_det(msg);
+ break;
+ case RSL_MT_RF_CHAN_REL_ACK:
+ rc = rsl_rx_rf_chan_rel_ack(msg->lchan);
+ break;
+ case RSL_MT_MODE_MODIFY_ACK:
+ count_codecs(sign_link->trx->bts, msg->lchan);
+ DEBUGP(DRSL, "%s CHANNEL MODE MODIFY ACK\n", ts_name);
+ break;
+ case RSL_MT_MODE_MODIFY_NACK:
+ LOGP(DRSL, LOGL_ERROR, "%s CHANNEL MODE MODIFY NACK\n", ts_name);
+ break;
+ case RSL_MT_IPAC_PDCH_ACT_ACK:
+ DEBUGP(DRSL, "%s IPAC PDCH ACT ACK\n", ts_name);
+ rc = rsl_rx_pdch_act_ack(msg);
+ break;
+ case RSL_MT_IPAC_PDCH_ACT_NACK:
+ LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH ACT NACK\n", ts_name);
+ break;
+ case RSL_MT_IPAC_PDCH_DEACT_ACK:
+ DEBUGP(DRSL, "%s IPAC PDCH DEACT ACK\n", ts_name);
+ rc = rsl_rx_pdch_deact_ack(msg);
+ break;
+ case RSL_MT_IPAC_PDCH_DEACT_NACK:
+ LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH DEACT NACK\n", ts_name);
+ 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:
+ LOGP(DRSL, LOGL_NOTICE, "%s Unimplemented Abis RSL DChan "
+ "msg 0x%02x\n", ts_name, rslh->c.msg_type);
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "%s unknown Abis RSL DChan msg 0x%02x\n",
+ ts_name, rslh->c.msg_type);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int rsl_rx_error_rep(struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+ struct tlv_parsed tp;
+ struct e1inp_sign_link *sign_link = msg->dst;
+
+ LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT ", gsm_trx_name(sign_link->trx));
+
+ rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh));
+
+ if (TLVP_PRESENT(&tp, RSL_IE_CAUSE))
+ print_rsl_cause(LOGL_ERROR, TLVP_VAL(&tp, RSL_IE_CAUSE),
+ TLVP_LEN(&tp, RSL_IE_CAUSE));
+
+ LOGPC(DRSL, LOGL_ERROR, "\n");
+
+ return 0;
+}
+
+static int abis_rsl_rx_trx(struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rslh = msgb_l2(msg);
+ struct e1inp_sign_link *sign_link = msg->dst;
+ int rc = 0;
+
+ switch (rslh->msg_type) {
+ case RSL_MT_ERROR_REPORT:
+ rc = rsl_rx_error_rep(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));
+ break;
+ case RSL_MT_OVERLOAD:
+ /* indicate CCCH / ACCH / processor overload */
+ LOGP(DRSL, LOGL_ERROR, "%s CCCH/ACCH/CPU Overload\n",
+ gsm_trx_name(sign_link->trx));
+ break;
+ case 0x42: /* Nokia specific: SI End ACK */
+ LOGP(DRSL, LOGL_INFO, "Nokia SI End ACK\n");
+ break;
+ case 0x43: /* Nokia specific: SI End NACK */
+ LOGP(DRSL, LOGL_INFO, "Nokia SI End NACK\n");
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "%s Unknown Abis RSL TRX message "
+ "type 0x%02x\n", gsm_trx_name(sign_link->trx), rslh->msg_type);
+ return -EINVAL;
+ }
+ return rc;
+}
+
+/* If T3101 expires, we never received a response to IMMEDIATE ASSIGN */
+static void t3101_expired(void *data)
+{
+ struct gsm_lchan *lchan = data;
+ LOGP(DRSL, LOGL_NOTICE,
+ "%s T3101 expired: no response to IMMEDIATE ASSIGN\n",
+ gsm_lchan_name(lchan));
+ rsl_rf_chan_release(lchan, 1, SACCH_DEACTIVATE);
+}
+
+/* If T3111 expires, we will send the RF Channel Request */
+static void t3111_expired(void *data)
+{
+ struct gsm_lchan *lchan = data;
+ LOGP(DRSL, LOGL_NOTICE,
+ "%s T3111 expired: releasing RF Channel\n",
+ gsm_lchan_name(lchan));
+ rsl_rf_chan_release(lchan, 0, SACCH_NONE);
+}
+
+/* If T3109 expires the MS has not send a UA/UM do the error release */
+static void t3109_expired(void *data)
+{
+ struct gsm_lchan *lchan = data;
+
+ LOGP(DRSL, LOGL_ERROR,
+ "%s SACCH deactivation timeout.\n", gsm_lchan_name(lchan));
+ rsl_rf_chan_release(lchan, 1, SACCH_NONE);
+}
+
+/* Format an IMM ASS REJ according to 04.08 Chapter 9.1.20 */
+static int rsl_send_imm_ass_rej(struct gsm_bts *bts,
+ unsigned int num_req_refs,
+ struct gsm48_req_ref *rqd_refs,
+ uint8_t wait_ind)
+{
+ uint8_t buf[GSM_MACBLOCK_LEN];
+ struct gsm48_imm_ass_rej *iar = (struct gsm48_imm_ass_rej *)buf;
+
+ /* create IMMEDIATE ASSIGN REJECT 04.08 message */
+ memset(iar, 0, sizeof(*iar));
+ iar->proto_discr = GSM48_PDISC_RR;
+ iar->msg_type = GSM48_MT_RR_IMM_ASS_REJ;
+ iar->page_mode = GSM48_PM_SAME;
+
+ memcpy(&iar->req_ref1, &rqd_refs[0], sizeof(iar->req_ref1));
+ iar->wait_ind1 = wait_ind;
+
+ if (num_req_refs >= 2)
+ memcpy(&iar->req_ref2, &rqd_refs[1], sizeof(iar->req_ref2));
+ else
+ memcpy(&iar->req_ref2, &rqd_refs[0], sizeof(iar->req_ref2));
+ iar->wait_ind2 = wait_ind;
+
+ if (num_req_refs >= 3)
+ memcpy(&iar->req_ref3, &rqd_refs[2], sizeof(iar->req_ref3));
+ else
+ memcpy(&iar->req_ref3, &rqd_refs[0], sizeof(iar->req_ref3));
+ iar->wait_ind3 = wait_ind;
+
+ if (num_req_refs >= 4)
+ memcpy(&iar->req_ref4, &rqd_refs[3], sizeof(iar->req_ref4));
+ else
+ memcpy(&iar->req_ref4, &rqd_refs[0], sizeof(iar->req_ref4));
+ iar->wait_ind4 = wait_ind;
+
+ /* we need to subtract 1 byte from sizeof(*iar) since ia includes the l2_plen field */
+ iar->l2_plen = GSM48_LEN2PLEN((sizeof(*iar)-1));
+
+ return rsl_imm_assign_cmd(bts, sizeof(*iar), (uint8_t *) iar);
+}
+
+/* Handle packet channel rach requests */
+static int rsl_rx_pchan_rqd(struct msgb *msg, struct gsm_bts *bts)
+{
+ struct gsm48_req_ref *rqd_ref;
+ struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg);
+ rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1];
+ uint8_t ra = rqd_ref->ra;
+ uint8_t t1, t2, t3;
+ uint32_t fn;
+ uint8_t rqd_ta;
+ uint8_t is_11bit;
+
+ /* Process rach request and forward contained information to PCU */
+ if (ra == 0x7F) {
+ is_11bit = 1;
+
+ /* FIXME: Also handle 11 bit rach requests */
+ LOGP(DRSL, LOGL_ERROR, "BTS %d eleven bit access burst not supported yet!\n",bts->nr);
+ 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_hdr->data[sizeof(struct gsm48_req_ref)+2];
+ }
+
+ return pcu_tx_rach_ind(bts, rqd_ta, ra, fn, is_11bit,
+ GSM_L1_BURST_TYPE_ACCESS_0);
+}
+
+/* MS has requested a channel on the RACH */
+static int rsl_rx_chan_rqd(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link = msg->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg);
+ struct gsm48_req_ref *rqd_ref;
+ enum gsm_chan_t lctype;
+ enum gsm_chreq_reason_t chreq_reason;
+ struct gsm_lchan *lchan;
+ uint8_t rqd_ta;
+ int is_lu;
+
+ uint16_t arfcn;
+ uint8_t subch;
+
+ /* parse request reference to be used in immediate assign */
+ if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE)
+ return -EINVAL;
+
+ rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1];
+
+ /* parse access delay and use as TA */
+ if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY)
+ return -EINVAL;
+ rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2];
+
+ /* Determine channel request cause code */
+ chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci);
+ LOGP(DRSL, LOGL_NOTICE, "BTS %d CHAN RQD: reason: %s (ra=0x%02x, neci=0x%02x, chreq_reason=0x%02x)\n",
+ msg->lchan->ts->trx->bts->nr,
+ get_value_string(gsm_chreq_descs, chreq_reason),
+ rqd_ref->ra, bts->network->neci, chreq_reason);
+
+ /* Handle PDCH related rach requests (in case of BSC-co-located-PCU */
+ if (chreq_reason == GSM_CHREQ_REASON_PDCH)
+ return rsl_rx_pchan_rqd(msg, bts);
+
+ /* determine channel type (SDCCH/TCH_F/TCH_H) based on
+ * request reference RA */
+ lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra);
+
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CHREQ_TOTAL]);
+
+ /*
+ * We want LOCATION UPDATES to succeed and will assign a TCH
+ * if we have no SDCCH available.
+ */
+ is_lu = !!(chreq_reason == GSM_CHREQ_REASON_LOCATION_UPD);
+
+ /* check availability / allocate channel */
+ lchan = lchan_alloc(bts, lctype, is_lu);
+ if (!lchan) {
+ LOGP(DRSL, LOGL_NOTICE, "BTS %d CHAN RQD: no resources for %s 0x%x\n",
+ msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra);
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_CHREQ_NO_CHANNEL]);
+ /* FIXME gather multiple CHAN RQD and reject up to 4 at the same time */
+ if (bts->network->T3122)
+ rsl_send_imm_ass_rej(bts, 1, rqd_ref, bts->network->T3122 & 0xff);
+ return 0;
+ }
+
+ /*
+ * Expecting lchan state to be NONE, except for dyn TS in PDCH mode.
+ * Those are expected to be ACTIVE: the PDCH release will be sent from
+ * rsl_chan_activate_lchan() below.
+ */
+ if (lchan->state != LCHAN_S_NONE
+ && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH
+ && lchan->state == LCHAN_S_ACTIVE))
+ LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel "
+ "in state %s\n", gsm_lchan_name(lchan),
+ gsm_lchans_name(lchan->state));
+
+ /* save the RACH data as we need it after the CHAN ACT ACK */
+ lchan->rqd_ref = talloc_zero(bts, struct gsm48_req_ref);
+ if (!lchan->rqd_ref) {
+ LOGP(DRSL, LOGL_ERROR, "Failed to allocate gsm48_req_ref.\n");
+ lchan_free(lchan);
+ return -ENOMEM;
+ }
+
+ memcpy(lchan->rqd_ref, rqd_ref, sizeof(*rqd_ref));
+ lchan->rqd_ta = rqd_ta;
+
+ arfcn = lchan->ts->trx->arfcn;
+ subch = lchan->nr;
+
+ lchan->encr.alg_id = RSL_ENC_ALG_A5(0); /* no encryption */
+ lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+ lchan->bs_power = 0; /* 0dB reduction, output power = Pn */
+ lchan->rsl_cmode = RSL_CMOD_SPD_SIGN;
+ lchan->tch_mode = GSM48_CMODE_SIGN;
+
+ /* Start another timer or assume the BTS sends a ACK/NACK? */
+ osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan);
+ osmo_timer_schedule(&lchan->act_timer, 4, 0);
+
+ DEBUGP(DRSL, "%s Activating ARFCN(%u) SS(%u) lctype %s "
+ "r=%s ra=0x%02x ta=%d\n", gsm_lchan_name(lchan), arfcn, subch,
+ gsm_lchant_name(lchan->type), gsm_chreq_name(chreq_reason),
+ rqd_ref->ra, rqd_ta);
+
+ rsl_chan_activate_lchan(lchan, RSL_ACT_INTRA_IMM_ASS, 0);
+
+ return 0;
+}
+
+static int rsl_send_imm_assignment(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ uint8_t buf[GSM_MACBLOCK_LEN];
+ struct gsm48_imm_ass *ia = (struct gsm48_imm_ass *) buf;
+
+ /* create IMMEDIATE ASSIGN 04.08 messge */
+ memset(ia, 0, sizeof(*ia));
+ /* we set ia->l2_plen once we know the length of the MA below */
+ ia->proto_discr = GSM48_PDISC_RR;
+ ia->msg_type = GSM48_MT_RR_IMM_ASS;
+ ia->page_mode = GSM48_PM_SAME;
+ gsm48_lchan2chan_desc(&ia->chan_desc, lchan);
+
+ /* use request reference extracted from CHAN_RQD */
+ memcpy(&ia->req_ref, lchan->rqd_ref, sizeof(ia->req_ref));
+ ia->timing_advance = lchan->rqd_ta;
+ if (!lchan->ts->hopping.enabled) {
+ ia->mob_alloc_len = 0;
+ } else {
+ ia->mob_alloc_len = lchan->ts->hopping.ma_len;
+ memcpy(ia->mob_alloc, lchan->ts->hopping.ma_data, ia->mob_alloc_len);
+ }
+ /* we need to subtract 1 byte from sizeof(*ia) since ia includes the l2_plen field */
+ ia->l2_plen = GSM48_LEN2PLEN((sizeof(*ia)-1) + ia->mob_alloc_len);
+
+ /* Start timer T3101 to wait for GSM48_MT_RR_PAG_RESP */
+ osmo_timer_setup(&lchan->T3101, t3101_expired, lchan);
+ osmo_timer_schedule(&lchan->T3101, bts->network->T3101, 0);
+
+ /* send IMMEDIATE ASSIGN CMD on RSL to BTS (to send on CCCH to MS) */
+ return rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (uint8_t *) ia);
+}
+
+/* current load on the CCCH */
+static int rsl_rx_ccch_load(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link = msg->dst;
+ struct abis_rsl_dchan_hdr *rslh = msgb_l2(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;
+
+ 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;
+ }
+ 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) {
+ sd.rach_slot_count = rslh->data[2] << 8 | rslh->data[3];
+ sd.rach_busy_count = rslh->data[4] << 8 | rslh->data[5];
+ sd.rach_access_count = rslh->data[6] << 8 | rslh->data[7];
+ osmo_signal_dispatch(SS_CCCH, S_CCCH_RACH_LOAD, &sd);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+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);
+ int rc = 0;
+ uint32_t tlli;
+
+ msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr,
+ "Abis RSL rx CCHAN: ");
+
+ switch (rslh->c.msg_type) {
+ case RSL_MT_CHAN_RQD:
+ /* MS has requested a channel on the RACH */
+ rc = rsl_rx_chan_rqd(msg);
+ break;
+ case RSL_MT_CCCH_LOAD_IND:
+ /* current load on the CCCH */
+ rc = rsl_rx_ccch_load(msg);
+ break;
+ case RSL_MT_DELETE_IND:
+ /* CCCH overloaded, IMM_ASSIGN was dropped */
+ case RSL_MT_CBCH_LOAD_IND:
+ /* current load on the CBCH */
+ LOGP(DRSL, LOGL_NOTICE, "Unimplemented Abis RSL TRX message "
+ "type 0x%02x\n", rslh->c.msg_type);
+ break;
+ case 0x10: /* Ericsson specific: Immediate Assign Sent */
+ /* FIXME: Replace the messy message parsing below
+ * with proper TV parser */
+ LOGP(DRSL, LOGL_INFO, "IMM.ass sent\n");
+ if(msg->len < 9)
+ LOGP(DRSL, LOGL_ERROR, "short IMM.ass sent message!\n");
+ else if(msg->data[4] != 0xf1)
+ LOGP(DRSL, LOGL_ERROR, "unsupported IMM.ass message format! (please fix)\n");
+ else {
+ msgb_pull(msg, 5); /* drop previous data to use msg_pull_u32 */
+ tlli = msgb_pull_u32(msg);
+ pcu_tx_imm_ass_sent(sign_link->trx->bts, tlli);
+ }
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "Unknown Abis RSL TRX message type "
+ "0x%02x\n", rslh->c.msg_type);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int rsl_rx_rll_err_ind(struct msgb *msg)
+{
+ struct tlv_parsed tp;
+ 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 (!TLVP_PRESENT(&tp, RSL_IE_RLM_CAUSE)) {
+ LOGP(DRLL, LOGL_ERROR,
+ "%s ERROR INDICATION without mandantory cause.\n",
+ gsm_lchan_name(msg->lchan));
+ return -1;
+ }
+
+ rlm_cause = *TLVP_VAL(&tp, RSL_IE_RLM_CAUSE);
+ LOGP(DRLL, LOGL_ERROR, "%s ERROR INDICATION cause=%s in state=%s\n",
+ gsm_lchan_name(msg->lchan),
+ rsl_rlm_cause_name(rlm_cause),
+ gsm_lchans_name(msg->lchan->state));
+
+ rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND);
+
+ if (rlm_cause == RLL_CAUSE_T200_EXPIRED) {
+ rate_ctr_inc(&msg->lchan->ts->trx->bts->network->bsc_ctrs->ctr[BSC_CTR_CHAN_RLL_ERR]);
+ return rsl_rf_chan_release_err(msg->lchan);
+ }
+
+ return 0;
+}
+
+static void rsl_handle_release(struct gsm_lchan *lchan)
+{
+ int sapi;
+ struct gsm_bts *bts;
+
+ /*
+ * Maybe only one link/SAPI was releasd or the error handling
+ * was activated. Just return now and let the other code handle
+ * it.
+ */
+ if (lchan->state != LCHAN_S_REL_REQ)
+ return;
+
+ for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) {
+ if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED)
+ continue;
+ LOGP(DRSL, LOGL_DEBUG, "%s waiting for SAPI=%d to be released.\n",
+ gsm_lchan_name(lchan), sapi);
+ return;
+ }
+
+
+ /* Stop T3109 and wait for T3111 before re-using the channel */
+ osmo_timer_del(&lchan->T3109);
+ osmo_timer_setup(&lchan->T3111, t3111_expired, lchan);
+ bts = lchan->ts->trx->bts;
+ osmo_timer_schedule(&lchan->T3111, bts->network->T3111, 0);
+}
+
+/* ESTABLISH INDICATION, LOCATION AREA UPDATE REQUEST
+ 0x02, 0x06,
+ 0x01, 0x20,
+ 0x02, 0x00,
+ 0x0b, 0x00, 0x0f, 0x05, 0x08, ... */
+
+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;
+ char *ts_name;
+ uint8_t sapi = rllh->link_id & 7;
+
+ msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr,
+ "Abis RSL rx RLL: ");
+ ts_name = gsm_lchan_name(msg->lchan);
+ DEBUGP(DRLL, "%s SAPI=%u ", ts_name, sapi);
+
+ switch (rllh->c.msg_type) {
+ case RSL_MT_DATA_IND:
+ DEBUGPC(DRLL, "DATA INDICATION\n");
+ if (msgb_l2len(msg) >
+ sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+ rllh->data[0] == RSL_IE_L3_INFO) {
+ msg->l3h = &rllh->data[3];
+ return gsm0408_rcvmsg(msg, rllh->link_id);
+ }
+ break;
+ case RSL_MT_EST_IND:
+ DEBUGPC(DRLL, "ESTABLISH INDICATION\n");
+ /* lchan is established, stop T3101 */
+ msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_MS;
+ osmo_timer_del(&msg->lchan->T3101);
+ if (msgb_l2len(msg) >
+ sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) &&
+ rllh->data[0] == RSL_IE_L3_INFO) {
+ msg->l3h = &rllh->data[3];
+ return gsm0408_rcvmsg(msg, rllh->link_id);
+ }
+ break;
+ case RSL_MT_EST_CONF:
+ DEBUGPC(DRLL, "ESTABLISH CONFIRM\n");
+ msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_NET;
+ rll_indication(msg->lchan, rllh->link_id,
+ BSC_RLLR_IND_EST_CONF);
+ break;
+ case RSL_MT_REL_IND:
+ /* BTS informs us of having received DISC from MS */
+ DEBUGPC(DRLL, "RELEASE INDICATION\n");
+ msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED;
+ rll_indication(msg->lchan, rllh->link_id,
+ BSC_RLLR_IND_REL_IND);
+ rsl_handle_release(msg->lchan);
+ break;
+ case RSL_MT_REL_CONF:
+ /* BTS informs us of having received UA from MS,
+ * in response to DISC that we've sent earlier */
+ DEBUGPC(DRLL, "RELEASE CONFIRMATION\n");
+ msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED;
+ rsl_handle_release(msg->lchan);
+ break;
+ case RSL_MT_ERROR_IND:
+ DEBUGPC(DRLL, "ERROR INDICATION\n");
+ rc = rsl_rx_rll_err_ind(msg);
+ break;
+ case RSL_MT_UNIT_DATA_IND:
+ DEBUGPC(DRLL, "UNIT DATA INDICATION\n");
+ LOGP(DRLL, LOGL_NOTICE, "unimplemented Abis RLL message "
+ "type 0x%02x\n", rllh->c.msg_type);
+ break;
+ default:
+ DEBUGPC(DRLL, "UNKNOWN\n");
+ LOGP(DRLL, LOGL_NOTICE, "unknown Abis RLL message "
+ "type 0x%02x\n", rllh->c.msg_type);
+ }
+ return rc;
+}
+
+static uint8_t ipa_smod_s_for_lchan(struct gsm_lchan *lchan)
+{
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return 0x00;
+ case GSM_LCHAN_TCH_H:
+ return 0x03;
+ default:
+ break;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return 0x01;
+ /* there's no half-rate EFR */
+ default:
+ break;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return 0x02;
+ case GSM_LCHAN_TCH_H:
+ return 0x05;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access speech mode for "
+ "tch_mode == 0x%02x\n", lchan->tch_mode);
+ return 0;
+}
+
+static uint8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan)
+{
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return RTP_PT_GSM_FULL;
+ case GSM_LCHAN_TCH_H:
+ return RTP_PT_GSM_HALF;
+ default:
+ break;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return RTP_PT_GSM_EFR;
+ /* there's no half-rate EFR */
+ default:
+ break;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ return RTP_PT_AMR;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access rtp payload type for "
+ "tch_mode == 0x%02x\n & lchan_type == %d",
+ lchan->tch_mode, lchan->type);
+ return 0;
+}
+
+/* ip.access specific RSL extensions */
+static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv)
+{
+ struct in_addr ip;
+ uint16_t port, conn_id;
+
+ if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_IP)) {
+ ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_LOCAL_IP);
+ DEBUGPC(DRSL, "LOCAL_IP=%s ", inet_ntoa(ip));
+ lchan->abis_ip.bound_ip = ntohl(ip.s_addr);
+ }
+
+ if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_PORT)) {
+ port = tlvp_val16_unal(tv, RSL_IE_IPAC_LOCAL_PORT);
+ port = ntohs(port);
+ DEBUGPC(DRSL, "LOCAL_PORT=%u ", port);
+ lchan->abis_ip.bound_port = port;
+ }
+
+ if (TLVP_PRESENT(tv, RSL_IE_IPAC_CONN_ID)) {
+ conn_id = tlvp_val16_unal(tv, RSL_IE_IPAC_CONN_ID);
+ conn_id = ntohs(conn_id);
+ DEBUGPC(DRSL, "CON_ID=%u ", conn_id);
+ lchan->abis_ip.conn_id = conn_id;
+ }
+
+ if (TLVP_PRESENT(tv, RSL_IE_IPAC_RTP_PAYLOAD2)) {
+ lchan->abis_ip.rtp_payload2 =
+ *TLVP_VAL(tv, RSL_IE_IPAC_RTP_PAYLOAD2);
+ DEBUGPC(DRSL, "RTP_PAYLOAD2=0x%02x ",
+ lchan->abis_ip.rtp_payload2);
+ }
+
+ if (TLVP_PRESENT(tv, RSL_IE_IPAC_SPEECH_MODE)) {
+ lchan->abis_ip.speech_mode =
+ *TLVP_VAL(tv, RSL_IE_IPAC_SPEECH_MODE);
+ DEBUGPC(DRSL, "speech_mode=0x%02x ",
+ lchan->abis_ip.speech_mode);
+ }
+
+ if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_IP)) {
+ ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_REMOTE_IP);
+ DEBUGPC(DRSL, "REMOTE_IP=%s ", inet_ntoa(ip));
+ lchan->abis_ip.connect_ip = ntohl(ip.s_addr);
+ }
+
+ if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_PORT)) {
+ port = tlvp_val16_unal(tv, RSL_IE_IPAC_REMOTE_PORT);
+ port = ntohs(port);
+ DEBUGPC(DRSL, "REMOTE_PORT=%u ", port);
+ lchan->abis_ip.connect_port = port;
+ }
+}
+
+/*! \brief Issue IPA RSL CRCX to configure RTP on BTS side
+ * \param[in] lchan Logical Channel for which we issue CRCX
+ */
+int rsl_ipacc_crcx(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = rsl_msgb_alloc();
+ struct abis_rsl_dchan_hdr *dh;
+
+ 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);
+
+ /* 0x1- == receive-only, 0x-1 == EFR codec */
+ lchan->abis_ip.speech_mode = 0x10 | ipa_smod_s_for_lchan(lchan);
+ lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
+ 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);
+
+ DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x RTP_PAYLOAD=%d\n",
+ gsm_lchan_name(lchan), lchan->abis_ip.speech_mode,
+ lchan->abis_ip.rtp_payload);
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/*! \brief Issue IPA RSL MDCX to configure MGW-side of RTP
+ * \param[in] lchan Logical Channel for which we issue MDCX
+ * \param[in] ip Remote (MGW) IP address for RTP
+ * \param[in] port Remote (MGW) UDP port number for RTP
+ * \param[in] rtp_payload2 Contents of RTP PAYLOAD 2 IE
+ */
+int rsl_ipacc_mdcx(struct gsm_lchan *lchan, uint32_t ip, uint16_t port,
+ uint8_t rtp_payload2)
+{
+ struct msgb *msg = rsl_msgb_alloc();
+ struct abis_rsl_dchan_hdr *dh;
+ uint32_t *att_ip;
+ struct in_addr ia;
+
+ 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);
+
+ /* we need to store these now as MDCX_ACK does not return them :( */
+ lchan->abis_ip.rtp_payload2 = rtp_payload2;
+ lchan->abis_ip.connect_port = port;
+ lchan->abis_ip.connect_ip = ip;
+
+ /* 0x0- == both directions, 0x-1 == EFR codec */
+ lchan->abis_ip.speech_mode = 0x00 | ipa_smod_s_for_lchan(lchan);
+ lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan);
+
+ ia.s_addr = htonl(ip);
+ DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD=%d RTP_PAYLOAD2=%d "
+ "CONN_ID=%d speech_mode=0x%02x\n", gsm_lchan_name(lchan),
+ inet_ntoa(ia), port, lchan->abis_ip.rtp_payload, rtp_payload2,
+ lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode);
+
+ 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(ip));
+ *att_ip = ia.s_addr;
+ msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, port);
+ 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 (rtp_payload2)
+ msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, rtp_payload2);
+
+ msg->dst = lchan->ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/* tell BTS to connect RTP stream to our local RTP socket */
+int rsl_ipacc_mdcx_to_rtpsock(struct gsm_lchan *lchan)
+{
+ struct rtp_socket *rs = lchan->abis_ip.rtp_socket;
+ int rc;
+
+ rc = rsl_ipacc_mdcx(lchan, ntohl(rs->rtp.sin_local.sin_addr.s_addr),
+ ntohs(rs->rtp.sin_local.sin_port),
+ /* FIXME: use RTP payload of bound socket, not BTS*/
+ lchan->abis_ip.rtp_payload2);
+
+ return rc;
+}
+
+int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act)
+{
+ struct msgb *msg = rsl_msgb_alloc();
+ struct abis_rsl_dchan_hdr *dh;
+ uint8_t msg_type;
+
+ if (ts->flags & TS_F_PDCH_PENDING_MASK) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s PDCH %s requested, but a PDCH%s%s is still pending\n",
+ gsm_ts_name(ts),
+ act ? "ACT" : "DEACT",
+ ts->flags & TS_F_PDCH_ACT_PENDING? " ACT" : "",
+ ts->flags & TS_F_PDCH_DEACT_PENDING? " DEACT" : "");
+ return -EINVAL;
+ }
+
+ if (act){
+ /* Callers should heed the GPRS mode. */
+ OSMO_ASSERT(ts->trx->bts->gprs.mode != BTS_GPRS_NONE);
+ msg_type = RSL_MT_IPAC_PDCH_ACT;
+ ts->flags |= TS_F_PDCH_ACT_PENDING;
+ } else {
+ msg_type = RSL_MT_IPAC_PDCH_DEACT;
+ ts->flags |= TS_F_PDCH_DEACT_PENDING;
+ }
+ /* TODO add timeout to cancel PDCH DE/ACT */
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ init_dchan_hdr(dh, msg_type);
+ dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ dh->chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_PDCH, ts->nr, 0);
+
+ DEBUGP(DRSL, "%s IPAC PDCH %sACT\n", gsm_ts_name(ts),
+ act ? "" : "DE");
+
+ msg->dst = ts->trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+ struct tlv_parsed tv;
+ struct gsm_lchan *lchan = msg->lchan;
+
+ /* the BTS has acknowledged a local bind, it now tells us the IP
+ * 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 (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) ||
+ !TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) ||
+ !TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) {
+ LOGP(DRSL, LOGL_NOTICE, "mandatory IE missing");
+ return -EINVAL;
+ }
+
+ ipac_parse_rtp(lchan, &tv);
+
+ osmo_signal_dispatch(SS_ABISIP, S_ABISIP_CRCX_ACK, msg->lchan);
+
+ return 0;
+}
+
+static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
+ struct tlv_parsed tv;
+ struct gsm_lchan *lchan = msg->lchan;
+
+ /* the BTS has acknowledged a remote connect request and
+ * 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));
+ ipac_parse_rtp(lchan, &tv);
+ osmo_signal_dispatch(SS_ABISIP, S_ABISIP_MDCX_ACK, msg->lchan);
+
+ return 0;
+}
+
+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 (TLVP_PRESENT(&tv, RSL_IE_CAUSE))
+ print_rsl_cause(LOGL_DEBUG, TLVP_VAL(&tv, RSL_IE_CAUSE),
+ TLVP_LEN(&tv, RSL_IE_CAUSE));
+
+ osmo_signal_dispatch(SS_ABISIP, S_ABISIP_DLCX_IND, msg->lchan);
+
+ return 0;
+}
+
+static int abis_rsl_rx_ipacc(struct msgb *msg)
+{
+ struct e1inp_sign_link *sign_link = msg->dst;
+ struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+ char *ts_name;
+ int rc = 0;
+
+ msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr,
+ "Abis RSL rx IPACC: ");
+ ts_name = gsm_lchan_name(msg->lchan);
+
+ switch (rllh->c.msg_type) {
+ case RSL_MT_IPAC_CRCX_ACK:
+ DEBUGP(DRSL, "%s IPAC_CRCX_ACK ", ts_name);
+ rc = abis_rsl_rx_ipacc_crcx_ack(msg);
+ break;
+ case RSL_MT_IPAC_CRCX_NACK:
+ /* somehow the BTS was unable to bind the lchan to its local
+ * port?!? */
+ LOGP(DRSL, LOGL_ERROR, "%s IPAC_CRCX_NACK\n", ts_name);
+ break;
+ case RSL_MT_IPAC_MDCX_ACK:
+ /* the BTS tells us that a connect operation was successful */
+ DEBUGP(DRSL, "%s IPAC_MDCX_ACK ", ts_name);
+ rc = abis_rsl_rx_ipacc_mdcx_ack(msg);
+ break;
+ case RSL_MT_IPAC_MDCX_NACK:
+ /* somehow the BTS was unable to connect the lchan to a remote
+ * port */
+ LOGP(DRSL, LOGL_ERROR, "%s IPAC_MDCX_NACK\n", ts_name);
+ break;
+ case RSL_MT_IPAC_DLCX_IND:
+ DEBUGP(DRSL, "%s IPAC_DLCX_IND ", ts_name);
+ rc = abis_rsl_rx_ipacc_dlcx_ind(msg);
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "Unknown ip.access msg_type 0x%02x\n",
+ rllh->c.msg_type);
+ break;
+ }
+ DEBUGPC(DRSL, "\n");
+
+ return rc;
+}
+
+int dyn_ts_switchover_start(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config to_pchan)
+{
+ int ss;
+ int rc = -EIO;
+
+ OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH);
+ DEBUGP(DRSL, "%s starting switchover to %s\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(to_pchan));
+
+ if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s: Attempt to switch dynamic channel to %s,"
+ " but is already in switchover.\n",
+ gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(to_pchan));
+ return ts->dyn.pchan_want == to_pchan? 0 : -EAGAIN;
+ }
+
+ if (ts->dyn.pchan_is == to_pchan) {
+ LOGP(DRSL, LOGL_INFO,
+ "%s %s Already is in %s mode, cannot switchover.\n",
+ gsm_ts_name(ts), gsm_pchan_name(ts->pchan),
+ gsm_pchan_name(to_pchan));
+ return -EINVAL;
+ }
+
+ /* Paranoia: let's make sure all is indeed released. */
+ for (ss = 0; ss < ts_subslots(ts); ss++) {
+ struct gsm_lchan *lc = &ts->lchan[ss];
+ if (lc->state != LCHAN_S_NONE) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Attempt to switch dynamic channel to %s,"
+ " but is not fully released.\n",
+ gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(to_pchan));
+ return -EAGAIN;
+ }
+ }
+
+ /* Record that we're busy switching. */
+ ts->dyn.pchan_want = to_pchan;
+
+ /*
+ * To switch from PDCH, we need to initiate the release from the BSC
+ * side. dyn_ts_switchover_continue() will be called from
+ * rsl_rx_rf_chan_rel_ack(). PDCH is always on lchan[0].
+ */
+ if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) {
+ rsl_lchan_set_state(ts->lchan, LCHAN_S_REL_REQ);
+ rc = rsl_rf_chan_release(ts->lchan, 0, SACCH_NONE);
+ if (rc) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s RSL RF Chan Release failed\n",
+ gsm_ts_and_pchan_name(ts));
+ return dyn_ts_switchover_failed(ts, rc);
+ }
+ return 0;
+ }
+
+ /*
+ * To switch from TCH/F and TCH/H pchans, this has been called from
+ * rsl_rx_rf_chan_rel_ack(), i.e. release is complete. Go ahead and
+ * activate as new type. This will always be PDCH.
+ */
+ return dyn_ts_switchover_continue(ts);
+}
+
+static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts)
+{
+ int rc;
+ uint8_t act_type;
+ uint8_t ho_ref;
+ int ss;
+ struct gsm_lchan *lchan;
+
+ OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH);
+ DEBUGP(DRSL, "%s switchover: release complete,"
+ " activating new pchan type\n",
+ gsm_ts_and_pchan_name(ts));
+
+ if (ts->dyn.pchan_is == ts->dyn.pchan_want) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Requested to switchover dynamic channel to the"
+ " same type it is already in.\n",
+ gsm_ts_and_pchan_name(ts));
+ return 0;
+ }
+
+ for (ss = 0; ss < ts_subslots(ts); ss++) {
+ lchan = &ts->lchan[ss];
+ if (lchan->rqd_ref) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s During dyn TS switchover, expecting no"
+ " Request Reference to be pending. Discarding!\n",
+ gsm_lchan_name(lchan));
+ talloc_free(lchan->rqd_ref);
+ lchan->rqd_ref = NULL;
+ }
+ }
+
+ /*
+ * When switching pchan modes, all lchans are unused. So always
+ * activate whatever wants to be activated on the first lchan. (We
+ * wouldn't remember to use lchan[1] across e.g. a PDCH deact anyway)
+ */
+ lchan = ts->lchan;
+
+ /*
+ * For TCH/x, the lchan->type has been set in lchan_alloc(), but it may
+ * have been lost during channel release due to dynamic switchover.
+ *
+ * For PDCH, the lchan->type will actually remain NONE.
+ * TODO: set GSM_LCHAN_PDTCH?
+ */
+ switch (ts->dyn.pchan_want) {
+ case GSM_PCHAN_TCH_F:
+ lchan->type = GSM_LCHAN_TCH_F;
+ break;
+ case GSM_PCHAN_TCH_H:
+ lchan->type = GSM_LCHAN_TCH_H;
+ break;
+ case GSM_PCHAN_PDCH:
+ lchan->type = GSM_LCHAN_NONE;
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Invalid target pchan for dynamic TS\n",
+ gsm_ts_and_pchan_name(ts));
+ }
+
+ act_type = (ts->dyn.pchan_want == GSM_PCHAN_PDCH)
+ ? RSL_ACT_OSMO_PDCH
+ : lchan->dyn.act_type;
+ ho_ref = (ts->dyn.pchan_want == GSM_PCHAN_PDCH)
+ ? 0
+ : lchan->dyn.ho_ref;
+
+ /* Fetch the rqd_ref back from before switchover started. */
+ lchan->rqd_ref = lchan->dyn.rqd_ref;
+ lchan->rqd_ta = lchan->dyn.rqd_ta;
+ lchan->dyn.rqd_ref = NULL;
+ lchan->dyn.rqd_ta = 0;
+
+ /* During switchover, we have received a release ack, which means that
+ * the act_timer has been stopped. Start the timer again so we mark
+ * this channel broken if the activation ack comes too late. */
+ osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan);
+ osmo_timer_schedule(&lchan->act_timer, 4, 0);
+
+ rc = rsl_chan_activate_lchan(lchan, act_type, ho_ref);
+ if (rc) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s RSL Chan Activate failed\n",
+ gsm_ts_and_pchan_name(ts));
+ return dyn_ts_switchover_failed(ts, rc);
+ }
+ return 0;
+}
+
+static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc)
+{
+ ts->dyn.pchan_want = ts->dyn.pchan_is;
+ LOGP(DRSL, LOGL_ERROR, "%s Error %d during dynamic channel switchover."
+ " Going back to previous pchan.\n", gsm_ts_and_pchan_name(ts),
+ rc);
+ return rc;
+}
+
+static void dyn_ts_switchover_complete(struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan_act;
+ enum gsm_phys_chan_config pchan_was;
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+
+ OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH);
+
+ pchan_act = pchan_for_lchant(lchan->type);
+ /*
+ * Paranoia: do the types match?
+ * In case of errors: we've received an act ack already, so what to do
+ * about it? Logging the error should suffice for now.
+ */
+ if (pchan_act != ts->dyn.pchan_want)
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Requested transition does not match lchan type %s\n",
+ gsm_ts_and_pchan_name(ts),
+ gsm_lchant_name(lchan->type));
+
+ pchan_was = ts->dyn.pchan_is;
+ ts->dyn.pchan_is = ts->dyn.pchan_want = pchan_act;
+
+ if (pchan_was != ts->dyn.pchan_is)
+ LOGP(DRSL, LOGL_INFO, "%s switchover from %s complete.\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan_was));
+}
+
+/* Entry-point where L2 RSL from BTS enters */
+int abis_rsl_rcvmsg(struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rslh;
+ int rc = 0;
+
+ if (!msg) {
+ DEBUGP(DRSL, "Empty RSL msg?..\n");
+ return -1;
+ }
+
+ if (msgb_l2len(msg) < sizeof(*rslh)) {
+ DEBUGP(DRSL, "Truncated RSL message with l2len: %u\n", msgb_l2len(msg));
+ msgb_free(msg);
+ return -1;
+ }
+
+ rslh = msgb_l2(msg);
+
+ switch (rslh->msg_discr & 0xfe) {
+ case ABIS_RSL_MDISC_RLL:
+ rc = abis_rsl_rx_rll(msg);
+ break;
+ case ABIS_RSL_MDISC_DED_CHAN:
+ rc = abis_rsl_rx_dchan(msg);
+ break;
+ case ABIS_RSL_MDISC_COM_CHAN:
+ rc = abis_rsl_rx_cchan(msg);
+ break;
+ case ABIS_RSL_MDISC_TRX:
+ rc = abis_rsl_rx_trx(msg);
+ break;
+ case ABIS_RSL_MDISC_LOC:
+ LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL msg disc 0x%02x\n",
+ rslh->msg_discr);
+ break;
+ case ABIS_RSL_MDISC_IPACCESS:
+ rc = abis_rsl_rx_ipacc(msg);
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "unknown RSL message discriminator "
+ "0x%02x\n", rslh->msg_discr);
+ rc = -EINVAL;
+ }
+ msgb_free(msg);
+ return rc;
+}
+
+int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number,
+ struct rsl_ie_cb_cmd_type cb_command,
+ const uint8_t *data, int len)
+{
+ struct abis_rsl_dchan_hdr *dh;
+ struct msgb *cb_cmd;
+
+ cb_cmd = rsl_msgb_alloc();
+ if (!cb_cmd)
+ return -1;
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(cb_cmd, sizeof(*dh));
+ init_dchan_hdr(dh, RSL_MT_SMS_BC_CMD);
+ dh->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN;
+ dh->chan_nr = chan_number; /* TODO: check the chan config */
+
+ msgb_tv_put(cb_cmd, RSL_IE_CB_CMD_TYPE, *(uint8_t*)&cb_command);
+ msgb_tlv_put(cb_cmd, RSL_IE_SMSCB_MSG, len, data);
+
+ cb_cmd->dst = bts->c0->rsl_link;
+
+ return abis_rsl_sendmsg(cb_cmd);
+}
+
+int rsl_nokia_si_begin(struct gsm_bts_trx *trx)
+{
+ struct abis_rsl_common_hdr *ch;
+ struct msgb *msg = rsl_msgb_alloc();
+
+ ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+ ch->msg_discr = ABIS_RSL_MDISC_TRX;
+ ch->msg_type = 0x40; /* Nokia SI Begin */
+
+ msg->dst = trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+int rsl_nokia_si_end(struct gsm_bts_trx *trx)
+{
+ struct abis_rsl_common_hdr *ch;
+ struct msgb *msg = rsl_msgb_alloc();
+
+ ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+ ch->msg_discr = ABIS_RSL_MDISC_TRX;
+ ch->msg_type = 0x41; /* Nokia SI End */
+
+ msgb_tv_put(msg, 0xFD, 0x00); /* Nokia Pagemode Info, No paging reorganisation required */
+
+ msg->dst = trx->rsl_link;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+int rsl_bs_power_control(struct gsm_bts_trx *trx, uint8_t channel, uint8_t reduction)
+{
+ struct abis_rsl_common_hdr *ch;
+ struct msgb *msg = rsl_msgb_alloc();
+
+ ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
+ ch->msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ ch->msg_type = RSL_MT_BS_POWER_CONTROL;
+
+ 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;
+
+ return abis_rsl_sendmsg(msg);
+}
+
+/**
+ * Release all allocated SAPIs starting from @param start and
+ * release them with the given release mode. Once the release
+ * confirmation arrives it will be attempted to release the
+ * the RF channel.
+ */
+int rsl_release_sapis_from(struct gsm_lchan *lchan, int start,
+ enum rsl_rel_mode release_mode)
+{
+ int no_sapi = 1;
+ int sapi;
+
+ for (sapi = start; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) {
+ uint8_t link_id;
+ if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED)
+ continue;
+
+ link_id = sapi;
+ if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H)
+ link_id |= 0x40;
+ rsl_release_request(lchan, link_id, release_mode);
+ no_sapi = 0;
+ }
+
+ return no_sapi;
+}
+
+int rsl_start_t3109(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ /* Disabled, mostly legacy code */
+ if (bts->network->T3109 == 0)
+ return -1;
+
+ osmo_timer_setup(&lchan->T3109, t3109_expired, lchan);
+ osmo_timer_schedule(&lchan->T3109, bts->network->T3109, 0);
+ return 0;
+}
+
+/**
+ * \brief directly RF Channel Release the lchan
+ *
+ * When no SAPI was allocated, directly release the logical channel. This
+ * should only be called from chan_alloc.c on channel release handling. In
+ * case no SAPI was established the RF Channel can be directly released,
+ */
+int rsl_direct_rf_release(struct gsm_lchan *lchan)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(lchan->sapis); ++i) {
+ if (lchan->sapis[i] != LCHAN_SAPI_UNUSED) {
+ LOGP(DRSL, LOGL_ERROR, "%s SAPI(%d) still allocated.\n",
+ gsm_lchan_name(lchan), i);
+ return -1;
+ }
+ }
+
+ /* Now release it */
+ return rsl_rf_chan_release(lchan, 0, SACCH_NONE);
+}
diff --git a/src/libbsc/arfcn_range_encode.c b/src/libbsc/arfcn_range_encode.c
new file mode 100644
index 000000000..9ca48407e
--- /dev/null
+++ b/src/libbsc/arfcn_range_encode.c
@@ -0,0 +1,330 @@
+/* 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 <openbsc/arfcn_range_encode.h>
+#include <openbsc/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;
+}
+
+/**
+ * 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)
+{
+ 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;
+
+
+ /* Test the two recursion anchors and stop processing */
+ if (size == 0)
+ return 0;
+
+ if (size == 1) {
+ out[index] = 1 + arfcns[0];
+ return 0;
+ }
+
+ /* 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;
+}
+
+/*
+ * 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;
+
+ /*
+ * 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/libbsc/bsc_api.c b/src/libbsc/bsc_api.c
new file mode 100644
index 000000000..7613cac90
--- /dev/null
+++ b/src/libbsc/bsc_api.c
@@ -0,0 +1,894 @@
+/* GSM 08.08 like API for OpenBSC. The bridge from MSC to BSC */
+
+/* (C) 2010-2011 by Holger Hans Peter Freyther
+ * (C) 2010-2011 by On-Waves
+ * (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 <openbsc/bsc_api.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/handover.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/trau_mux.h>
+
+#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmocom/core/talloc.h>
+
+#define GSM0808_T10_VALUE 6, 0
+
+
+static void rll_ind_cb(struct gsm_lchan *, uint8_t, void *, enum bsc_rllr_ind);
+static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id);
+static void handle_release(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan);
+static void handle_chan_ack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan);
+static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan);
+
+/* GSM 08.08 3.2.2.33 */
+static uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan)
+{
+ uint8_t channel_mode = 0, channel = 0;
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_SPEECH_AMR:
+ channel_mode = 0x9;
+ break;
+ case GSM48_CMODE_SIGN:
+ channel_mode = 0x8;
+ break;
+ case GSM48_CMODE_DATA_14k5:
+ channel_mode = 0xe;
+ break;
+ case GSM48_CMODE_DATA_12k0:
+ channel_mode = 0xb;
+ break;
+ case GSM48_CMODE_DATA_6k0:
+ channel_mode = 0xc;
+ break;
+ case GSM48_CMODE_DATA_3k6:
+ channel_mode = 0xd;
+ break;
+ }
+
+ switch (lchan->type) {
+ case GSM_LCHAN_NONE:
+ channel = 0x0;
+ break;
+ case GSM_LCHAN_SDCCH:
+ channel = 0x1;
+ break;
+ case GSM_LCHAN_TCH_F:
+ channel = 0x8;
+ break;
+ case GSM_LCHAN_TCH_H:
+ channel = 0x9;
+ break;
+ case GSM_LCHAN_UNKNOWN:
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Unknown lchan type: %p\n", lchan);
+ break;
+ }
+
+ return channel_mode << 4 | channel;
+}
+
+static uint8_t chan_mode_to_speech(struct gsm_lchan *lchan)
+{
+ int mode = 0;
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ mode = 1;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ mode = 0x11;
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ mode = 0x21;
+ break;
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ default:
+ LOGP(DMSC, LOGL_ERROR, "Using non speech mode: %d\n", mode);
+ return 0;
+ break;
+ }
+
+ /* assume to always do AMR HR on any TCH type */
+ if (lchan->type == GSM_LCHAN_TCH_H ||
+ lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+ mode |= 0x4;
+
+ return mode;
+}
+
+static void assignment_t10_timeout(void *_conn)
+{
+ struct bsc_api *api;
+ struct gsm_subscriber_connection *conn =
+ (struct gsm_subscriber_connection *) _conn;
+
+ LOGP(DMSC, LOGL_ERROR, "Assignment T10 timeout on %p\n", conn);
+
+ /*
+ * normal release on the secondary channel but only if the
+ * secondary_channel has not been released by the handle_chan_nack.
+ */
+ if (conn->secondary_lchan)
+ lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END);
+ conn->secondary_lchan = NULL;
+
+ /* inform them about the failure */
+ api = conn->network->bsc_api;
+ api->assign_fail(conn, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL);
+}
+
+/*! \brief Determine and apply AMR multi-rate configuration to lchan
+ * Determine which AMR multi-rate configuration to use and apply it to
+ * the lchan (so it can be communicated to BTS and MS during channel
+ * activation.
+ * \param[in] conn subscriber connection (used to resolve bsc_api)
+ * \param[out] lchan logical channel to which to apply mr config
+ * \param[in] full_rate whether to use full-rate (1) or half-rate (0) config
+ */
+static void handle_mr_config(struct gsm_subscriber_connection *conn,
+ struct gsm_lchan *lchan, int full_rate)
+{
+ struct bsc_api *api;
+ api = conn->network->bsc_api;
+ struct amr_multirate_conf *mr;
+ struct gsm48_multi_rate_conf *mr_conf;
+
+ /* BSC api override for this method, used in OsmoBSC mode with
+ * bsc_mr_config() to use MSC-specific/specified configuration */
+ if (api->mr_config)
+ return api->mr_config(conn, lchan, full_rate);
+
+ /* NITB case: use the BTS-specic multi-rate configuration from
+ * the vty/configuration file */
+ if (full_rate)
+ mr = &lchan->ts->trx->bts->mr_full;
+ else
+ mr = &lchan->ts->trx->bts->mr_half;
+
+ mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie;
+ mr_conf->ver = 1;
+
+ /* default, if no AMR codec defined */
+ if (!mr->gsm48_ie[1]) {
+ mr_conf->icmi = 1;
+ mr_conf->m5_90 = 1;
+ }
+ /* store encoded MR config IE lchan for both MS (uplink) and BTS
+ * (downlink) directions */
+ gsm48_multirate_config(lchan->mr_ms_lv, mr, mr->ms_mode);
+ gsm48_multirate_config(lchan->mr_bts_lv, mr, mr->bts_mode);
+}
+
+/*
+ * Start a new assignment and make sure that it is completed within T10 either
+ * positively, negatively or by the timeout.
+ *
+ * 1.) allocate a new lchan
+ * 2.) copy the encryption key and other data from the
+ * old to the new channel.
+ * 3.) RSL Channel Activate this channel and wait
+ *
+ * -> Signal handler for the LCHAN
+ * 4.) Send GSM 04.08 assignment command to the MS
+ *
+ * -> Assignment Complete/Assignment Failure
+ * 5.) Release the SDCCH, continue signalling on the new link
+ */
+static int handle_new_assignment(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate)
+{
+ struct gsm_lchan *new_lchan;
+ int chan_type;
+
+ chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H;
+
+ new_lchan = lchan_alloc(conn->bts, chan_type, 0);
+
+ if (!new_lchan) {
+ LOGP(DMSC, LOGL_NOTICE, "No free channel.\n");
+ return -1;
+ }
+
+ /* copy old data to the new channel */
+ memcpy(&new_lchan->encr, &conn->lchan->encr, sizeof(new_lchan->encr));
+ new_lchan->ms_power = conn->lchan->ms_power;
+ new_lchan->bs_power = conn->lchan->bs_power;
+ new_lchan->rqd_ta = conn->lchan->rqd_ta;
+
+ /* copy new data to it */
+ new_lchan->tch_mode = chan_mode;
+ new_lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+
+ /* handle AMR correctly */
+ if (chan_mode == GSM48_CMODE_SPEECH_AMR)
+ handle_mr_config(conn, new_lchan, full_rate);
+
+ if (rsl_chan_activate_lchan(new_lchan, 0x1, 0) < 0) {
+ LOGP(DHO, LOGL_ERROR, "could not activate channel\n");
+ lchan_free(new_lchan);
+ return -1;
+ }
+
+ /* remember that we have the channel */
+ conn->secondary_lchan = new_lchan;
+ new_lchan->conn = conn;
+
+ rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
+ return 0;
+}
+
+struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_lchan *lchan)
+{
+ struct gsm_subscriber_connection *conn;
+ struct gsm_network *net = lchan->ts->trx->bts->network;
+
+ conn = talloc_zero(net, struct gsm_subscriber_connection);
+ if (!conn)
+ return NULL;
+
+ conn->network = net;
+ conn->lchan = lchan;
+ conn->bts = lchan->ts->trx->bts;
+ conn->via_ran = RAN_GERAN_A;
+ lchan->conn = conn;
+ llist_add_tail(&conn->entry, &net->subscr_conns);
+ return conn;
+}
+
+void bsc_subscr_con_free(struct gsm_subscriber_connection *conn)
+{
+ if (!conn)
+ return;
+
+
+ if (conn->subscr) {
+ subscr_put(conn->subscr);
+ conn->subscr = NULL;
+ }
+
+
+ if (conn->ho_lchan) {
+ LOGP(DNM, LOGL_ERROR, "The ho_lchan should have been cleared.\n");
+ conn->ho_lchan->conn = NULL;
+ }
+
+ if (conn->lchan) {
+ LOGP(DNM, LOGL_ERROR, "The lchan should have been cleared.\n");
+ conn->lchan->conn = NULL;
+ }
+
+ if (conn->secondary_lchan) {
+ LOGP(DNM, LOGL_ERROR, "The secondary_lchan should have been cleared.\n");
+ conn->secondary_lchan->conn = NULL;
+ }
+
+ llist_del(&conn->entry);
+ talloc_free(conn);
+}
+
+int bsc_api_init(struct gsm_network *network, struct bsc_api *api)
+{
+ network->bsc_api = api;
+ return 0;
+}
+
+/*! \brief process incoming 08.08 DTAP from MSC (send via BTS to MS) */
+int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, int link_id, int allow_sacch)
+{
+ uint8_t sapi;
+
+
+ if (!conn->lchan) {
+ LOGP(DMSC, LOGL_ERROR,
+ "Called submit dtap without an lchan.\n");
+ msgb_free(msg);
+ return -1;
+ }
+
+ 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) {
+ if (conn->lchan->type == GSM_LCHAN_TCH_F || conn->lchan->type == GSM_LCHAN_TCH_H)
+ link_id |= 0x40;
+ }
+
+ msg->l3h = msg->data;
+ /* is requested SAPI already up? */
+ if (conn->lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) {
+ /* Establish L2 for additional SAPI */
+ OBSC_LINKID_CB(msg) = link_id;
+ if (rll_establish(msg->lchan, sapi, rll_ind_cb, msg) != 0) {
+ msgb_free(msg);
+ send_sapi_reject(conn, link_id);
+ return -1;
+ }
+ return 0;
+ } else {
+ /* Directly forward via RLL/RSL to BTS */
+ return rsl_data_request(msg, link_id);
+ }
+}
+
+/*
+ * \brief Check if the given channel is compatible with the mode/fullrate
+ */
+static int chan_compat_with_mode(struct gsm_lchan *lchan, int chan_mode, int full_rate)
+{
+ switch (chan_mode) {
+ case GSM48_CMODE_SIGN:
+ /* signalling is always possible */
+ return 1;
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_AMR:
+ case GSM48_CMODE_DATA_3k6:
+ case GSM48_CMODE_DATA_6k0:
+ /* these services can all run on TCH/H, but we may have
+ * an explicit override by the 'full_rate' argument */
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return 1;
+ case GSM_LCHAN_TCH_H:
+ if (full_rate)
+ return 0;
+ else
+ return 1;
+ break;
+ default:
+ return 0;
+ }
+ break;
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_SPEECH_EFR:
+ /* these services all explicitly require a TCH/F */
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ return 1;
+ else
+ return 0;
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * Send a GSM08.08 Assignment Request. Right now this does not contain the
+ * audio codec type or the allowed rates for the config. It is assumed that
+ * this is for audio handling only. In case the current channel does not allow
+ * the selected mode a new one will be allocated.
+ *
+ * TODO: Add multirate configuration, make it work for more than audio.
+ */
+int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate)
+{
+ struct bsc_api *api;
+ api = conn->network->bsc_api;
+
+ if (!chan_compat_with_mode(conn->lchan, chan_mode, full_rate)) {
+ if (handle_new_assignment(conn, chan_mode, full_rate) != 0)
+ goto error;
+ } else {
+ if (chan_mode == GSM48_CMODE_SPEECH_AMR)
+ handle_mr_config(conn, conn->lchan, full_rate);
+
+ LOGP(DMSC, LOGL_NOTICE,
+ "Sending %s ChanModify for speech: %s on channel %s\n",
+ gsm_lchan_name(conn->lchan),
+ get_value_string(gsm48_chan_mode_names, chan_mode),
+ get_value_string(gsm_chan_t_names, conn->lchan->type));
+ gsm48_lchan_modify(conn->lchan, chan_mode);
+ }
+
+ /* we will now start the timer to complete the assignment */
+ osmo_timer_setup(&conn->T10, assignment_t10_timeout, conn);
+ osmo_timer_schedule(&conn->T10, GSM0808_T10_VALUE);
+ return 0;
+
+error:
+ api->assign_fail(conn, 0, NULL);
+ return -1;
+}
+
+int gsm0808_page(struct gsm_bts *bts, unsigned int page_group, unsigned int mi_len,
+ uint8_t *mi, int chan_type)
+{
+ return rsl_paging_cmd(bts, page_group, mi_len, mi, chan_type, false);
+}
+
+static void handle_ass_compl(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm48_hdr *gh;
+ struct bsc_api *api = conn->network->bsc_api;
+
+ if (conn->secondary_lchan != msg->lchan) {
+ LOGP(DMSC, LOGL_ERROR, "Assignment Compl should occur on second lchan.\n");
+ return;
+ }
+
+ gh = msgb_l3(msg);
+ if (msgb_l3len(msg) - sizeof(*gh) != 1) {
+ LOGP(DMSC, LOGL_ERROR, "Assignment Compl invalid: %zu\n",
+ msgb_l3len(msg) - sizeof(*gh));
+ return;
+ }
+
+ /* switch TRAU muxer for E1 based BTS from one channel to another */
+ if (is_e1_bts(conn->bts))
+ switch_trau_mux(conn->lchan, conn->secondary_lchan);
+
+ /* swap channels */
+ osmo_timer_del(&conn->T10);
+
+ lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END);
+ conn->lchan = conn->secondary_lchan;
+ conn->secondary_lchan = NULL;
+
+ if (is_ipaccess_bts(conn->bts) && conn->lchan->tch_mode != GSM48_CMODE_SIGN)
+ rsl_ipacc_crcx(conn->lchan);
+
+ api->assign_compl(conn, gh->data[0],
+ lchan_to_chosen_channel(conn->lchan),
+ conn->lchan->encr.alg_id,
+ chan_mode_to_speech(conn->lchan));
+}
+
+static void handle_ass_fail(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct bsc_api *api = conn->network->bsc_api;
+ uint8_t *rr_failure;
+ struct gsm48_hdr *gh;
+
+
+ if (conn->lchan != msg->lchan) {
+ LOGP(DMSC, LOGL_ERROR, "Assignment failure should occur on primary lchan.\n");
+ return;
+ }
+
+ /* stop the timer and release it */
+ osmo_timer_del(&conn->T10);
+ lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END);
+ conn->secondary_lchan = NULL;
+
+ gh = msgb_l3(msg);
+ if (msgb_l3len(msg) - sizeof(*gh) != 1) {
+ LOGP(DMSC, LOGL_ERROR, "assignment failure unhandled: %zu\n",
+ msgb_l3len(msg) - sizeof(*gh));
+ rr_failure = NULL;
+ } else {
+ rr_failure = &gh->data[0];
+ }
+
+ api->assign_fail(conn,
+ GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE,
+ rr_failure);
+}
+
+static void handle_classmark_chg(struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+ uint8_t cm2_len, cm3_len = 0;
+ uint8_t *cm2, *cm3 = NULL;
+
+ DEBUGP(DRR, "CLASSMARK CHANGE ");
+
+ /* classmark 2 */
+ cm2_len = gh->data[0];
+ cm2 = &gh->data[1];
+ DEBUGPC(DRR, "CM2(len=%u) ", cm2_len);
+
+ if (payload_len > cm2_len + 1) {
+ /* we must have a classmark3 */
+ if (gh->data[cm2_len+1] != 0x20) {
+ DEBUGPC(DRR, "ERR CM3 TAG\n");
+ return;
+ }
+ if (cm2_len > 3) {
+ DEBUGPC(DRR, "CM2 too long!\n");
+ return;
+ }
+
+ cm3_len = gh->data[cm2_len+2];
+ cm3 = &gh->data[cm2_len+3];
+ if (cm3_len > 14) {
+ DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len);
+ return;
+ }
+ DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len);
+ }
+ api->classmark_chg(conn, cm2, cm2_len, cm3, cm3_len);
+}
+
+/* Chapter 9.1.16 Handover complete */
+static void handle_rr_ho_compl(struct msgb *msg)
+{
+ struct lchan_signal_data sig;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+
+ DEBUGP(DRR, "HANDOVER COMPLETE cause = %s\n",
+ rr_cause_name(gh->data[0]));
+
+ sig.lchan = msg->lchan;
+ sig.mr = NULL;
+ osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, &sig);
+ /* FIXME: release old channel */
+}
+
+/* Chapter 9.1.17 Handover Failure */
+static void handle_rr_ho_fail(struct msgb *msg)
+{
+ struct lchan_signal_data sig;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+
+ DEBUGP(DRR, "HANDOVER FAILED cause = %s\n",
+ rr_cause_name(gh->data[0]));
+
+ sig.lchan = msg->lchan;
+ sig.mr = NULL;
+ osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, &sig);
+ /* FIXME: release allocated new channel */
+}
+
+
+static void dispatch_dtap(struct gsm_subscriber_connection *conn,
+ uint8_t link_id, struct msgb *msg)
+{
+ struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api;
+ struct gsm48_hdr *gh;
+ uint8_t pdisc;
+ uint8_t msg_type;
+ int rc;
+
+ if (msgb_l3len(msg) < sizeof(*gh)) {
+ LOGP(DMSC, LOGL_ERROR, "Message too short for a GSM48 header.\n");
+ return;
+ }
+
+ gh = msgb_l3(msg);
+ pdisc = gsm48_hdr_pdisc(gh);
+ msg_type = gsm48_hdr_msg_type(gh);
+
+ /* the idea is to handle all RR messages here, and only hand
+ * MM/CC/SMS-CP/LCS up to the MSC. Some messages like PAGING
+ * RESPONSE or CM SERVICE REQUEST will not be covered here, as
+ * they are only possible in the first L3 message of each L2
+ * channel, i.e. 'conn' will not exist and gsm0408_rcvmsg()
+ * will call api->compl_l3() for it */
+ switch (pdisc) {
+ case GSM48_PDISC_RR:
+ switch (msg_type) {
+ case GSM48_MT_RR_GPRS_SUSP_REQ:
+ DEBUGP(DRR, "%s\n",
+ gsm48_rr_msg_name(GSM48_MT_RR_GPRS_SUSP_REQ));
+ break;
+ case GSM48_MT_RR_STATUS:
+ LOGP(DRR, LOGL_NOTICE, "%s (cause: %s)\n",
+ gsm48_rr_msg_name(GSM48_MT_RR_GPRS_SUSP_REQ),
+ rr_cause_name(gh->data[0]));
+ break;
+ case GSM48_MT_RR_MEAS_REP:
+ /* This shouldn't actually end up here, as RSL treats
+ * L3 Info of 08.58 MEASUREMENT REPORT different by calling
+ * directly into gsm48_parse_meas_rep */
+ LOGP(DMEAS, LOGL_ERROR, "DIRECT GSM48 MEASUREMENT REPORT ?!? ");
+ break;
+ case GSM48_MT_RR_HANDO_COMPL:
+ handle_rr_ho_compl(msg);
+ break;
+ case GSM48_MT_RR_HANDO_FAIL:
+ handle_rr_ho_fail(msg);
+ break;
+ case GSM48_MT_RR_CIPH_M_COMPL:
+ if (api->cipher_mode_compl)
+ api->cipher_mode_compl(conn, msg,
+ conn->lchan->encr.alg_id);
+ break;
+ case GSM48_MT_RR_ASS_COMPL:
+ handle_ass_compl(conn, msg);
+ break;
+ case GSM48_MT_RR_ASS_FAIL:
+ handle_ass_fail(conn, msg);
+ break;
+ case GSM48_MT_RR_CHAN_MODE_MODIF_ACK:
+ osmo_timer_del(&conn->T10);
+ rc = gsm48_rx_rr_modif_ack(msg);
+ if (rc < 0) {
+ api->assign_fail(conn,
+ GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+ NULL);
+ } else if (rc >= 0) {
+ api->assign_compl(conn, 0,
+ lchan_to_chosen_channel(conn->lchan),
+ conn->lchan->encr.alg_id,
+ chan_mode_to_speech(conn->lchan));
+ }
+ break;
+ case GSM48_MT_RR_CLSM_CHG:
+ handle_classmark_chg(conn, msg);
+ break;
+ case GSM48_MT_RR_APP_INFO:
+ /* Passing RR APP INFO to MSC, not quite
+ * according to spec */
+ if (api->dtap)
+ api->dtap(conn, link_id, msg);
+ break;
+ default:
+ /* Normally, a MSC should never receive RR
+ * messages, but we'd rather forward what we
+ * don't know than drop it... */
+ LOGP(DRR, LOGL_NOTICE,
+ "BSC: Passing %s 04.08 RR message to MSC\n",
+ gsm48_rr_msg_name(msg_type));
+ if (api->dtap)
+ api->dtap(conn, link_id, msg);
+ }
+ break;
+ default:
+ if (api->dtap)
+ api->dtap(conn, link_id, msg);
+ break;
+ }
+}
+
+/*! \brief RSL has received a DATA INDICATION with L3 from MS */
+int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id)
+{
+ int rc;
+ struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api;
+ struct gsm_lchan *lchan;
+
+ lchan = msg->lchan;
+ if (lchan->state != LCHAN_S_ACTIVE) {
+ LOGP(DRSL, LOGL_INFO, "Got data in non active state(%s), "
+ "discarding.\n", gsm_lchans_name(lchan->state));
+ return -1;
+ }
+
+
+ if (lchan->conn) {
+ /* if we already have a connection, forward via DTAP to
+ * MSC */
+ dispatch_dtap(lchan->conn, link_id, msg);
+ } else {
+ /* allocate a new connection */
+ rc = BSC_API_CONN_POL_REJECT;
+ lchan->conn = bsc_subscr_con_allocate(msg->lchan);
+ if (!lchan->conn) {
+ lchan_release(lchan, 1, RSL_REL_NORMAL);
+ return -1;
+ }
+
+ /* fwd via bsc_api to send COMPLETE L3 INFO to MSC */
+ rc = api->compl_l3(lchan->conn, msg, 0);
+
+ if (rc != BSC_API_CONN_POL_ACCEPT) {
+ lchan->conn->lchan = NULL;
+ bsc_subscr_con_free(lchan->conn);
+ lchan_release(lchan, 1, RSL_REL_NORMAL);
+ }
+ }
+
+ return 0;
+}
+
+/*! \brief We received a GSM 08.08 CIPHER MODE from the MSC */
+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, "Need to have an encrytpion key.\n");
+ return -1;
+ }
+
+ if (len > MAX_A5_KEY_LEN) {
+ LOGP(DRSL, LOGL_ERROR, "The key is too long: %d\n", len);
+ return -1;
+ }
+
+ 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);
+}
+
+/*
+ * Release all occupied RF Channels but stay around for more.
+ */
+int gsm0808_clear(struct gsm_subscriber_connection *conn)
+{
+ if (conn->ho_lchan)
+ bsc_clear_handover(conn, 1);
+
+ if (conn->secondary_lchan)
+ lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END);
+
+ if (conn->lchan)
+ lchan_release(conn->lchan, 1, RSL_REL_NORMAL);
+
+ conn->lchan = NULL;
+ conn->secondary_lchan = NULL;
+ conn->ho_lchan = NULL;
+ conn->bts = NULL;
+
+ osmo_timer_del(&conn->T10);
+
+ return 0;
+}
+
+static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id)
+{
+ struct bsc_api *api;
+
+ if (!conn)
+ return;
+
+ api = conn->network->bsc_api;
+ if (!api || !api->sapi_n_reject)
+ return;
+
+ api->sapi_n_reject(conn, link_id);
+}
+
+static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, enum bsc_rllr_ind rllr_ind)
+{
+ struct msgb *msg = _data;
+
+ /*
+ * There seems to be a small window that the RLL timer can
+ * fire after a lchan_release call and before the S_CHALLOC_FREED
+ * is called. Check if a conn is set before proceeding.
+ */
+ if (!lchan->conn)
+ return;
+
+ switch (rllr_ind) {
+ case BSC_RLLR_IND_EST_CONF:
+ rsl_data_request(msg, OBSC_LINKID_CB(msg));
+ break;
+ case BSC_RLLR_IND_REL_IND:
+ case BSC_RLLR_IND_ERR_IND:
+ case BSC_RLLR_IND_TIMEOUT:
+ send_sapi_reject(lchan->conn, OBSC_LINKID_CB(msg));
+ msgb_free(msg);
+ break;
+ }
+}
+
+static int bsc_handle_lchan_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct bsc_api *bsc;
+ struct gsm_lchan *lchan;
+ struct lchan_signal_data *lchan_data;
+
+ if (subsys != SS_LCHAN)
+ return 0;
+
+
+ lchan_data = signal_data;
+ if (!lchan_data->lchan || !lchan_data->lchan->conn)
+ return 0;
+
+ lchan = lchan_data->lchan;
+ bsc = lchan->ts->trx->bts->network->bsc_api;
+ if (!bsc)
+ return 0;
+
+ switch (signal) {
+ case S_LCHAN_UNEXPECTED_RELEASE:
+ handle_release(lchan->conn, bsc, lchan);
+ break;
+ case S_LCHAN_ACTIVATE_ACK:
+ handle_chan_ack(lchan->conn, bsc, lchan);
+ break;
+ case S_LCHAN_ACTIVATE_NACK:
+ handle_chan_nack(lchan->conn, bsc, lchan);
+ break;
+ }
+
+ return 0;
+}
+
+static void handle_release(struct gsm_subscriber_connection *conn,
+ struct bsc_api *bsc, struct gsm_lchan *lchan)
+{
+ int destruct = 1;
+
+ if (conn->secondary_lchan == lchan) {
+ osmo_timer_del(&conn->T10);
+ conn->secondary_lchan = NULL;
+
+ bsc->assign_fail(conn,
+ GSM0808_CAUSE_RADIO_INTERFACE_FAILURE,
+ NULL);
+ }
+
+ /* clear the connection now */
+ if (bsc->clear_request)
+ destruct = bsc->clear_request(conn, 0);
+
+ /* now give up all channels */
+ if (conn->lchan == lchan)
+ conn->lchan = NULL;
+ if (conn->ho_lchan == lchan) {
+ bsc_clear_handover(conn, 0);
+ conn->ho_lchan = NULL;
+ }
+ lchan->conn = NULL;
+
+ gsm0808_clear(conn);
+
+ if (destruct)
+ bsc_subscr_con_free(conn);
+}
+
+static void handle_chan_ack(struct gsm_subscriber_connection *conn,
+ struct bsc_api *api, struct gsm_lchan *lchan)
+{
+ if (conn->secondary_lchan != lchan)
+ return;
+
+ LOGP(DMSC, LOGL_NOTICE, "Sending assignment on chan: %p\n", lchan);
+ gsm48_send_rr_ass_cmd(conn->lchan, lchan, lchan->ms_power);
+}
+
+static void handle_chan_nack(struct gsm_subscriber_connection *conn,
+ struct bsc_api *api, struct gsm_lchan *lchan)
+{
+ if (conn->secondary_lchan != lchan)
+ return;
+
+ LOGP(DMSC, LOGL_ERROR, "Channel activation failed. Waiting for timeout now\n");
+ conn->secondary_lchan->conn = NULL;
+ conn->secondary_lchan = NULL;
+}
+
+static __attribute__((constructor)) void on_dso_load_bsc(void)
+{
+ osmo_signal_register_handler(SS_LCHAN, bsc_handle_lchan_signal, NULL);
+}
+
diff --git a/src/libbsc/bsc_ctrl_commands.c b/src/libbsc/bsc_ctrl_commands.c
new file mode 100644
index 000000000..641fe2bf6
--- /dev/null
+++ b/src/libbsc/bsc_ctrl_commands.c
@@ -0,0 +1,459 @@
+/*
+ * (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 <openbsc/ipaccess.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/debug.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/bsc_msc_data.h>
+
+#define CTRL_CMD_VTY_STRING(cmdname, cmdstr, dtype, element) \
+ CTRL_HELPER_GET_STRING(cmdname, dtype, element) \
+ CTRL_HELPER_SET_STRING(cmdname, dtype, element) \
+static struct ctrl_cmd_element cmd_##cmdname = { \
+ .name = cmdstr, \
+ .get = get_##cmdname, \
+ .set = set_##cmdname, \
+ .verify = verify_vty_description_string, \
+}
+
+/**
+ * Check that there are no newlines or comments or other things
+ * that could make the VTY configuration unparsable.
+ */
+static int verify_vty_description_string(struct ctrl_cmd *cmd,
+ const char *value, void *data)
+{
+ int i;
+ const size_t len = strlen(value);
+
+ for (i = 0; i < len; ++i) {
+ switch(value[i]) {
+ case '#':
+ case '\n':
+ case '\r':
+ cmd->reply = "String includes illegal character";
+ return -1;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+CTRL_CMD_DEFINE_RANGE(net_mnc, "mnc", struct gsm_network, network_code, 0, 999);
+CTRL_CMD_DEFINE_RANGE(net_mcc, "mcc", struct gsm_network, country_code, 1, 999);
+CTRL_CMD_VTY_STRING(net_short_name, "short-name", struct gsm_network, name_short);
+CTRL_CMD_VTY_STRING(net_long_name, "long-name", struct gsm_network, name_long);
+
+static int set_net_apply_config(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ if (!is_ipaccess_bts(bts))
+ continue;
+
+ /*
+ * The ip.access nanoBTS seems to be unrelaible on BSSGP
+ * so let's us just reboot it. For the sysmoBTS we can just
+ * restart the process as all state is gone.
+ */
+ if (!is_sysmobts_v2(bts) && strcmp(cmd->value, "restart") == 0) {
+ struct gsm_bts_trx *trx;
+ llist_for_each_entry_reverse(trx, &bts->trx_list, list)
+ abis_nm_ipaccess_restart(trx);
+ } else
+ ipaccess_drop_oml(bts);
+ }
+
+ cmd->reply = "Tried to drop the BTS";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(net_apply_config, "apply-configuration");
+
+static int verify_net_mcc_mnc_apply(struct ctrl_cmd *cmd, const char *value, void *d)
+{
+ char *tmp, *saveptr, *mcc, *mnc;
+
+ tmp = talloc_strdup(cmd, value);
+ if (!tmp)
+ return 1;
+
+ mcc = strtok_r(tmp, ",", &saveptr);
+ mnc = strtok_r(NULL, ",", &saveptr);
+ talloc_free(tmp);
+
+ if (!mcc || !mnc)
+ return 1;
+ return 0;
+}
+
+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;
+ int mcc, mnc;
+
+ tmp = talloc_strdup(cmd, cmd->value);
+ if (!tmp)
+ goto oom;
+
+
+ mcc_str = strtok_r(tmp, ",", &saveptr);
+ mnc_str = strtok_r(NULL, ",", &saveptr);
+
+ mcc = atoi(mcc_str);
+ mnc = atoi(mnc_str);
+
+ talloc_free(tmp);
+
+ if (net->network_code == mnc && net->country_code == mcc) {
+ cmd->reply = "Nothing changed";
+ return CTRL_CMD_REPLY;
+ }
+
+ net->network_code = mnc;
+ net->country_code = mcc;
+
+ return set_net_apply_config(cmd, data);
+
+oom:
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+}
+CTRL_CMD_DEFINE_WO(net_mcc_mnc_apply, "mcc-mnc-apply");
+
+/* BTS related commands below */
+CTRL_CMD_DEFINE_RANGE(bts_lac, "location-area-code", struct gsm_bts, location_area_code, 0, 65535);
+CTRL_CMD_DEFINE_RANGE(bts_ci, "cell-identity", struct gsm_bts, cell_identity, 0, 65535);
+
+static int set_bts_apply_config(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ if (!is_ipaccess_bts(bts)) {
+ cmd->reply = "BTS is not IP based";
+ return CTRL_CMD_ERROR;
+ }
+
+ ipaccess_drop_oml(bts);
+ cmd->reply = "Tried to drop the BTS";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(bts_apply_config, "apply-configuration");
+
+static int set_bts_si(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int rc;
+
+ rc = gsm_bts_set_system_infos(bts);
+ if (rc != 0) {
+ cmd->reply = "Failed to generate SI";
+ return CTRL_CMD_ERROR;
+ }
+
+ cmd->reply = "Generated new System Information";
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_WO_NOVRF(bts_si, "send-new-system-informations");
+
+static int get_bts_chan_load(struct ctrl_cmd *cmd, void *data)
+{
+ int i;
+ struct pchan_load pl;
+ struct gsm_bts *bts;
+ const char *space = "";
+
+ bts = cmd->node;
+ memset(&pl, 0, sizeof(pl));
+ bts_chan_load(&pl, bts);
+
+ cmd->reply = talloc_strdup(cmd, "");
+
+ for (i = 0; i < ARRAY_SIZE(pl.pchan); ++i) {
+ const struct load_counter *lc = &pl.pchan[i];
+
+ /* These can never have user load */
+ if (i == GSM_PCHAN_NONE)
+ continue;
+ if (i == GSM_PCHAN_CCCH)
+ continue;
+ if (i == GSM_PCHAN_PDCH)
+ continue;
+ if (i == GSM_PCHAN_UNKNOWN)
+ continue;
+
+ cmd->reply = talloc_asprintf_append(cmd->reply,
+ "%s%s,%u,%u",
+ space, gsm_pchan_name(i), lc->used, lc->total);
+ if (!cmd->reply)
+ goto error;
+ space = " ";
+ }
+
+ return CTRL_CMD_REPLY;
+
+error:
+ cmd->reply = "Memory allocation failure";
+ return CTRL_CMD_ERROR;
+}
+
+CTRL_CMD_DEFINE_RO(bts_chan_load, "channel-load");
+
+static int get_bts_oml_conn(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = bts->oml_link ? "connected" : "disconnected";
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_RO(bts_oml_conn, "oml-connection-state");
+
+static int verify_bts_gprs_mode(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int valid;
+ enum bts_gprs_mode mode;
+ struct gsm_bts *bts = cmd->node;
+
+ mode = bts_gprs_mode_parse(value, &valid);
+ if (!valid) {
+ cmd->reply = "Mode is not known";
+ return 1;
+ }
+
+ if (!bts_gprs_mode_is_compat(bts, mode)) {
+ cmd->reply = "bts does not support this mode";
+ return 1;
+ }
+
+ return 0;
+}
+
+static int get_bts_gprs_mode(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ cmd->reply = talloc_strdup(cmd, bts_gprs_mode_name(bts->gprs.mode));
+ return CTRL_CMD_REPLY;
+}
+
+static int set_bts_gprs_mode(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+
+ bts->gprs.mode = bts_gprs_mode_parse(cmd->value, NULL);
+ return get_bts_gprs_mode(cmd, data);
+}
+
+CTRL_CMD_DEFINE(bts_gprs_mode, "gprs-mode");
+
+static int get_bts_rf_state(struct ctrl_cmd *cmd, void *data)
+{
+ const char *oper, *admin, *policy;
+ struct gsm_bts *bts = cmd->node;
+
+ if (!bts) {
+ cmd->reply = "bts not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts));
+ admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
+ policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
+
+ cmd->reply = talloc_asprintf(cmd, "%s,%s,%s", oper, admin, policy);
+ if (!cmd->reply) {
+ cmd->reply = "OOM.";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(bts_rf_state, "rf_state");
+
+static int get_net_rf_lock(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+ struct gsm_bts *bts;
+ const char *policy_name;
+
+ policy_name = osmo_bsc_rf_get_policy_name(net->bsc_data->rf_ctrl->policy);
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ struct gsm_bts_trx *trx;
+
+ /* Exclude the BTS from the global lock */
+ if (bts->excl_from_rf_lock)
+ continue;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->mo.nm_state.availability == NM_AVSTATE_OK &&
+ trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) {
+ cmd->reply = talloc_asprintf(cmd,
+ "state=on,policy=%s,bts=%u,trx=%u",
+ policy_name, bts->nr, trx->nr);
+ return CTRL_CMD_REPLY;
+ }
+ }
+ }
+
+ cmd->reply = talloc_asprintf(cmd, "state=off,policy=%s",
+ policy_name);
+ return CTRL_CMD_REPLY;
+}
+
+#define TIME_FORMAT_RFC2822 "%a, %d %b %Y %T %z"
+
+static int set_net_rf_lock(struct ctrl_cmd *cmd, void *data)
+{
+ int locked = atoi(cmd->value);
+ struct gsm_network *net = cmd->node;
+ time_t now = time(NULL);
+ char now_buf[64];
+ struct osmo_bsc_rf *rf;
+
+ if (!net) {
+ cmd->reply = "net not found.";
+ return CTRL_CMD_ERROR;
+ }
+
+ rf = net->bsc_data->rf_ctrl;
+
+ if (!rf) {
+ cmd->reply = "RF Ctrl is not enabled in the BSC Configuration";
+ return CTRL_CMD_ERROR;
+ }
+
+ talloc_free(rf->last_rf_lock_ctrl_command);
+ strftime(now_buf, sizeof(now_buf), TIME_FORMAT_RFC2822, gmtime(&now));
+ rf->last_rf_lock_ctrl_command =
+ talloc_asprintf(rf, "rf_locked %u (%s)", locked, now_buf);
+
+ osmo_bsc_rf_schedule_lock(rf, locked == 1 ? '0' : '1');
+
+ cmd->reply = talloc_asprintf(cmd, "%u", locked);
+ if (!cmd->reply) {
+ cmd->reply = "OOM.";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+}
+
+static int verify_net_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ int locked = atoi(cmd->value);
+
+ if ((locked != 0) && (locked != 1))
+ return 1;
+
+ return 0;
+}
+CTRL_CMD_DEFINE(net_rf_lock, "rf_locked");
+
+static int get_net_bts_num(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_network *net = cmd->node;
+
+ cmd->reply = talloc_asprintf(cmd, "%u", net->num_bts);
+ return CTRL_CMD_REPLY;
+}
+CTRL_CMD_DEFINE_RO(net_bts_num, "number-of-bts");
+
+/* TRX related commands below here */
+CTRL_HELPER_GET_INT(trx_max_power, struct gsm_bts_trx, max_power_red);
+static int verify_trx_max_power(struct ctrl_cmd *cmd, const char *value, void *_data)
+{
+ int tmp = atoi(value);
+
+ if (tmp < 0 || tmp > 22) {
+ cmd->reply = "Value must be between 0 and 22";
+ return -1;
+ }
+
+ if (tmp & 1) {
+ cmd->reply = "Value must be even";
+ return -1;
+ }
+
+ return 0;
+}
+CTRL_CMD_DEFINE_RANGE(trx_arfcn, "arfcn", struct gsm_bts_trx, arfcn, 0, 1023);
+
+static int set_trx_max_power(struct ctrl_cmd *cmd, void *_data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ int old_power;
+
+ /* remember the old value, set the new one */
+ old_power = trx->max_power_red;
+ trx->max_power_red = atoi(cmd->value);
+
+ /* Maybe update the value */
+ if (old_power != trx->max_power_red) {
+ LOGP(DCTRL, LOGL_NOTICE,
+ "%s updating max_pwr_red(%d)\n",
+ gsm_trx_name(trx), trx->max_power_red);
+ abis_nm_update_max_power_red(trx);
+ }
+
+ return get_trx_max_power(cmd, _data);
+}
+CTRL_CMD_DEFINE(trx_max_power, "max-power-reduction");
+
+int bsc_base_ctrl_cmds_install(void)
+{
+ int rc = 0;
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mnc);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_short_name);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_long_name);
+ 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_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/libbsc/bsc_ctrl_lookup.c b/src/libbsc/bsc_ctrl_lookup.c
new file mode 100644
index 000000000..a8a8cf526
--- /dev/null
+++ b/src/libbsc/bsc_ctrl_lookup.c
@@ -0,0 +1,107 @@
+/* SNMP-like status interface. Look-up of BTS/TRX
+ *
+ * (C) 2010-2011 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2010-2011 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/ctrl/control_if.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+
+extern vector ctrl_node_vec;
+
+/*! \brief control interface lookup function for bsc/bts gsm_data
+ * \param[in] data Private data passed to controlif_setup()
+ * \param[in] vline Vector of the line holding the command string
+ * \param[out] node_type type (CTRL_NODE_) that was determined
+ * \param[out] node_data private dta of node that was determined
+ * \param i Current index into vline, up to which it is parsed
+ */
+static int bsc_ctrl_node_lookup(void *data, vector vline, int *node_type,
+ void **node_data, int *i)
+{
+ struct gsm_network *net = data;
+ struct gsm_bts *bts = NULL;
+ struct gsm_bts_trx *trx = NULL;
+ struct gsm_bts_trx_ts *ts = NULL;
+ char *token = vector_slot(vline, *i);
+ long num;
+
+ /* TODO: We need to make sure that the following chars are digits
+ * and/or use strtol to check if number conversion was successful
+ * Right now something like net.bts_stats will not work */
+ if (!strcmp(token, "bts")) {
+ if (*node_type != CTRL_NODE_ROOT || !net)
+ goto err_missing;
+ (*i)++;
+ if (!ctrl_parse_get_num(vline, *i, &num))
+ goto err_index;
+
+ bts = gsm_bts_num(net, num);
+ if (!bts)
+ goto err_missing;
+ *node_data = bts;
+ *node_type = CTRL_NODE_BTS;
+ } else if (!strcmp(token, "trx")) {
+ if (*node_type != CTRL_NODE_BTS || !*node_data)
+ goto err_missing;
+ bts = *node_data;
+ (*i)++;
+ if (!ctrl_parse_get_num(vline, *i, &num))
+ goto err_index;
+
+ trx = gsm_bts_trx_num(bts, num);
+ if (!trx)
+ goto err_missing;
+ *node_data = trx;
+ *node_type = CTRL_NODE_TRX;
+ } else if (!strcmp(token, "ts")) {
+ if (*node_type != CTRL_NODE_TRX || !*node_data)
+ goto err_missing;
+ trx = *node_data;
+ (*i)++;
+ if (!ctrl_parse_get_num(vline, *i, &num))
+ goto err_index;
+
+ if ((num >= 0) && (num < TRX_NR_TS))
+ ts = &trx->ts[num];
+ if (!ts)
+ goto err_missing;
+ *node_data = ts;
+ *node_type = CTRL_NODE_TS;
+ } else
+ return 0;
+
+ return 1;
+err_missing:
+ return -ENODEV;
+err_index:
+ return -ERANGE;
+}
+
+struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net,
+ const char *bind_addr, uint16_t port)
+{
+ return ctrl_interface_setup_dynip(net, bind_addr, port,
+ bsc_ctrl_node_lookup);
+}
diff --git a/src/libbsc/bsc_dyn_ts.c b/src/libbsc/bsc_dyn_ts.c
new file mode 100644
index 000000000..e5422fc5c
--- /dev/null
+++ b/src/libbsc/bsc_dyn_ts.c
@@ -0,0 +1,77 @@
+/* Dynamic PDCH initialisation implementation shared across NM and RSL */
+
+/* (C) 2016 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/core/logging.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_rsl.h>
+
+void tchf_pdch_ts_init(struct gsm_bts_trx_ts *ts)
+{
+ int rc;
+
+ if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, "%s: GPRS mode is 'none':"
+ " not activating PDCH.\n",
+ gsm_ts_and_pchan_name(ts));
+ return;
+ }
+
+ LOGP(DRSL, LOGL_DEBUG, "%s: trying to PDCH ACT\n",
+ gsm_ts_and_pchan_name(ts));
+
+ rc = rsl_ipacc_pdch_activate(ts, 1);
+ if (rc != 0)
+ LOGP(DRSL, LOGL_ERROR, "%s %s: PDCH ACT failed\n",
+ gsm_ts_name(ts), gsm_pchan_name(ts->pchan));
+}
+
+void tchf_tchh_pdch_ts_init(struct gsm_bts_trx_ts *ts)
+{
+ if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, "%s: GPRS mode is 'none':"
+ " not activating PDCH.\n",
+ gsm_ts_and_pchan_name(ts));
+ return;
+ }
+
+ dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH);
+}
+
+void dyn_ts_init(struct gsm_bts_trx_ts *ts)
+{
+ /* Clear all TCH/F_PDCH flags */
+ ts->flags &= ~(TS_F_PDCH_PENDING_MASK | TS_F_PDCH_ACTIVE);
+
+ /* Clear TCH/F_TCH/H_PDCH state */
+ ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ ts->dyn.pending_chan_activ = NULL;
+
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ tchf_pdch_ts_init(ts);
+ break;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ tchf_tchh_pdch_ts_init(ts);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/src/libbsc/bsc_init.c b/src/libbsc/bsc_init.c
new file mode 100644
index 000000000..ec87a7bc7
--- /dev/null
+++ b/src/libbsc/bsc_init.c
@@ -0,0 +1,552 @@
+/* A hackish minimal BSC (+MSC +HLR) implementation */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.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 <openbsc/gsm_data.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/debug.h>
+#include <openbsc/misdn.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/ports.h>
+#include <openbsc/system_information.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/chan_alloc.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/ipaccess.h>
+#include <osmocom/gsm/sysinfo.h>
+#include <openbsc/e1_config.h>
+#include <openbsc/common_bsc.h>
+#include <openbsc/pcu_if.h>
+#include <limits.h>
+
+/* global pointer to the gsm network data structure */
+extern struct gsm_network *bsc_gsmnet;
+
+/* Callback function for NACK on the OML NM */
+static int oml_msg_nack(struct nm_nack_signal_data *nack)
+{
+ if (nack->mt == NM_MT_GET_ATTR_NACK) {
+ LOGP(DNM, LOGL_ERROR, "BTS%u does not support Get Attributes "
+ "OML message.\n", nack->bts->nr);
+ return 0;
+ }
+
+ if (nack->mt == NM_MT_SET_BTS_ATTR_NACK)
+ LOGP(DNM, LOGL_ERROR, "Failed to set BTS attributes. That is fatal. "
+ "Was the bts type and frequency properly specified?\n");
+ else
+ LOGP(DNM, LOGL_ERROR, "Got %s NACK going to drop the OML links.\n",
+ abis_nm_nack_name(nack->mt));
+
+ if (!nack->bts) {
+ LOGP(DNM, LOGL_ERROR, "Unknown bts. Can not drop it.\n");
+ return 0;
+ }
+
+ if (is_ipaccess_bts(nack->bts))
+ ipaccess_drop_oml(nack->bts);
+
+ return 0;
+}
+
+/* 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_nack_signal_data *nack;
+
+ switch (signal) {
+ case S_NM_NACK:
+ nack = signal_data;
+ return oml_msg_nack(nack);
+ default:
+ break;
+ }
+ return 0;
+}
+
+int bsc_shutdown_net(struct gsm_network *net)
+{
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ LOGP(DNM, LOGL_NOTICE, "shutting down OML for BTS %u\n", bts->nr);
+ osmo_signal_dispatch(SS_L_GLOBAL, S_GLOBAL_BTS_CLOSE_OM, bts);
+ }
+
+ return 0;
+}
+
+static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len)
+{
+ struct gsm_bts *bts = trx->bts;
+ int rc, j;
+
+ DEBUGP(DRR, "SI%s: %s\n", get_value_string(osmo_sitype_strs, i),
+ osmo_hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN));
+
+ switch (i) {
+ case SYSINFO_TYPE_5:
+ case SYSINFO_TYPE_5bis:
+ case SYSINFO_TYPE_5ter:
+ case SYSINFO_TYPE_6:
+ rc = rsl_sacch_filling(trx, osmo_sitype2rsl(i),
+ GSM_BTS_SI(bts, i), si_len);
+ break;
+ case SYSINFO_TYPE_2quater:
+ for (j = 0; j <= bts->si2q_count; j++)
+ rc = rsl_bcch_info(trx, i, (const uint8_t *)GSM_BTS_SI2Q(bts, j), GSM_MACBLOCK_LEN);
+ break;
+ default:
+ rc = rsl_bcch_info(trx, i, GSM_BTS_SI(bts, i), si_len);
+ break;
+ }
+
+ return rc;
+}
+
+/* set all system information types for a TRX */
+int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx)
+{
+ int i, rc;
+ struct gsm_bts *bts = trx->bts;
+ uint8_t gen_si[_MAX_SYSINFO_TYPE], n_si = 0, n;
+ int si_len[_MAX_SYSINFO_TYPE];
+
+ bts->si_common.cell_sel_par.ms_txpwr_max_ccch =
+ ms_pwr_ctl_lvl(bts->band, bts->ms_max_power);
+ bts->si_common.cell_sel_par.neci = bts->network->neci;
+
+ /* Zero, forget the state of the SIs */
+ bts->si_valid = 0;
+
+ /* First, we determine which of the SI messages we actually need */
+
+ if (trx == bts->c0) {
+ /* 1...4 are always present on a C0 TRX */
+ gen_si[n_si++] = SYSINFO_TYPE_1;
+ gen_si[n_si++] = SYSINFO_TYPE_2;
+ gen_si[n_si++] = SYSINFO_TYPE_2bis;
+ gen_si[n_si++] = SYSINFO_TYPE_2ter;
+ gen_si[n_si++] = SYSINFO_TYPE_2quater;
+ gen_si[n_si++] = SYSINFO_TYPE_3;
+ gen_si[n_si++] = SYSINFO_TYPE_4;
+
+ /* 13 is always present on a C0 TRX of a GPRS BTS */
+ if (bts->gprs.mode != BTS_GPRS_NONE)
+ gen_si[n_si++] = SYSINFO_TYPE_13;
+ }
+
+ /* 5 and 6 are always present on every TRX */
+ gen_si[n_si++] = SYSINFO_TYPE_5;
+ gen_si[n_si++] = SYSINFO_TYPE_5bis;
+ gen_si[n_si++] = SYSINFO_TYPE_5ter;
+ gen_si[n_si++] = SYSINFO_TYPE_6;
+
+ /* Second, we generate the selected SI via RSL */
+
+ for (n = 0; n < n_si; n++) {
+ i = gen_si[n];
+ /* Only generate SI if this SI is not in "static" (user-defined) mode */
+ if (!(bts->si_mode_static & (1 << i))) {
+ /* Set SI as being valid. gsm_generate_si() might unset
+ * it, if SI is not required. */
+ bts->si_valid |= (1 << i);
+ rc = gsm_generate_si(bts, i);
+ if (rc < 0)
+ goto err_out;
+ si_len[i] = rc;
+ } else {
+ if (i == SYSINFO_TYPE_5 || i == SYSINFO_TYPE_5bis
+ || i == SYSINFO_TYPE_5ter)
+ si_len[i] = 18;
+ else if (i == SYSINFO_TYPE_6)
+ si_len[i] = 11;
+ else
+ si_len[i] = 23;
+ }
+ }
+
+ /* Third, we send the selected SI via RSL */
+
+ for (n = 0; n < n_si; n++) {
+ i = gen_si[n];
+ if (!GSM_BTS_HAS_SI(bts, i))
+ continue;
+ rc = rsl_si(trx, i, si_len[i]);
+ if (rc < 0)
+ return rc;
+ }
+
+ /* Make sure the PCU is aware (in case anything GPRS related has
+ * changed in SI */
+ pcu_info_update(bts);
+
+ return 0;
+err_out:
+ LOGP(DRR, LOGL_ERROR, "Cannot generate SI%s for BTS %u: error <%s>, "
+ "most likely a problem with neighbor cell list generation\n",
+ get_value_string(osmo_sitype_strs, i), bts->nr, strerror(-rc));
+ return rc;
+}
+
+/* set all system information types for a BTS */
+int gsm_bts_set_system_infos(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ /* Generate a new ID */
+ bts->bcch_change_mark += 1;
+ bts->bcch_change_mark %= 0x7;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ int rc;
+
+ rc = gsm_bts_trx_set_system_infos(trx);
+ if (rc != 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+/* Produce a MA as specified in 10.5.2.21 */
+static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts)
+{
+ /* we have three bitvecs: the per-timeslot ARFCNs, the cell chan ARFCNs
+ * and the MA */
+ struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc;
+ struct bitvec *ts_arfcn = &ts->hopping.arfcns;
+ struct bitvec *ma = &ts->hopping.ma;
+ unsigned int num_cell_arfcns, bitnum, n_chan;
+ int i;
+
+ /* re-set the MA to all-zero */
+ ma->cur_bit = 0;
+ ts->hopping.ma_len = 0;
+ memset(ma->data, 0, ma->data_len);
+
+ if (!ts->hopping.enabled)
+ return 0;
+
+ /* count the number of ARFCNs in the cell channel allocation */
+ num_cell_arfcns = 0;
+ for (i = 0; i < 1024; i++) {
+ if (bitvec_get_bit_pos(cell_chan, i))
+ num_cell_arfcns++;
+ }
+
+ /* pad it to octet-aligned number of bits */
+ ts->hopping.ma_len = num_cell_arfcns / 8;
+ if (num_cell_arfcns % 8)
+ ts->hopping.ma_len++;
+
+ n_chan = 0;
+ for (i = 0; i < 1024; i++) {
+ if (!bitvec_get_bit_pos(cell_chan, i))
+ continue;
+ /* set the corresponding bit in the MA */
+ bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan;
+ if (bitvec_get_bit_pos(ts_arfcn, i))
+ bitvec_set_bit_pos(ma, bitnum, 1);
+ else
+ bitvec_set_bit_pos(ma, bitnum, 0);
+ n_chan++;
+ }
+
+ /* ARFCN 0 is special: It is coded last in the bitmask */
+ if (bitvec_get_bit_pos(cell_chan, 0)) {
+ n_chan++;
+ /* set the corresponding bit in the MA */
+ bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan;
+ if (bitvec_get_bit_pos(ts_arfcn, 0))
+ bitvec_set_bit_pos(ma, bitnum, 1);
+ else
+ bitvec_set_bit_pos(ma, bitnum, 0);
+ }
+
+ return 0;
+}
+
+static void bootstrap_rsl(struct gsm_bts_trx *trx)
+{
+ unsigned int i;
+
+ LOGP(DRSL, LOGL_NOTICE, "bootstrapping RSL for BTS/TRX (%u/%u) "
+ "on ARFCN %u using MCC=%u MNC=%u LAC=%u CID=%u BSIC=%u\n",
+ trx->bts->nr, trx->nr, trx->arfcn, bsc_gsmnet->country_code,
+ bsc_gsmnet->network_code, trx->bts->location_area_code,
+ trx->bts->cell_identity, trx->bts->bsic);
+
+ if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) {
+ rsl_nokia_si_begin(trx);
+ }
+
+ gsm_bts_trx_set_system_infos(trx);
+
+ if (trx->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);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+ generate_ma_for_ts(&trx->ts[i]);
+}
+
+/* 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 ts_no, lchan_no;
+ /* 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;
+ /* was static in system_information.c */
+ extern int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts);
+ uint8_t ca[20];
+ /* has to be called before generate_ma_for_ts to
+ set bts->si_common.cell_alloc */
+ generate_cell_chan_list(ca, trx->bts);
+
+ /* Request generic BTS-level attributes */
+ abis_nm_get_attr(trx->bts, NM_OC_BTS, trx->bts->nr, trx->nr, 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, cur_trx->bts->nr, 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);
+ break;
+ case S_L_INP_TEI_DN:
+ LOGP(DLMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", isd->link_type, trx);
+
+ if (isd->link_type == E1INP_SIGN_OML)
+ rate_ctr_inc(&trx->bts->network->bsc_ctrs->ctr[BSC_CTR_BTS_OML_FAIL]);
+ else if (isd->link_type == E1INP_SIGN_RSL)
+ rate_ctr_inc(&trx->bts->network->bsc_ctrs->ctr[BSC_CTR_BTS_RSL_FAIL]);
+
+ /*
+ * free all allocated channels. change the nm_state so the
+ * trx and trx_ts becomes unusable and chan_alloc.c can not
+ * allocate from it.
+ */
+ for (ts_no = 0; ts_no < ARRAY_SIZE(trx->ts); ++ts_no) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_no];
+
+ for (lchan_no = 0; lchan_no < ARRAY_SIZE(ts->lchan); ++lchan_no) {
+ if (ts->lchan[lchan_no].state != LCHAN_S_NONE)
+ lchan_free(&ts->lchan[lchan_no]);
+ lchan_reset(&ts->lchan[lchan_no]);
+ }
+ }
+
+ gsm_bts_mo_reset(trx->bts);
+
+ abis_nm_clear_queue(trx->bts);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int bootstrap_bts(struct gsm_bts *bts)
+{
+ int i, n;
+
+ if (!bts->model)
+ return -EFAULT;
+
+ if (bts->model->start && !bts->model->started) {
+ int ret = bts->model->start(bts->network);
+ if (ret < 0)
+ return ret;
+
+ bts->model->started = true;
+ }
+
+ /* FIXME: What about secondary TRX of a BTS? What about a BTS that has TRX
+ * in different bands? Why is 'band' a parameter of the BTS and not of the TRX? */
+ switch (bts->band) {
+ case GSM_BAND_1800:
+ if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) {
+ LOGP(DNM, LOGL_ERROR, "GSM1800 channel must be between 512-885.\n");
+ return -EINVAL;
+ }
+ break;
+ case GSM_BAND_1900:
+ if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) {
+ LOGP(DNM, LOGL_ERROR, "GSM1900 channel must be between 512-810.\n");
+ return -EINVAL;
+ }
+ break;
+ case GSM_BAND_900:
+ if ((bts->c0->arfcn > 124 && bts->c0->arfcn < 955) ||
+ bts->c0->arfcn > 1023) {
+ LOGP(DNM, LOGL_ERROR, "GSM900 channel must be between 0-124, 955-1023.\n");
+ return -EINVAL;
+ }
+ break;
+ case GSM_BAND_850:
+ if (bts->c0->arfcn < 128 || bts->c0->arfcn > 251) {
+ LOGP(DNM, LOGL_ERROR, "GSM850 channel must be between 128-251.\n");
+ return -EINVAL;
+ }
+ break;
+ default:
+ LOGP(DNM, LOGL_ERROR, "Unsupported frequency band.\n");
+ return -EINVAL;
+ }
+
+ if (bts->network->auth_policy == GSM_AUTH_POLICY_ACCEPT_ALL &&
+ !bts->si_common.rach_control.cell_bar)
+ LOGP(DNM, LOGL_ERROR, "\nWARNING: You are running an 'accept-all' "
+ "network on a BTS that is not barred. This "
+ "configuration is likely to interfere with production "
+ "GSM networks and should only be used in a RF "
+ "shielded environment such as a faraday cage!\n\n");
+
+ /* Control Channel Description is set from vty/config */
+
+ /* T3212 is set from vty/config */
+
+ /* Set ccch config by looking at ts config */
+ for (n=0, i=0; i<8; i++)
+ n += bts->c0->ts[i].pchan == GSM_PCHAN_CCCH ? 1 : 0;
+
+ switch (n) {
+ case 0:
+ bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C;
+ /* Limit reserved block to 2 on combined channel according to
+ 3GPP TS 44.018 Table 10.5.2.11.1 */
+ if (bts->si_common.chan_desc.bs_ag_blks_res > 2) {
+ LOGP(DNM, LOGL_NOTICE, "CCCH is combined with SDCCHs, "
+ "reducing BS-AG-BLKS-RES value %d -> 2\n",
+ bts->si_common.chan_desc.bs_ag_blks_res);
+ bts->si_common.chan_desc.bs_ag_blks_res = 2;
+ }
+ break;
+ case 1:
+ bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC;
+ break;
+ case 2:
+ bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC;
+ break;
+ case 3:
+ bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC;
+ break;
+ case 4:
+ bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC;
+ break;
+ default:
+ LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n");
+ return -EINVAL;
+ }
+
+ bts->si_common.cell_options.pwrc = 0; /* PWRC not set */
+
+ bts->si_common.cell_sel_par.acs = 0;
+
+ bts->si_common.ncc_permitted = 0xff;
+
+ /* Initialize the BTS state */
+ gsm_bts_mo_reset(bts);
+
+ return 0;
+}
+
+int bsc_network_alloc(mncc_recv_cb_t mncc_recv)
+{
+ /* initialize our data structures */
+ bsc_gsmnet = bsc_network_init(tall_bsc_ctx, 1, 1, mncc_recv);
+ if (!bsc_gsmnet)
+ return -ENOMEM;
+
+ bsc_gsmnet->name_long = talloc_strdup(bsc_gsmnet, "OpenBSC");
+ bsc_gsmnet->name_short = talloc_strdup(bsc_gsmnet, "OpenBSC");
+
+ return 0;
+}
+
+int bsc_network_configure(const char *config_file)
+{
+ struct gsm_bts *bts;
+ int rc;
+
+ 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);
+ 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);
+ if (rc < 0)
+ return rc;
+
+ osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
+ 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);
+ if (rc < 0) {
+ LOGP(DNM, LOGL_FATAL, "Error bootstrapping BTS\n");
+ return rc;
+ }
+ rc = e1_reconfig_bts(bts);
+ if (rc < 0) {
+ LOGP(DNM, LOGL_FATAL, "Error enabling E1 input driver\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/libbsc/bsc_msc.c b/src/libbsc/bsc_msc.c
new file mode 100644
index 000000000..82a572dbe
--- /dev/null
+++ b/src/libbsc/bsc_msc.c
@@ -0,0 +1,320 @@
+/* Routines to talk to the MSC using the IPA Protocol */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 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 <openbsc/bsc_msc.h>
+#include <openbsc/debug.h>
+#include <osmocom/abis/ipaccess.h>
+
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+static void connection_loss(struct bsc_msc_connection *con)
+{
+ struct osmo_fd *fd;
+
+ fd = &con->write_queue.bfd;
+
+ if (con->pending_msg) {
+ LOGP(DMSC, LOGL_ERROR,
+ "MSC(%s) dropping incomplete message.\n", con->name);
+ msgb_free(con->pending_msg);
+ con->pending_msg = NULL;
+ }
+
+ close(fd->fd);
+ fd->fd = -1;
+ fd->cb = osmo_wqueue_bfd_cb;
+ fd->when = 0;
+
+ con->is_connected = 0;
+ con->first_contact = 0;
+ con->connection_loss(con);
+}
+
+static void msc_con_timeout(void *_con)
+{
+ struct bsc_msc_connection *con = _con;
+
+ LOGP(DMSC, LOGL_ERROR,
+ "MSC(%s) Connection timeout.\n", con->name);
+ bsc_msc_lost(con);
+}
+
+/* called in the case of a non blocking connect */
+static int msc_connection_connect(struct osmo_fd *fd, unsigned int what)
+{
+ int rc;
+ int val;
+ struct bsc_msc_connection *con;
+ struct osmo_wqueue *queue;
+
+ socklen_t len = sizeof(val);
+
+ queue = container_of(fd, struct osmo_wqueue, bfd);
+ con = container_of(queue, struct bsc_msc_connection, write_queue);
+
+ if ((what & BSC_FD_WRITE) == 0) {
+ LOGP(DMSC, LOGL_ERROR,
+ "MSC(%s) Callback but not writable.\n", con->name);
+ return -1;
+ }
+
+ /* From here on we will either be connected or reconnect */
+ osmo_timer_del(&con->timeout_timer);
+
+ /* check the socket state */
+ rc = getsockopt(fd->fd, SOL_SOCKET, SO_ERROR, &val, &len);
+ if (rc != 0) {
+ LOGP(DMSC, LOGL_ERROR,
+ "getsockopt for the MSC(%s) socket failed.\n", con->name);
+ goto error;
+ }
+ if (val != 0) {
+ LOGP(DMSC, LOGL_ERROR,
+ "Not connected to the MSC(%s): %d\n",
+ con->name, val);
+ goto error;
+ }
+
+
+ /* go to full operation */
+ fd->cb = osmo_wqueue_bfd_cb;
+ fd->when = BSC_FD_READ | BSC_FD_EXCEPT;
+
+ con->is_connected = 1;
+ LOGP(DMSC, LOGL_NOTICE,
+ "(Re)Connected to the MSC(%s).\n", con->name);
+ if (con->connected)
+ con->connected(con);
+ return 0;
+
+error:
+ osmo_fd_unregister(fd);
+ connection_loss(con);
+ return -1;
+}
+static void setnonblocking(struct osmo_fd *fd)
+{
+ int flags;
+
+ flags = fcntl(fd->fd, F_GETFL);
+ if (flags < 0) {
+ perror("fcntl get failed");
+ close(fd->fd);
+ fd->fd = -1;
+ return;
+ }
+
+ flags |= O_NONBLOCK;
+ flags = fcntl(fd->fd, F_SETFL, flags);
+ if (flags < 0) {
+ perror("fcntl get failed");
+ close(fd->fd);
+ fd->fd = -1;
+ return;
+ }
+}
+
+int bsc_msc_connect(struct bsc_msc_connection *con)
+{
+ struct bsc_msc_dest *dest;
+ struct osmo_fd *fd;
+ struct sockaddr_in sin;
+ int on = 1, ret;
+
+ if (llist_empty(con->dests)) {
+ LOGP(DMSC, LOGL_ERROR,
+ "No MSC(%s) connections configured.\n",
+ con->name);
+ connection_loss(con);
+ return -1;
+ }
+
+ /* TODO: Why are we not using the libosmocore soecket
+ * abstraction, or libosmo-netif? */
+
+ /* move to the next connection */
+ dest = (struct bsc_msc_dest *) con->dests->next;
+ llist_del(&dest->list);
+ llist_add_tail(&dest->list, con->dests);
+
+ LOGP(DMSC, LOGL_NOTICE,
+ "Attempting to connect MSC(%s) at %s:%d\n",
+ con->name, dest->ip, dest->port);
+
+ con->is_connected = 0;
+
+ msgb_free(con->pending_msg);
+ con->pending_msg = NULL;
+
+ fd = &con->write_queue.bfd;
+ fd->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ fd->priv_nr = 1;
+
+ if (fd->fd < 0) {
+ perror("Creating TCP socket failed");
+ return fd->fd;
+ }
+
+ /* make it non blocking */
+ setnonblocking(fd);
+
+ /* set the socket priority */
+ ret = setsockopt(fd->fd, IPPROTO_IP, IP_TOS,
+ &dest->dscp, sizeof(dest->dscp));
+ if (ret != 0)
+ LOGP(DMSC, LOGL_ERROR,
+ "Failed to set DSCP to %d on MSC(%s). %s\n",
+ dest->dscp, con->name, strerror(errno));
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(dest->port);
+ inet_aton(dest->ip, &sin.sin_addr);
+
+ ret = setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+ if (ret != 0)
+ LOGP(DMSC, LOGL_ERROR,
+ "Failed to set SO_REUSEADDR socket option\n");
+ ret = connect(fd->fd, (struct sockaddr *) &sin, sizeof(sin));
+
+ if (ret == -1 && errno == EINPROGRESS) {
+ LOGP(DMSC, LOGL_ERROR,
+ "MSC(%s) Connection in progress\n", con->name);
+ fd->when = BSC_FD_WRITE;
+ fd->cb = msc_connection_connect;
+ osmo_timer_setup(&con->timeout_timer, msc_con_timeout, con);
+ osmo_timer_schedule(&con->timeout_timer, 20, 0);
+ } else if (ret < 0) {
+ perror("Connection failed");
+ connection_loss(con);
+ return ret;
+ } else {
+ fd->when = BSC_FD_READ | BSC_FD_EXCEPT;
+ fd->cb = osmo_wqueue_bfd_cb;
+ con->is_connected = 1;
+ if (con->connected)
+ con->connected(con);
+ }
+
+ ret = osmo_fd_register(fd);
+ if (ret < 0) {
+ perror("Registering the fd failed");
+ close(fd->fd);
+ return ret;
+ }
+
+ return ret;
+}
+
+struct bsc_msc_connection *bsc_msc_create(void *ctx, struct llist_head *dests)
+{
+ struct bsc_msc_connection *con;
+
+ con = talloc_zero(NULL, struct bsc_msc_connection);
+ if (!con) {
+ LOGP(DMSC, LOGL_FATAL, "Failed to create the MSC connection.\n");
+ return NULL;
+ }
+
+ con->dests = dests;
+ con->write_queue.bfd.fd = -1;
+ con->name = "";
+ osmo_wqueue_init(&con->write_queue, 100);
+ return con;
+}
+
+void bsc_msc_lost(struct bsc_msc_connection *con)
+{
+ osmo_wqueue_clear(&con->write_queue);
+ osmo_timer_del(&con->timeout_timer);
+ osmo_timer_del(&con->reconnect_timer);
+
+ if (con->write_queue.bfd.fd >= 0)
+ osmo_fd_unregister(&con->write_queue.bfd);
+ connection_loss(con);
+}
+
+static void reconnect_msc(void *_msc)
+{
+ struct bsc_msc_connection *con = _msc;
+
+ LOGP(DMSC, LOGL_NOTICE,
+ "Attempting to reconnect to the MSC(%s).\n", con->name);
+ bsc_msc_connect(con);
+}
+
+void bsc_msc_schedule_connect(struct bsc_msc_connection *con)
+{
+ LOGP(DMSC, LOGL_NOTICE,
+ "Attempting to reconnect to the MSC(%s)\n", con->name);
+ osmo_timer_setup(&con->reconnect_timer, reconnect_msc, con);
+ osmo_timer_schedule(&con->reconnect_timer, 5, 0);
+}
+
+struct msgb *bsc_msc_id_get_resp(int fixed, const char *token, const uint8_t *res, int len)
+{
+ struct msgb *msg;
+
+ if (!token) {
+ LOGP(DMSC, LOGL_ERROR, "No token specified.\n");
+ return NULL;
+ }
+
+ msg = msgb_alloc_headroom(4096, 128, "id resp");
+ if (!msg) {
+ LOGP(DMSC, LOGL_ERROR, "Failed to create the message.\n");
+ return NULL;
+ }
+
+ /*
+ * The situation is bizarre. The encoding doesn't follow the
+ * TLV structure. It is more like a LV and old versions had
+ * it wrong but we want new versions to old servers so we
+ * introduce the quirk here.
+ */
+ msg->l2h = msgb_v_put(msg, IPAC_MSGT_ID_RESP);
+ if (fixed) {
+ msgb_put_u8(msg, 0);
+ msgb_put_u8(msg, strlen(token) + 2);
+ msgb_tv_fixed_put(msg, IPAC_IDTAG_UNITNAME, strlen(token) + 1, (uint8_t *) token);
+ if (len > 0) {
+ msgb_put_u8(msg, 0);
+ msgb_put_u8(msg, len + 1);
+ msgb_tv_fixed_put(msg, 0x24, len, res);
+ }
+ } else {
+ msgb_l16tv_put(msg, strlen(token) + 1,
+ IPAC_IDTAG_UNITNAME, (uint8_t *) token);
+ }
+
+ return msg;
+}
diff --git a/src/libbsc/bsc_rf_ctrl.c b/src/libbsc/bsc_rf_ctrl.c
new file mode 100644
index 000000000..b7b6fc819
--- /dev/null
+++ b/src/libbsc/bsc_rf_ctrl.c
@@ -0,0 +1,534 @@
+/* RF Ctl handling socket */
+
+/* (C) 2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2014 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010-2014 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 <openbsc/osmo_bsc_rf.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/ipaccess.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#define RF_CMD_QUERY '?'
+#define RF_CMD_OFF '0'
+#define RF_CMD_ON '1'
+#define RF_CMD_D_OFF 'd'
+#define RF_CMD_ON_G 'g'
+
+static const struct value_string opstate_names[] = {
+ { OSMO_BSC_RF_OPSTATE_INOPERATIONAL, "inoperational" },
+ { OSMO_BSC_RF_OPSTATE_OPERATIONAL, "operational" },
+ { 0, NULL }
+};
+
+static const struct value_string adminstate_names[] = {
+ { OSMO_BSC_RF_ADMINSTATE_UNLOCKED, "unlocked" },
+ { OSMO_BSC_RF_ADMINSTATE_LOCKED, "locked" },
+ { 0, NULL }
+};
+
+static const struct value_string policy_names[] = {
+ { OSMO_BSC_RF_POLICY_OFF, "off" },
+ { OSMO_BSC_RF_POLICY_ON, "on" },
+ { OSMO_BSC_RF_POLICY_GRACE, "grace" },
+ { OSMO_BSC_RF_POLICY_UNKNOWN, "unknown" },
+ { 0, NULL }
+};
+
+const char *osmo_bsc_rf_get_opstate_name(enum osmo_bsc_rf_opstate opstate)
+{
+ return get_value_string(opstate_names, opstate);
+}
+
+const char *osmo_bsc_rf_get_adminstate_name(enum osmo_bsc_rf_adminstate adminstate)
+{
+ return get_value_string(adminstate_names, adminstate);
+}
+
+const char *osmo_bsc_rf_get_policy_name(enum osmo_bsc_rf_policy policy)
+{
+ return get_value_string(policy_names, policy);
+}
+
+enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_bts(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED)
+ return OSMO_BSC_RF_OPSTATE_OPERATIONAL;
+ }
+
+ /* No trx were active, so this bts is disabled */
+ return OSMO_BSC_RF_OPSTATE_INOPERATIONAL;
+}
+
+enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_bts(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
+ return OSMO_BSC_RF_ADMINSTATE_UNLOCKED;
+ }
+
+ /* All trx administrative states were locked */
+ return OSMO_BSC_RF_ADMINSTATE_LOCKED;
+}
+
+enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts)
+{
+ struct osmo_bsc_data *bsc_data = bts->network->bsc_data;
+
+ if (!bsc_data)
+ return OSMO_BSC_RF_POLICY_UNKNOWN;
+
+ switch (bsc_data->rf_ctrl->policy) {
+ case S_RF_ON:
+ return OSMO_BSC_RF_POLICY_ON;
+ case S_RF_OFF:
+ return OSMO_BSC_RF_POLICY_OFF;
+ case S_RF_GRACE:
+ return OSMO_BSC_RF_POLICY_GRACE;
+ default:
+ return OSMO_BSC_RF_POLICY_UNKNOWN;
+ }
+}
+
+static int lock_each_trx(struct gsm_network *net, int lock)
+{
+ struct gsm_bts *bts;
+
+ 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) {
+ LOGP(DLINP, LOGL_DEBUG,
+ "Excluding BTS(%d) from trx lock.\n", bts->nr);
+ continue;
+ }
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ gsm_trx_lock_rf(trx, lock);
+ }
+ }
+
+ return 0;
+}
+
+static void send_resp(struct osmo_bsc_rf_conn *conn, char send)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc(10, "RF Query");
+ if (!msg) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to allocate response msg.\n");
+ return;
+ }
+
+ msg->l2h = msgb_put(msg, 1);
+ msg->l2h[0] = send;
+
+ if (osmo_wqueue_enqueue(&conn->queue, msg) != 0) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the answer.\n");
+ msgb_free(msg);
+ return;
+ }
+
+ return;
+}
+
+
+/*
+ * Send a
+ * 'g' when we are in grace mode
+ * '1' when one TRX is online,
+ * '0' otherwise
+ */
+static void handle_query(struct osmo_bsc_rf_conn *conn)
+{
+ struct gsm_bts *bts;
+ char send = RF_CMD_OFF;
+
+ if (conn->rf->policy == S_RF_GRACE)
+ return send_resp(conn, RF_CMD_ON_G);
+
+ llist_for_each_entry(bts, &conn->rf->gsm_network->bts_list, list) {
+ struct gsm_bts_trx *trx;
+
+ /* Exclude the BTS from the global lock */
+ if (bts->excl_from_rf_lock) {
+ LOGP(DLINP, LOGL_DEBUG,
+ "Excluding BTS(%d) from query.\n", bts->nr);
+ 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) {
+ send = RF_CMD_ON;
+ break;
+ }
+ }
+ }
+
+ send_resp(conn, send);
+}
+
+static void rf_check_cb(void *_data)
+{
+ struct gsm_bts *bts;
+ struct osmo_bsc_rf *rf = _data;
+
+ llist_for_each_entry(bts, &rf->gsm_network->bts_list, list) {
+ struct gsm_bts_trx *trx;
+
+ /* don't bother to check a booting or missing BTS */
+ if (!bts->oml_link || !is_ipaccess_bts(bts))
+ continue;
+
+ /* Exclude the BTS from the global lock */
+ if (bts->excl_from_rf_lock) {
+ LOGP(DLINP, LOGL_DEBUG,
+ "Excluding BTS(%d) from query.\n", bts->nr);
+ 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_ENABLED ||
+ trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) {
+ LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n");
+ ipaccess_drop_oml(bts);
+ break;
+ }
+ }
+ }
+}
+
+static void send_signal(struct osmo_bsc_rf *rf, int val)
+{
+ struct rf_signal_data sig;
+ sig.net = rf->gsm_network;
+
+ rf->policy = val;
+ osmo_signal_dispatch(SS_RF, val, &sig);
+}
+
+static int switch_rf_off(struct osmo_bsc_rf *rf)
+{
+ lock_each_trx(rf->gsm_network, 1);
+ send_signal(rf, S_RF_OFF);
+
+ return 0;
+}
+
+static void grace_timeout(void *_data)
+{
+ struct osmo_bsc_rf *rf = (struct osmo_bsc_rf *) _data;
+
+ LOGP(DLINP, LOGL_NOTICE, "Grace timeout. Going to disable all BTS/TRX.\n");
+ switch_rf_off(rf);
+}
+
+static int enter_grace(struct osmo_bsc_rf *rf)
+{
+ if (osmo_timer_pending(&rf->grace_timeout)) {
+ LOGP(DLINP, LOGL_NOTICE, "RF Grace timer is pending. Not restarting.\n");
+ return 0;
+ }
+
+ osmo_timer_setup(&rf->grace_timeout, grace_timeout, rf);
+ osmo_timer_schedule(&rf->grace_timeout, rf->gsm_network->bsc_data->mid_call_timeout, 0);
+ LOGP(DLINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n",
+ rf->gsm_network->bsc_data->mid_call_timeout);
+
+ send_signal(rf, S_RF_GRACE);
+ return 0;
+}
+
+static void rf_delay_cmd_cb(void *data)
+{
+ struct osmo_bsc_rf *rf = data;
+
+ switch (rf->last_request) {
+ case RF_CMD_D_OFF:
+ rf->last_state_command = "RF Direct Off";
+ osmo_timer_del(&rf->rf_check);
+ osmo_timer_del(&rf->grace_timeout);
+ switch_rf_off(rf);
+ break;
+ case RF_CMD_ON:
+ rf->last_state_command = "RF Direct On";
+ osmo_timer_del(&rf->grace_timeout);
+ lock_each_trx(rf->gsm_network, 0);
+ send_signal(rf, S_RF_ON);
+ osmo_timer_schedule(&rf->rf_check, 3, 0);
+ break;
+ case RF_CMD_OFF:
+ rf->last_state_command = "RF Scheduled Off";
+ osmo_timer_del(&rf->rf_check);
+ enter_grace(rf);
+ break;
+ }
+}
+
+static int rf_read_cmd(struct osmo_fd *fd)
+{
+ struct osmo_bsc_rf_conn *conn = fd->data;
+ char buf[1];
+ int rc;
+
+ rc = read(fd->fd, buf, sizeof(buf));
+ if (rc != sizeof(buf)) {
+ LOGP(DLINP, LOGL_ERROR, "Short read %d/%s\n", errno, strerror(errno));
+ osmo_fd_unregister(fd);
+ close(fd->fd);
+ osmo_wqueue_clear(&conn->queue);
+ talloc_free(conn);
+ return -1;
+ }
+
+ switch (buf[0]) {
+ case RF_CMD_QUERY:
+ handle_query(conn);
+ break;
+ case RF_CMD_D_OFF:
+ case RF_CMD_ON:
+ case RF_CMD_OFF:
+ osmo_bsc_rf_schedule_lock(conn->rf, buf[0]);
+ break;
+ default:
+ conn->rf->last_state_command = "Unknown command";
+ LOGP(DLINP, LOGL_ERROR, "Unknown command %d\n", buf[0]);
+ break;
+ }
+
+ return 0;
+}
+
+static int rf_write_cmd(struct osmo_fd *fd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(fd->fd, msg->data, msg->len);
+ if (rc != msg->len) {
+ LOGP(DLINP, LOGL_ERROR, "Short write %d/%s\n", errno, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int rf_ctrl_accept(struct osmo_fd *bfd, unsigned int what)
+{
+ struct osmo_bsc_rf_conn *conn;
+ struct osmo_bsc_rf *rf = bfd->data;
+ struct sockaddr_un addr;
+ socklen_t len = sizeof(addr);
+ int fd;
+
+ fd = accept(bfd->fd, (struct sockaddr *) &addr, &len);
+ if (fd < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to accept. errno: %d/%s\n",
+ errno, strerror(errno));
+ return -1;
+ }
+
+ conn = talloc_zero(rf, struct osmo_bsc_rf_conn);
+ if (!conn) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to allocate mem.\n");
+ close(fd);
+ return -1;
+ }
+
+ osmo_wqueue_init(&conn->queue, 10);
+ conn->queue.bfd.data = conn;
+ conn->queue.bfd.fd = fd;
+ conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE;
+ conn->queue.read_cb = rf_read_cmd;
+ conn->queue.write_cb = rf_write_cmd;
+ conn->rf = rf;
+
+ if (osmo_fd_register(&conn->queue.bfd) != 0) {
+ close(fd);
+ talloc_free(conn);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void rf_auto_off_cb(void *_timer)
+{
+ struct osmo_bsc_rf *rf = _timer;
+
+ LOGP(DLINP, LOGL_NOTICE,
+ "Going to switch off RF due lack of a MSC connection.\n");
+ osmo_bsc_rf_schedule_lock(rf, RF_CMD_D_OFF);
+}
+
+static int msc_signal_handler(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_network *net;
+ struct msc_signal_data *msc;
+ struct osmo_bsc_rf *rf;
+
+ /* check if we want to handle this signal */
+ if (subsys != SS_MSC)
+ return 0;
+
+ net = handler_data;
+ msc = signal_data;
+
+ /* check if we have the needed information */
+ if (!net->bsc_data)
+ return 0;
+ if (msc->data->type != MSC_CON_TYPE_NORMAL)
+ return 0;
+
+ rf = net->bsc_data->rf_ctrl;
+ switch (signal) {
+ case S_MSC_LOST:
+ if (net->bsc_data->auto_off_timeout < 0)
+ return 0;
+ if (osmo_timer_pending(&rf->auto_off_timer))
+ return 0;
+ osmo_timer_schedule(&rf->auto_off_timer,
+ net->bsc_data->auto_off_timeout, 0);
+ break;
+ case S_MSC_CONNECTED:
+ osmo_timer_del(&rf->auto_off_timer);
+ break;
+ }
+
+ return 0;
+}
+
+static int rf_create_socket(struct osmo_bsc_rf *rf, const char *path)
+{
+ unsigned int namelen;
+ struct sockaddr_un local;
+ struct osmo_fd *bfd;
+ int rc;
+
+ bfd = &rf->listen;
+ bfd->fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (bfd->fd < 0) {
+ LOGP(DLINP, LOGL_ERROR, "Can not create socket. %d/%s\n",
+ errno, strerror(errno));
+ return -1;
+ }
+
+ local.sun_family = AF_UNIX;
+ osmo_strlcpy(local.sun_path, path, sizeof(local.sun_path));
+ unlink(local.sun_path);
+
+ /* we use the same magic that X11 uses in Xtranssock.c for
+ * calculating the proper length of the sockaddr */
+#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
+ local.sun_len = strlen(local.sun_path);
+#endif
+#if defined(BSD44SOCKETS) || defined(SUN_LEN)
+ namelen = SUN_LEN(&local);
+#else
+ namelen = strlen(local.sun_path) +
+ offsetof(struct sockaddr_un, sun_path);
+#endif
+
+ rc = bind(bfd->fd, (struct sockaddr *) &local, namelen);
+ if (rc != 0) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to bind '%s' errno: %d/%s\n",
+ local.sun_path, errno, strerror(errno));
+ close(bfd->fd);
+ return -1;
+ }
+
+ if (listen(bfd->fd, 0) != 0) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to listen: %d/%s\n", errno, strerror(errno));
+ close(bfd->fd);
+ return -1;
+ }
+
+ bfd->when = BSC_FD_READ;
+ bfd->cb = rf_ctrl_accept;
+ bfd->data = rf;
+
+ if (osmo_fd_register(bfd) != 0) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to register bfd.\n");
+ close(bfd->fd);
+ return -1;
+ }
+
+ return 0;
+}
+
+struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net)
+{
+ struct osmo_bsc_rf *rf;
+
+ rf = talloc_zero(NULL, struct osmo_bsc_rf);
+ if (!rf) {
+ LOGP(DLINP, LOGL_ERROR, "Failed to create osmo_bsc_rf.\n");
+ return NULL;
+ }
+
+ if (path && rf_create_socket(rf, path) != 0) {
+ talloc_free(rf);
+ return NULL;
+ }
+
+ rf->gsm_network = net;
+ rf->policy = S_RF_ON;
+ rf->last_state_command = "";
+ rf->last_rf_lock_ctrl_command = talloc_strdup(rf, "");
+
+ /* check the rf state */
+ osmo_timer_setup(&rf->rf_check, rf_check_cb, rf);
+
+ /* delay cmd handling */
+ osmo_timer_setup(&rf->delay_cmd, rf_delay_cmd_cb, rf);
+
+ osmo_timer_setup(&rf->auto_off_timer, rf_auto_off_cb, rf);
+
+ /* listen to RF signals */
+ osmo_signal_register_handler(SS_MSC, msc_signal_handler, net);
+
+ return rf;
+}
+
+void osmo_bsc_rf_schedule_lock(struct osmo_bsc_rf *rf, char cmd)
+{
+ rf->last_request = cmd;
+ if (!osmo_timer_pending(&rf->delay_cmd))
+ osmo_timer_schedule(&rf->delay_cmd, 1, 0);
+}
diff --git a/src/libbsc/bsc_rll.c b/src/libbsc/bsc_rll.c
new file mode 100644
index 000000000..bb488da15
--- /dev/null
+++ b/src/libbsc/bsc_rll.c
@@ -0,0 +1,139 @@
+/* GSM BSC Radio Link Layer API
+ * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.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 <errno.h>
+
+#include <openbsc/debug.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/linuxlist.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+
+struct bsc_rll_req {
+ struct llist_head list;
+ struct osmo_timer_list timer;
+
+ struct gsm_lchan *lchan;
+ uint8_t link_id;
+
+ void (*cb)(struct gsm_lchan *lchan, uint8_t link_id,
+ void *data, enum bsc_rllr_ind);
+ void *data;
+};
+
+/* we only compare C1, C2 and SAPI */
+#define LINKID_MASK 0xC7
+
+static LLIST_HEAD(bsc_rll_reqs);
+
+static void complete_rllr(struct bsc_rll_req *rllr, enum bsc_rllr_ind type)
+{
+ llist_del(&rllr->list);
+ rllr->cb(rllr->lchan, rllr->link_id, rllr->data, type);
+ talloc_free(rllr);
+}
+
+static void timer_cb(void *_rllr)
+{
+ struct bsc_rll_req *rllr = _rllr;
+
+ complete_rllr(rllr, BSC_RLLR_IND_TIMEOUT);
+}
+
+/* establish a RLL connection with given SAPI / priority */
+int rll_establish(struct gsm_lchan *lchan, uint8_t sapi,
+ void (*cb)(struct gsm_lchan *, uint8_t, void *,
+ enum bsc_rllr_ind),
+ void *data)
+{
+ struct bsc_rll_req *rllr = talloc_zero(tall_bsc_ctx, struct bsc_rll_req);
+ uint8_t link_id;
+ if (!rllr)
+ return -ENOMEM;
+
+ link_id = sapi;
+
+ /* If we are a TCH and not in signalling mode, we need to
+ * indicate that the new RLL connection is to be made on the SACCH */
+ if ((lchan->type == GSM_LCHAN_TCH_F ||
+ lchan->type == GSM_LCHAN_TCH_H) && sapi != 0)
+ link_id |= 0x40;
+
+ rllr->lchan = lchan;
+ rllr->link_id = link_id;
+ rllr->cb = cb;
+ rllr->data = data;
+
+ llist_add(&rllr->list, &bsc_rll_reqs);
+
+ osmo_timer_setup(&rllr->timer, timer_cb, rllr);
+ osmo_timer_schedule(&rllr->timer, 7, 0);
+
+ /* send the RSL RLL ESTablish REQuest */
+ return rsl_establish_request(rllr->lchan, rllr->link_id);
+}
+
+/* Called from RSL code in case we have received an indication regarding
+ * any RLL link */
+void rll_indication(struct gsm_lchan *lchan, uint8_t link_id, uint8_t type)
+{
+ struct bsc_rll_req *rllr, *rllr2;
+
+ llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) {
+ if (rllr->lchan == lchan &&
+ (rllr->link_id & LINKID_MASK) == (link_id & LINKID_MASK)) {
+ osmo_timer_del(&rllr->timer);
+ complete_rllr(rllr, type);
+ return;
+ }
+ }
+}
+
+static int rll_lchan_signal(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct challoc_signal_data *challoc;
+ struct bsc_rll_req *rllr, *rllr2;
+
+ if (subsys != SS_CHALLOC || signal != S_CHALLOC_FREED)
+ return 0;
+
+ challoc = (struct challoc_signal_data *) signal_data;
+
+ llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) {
+ if (rllr->lchan == challoc->lchan) {
+ osmo_timer_del(&rllr->timer);
+ complete_rllr(rllr, BSC_RLLR_IND_ERR_IND);
+ }
+ }
+
+ return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_rll(void)
+{
+ osmo_signal_register_handler(SS_CHALLOC, rll_lchan_signal, NULL);
+}
diff --git a/src/libbsc/bsc_subscriber.c b/src/libbsc/bsc_subscriber.c
new file mode 100644
index 000000000..73e61e801
--- /dev/null
+++ b/src/libbsc/bsc_subscriber.c
@@ -0,0 +1,168 @@
+/* GSM subscriber details for use in BSC land */
+
+/*
+ * (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@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 <talloc.h>
+#include <string.h>
+#include <limits.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/core/logging.h>
+
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/debug.h>
+
+static struct bsc_subscr *bsc_subscr_alloc(struct llist_head *list)
+{
+ struct bsc_subscr *bsub;
+
+ bsub = talloc_zero(list, struct bsc_subscr);
+ if (!bsub)
+ return NULL;
+
+ llist_add_tail(&bsub->entry, list);
+ bsub->use_count = 1;
+
+ return bsub;
+}
+
+struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list,
+ const char *imsi)
+{
+ struct bsc_subscr *bsub;
+
+ if (!imsi || !*imsi)
+ return NULL;
+
+ llist_for_each_entry(bsub, list, entry) {
+ if (!strcmp(bsub->imsi, imsi))
+ return bsc_subscr_get(bsub);
+ }
+ return NULL;
+}
+
+struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list,
+ uint32_t tmsi)
+{
+ struct bsc_subscr *bsub;
+
+ if (tmsi == GSM_RESERVED_TMSI)
+ return NULL;
+
+ llist_for_each_entry(bsub, list, entry) {
+ if (bsub->tmsi == tmsi)
+ return bsc_subscr_get(bsub);
+ }
+ return NULL;
+}
+
+void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi)
+{
+ if (!bsub)
+ return;
+ osmo_strlcpy(bsub->imsi, imsi, sizeof(bsub->imsi));
+}
+
+struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list,
+ const char *imsi)
+{
+ struct bsc_subscr *bsub;
+ bsub = bsc_subscr_find_by_imsi(list, imsi);
+ if (bsub)
+ return bsub;
+ bsub = bsc_subscr_alloc(list);
+ bsc_subscr_set_imsi(bsub, imsi);
+ return bsub;
+}
+
+struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list,
+ uint32_t tmsi)
+{
+ struct bsc_subscr *bsub;
+ bsub = bsc_subscr_find_by_tmsi(list, tmsi);
+ if (bsub)
+ return bsub;
+ bsub = bsc_subscr_alloc(list);
+ bsub->tmsi = tmsi;
+ return bsub;
+}
+
+const char *bsc_subscr_name(struct bsc_subscr *bsub)
+{
+ static char buf[32];
+ if (!bsub)
+ return "unknown";
+ if (bsub->imsi[0])
+ snprintf(buf, sizeof(buf), "IMSI:%s", bsub->imsi);
+ else
+ snprintf(buf, sizeof(buf), "TMSI:0x%08x", bsub->tmsi);
+ return buf;
+}
+
+static void bsc_subscr_free(struct bsc_subscr *bsub)
+{
+ llist_del(&bsub->entry);
+ talloc_free(bsub);
+}
+
+struct bsc_subscr *_bsc_subscr_get(struct bsc_subscr *bsub,
+ const char *file, int line)
+{
+ OSMO_ASSERT(bsub->use_count < INT_MAX);
+ bsub->use_count++;
+ LOGPSRC(DREF, LOGL_DEBUG, file, line,
+ "BSC subscr %s usage increases to: %d\n",
+ bsc_subscr_name(bsub), bsub->use_count);
+ return bsub;
+}
+
+struct bsc_subscr *_bsc_subscr_put(struct bsc_subscr *bsub,
+ const char *file, int line)
+{
+ bsub->use_count--;
+ LOGPSRC(DREF, bsub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR,
+ file, line,
+ "BSC subscr %s usage decreases to: %d\n",
+ bsc_subscr_name(bsub), bsub->use_count);
+ if (bsub->use_count <= 0)
+ bsc_subscr_free(bsub);
+ return NULL;
+}
+
+void log_set_filter_bsc_subscr(struct log_target *target,
+ struct bsc_subscr *bsc_subscr)
+{
+ struct bsc_subscr **fsub = (void*)&target->filter_data[LOG_FLT_BSC_SUBSCR];
+
+ /* free the old data */
+ if (*fsub) {
+ bsc_subscr_put(*fsub);
+ *fsub = NULL;
+ }
+
+ if (bsc_subscr) {
+ target->filter_map |= (1 << LOG_FLT_BSC_SUBSCR);
+ *fsub = bsc_subscr_get(bsc_subscr);
+ } else
+ target->filter_map &= ~(1 << LOG_FLT_BSC_SUBSCR);
+}
diff --git a/src/libbsc/bsc_vty.c b/src/libbsc/bsc_vty.c
new file mode 100644
index 000000000..9fc289503
--- /dev/null
+++ b/src/libbsc/bsc_vty.c
@@ -0,0 +1,4304 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009-2010 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 <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/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm0502.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_om2000.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/db.h>
+#include <openbsc/vty.h>
+#include <osmocom/gprs/gprs_ns.h>
+#include <openbsc/system_information.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/osmo_bsc_rf.h>
+#include <openbsc/pcu_if.h>
+
+#include <openbsc/common_cs.h>
+
+#include <inttypes.h>
+
+#include "../../bscconfig.h"
+
+
+#define LCHAN_NR_STR "Logical Channel Number\n"
+
+
+/* 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" },
+ { BTS_LOC_FIX_3D, "fix3d" },
+ { 0, NULL }
+};
+
+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 int dummy_config_write(struct vty *v)
+{
+ return CMD_SUCCESS;
+}
+
+static 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),
+ get_value_string(abis_nm_adm_state_names, nms->administrative),
+ abis_nm_avail_name(nms->availability), VTY_NEWLINE);
+}
+
+static void dump_pchan_load_vty(struct vty *vty, char *prefix,
+ const struct pchan_load *pl)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pl->pchan); i++) {
+ const struct load_counter *lc = &pl->pchan[i];
+ unsigned int percent;
+
+ if (lc->total == 0)
+ continue;
+
+ percent = (lc->used * 100) / lc->total;
+
+ vty_out(vty, "%s%20s: %3u%% (%u/%u)%s", prefix,
+ gsm_pchan_name(i), percent, lc->used, lc->total,
+ VTY_NEWLINE);
+ }
+}
+
+static void net_dump_vty(struct vty *vty, struct gsm_network *net)
+{
+ struct pchan_load pl;
+
+ vty_out(vty, "BSC is on Country Code %u, Network Code %u "
+ "and has %u BTS%s", net->country_code, net->network_code,
+ net->num_bts, VTY_NEWLINE);
+ vty_out(vty, " Long network name: '%s'%s",
+ net->name_long, VTY_NEWLINE);
+ vty_out(vty, " Short network name: '%s'%s",
+ net->name_short, VTY_NEWLINE);
+ vty_out(vty, " Authentication policy: %s",
+ gsm_auth_policy_name(net->auth_policy));
+ if (net->authorized_reg_str)
+ vty_out(vty, ", authorized regexp: %s", net->authorized_reg_str);
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, " Auto create subscriber: %s%s",
+ net->auto_create_subscr ? "yes" : "no", VTY_NEWLINE);
+ vty_out(vty, " Auto assign extension: %s%s",
+ net->auto_assign_exten ? "yes" : "no", VTY_NEWLINE);
+ vty_out(vty, " Location updating reject cause: %u%s",
+ net->reject_cause, VTY_NEWLINE);
+ vty_out(vty, " Encryption: A5/%u%s", net->a5_encryption,
+ VTY_NEWLINE);
+ vty_out(vty, " NECI (TCH/H): %u%s", net->neci,
+ VTY_NEWLINE);
+ vty_out(vty, " Use TCH for Paging any: %d%s", net->pag_any_tch,
+ VTY_NEWLINE);
+ vty_out(vty, " RRLP Mode: %s%s", rrlp_mode_name(net->rrlp.mode),
+ VTY_NEWLINE);
+ vty_out(vty, " MM Info: %s%s", net->send_mm_info ? "On" : "Off",
+ VTY_NEWLINE);
+ vty_out(vty, " Handover: %s%s", net->handover.active ? "On" : "Off",
+ VTY_NEWLINE);
+ network_chan_load(&pl, net);
+ vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE);
+ dump_pchan_load_vty(vty, " ", &pl);
+
+ /* show rf */
+ if (net->bsc_data)
+ vty_out(vty, " Last RF Command: %s%s",
+ net->bsc_data->rf_ctrl->last_state_command,
+ VTY_NEWLINE);
+ if (net->bsc_data)
+ vty_out(vty, " Last RF Lock Command: %s%s",
+ net->bsc_data->rf_ctrl->last_rf_lock_ctrl_command,
+ VTY_NEWLINE);
+}
+
+DEFUN(bsc_show_net, bsc_show_net_cmd, "show network",
+ SHOW_STR "Display information about a GSM NETWORK\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ net_dump_vty(vty, net);
+
+ 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);
+}
+
+static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+ struct pchan_load pl;
+
+ 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);
+ 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, "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: %s%s",
+ bts->early_classmark_allowed ? "allowed" : "forbidden",
+ 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);
+ 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);
+ 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 state: %s.%s",
+ bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE);
+ } else {
+ vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE);
+ e1isl_dump_vty(vty, bts->oml_link);
+ }
+
+ /* 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);
+}
+
+DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]",
+ SHOW_STR "Display information about a BTS\n"
+ "BTS number")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ int bts_nr;
+
+ if (argc != 0) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+ return CMD_SUCCESS;
+ }
+ /* print all BTS's */
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++)
+ bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+
+ 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 != GSM_PCHAN_NONE)
+ vty_out(vty, " phys_chan_config %s%s",
+ gsm_pchan_name(ts->pchan), VTY_NEWLINE);
+ vty_out(vty, " hopping enabled %u%s",
+ ts->hopping.enabled, VTY_NEWLINE);
+ if (ts->hopping.enabled) {
+ unsigned int i;
+ vty_out(vty, " hopping sequence-number %u%s",
+ ts->hopping.hsn, VTY_NEWLINE);
+ vty_out(vty, " hopping maio %u%s",
+ ts->hopping.maio, VTY_NEWLINE);
+ for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) {
+ if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i))
+ continue;
+ vty_out(vty, " hopping arfcn add %u%s",
+ i, VTY_NEWLINE);
+ }
+ }
+ config_write_e1_link(vty, &ts->e1_link, " ");
+
+ if (ts->trx->bts->model->config_write_ts)
+ ts->trx->bts->model->config_write_ts(vty, ts);
+}
+
+static void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ int i;
+
+ vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE);
+ if (trx->description)
+ vty_out(vty, " description %s%s", trx->description,
+ VTY_NEWLINE);
+ vty_out(vty, " rf_locked %u%s",
+ trx->mo.nm_state.administrative == NM_STATE_LOCKED ? 1 : 0,
+ VTY_NEWLINE);
+ vty_out(vty, " arfcn %u%s", trx->arfcn, VTY_NEWLINE);
+ vty_out(vty, " nominal power %u%s", trx->nominal_power, VTY_NEWLINE);
+ vty_out(vty, " max_power_red %u%s", trx->max_power_red, VTY_NEWLINE);
+ config_write_e1_link(vty, &trx->rsl_e1_link, " rsl ");
+ vty_out(vty, " rsl e1 tei %u%s", trx->rsl_tei, VTY_NEWLINE);
+
+ if (trx->bts->model->config_write_trx)
+ trx->bts->model->config_write_trx(vty, trx);
+
+ for (i = 0; i < TRX_NR_TS; i++)
+ config_write_ts_single(vty, &trx->ts[i]);
+}
+
+static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts)
+{
+ unsigned int i;
+ vty_out(vty, " gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode),
+ VTY_NEWLINE);
+ if (bts->gprs.mode == BTS_GPRS_NONE)
+ return;
+
+ vty_out(vty, " gprs 11bit_rach_support_for_egprs %u%s",
+ bts->gprs.supports_egprs_11bit_rach, VTY_NEWLINE);
+
+ vty_out(vty, " gprs routing area %u%s", bts->gprs.rac,
+ VTY_NEWLINE);
+ vty_out(vty, " gprs network-control-order nc%u%s",
+ bts->gprs.net_ctrl_ord, VTY_NEWLINE);
+ if (!bts->gprs.ctrl_ack_type_use_block)
+ vty_out(vty, " gprs control-ack-type-rach%s", VTY_NEWLINE);
+ vty_out(vty, " gprs cell bvci %u%s", bts->gprs.cell.bvci,
+ VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++)
+ vty_out(vty, " gprs cell timer %s %u%s",
+ get_value_string(gprs_bssgp_cfg_strs, i),
+ bts->gprs.cell.timer[i], VTY_NEWLINE);
+ vty_out(vty, " gprs nsei %u%s", bts->gprs.nse.nsei,
+ VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(bts->gprs.nse.timer); i++)
+ vty_out(vty, " gprs ns timer %s %u%s",
+ get_value_string(gprs_ns_timer_strs, i),
+ bts->gprs.nse.timer[i], VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+ struct gsm_bts_gprs_nsvc *nsvc =
+ &bts->gprs.nsvc[i];
+ struct in_addr ia;
+
+ ia.s_addr = htonl(nsvc->remote_ip);
+ vty_out(vty, " gprs nsvc %u nsvci %u%s", i,
+ nsvc->nsvci, VTY_NEWLINE);
+ vty_out(vty, " gprs nsvc %u local udp port %u%s", i,
+ nsvc->local_port, VTY_NEWLINE);
+ vty_out(vty, " gprs nsvc %u remote udp port %u%s", i,
+ nsvc->remote_port, VTY_NEWLINE);
+ vty_out(vty, " gprs nsvc %u remote ip %s%s", i,
+ inet_ntoa(ia), VTY_NEWLINE);
+ }
+}
+
+/* Write the model data if there is one */
+static void config_write_bts_model(struct vty *vty, struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ if (!bts->model)
+ return;
+
+ if (bts->model->config_write_bts)
+ bts->model->config_write_bts(vty, bts);
+
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ config_write_trx_single(vty, trx);
+}
+
+static void write_amr_modes(struct vty *vty, const char *prefix,
+ const char *name, struct amr_mode *modes, int num)
+{
+ int i;
+
+ vty_out(vty, " %s threshold %s", prefix, name);
+ for (i = 0; i < num - 1; i++)
+ vty_out(vty, " %d", modes[i].threshold);
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, " %s hysteresis %s", prefix, name);
+ for (i = 0; i < num - 1; i++)
+ vty_out(vty, " %d", modes[i].hysteresis);
+ vty_out(vty, "%s", VTY_NEWLINE);
+}
+
+static void config_write_bts_amr(struct vty *vty, struct gsm_bts *bts,
+ struct amr_multirate_conf *mr, int full)
+{
+ 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);
+ }
+
+ /* Is periodic LU enabled or disabled? */
+ if (bts->si_common.chan_desc.t3212 == 0)
+ vty_out(vty, " no periodic location update%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " periodic location update %u%s",
+ bts->si_common.chan_desc.t3212 * 6, 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-descrption attach %u%s",
+ bts->si_common.chan_desc.att, VTY_NEWLINE);
+ vty_out(vty, " channel-descrption bs-pa-mfrms %u%s",
+ bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE);
+ vty_out(vty, " channel-descrption bs-ag-blks-res %u%s",
+ bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE);
+
+ if (bts->rach_b_thresh != -1)
+ vty_out(vty, " rach nm busy threshold %u%s",
+ bts->rach_b_thresh, VTY_NEWLINE);
+ if (bts->rach_ldavg_slots != -1)
+ vty_out(vty, " rach nm load average %u%s",
+ bts->rach_ldavg_slots, VTY_NEWLINE);
+ if (bts->si_common.rach_control.cell_bar)
+ vty_out(vty, " cell barred 1%s", VTY_NEWLINE);
+ if ((bts->si_common.rach_control.t2 & 0x4) == 0)
+ vty_out(vty, " rach emergency call allowed 1%s", VTY_NEWLINE);
+ if ((bts->si_common.rach_control.t3) != 0)
+ for (i = 0; i < 8; i++)
+ if (bts->si_common.rach_control.t3 & (0x1 << i))
+ vty_out(vty, " rach access-control-class %d barred%s", i, VTY_NEWLINE);
+ if ((bts->si_common.rach_control.t2 & 0xfb) != 0)
+ for (i = 0; i < 8; i++)
+ if ((i != 2) && (bts->si_common.rach_control.t2 & (0x1 << i)))
+ vty_out(vty, " rach access-control-class %d barred%s", i+8, VTY_NEWLINE);
+ 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);
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ case GSM_BTS_TYPE_OSMOBTS:
+ vty_out(vty, " ip.access unit_id %u %u%s",
+ bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE);
+ if (bts->ip_access.rsl_ip) {
+ struct in_addr ia;
+ ia.s_addr = htonl(bts->ip_access.rsl_ip);
+ vty_out(vty, " ip.access rsl-ip %s%s", inet_ntoa(ia),
+ VTY_NEWLINE);
+ }
+ vty_out(vty, " oml ip.access stream_id %u line %u%s",
+ bts->oml_tei, bts->oml_e1_link.e1_nr, VTY_NEWLINE);
+ break;
+ case GSM_BTS_TYPE_NOKIA_SITE:
+ vty_out(vty, " nokia_site skip-reset %d%s", bts->nokia.skip_reset, VTY_NEWLINE);
+ vty_out(vty, " nokia_site no-local-rel-conf %d%s",
+ bts->nokia.no_loc_rel_cnf, VTY_NEWLINE);
+ vty_out(vty, " nokia_site bts-reset-timer %d%s", bts->nokia.bts_reset_timer_cnf, VTY_NEWLINE);
+ /* fall through: Nokia requires "oml e1" parameters also */
+ default:
+ config_write_e1_link(vty, &bts->oml_e1_link, " oml ");
+ vty_out(vty, " oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE);
+ break;
+ }
+
+ /* 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);
+ }
+
+ 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);
+
+ 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);
+
+ 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);
+
+ vty_out(vty, "network%s", VTY_NEWLINE);
+ vty_out(vty, " network country code %u%s", gsmnet->country_code, VTY_NEWLINE);
+ vty_out(vty, " mobile network code %u%s", gsmnet->network_code, VTY_NEWLINE);
+ vty_out(vty, " short name %s%s", gsmnet->name_short, VTY_NEWLINE);
+ vty_out(vty, " long name %s%s", gsmnet->name_long, VTY_NEWLINE);
+ vty_out(vty, " auth policy %s%s", gsm_auth_policy_name(gsmnet->auth_policy), VTY_NEWLINE);
+ if (gsmnet->authorized_reg_str)
+ vty_out(vty, " authorized-regexp %s%s", gsmnet->authorized_reg_str, VTY_NEWLINE);
+ vty_out(vty, " location updating reject cause %u%s",
+ gsmnet->reject_cause, VTY_NEWLINE);
+ vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE);
+ vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE);
+ vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE);
+ vty_out(vty, " rrlp mode %s%s", rrlp_mode_name(gsmnet->rrlp.mode),
+ VTY_NEWLINE);
+ vty_out(vty, " mm info %u%s", gsmnet->send_mm_info, VTY_NEWLINE);
+ vty_out(vty, " handover %u%s", gsmnet->handover.active, VTY_NEWLINE);
+ vty_out(vty, " handover window rxlev averaging %u%s",
+ gsmnet->handover.win_rxlev_avg, VTY_NEWLINE);
+ vty_out(vty, " handover window rxqual averaging %u%s",
+ gsmnet->handover.win_rxqual_avg, VTY_NEWLINE);
+ vty_out(vty, " handover window rxlev neighbor averaging %u%s",
+ gsmnet->handover.win_rxlev_avg_neigh, VTY_NEWLINE);
+ vty_out(vty, " handover power budget interval %u%s",
+ gsmnet->handover.pwr_interval, VTY_NEWLINE);
+ vty_out(vty, " handover power budget hysteresis %u%s",
+ gsmnet->handover.pwr_hysteresis, VTY_NEWLINE);
+ vty_out(vty, " handover maximum distance %u%s",
+ gsmnet->handover.max_distance, VTY_NEWLINE);
+ vty_out(vty, " timer t3101 %u%s", gsmnet->T3101, VTY_NEWLINE);
+ vty_out(vty, " timer t3103 %u%s", gsmnet->T3103, VTY_NEWLINE);
+ vty_out(vty, " timer t3105 %u%s", gsmnet->T3105, VTY_NEWLINE);
+ vty_out(vty, " timer t3107 %u%s", gsmnet->T3107, VTY_NEWLINE);
+ vty_out(vty, " timer t3109 %u%s", gsmnet->T3109, VTY_NEWLINE);
+ vty_out(vty, " timer t3111 %u%s", gsmnet->T3111, VTY_NEWLINE);
+ vty_out(vty, " timer t3113 %u%s", gsmnet->T3113, VTY_NEWLINE);
+ vty_out(vty, " timer t3115 %u%s", gsmnet->T3115, VTY_NEWLINE);
+ vty_out(vty, " timer t3117 %u%s", gsmnet->T3117, VTY_NEWLINE);
+ vty_out(vty, " timer t3119 %u%s", gsmnet->T3119, VTY_NEWLINE);
+ vty_out(vty, " timer t3122 %u%s", gsmnet->T3122, VTY_NEWLINE);
+ vty_out(vty, " timer t3141 %u%s", gsmnet->T3141, VTY_NEWLINE);
+ vty_out(vty, " dyn_ts_allow_tch_f %d%s",
+ gsmnet->dyn_ts_allow_tch_f ? 1 : 0, VTY_NEWLINE);
+ vty_out(vty, " subscriber-keep-in-ram %d%s",
+ gsmnet->subscr_group->keep_subscr, VTY_NEWLINE);
+ if (gsmnet->tz.override != 0) {
+ if (gsmnet->tz.dst)
+ vty_out(vty, " timezone %d %d %d%s",
+ gsmnet->tz.hr, gsmnet->tz.mn, gsmnet->tz.dst,
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " timezone %d %d%s",
+ gsmnet->tz.hr, gsmnet->tz.mn, VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s",
+ trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE);
+ vty_out(vty, "Description: %s%s",
+ trx->description ? trx->description : "(null)", VTY_NEWLINE);
+ vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, "
+ "resulting BS power: %d dBm%s",
+ trx->nominal_power, trx->max_power_red,
+ trx->nominal_power - trx->max_power_red, VTY_NEWLINE);
+ vty_out(vty, " NM State: ");
+ net_dump_nmstate(vty, &trx->mo.nm_state);
+ 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%s",
+ trx->rsl_tei, VTY_NEWLINE);
+ } else {
+ vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE);
+ e1isl_dump_vty(vty, trx->rsl_link);
+ }
+}
+
+DEFUN(show_trx,
+ show_trx_cmd,
+ "show trx [<0-255>] [<0-255>]",
+ SHOW_STR "Display information about a TRX\n"
+ "BTS Number\n"
+ "TRX Number\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts = NULL;
+ struct gsm_bts_trx *trx;
+ int bts_nr, trx_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+ }
+ if (argc >= 2) {
+ trx_nr = atoi(argv[1]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ trx_dump_vty(vty, trx);
+ return CMD_SUCCESS;
+ }
+ if (bts) {
+ /* print all TRX in this BTS */
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ trx_dump_vty(vty, trx);
+ }
+ return CMD_SUCCESS;
+ }
+
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ bts = gsm_bts_num(net, bts_nr);
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ trx_dump_vty(vty, trx);
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+
+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, TSC %u",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan), gsm_ts_tsc(ts));
+ if (ts->pchan == GSM_PCHAN_TCH_F_PDCH)
+ vty_out(vty, " (%s mode)",
+ ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F");
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, " NM State: ");
+ 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>]",
+ SHOW_STR "Display information about a TS\n"
+ "BTS Number\n" "TRX Number\n" "Timeslot Number\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts = NULL;
+ struct gsm_bts_trx *trx = NULL;
+ struct gsm_bts_trx_ts *ts = NULL;
+ int bts_nr, trx_nr, ts_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+ }
+ if (argc >= 2) {
+ trx_nr = atoi(argv[1]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ }
+ if (argc >= 3) {
+ ts_nr = atoi(argv[2]);
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% can't find TS '%s'%s", argv[2],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ /* Fully Specified: print and exit */
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ return CMD_SUCCESS;
+ }
+
+ if (bts && trx) {
+ /* Iterate over all TS in this TRX */
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ }
+ } else if (bts) {
+ /* Iterate over all TRX in this BTS, TS in each TRX */
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ }
+ }
+ } else {
+ /* Iterate over all BTS, TRX in each BTS, TS in each TRX */
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ bts = gsm_bts_num(net, bts_nr);
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ }
+ }
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+static void subscr_dump_vty(struct vty *vty, struct gsm_subscriber *subscr)
+{
+ vty_out(vty, " ID: %llu, Authorized: %d%s", subscr->id,
+ subscr->authorized, VTY_NEWLINE);
+ if (strlen(subscr->name))
+ vty_out(vty, " Name: '%s'%s", subscr->name, VTY_NEWLINE);
+ if (strlen(subscr->extension))
+ vty_out(vty, " Extension: %s%s", subscr->extension,
+ VTY_NEWLINE);
+ vty_out(vty, " IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
+ if (subscr->tmsi != GSM_RESERVED_TMSI)
+ vty_out(vty, " TMSI: %08X%s", subscr->tmsi,
+ VTY_NEWLINE);
+
+ vty_out(vty, " Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+}
+
+static void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub)
+{
+ if (strlen(bsub->imsi))
+ vty_out(vty, " IMSI: %s%s", bsub->imsi, VTY_NEWLINE);
+ if (bsub->tmsi != GSM_RESERVED_TMSI)
+ vty_out(vty, " TMSI: 0x%08x%s", bsub->tmsi,
+ VTY_NEWLINE);
+ vty_out(vty, " Use count: %d%s", bsub->use_count, VTY_NEWLINE);
+}
+
+static void meas_rep_dump_uni_vty(struct vty *vty,
+ struct gsm_meas_rep_unidir *mru,
+ const char *prefix,
+ const char *dir)
+{
+ vty_out(vty, "%s RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ",
+ prefix, dir, rxlev2dbm(mru->full.rx_lev),
+ dir, rxlev2dbm(mru->sub.rx_lev));
+ vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s",
+ dir, mru->full.rx_qual, dir, mru->sub.rx_qual,
+ VTY_NEWLINE);
+}
+
+static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr,
+ const char *prefix)
+{
+ vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE);
+ vty_out(vty, "%s Flags: %s%s%s%s%s", prefix,
+ mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "",
+ mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "",
+ mr->flags & MEAS_REP_F_FPC ? "FPC " : "",
+ mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ",
+ VTY_NEWLINE);
+ if (mr->flags & MEAS_REP_F_MS_TO)
+ vty_out(vty, "%s MS Timing Offset: %d%s", prefix, mr->ms_timing_offset, VTY_NEWLINE);
+ if (mr->flags & MEAS_REP_F_MS_L1)
+ vty_out(vty, "%s L1 MS Power: %u dBm, Timing Advance: %u%s",
+ prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE);
+ if (mr->flags & MEAS_REP_F_DL_VALID)
+ meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl");
+ meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul");
+}
+
+/* FIXME: move this to libosmogsm */
+static const struct value_string gsm48_cmode_names[] = {
+ { GSM48_CMODE_SIGN, "signalling" },
+ { GSM48_CMODE_SPEECH_V1, "FR or HR" },
+ { GSM48_CMODE_SPEECH_EFR, "EFR" },
+ { GSM48_CMODE_SPEECH_AMR, "AMR" },
+ { GSM48_CMODE_DATA_14k5, "CSD(14k5)" },
+ { GSM48_CMODE_DATA_12k0, "CSD(12k0)" },
+ { GSM48_CMODE_DATA_6k0, "CSD(6k0)" },
+ { GSM48_CMODE_DATA_3k6, "CSD(3k6)" },
+ { 0, NULL }
+};
+
+/* 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)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ if (ts->dyn.pchan_is == ts->dyn.pchan_want)
+ vty_out(vty, " as %s",
+ gsm_pchan_name(ts->dyn.pchan_is));
+ else
+ vty_out(vty, " switching %s -> %s",
+ gsm_pchan_name(ts->dyn.pchan_is),
+ gsm_pchan_name(ts->dyn.pchan_want));
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0)
+ vty_out(vty, " as %s",
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F");
+ else
+ vty_out(vty, " switching %s -> %s",
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F",
+ (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH"
+ : "TCH/F");
+ break;
+ default:
+ /* no dyn ts */
+ break;
+ }
+}
+
+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);
+ /* show dyn TS details, if applicable */
+ switch (lchan->ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ vty_out(vty, " Osmocom Dyn TS:");
+ vty_out_dyn_ts_status(vty, lchan->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, lchan->ts);
+ vty_out(vty, VTY_NEWLINE);
+ break;
+ default:
+ /* no dyn ts */
+ break;
+ }
+ vty_out(vty, " Connection: %u, State: %s%s%s%s",
+ lchan->conn ? 1: 0,
+ gsm_lchans_name(lchan->state),
+ lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "",
+ lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "",
+ 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",
+ get_value_string(gsm48_cmode_names, lchan->tch_mode),
+ VTY_NEWLINE);
+ if (lchan->conn && lchan->conn->subscr) {
+ vty_out(vty, " Subscriber:%s", VTY_NEWLINE);
+ subscr_dump_vty(vty, lchan->conn->subscr);
+ } else
+ vty_out(vty, " No Subscriber%s", VTY_NEWLINE);
+ if (is_ipaccess_bts(lchan->ts->trx->bts)) {
+ struct in_addr ia;
+ 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);
+ }
+
+ /* 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));
+ 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), gsm_lchans_name(lchan->state),
+ 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 *))
+{
+ int lchan_nr;
+ for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; lchan_nr++) {
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+ if ((lchan->type == GSM_LCHAN_NONE) && (lchan->state == LCHAN_S_NONE))
+ continue;
+ dump_cb(vty, lchan);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ int ts_nr;
+
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ dump_lchan_trx_ts(ts, vty, dump_cb);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ int trx_nr;
+
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr);
+ dump_lchan_trx(trx, vty, dump_cb);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int lchan_summary(struct vty *vty, int argc, const char **argv,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int bts_nr, trx_nr, ts_nr, lchan_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS %s%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+
+ if (argc == 1)
+ return dump_lchan_bts(bts, vty, dump_cb);
+ }
+ if (argc >= 2) {
+ trx_nr = atoi(argv[1]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX %s%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx = gsm_bts_trx_num(bts, trx_nr);
+
+ if (argc == 2)
+ return dump_lchan_trx(trx, vty, dump_cb);
+ }
+ if (argc >= 3) {
+ ts_nr = atoi(argv[2]);
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% can't find TS %s%s", argv[2],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ ts = &trx->ts[ts_nr];
+
+ if (argc == 3)
+ return dump_lchan_trx_ts(ts, vty, dump_cb);
+ }
+ if (argc >= 4) {
+ lchan_nr = atoi(argv[3]);
+ if (lchan_nr >= TS_MAX_LCHAN) {
+ vty_out(vty, "%% can't find LCHAN %s%s", argv[3],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan = &ts->lchan[lchan_nr];
+ dump_cb(vty, lchan);
+ return CMD_SUCCESS;
+ }
+
+
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ bts = gsm_bts_num(net, bts_nr);
+ dump_lchan_bts(bts, vty, dump_cb);
+ }
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(show_lchan,
+ show_lchan_cmd,
+ "show lchan [<0-255>] [<0-255>] [<0-7>] [lchan_nr]",
+ SHOW_STR "Display information about a logical channel\n"
+ "BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+ LCHAN_NR_STR)
+
+{
+ return lchan_summary(vty, argc, argv, lchan_dump_full_vty);
+}
+
+DEFUN(show_lchan_summary,
+ show_lchan_summary_cmd,
+ "show lchan summary [<0-255>] [<0-255>] [<0-7>] [lchan_nr]",
+ SHOW_STR "Display information about a logical channel\n"
+ "Short summary\n"
+ "BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+ LCHAN_NR_STR)
+{
+ return lchan_summary(vty, argc, argv, lchan_dump_short_vty);
+}
+
+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);
+ bsc_subscr_dump_vty(vty, pag->bsub);
+}
+
+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)
+ paging_dump_vty(vty, pag);
+}
+
+DEFUN(show_paging,
+ show_paging_cmd,
+ "show paging [<0-255>]",
+ SHOW_STR "Display information about paging reuqests of a BTS\n"
+ "BTS Number\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
+ int bts_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS %s%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+ bts_paging_dump_vty(vty, bts);
+
+ return CMD_SUCCESS;
+ }
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ bts = gsm_bts_num(net, bts_nr);
+ bts_paging_dump_vty(vty, bts);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_paging_group,
+ show_paging_group_cmd,
+ "show paging-group <0-255> IMSI",
+ SHOW_STR "Display the paging group\n"
+ "BTS Number\n" "IMSI\n")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
+ unsigned int page_group;
+ int bts_nr = atoi(argv[0]);
+
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS %s%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts = gsm_bts_num(net, bts_nr);
+ if (!bts) {
+ vty_out(vty, "%% can't find BTS %s%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc,
+ str_to_imsi(argv[1]));
+ vty_out(vty, "%%Paging group for IMSI %" PRIu64 " on BTS #%d is %u%s",
+ str_to_imsi(argv[1]), bts->nr,
+ page_group, VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_neci,
+ cfg_net_neci_cmd,
+ "neci (0|1)",
+ "New Establish Cause Indication\n"
+ "Don't set the NECI bit\n" "Set the NECI bit\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+ gsmnet->neci = atoi(argv[0]);
+ gsm_net_update_ctype(gsmnet);
+ return CMD_SUCCESS;
+}
+
+#define HANDOVER_STR "Handover Options\n"
+
+DEFUN(cfg_net_handover, cfg_net_handover_cmd,
+ "handover (0|1)",
+ HANDOVER_STR
+ "Don't perform in-call handover\n"
+ "Perform in-call handover\n")
+{
+ int enable = atoi(argv[0]);
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+ if (enable && ipacc_rtp_direct) {
+ vty_out(vty, "%% Cannot enable handover unless RTP Proxy mode "
+ "is enabled by using the -P command line option%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ gsmnet->handover.active = enable;
+
+ return CMD_SUCCESS;
+}
+
+#define HO_WIN_STR HANDOVER_STR "Measurement Window\n"
+#define HO_WIN_RXLEV_STR HO_WIN_STR "Received Level Averaging\n"
+#define HO_WIN_RXQUAL_STR HO_WIN_STR "Received Quality Averaging\n"
+#define HO_PBUDGET_STR HANDOVER_STR "Power Budget\n"
+#define HO_AVG_COUNT_STR "Amount to use for Averaging\n"
+
+DEFUN(cfg_net_ho_win_rxlev_avg, cfg_net_ho_win_rxlev_avg_cmd,
+ "handover window rxlev averaging <1-10>",
+ HO_WIN_RXLEV_STR
+ "How many RxLev measurements are used for averaging\n"
+ HO_AVG_COUNT_STR)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->handover.win_rxlev_avg = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_win_rxqual_avg, cfg_net_ho_win_rxqual_avg_cmd,
+ "handover window rxqual averaging <1-10>",
+ HO_WIN_RXQUAL_STR
+ "How many RxQual measurements are used for averaging\n"
+ HO_AVG_COUNT_STR)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->handover.win_rxqual_avg = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_win_rxlev_neigh_avg, cfg_net_ho_win_rxlev_avg_neigh_cmd,
+ "handover window rxlev neighbor averaging <1-10>",
+ HO_WIN_RXLEV_STR "Neighbor\n"
+ "How many RxQual measurements are used for averaging\n"
+ HO_AVG_COUNT_STR)
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->handover.win_rxlev_avg_neigh = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_pwr_interval, cfg_net_ho_pwr_interval_cmd,
+ "handover power budget interval <1-99>",
+ HO_PBUDGET_STR
+ "How often to check if we have a better cell (SACCH frames)\n"
+ "Interval\n" "Number\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->handover.pwr_interval = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_pwr_hysteresis, cfg_net_ho_pwr_hysteresis_cmd,
+ "handover power budget hysteresis <0-999>",
+ HO_PBUDGET_STR
+ "How many dB does a neighbor to be stronger to become a HO candidate\n"
+ "Hysteresis\n" "Number\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->handover.pwr_hysteresis = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_ho_max_distance, cfg_net_ho_max_distance_cmd,
+ "handover maximum distance <0-9999>",
+ HANDOVER_STR
+ "How big is the maximum timing advance before HO is forced\n"
+ "Distance\n" "Number\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->handover.max_distance = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_pag_any_tch,
+ cfg_net_pag_any_tch_cmd,
+ "paging any use tch (0|1)",
+ "Assign a TCH when receiving a Paging Any request\n"
+ "Any Channel\n" "Use\n" "TCH\n"
+ "Do not use TCH for Paging Request Any\n"
+ "Do use TCH for Paging Request Any\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ gsmnet->pag_any_tch = atoi(argv[0]);
+ gsm_net_update_ctype(gsmnet);
+ return CMD_SUCCESS;
+}
+
+#define DECLARE_TIMER(number, doc) \
+ DEFUN(cfg_net_T##number, \
+ cfg_net_T##number##_cmd, \
+ "timer t" #number " <0-65535>", \
+ "Configure GSM Timers\n" \
+ doc "Timer Value in seconds\n") \
+{ \
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty); \
+ int value = atoi(argv[0]); \
+ \
+ if (value < 0 || value > 65535) { \
+ vty_out(vty, "Timer value %s out of range.%s", \
+ argv[0], VTY_NEWLINE); \
+ return CMD_WARNING; \
+ } \
+ \
+ gsmnet->T##number = value; \
+ return CMD_SUCCESS; \
+}
+
+DECLARE_TIMER(3101, "Set the timeout value for IMMEDIATE ASSIGNMENT.\n")
+DECLARE_TIMER(3103, "Set the timeout value for HANDOVER.\n")
+DECLARE_TIMER(3105, "Set the timer for repetition of PHYSICAL INFORMATION.\n")
+DECLARE_TIMER(3107, "Currently not used.\n")
+DECLARE_TIMER(3109, "Set the RSL SACCH deactivation timeout.\n")
+DECLARE_TIMER(3111, "Set the RSL timeout to wait before releasing the RF Channel.\n")
+DECLARE_TIMER(3113, "Set the time to try paging a subscriber.\n")
+DECLARE_TIMER(3115, "Currently not used.\n")
+DECLARE_TIMER(3117, "Currently not used.\n")
+DECLARE_TIMER(3119, "Currently not used.\n")
+DECLARE_TIMER(3122, "Waiting time (seconds) after IMM ASS REJECT\n")
+DECLARE_TIMER(3141, "Currently not used.\n")
+
+DEFUN_DEPRECATED(cfg_net_dtx,
+ cfg_net_dtx_cmd,
+ "dtx-used (0|1)",
+ ".HIDDEN\n""Obsolete\n""Obsolete\n")
+{
+ vty_out(vty, "%% 'dtx-used' is now deprecated: use dtx * "
+ "configuration options of BTS instead%s", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+/* per-BTS configuration */
+DEFUN(cfg_bts,
+ cfg_bts_cmd,
+ "bts <0-255>",
+ "Select a BTS to configure\n"
+ "BTS Number\n")
+{
+ 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 = gsm_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(cfg_bts_type,
+ cfg_bts_type_cmd,
+ "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(cfg_bts_band,
+ cfg_bts_band_cmd,
+ "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(cfg_bts_dtxu, cfg_bts_dtxu_cmd, "dtx uplink [force]",
+ "Configure discontinuous transmission\n"
+ "Enable Uplink DTX for this BTS\n"
+ "MS 'shall' use DTXu instead of 'may' use (might not be supported by "
+ "older phones).\n")
+{
+ 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(cfg_bts_no_dtxu, cfg_bts_no_dtxu_cmd, "no dtx uplink",
+ NO_STR
+ "Configure discontinuous transmission\n"
+ "Disable Uplink DTX for this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_dtxd, cfg_bts_dtxd_cmd, "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(cfg_bts_no_dtxd, cfg_bts_no_dtxd_cmd, "no dtx downlink",
+ NO_STR
+ "Configure discontinuous transmission\n"
+ "Disable Downlink DTX for this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->dtxd = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_ci,
+ cfg_bts_ci_cmd,
+ "cell_identity <0-65535>",
+ "Set the Cell identity of this BTS\n" "Cell Identity\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int ci = atoi(argv[0]);
+
+ if (ci < 0 || ci > 0xffff) {
+ vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s",
+ ci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts->cell_identity = ci;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_lac,
+ cfg_bts_lac_cmd,
+ "location_area_code <0-65535>",
+ "Set the Location Area Code (LAC) of this BTS\n" "LAC\n")
+{
+ 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(cfg_bts_bsic,
+ cfg_bts_bsic_cmd,
+ "base_station_id_code <0-63>",
+ "Set the Base Station Identity Code (BSIC) of this BTS\n"
+ "BSIC of this BTS\n")
+{
+ 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(cfg_bts_unit_id,
+ cfg_bts_unit_id_cmd,
+ "ip.access unit_id <0-65534> <0-255>",
+ "Abis/IP specific options\n"
+ "Set the IPA BTS Unit ID\n"
+ "Unit ID (Site)\n"
+ "Unit ID (BTS)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int site_id = atoi(argv[0]);
+ int bts_id = atoi(argv[1]);
+
+ if (!is_ipaccess_bts(bts)) {
+ vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->ip_access.site_id = site_id;
+ bts->ip_access.bts_id = bts_id;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rsl_ip,
+ cfg_bts_rsl_ip_cmd,
+ "ip.access rsl-ip A.B.C.D",
+ "Abis/IP specific options\n"
+ "Set the IPA RSL IP Address of the BSC\n"
+ "Destination IP address for RSL connection\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct in_addr ia;
+
+ if (!is_ipaccess_bts(bts)) {
+ vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ inet_aton(argv[0], &ia);
+ bts->ip_access.rsl_ip = ntohl(ia.s_addr);
+
+ return CMD_SUCCESS;
+}
+
+#define NOKIA_STR "Nokia *Site related commands\n"
+
+DEFUN(cfg_bts_nokia_site_skip_reset,
+ cfg_bts_nokia_site_skip_reset_cmd,
+ "nokia_site skip-reset (0|1)",
+ NOKIA_STR
+ "Skip the reset step during bootstrap process of this BTS\n"
+ "Do NOT skip the reset\n" "Skip the reset\n")
+{
+ 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(cfg_bts_nokia_site_no_loc_rel_cnf,
+ cfg_bts_nokia_site_no_loc_rel_cnf_cmd,
+ "nokia_site no-local-rel-conf (0|1)",
+ NOKIA_STR
+ "Do not wait for RELease CONFirm message when releasing channel locally\n"
+ "Wait for RELease CONFirm\n" "Do not wait for RELease CONFirm\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (!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(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")
+{
+ 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(cfg_bts_stream_id,
+ cfg_bts_stream_id_cmd,
+ "oml ip.access stream_id <0-255> line E1_LINE",
+ OML_STR IPA_STR
+ "Set the ip.access Stream ID of the OML link of this BTS\n"
+ "Stream Identifier\n" "Virtual E1 Line Number\n" "Virtual E1 Line Number\n")
+{
+ 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;
+}
+
+#define OML_E1_STR OML_STR "OML E1/T1 Configuration\n"
+
+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(cfg_bts_oml_e1_tei,
+ cfg_bts_oml_e1_tei_cmd,
+ "oml e1 tei <0-63>",
+ OML_E1_STR
+ "Set the TEI to be used for OML\n"
+ "TEI Number\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->oml_tei = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_challoc, cfg_bts_challoc_cmd,
+ "channel allocator (ascending|descending)",
+ "Channnel Allocator\n" "Channel Allocator\n"
+ "Allocate Timeslots and Transceivers in ascending order\n"
+ "Allocate Timeslots and Transceivers in descending order\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (!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(cfg_bts_rach_tx_integer,
+ cfg_bts_rach_tx_integer_cmd,
+ "rach tx integer <0-15>",
+ RACH_STR
+ "Set the raw tx integer value in RACH Control parameters IE\n"
+ "Set the raw tx integer value in RACH Control parameters IE\n"
+ "Raw tx integer value in RACH Control parameters IE\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_max_trans,
+ cfg_bts_rach_max_trans_cmd,
+ "rach max transmission (1|2|4|7)",
+ RACH_STR
+ "Set the maximum number of RACH burst transmissions\n"
+ "Set the maximum number of RACH burst transmissions\n"
+ "Maximum number of 1 RACH burst transmissions\n"
+ "Maximum number of 2 RACH burst transmissions\n"
+ "Maximum number of 4 RACH burst transmissions\n"
+ "Maximum number of 7 RACH burst transmissions\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0]));
+ return CMD_SUCCESS;
+}
+
+#define CD_STR "Channel Description\n"
+
+DEFUN(cfg_bts_chan_desc_att,
+ cfg_bts_chan_desc_att_cmd,
+ "channel-descrption attach (0|1)",
+ CD_STR
+ "Set if attachment is required\n"
+ "Attachment is NOT required\n"
+ "Attachment is required (standard)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->si_common.chan_desc.att = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_chan_desc_bs_pa_mfrms,
+ cfg_bts_chan_desc_bs_pa_mfrms_cmd,
+ "channel-descrption bs-pa-mfrms <2-9>",
+ CD_STR
+ "Set number of multiframe periods for paging groups\n"
+ "Number of multiframe periods for paging groups\n")
+{
+ 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;
+}
+
+DEFUN(cfg_bts_chan_desc_bs_ag_blks_res,
+ cfg_bts_chan_desc_bs_ag_blks_res_cmd,
+ "channel-descrption bs-ag-blks-res <0-7>",
+ CD_STR
+ "Set number of blocks reserved for access grant\n"
+ "Number of blocks reserved for access grant\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int bs_ag_blks_res = atoi(argv[0]);
+
+ bts->si_common.chan_desc.bs_ag_blks_res = bs_ag_blks_res;
+ return CMD_SUCCESS;
+}
+
+#define NM_STR "Network Management\n"
+
+DEFUN(cfg_bts_rach_nm_b_thresh,
+ cfg_bts_rach_nm_b_thresh_cmd,
+ "rach nm busy threshold <0-255>",
+ RACH_STR NM_STR
+ "Set the NM Busy Threshold\n"
+ "Set the NM Busy Threshold\n"
+ "NM Busy Threshold in dB")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->rach_b_thresh = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_nm_ldavg,
+ cfg_bts_rach_nm_ldavg_cmd,
+ "rach nm load average <0-65535>",
+ RACH_STR NM_STR
+ "Set the NM Loadaverage Slots value\n"
+ "Set the NM Loadaverage Slots value\n"
+ "NM Loadaverage Slots value\n")
+{
+ struct gsm_bts *bts = vty->index;
+ bts->rach_ldavg_slots = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd,
+ "cell barred (0|1)",
+ "Should this cell be barred from access?\n"
+ "Should this cell be barred from access?\n"
+ "Cell should NOT be barred\n"
+ "Cell should be barred\n")
+
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.rach_control.cell_bar = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_ec_allowed, cfg_bts_rach_ec_allowed_cmd,
+ "rach emergency call allowed (0|1)",
+ RACH_STR
+ "Should this cell allow emergency calls?\n"
+ "Should this cell allow emergency calls?\n"
+ "Should this cell allow emergency calls?\n"
+ "Do NOT allow emergency calls\n"
+ "Allow emergency calls\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (atoi(argv[0]) == 0)
+ bts->si_common.rach_control.t2 |= 0x4;
+ else
+ bts->si_common.rach_control.t2 &= ~0x4;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rach_ac_class, cfg_bts_rach_ac_class_cmd,
+ "rach access-control-class (0|1|2|3|4|5|6|7|8|9|11|12|13|14|15) (barred|allowed)",
+ RACH_STR
+ "Set access control class\n"
+ "Access control class 0\n"
+ "Access control class 1\n"
+ "Access control class 2\n"
+ "Access control class 3\n"
+ "Access control class 4\n"
+ "Access control class 5\n"
+ "Access control class 6\n"
+ "Access control class 7\n"
+ "Access control class 8\n"
+ "Access control class 9\n"
+ "Access control class 11 for PLMN use\n"
+ "Access control class 12 for security services\n"
+ "Access control class 13 for public utilities (e.g. water/gas suppliers)\n"
+ "Access control class 14 for emergency services\n"
+ "Access control class 15 for PLMN staff\n"
+ "barred to use access control class\n"
+ "allowed to use access control class\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ uint8_t control_class;
+ uint8_t allowed = 0;
+
+ if (strcmp(argv[1], "allowed") == 0)
+ allowed = 1;
+
+ control_class = atoi(argv[0]);
+ if (control_class < 8)
+ if (allowed)
+ bts->si_common.rach_control.t3 &= ~(0x1 << control_class);
+ else
+ bts->si_common.rach_control.t3 |= (0x1 << control_class);
+ else
+ if (allowed)
+ bts->si_common.rach_control.t2 &= ~(0x1 << (control_class - 8));
+ else
+ bts->si_common.rach_control.t2 |= (0x1 << (control_class - 8));
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_ms_max_power, cfg_bts_ms_max_power_cmd,
+ "ms max power <0-40>",
+ "MS Options\n"
+ "Maximum transmit power of the MS\n"
+ "Maximum transmit power of the MS\n"
+ "Maximum transmit power of the MS in dBm")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->ms_max_power = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define CELL_STR "Cell Parameters\n"
+
+DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd,
+ "cell reselection hysteresis <0-14>",
+ CELL_STR "Cell re-selection parameters\n"
+ "Cell Re-Selection Hysteresis in dB\n"
+ "Cell Re-Selection Hysteresis in dB")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_rxlev_acc_min, cfg_bts_rxlev_acc_min_cmd,
+ "rxlev access min <0-63>",
+ "Minimum RxLev needed for cell access\n"
+ "Minimum RxLev needed for cell access\n"
+ "Minimum RxLev needed for cell access\n"
+ "Minimum RxLev needed for cell access (better than -110dBm)")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_bar_qualify, cfg_bts_cell_bar_qualify_cmd,
+ "cell bar qualify (0|1)",
+ CELL_STR "Cell Bar Qualify\n" "Cell Bar Qualify\n"
+ "Set CBQ to 0\n" "Set CBQ to 1\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.cbq = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_resel_ofs, cfg_bts_cell_resel_ofs_cmd,
+ "cell reselection offset <0-126>",
+ CELL_STR "Cell Re-Selection Parameters\n"
+ "Cell Re-Selection Offset (CRO) in dB\n"
+ "Cell Re-Selection Offset (CRO) in dB\n"
+ )
+{
+ 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(cfg_bts_temp_ofs, cfg_bts_temp_ofs_cmd,
+ "temporary offset <0-60>",
+ "Cell selection temporary negative offset\n"
+ "Cell selection temporary negative offset\n"
+ "Cell selection temporary negative offset in dB")
+{
+ 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(cfg_bts_temp_ofs_inf, cfg_bts_temp_ofs_inf_cmd,
+ "temporary offset infinite",
+ "Cell selection temporary negative offset\n"
+ "Cell selection temporary negative offset\n"
+ "Sets cell selection temporary negative offset to infinity")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.temp_offs = 7;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_penalty_time, cfg_bts_penalty_time_cmd,
+ "penalty time <20-620>",
+ "Cell selection penalty time\n"
+ "Cell selection penalty time\n"
+ "Cell selection penalty time in seconds (by 20s increments)\n")
+{
+ 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(cfg_bts_penalty_time_rsvd, cfg_bts_penalty_time_rsvd_cmd,
+ "penalty time reserved",
+ "Cell selection penalty time\n"
+ "Cell selection penalty time\n"
+ "Set cell selection penalty time to reserved value 31, "
+ "(indicate that CELL_RESELECT_OFFSET is subtracted from C2 "
+ "and TEMPORARY_OFFSET is ignored)")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.cell_ro_sel_par.present = 1;
+ bts->si_common.cell_ro_sel_par.penalty_time = 31;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_per_loc_upd, cfg_bts_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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.chan_desc.t3212 = atoi(argv[0]) / 6;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_per_loc_upd, cfg_bts_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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->si_common.chan_desc.t3212 = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_radio_link_timeout, cfg_bts_radio_link_timeout_cmd,
+ "radio-link-timeout <4-64>",
+ "Radio link timeout criterion (BTS side)\n"
+ "Radio link timeout value (lost SACCH block)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ gsm_bts_set_radio_link_timeout(bts, atoi(argv[0]));
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_radio_link_timeout_inf, cfg_bts_radio_link_timeout_inf_cmd,
+ "radio-link-timeout infinite",
+ "Radio link timeout criterion (BTS side)\n"
+ "Infinite Radio link timeout value (use only for BTS RF testing)\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (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"
+
+DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd,
+ "gprs cell bvci <2-65535>",
+ GPRS_TEXT
+ "GPRS Cell Settings\n"
+ "GPRS BSSGP VC Identifier\n"
+ "GPRS BSSGP VC Identifier")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.cell.bvci = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd,
+ "gprs nsei <0-65535>",
+ GPRS_TEXT
+ "GPRS NS Entity Identifier\n"
+ "GPRS NS Entity Identifier")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.nse.nsei = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \
+ "NSVC Logical Number\n"
+
+DEFUN(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd,
+ "gprs nsvc <0-1> nsvci <0-65535>",
+ GPRS_TEXT NSVC_TEXT
+ "NS Virtual Connection Identifier\n"
+ "GPRS NS VC Identifier")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = atoi(argv[0]);
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.nsvc[idx].nsvci = atoi(argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd,
+ "gprs nsvc <0-1> local udp port <0-65535>",
+ GPRS_TEXT NSVC_TEXT
+ "GPRS NS Local UDP Port\n"
+ "GPRS NS Local UDP Port\n"
+ "GPRS NS Local UDP Port\n"
+ "GPRS NS Local UDP Port Number\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = atoi(argv[0]);
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.nsvc[idx].local_port = atoi(argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd,
+ "gprs nsvc <0-1> remote udp port <0-65535>",
+ GPRS_TEXT NSVC_TEXT
+ "GPRS NS Remote UDP Port\n"
+ "GPRS NS Remote UDP Port\n"
+ "GPRS NS Remote UDP Port\n"
+ "GPRS NS Remote UDP Port Number\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = atoi(argv[0]);
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.nsvc[idx].remote_port = atoi(argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd,
+ "gprs nsvc <0-1> remote ip A.B.C.D",
+ GPRS_TEXT NSVC_TEXT
+ "GPRS NS Remote IP Address\n"
+ "GPRS NS Remote IP Address\n"
+ "GPRS NS Remote IP Address\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = atoi(argv[0]);
+ struct in_addr ia;
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ inet_aton(argv[1], &ia);
+ bts->gprs.nsvc[idx].remote_ip = ntohl(ia.s_addr);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_pag_free, cfg_bts_pag_free_cmd,
+ "paging free <-1-1024>",
+ "Paging options\n"
+ "Only page when having a certain amount of free slots\n"
+ "amount of required free paging slots. -1 to disable\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->paging.free_chans_need = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_ns_timer, cfg_bts_gprs_ns_timer_cmd,
+ "gprs ns timer " NS_TIMERS " <0-255>",
+ GPRS_TEXT "Network Service\n"
+ "Network Service Timer\n"
+ NS_TIMERS_HELP "Timer Value\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+ int val = atoi(argv[1]);
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ 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(cfg_bts_gprs_cell_timer, cfg_bts_gprs_cell_timer_cmd,
+ "gprs cell timer " BSSGP_TIMERS " <0-255>",
+ GPRS_TEXT "Cell / BSSGP\n"
+ "Cell/BSSGP Timer\n"
+ BSSGP_TIMERS_HELP "Timer Value\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]);
+ int val = atoi(argv[1]);
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer))
+ return CMD_WARNING;
+
+ bts->gprs.cell.timer[idx] = val;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd,
+ "gprs routing area <0-255>",
+ GPRS_TEXT
+ "GPRS Routing Area Code\n"
+ "GPRS Routing Area Code\n"
+ "GPRS Routing Area Code\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.rac = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_ctrl_ack, cfg_bts_gprs_ctrl_ack_cmd,
+ "gprs control-ack-type-rach", GPRS_TEXT
+ "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to "
+ "four access bursts format instead of default RLC/MAC control block\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.ctrl_ack_type_use_block = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_bts_gprs_ctrl_ack, cfg_no_bts_gprs_ctrl_ack_cmd,
+ "no gprs control-ack-type-rach", NO_STR GPRS_TEXT
+ "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to "
+ "four access bursts format instead of default RLC/MAC control block\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.ctrl_ack_type_use_block = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_net_ctrl_ord, cfg_bts_gprs_net_ctrl_ord_cmd,
+ "gprs network-control-order (nc0|nc1|nc2)",
+ GPRS_TEXT
+ "GPRS Network Control Order\n"
+ "MS controlled cell re-selection, no measurement reporting\n"
+ "MS controlled cell re-selection, MS sends measurement reports\n"
+ "Network controlled cell re-selection, MS sends measurement reports\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->gprs.mode == BTS_GPRS_NONE) {
+ vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->gprs.net_ctrl_ord = atoi(argv[0] + 2);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd,
+ "gprs mode (none|gprs|egprs)",
+ GPRS_TEXT
+ "GPRS Mode for this BTS\n"
+ "GPRS Disabled on this BTS\n"
+ "GPRS Enabled on this BTS\n"
+ "EGPRS (EDGE) Enabled on this BTS\n")
+{
+ struct gsm_bts *bts = vty->index;
+ enum bts_gprs_mode mode = bts_gprs_mode_parse(argv[0], NULL);
+
+ if (!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(cfg_bts_gprs_11bit_rach_support_for_egprs,
+ cfg_bts_gprs_11bit_rach_support_for_egprs_cmd,
+ "gprs 11bit_rach_support_for_egprs (0|1)",
+ GPRS_TEXT "11 bit RACH options\n"
+ "Disable 11 bit RACH for EGPRS\n"
+ "Enable 11 bit RACH for EGPRS")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->gprs.supports_egprs_11bit_rach = atoi(argv[0]);
+
+ if (bts->gprs.supports_egprs_11bit_rach > 1) {
+ vty_out(vty, "Error in RACH type%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if ((bts->gprs.mode == BTS_GPRS_NONE) &&
+ (bts->gprs.supports_egprs_11bit_rach == 1)) {
+ vty_out(vty, "Error:gprs mode is none and 11bit rach is"
+ " enabled%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+#define SI_TEXT "System Information Messages\n"
+#define SI_TYPE_TEXT "(1|2|3|4|5|6|7|8|9|10|13|16|17|18|19|20|2bis|2ter|2quater|5bis|5ter)"
+#define SI_TYPE_HELP "System Information Type 1\n" \
+ "System Information Type 2\n" \
+ "System Information Type 3\n" \
+ "System Information Type 4\n" \
+ "System Information Type 5\n" \
+ "System Information Type 6\n" \
+ "System Information Type 7\n" \
+ "System Information Type 8\n" \
+ "System Information Type 9\n" \
+ "System Information Type 10\n" \
+ "System Information Type 13\n" \
+ "System Information Type 16\n" \
+ "System Information Type 17\n" \
+ "System Information Type 18\n" \
+ "System Information Type 19\n" \
+ "System Information Type 20\n" \
+ "System Information Type 2bis\n" \
+ "System Information Type 2ter\n" \
+ "System Information Type 2quater\n" \
+ "System Information Type 5bis\n" \
+ "System Information Type 5ter\n"
+
+DEFUN(cfg_bts_si_mode, cfg_bts_si_mode_cmd,
+ "system-information " SI_TYPE_TEXT " mode (static|computed)",
+ SI_TEXT SI_TYPE_HELP
+ "System Information Mode\n"
+ "Static user-specified\n"
+ "Dynamic, BSC-computed\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int type;
+
+ type = get_string_value(osmo_sitype_strs, argv[0]);
+ if (type < 0) {
+ vty_out(vty, "Error SI Type%s", VTY_NEWLINE);
+ 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(cfg_bts_si_static, cfg_bts_si_static_cmd,
+ "system-information " SI_TYPE_TEXT " static HEXSTRING",
+ SI_TEXT SI_TYPE_HELP
+ "Static System Information filling\n"
+ "Static user-specified SI content in HEX notation\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int rc, type;
+
+ type = get_string_value(osmo_sitype_strs, argv[0]);
+ if (type < 0) {
+ vty_out(vty, "Error SI Type%s", VTY_NEWLINE);
+ 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(cfg_bts_early_cm, cfg_bts_early_cm_cmd,
+ "early-classmark-sending (allowed|forbidden)",
+ "Early Classmark Sending\n"
+ "Early Classmark Sending is allowed\n"
+ "Early Classmark Sending is forbidden\n")
+{
+ 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(cfg_bts_neigh_mode, cfg_bts_neigh_mode_cmd,
+ "neighbor-list mode (automatic|manual|manual-si5)",
+ "Neighbor List\n" "Mode of Neighbor List generation\n"
+ "Automatically from all BTS in this OpenBSC\n" "Manual\n"
+ "Manual with different lists for SI2 and SI5\n")
+{
+ 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(cfg_bts_neigh, cfg_bts_neigh_cmd,
+ "neighbor-list (add|del) arfcn <0-1023>",
+ "Neighbor List\n" "Add to manual neighbor list\n"
+ "Delete from manual neighbor list\n" "ARFCN of neighbor\n"
+ "ARFCN of neighbor\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct bitvec *bv = &bts->si_common.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 (!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(cfg_bts_si2quater_neigh_add, cfg_bts_si2quater_neigh_add_cmd,
+ "si2quater neighbor-list add earfcn <0-65535> thresh-hi <0-31> "
+ "thresh-lo <0-32> prio <0-8> qrxlv <0-32> meas <0-8>",
+ "SI2quater Neighbor List\n" "SI2quater Neighbor List\n"
+ "Add to manual SI2quater neighbor list\n"
+ "EARFCN of neighbor\n" "EARFCN of neighbor\n"
+ "threshold high bits\n" "threshold high bits\n"
+ "threshold low bits\n" "threshold low bits (32 means NA)\n"
+ "priority\n" "priority (8 means NA)\n"
+ "QRXLEVMIN\n" "QRXLEVMIN (32 means NA)\n"
+ "measurement bandwidth\n" "measurement bandwidth (8 means NA)\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+ uint16_t arfcn = atoi(argv[0]);
+ uint8_t thresh_hi = atoi(argv[1]), thresh_lo = atoi(argv[2]),
+ prio = atoi(argv[3]), qrx = atoi(argv[4]), meas = atoi(argv[5]);
+ int r = bts_earfcn_add(bts, arfcn, thresh_hi, thresh_lo, prio, qrx, meas);
+
+ switch (r) {
+ case 1:
+ vty_out(vty, "Warning: multiple threshold-high are not supported, overriding with %u%s",
+ thresh_hi, VTY_NEWLINE);
+ break;
+ case EARFCN_THRESH_LOW_INVALID:
+ vty_out(vty, "Warning: multiple threshold-low are not supported, overriding with %u%s",
+ thresh_lo, VTY_NEWLINE);
+ break;
+ case EARFCN_QRXLV_INVALID + 1:
+ vty_out(vty, "Warning: multiple QRXLEVMIN are not supported, overriding with %u%s",
+ qrx, VTY_NEWLINE);
+ break;
+ case EARFCN_PRIO_INVALID:
+ vty_out(vty, "Warning: multiple priorities are not supported, overriding with %u%s",
+ prio, VTY_NEWLINE);
+ break;
+ default:
+ if (r < 0) {
+ vty_out(vty, "Unable to add ARFCN %u: %s%s", arfcn, strerror(-r), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ if (si2q_num(bts) <= SI2Q_MAX_NUM)
+ return CMD_SUCCESS;
+
+ vty_out(vty, "Warning: not enough space in SI2quater (%u/%u used) for a given EARFCN %u%s",
+ bts->si2q_count, SI2Q_MAX_NUM, arfcn, VTY_NEWLINE);
+ osmo_earfcn_del(e, arfcn);
+
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_bts_si2quater_neigh_del, cfg_bts_si2quater_neigh_del_cmd,
+ "si2quater neighbor-list del earfcn <0-65535>",
+ "SI2quater Neighbor List\n"
+ "SI2quater Neighbor List\n"
+ "Delete from SI2quater manual neighbor list\n"
+ "EARFCN of neighbor\n"
+ "EARFCN\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list;
+ uint16_t arfcn = atoi(argv[0]);
+ int r = osmo_earfcn_del(e, arfcn);
+ if (r < 0) {
+ vty_out(vty, "Unable to delete arfcn %u: %s%s", arfcn,
+ strerror(-r), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_si2quater_uarfcn_add, cfg_bts_si2quater_uarfcn_add_cmd,
+ "si2quater neighbor-list add uarfcn <0-16383> <0-511> <0-1>",
+ "SI2quater Neighbor List\n"
+ "SI2quater Neighbor List\n" "Add to manual SI2quater neighbor list\n"
+ "UARFCN of neighbor\n" "UARFCN of neighbor\n" "scrambling code\n"
+ "diversity bit\n")
+{
+ 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(cfg_bts_si2quater_uarfcn_del, cfg_bts_si2quater_uarfcn_del_cmd,
+ "si2quater neighbor-list del uarfcn <0-16383> <0-511>",
+ "SI2quater Neighbor List\n"
+ "SI2quater Neighbor List\n"
+ "Delete from SI2quater manual neighbor list\n"
+ "UARFCN of neighbor\n"
+ "UARFCN\n"
+ "scrambling code\n")
+{
+ 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(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd,
+ "si5 neighbor-list (add|del) arfcn <0-1023>",
+ "SI5 Neighbor List\n"
+ "SI5 Neighbor List\n" "Add to manual SI5 neighbor list\n"
+ "Delete from SI5 manual neighbor list\n" "ARFCN of neighbor\n"
+ "ARFCN of neighbor\n")
+{
+ 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 (!strcmp(argv[0], "add"))
+ bitvec_set_bit_pos(bv, arfcn, 1);
+ else
+ bitvec_set_bit_pos(bv, arfcn, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd,
+ "pcu-socket PATH",
+ "PCU Socket Path for using OsmoPCU co-located with BSC (legacy BTS)\n"
+ "Path in the file system for the unix-domain PCU socket\n")
+{
+ 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;
+}
+
+#define EXCL_RFLOCK_STR "Exclude this BTS from the global RF Lock\n"
+
+DEFUN(cfg_bts_excl_rf_lock,
+ cfg_bts_excl_rf_lock_cmd,
+ "rf-lock-exclude",
+ EXCL_RFLOCK_STR)
+{
+ struct gsm_bts *bts = vty->index;
+ bts->excl_from_rf_lock = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_excl_rf_lock,
+ cfg_bts_no_excl_rf_lock_cmd,
+ "no rf-lock-exclude",
+ NO_STR EXCL_RFLOCK_STR)
+{
+ 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(cfg_bts_force_comb_si,
+ cfg_bts_force_comb_si_cmd,
+ "force-combined-si",
+ FORCE_COMB_SI_STR)
+{
+ struct gsm_bts *bts = vty->index;
+ bts->force_combined_si = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_force_comb_si,
+ cfg_bts_no_force_comb_si_cmd,
+ "no force-combined-si",
+ NO_STR FORCE_COMB_SI_STR)
+{
+ struct gsm_bts *bts = vty->index;
+ bts->force_combined_si = 0;
+ 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(cfg_bts_codec0, cfg_bts_codec0_cmd,
+ "codec-support fr",
+ "Codec Support settings\nFullrate\n")
+{
+ _get_codec_from_arg(vty, 0, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_codec1, cfg_bts_codec1_cmd,
+ "codec-support fr" CODEC_PAR_STR,
+ "Codec Support settings\nFullrate\n"
+ CODEC_HELP_STR)
+{
+ _get_codec_from_arg(vty, 1, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_codec2, cfg_bts_codec2_cmd,
+ "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR,
+ "Codec Support settings\nFullrate\n"
+ CODEC_HELP_STR CODEC_HELP_STR)
+{
+ _get_codec_from_arg(vty, 2, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_codec3, cfg_bts_codec3_cmd,
+ "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR,
+ "Codec Support settings\nFullrate\n"
+ CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR)
+{
+ _get_codec_from_arg(vty, 3, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_codec4, cfg_bts_codec4_cmd,
+ "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR,
+ "Codec Support settings\nFullrate\n"
+ CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR)
+{
+ _get_codec_from_arg(vty, 4, argv);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_depends_on, cfg_bts_depends_on_cmd,
+ "depends-on-bts <0-255>",
+ "This BTS can only be started if another one is up\n" "BTS Number\n")
+{
+ 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(cfg_bts_no_depends_on, cfg_bts_no_depends_on_cmd,
+ "depeneds-on-bts <0-255>",
+ NO_STR "This BTS can only be started if another one is up\n"
+ "BTS Number\n")
+{
+ 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 void 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;
+
+ mr->gsm48_ie[1] = 0;
+ for (i = 0; i < argc; i++)
+ mr->gsm48_ie[1] |= 1 << atoi(argv[i]);
+ mr_conf->icmi = 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;
+ }
+}
+
+#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(cfg_bts_amr_fr_modes1, cfg_bts_amr_fr_modes1_cmd,
+ "amr tch-f modes" AMR_TCHF_PAR_STR,
+ AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+ AMR_TCHF_HELP_STR)
+{
+ get_amr_from_arg(vty, 1, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_modes2, cfg_bts_amr_fr_modes2_cmd,
+ "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
+ AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+ AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+{
+ get_amr_from_arg(vty, 2, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_modes3, cfg_bts_amr_fr_modes3_cmd,
+ "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
+ AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+ AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+{
+ get_amr_from_arg(vty, 3, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_modes4, cfg_bts_amr_fr_modes4_cmd,
+ "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR,
+ AMR_TEXT "Full Rate\n" AMR_MODE_TEXT
+ AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR)
+{
+ get_amr_from_arg(vty, 4, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_start_mode, cfg_bts_amr_fr_start_mode_cmd,
+ "amr tch-f start-mode (auto|1|2|3|4)",
+ AMR_TEXT "Full Rate\n" AMR_START_TEXT)
+{
+ get_amr_start_from_arg(vty, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_thres1, cfg_bts_amr_fr_thres1_cmd,
+ "amr tch-f threshold (ms|bts) <0-63>",
+ AMR_TEXT "Full Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR)
+{
+ get_amr_th_from_arg(vty, 2, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_thres2, cfg_bts_amr_fr_thres2_cmd,
+ "amr tch-f threshold (ms|bts) <0-63> <0-63>",
+ AMR_TEXT "Full Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR AMR_TH_HELP_STR)
+{
+ get_amr_th_from_arg(vty, 3, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_thres3, cfg_bts_amr_fr_thres3_cmd,
+ "amr tch-f threshold (ms|bts) <0-63> <0-63> <0-63>",
+ AMR_TEXT "Full Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR)
+{
+ get_amr_th_from_arg(vty, 4, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_hyst1, cfg_bts_amr_fr_hyst1_cmd,
+ "amr tch-f hysteresis (ms|bts) <0-15>",
+ AMR_TEXT "Full Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR)
+{
+ get_amr_hy_from_arg(vty, 2, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_hyst2, cfg_bts_amr_fr_hyst2_cmd,
+ "amr tch-f hysteresis (ms|bts) <0-15> <0-15>",
+ AMR_TEXT "Full Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR AMR_HY_HELP_STR)
+{
+ get_amr_hy_from_arg(vty, 3, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_fr_hyst3, cfg_bts_amr_fr_hyst3_cmd,
+ "amr tch-f hysteresis (ms|bts) <0-15> <0-15> <0-15>",
+ AMR_TEXT "Full Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR)
+{
+ get_amr_hy_from_arg(vty, 4, argv, 1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_modes1, cfg_bts_amr_hr_modes1_cmd,
+ "amr tch-h modes" AMR_TCHH_PAR_STR,
+ AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+ AMR_TCHH_HELP_STR)
+{
+ get_amr_from_arg(vty, 1, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_modes2, cfg_bts_amr_hr_modes2_cmd,
+ "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
+ AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+ AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+{
+ get_amr_from_arg(vty, 2, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_modes3, cfg_bts_amr_hr_modes3_cmd,
+ "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
+ AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+ AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+{
+ get_amr_from_arg(vty, 3, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_modes4, cfg_bts_amr_hr_modes4_cmd,
+ "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR,
+ AMR_TEXT "Half Rate\n" AMR_MODE_TEXT
+ AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR)
+{
+ get_amr_from_arg(vty, 4, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_start_mode, cfg_bts_amr_hr_start_mode_cmd,
+ "amr tch-h start-mode (auto|1|2|3|4)",
+ AMR_TEXT "Half Rate\n" AMR_START_TEXT)
+{
+ get_amr_start_from_arg(vty, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_thres1, cfg_bts_amr_hr_thres1_cmd,
+ "amr tch-h threshold (ms|bts) <0-63>",
+ AMR_TEXT "Half Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR)
+{
+ get_amr_th_from_arg(vty, 2, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_thres2, cfg_bts_amr_hr_thres2_cmd,
+ "amr tch-h threshold (ms|bts) <0-63> <0-63>",
+ AMR_TEXT "Half Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR AMR_TH_HELP_STR)
+{
+ get_amr_th_from_arg(vty, 3, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_thres3, cfg_bts_amr_hr_thres3_cmd,
+ "amr tch-h threshold (ms|bts) <0-63> <0-63> <0-63>",
+ AMR_TEXT "Half Rate\n" AMR_TH_TEXT
+ AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR)
+{
+ get_amr_th_from_arg(vty, 4, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_hyst1, cfg_bts_amr_hr_hyst1_cmd,
+ "amr tch-h hysteresis (ms|bts) <0-15>",
+ AMR_TEXT "Half Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR)
+{
+ get_amr_hy_from_arg(vty, 2, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_hyst2, cfg_bts_amr_hr_hyst2_cmd,
+ "amr tch-h hysteresis (ms|bts) <0-15> <0-15>",
+ AMR_TEXT "Half Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR AMR_HY_HELP_STR)
+{
+ get_amr_hy_from_arg(vty, 3, argv, 0);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_amr_hr_hyst3, cfg_bts_amr_hr_hyst3_cmd,
+ "amr tch-h hysteresis (ms|bts) <0-15> <0-15> <0-15>",
+ AMR_TEXT "Half Rate\n" AMR_HY_TEXT
+ AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR)
+{
+ get_amr_hy_from_arg(vty, 4, argv, 0);
+ return CMD_SUCCESS;
+}
+
+#define TRX_TEXT "Radio Transceiver\n"
+
+/* per TRX configuration */
+DEFUN(cfg_trx,
+ cfg_trx_cmd,
+ "trx <0-255>",
+ TRX_TEXT
+ "Select a TRX to configure")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_trx *trx;
+
+ 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(cfg_trx_arfcn,
+ cfg_trx_arfcn_cmd,
+ "arfcn <0-1023>",
+ "Set the ARFCN for this TRX\n"
+ "Absolute Radio Frequency Channel Number\n")
+{
+ int arfcn = atoi(argv[0]);
+ struct gsm_bts_trx *trx = vty->index;
+
+ /* 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(cfg_trx_nominal_power,
+ cfg_trx_nominal_power_cmd,
+ "nominal power <0-100>",
+ "Nominal TRX RF Power in dBm\n"
+ "Nominal TRX RF Power in dBm\n"
+ "Nominal TRX RF Power in dBm\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->nominal_power = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_max_power_red,
+ cfg_trx_max_power_red_cmd,
+ "max_power_red <0-100>",
+ "Reduction of maximum BS RF Power (relative to nominal power)\n"
+ "Reduction of maximum BS RF Power in dB\n")
+{
+ 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;
+}
+
+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(cfg_trx_rsl_e1_tei,
+ cfg_trx_rsl_e1_tei_cmd,
+ "rsl e1 tei <0-63>",
+ "RSL Parameters\n"
+ "Set the TEI to be used for RSL\n"
+ "Set the TEI to be used for RSL\n"
+ "TEI to be used for RSL\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->rsl_tei = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_rf_locked,
+ cfg_trx_rf_locked_cmd,
+ "rf_locked (0|1)",
+ "Set or unset the RF Locking (Turn off RF of the TRX)\n"
+ "TRX is NOT RF locked (active)\n"
+ "TRX is RF locked (turned off)\n")
+{
+ int locked = atoi(argv[0]);
+ struct gsm_bts_trx *trx = vty->index;
+
+ gsm_trx_lock_rf(trx, locked);
+ return CMD_SUCCESS;
+}
+
+/* per TS configuration */
+DEFUN(cfg_ts,
+ cfg_ts_cmd,
+ "timeslot <0-7>",
+ "Select a Timeslot to configure\n"
+ "Timeslot number\n")
+{
+ int ts_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = vty->index;
+ struct gsm_bts_trx_ts *ts;
+
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s",
+ TRX_NR_TS, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ts = &trx->ts[ts_nr];
+
+ vty->index = ts;
+ vty->node = TS_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_pchan,
+ cfg_ts_pchan_cmd,
+ "phys_chan_config PCHAN", /* dynamically generated! */
+ "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int pchanc;
+
+ pchanc = gsm_pchan_parse(argv[0]);
+ if (pchanc < 0)
+ return CMD_WARNING;
+
+ ts->pchan = 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)
+ return CMD_WARNING;
+
+ ts->pchan = pchanc;
+
+ return CMD_SUCCESS;
+}
+
+
+
+DEFUN(cfg_ts_tsc,
+ cfg_ts_tsc_cmd,
+ "training_sequence_code <0-7>",
+ "Training Sequence Code of the Timeslot\n" "TSC\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+
+ if (!gsm_btsmodel_has_feature(ts->trx->bts->model, 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(cfg_ts_hopping,
+ cfg_ts_hopping_cmd,
+ "hopping enabled (0|1)",
+ HOPPING_STR "Enable or disable frequency hopping\n"
+ "Disable frequency hopping\n" "Enable frequency hopping\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int enabled = atoi(argv[0]);
+
+ if (enabled && !gsm_btsmodel_has_feature(ts->trx->bts->model, BTS_FEAT_HOPPING)) {
+ vty_out(vty, "BTS model does not support hopping%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ts->hopping.enabled = enabled;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_hsn,
+ cfg_ts_hsn_cmd,
+ "hopping sequence-number <0-63>",
+ HOPPING_STR
+ "Which hopping sequence to use for this channel\n"
+ "Hopping Sequence Number (HSN)\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+
+ ts->hopping.hsn = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_maio,
+ cfg_ts_maio_cmd,
+ "hopping maio <0-63>",
+ HOPPING_STR
+ "Which hopping MAIO to use for this channel\n"
+ "Mobile Allocation Index Offset (MAIO)\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+
+ ts->hopping.maio = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_arfcn_add,
+ cfg_ts_arfcn_add_cmd,
+ "hopping arfcn add <0-1023>",
+ HOPPING_STR "Configure hopping ARFCN list\n"
+ "Add an entry to the hopping ARFCN list\n" "ARFCN\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int arfcn = atoi(argv[0]);
+
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_arfcn_del,
+ cfg_ts_arfcn_del_cmd,
+ "hopping arfcn del <0-1023>",
+ HOPPING_STR "Configure hopping ARFCN list\n"
+ "Delete an entry to the hopping ARFCN list\n" "ARFCN\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+ int arfcn = atoi(argv[0]);
+
+ bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ts_e1_subslot,
+ cfg_ts_e1_subslot_cmd,
+ "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)",
+ "E1/T1 channel connected to this on-air timeslot\n"
+ "E1/T1 channel connected to this on-air timeslot\n"
+ "E1/T1 line connected to this on-air timeslot\n"
+ "E1/T1 timeslot connected to this on-air timeslot\n"
+ "E1/T1 timeslot connected to this on-air timeslot\n"
+ "E1/T1 sub-slot connected to this on-air timeslot\n"
+ "E1/T1 sub-slot 0 connected to this on-air timeslot\n"
+ "E1/T1 sub-slot 1 connected to this on-air timeslot\n"
+ "E1/T1 sub-slot 2 connected to this on-air timeslot\n"
+ "E1/T1 sub-slot 3 connected to this on-air timeslot\n"
+ "Full E1/T1 timeslot connected to this on-air timeslot\n")
+{
+ struct gsm_bts_trx_ts *ts = vty->index;
+
+ parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]);
+
+ return CMD_SUCCESS;
+}
+
+void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net)
+{
+ vty_out(vty, "Channel Requests : %lu total, %lu no channel%s",
+ net->bsc_ctrs->ctr[BSC_CTR_CHREQ_TOTAL].current,
+ net->bsc_ctrs->ctr[BSC_CTR_CHREQ_NO_CHANNEL].current,
+ VTY_NEWLINE);
+ vty_out(vty, "Channel Failures : %lu rf_failures, %lu rll failures%s",
+ net->bsc_ctrs->ctr[BSC_CTR_CHAN_RF_FAIL].current,
+ net->bsc_ctrs->ctr[BSC_CTR_CHAN_RLL_ERR].current,
+ VTY_NEWLINE);
+ vty_out(vty, "Paging : %lu attempted, %lu complete, %lu expired%s",
+ net->bsc_ctrs->ctr[BSC_CTR_PAGING_ATTEMPTED].current,
+ net->bsc_ctrs->ctr[BSC_CTR_PAGING_COMPLETED].current,
+ net->bsc_ctrs->ctr[BSC_CTR_PAGING_EXPIRED].current,
+ VTY_NEWLINE);
+ vty_out(vty, "BTS failures : %lu OML, %lu RSL%s",
+ net->bsc_ctrs->ctr[BSC_CTR_BTS_OML_FAIL].current,
+ net->bsc_ctrs->ctr[BSC_CTR_BTS_RSL_FAIL].current,
+ VTY_NEWLINE);
+}
+
+DEFUN(drop_bts,
+ drop_bts_cmd,
+ "drop bts connection <0-65535> (oml|rsl)",
+ "Debug/Simulation command to drop Abis/IP BTS\n"
+ "Debug/Simulation command to drop Abis/IP BTS\n"
+ "Debug/Simulation command to drop Abis/IP BTS\n"
+ "BTS NR\n" "Drop OML Connection\n" "Drop RSL Connection\n")
+{
+ struct gsm_network *gsmnet;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts *bts;
+ unsigned int bts_nr;
+
+ gsmnet = gsmnet_from_vty(vty);
+
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= gsmnet->num_bts) {
+ vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s",
+ gsmnet->num_bts, bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts = gsm_bts_num(gsmnet, bts_nr);
+ if (!bts) {
+ vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!is_ipaccess_bts(bts)) {
+ vty_out(vty, "This command only works for ipaccess.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+
+ /* close all connections */
+ if (strcmp(argv[1], "oml") == 0) {
+ ipaccess_drop_oml(bts);
+ } else if (strcmp(argv[1], "rsl") == 0) {
+ /* close all rsl connections */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ ipaccess_drop_rsl(trx);
+ }
+ } else {
+ vty_out(vty, "Argument must be 'oml# or 'rsl'.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(restart_bts, restart_bts_cmd,
+ "restart-bts <0-65535>",
+ "Restart ip.access nanoBTS through OML\n"
+ "BTS Number\n")
+{
+ struct gsm_network *gsmnet;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts *bts;
+ unsigned int bts_nr;
+
+ gsmnet = gsmnet_from_vty(vty);
+
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= gsmnet->num_bts) {
+ vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s",
+ gsmnet->num_bts, bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts = gsm_bts_num(gsmnet, bts_nr);
+ if (!bts) {
+ vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) {
+ vty_out(vty, "This command only works for ipaccess nanoBTS.%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* go from last TRX to c0 */
+ llist_for_each_entry_reverse(trx, &bts->trx_list, list)
+ abis_nm_ipaccess_restart(trx);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(smscb_cmd, smscb_cmd_cmd,
+ "bts <0-255> smscb-command <1-4> HEXSTRING",
+ "BTS related commands\n" "BTS Number\n"
+ "SMS Cell Broadcast\n" "Last Valid Block\n"
+ "Hex Encoded SMSCB message (up to 88 octets)\n")
+{
+ struct gsm_bts *bts;
+ int bts_nr = atoi(argv[0]);
+ int last_block = atoi(argv[1]);
+ struct rsl_ie_cb_cmd_type cb_cmd;
+ uint8_t buf[88];
+ int rc;
+
+ 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;
+ }
+ rc = osmo_hexparse(argv[2], buf, sizeof(buf));
+ if (rc < 0 || rc > sizeof(buf)) {
+ vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ cb_cmd.spare = 0;
+ cb_cmd.def_bcast = 0;
+ cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL;
+
+ switch (last_block) {
+ case 1:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1;
+ break;
+ case 2:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2;
+ break;
+ case 3:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3;
+ break;
+ case 4:
+ cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4;
+ break;
+ }
+
+ rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, buf, rc);
+
+ return CMD_SUCCESS;
+}
+
+/* resolve a gsm_bts_trx_ts basd on the given numeric identifiers */
+static struct gsm_bts_trx_ts *vty_get_ts(struct vty *vty, const char *bts_str, const char *trx_str,
+ const char *ts_str)
+{
+ 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 related commands\n" "BTS Number\n" "Transceiver\n" "Transceiver Number\n"
+ "TRX Timeslot\n" "Timeslot Number\n" "Packet Data Channel\n"
+ "Activate Dynamic PDCH/TCH (-> PDCH mode)\n"
+ "Deactivate Dynamic PDCH/TCH (-> TCH mode)\n")
+{
+ struct gsm_bts_trx_ts *ts;
+ int activate;
+
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts)
+ return CMD_WARNING;
+
+ if (!is_ipaccess_bts(ts->trx->bts)) {
+ vty_out(vty, "%% This command only works for ipaccess BTS%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) {
+ vty_out(vty, "%% Timeslot %u is not in dynamic TCH_F/PDCH "
+ "mode%s", ts->nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[3], "activate"))
+ activate = 1;
+ else
+ activate = 0;
+
+ rsl_ipacc_pdch_activate(ts, activate);
+
+ return CMD_SUCCESS;
+
+}
+
+/* determine the logical channel type based on the physical channel type */
+static int lchan_type_by_pchan(enum gsm_phys_chan_config pchan)
+{
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ return GSM_LCHAN_TCH_F;
+ case GSM_PCHAN_TCH_H:
+ return GSM_LCHAN_TCH_H;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ return GSM_LCHAN_SDCCH;
+ default:
+ return -1;
+ }
+}
+
+/* configure the lchan for a single AMR mode (as specified) */
+static int lchan_set_single_amr_mode(struct gsm_lchan *lchan, uint8_t amr_mode)
+{
+ struct amr_multirate_conf mr;
+ struct gsm48_multi_rate_conf *mr_conf;
+ mr_conf = (struct gsm48_multi_rate_conf *) &mr.gsm48_ie;
+
+ if (amr_mode > 7)
+ return -1;
+
+ memset(&mr, 0, sizeof(mr));
+ mr_conf->ver = 1;
+ /* bit-mask of supported modes, only one bit is set. Reflects
+ * Figure 10.5.2.47a where there are no thershold and only a
+ * single mode */
+ mr.gsm48_ie[1] = 1 << amr_mode;
+
+ mr.ms_mode[0].mode = amr_mode;
+ mr.bts_mode[0].mode = amr_mode;
+
+ /* encode this configuration into the lchan for both uplink and
+ * downlink direction */
+ gsm48_multirate_config(lchan->mr_ms_lv, &mr, mr.ms_mode);
+ gsm48_multirate_config(lchan->mr_bts_lv, &mr, mr.bts_mode);
+
+ return 0;
+}
+
+/* Debug/Measurement command to activate a given logical channel
+ * manually in a given mode/codec. This is useful for receiver
+ * performance testing (FER/RBER/...) */
+DEFUN(lchan_act, lchan_act_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> (activate|deactivate) (hr|fr|efr|amr) [<0-7>]",
+ "BTS related commands\n" "BTS Number\n" "Transceiver\n" "Transceiver Number\n"
+ "TRX Timeslot\n" "Timeslot Number\n" "Sub-Slot Number\n" "Sub-Slot Number\n"
+ "Manual Channel Activation (e.g. for BER test)\n"
+ "Manual Channel Deactivation (e.g. for BER test)\n"
+ "Half-Rate v1\n" "Full-Rate\n" "Enhanced Full Rate\n" "Adaptive Multi-Rate\n" "AMR Mode\n")
+{
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int ss_nr = atoi(argv[3]);
+ const char *act_str = argv[4];
+ const char *codec_str = argv[5];
+ int activate;
+
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts)
+ return CMD_WARNING;
+
+ lchan = &ts->lchan[ss_nr];
+
+ if (!strcmp(act_str, "activate"))
+ activate = 1;
+ else
+ activate = 0;
+
+ if (ss_nr >= ts_subslots(ts)) {
+ vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s",
+ ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (activate) {
+ int lchan_t;
+ if (lchan->state != LCHAN_S_NONE) {
+ vty_out(vty, "%% Cannot activate: Channel busy!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan_t = lchan_type_by_pchan(ts->pchan);
+ if (lchan_t < 0)
+ return CMD_WARNING;
+ /* configure the lchan */
+ lchan->type = lchan_t;
+ lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH;
+ if (!strcmp(codec_str, "hr") || !strcmp(codec_str, "fr"))
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ else if (!strcmp(codec_str, "efr"))
+ lchan->tch_mode = GSM48_CMODE_SPEECH_EFR;
+ else if (!strcmp(codec_str, "amr")) {
+ int amr_mode;
+ if (argc < 7) {
+ vty_out(vty, "%% AMR requires specification of AMR mode%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ amr_mode = atoi(argv[6]);
+ lchan->tch_mode = GSM48_CMODE_SPEECH_AMR;
+ lchan_set_single_amr_mode(lchan, amr_mode);
+ }
+ vty_out(vty, "%% activating lchan %s%s", gsm_lchan_name(lchan), VTY_NEWLINE);
+ rsl_chan_activate_lchan(lchan, RSL_ACT_TYPE_INITIAL, 0);
+ rsl_ipacc_crcx(lchan);
+ } else {
+ rsl_direct_rf_release(lchan);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(lchan_mdcx, lchan_mdcx_cmd,
+ "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> mdcx A.B.C.D <0-65535>",
+ "BTS related commands\n" "BTS Number\n" "Transceiver\n" "Transceiver Number\n"
+ "TRX Timeslot\n" "Timeslot Number\n" "Sub-Slot\n" "Sub-Slot Number\n"
+ "Modify RTP Connection\n" "MGW IP Address\n" "MGW UDP Port\n")
+{
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int ss_nr = atoi(argv[3]);
+ int port = atoi(argv[5]);
+ struct in_addr ia;
+ inet_aton(argv[4], &ia);
+
+ ts = vty_get_ts(vty, argv[0], argv[1], argv[2]);
+ if (!ts)
+ return CMD_WARNING;
+
+ lchan = &ts->lchan[ss_nr];
+
+ if (ss_nr >= ts_subslots(ts)) {
+ vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s",
+ ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "%% connecting RTP of %s to %s:%u%s", gsm_lchan_name(lchan),
+ inet_ntoa(ia), port, VTY_NEWLINE);
+ rsl_ipacc_mdcx(lchan, ntohl(ia.s_addr), port, 0);
+ return CMD_SUCCESS;
+}
+extern int bsc_vty_init_extra(void);
+
+int bsc_vty_init(struct gsm_network *network)
+{
+ cfg_ts_pchan_cmd.string =
+ vty_cmd_string_from_valstr(tall_bsc_ctx,
+ gsm_pchant_names,
+ "phys_chan_config (", "|", ")",
+ VTY_DO_LOWER);
+ cfg_ts_pchan_cmd.doc =
+ vty_cmd_string_from_valstr(tall_bsc_ctx,
+ gsm_pchant_descs,
+ "Physical Channel Combination\n",
+ "\n", "", 0);
+
+ cfg_bts_type_cmd.string =
+ vty_cmd_string_from_valstr(tall_bsc_ctx,
+ bts_type_names,
+ "type (", "|", ")",
+ VTY_DO_LOWER);
+ cfg_bts_type_cmd.doc =
+ vty_cmd_string_from_valstr(tall_bsc_ctx,
+ bts_type_descs,
+ "BTS Vendor/Type\n",
+ "\n", "", 0);
+
+ common_cs_vty_init(network, config_write_net);
+
+ install_element_ve(&bsc_show_net_cmd);
+ install_element_ve(&show_bts_cmd);
+ install_element_ve(&show_trx_cmd);
+ install_element_ve(&show_ts_cmd);
+ install_element_ve(&show_lchan_cmd);
+ install_element_ve(&show_lchan_summary_cmd);
+
+ install_element_ve(&show_paging_cmd);
+ install_element_ve(&show_paging_group_cmd);
+
+ logging_vty_add_cmds(NULL);
+ osmo_stats_vty_add_cmds();
+
+ install_element(GSMNET_NODE, &cfg_net_neci_cmd);
+ install_element(GSMNET_NODE, &cfg_net_handover_cmd);
+ install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_cmd);
+ install_element(GSMNET_NODE, &cfg_net_ho_win_rxqual_avg_cmd);
+ install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_neigh_cmd);
+ install_element(GSMNET_NODE, &cfg_net_ho_pwr_interval_cmd);
+ install_element(GSMNET_NODE, &cfg_net_ho_pwr_hysteresis_cmd);
+ install_element(GSMNET_NODE, &cfg_net_ho_max_distance_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3101_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3103_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3105_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3107_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3109_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3111_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3113_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3115_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3117_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3119_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3122_cmd);
+ install_element(GSMNET_NODE, &cfg_net_T3141_cmd);
+ install_element(GSMNET_NODE, &cfg_net_dtx_cmd);
+ install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd);
+
+ install_element(GSMNET_NODE, &cfg_bts_cmd);
+ install_node(&bts_node, config_write_bts);
+ vty_install_default(BTS_NODE);
+ install_element(BTS_NODE, &cfg_bts_type_cmd);
+ install_element(BTS_NODE, &cfg_description_cmd);
+ install_element(BTS_NODE, &cfg_no_description_cmd);
+ install_element(BTS_NODE, &cfg_bts_band_cmd);
+ install_element(BTS_NODE, &cfg_bts_ci_cmd);
+ install_element(BTS_NODE, &cfg_bts_dtxu_cmd);
+ install_element(BTS_NODE, &cfg_bts_dtxd_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_dtxu_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_dtxd_cmd);
+ install_element(BTS_NODE, &cfg_bts_lac_cmd);
+ install_element(BTS_NODE, &cfg_bts_tsc_cmd);
+ install_element(BTS_NODE, &cfg_bts_bsic_cmd);
+ install_element(BTS_NODE, &cfg_bts_unit_id_cmd);
+ install_element(BTS_NODE, &cfg_bts_rsl_ip_cmd);
+ install_element(BTS_NODE, &cfg_bts_nokia_site_skip_reset_cmd);
+ install_element(BTS_NODE, &cfg_bts_nokia_site_no_loc_rel_cnf_cmd);
+ install_element(BTS_NODE, &cfg_bts_nokia_site_bts_reset_timer_cnf_cmd);
+ install_element(BTS_NODE, &cfg_bts_stream_id_cmd);
+ install_element(BTS_NODE, &cfg_bts_oml_e1_cmd);
+ install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd);
+ install_element(BTS_NODE, &cfg_bts_challoc_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_desc_att_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_desc_bs_pa_mfrms_cmd);
+ install_element(BTS_NODE, &cfg_bts_chan_desc_bs_ag_blks_res_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd);
+ install_element(BTS_NODE, &cfg_bts_cell_barred_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd);
+ install_element(BTS_NODE, &cfg_bts_rach_ac_class_cmd);
+ install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd);
+ install_element(BTS_NODE, &cfg_bts_per_loc_upd_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_per_loc_upd_cmd);
+ install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd);
+ install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd);
+ install_element(BTS_NODE, &cfg_bts_cell_bar_qualify_cmd);
+ install_element(BTS_NODE, &cfg_bts_cell_resel_ofs_cmd);
+ install_element(BTS_NODE, &cfg_bts_temp_ofs_cmd);
+ install_element(BTS_NODE, &cfg_bts_temp_ofs_inf_cmd);
+ install_element(BTS_NODE, &cfg_bts_penalty_time_cmd);
+ install_element(BTS_NODE, &cfg_bts_penalty_time_rsvd_cmd);
+ install_element(BTS_NODE, &cfg_bts_radio_link_timeout_cmd);
+ install_element(BTS_NODE, &cfg_bts_radio_link_timeout_inf_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_11bit_rach_support_for_egprs_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_net_ctrl_ord_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_ctrl_ack_cmd);
+ install_element(BTS_NODE, &cfg_no_bts_gprs_ctrl_ack_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd);
+ install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd);
+ install_element(BTS_NODE, &cfg_bts_pag_free_cmd);
+ install_element(BTS_NODE, &cfg_bts_si_mode_cmd);
+ install_element(BTS_NODE, &cfg_bts_si_static_cmd);
+ install_element(BTS_NODE, &cfg_bts_early_cm_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_trx_cmd);
+ install_node(&trx_node, dummy_config_write);
+ vty_install_default(TRX_NODE);
+ 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);
+ vty_install_default(TS_NODE);
+ install_element(TS_NODE, &cfg_ts_pchan_cmd);
+ install_element(TS_NODE, &cfg_ts_pchan_compat_cmd);
+ install_element(TS_NODE, &cfg_ts_tsc_cmd);
+ install_element(TS_NODE, &cfg_ts_hopping_cmd);
+ install_element(TS_NODE, &cfg_ts_hsn_cmd);
+ install_element(TS_NODE, &cfg_ts_maio_cmd);
+ install_element(TS_NODE, &cfg_ts_arfcn_add_cmd);
+ install_element(TS_NODE, &cfg_ts_arfcn_del_cmd);
+ install_element(TS_NODE, &cfg_ts_e1_subslot_cmd);
+
+ install_element(ENABLE_NODE, &drop_bts_cmd);
+ install_element(ENABLE_NODE, &restart_bts_cmd);
+ install_element(ENABLE_NODE, &pdch_act_cmd);
+ install_element(ENABLE_NODE, &lchan_act_cmd);
+ install_element(ENABLE_NODE, &lchan_mdcx_cmd);
+ install_element(ENABLE_NODE, &smscb_cmd_cmd);
+
+ abis_nm_vty_init();
+ abis_om2k_vty_init();
+ e1inp_vty_init();
+ osmo_fsm_vty_add_cmds();
+
+ bsc_vty_init_extra();
+
+ return 0;
+}
diff --git a/src/libbsc/bts_ericsson_rbs2000.c b/src/libbsc/bts_ericsson_rbs2000.c
new file mode 100644
index 000000000..99da4e75f
--- /dev/null
+++ b/src/libbsc/bts_ericsson_rbs2000.c
@@ -0,0 +1,204 @@
+/* Ericsson RBS-2xxx specific code */
+
+/* (C) 2011 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/gsm/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/abis/lapd.h>
+
+static void bootstrap_om_bts(struct gsm_bts *bts)
+{
+ LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+
+ /* FIXME: this is global init, not bootstrapping */
+ abis_om2k_bts_init(bts);
+ abis_om2k_trx_init(bts->c0);
+
+ /* TODO: Should we wait for a Failure report? */
+ om2k_bts_fsm_start(bts);
+}
+
+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 */
+}
+
+static int shutdown_om(struct gsm_bts *bts)
+{
+ /* FIXME */
+ return 0;
+}
+
+
+/* Tell LAPD to start start the SAP (send SABM requests) for all signalling
+ * timeslots in this line */
+static void start_sabm_in_line(struct e1inp_line *line, int start)
+{
+ struct e1inp_sign_link *link;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(line->ts); i++) {
+ struct e1inp_ts *ts = &line->ts[i];
+
+ if (ts->type != E1INP_TS_TYPE_SIGN)
+ continue;
+
+ llist_for_each_entry(link, &ts->sign.sign_links, list) {
+ if (!ts->lapd)
+ continue;
+ lapd_instance_set_profile(ts->lapd,
+ &lapd_profile_abis_ericsson);
+
+ if (start)
+ lapd_sap_start(ts->lapd, link->tei, link->sapi);
+ else
+ lapd_sap_stop(ts->lapd, link->tei, link->sapi);
+ }
+ }
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_bts *bts;
+
+ if (subsys != SS_L_GLOBAL)
+ return 0;
+
+ switch (signal) {
+ case S_GLOBAL_BTS_CLOSE_OM:
+ bts = signal_data;
+ if (bts->type == GSM_BTS_TYPE_RBS2000)
+ shutdown_om(signal_data);
+ break;
+ }
+
+ return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct input_signal_data *isd = signal_data;
+ struct e1inp_ts *e1i_ts;
+
+ if (subsys != SS_L_INPUT)
+ return 0;
+
+ LOGP(DNM, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__,
+ get_value_string(e1inp_signal_names, signal));
+ switch (signal) {
+ case S_L_INP_TEI_UP:
+ switch (isd->link_type) {
+ case E1INP_SIGN_OML:
+ if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000)
+ break;
+ if (isd->tei == isd->trx->bts->oml_tei)
+ bootstrap_om_bts(isd->trx->bts);
+ else
+ bootstrap_om_trx(isd->trx);
+ break;
+ }
+ break;
+ case S_L_INP_TEI_DN:
+ if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000)
+ break;
+ 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);
+ /* 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);
+ lapd_sap_start(e1i_ts->lapd, isd->tei, isd->sapi);
+ break;
+ case S_L_INP_LINE_INIT:
+ case S_L_INP_LINE_NOALARM:
+ if (strcasecmp(isd->line->driver->name, "DAHDI")
+ && strcasecmp(isd->line->driver->name, "MISDN_LAPD")
+ && strcasecmp(isd->line->driver->name, "UNIXSOCKET"))
+ break;
+ start_sabm_in_line(isd->line, 1);
+ break;
+ case S_L_INP_LINE_ALARM:
+ if (strcasecmp(isd->line->driver->name, "DAHDI")
+ && strcasecmp(isd->line->driver->name, "MISDN_LAPD")
+ && strcasecmp(isd->line->driver->name, "UNIXSOCKET"))
+ break;
+ start_sabm_in_line(isd->line, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static void config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+ abis_om2k_config_write_bts(vty, bts);
+}
+
+static int bts_model_rbs2k_start(struct gsm_network *net);
+
+static void bts_model_rbs2k_e1line_bind_ops(struct e1inp_line *line)
+{
+ e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops);
+}
+
+static struct gsm_bts_model model_rbs2k = {
+ .type = GSM_BTS_TYPE_RBS2000,
+ .name = "rbs2000",
+ .start = bts_model_rbs2k_start,
+ .oml_rcvmsg = &abis_om2k_rcvmsg,
+ .config_write_bts = &config_write_bts,
+ .e1line_bind_ops = &bts_model_rbs2k_e1line_bind_ops,
+};
+
+static int bts_model_rbs2k_start(struct gsm_network *net)
+{
+ model_rbs2k.features.data = &model_rbs2k._features_data[0];
+ model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data);
+
+ gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_GPRS);
+ gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_EGPRS);
+ gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HOPPING);
+ gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HSCSD);
+ gsm_btsmodel_set_feature(&model_rbs2k, 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/libbsc/bts_init.c b/src/libbsc/bts_init.c
new file mode 100644
index 000000000..d6b152a79
--- /dev/null
+++ b/src/libbsc/bts_init.c
@@ -0,0 +1,30 @@
+/* (C) 2011 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 <openbsc/bss.h>
+
+int bts_init(void)
+{
+ bts_model_bs11_init();
+ bts_model_rbs2k_init();
+ bts_model_nanobts_init();
+ bts_model_nokia_site_init();
+ bts_model_sysmobts_init();
+ /* Your new BTS here. */
+ return 0;
+}
diff --git a/src/libbsc/bts_ipaccess_nanobts.c b/src/libbsc/bts_ipaccess_nanobts.c
new file mode 100644
index 000000000..a1bde778f
--- /dev/null
+++ b/src/libbsc/bts_ipaccess_nanobts.c
@@ -0,0 +1,520 @@
+/* ip.access nanoBTS specific code */
+
+/* (C) 2009-2010 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 <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <osmocom/abis/subchan_demux.h>
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/core/logging.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/bts_ipaccess_nanobts_omlattr.h>
+
+extern struct gsm_network *bsc_gsmnet;
+
+static int bts_model_nanobts_start(struct gsm_network *net);
+static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line);
+
+struct gsm_bts_model bts_model_nanobts = {
+ .type = GSM_BTS_TYPE_NANOBTS,
+ .name = "nanobts",
+ .start = bts_model_nanobts_start,
+ .oml_rcvmsg = &abis_nm_rcvmsg,
+ .e1line_bind_ops = bts_model_nanobts_e1line_bind_ops,
+ .nm_att_tlvdef = {
+ .def = {
+ /* ip.access specifics */
+ [NM_ATT_IPACC_DST_IP] = { TLV_TYPE_FIXED, 4 },
+ [NM_ATT_IPACC_DST_IP_PORT] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_IPACC_STREAM_ID] = { TLV_TYPE_TV, },
+ [NM_ATT_IPACC_SEC_OML_CFG] = { TLV_TYPE_FIXED, 6 },
+ [NM_ATT_IPACC_IP_IF_CFG] = { TLV_TYPE_FIXED, 8 },
+ [NM_ATT_IPACC_IP_GW_CFG] = { TLV_TYPE_FIXED, 12 },
+ [NM_ATT_IPACC_IN_SERV_TIME] = { TLV_TYPE_FIXED, 4 },
+ [NM_ATT_IPACC_LOCATION] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_PAGING_CFG] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_IPACC_UNIT_ID] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_UNIT_NAME] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_SNMP_CFG] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_PRIM_OML_CFG_LIST] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_NV_FLAGS] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_FREQ_CTRL] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_IPACC_PRIM_OML_FB_TOUT] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_CUR_SW_CFG] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_TIMING_BUS] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_CGI] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_RAC] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_OBJ_VERSION] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_GPRS_PAGING_CFG]= { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_NSEI] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_BVCI] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_NSVCI] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_NS_CFG] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_BSSGP_CFG] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_NS_LINK_CFG] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_RLC_CFG] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_ALM_THRESH_LIST]= { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_MONIT_VAL_LIST] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_TIB_CONTROL] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_SUPP_FEATURES] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_CODING_SCHEMES] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_RLC_CFG_2] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_HEARTB_TOUT] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_UPTIME] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_RLC_CFG_3] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_SSL_CFG] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_SEC_POSSIBLE] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_IML_SSL_STATE] = { TLV_TYPE_TL16V },
+ [NM_ATT_IPACC_REVOC_DATE] = { TLV_TYPE_TL16V },
+ },
+ },
+};
+
+
+/* Callback function to be called whenever we get a GSM 12.21 state change event */
+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 *bts;
+ struct gsm_bts_trx *trx;
+ 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 */
+
+ /* S_NM_STATECHG_ADM is called after we call chg_adm_state() and would create
+ * endless loop */
+ if (evt != S_NM_STATECHG_OPER)
+ return 0;
+
+ switch (obj_class) {
+ case NM_OC_SITE_MANAGER:
+ bts = container_of(obj, struct gsm_bts, site_mgr);
+ if ((new_state->operational == NM_OPSTATE_ENABLED &&
+ new_state->availability == NM_AVSTATE_OK) ||
+ (new_state->operational == NM_OPSTATE_DISABLED &&
+ new_state->availability == NM_AVSTATE_OFF_LINE))
+ abis_nm_opstart(bts, obj_class, 0xff, 0xff, 0xff);
+ break;
+ case NM_OC_BTS:
+ bts = obj;
+ if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
+ msgb = nanobts_attr_bts_get(bts);
+ abis_nm_set_bts_attr(bts, msgb->data, msgb->len);
+ msgb_free(msgb);
+ abis_nm_chg_adm_state(bts, obj_class,
+ bts->bts_nr, 0xff, 0xff,
+ NM_STATE_UNLOCKED);
+ abis_nm_opstart(bts, obj_class,
+ bts->bts_nr, 0xff, 0xff);
+ }
+ break;
+ case NM_OC_CHANNEL:
+ ts = obj;
+ trx = ts->trx;
+ if (new_state->operational == NM_OPSTATE_DISABLED &&
+ new_state->availability == NM_AVSTATE_DEPENDENCY) {
+ enum abis_nm_chan_comb ccomb =
+ abis_nm_chcomb4pchan(ts->pchan);
+ if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL) {
+ ipaccess_drop_oml(trx->bts);
+ return -1;
+ }
+ abis_nm_chg_adm_state(trx->bts, obj_class,
+ trx->bts->bts_nr, trx->nr, ts->nr,
+ NM_STATE_UNLOCKED);
+ abis_nm_opstart(trx->bts, obj_class,
+ trx->bts->bts_nr, trx->nr, ts->nr);
+ }
+ if (new_state->operational == NM_OPSTATE_ENABLED
+ && new_state->availability == NM_AVSTATE_OK)
+ dyn_ts_init(ts);
+ break;
+ case NM_OC_RADIO_CARRIER:
+ trx = obj;
+ if (new_state->operational == NM_OPSTATE_DISABLED &&
+ new_state->availability == NM_AVSTATE_OK)
+ abis_nm_opstart(trx->bts, obj_class, trx->bts->bts_nr,
+ trx->nr, 0xff);
+ 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);
+ }
+ break;
+ case NM_OC_GPRS_CELL:
+ bts = container_of(obj, struct gsm_bts, gprs.cell);
+ if (bts->gprs.mode == BTS_GPRS_NONE)
+ break;
+ if (new_state->availability == NM_AVSTATE_DEPENDENCY) {
+ msgb = nanobts_attr_cell_get(bts);
+ abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+ 0, 0xff, msgb->data,
+ msgb->len);
+ msgb_free(msgb);
+ abis_nm_opstart(bts, obj_class, bts->bts_nr,
+ 0, 0xff);
+ abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
+ 0, 0xff, NM_STATE_UNLOCKED);
+ abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE, bts->bts_nr,
+ 0xff, 0xff, NM_STATE_UNLOCKED);
+ }
+ break;
+ case NM_OC_GPRS_NSVC:
+ nsvc = obj;
+ bts = nsvc->bts;
+ if (bts->gprs.mode == BTS_GPRS_NONE)
+ break;
+ /* We skip NSVC1 since we only use NSVC0 */
+ if (nsvc->id == 1)
+ break;
+ if ((new_state->availability == NM_AVSTATE_OFF_LINE) ||
+ (new_state->availability == NM_AVSTATE_DEPENDENCY)) {
+ msgb = nanobts_attr_nscv_get(bts);
+ abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+ nsvc->id, 0xff,
+ msgb->data, msgb->len);
+ msgb_free(msgb);
+ abis_nm_opstart(bts, obj_class, bts->bts_nr,
+ nsvc->id, 0xff);
+ abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr,
+ nsvc->id, 0xff,
+ NM_STATE_UNLOCKED);
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* 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);
+ struct e1inp_sign_link *sign_link = mb->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+
+ if (!trx)
+ return -EINVAL;
+
+ if (!is_ipaccess_bts(trx->bts))
+ return 0;
+
+ switch (foh->obj_class) {
+ case NM_OC_BASEB_TRANSC:
+ abis_nm_chg_adm_state(trx->bts, foh->obj_class,
+ trx->bts->bts_nr, trx->nr, 0xff,
+ NM_STATE_UNLOCKED);
+ abis_nm_opstart(trx->bts, foh->obj_class,
+ trx->bts->bts_nr, trx->nr, 0xff);
+ /* TRX software is active, tell it to initiate RSL Link */
+ abis_nm_ipaccess_rsl_connect(trx, trx->bts->ip_access.rsl_ip,
+ 3003, trx->rsl_tei);
+ break;
+ case NM_OC_RADIO_CARRIER: {
+ /*
+ * Locking the radio carrier will make it go
+ * offline again and we would come here. The
+ * framework should determine that there was
+ * no change and avoid recursion.
+ *
+ * This code is here to make sure that on start
+ * a TRX remains locked.
+ */
+ int rc_state = trx->mo.nm_state.administrative;
+ /* Patch ARFCN into radio attribute */
+ struct msgb *msgb = nanobts_attr_radio_get(trx->bts, trx);
+ abis_nm_set_radio_attr(trx, msgb->data, msgb->len);
+ msgb_free(msgb);
+ abis_nm_chg_adm_state(trx->bts, foh->obj_class,
+ trx->bts->bts_nr, trx->nr, 0xff,
+ rc_state);
+ abis_nm_opstart(trx->bts, foh->obj_class, trx->bts->bts_nr,
+ trx->nr, 0xff);
+ break;
+ }
+ }
+ return 0;
+}
+
+/* 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)
+{
+ if (subsys != SS_NM)
+ return 0;
+
+ switch (signal) {
+ case S_NM_SW_ACTIV_REP:
+ return sw_activ_rep(signal_data);
+ case S_NM_STATECHG_OPER:
+ case S_NM_STATECHG_ADM:
+ return nm_statechg_event(signal, signal_data);
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int bts_model_nanobts_start(struct gsm_network *net)
+{
+ osmo_signal_unregister_handler(SS_NM, bts_ipa_nm_sig_cb, NULL);
+ osmo_signal_register_handler(SS_NM, bts_ipa_nm_sig_cb, NULL);
+ return 0;
+}
+
+int bts_model_nanobts_init(void)
+{
+ bts_model_nanobts.features.data = &bts_model_nanobts._features_data[0];
+ bts_model_nanobts.features.data_len =
+ sizeof(bts_model_nanobts._features_data);
+
+ gsm_btsmodel_set_feature(&bts_model_nanobts, BTS_FEAT_GPRS);
+ gsm_btsmodel_set_feature(&bts_model_nanobts, BTS_FEAT_EGPRS);
+ gsm_btsmodel_set_feature(&bts_model_nanobts, BTS_FEAT_MULTI_TSC);
+
+ return gsm_bts_model_register(&bts_model_nanobts);
+}
+
+#define OML_UP 0x0001
+#define RSL_UP 0x0002
+
+static struct gsm_bts *
+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))
+ continue;
+
+ if (bts->ip_access.site_id == site_id &&
+ bts->ip_access.bts_id == bts_id)
+ return bts;
+ }
+ return NULL;
+}
+
+/* These are exported because they are used by the VTY interface. */
+void ipaccess_drop_rsl(struct gsm_bts_trx *trx)
+{
+ if (!trx->rsl_link)
+ return;
+
+ e1inp_sign_link_destroy(trx->rsl_link);
+ trx->rsl_link = NULL;
+}
+
+void ipaccess_drop_oml(struct gsm_bts *bts)
+{
+ struct gsm_bts *rdep_bts;
+ struct gsm_bts_trx *trx;
+
+ if (!bts->oml_link)
+ return;
+
+ e1inp_sign_link_destroy(bts->oml_link);
+ bts->oml_link = NULL;
+
+ /* we have issues reconnecting RSL, drop everything. */
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ ipaccess_drop_rsl(trx);
+
+ bts->ip_access.flags = 0;
+
+ /*
+ * 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
+ * should be fine in userspace.
+ * The oml_link is serving as recursion anchor for us and
+ * it is set to NULL some lines above.
+ */
+ llist_for_each_entry(rdep_bts, &bts->network->bts_list, list) {
+ if (!bts_depend_is_depedency(rdep_bts, bts))
+ continue;
+ LOGP(DLINP, LOGL_NOTICE, "Dropping BTS(%u) due BTS(%u).\n",
+ rdep_bts->nr, bts->nr);
+ ipaccess_drop_oml(rdep_bts);
+ }
+}
+
+/* This function is called once the OML/RSL link becomes up. */
+static struct e1inp_sign_link *
+ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line,
+ enum e1inp_sign_type type)
+{
+ struct gsm_bts *bts;
+ struct ipaccess_unit *dev = unit_data;
+ struct e1inp_sign_link *sign_link = NULL;
+
+ bts = find_bts_by_unitid(bsc_gsmnet, dev->site_id, dev->bts_id);
+ if (!bts) {
+ LOGP(DLINP, LOGL_ERROR, "Unable to find BTS configuration for "
+ " %u/%u/%u, disconnecting\n", dev->site_id,
+ dev->bts_id, dev->trx_id);
+ return NULL;
+ }
+ DEBUGP(DLINP, "Identified BTS %u/%u/%u\n",
+ dev->site_id, dev->bts_id, dev->trx_id);
+
+ switch(type) {
+ case E1INP_SIGN_OML:
+ /* remove old OML signal link for this BTS. */
+ ipaccess_drop_oml(bts);
+
+ if (!bts_depend_check(bts)) {
+ LOGP(DLINP, LOGL_NOTICE,
+ "Dependency not full-filled for %u/%u/%u\n",
+ dev->site_id, dev->bts_id, dev->trx_id);
+ return NULL;
+ }
+
+ /* create new OML link. */
+ sign_link = bts->oml_link =
+ e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML - 1],
+ E1INP_SIGN_OML, bts->c0,
+ bts->oml_tei, 0);
+ break;
+ case E1INP_SIGN_RSL: {
+ struct e1inp_ts *ts;
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, dev->trx_id);
+
+ /* no OML link set yet? give up. */
+ if (!bts->oml_link || !trx)
+ return NULL;
+
+ /* remove old RSL link for this TRX. */
+ ipaccess_drop_rsl(trx);
+
+ /* set new RSL link for this TRX. */
+ line = bts->oml_link->ts->line;
+ ts = &line->ts[E1INP_SIGN_RSL + dev->trx_id - 1];
+ e1inp_ts_config_sign(ts, line);
+ sign_link = trx->rsl_link =
+ e1inp_sign_link_create(ts, E1INP_SIGN_RSL,
+ trx, trx->rsl_tei, 0);
+ trx->rsl_link->ts->sign.delay = 0;
+ break;
+ }
+ default:
+ break;
+ }
+ return sign_link;
+}
+
+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_sign_link *link;
+
+ llist_for_each_entry(link, &ts->sign.sign_links, list) {
+ struct gsm_bts *bts = link->trx->bts;
+
+ ipaccess_drop_oml(bts);
+ /* Yes, we only use the first element of the list. */
+ break;
+ }
+}
+
+/* This function is called if we receive one OML/RSL message. */
+static int ipaccess_sign_link(struct msgb *msg)
+{
+ int ret = 0;
+ struct e1inp_sign_link *link = msg->dst;
+ struct e1inp_ts *e1i_ts = link->ts;
+
+ switch (link->type) {
+ case E1INP_SIGN_RSL:
+ if (!(link->trx->bts->ip_access.flags &
+ (RSL_UP << link->trx->nr))) {
+ e1inp_event(e1i_ts, S_L_INP_TEI_UP,
+ link->tei, link->sapi);
+ link->trx->bts->ip_access.flags |=
+ (RSL_UP << link->trx->nr);
+ }
+ ret = abis_rsl_rcvmsg(msg);
+ break;
+ case E1INP_SIGN_OML:
+ if (!(link->trx->bts->ip_access.flags & OML_UP)) {
+ e1inp_event(e1i_ts, S_L_INP_TEI_UP,
+ link->tei, link->sapi);
+ link->trx->bts->ip_access.flags |= OML_UP;
+ }
+ ret = abis_nm_rcvmsg(msg);
+ break;
+ default:
+ LOGP(DLINP, LOGL_ERROR, "Unknown signal link type %d\n",
+ link->type);
+ msgb_free(msg);
+ break;
+ }
+ return ret;
+}
+
+/* not static, ipaccess-config needs it. */
+struct e1inp_line_ops ipaccess_e1inp_line_ops = {
+ .cfg = {
+ .ipa = {
+ .addr = "0.0.0.0",
+ .role = E1INP_LINE_R_BSC,
+ },
+ },
+ .sign_link_up = ipaccess_sign_link_up,
+ .sign_link_down = ipaccess_sign_link_down,
+ .sign_link = ipaccess_sign_link,
+};
+
+static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line)
+{
+ e1inp_line_bind_ops(line, &ipaccess_e1inp_line_ops);
+}
diff --git a/src/libbsc/bts_ipaccess_nanobts_omlattr.c b/src/libbsc/bts_ipaccess_nanobts_omlattr.c
new file mode 100644
index 000000000..473e1caea
--- /dev/null
+++ b/src/libbsc/bts_ipaccess_nanobts_omlattr.c
@@ -0,0 +1,240 @@
+/* ip.access nanoBTS specific code, OML attribute table generator */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Philipp Maier
+ *
+ * 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/core/msgb.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+
+static void patch_16(uint8_t *data, const uint16_t val)
+{
+ memcpy(data, &val, sizeof(val));
+}
+
+static void patch_32(uint8_t *data, const uint32_t val)
+{
+ memcpy(data, &val, sizeof(val));
+}
+
+struct msgb *nanobts_attr_bts_get(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);
+
+ /* interference avg. period in numbers of SACCH multifr */
+ msgb_tv_put(msgb, NM_ATT_INTAVE_PARAM, 0x06);
+
+ rlt = gsm_bts_get_radio_link_timeout(bts);
+ if (rlt == -1) {
+ /* Osmocom extension: Use infinite radio link timeout */
+ buf[0] = 0xFF;
+ buf[1] = 0x00;
+ } else {
+ /* conn fail based on SACCH error rate */
+ buf[0] = 0x01;
+ buf[1] = rlt;
+ }
+ msgb_tl16v_put(msgb, NM_ATT_CONN_FAIL_CRIT, 2, buf);
+
+ memcpy(buf, "\x1e\x24\x24\xa8\x34\x21\xa8", 7);
+ msgb_tv_fixed_put(msgb, NM_ATT_T200, 7, buf);
+
+ msgb_tv_put(msgb, NM_ATT_MAX_TA, 0x3f);
+
+ /* seconds */
+ memcpy(buf, "\x00\x01\x0a", 3);
+ msgb_tv_fixed_put(msgb, NM_ATT_OVERL_PERIOD, 3, buf);
+
+ /* percent */
+ msgb_tv_put(msgb, NM_ATT_CCCH_L_T, 10);
+
+ /* seconds */
+ msgb_tv_put(msgb, NM_ATT_CCCH_L_I_P, 1);
+
+ /* busy threshold in - dBm */
+ buf[0] = 10;
+ 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 */
+ buf[0] = 0x03;
+ buf[1] = 0xe8;
+ if (bts->rach_ldavg_slots != -1) {
+ buf[0] = (bts->rach_ldavg_slots >> 8) & 0x0f;
+ buf[1] = bts->rach_ldavg_slots & 0xff;
+ }
+ msgb_tv_fixed_put(msgb, NM_ATT_LDAVG_SLOTS, 2, buf);
+
+ /* miliseconds */
+ msgb_tv_put(msgb, NM_ATT_BTS_AIR_TIMER, 128);
+
+ /* 10 retransmissions of physical config */
+ msgb_tv_put(msgb, NM_ATT_NY1, 10);
+
+ buf[0] = (bts->c0->arfcn >> 8) & 0x0f;
+ buf[1] = bts->c0->arfcn & 0xff;
+ msgb_tv_fixed_put(msgb, NM_ATT_BCCH_ARFCN, 2, buf);
+
+ msgb_tv_put(msgb, NM_ATT_BSIC, bts->bsic);
+
+ abis_nm_ipaccess_cgi(buf, bts);
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_CGI, 7, buf);
+
+ return msgb;
+}
+
+struct msgb *nanobts_attr_nse_get(struct gsm_bts *bts)
+{
+ struct msgb *msgb;
+ uint8_t buf[256];
+ msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+ /* NSEI 925 */
+ buf[0] = bts->gprs.nse.nsei >> 8;
+ buf[1] = bts->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);
+
+ return msgb;
+}
+
+struct msgb *nanobts_attr_cell_get(struct gsm_bts *bts)
+{
+ struct msgb *msgb;
+ uint8_t buf[256];
+ msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+ /* 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 */
+ 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;
+ 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;
+ buf[1] = 0x00;
+ }
+ 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 *msgb;
+ uint8_t buf[256];
+ msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+ /* 925 */
+ buf[0] = bts->gprs.nsvc[0].nsvci >> 8;
+ buf[1] = bts->gprs.nsvc[0].nsvci & 0xff;
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_NSVCI, 2, buf);
+
+ /* remote udp port */
+ patch_16(&buf[0], htons(bts->gprs.nsvc[0].remote_port));
+ /* remote ip address */
+ patch_32(&buf[2], htonl(bts->gprs.nsvc[0].remote_ip));
+ /* local udp port */
+ patch_16(&buf[6], htons(bts->gprs.nsvc[0].local_port));
+ msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_LINK_CFG, 8, buf);
+
+ return msgb;
+}
+
+struct msgb *nanobts_attr_radio_get(struct gsm_bts *bts,
+ struct gsm_bts_trx *trx)
+{
+ struct msgb *msgb;
+ uint8_t buf[256];
+ msgb = msgb_alloc(1024, "nanobts_attr_bts");
+
+ /* number of -2dB reduction steps / Pn */
+ msgb_tv_put(msgb, NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2);
+
+ buf[0] = trx->arfcn >> 8;
+ buf[1] = trx->arfcn & 0xff;
+ msgb_tl16v_put(msgb, NM_ATT_ARFCN_LIST, 2, buf);
+
+ return msgb;
+}
diff --git a/src/libbsc/bts_nokia_site.c b/src/libbsc/bts_nokia_site.c
new file mode 100644
index 000000000..3ca76c017
--- /dev/null
+++ b/src/libbsc/bts_nokia_site.c
@@ -0,0 +1,1739 @@
+/* Nokia XXXsite family specific code */
+
+/* (C) 2011 by Dieter Spaar <spaar@mirider.augusta.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/>.
+ *
+ */
+
+/*
+ TODO: Attention: There are some static variables used for states during
+ configuration. Those variables have to be moved to a BTS specific context,
+ otherwise there will most certainly be problems if more than one Nokia BTS
+ is used.
+*/
+
+#include <time.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/timer.h>
+
+#include <osmocom/abis/lapd.h>
+
+/* TODO: put in a separate file ? */
+
+extern int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg);
+/* was static in system_information.c */
+extern int generate_cell_chan_list(uint8_t * chan_list, struct gsm_bts *bts);
+
+static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts);
+static void reset_timer_cb(void *_bts);
+static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref);
+static int dump_elements(uint8_t * data, int len) __attribute__((unused));
+
+static void bootstrap_om_bts(struct gsm_bts *bts)
+{
+ LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+
+ if (!bts->nokia.skip_reset) {
+ if (!bts->nokia.did_reset)
+ abis_nm_reset(bts, 1);
+ } else
+ bts->nokia.did_reset = 1;
+}
+
+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);
+}
+
+static int shutdown_om(struct gsm_bts *bts)
+{
+ /* TODO !? */
+ return 0;
+}
+
+#define SAPI_OML 62
+#define SAPI_RSL 0
+
+/*
+
+ Tell LAPD to start start the SAP (send SABM requests) for all signalling
+ timeslots in this line
+
+ Attention: this has to be adapted for mISDN
+*/
+
+static void start_sabm_in_line(struct e1inp_line *line, int start, int sapi)
+{
+ struct e1inp_sign_link *link;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(line->ts); i++) {
+ struct e1inp_ts *ts = &line->ts[i];
+
+ if (ts->type != E1INP_TS_TYPE_SIGN)
+ continue;
+
+ llist_for_each_entry(link, &ts->sign.sign_links, list) {
+ if (sapi != -1 && link->sapi != sapi)
+ continue;
+
+#if 0 /* debugging */
+ printf("sap start/stop (%d): %d tei=%d sapi=%d\n",
+ start, i + 1, link->tei, link->sapi);
+#endif
+
+ if (start) {
+ ts->lapd->profile.t200_sec = 1;
+ ts->lapd->profile.t200_usec = 0;
+ lapd_sap_start(ts->lapd, link->tei,
+ link->sapi);
+ } else
+ lapd_sap_stop(ts->lapd, link->tei,
+ link->sapi);
+ }
+ }
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_bts *bts;
+
+ if (subsys != SS_L_GLOBAL)
+ return 0;
+
+ switch (signal) {
+ case S_GLOBAL_BTS_CLOSE_OM:
+ bts = signal_data;
+ if (bts->type == GSM_BTS_TYPE_NOKIA_SITE)
+ shutdown_om(signal_data);
+ break;
+ }
+
+ return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct input_signal_data *isd = signal_data;
+
+ if (subsys != SS_L_INPUT)
+ return 0;
+
+ switch (signal) {
+ case S_L_INP_LINE_INIT:
+ start_sabm_in_line(isd->line, 1, SAPI_OML); /* start only OML */
+ break;
+ case S_L_INP_TEI_DN:
+ break;
+ case S_L_INP_TEI_UP:
+ switch (isd->link_type) {
+ case E1INP_SIGN_OML:
+ if (isd->trx->bts->type != GSM_BTS_TYPE_NOKIA_SITE)
+ break;
+
+ if (isd->tei == isd->trx->bts->oml_tei)
+ bootstrap_om_bts(isd->trx->bts);
+ else
+ bootstrap_om_trx(isd->trx);
+ break;
+ }
+ break;
+ case S_L_INP_TEI_UNKNOWN:
+ /* We are receiving LAPD frames with one TEI that we do not
+ * seem to know, likely that we (the BSC) stopped working
+ * and lost our local states. However, the BTS is already
+ * configured, we try to take over the RSL links. */
+ start_sabm_in_line(isd->line, 1, SAPI_RSL);
+ break;
+ }
+
+ return 0;
+}
+
+static void nm_statechg_evt(unsigned int signal,
+ struct nm_statechg_signal_data *nsd)
+{
+ if (nsd->bts->type != GSM_BTS_TYPE_NOKIA_SITE)
+ return;
+}
+
+static int nm_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ if (subsys != SS_NM)
+ return 0;
+
+ switch (signal) {
+ case S_NM_STATECHG_OPER:
+ case S_NM_STATECHG_ADM:
+ nm_statechg_evt(signal, signal_data);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* TODO: put in a separate file ? */
+
+static const struct value_string nokia_msgt_name[] = {
+ { 0x80, "NOKIA_BTS_CONF_DATA" },
+ { 0x81, "NOKIA_BTS_ACK" },
+ { 0x82, "NOKIA_BTS_OMU_STARTED" },
+ { 0x83, "NOKIA_BTS_START_DOWNLOAD_REQ" },
+ { 0x84, "NOKIA_BTS_MF_REQ" },
+ { 0x85, "NOKIA_BTS_AF_REQ" },
+ { 0x86, "NOKIA_BTS_RESET_REQ" },
+ { 0x87, "NOKIA_reserved" },
+ { 0x88, "NOKIA_BTS_CONF_REQ" },
+ { 0x89, "NOKIA_BTS_TEST_REQ" },
+ { 0x8A, "NOKIA_BTS_TEST_REPORT" },
+ { 0x8B, "NOKIA_reserved" },
+ { 0x8C, "NOKIA_reserved" },
+ { 0x8D, "NOKIA_reserved" },
+ { 0x8E, "NOKIA_BTS_CONF_COMPL" },
+ { 0x8F, "NOKIA_reserved" },
+ { 0x90, "NOKIA_BTS_STM_TEST_REQ" },
+ { 0x91, "NOKIA_BTS_STM_TEST_REPORT" },
+ { 0x92, "NOKIA_BTS_TRANSMISSION_COMMAND" },
+ { 0x93, "NOKIA_BTS_TRANSMISSION_ANSWER" },
+ { 0x94, "NOKIA_BTS_HW_DB_UPLOAD_REQ" },
+ { 0x95, "NOKIA_BTS_START_HW_DB_DOWNLOAD_REQ" },
+ { 0x96, "NOKIA_BTS_HW_DB_SAVE_REQ" },
+ { 0x97, "NOKIA_BTS_FLASH_ERASURE_REQ" },
+ { 0x98, "NOKIA_BTS_HW_DB_DOWNLOAD_REQ" },
+ { 0x99, "NOKIA_BTS_PWR_SUPPLY_CONTROL" },
+ { 0x9A, "NOKIA_BTS_ATTRIBUTE_REQ" },
+ { 0x9B, "NOKIA_BTS_ATTRIBUTE_REPORT" },
+ { 0x9C, "NOKIA_BTS_HW_REQ" },
+ { 0x9D, "NOKIA_BTS_HW_REPORT" },
+ { 0x9E, "NOKIA_BTS_RTE_TEST_REQ" },
+ { 0x9F, "NOKIA_BTS_RTE_TEST_REPORT" },
+ { 0xA0, "NOKIA_BTS_HW_DB_VERIFICATION_REQ" },
+ { 0xA1, "NOKIA_BTS_CLOCK_REQ" },
+ { 0xA2, "NOKIA_AC_CIRCUIT_REQ_NACK" },
+ { 0xA3, "NOKIA_AC_INTERRUPTED" },
+ { 0xA4, "NOKIA_BTS_NEW_TRE_INFO" },
+ { 0xA5, "NOKIA_AC_BSC_CIRCUITS_ALLOCATED" },
+ { 0xA6, "NOKIA_BTS_TRE_POLL_LIST" },
+ { 0xA7, "NOKIA_AC_CIRCUIT_REQ" },
+ { 0xA8, "NOKIA_BTS_BLOCK_CTRL_REQ" },
+ { 0xA9, "NOKIA_BTS_GSM_TIME_REQ" },
+ { 0xAA, "NOKIA_BTS_GSM_TIME" },
+ { 0xAB, "NOKIA_BTS_OUTPUT_CONTROL" },
+ { 0xAC, "NOKIA_BTS_STATE_CHANGED" },
+ { 0xAD, "NOKIA_BTS_SW_SAVE_REQ" },
+ { 0xAE, "NOKIA_BTS_ALARM" },
+ { 0xAF, "NOKIA_BTS_CHA_ADM_STATE" },
+ { 0xB0, "NOKIA_AC_POOL_SIZE_REPORT" },
+ { 0xB1, "NOKIA_AC_POOL_SIZE_INQUIRY" },
+ { 0xB2, "NOKIA_BTS_COMMISS_TEST_COMPLETED" },
+ { 0xB3, "NOKIA_BTS_COMMISS_TEST_REQ" },
+ { 0xB4, "NOKIA_BTS_TRANSP_BTS_TO_BSC" },
+ { 0xB5, "NOKIA_BTS_TRANSP_BSC_TO_BTS" },
+ { 0xB6, "NOKIA_BTS_LCS_COMMAND" },
+ { 0xB7, "NOKIA_BTS_LCS_ANSWER" },
+ { 0xB8, "NOKIA_BTS_LMU_FN_OFFSET_COMMAND" },
+ { 0xB9, "NOKIA_BTS_LMU_FN_OFFSET_ANSWER" },
+ { 0, NULL }
+};
+
+static const char *get_msg_type_name_string(uint8_t msg_type)
+{
+ return get_value_string(nokia_msgt_name, msg_type);
+}
+
+static const struct value_string nokia_element_name[] = {
+ { 0x01, "Ny1" },
+ { 0x02, "T3105_F" },
+ { 0x03, "Interference band limits" },
+ { 0x04, "Interference report timer in secs" },
+ { 0x05, "Channel configuration per TS" },
+ { 0x06, "BSIC" },
+ { 0x07, "RACH report timer in secs" },
+ { 0x08, "Hardware database status" },
+ { 0x09, "BTS RX level" },
+ { 0x0A, "ARFN" },
+ { 0x0B, "STM antenna attenuation" },
+ { 0x0C, "Cell allocation bitmap" },
+ { 0x0D, "Radio definition per TS" },
+ { 0x0E, "Frame number" },
+ { 0x0F, "Antenna diversity" },
+ { 0x10, "T3105_D" },
+ { 0x11, "File format" },
+ { 0x12, "Last File" },
+ { 0x13, "BTS type" },
+ { 0x14, "Erasure mode" },
+ { 0x15, "Hopping mode" },
+ { 0x16, "Floating TRX" },
+ { 0x17, "Power supplies" },
+ { 0x18, "Reset type" },
+ { 0x19, "Averaging period" },
+ { 0x1A, "RBER2" },
+ { 0x1B, "LAC" },
+ { 0x1C, "CI" },
+ { 0x1D, "Failure parameters" },
+ { 0x1E, "(RF max power reduction)" },
+ { 0x1F, "Measured RX_SENS" },
+ { 0x20, "Extended cell radius" },
+ { 0x21, "reserved" },
+ { 0x22, "Success-Failure" },
+ { 0x23, "Ack-Nack" },
+ { 0x24, "OMU test results" },
+ { 0x25, "File identity" },
+ { 0x26, "Generation and version code" },
+ { 0x27, "SW description" },
+ { 0x28, "BCCH LEV" },
+ { 0x29, "Test type" },
+ { 0x2A, "Subscriber number" },
+ { 0x2B, "reserved" },
+ { 0x2C, "HSN" },
+ { 0x2D, "reserved" },
+ { 0x2E, "MS RXLEV" },
+ { 0x2F, "MS TXLEV" },
+ { 0x30, "RXQUAL" },
+ { 0x31, "RX SENS" },
+ { 0x32, "Alarm block" },
+ { 0x33, "Neighbouring BCCH levels" },
+ { 0x34, "STM report type" },
+ { 0x35, "MA" },
+ { 0x36, "MAIO" },
+ { 0x37, "H_FLAG" },
+ { 0x38, "TCH_ARFN" },
+ { 0x39, "Clock output" },
+ { 0x3A, "Transmitted power" },
+ { 0x3B, "Clock sync" },
+ { 0x3C, "TMS protocol discriminator" },
+ { 0x3D, "TMS protocol data" },
+ { 0x3E, "FER" },
+ { 0x3F, "SWR result" },
+ { 0x40, "Object identity" },
+ { 0x41, "STM RX Antenna Test" },
+ { 0x42, "reserved" },
+ { 0x43, "reserved" },
+ { 0x44, "Object current state" },
+ { 0x45, "reserved" },
+ { 0x46, "FU channel configuration" },
+ { 0x47, "reserved" },
+ { 0x48, "ARFN of a CU" },
+ { 0x49, "FU radio definition" },
+ { 0x4A, "reserved" },
+ { 0x4B, "Severity" },
+ { 0x4C, "Diversity selection" },
+ { 0x4D, "RX antenna test" },
+ { 0x4E, "RX antenna supervision period" },
+ { 0x4F, "RX antenna state" },
+ { 0x50, "Sector configuration" },
+ { 0x51, "Additional info" },
+ { 0x52, "SWR parameters" },
+ { 0x53, "HW inquiry mode" },
+ { 0x54, "reserved" },
+ { 0x55, "Availability status" },
+ { 0x56, "reserved" },
+ { 0x57, "EAC inputs" },
+ { 0x58, "EAC outputs" },
+ { 0x59, "reserved" },
+ { 0x5A, "Position" },
+ { 0x5B, "HW unit identity" },
+ { 0x5C, "RF test signal attenuation" },
+ { 0x5D, "Operational state" },
+ { 0x5E, "Logical object identity" },
+ { 0x5F, "reserved" },
+ { 0x60, "BS_TXPWR_OM" },
+ { 0x61, "Loop_Duration" },
+ { 0x62, "LNA_Path_Selection" },
+ { 0x63, "Serial number" },
+ { 0x64, "HW version" },
+ { 0x65, "Obj. identity and obj. state" },
+ { 0x66, "reserved" },
+ { 0x67, "EAC input definition" },
+ { 0x68, "EAC id and text" },
+ { 0x69, "HW unit status" },
+ { 0x6A, "SW release version" },
+ { 0x6B, "FW version" },
+ { 0x6C, "Bit_Error_Ratio" },
+ { 0x6D, "RXLEV_with_Attenuation" },
+ { 0x6E, "RXLEV_without_Attenuation" },
+ { 0x6F, "reserved" },
+ { 0x70, "CU_Results" },
+ { 0x71, "reserved" },
+ { 0x72, "LNA_Path_Results" },
+ { 0x73, "RTE Results" },
+ { 0x74, "Real Time" },
+ { 0x75, "RX diversity selection" },
+ { 0x76, "EAC input config" },
+ { 0x77, "Feature support" },
+ { 0x78, "File version" },
+ { 0x79, "Outputs" },
+ { 0x7A, "FU parameters" },
+ { 0x7B, "Diagnostic info" },
+ { 0x7C, "FU BSIC" },
+ { 0x7D, "TRX Configuration" },
+ { 0x7E, "Download status" },
+ { 0x7F, "RX difference limit" },
+ { 0x80, "TRX HW capability" },
+ { 0x81, "Common HW config" },
+ { 0x82, "Autoconfiguration pool size" },
+ { 0x83, "TRE diagnostic info" },
+ { 0x84, "TRE object identity" },
+ { 0x85, "New TRE Info" },
+ { 0x86, "Acknowledgement period" },
+ { 0x87, "Synchronization mode" },
+ { 0x88, "reserved" },
+ { 0x89, "Block Control Data" },
+ { 0x8A, "SW load mode" },
+ { 0x8B, "Recommended recovery action" },
+ { 0x8C, "BSC BCF id" },
+ { 0x8D, "Q1 baud rate" },
+ { 0x8E, "Allocation status" },
+ { 0x8F, "Functional entity number" },
+ { 0x90, "Transmission delay" },
+ { 0x91, "Loop Duration ms" },
+ { 0x92, "Logical channel" },
+ { 0x93, "Q1 address" },
+ { 0x94, "Alarm detail" },
+ { 0x95, "Cabinet type" },
+ { 0x96, "HW unit existence" },
+ { 0x97, "RF power parameters" },
+ { 0x98, "Message scenario" },
+ { 0x99, "HW unit max amount" },
+ { 0x9A, "Master TRX" },
+ { 0x9B, "Transparent data" },
+ { 0x9C, "BSC topology info" },
+ { 0x9D, "Air i/f modulation" },
+ { 0x9E, "LCS Q1 command data" },
+ { 0x9F, "Frame number offset" },
+ { 0xA0, "Abis TSL" },
+ { 0xA1, "Dynamic pool info" },
+ { 0xA2, "LCS LLP data" },
+ { 0xA3, "LCS Q1 answer data" },
+ { 0xA4, "DFCA FU Radio Definition" },
+ { 0xA5, "Antenna hopping" },
+ { 0xA6, "Field record sequence number" },
+ { 0xA7, "Timeslot offslot" },
+ { 0xA8, "EPCR capability" },
+ { 0xA9, "Connectsite optional element" },
+ { 0xAA, "TSC" },
+ { 0xAB, "Special TX Power Setting" },
+ { 0xAC, "Optional sync settings" },
+ { 0xFA, "Abis If parameters" },
+ { 0, NULL }
+};
+
+static const char *get_element_name_string(uint16_t element)
+{
+ return get_value_string(nokia_element_name, element);
+}
+
+static const struct value_string nokia_bts_types[] = {
+ { 0x0a, "MetroSite GSM 900" },
+ { 0x0b, "MetroSite GSM 1800" },
+ { 0x0c, "MetroSite GSM 1900 (PCS)" },
+ { 0x0d, "MetroSite GSM 900 & 1800" },
+ { 0x0e, "InSite GSM 900" },
+ { 0x0f, "InSite GSM 1800" },
+ { 0x10, "InSite GSM 1900" },
+ { 0x11, "UltraSite GSM 900" },
+ { 0x12, "UltraSite GSM 1800" },
+ { 0x13, "UltraSite GSM/US-TDMA 1900" },
+ { 0x14, "UltraSite GSM 900 & 1800" },
+ { 0x16, "UltraSite GSM/US-TDMA 850" },
+ { 0x18, "MetroSite GSM/US-TDMA 850" },
+ { 0x19, "UltraSite GSM 800/1900" },
+ { 0, NULL }
+};
+
+static const char *get_bts_type_string(uint8_t type)
+{
+ return get_value_string(nokia_bts_types, type);
+}
+
+static const struct value_string nokia_severity[] = {
+ { 0, "indeterminate" },
+ { 1, "critical" },
+ { 2, "major" },
+ { 3, "minor" },
+ { 4, "warning" },
+ { 0, NULL }
+};
+
+static const char *get_severity_string(uint8_t severity)
+{
+ return get_value_string(nokia_severity, severity);
+}
+
+/* TODO: put in a separate file ? */
+
+/* some message IDs */
+
+#define NOKIA_MSG_CONF_DATA 128
+#define NOKIA_MSG_ACK 129
+#define NOKIA_MSG_OMU_STARTED 130
+#define NOKIA_MSG_START_DOWNLOAD_REQ 131
+#define NOKIA_MSG_MF_REQ 132
+#define NOKIA_MSG_RESET_REQ 134
+#define NOKIA_MSG_CONF_REQ 136
+#define NOKIA_MSG_CONF_COMPLETE 142
+#define NOKIA_MSG_BLOCK_CTRL_REQ 168
+#define NOKIA_MSG_STATE_CHANGED 172
+#define NOKIA_MSG_ALARM 174
+
+/* some element IDs */
+
+#define NOKIA_EI_BTS_TYPE 0x13
+#define NOKIA_EI_ACK 0x23
+#define NOKIA_EI_ADD_INFO 0x51
+#define NOKIA_EI_SEVERITY 0x4B
+#define NOKIA_EI_ALARM_DETAIL 0x94
+
+#define OM_ALLOC_SIZE 1024
+#define OM_HEADROOM_SIZE 128
+
+static uint8_t fu_config_template[] = {
+ 0x7F, 0x7A, 0x39,
+ /* ID = 0x7A (FU parameters) ## constructed ## */
+ /* length = 57 */
+ /* [3] */
+
+ 0x5F, 0x40, 0x04,
+ /* ID = 0x40 (Object identity) */
+ /* length = 4 */
+ /* [6] */
+ 0x00, 0x07, 0x01, 0xFF,
+
+ 0x41, 0x02,
+ /* ID = 0x01 (Ny1) */
+ /* length = 2 */
+ /* [12] */
+ 0x00, 0x05,
+
+ 0x42, 0x02,
+ /* ID = 0x02 (T3105_F) */
+ /* length = 2 */
+ /* [16] */
+ 0x00, 0x28, /* FIXME: use net->T3105 */
+
+ 0x50, 0x02,
+ /* ID = 0x10 (T3105_D) */
+ /* length = 2 */
+ /* [20] */
+ 0x00, 0x28, /* FIXME: use net->T3105 */
+
+ 0x43, 0x05,
+ /* ID = 0x03 (Interference band limits) */
+ /* length = 5 */
+ /* [24] */
+ 0x0F, 0x1B, 0x27, 0x33, 0x3F,
+
+ 0x44, 0x02,
+ /* ID = 0x04 (Interference report timer in secs) */
+ /* length = 2 */
+ /* [31] */
+ 0x00, 0x10,
+
+ 0x47, 0x01,
+ /* ID = 0x07 (RACH report timer in secs) */
+ /* length = 1 */
+ /* [35] */
+ 0x1E,
+
+ 0x4C, 0x10,
+ /* ID = 0x0C (Cell allocation bitmap) ####### */
+ /* length = 16 */
+ /* [38] */
+ 0x8F, 0xB1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+ 0x59, 0x01,
+ /* ID = 0x19 (Averaging period) */
+ /* length = 1 */
+ /* [56] */
+ 0x01,
+
+ 0x5E, 0x01,
+ /* ID = 0x1E ((RF max power reduction)) */
+ /* length = 1 */
+ /* [59] */
+ 0x00,
+
+ 0x7F, 0x46, 0x11,
+ /* ID = 0x46 (FU channel configuration) ## constructed ## */
+ /* length = 17 */
+ /* [63] */
+
+ 0x5F, 0x40, 0x04,
+ /* ID = 0x40 (Object identity) */
+ /* length = 4 */
+ /* [66] */
+ 0x00, 0x07, 0x01, 0xFF,
+
+ 0x45, 0x08,
+ /* ID = 0x05 (Channel configuration per TS) */
+ /* length = 8 */
+ /* [72] */
+ 0x01, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+
+ 0x7F, 0x65, 0x0B,
+ /* ID = 0x65 (Obj. identity and obj. state) ## constructed ## */
+ /* length = 11 */
+ /* [83] */
+
+ 0x5F, 0x40, 0x04,
+ /* ID = 0x40 (Object identity) */
+ /* length = 4 */
+ /* [86] */
+ 0x00, 0x04, 0x01, 0xFF,
+
+ 0x5F, 0x44, 0x01,
+ /* ID = 0x44 (Object current state) */
+ /* length = 1 */
+ /* [93] */
+ 0x03,
+
+ 0x7F, 0x7C, 0x0A,
+ /* ID = 0x7C (FU BSIC) ## constructed ## */
+ /* length = 10 */
+ /* [97] */
+
+ 0x5F, 0x40, 0x04,
+ /* ID = 0x40 (Object identity) */
+ /* length = 4 */
+ /* [100] */
+ 0x00, 0x07, 0x01, 0xFF,
+
+ 0x46, 0x01,
+ /* ID = 0x06 (BSIC) */
+ /* length = 1 */
+ /* [106] */
+ 0x00,
+
+ 0x7F, 0x48, 0x0B,
+ /* ID = 0x48 (ARFN of a CU) ## constructed ## */
+ /* length = 11 */
+ /* [110] */
+
+ 0x5F, 0x40, 0x04,
+ /* ID = 0x40 (Object identity) */
+ /* length = 4 */
+ /* [113] */
+ 0x00, 0x08, 0x01, 0xFF,
+
+ 0x4A, 0x02,
+ /* ID = 0x0A (ARFN) ####### */
+ /* length = 2 */
+ /* [119] */
+ 0x03, 0x62,
+
+ 0x7F, 0x49, 0x59,
+ /* ID = 0x49 (FU radio definition) ## constructed ## */
+ /* length = 89 */
+ /* [124] */
+
+ 0x5F, 0x40, 0x04,
+ /* ID = 0x40 (Object identity) */
+ /* length = 4 */
+ /* [127] */
+ 0x00, 0x07, 0x01, 0xFF,
+
+ 0x4D, 0x50,
+ /* ID = 0x0D (Radio definition per TS) ####### */
+ /* length = 80 */
+ /* [133] */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* MA */
+ 0x03, 0x62, /* HSN, MAIO or ARFCN if no hopping */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x62,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x62,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x62,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x62,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x62,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x62,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x62,
+};
+
+/* TODO: put in a separate file ? */
+
+/*
+ build the configuration for each TRX
+*/
+
+static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id,
+ uint8_t * fu_config, int *hopping)
+{
+ int i;
+
+ *hopping = 0;
+
+ memcpy(fu_config, fu_config_template, sizeof(fu_config_template));
+
+ /* set ID */
+
+ fu_config[6 + 2] = id;
+ fu_config[66 + 2] = id;
+ fu_config[86 + 2] = id;
+ fu_config[100 + 2] = id;
+ fu_config[113 + 2] = id;
+ fu_config[127 + 2] = id;
+
+ /* set ARFCN */
+
+ uint16_t arfcn = trx->arfcn;
+
+ fu_config[119] = arfcn >> 8;
+ fu_config[119 + 1] = arfcn & 0xFF;
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+
+ if (ts->hopping.enabled) {
+ /* reverse order */
+ int j;
+ for (j = 0; j < ts->hopping.ma_len; j++)
+ fu_config[133 + (i * 10) + (7 - j)] =
+ ts->hopping.ma_data[j];
+ fu_config[133 + 8 + (i * 10)] = ts->hopping.hsn;
+ fu_config[133 + 8 + 1 + (i * 10)] = ts->hopping.maio;
+ *hopping = 1;
+ } else {
+ fu_config[133 + 8 + (i * 10)] = arfcn >> 8;
+ fu_config[133 + 8 + 1 + (i * 10)] = arfcn & 0xFF;
+ }
+ }
+
+ /* set BSIC */
+
+ /*
+ Attention: all TRX except the first one seem to get the TSC
+ from the CHANNEL ACTIVATION command (in CHANNEL IDENTIFICATION,
+ GSM 04.08 CHANNEL DESCRIPTION).
+ There was a bug in rsl_chan_activate_lchan() setting this parameter.
+ */
+
+ uint8_t bsic = trx->bts->bsic;
+
+ fu_config[106] = bsic;
+
+ /* set CA */
+
+ if (generate_cell_chan_list(&fu_config[38], trx->bts) != 0) {
+ fprintf(stderr, "generate_cell_chan_list failed\n");
+ return 0;
+ }
+
+ /* set channel configuration */
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ uint8_t chan_config;
+
+ /*
+ 0 = FCCH + SCH + BCCH + CCCH
+ 1 = FCCH + SCH + BCCH + CCCH + SDCCH/4 + SACCH/4
+ 2 = BCCH + CCCH (This combination is not used in any BTS)
+ 3 = FCCH + SCH + BCCH + CCCH + SDCCH/4 with SDCCH2 used as CBCH
+ 4 = SDCCH/8 + SACCH/8
+ 5 = SDCCH/8 with SDCCH2 used as CBCH
+ 6 = TCH/F + FACCH/F + SACCH/F
+ 7 = E-RACH (Talk family)
+ 9 = Dual rate (capability for TCH/F and TCH/H)
+ 10 = reserved for BTS internal use
+ 11 = PBCCH + PCCCH + PDTCH + PACCH + PTCCH (can be used in GPRS release 2).
+ 0xFF = spare TS
+ */
+
+ if (ts->pchan == GSM_PCHAN_NONE)
+ chan_config = 0xFF;
+ else if (ts->pchan == GSM_PCHAN_CCCH)
+ chan_config = 0;
+ else if (ts->pchan == GSM_PCHAN_CCCH_SDCCH4)
+ chan_config = 1;
+ else if (ts->pchan == GSM_PCHAN_TCH_F)
+ chan_config = 6; /* 9 should work too */
+ else if (ts->pchan == GSM_PCHAN_TCH_H)
+ chan_config = 9;
+ else if (ts->pchan == GSM_PCHAN_SDCCH8_SACCH8C)
+ chan_config = 4;
+ else if (ts->pchan == GSM_PCHAN_PDCH)
+ chan_config = 11;
+ else {
+ fprintf(stderr,
+ "unsupported channel config %d for timeslot %d\n",
+ ts->pchan, i);
+ return 0;
+ }
+
+ fu_config[72 + i] = chan_config;
+ }
+ return sizeof(fu_config_template);
+}
+
+/* TODO: put in a separate file ? */
+
+static uint8_t bts_config_1[] = {
+ 0x4E, 0x02,
+ /* ID = 0x0E (Frame number) */
+ /* length = 2 */
+ /* [2] */
+ 0xFF, 0xFF,
+
+ 0x5F, 0x4E, 0x02,
+ /* ID = 0x4E (RX antenna supervision period) */
+ /* length = 2 */
+ /* [7] */
+ 0xFF, 0xFF,
+
+ 0x5F, 0x50, 0x02,
+ /* ID = 0x50 (Sector configuration) */
+ /* length = 2 */
+ /* [12] */
+ 0x01, 0x01,
+};
+
+static uint8_t bts_config_2[] = {
+ 0x55, 0x02,
+ /* ID = 0x15 (Hopping mode) */
+ /* length = 2 */
+ /* [2] */
+ 0x01, 0x00,
+
+ 0x5F, 0x75, 0x02,
+ /* ID = 0x75 (RX diversity selection) */
+ /* length = 2 */
+ /* [7] */
+ 0x01, 0x01,
+};
+
+static uint8_t bts_config_3[] = {
+ 0x5F, 0x20, 0x02,
+ /* ID = 0x20 (Extended cell radius) */
+ /* length = 2 */
+ /* [3] */
+ 0x01, 0x00,
+};
+
+static uint8_t bts_config_4[] = {
+ 0x5F, 0x74, 0x09,
+ /* ID = 0x74 (Real Time) */
+ /* length = 9 */
+ /* [3] year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */
+ 0x07, 0xDB, 0x06, 0x02, 0x0B, 0x20, 0x0C, 0x00,
+ 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [15] */
+ 0x01, 0x01, 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [21] */
+ 0x02, 0x01, 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [27] */
+ 0x03, 0x01, 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [33] */
+ 0x04, 0x01, 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [39] */
+ 0x05, 0x01, 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [45] */
+ 0x06, 0x01, 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [51] */
+ 0x07, 0x01, 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [57] */
+ 0x08, 0x01, 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [63] */
+ 0x09, 0x01, 0x00,
+
+ 0x5F, 0x76, 0x03,
+ /* ID = 0x76 (EAC input config) */
+ /* length = 3 */
+ /* [69] */
+ 0x0A, 0x01, 0x00,
+};
+
+static uint8_t bts_config_insite[] = {
+ 0x4E, 0x02,
+ /* ID = 0x0E (Frame number) */
+ /* length = 2 */
+ /* [2] */
+ 0xFF, 0xFF,
+
+ 0x5F, 0x4E, 0x02,
+ /* ID = 0x4E (RX antenna supervision period) */
+ /* length = 2 */
+ /* [7] */
+ 0xFF, 0xFF,
+
+ 0x5F, 0x50, 0x02,
+ /* ID = 0x50 (Sector configuration) */
+ /* length = 2 */
+ /* [12] */
+ 0x01, 0x01,
+
+ 0x55, 0x02,
+ /* ID = 0x15 (Hopping mode) */
+ /* length = 2 */
+ /* [16] */
+ 0x01, 0x00,
+
+ 0x5F, 0x20, 0x02,
+ /* ID = 0x20 (Extended cell radius) */
+ /* length = 2 */
+ /* [21] */
+ 0x01, 0x00,
+
+ 0x5F, 0x74, 0x09,
+ /* ID = 0x74 (Real Time) */
+ /* length = 9 */
+ /* [26] */
+ 0x07, 0xDB, 0x07, 0x0A, 0x0F, 0x09, 0x0B, 0x00,
+ 0x00,
+};
+
+void set_real_time(uint8_t * real_time)
+{
+ time_t t;
+ struct tm *tm;
+
+ t = time(NULL);
+ tm = localtime(&t);
+
+ /* year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */
+
+ real_time[0] = (1900 + tm->tm_year) >> 8;
+ real_time[1] = (1900 + tm->tm_year) & 0xFF;
+ real_time[2] = tm->tm_mon + 1;
+ real_time[3] = tm->tm_mday;
+ real_time[4] = tm->tm_hour;
+ real_time[5] = tm->tm_min;
+ real_time[6] = tm->tm_sec;
+ real_time[7] = 0;
+ real_time[8] = 0;
+}
+
+/* TODO: put in a separate file ? */
+
+/*
+ build the configuration data
+*/
+
+static int make_bts_config(uint8_t bts_type, int n_trx, uint8_t * fu_config,
+ int need_hopping)
+{
+ /* is it an InSite BTS ? */
+ if (bts_type == 0x0E || bts_type == 0x0F || bts_type == 0x10) { /* TODO */
+ if (n_trx != 1) {
+ fprintf(stderr, "InSite has only one TRX\n");
+ return 0;
+ }
+ if (need_hopping != 0) {
+ fprintf(stderr, "InSite does not support hopping\n");
+ return 0;
+ }
+ memcpy(fu_config, bts_config_insite, sizeof(bts_config_insite));
+ set_real_time(&fu_config[26]);
+ return sizeof(bts_config_insite);
+ }
+
+ int len = 0;
+ int i;
+
+ memcpy(fu_config + len, bts_config_1, sizeof(bts_config_1));
+
+ /* set sector configuration */
+ fu_config[len + 12 - 1] = 1 + n_trx; /* len */
+ for (i = 0; i < n_trx; i++)
+ fu_config[len + 12 + 1 + i] = ((i + 1) & 0xFF);
+
+ len += (sizeof(bts_config_1) + (n_trx - 1));
+
+ memcpy(fu_config + len, bts_config_2, sizeof(bts_config_2));
+ /* set hopping mode (Baseband and RF hopping work for the MetroSite) */
+ if (need_hopping)
+ fu_config[len + 2 + 1] = 1; /* 0: no hopping, 1: Baseband hopping, 2: RF hopping */
+ len += sizeof(bts_config_2);
+
+ /* set extended cell radius for each TRX */
+ for (i = 0; i < n_trx; i++) {
+ memcpy(fu_config + len, bts_config_3, sizeof(bts_config_3));
+ fu_config[len + 3] = ((i + 1) & 0xFF);
+ len += sizeof(bts_config_3);
+ }
+
+ memcpy(fu_config + len, bts_config_4, sizeof(bts_config_4));
+ set_real_time(&fu_config[len + 3]);
+ len += sizeof(bts_config_4);
+
+ return len;
+}
+
+/* TODO: put in a separate file ? */
+
+static struct msgb *nm_msgb_alloc(void)
+{
+ return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, "OML");
+}
+
+/* TODO: put in a separate file ? */
+
+struct abis_om_nokia_hdr {
+ uint8_t msg_type;
+ uint8_t spare;
+ uint16_t reference;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+#define ABIS_OM_NOKIA_HDR_SIZE (sizeof(struct abis_om_hdr) + sizeof(struct abis_om_nokia_hdr))
+
+static int abis_nm_send(struct gsm_bts *bts, uint8_t msg_type, uint16_t ref,
+ uint8_t * data, int len_data)
+{
+ struct abis_om_hdr *oh;
+ struct abis_om_nokia_hdr *noh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ oh = (struct abis_om_hdr *)msgb_put(msg,
+ ABIS_OM_NOKIA_HDR_SIZE + len_data);
+
+ oh->mdisc = ABIS_OM_MDISC_FOM;
+ oh->placement = ABIS_OM_PLACEMENT_ONLY;
+ oh->sequence = 0;
+ oh->length = sizeof(struct abis_om_nokia_hdr) + len_data;
+
+ noh = (struct abis_om_nokia_hdr *)oh->data;
+
+ noh->msg_type = msg_type;
+ noh->spare = 0;
+ noh->reference = htons(ref);
+ memcpy(noh->data, data, len_data);
+
+ DEBUGPC(DNM, "Sending %s\n", get_msg_type_name_string(msg_type));
+
+ return abis_nm_sendmsg(bts, msg);
+}
+
+/* TODO: put in a separate file ? */
+
+static uint8_t download_req[] = {
+ 0x5F, 0x25, 0x0B,
+ /* ID = 0x25 (File identity) */
+ /* length = 11 */
+ /* [3] */
+ 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
+ 0x2A, 0x2A, 0x2A,
+
+ 0x5F, 0x78, 0x03,
+ /* ID = 0x78 (File version) */
+ /* length = 3 */
+ /* [17] */
+ 0x2A, 0x2A, 0x2A,
+
+ 0x5F, 0x81, 0x0A, 0x01,
+ /* ID = 0x8A (SW load mode) */
+ /* length = 1 */
+ /* [24] */
+ 0x01,
+
+ 0x5F, 0x81, 0x06, 0x01,
+ /* ID = 0x86 (Acknowledgement period) */
+ /* length = 1 */
+ /* [29] */
+ 0x01,
+};
+
+static int abis_nm_download_req(struct gsm_bts *bts, uint16_t ref)
+{
+ uint8_t *data = download_req;
+ int len_data = sizeof(download_req);
+
+ return abis_nm_send(bts, NOKIA_MSG_START_DOWNLOAD_REQ, ref, data,
+ len_data);
+}
+
+/* TODO: put in a separate file ? */
+
+static uint8_t ack[] = {
+ 0x5F, 0x23, 0x01,
+ /* ID = 0x23 (Ack-Nack) */
+ /* length = 1 */
+ /* [3] */
+ 0x01,
+};
+
+static int abis_nm_ack(struct gsm_bts *bts, uint16_t ref)
+{
+ uint8_t *data = ack;
+ int len_data = sizeof(ack);
+
+ return abis_nm_send(bts, NOKIA_MSG_ACK, ref, data, len_data);
+}
+
+/* TODO: put in a separate file ? */
+
+static uint8_t reset[] = {
+ 0x5F, 0x40, 0x04,
+ /* ID = 0x40 (Object identity) */
+ /* length = 4 */
+ /* [3] */
+ 0x00, 0x01, 0xFF, 0xFF,
+};
+
+static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref)
+{
+ uint8_t *data = reset;
+ int len_data = sizeof(reset);
+ LOGP(DLINP, LOGL_INFO, "Nokia BTS reset timer: %d\n", bts->nokia.bts_reset_timer_cnf);
+ return abis_nm_send(bts, NOKIA_MSG_RESET_REQ, ref, data, len_data);
+}
+
+/* TODO: put in a separate file ? */
+
+static int abis_nm_send_multi_segments(struct gsm_bts *bts, uint8_t msg_type,
+ uint16_t ref, uint8_t * data, int len)
+{
+ int len_remain, len_to_send, max_send;
+ int seq = 0;
+ int ret;
+
+ len_remain = len;
+
+ while (len_remain) {
+ struct abis_om_hdr *oh;
+ struct abis_om_nokia_hdr *noh;
+ struct msgb *msg = nm_msgb_alloc();
+
+ if (seq == 0)
+ max_send = 256 - sizeof(struct abis_om_nokia_hdr);
+ else
+ max_send = 256;
+
+ if (len_remain > max_send) {
+ len_to_send = max_send;
+
+ if (seq == 0) {
+ /* first segment */
+ oh = (struct abis_om_hdr *)msgb_put(msg,
+ ABIS_OM_NOKIA_HDR_SIZE
+ +
+ len_to_send);
+
+ oh->mdisc = ABIS_OM_MDISC_FOM;
+ oh->placement = ABIS_OM_PLACEMENT_FIRST; /* first segment of multi-segment message */
+ oh->sequence = seq;
+ oh->length = 0; /* 256 bytes */
+
+ noh = (struct abis_om_nokia_hdr *)oh->data;
+
+ noh->msg_type = msg_type;
+ noh->spare = 0;
+ noh->reference = htons(ref);
+ memcpy(noh->data, data, len_to_send);
+ } else {
+ /* segment in between */
+ oh = (struct abis_om_hdr *)msgb_put(msg,
+ sizeof
+ (struct
+ abis_om_hdr)
+ +
+ len_to_send);
+
+ oh->mdisc = ABIS_OM_MDISC_FOM;
+ oh->placement = ABIS_OM_PLACEMENT_MIDDLE; /* segment of multi-segment message */
+ oh->sequence = seq;
+ oh->length = 0; /* 256 bytes */
+
+ memcpy(oh->data, data, len_to_send);
+ }
+ } else {
+
+ len_to_send = len_remain;
+
+ /* check if message fits in a single segment */
+
+ if (seq == 0)
+ return abis_nm_send(bts, msg_type, ref, data,
+ len_to_send);
+
+ /* last segment */
+
+ oh = (struct abis_om_hdr *)msgb_put(msg,
+ sizeof(struct
+ abis_om_hdr)
+ + len_to_send);
+
+ oh->mdisc = ABIS_OM_MDISC_FOM;
+ oh->placement = ABIS_OM_PLACEMENT_LAST; /* last segment of multi-segment message */
+ oh->sequence = seq;
+ oh->length = len_to_send;
+
+ memcpy(oh->data, data, len_to_send);
+ }
+
+ DEBUGPC(DNM, "Sending multi-segment %d\n", seq);
+
+ ret = abis_nm_sendmsg(bts, msg);
+ if (ret < 0)
+ return ret;
+
+ nokia_abis_nm_queue_send_next(bts);
+
+ /* next segment */
+ len_remain -= len_to_send;
+ data += len_to_send;
+ seq++;
+ }
+ return ret;
+}
+
+/* TODO: put in a separate file ? */
+
+static int abis_nm_send_config(struct gsm_bts *bts, uint8_t bts_type)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t config[2048]; /* TODO: might be too small if lots of TRX are used */
+ int len = 0;
+ int idx = 0;
+ int ret;
+ int hopping = 0;
+ int need_hopping = 0;
+
+ memset(config, 0, sizeof(config));
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+#if 0 /* debugging */
+ printf("TRX\n");
+ printf(" arfcn: %d\n", trx->arfcn);
+ printf(" bsic: %d\n", trx->bts->bsic);
+ uint8_t ca[20];
+ memset(ca, 0xFF, sizeof(ca));
+ ret = generate_cell_chan_list(ca, trx->bts);
+ printf(" ca (%d): %s\n", ret, osmo_hexdump(ca, sizeof(ca)));
+ int i;
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+
+ printf(" pchan %d: %d\n", i, ts->pchan);
+ }
+#endif
+ ret = make_fu_config(trx, idx + 1, config + len, &hopping);
+ need_hopping |= hopping;
+ len += ret;
+
+ idx++;
+ }
+
+ ret = make_bts_config(bts_type, idx, config + len, need_hopping);
+ len += ret;
+
+#if 0 /* debugging */
+ dump_elements(config, len);
+#endif
+
+ return abis_nm_send_multi_segments(bts, NOKIA_MSG_CONF_DATA, 1, config,
+ len);
+}
+
+#define GET_NEXT_BYTE if(idx >= len) return 0; \
+ ub = data[idx++];
+
+static int find_element(uint8_t * data, int len, uint16_t id, uint8_t * value,
+ int max_value)
+{
+ uint8_t ub;
+ int idx = 0;
+ int found = 0;
+ int constructed __attribute__((unused));
+ uint16_t id_value;
+
+ for (;;) {
+
+ GET_NEXT_BYTE;
+
+ /* encoding bit, construced means that other elements are contained */
+ constructed = ((ub & 0x20) ? 1 : 0);
+
+ if ((ub & 0x1F) == 0x1F) {
+ /* fixed pattern, ID follows */
+ GET_NEXT_BYTE; /* ID */
+ id_value = ub & 0x7F;
+ if (ub & 0x80) {
+ /* extension bit */
+ GET_NEXT_BYTE; /* ID low part */
+ id_value = (id_value << 7) | (ub & 0x7F);
+ }
+ if (id_value == id)
+ found = 1;
+ } else {
+ id_value = (ub & 0x3F);
+ if (id_value == id)
+ found = 1;
+ }
+
+ GET_NEXT_BYTE; /* length */
+
+ if (found) {
+ /* get data */
+ uint8_t n = ub;
+ uint8_t i;
+ for (i = 0; i < n; i++) {
+ GET_NEXT_BYTE;
+ if (max_value <= 0)
+ return -1; /* buffer too small */
+ *value = ub;
+ value++;
+ max_value--;
+ }
+ return n; /* length */
+ } else {
+ /* skip data */
+ uint8_t n = ub;
+ uint8_t i;
+ for (i = 0; i < n; i++) {
+ GET_NEXT_BYTE;
+ }
+ }
+ }
+ return 0; /* not found */
+}
+
+static int dump_elements(uint8_t * data, int len)
+{
+ uint8_t ub;
+ int idx = 0;
+ int constructed;
+ uint16_t id_value;
+ static char indent[100] = ""; /* TODO: move static to BTS context */
+
+ for (;;) {
+
+ GET_NEXT_BYTE;
+
+ /* encoding bit, construced means that other elements are contained */
+ constructed = ((ub & 0x20) ? 1 : 0);
+
+ if ((ub & 0x1F) == 0x1F) {
+ /* fixed pattern, ID follows */
+ GET_NEXT_BYTE; /* ID */
+ id_value = ub & 0x7F;
+ if (ub & 0x80) {
+ /* extension bit */
+ GET_NEXT_BYTE; /* ID low part */
+ id_value = (id_value << 7) | (ub & 0x7F);
+ }
+
+ } else {
+ id_value = (ub & 0x3F);
+ }
+
+ GET_NEXT_BYTE; /* length */
+
+ printf("%s--ID = 0x%02X (%s) %s\n", indent, id_value,
+ get_element_name_string(id_value),
+ constructed ? "** constructed **" : "");
+ printf("%s length = %d\n", indent, ub);
+ printf("%s %s\n", indent, osmo_hexdump(data + idx, ub));
+
+ if (constructed) {
+ int indent_len = strlen(indent);
+ strcat(indent, " ");
+
+ dump_elements(data + idx, ub);
+
+ indent[indent_len] = 0;
+ }
+ /* skip data */
+ uint8_t n = ub;
+ uint8_t i;
+ for (i = 0; i < n; i++) {
+ GET_NEXT_BYTE;
+ }
+ }
+ return 0;
+}
+
+/* TODO: put in a separate file ? */
+
+/* taken from abis_nm.c */
+
+static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts)
+{
+ int wait = 0;
+ struct msgb *msg;
+ /* the queue is empty */
+ while (!llist_empty(&bts->abis_queue)) {
+ msg = msgb_dequeue(&bts->abis_queue);
+ wait = OBSC_NM_W_ACK_CB(msg);
+ abis_sendmsg(msg);
+
+ if (wait)
+ break;
+ }
+
+ bts->abis_nm_pend = wait;
+}
+
+/* TODO: put in a separate file ? */
+
+/* timer for restarting OML after BTS reset */
+
+static void reset_timer_cb(void *_bts)
+{
+ struct gsm_bts *bts = _bts;
+ struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
+ struct e1inp_line *line;
+
+ bts->nokia.wait_reset = 0;
+
+ /* OML link */
+ line = e1inp_line_find(e1_link->e1_nr);
+ if (!line) {
+ LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to "
+ "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr);
+ return;
+ }
+
+ start_sabm_in_line(line, 0, -1); /* stop all first */
+ start_sabm_in_line(line, 1, SAPI_OML); /* start only OML */
+}
+
+/* TODO: put in a separate file ? */
+
+/*
+ This is how the configuration is done:
+ - start OML link
+ - reset BTS
+ - receive ACK, wait some time and restart OML link
+ - receive OMU STARTED message, send START DOWNLOAD REQ
+ - receive CNF REQ message, send CONF DATA
+ - receive ACK, start RSL link(s)
+ ACK some other messages received from the BTS.
+
+ Probably its also possible to configure the BTS without a reset, this
+ has not been tested yet.
+*/
+
+static int abis_nm_rcvmsg_fom(struct msgb *mb)
+{
+ struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)mb->dst;
+ struct gsm_bts *bts = sign_link->trx->bts;
+ struct abis_om_hdr *oh = msgb_l2(mb);
+ struct abis_om_nokia_hdr *noh = msgb_l3(mb);
+ uint8_t mt = noh->msg_type;
+ int ret = 0;
+ uint16_t ref = ntohs(noh->reference);
+ uint8_t info[256];
+ uint8_t ack = 0xFF;
+ uint8_t severity = 0xFF;
+ int str_len;
+ int len_data;
+
+ if (bts->nokia.wait_reset) {
+ LOGP(DNM, LOGL_INFO,
+ "Ignore message while waiting for reset\n");
+ return ret;
+ }
+
+ if (oh->length < sizeof(struct abis_om_nokia_hdr)) {
+ LOGP(DNM, LOGL_ERROR, "Message too short\n");
+ return -EINVAL;
+ }
+
+ len_data = oh->length - sizeof(struct abis_om_nokia_hdr);
+ LOGP(DNM, LOGL_INFO, "(0x%02X) %s\n", mt, get_msg_type_name_string(mt));
+#if 0 /* debugging */
+ dump_elements(noh->data, len_data);
+#endif
+
+ switch (mt) {
+ case NOKIA_MSG_OMU_STARTED:
+ if (find_element(noh->data, len_data,
+ NOKIA_EI_BTS_TYPE, &bts->nokia.bts_type,
+ sizeof(uint8_t)) == sizeof(uint8_t))
+ LOGP(DNM, LOGL_INFO, "BTS type = %d (%s)\n",
+ bts->nokia.bts_type,
+ get_bts_type_string(bts->nokia.bts_type));
+ else
+ LOGP(DNM, LOGL_ERROR, "BTS type not found\n");
+ /* send START_DOWNLOAD_REQ */
+ abis_nm_download_req(bts, ref);
+ break;
+ case NOKIA_MSG_MF_REQ:
+ break;
+ case NOKIA_MSG_CONF_REQ:
+ /* send ACK */
+ abis_nm_ack(bts, ref);
+ nokia_abis_nm_queue_send_next(bts);
+ /* send CONF_DATA */
+ abis_nm_send_config(bts, bts->nokia.bts_type);
+ bts->nokia.configured = 1;
+ break;
+ case NOKIA_MSG_ACK:
+ if (find_element
+ (noh->data, len_data, NOKIA_EI_ACK, &ack,
+ sizeof(uint8_t)) == sizeof(uint8_t)) {
+ LOGP(DNM, LOGL_INFO, "ACK = %d\n", ack);
+ if (ack != 1) {
+ LOGP(DNM, LOGL_ERROR, "No ACK received (%d)\n",
+ ack);
+ /* TODO: properly handle failures (NACK) */
+ }
+ } else
+ LOGP(DNM, LOGL_ERROR, "ACK not found\n");
+
+ /* TODO: the assumption for the following is that no NACK was received */
+
+ /* ACK for reset message ? */
+ if (!bts->nokia.did_reset) {
+ bts->nokia.did_reset = 1;
+
+ /*
+ TODO: For the InSite processing the received data is
+ blocked in the driver during reset.
+ Otherwise the LAPD module might assert because the InSite
+ sends garbage on the E1 line during reset.
+ This is done by looking at "wait_reset" in the driver
+ (function handle_ts1_read()) and ignoring the received data.
+ It seems to be necessary for the MetroSite too.
+ */
+ bts->nokia.wait_reset = 1;
+
+ osmo_timer_setup(&bts->nokia.reset_timer,
+ reset_timer_cb, bts);
+ osmo_timer_schedule(&bts->nokia.reset_timer, bts->nokia.bts_reset_timer_cnf, 0);
+
+ struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
+ struct e1inp_line *line;
+ /* OML link */
+ line = e1inp_line_find(e1_link->e1_nr);
+ if (!line) {
+ LOGP(DLINP, LOGL_ERROR,
+ "BTS %u OML link referring to "
+ "non-existing E1 line %u\n", bts->nr,
+ e1_link->e1_nr);
+ return -ENOMEM;
+ }
+
+ start_sabm_in_line(line, 0, -1); /* stop all first */
+ }
+
+ /* ACK for CONF DATA message ? */
+ if (bts->nokia.configured != 0) {
+ /* start TRX (RSL link) */
+
+ struct gsm_e1_subslot *e1_link =
+ &sign_link->trx->rsl_e1_link;
+ struct e1inp_line *line;
+
+ bts->nokia.configured = 0;
+
+ /* RSL Link */
+ line = e1inp_line_find(e1_link->e1_nr);
+ if (!line) {
+ LOGP(DLINP, LOGL_ERROR,
+ "TRX (%u/%u) RSL link referring "
+ "to non-existing E1 line %u\n",
+ sign_link->trx->bts->nr, sign_link->trx->nr,
+ e1_link->e1_nr);
+ return -ENOMEM;
+ }
+ /* start TRX */
+ start_sabm_in_line(line, 1, SAPI_RSL); /* start only RSL */
+ }
+ break;
+ case NOKIA_MSG_STATE_CHANGED:
+ /* send ACK */
+ abis_nm_ack(bts, ref);
+ break;
+ case NOKIA_MSG_CONF_COMPLETE:
+ /* send ACK */
+ abis_nm_ack(bts, ref);
+ break;
+ case NOKIA_MSG_BLOCK_CTRL_REQ: /* seems to be send when something goes wrong !? */
+ /* send ACK (do we have to send an ACK ?) */
+ abis_nm_ack(bts, ref);
+ break;
+ case NOKIA_MSG_ALARM:
+ find_element(noh->data, len_data, NOKIA_EI_SEVERITY, &severity,
+ sizeof(severity));
+ /* TODO: there might be alarms with both elements set */
+ str_len =
+ find_element(noh->data, len_data, NOKIA_EI_ADD_INFO, info,
+ sizeof(info));
+ if (str_len > 0) {
+ info[str_len] = 0;
+ LOGP(DNM, LOGL_INFO, "ALARM Severity %s (%d) : %s\n",
+ get_severity_string(severity), severity, info);
+ } else { /* nothing found, try details */
+ str_len =
+ find_element(noh->data, len_data,
+ NOKIA_EI_ALARM_DETAIL, info,
+ sizeof(info));
+ if (str_len > 0) {
+ uint16_t code;
+ info[str_len] = 0;
+ code = (info[0] << 8) + info[1];
+ LOGP(DNM, LOGL_INFO,
+ "ALARM Severity %s (%d), code 0x%X : %s\n",
+ get_severity_string(severity), severity,
+ code, info + 2);
+ }
+ }
+ /* send ACK */
+ abis_nm_ack(bts, ref);
+ break;
+ }
+
+ nokia_abis_nm_queue_send_next(bts);
+
+ return ret;
+}
+
+/* TODO: put in a separate file ? */
+
+int abis_nokia_rcvmsg(struct msgb *msg)
+{
+ struct abis_om_hdr *oh = msgb_l2(msg);
+ int rc = 0;
+
+ /* Various consistency checks */
+ if (oh->placement != ABIS_OM_PLACEMENT_ONLY) {
+ LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n",
+ oh->placement);
+ 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);
+ return -EINVAL;
+ }
+ msg->l3h = (unsigned char *)oh + sizeof(*oh);
+
+ switch (oh->mdisc) {
+ case ABIS_OM_MDISC_FOM:
+ LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_FOM\n");
+ rc = abis_nm_rcvmsg_fom(msg);
+ break;
+ case ABIS_OM_MDISC_MANUF:
+ LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_MANUF\n");
+ break;
+ case ABIS_OM_MDISC_MMI:
+ case ABIS_OM_MDISC_TRAU:
+ LOGP(DNM, LOGL_ERROR,
+ "unimplemented ABIS OML message discriminator 0x%x\n",
+ oh->mdisc);
+ break;
+ default:
+ LOGP(DNM, LOGL_ERROR,
+ "unknown ABIS OML message discriminator 0x%x\n",
+ oh->mdisc);
+ return -EINVAL;
+ }
+
+ msgb_free(msg);
+ return rc;
+}
+
+static int bts_model_nokia_site_start(struct gsm_network *net);
+
+static void bts_model_nokia_site_e1line_bind_ops(struct e1inp_line *line)
+{
+ e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops);
+}
+
+static struct gsm_bts_model model_nokia_site = {
+ .type = GSM_BTS_TYPE_NOKIA_SITE,
+ .name = "nokia_site",
+ .start = bts_model_nokia_site_start,
+ .oml_rcvmsg = &abis_nokia_rcvmsg,
+ .e1line_bind_ops = &bts_model_nokia_site_e1line_bind_ops,
+};
+
+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);
+
+ gsm_btsmodel_set_feature(&model_nokia_site, BTS_FEAT_HOPPING);
+ gsm_btsmodel_set_feature(&model_nokia_site, BTS_FEAT_HSCSD);
+ gsm_btsmodel_set_feature(&model_nokia_site, 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);
+
+ my_net = net;
+
+ return 0;
+}
+
+int bts_model_nokia_site_init(void)
+{
+ return gsm_bts_model_register(&model_nokia_site);
+}
diff --git a/src/libbsc/bts_siemens_bs11.c b/src/libbsc/bts_siemens_bs11.c
new file mode 100644
index 000000000..c083b1e06
--- /dev/null
+++ b/src/libbsc/bts_siemens_bs11.c
@@ -0,0 +1,602 @@
+/* Siemens BS-11 specific code */
+
+/* (C) 2009-2010 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/gsm/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <openbsc/signal.h>
+
+static int bts_model_bs11_start(struct gsm_network *net);
+
+static void bts_model_bs11_e1line_bind_ops(struct e1inp_line *line)
+{
+ e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops);
+}
+
+static struct gsm_bts_model model_bs11 = {
+ .type = GSM_BTS_TYPE_BS11,
+ .name = "bs11",
+ .start = bts_model_bs11_start,
+ .oml_rcvmsg = &abis_nm_rcvmsg,
+ .e1line_bind_ops = bts_model_bs11_e1line_bind_ops,
+ .nm_att_tlvdef = {
+ .def = {
+ [NM_ATT_AVAIL_STATUS] = { TLV_TYPE_TLV },
+ /* BS11 specifics */
+ [NM_ATT_BS11_ESN_FW_CODE_NO] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_ESN_HW_CODE_NO] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_ESN_PCB_SERIAL] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_BOOT_SW_VERS] = { TLV_TYPE_TLV },
+ [0xd5] = { TLV_TYPE_TLV },
+ [0xa8] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_PASSWORD] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_TXPWR] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_RSSI_OFFS] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_LINE_CFG] = { TLV_TYPE_TV },
+ [NM_ATT_BS11_L1_PROT_TYPE] = { TLV_TYPE_TV },
+ [NM_ATT_BS11_BIT_ERR_THESH] = { TLV_TYPE_FIXED, 2 },
+ [NM_ATT_BS11_DIVERSITY] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV },
+ [NM_ATT_BS11_LMT_LOGIN_TIME] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV },
+ [NM_ATT_BS11_LMT_USER_NAME] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_BTS_STATE] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_E1_STATE] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_PLL_MODE] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_PLL] = { TLV_TYPE_TLV },
+ [NM_ATT_BS11_CCLK_ACCURACY] = { TLV_TYPE_TV },
+ [NM_ATT_BS11_CCLK_TYPE] = { TLV_TYPE_TV },
+ [0x95] = { TLV_TYPE_FIXED, 2 },
+ },
+ },
+};
+
+/* The following definitions are for OM and NM packets that we cannot yet
+ * generate by code but we just pass on */
+
+// BTS Site Manager, SET ATTRIBUTES
+
+/*
+ Object Class: BTS Site Manager
+ Instance 1: FF
+ Instance 2: FF
+ Instance 3: FF
+SET ATTRIBUTES
+ sAbisExternalTime: 2007/09/08 14:36:11
+ omLAPDRelTimer: 30sec
+ shortLAPDIntTimer: 5sec
+ emergencyTimer1: 10 minutes
+ emergencyTimer2: 0 minutes
+*/
+
+unsigned char msg_1[] =
+{
+ NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, 0xFF, 0xFF, 0xFF,
+ NM_ATT_BS11_ABIS_EXT_TIME, 0x07,
+ 0xD7, 0x09, 0x08, 0x0E, 0x24, 0x0B, 0xCE,
+ 0x02,
+ 0x00, 0x1E,
+ NM_ATT_BS11_SH_LAPD_INT_TIMER,
+ 0x01, 0x05,
+ 0x42, 0x02, 0x00, 0x0A,
+ 0x44, 0x02, 0x00, 0x00
+};
+
+// BTS, SET BTS ATTRIBUTES
+
+/*
+ Object Class: BTS
+ BTS relat. Number: 0
+ Instance 2: FF
+ Instance 3: FF
+SET BTS ATTRIBUTES
+ bsIdentityCode / BSIC:
+ PLMN_colour_code: 7h
+ BS_colour_code: 7h
+ BTS Air Timer T3105: 4 ,unit 10 ms
+ btsIsHopping: FALSE
+ periodCCCHLoadIndication: 1sec
+ thresholdCCCHLoadIndication: 0%
+ cellAllocationNumber: 00h = GSM 900
+ enableInterferenceClass: 00h = Disabled
+ fACCHQual: 6 (FACCH stealing flags minus 1)
+ intaveParameter: 31 SACCH multiframes
+ interferenceLevelBoundaries:
+ Interference Boundary 1: 0Ah
+ Interference Boundary 2: 0Fh
+ Interference Boundary 3: 14h
+ Interference Boundary 4: 19h
+ Interference Boundary 5: 1Eh
+ mSTxPwrMax: 11
+ GSM range: 2=39dBm, 15=13dBm, stepsize 2 dBm
+ DCS1800 range: 0=30dBm, 15=0dBm, stepsize 2 dBm
+ PCS1900 range: 0=30dBm, 15=0dBm, stepsize 2 dBm
+ 30=33dBm, 31=32dBm
+ ny1:
+ Maximum number of repetitions for PHYSICAL INFORMATION message (GSM 04.08): 20
+ powerOutputThresholds:
+ Out Power Fault Threshold: -10 dB
+ Red Out Power Threshold: - 6 dB
+ Excessive Out Power Threshold: 5 dB
+ rACHBusyThreshold: -127 dBm
+ rACHLoadAveragingSlots: 250 ,number of RACH burst periods
+ rfResourceIndicationPeriod: 125 SACCH multiframes
+ T200:
+ SDCCH: 044 in 5 ms
+ FACCH/Full rate: 031 in 5 ms
+ FACCH/Half rate: 041 in 5 ms
+ SACCH with TCH SAPI0: 090 in 10 ms
+ SACCH with SDCCH: 090 in 10 ms
+ SDCCH with SAPI3: 090 in 5 ms
+ SACCH with TCH SAPI3: 135 in 10 ms
+ tSync: 9000 units of 10 msec
+ tTrau: 9000 units of 10 msec
+ enableUmLoopTest: 00h = disabled
+ enableExcessiveDistance: 00h = Disabled
+ excessiveDistance: 64km
+ hoppingMode: 00h = baseband hopping
+ cellType: 00h = Standard Cell
+ BCCH ARFCN / bCCHFrequency: 1
+*/
+
+static unsigned char bs11_attr_bts[] =
+{
+ NM_ATT_BSIC, HARDCODED_BSIC,
+ NM_ATT_BTS_AIR_TIMER, 0x04,
+ NM_ATT_BS11_BTSLS_HOPPING, 0x00,
+ NM_ATT_CCCH_L_I_P, 0x01,
+ NM_ATT_CCCH_L_T, 0x00,
+ NM_ATT_BS11_CELL_ALLOC_NR, NM_BS11_CANR_GSM,
+ NM_ATT_BS11_ENA_INTERF_CLASS, 0x01,
+ NM_ATT_BS11_FACCH_QUAL, 0x06,
+ /* interference avg. period in numbers of SACCH multifr */
+ NM_ATT_INTAVE_PARAM, 0x1F,
+ NM_ATT_INTERF_BOUND, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x7B,
+ NM_ATT_CCCH_L_T, 0x23,
+ NM_ATT_GSM_TIME, 0x28, 0x00,
+ NM_ATT_ADM_STATE, 0x03,
+ NM_ATT_RACH_B_THRESH, 0x7F,
+ NM_ATT_LDAVG_SLOTS, 0x00, 0xFA,
+ NM_ATT_BS11_RF_RES_IND_PER, 0x7D,
+ NM_ATT_T200, 0x2C, 0x1F, 0x29, 0x5A, 0x5A, 0x5A, 0x87,
+ NM_ATT_BS11_TSYNC, 0x23, 0x28,
+ NM_ATT_BS11_TTRAU, 0x23, 0x28,
+ NM_ATT_TEST_DUR, 0x01, 0x00,
+ NM_ATT_OUTST_ALARM, 0x01, 0x00,
+ NM_ATT_BS11_EXCESSIVE_DISTANCE, 0x01, 0x40,
+ NM_ATT_BS11_HOPPING_MODE, 0x01, 0x00,
+ NM_ATT_BS11_PLL, 0x01, 0x00,
+ NM_ATT_BCCH_ARFCN, 0x00, HARDCODED_ARFCN/*0x01*/,
+};
+
+// Handover Recognition, SET ATTRIBUTES
+
+/*
+Illegal Contents GSM Formatted O&M Msg
+ Object Class: Handover Recognition
+ BTS relat. Number: 0
+ Instance 2: FF
+ Instance 3: FF
+SET ATTRIBUTES
+ enableDelayPowerBudgetHO: 00h = Disabled
+ enableDistanceHO: 00h = Disabled
+ enableInternalInterCellHandover: 00h = Disabled
+ enableInternalIntraCellHandover: 00h = Disabled
+ enablePowerBudgetHO: 00h = Disabled
+ enableRXLEVHO: 00h = Disabled
+ enableRXQUALHO: 00h = Disabled
+ hoAveragingDistance: 8 SACCH multiframes
+ hoAveragingLev:
+ A_LEV_HO: 8 SACCH multiframes
+ W_LEV_HO: 1 SACCH multiframes
+ hoAveragingPowerBudget: 16 SACCH multiframes
+ hoAveragingQual:
+ A_QUAL_HO: 8 SACCH multiframes
+ W_QUAL_HO: 2 SACCH multiframes
+ hoLowerThresholdLevDL: (10 - 110) dBm
+ hoLowerThresholdLevUL: (5 - 110) dBm
+ hoLowerThresholdQualDL: 06h = 6.4% < BER < 12.8%
+ hoLowerThresholdQualUL: 06h = 6.4% < BER < 12.8%
+ hoThresholdLevDLintra : (20 - 110) dBm
+ hoThresholdLevULintra: (20 - 110) dBm
+ hoThresholdMsRangeMax: 20 km
+ nCell: 06h
+ timerHORequest: 3 ,unit 2 SACCH multiframes
+*/
+
+unsigned char msg_3[] =
+{
+ NM_MT_BS11_SET_ATTR, NM_OC_BS11_HANDOVER, 0x00, 0xFF, 0xFF,
+ 0xD0, 0x00, /* enableDelayPowerBudgetHO */
+ 0x64, 0x00, /* enableDistanceHO */
+ 0x67, 0x00, /* enableInternalInterCellHandover */
+ 0x68, 0x00, /* enableInternalInterCellHandover */
+ 0x6A, 0x00, /* enablePowerBudgetHO */
+ 0x6C, 0x00, /* enableRXLEVHO */
+ 0x6D, 0x00, /* enableRXQUALHO */
+ 0x6F, 0x08, /* hoAveragingDistance */
+ 0x70, 0x08, 0x01, /* hoAveragingLev */
+ 0x71, 0x10, 0x10, 0x10,
+ 0x72, 0x08, 0x02, /* hoAveragingQual */
+ 0x73, 0x0A, /* hoLowerThresholdLevDL */
+ 0x74, 0x05, /* hoLowerThresholdLevUL */
+ 0x75, 0x06, /* hoLowerThresholdQualDL */
+ 0x76, 0x06, /* hoLowerThresholdQualUL */
+ 0x78, 0x14, /* hoThresholdLevDLintra */
+ 0x79, 0x14, /* hoThresholdLevULintra */
+ 0x7A, 0x14, /* hoThresholdMsRangeMax */
+ 0x7D, 0x06, /* nCell */
+ NM_ATT_BS11_TIMER_HO_REQUEST, 0x03,
+ 0x20, 0x01, 0x00,
+ 0x45, 0x01, 0x00,
+ 0x48, 0x01, 0x00,
+ 0x5A, 0x01, 0x00,
+ 0x5B, 0x01, 0x05,
+ 0x5E, 0x01, 0x1A,
+ 0x5F, 0x01, 0x20,
+ 0x9D, 0x01, 0x00,
+ 0x47, 0x01, 0x00,
+ 0x5C, 0x01, 0x64,
+ 0x5D, 0x01, 0x1E,
+ 0x97, 0x01, 0x20,
+ 0xF7, 0x01, 0x3C,
+};
+
+// Power Control, SET ATTRIBUTES
+
+/*
+ Object Class: Power Control
+ BTS relat. Number: 0
+ Instance 2: FF
+ Instance 3: FF
+SET ATTRIBUTES
+ enableMsPowerControl: 00h = Disabled
+ enablePowerControlRLFW: 00h = Disabled
+ pcAveragingLev:
+ A_LEV_PC: 4 SACCH multiframes
+ W_LEV_PC: 1 SACCH multiframes
+ pcAveragingQual:
+ A_QUAL_PC: 4 SACCH multiframes
+ W_QUAL_PC: 2 SACCH multiframes
+ pcLowerThresholdLevDL: 0Fh
+ pcLowerThresholdLevUL: 0Ah
+ pcLowerThresholdQualDL: 05h = 3.2% < BER < 6.4%
+ pcLowerThresholdQualUL: 05h = 3.2% < BER < 6.4%
+ pcRLFThreshold: 0Ch
+ pcUpperThresholdLevDL: 14h
+ pcUpperThresholdLevUL: 0Fh
+ pcUpperThresholdQualDL: 04h = 1.6% < BER < 3.2%
+ pcUpperThresholdQualUL: 04h = 1.6% < BER < 3.2%
+ powerConfirm: 2 ,unit 2 SACCH multiframes
+ powerControlInterval: 2 ,unit 2 SACCH multiframes
+ powerIncrStepSize: 02h = 4 dB
+ powerRedStepSize: 01h = 2 dB
+ radioLinkTimeoutBs: 64 SACCH multiframes
+ enableBSPowerControl: 00h = disabled
+*/
+
+unsigned char msg_4[] =
+{
+ NM_MT_BS11_SET_ATTR, NM_OC_BS11_PWR_CTRL, 0x00, 0xFF, 0xFF,
+ NM_ATT_BS11_ENA_MS_PWR_CTRL, 0x00,
+ NM_ATT_BS11_ENA_PWR_CTRL_RLFW, 0x00,
+ 0x7E, 0x04, 0x01, /* pcAveragingLev */
+ 0x7F, 0x04, 0x02, /* pcAveragingQual */
+ 0x80, 0x0F, /* pcLowerThresholdLevDL */
+ 0x81, 0x0A, /* pcLowerThresholdLevUL */
+ 0x82, 0x05, /* pcLowerThresholdQualDL */
+ 0x83, 0x05, /* pcLowerThresholdQualUL */
+ 0x84, 0x0C, /* pcRLFThreshold */
+ 0x85, 0x14, /* pcUpperThresholdLevDL */
+ 0x86, 0x0F, /* pcUpperThresholdLevUL */
+ 0x87, 0x04, /* pcUpperThresholdQualDL */
+ 0x88, 0x04, /* pcUpperThresholdQualUL */
+ 0x89, 0x02, /* powerConfirm */
+ 0x8A, 0x02, /* powerConfirmInterval */
+ 0x8B, 0x02, /* powerIncrStepSize */
+ 0x8C, 0x01, /* powerRedStepSize */
+ 0x8D, 0x40, /* radioLinkTimeoutBs */
+ 0x65, 0x01, 0x00 // set to 0x01 to enable BSPowerControl
+};
+
+
+// Transceiver, SET TRX ATTRIBUTES (TRX 0)
+
+/*
+ Object Class: Transceiver
+ BTS relat. Number: 0
+ Tranceiver number: 0
+ Instance 3: FF
+SET TRX ATTRIBUTES
+ aRFCNList (HEX): 0001
+ txPwrMaxReduction: 00h = 30dB
+ radioMeasGran: 254 SACCH multiframes
+ radioMeasRep: 01h = enabled
+ memberOfEmergencyConfig: 01h = TRUE
+ trxArea: 00h = TRX doesn't belong to a concentric cell
+*/
+
+static unsigned char bs11_attr_radio[] =
+{
+ NM_ATT_ARFCN_LIST, 0x01, 0x00, HARDCODED_ARFCN /*0x01*/,
+ NM_ATT_RF_MAXPOWR_R, 0x00,
+ NM_ATT_BS11_RADIO_MEAS_GRAN, 0x01, 0x05,
+ NM_ATT_BS11_RADIO_MEAS_REP, 0x01, 0x01,
+ NM_ATT_BS11_EMRG_CFG_MEMBER, 0x01, 0x01,
+ NM_ATT_BS11_TRX_AREA, 0x01, 0x00,
+};
+
+/*
+ * Patch the various SYSTEM INFORMATION tables to update
+ * the LAI
+ */
+static void patch_nm_tables(struct gsm_bts *bts)
+{
+ uint8_t arfcn_low = bts->c0->arfcn & 0xff;
+ uint8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f;
+
+ /* T3105 attribute in units of 10ms */
+ bs11_attr_bts[2] = bts->network->T3105 / 10;
+
+ /* patch ARFCN into BTS Attributes */
+ bs11_attr_bts[69] &= 0xf0;
+ bs11_attr_bts[69] |= arfcn_high;
+ bs11_attr_bts[70] = arfcn_low;
+
+ /* patch ARFCN into TRX Attributes */
+ bs11_attr_radio[2] &= 0xf0;
+ bs11_attr_radio[2] |= arfcn_high;
+ bs11_attr_radio[3] = arfcn_low;
+
+ /* patch the RACH attributes */
+ if (bts->rach_b_thresh != -1)
+ bs11_attr_bts[33] = bts->rach_b_thresh & 0xff;
+
+ if (bts->rach_ldavg_slots != -1) {
+ uint8_t avg_high = bts->rach_ldavg_slots & 0xff;
+ uint8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f;
+
+ bs11_attr_bts[35] = avg_high;
+ bs11_attr_bts[36] = avg_low;
+ }
+
+ /* patch BSIC */
+ bs11_attr_bts[1] = bts->bsic;
+
+ /* patch the power reduction */
+ bs11_attr_radio[5] = bts->c0->max_power_red / 2;
+}
+
+
+static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts)
+{
+ enum abis_nm_chan_comb ccomb = abis_nm_chcomb4pchan(ts->pchan);
+ struct gsm_e1_subslot *e1l = &ts->e1_link;
+
+ abis_nm_set_channel_attr(ts, ccomb);
+
+ if (is_ipaccess_bts(ts->trx->bts))
+ return;
+
+ if (ts_is_tch(ts))
+ abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts,
+ e1l->e1_ts_ss);
+}
+
+static void nm_reconfig_trx(struct gsm_bts_trx *trx)
+{
+ struct gsm_e1_subslot *e1l = &trx->rsl_e1_link;
+ int i;
+
+ patch_nm_tables(trx->bts);
+
+ switch (trx->bts->type) {
+ case GSM_BTS_TYPE_BS11:
+ /* FIXME: discover this by fetching an attribute */
+#if 0
+ trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */
+#else
+ 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;
+ }
+
+ for (i = 0; i < TRX_NR_TS; i++)
+ nm_reconfig_ts(&trx->ts[i]);
+}
+
+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;
+ }
+
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ nm_reconfig_trx(trx);
+}
+
+
+static void bootstrap_om_bs11(struct gsm_bts *bts)
+{
+ LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+
+ /* stop sending event reports */
+ abis_nm_event_reports(bts, 0);
+
+ /* begin DB transmission */
+ abis_nm_bs11_db_transmission(bts, 1);
+
+ /* end DB transmission */
+ abis_nm_bs11_db_transmission(bts, 0);
+
+ /* Reset BTS Site manager resource */
+ abis_nm_bs11_reset_resource(bts);
+
+ /* begin DB transmission */
+ abis_nm_bs11_db_transmission(bts, 1);
+
+ /* reconfigure BTS with all TRX and all TS */
+ nm_reconfig_bts(bts);
+
+ /* end DB transmission */
+ abis_nm_bs11_db_transmission(bts, 0);
+
+ /* Reset BTS Site manager resource */
+ abis_nm_bs11_reset_resource(bts);
+
+ /* restart sending event reports */
+ abis_nm_event_reports(bts, 1);
+}
+
+static int shutdown_om(struct gsm_bts *bts)
+{
+ /* stop sending event reports */
+ abis_nm_event_reports(bts, 0);
+
+ /* begin DB transmission */
+ abis_nm_bs11_db_transmission(bts, 1);
+
+ /* end DB transmission */
+ abis_nm_bs11_db_transmission(bts, 0);
+
+ /* Reset BTS Site manager resource */
+ abis_nm_bs11_reset_resource(bts);
+
+ return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_bts *bts;
+
+ if (subsys != SS_L_GLOBAL)
+ return 0;
+
+ switch (signal) {
+ case S_GLOBAL_BTS_CLOSE_OM:
+ bts = signal_data;
+ if (bts->type == GSM_BTS_TYPE_BS11)
+ shutdown_om(signal_data);
+ break;
+ }
+
+ return 0;
+}
+
+/* Callback function to be called every time we receive a signal from INPUT */
+static int inp_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct input_signal_data *isd = signal_data;
+
+ if (subsys != SS_L_INPUT)
+ return 0;
+
+ switch (signal) {
+ case S_L_INP_TEI_UP:
+ switch (isd->link_type) {
+ case E1INP_SIGN_OML:
+ if (isd->trx->bts->type == GSM_BTS_TYPE_BS11)
+ bootstrap_om_bs11(isd->trx->bts);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+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);
+
+ gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_HOPPING);
+ gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_HSCSD);
+ gsm_btsmodel_set_feature(&model_bs11, 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_bs11_init(void)
+{
+ return gsm_bts_model_register(&model_bs11);
+}
diff --git a/src/libbsc/bts_sysmobts.c b/src/libbsc/bts_sysmobts.c
new file mode 100644
index 000000000..e4b6cdc78
--- /dev/null
+++ b/src/libbsc/bts_sysmobts.c
@@ -0,0 +1,60 @@
+/* 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 <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/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;
+
+ model_sysmobts.features.data = &model_sysmobts._features_data[0];
+ model_sysmobts.features.data_len =
+ sizeof(model_sysmobts._features_data);
+ memset(model_sysmobts.features.data, 0, sizeof(model_sysmobts.features.data_len));
+
+ gsm_btsmodel_set_feature(&model_sysmobts, BTS_FEAT_GPRS);
+ gsm_btsmodel_set_feature(&model_sysmobts, BTS_FEAT_EGPRS);
+
+ return gsm_bts_model_register(&model_sysmobts);
+}
diff --git a/src/libbsc/bts_unknown.c b/src/libbsc/bts_unknown.c
new file mode 100644
index 000000000..f1135294f
--- /dev/null
+++ b/src/libbsc/bts_unknown.c
@@ -0,0 +1,40 @@
+/* Generic BTS - VTY code tries to allocate this BTS before type is known */
+
+/* (C) 2010 by Daniel Willmann <daniel@totalueberwachung.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 <openbsc/gsm_data.h>
+#include <osmocom/gsm/tlv.h>
+#include <openbsc/abis_nm.h>
+
+static struct gsm_bts_model model_unknown = {
+ .type = GSM_BTS_TYPE_UNKNOWN,
+ .name = "unknown",
+ .oml_rcvmsg = &abis_nm_rcvmsg,
+ .nm_att_tlvdef = {
+ .def = {
+ },
+ },
+};
+
+int bts_model_unknown_init(void)
+{
+ return gsm_bts_model_register(&model_unknown);
+}
diff --git a/src/libbsc/chan_alloc.c b/src/libbsc/chan_alloc.c
new file mode 100644
index 000000000..33b79a0b2
--- /dev/null
+++ b/src/libbsc/chan_alloc.c
@@ -0,0 +1,543 @@
+/* GSM Channel allocation routines
+ *
+ * (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <openbsc/rtp_proxy.h>
+#include <openbsc/signal.h>
+
+#include <osmocom/core/talloc.h>
+
+static int ts_is_usable(struct gsm_bts_trx_ts *ts)
+{
+ /* FIXME: How does this behave for BS-11 ? */
+ if (is_ipaccess_bts(ts->trx->bts)) {
+ if (!nm_is_running(&ts->mo.nm_state))
+ return 0;
+ }
+
+ /* If a TCH/F_PDCH TS is busy changing, it is already taken or not
+ * yet available. */
+ if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) {
+ if (ts->flags & TS_F_PDCH_PENDING_MASK)
+ return 0;
+ }
+
+ /* If a dynamic channel is busy changing, it is already taken or not
+ * yet available. */
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ if (ts->dyn.pchan_is != ts->dyn.pchan_want)
+ return 0;
+ }
+
+ return 1;
+}
+
+int trx_is_usable(struct gsm_bts_trx *trx)
+{
+ /* FIXME: How does this behave for BS-11 ? */
+ if (is_ipaccess_bts(trx->bts)) {
+ if (!nm_is_running(&trx->mo.nm_state) ||
+ !nm_is_running(&trx->bb_transc.mo.nm_state))
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct gsm_lchan *
+_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan,
+ enum gsm_phys_chan_config dyn_as_pchan)
+{
+ struct gsm_bts_trx_ts *ts;
+ int j, start, stop, dir, ss;
+ int check_subslots;
+
+ if (!trx_is_usable(trx))
+ return NULL;
+
+ if (trx->bts->chan_alloc_reverse) {
+ /* check TS 7..0 */
+ start = 7;
+ stop = -1;
+ dir = -1;
+ } else {
+ /* check TS 0..7 */
+ start = 0;
+ stop = 8;
+ dir = 1;
+ }
+
+ for (j = start; j != stop; j += dir) {
+ ts = &trx->ts[j];
+ if (!ts_is_usable(ts))
+ continue;
+ if (ts->pchan != pchan)
+ continue;
+
+ /*
+ * Allocation for fully dynamic timeslots
+ * (does not apply for ip.access style GSM_PCHAN_TCH_F_PDCH)
+ *
+ * Note the special nature of a dynamic timeslot in PDCH mode:
+ * in PDCH mode, typically, lchan->type is GSM_LCHAN_NONE and
+ * lchan->state is LCHAN_S_NONE -- an otherwise unused slot
+ * becomes PDCH implicitly. In the same sense, this channel
+ * allocator will never be asked to find an available PDCH
+ * slot; only TCH/F or TCH/H will be requested, and PDCH mode
+ * means that it is available for switchover.
+ *
+ * A dynamic timeslot in PDCH mode may be switched to TCH/F or
+ * TCH/H. If a dyn TS is already in TCH/F or TCH/H mode, it
+ * means that it is in use and its mode can't be switched.
+ *
+ * The logic concerning channels for TCH/F is trivial: there is
+ * only one channel, so a dynamic TS in TCH/F mode is already
+ * taken and not available for allocation. For TCH/H, we need
+ * to check whether a dynamic timeslot is already in TCH/H mode
+ * and whether one of the two channels is still available.
+ */
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
+ /* The TS's mode is being switched. Not
+ * available anymore/yet. */
+ DEBUGP(DRLL, "%s already in switchover\n",
+ gsm_ts_and_pchan_name(ts));
+ continue;
+ }
+ if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) {
+ /* This slot is available. Still check for
+ * error states to be sure; in all cases the
+ * first lchan will be used. */
+ if (ts->lchan->state != LCHAN_S_NONE
+ && ts->lchan->state != LCHAN_S_ACTIVE)
+ continue;
+ return ts->lchan;
+ }
+ if (ts->dyn.pchan_is != dyn_as_pchan)
+ /* not applicable. */
+ continue;
+ /* The requested type matches the dynamic timeslot's
+ * current mode. A channel may still be available
+ * (think TCH/H). */
+ check_subslots = ts_subslots(ts);
+ break;
+
+ case GSM_PCHAN_TCH_F_PDCH:
+ /* Available for voice when in PDCH mode */
+ if (ts_pchan(ts) != GSM_PCHAN_PDCH)
+ continue;
+ /* Subslots of a PDCH ts don't need to be checked. */
+ return ts->lchan;
+
+ default:
+ /* Not a dynamic channel, there is only one pchan kind: */
+ check_subslots = ts_subslots(ts);
+ break;
+ }
+
+ /* Is a sub-slot still available? */
+ for (ss = 0; ss < check_subslots; ss++) {
+ struct gsm_lchan *lc = &ts->lchan[ss];
+ if (lc->type == GSM_LCHAN_NONE &&
+ lc->state == LCHAN_S_NONE)
+ return lc;
+ }
+ }
+
+ return NULL;
+}
+
+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)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_lchan *lc;
+
+ 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;
+ }
+ }
+
+ return NULL;
+}
+
+static struct gsm_lchan *
+_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
+{
+ return _lc_dyn_find_bts(bts, pchan, GSM_PCHAN_NONE);
+}
+
+/* Allocate a logical channel.
+ *
+ * Dynamic channel types: we always prefer a dedicated TS, and only pick +
+ * switch a dynamic TS if no pure TS of the requested PCHAN is available.
+ *
+ * TCH_F/PDCH: if we pick a PDCH ACT style dynamic TS as TCH/F channel, PDCH
+ * will be disabled in rsl_chan_activate_lchan(); there is no need to check
+ * whether PDCH mode is currently active, here.
+ */
+struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type,
+ int allow_bigger)
+{
+ struct gsm_lchan *lchan = NULL;
+ enum gsm_phys_chan_config first, first_cbch, second, second_cbch;
+
+ switch (type) {
+ case GSM_LCHAN_SDCCH:
+ if (bts->chan_alloc_reverse) {
+ first = GSM_PCHAN_SDCCH8_SACCH8C;
+ first_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
+ second = GSM_PCHAN_CCCH_SDCCH4;
+ second_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH;
+ } else {
+ first = GSM_PCHAN_CCCH_SDCCH4;
+ first_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH;
+ second = GSM_PCHAN_SDCCH8_SACCH8C;
+ second_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH;
+ }
+
+ lchan = _lc_find_bts(bts, first);
+ if (lchan == NULL)
+ lchan = _lc_find_bts(bts, first_cbch);
+ if (lchan == NULL)
+ lchan = _lc_find_bts(bts, second);
+ if (lchan == NULL)
+ lchan = _lc_find_bts(bts, second_cbch);
+
+ /* allow to assign bigger channels */
+ if (allow_bigger) {
+ if (lchan == NULL) {
+ lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
+ if (lchan)
+ type = GSM_LCHAN_TCH_H;
+ }
+
+ if (lchan == NULL) {
+ lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+ if (lchan)
+ type = GSM_LCHAN_TCH_F;
+ }
+
+ /* try dynamic TCH/F_PDCH */
+ if (lchan == NULL) {
+ lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F_PDCH);
+ /* TCH/F_PDCH will be used as TCH/F */
+ if (lchan)
+ type = GSM_LCHAN_TCH_F;
+ }
+
+ /* try fully dynamic TCH/F_TCH/H_PDCH */
+ if (lchan == NULL) {
+ lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_TCH_H_PDCH,
+ GSM_PCHAN_TCH_H);
+ if (lchan)
+ type = GSM_LCHAN_TCH_H;
+ }
+ /*
+ * No need to check fully dynamic channels for TCH/F:
+ * if no TCH/H was available, neither will be TCH/F.
+ */
+ }
+ break;
+ case GSM_LCHAN_TCH_F:
+ lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+ /* If we don't have TCH/F available, fall-back to TCH/H */
+ if (!lchan) {
+ lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
+ if (lchan)
+ type = GSM_LCHAN_TCH_H;
+ }
+ /* If we don't have TCH/H either, try dynamic TCH/F_PDCH */
+ if (!lchan) {
+ lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F_PDCH);
+ /* 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;
+ }
+
+ /* 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;
+ }
+ /* ...and as TCH/H. */
+ 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;
+ }
+ break;
+ case GSM_LCHAN_TCH_H:
+ lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
+ /* If we don't have TCH/H available, fall-back to TCH/F */
+ if (!lchan) {
+ lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+ if (lchan)
+ type = GSM_LCHAN_TCH_F;
+ }
+ /* No dedicated TCH/x available -- try fully dynamic
+ * TCH/F_TCH/H_PDCH */
+ if (!lchan) {
+ lchan = _lc_dyn_find_bts(bts,
+ GSM_PCHAN_TCH_F_TCH_H_PDCH,
+ GSM_PCHAN_TCH_H);
+ if (lchan)
+ type = GSM_LCHAN_TCH_H;
+ }
+ /*
+ * No need to check TCH/F_TCH/H_PDCH channels for TCH/F:
+ * if no TCH/H was available, neither will be TCH/F.
+ */
+ /* If we don't have TCH/F either, try dynamic TCH/F_PDCH */
+ if (!lchan) {
+ lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F_PDCH);
+ if (lchan)
+ type = GSM_LCHAN_TCH_F;
+ }
+ break;
+ default:
+ LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type);
+ }
+
+ if (lchan) {
+ lchan->type = type;
+
+ LOGP(DRLL, LOGL_INFO, "%s Allocating lchan=%u as %s\n",
+ gsm_ts_and_pchan_name(lchan->ts),
+ lchan->nr, gsm_lchant_name(lchan->type));
+
+ /* clear sapis */
+ memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis));
+
+ /* clear multi rate config */
+ memset(&lchan->mr_ms_lv, 0, sizeof(lchan->mr_ms_lv));
+ memset(&lchan->mr_bts_lv, 0, sizeof(lchan->mr_bts_lv));
+ lchan->broken_reason = "";
+ } else {
+ struct challoc_signal_data sig;
+
+ LOGP(DRLL, LOGL_ERROR, "Failed to allocate %s channel\n",
+ gsm_lchant_name(type));
+
+ sig.bts = bts;
+ sig.type = type;
+ osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_ALLOC_FAIL, &sig);
+ }
+
+ return lchan;
+}
+
+/* Free a logical channel */
+void lchan_free(struct gsm_lchan *lchan)
+{
+ struct challoc_signal_data sig;
+ int i;
+
+ sig.type = lchan->type;
+ lchan->type = GSM_LCHAN_NONE;
+
+
+ if (lchan->conn) {
+ struct lchan_signal_data sig;
+
+ /* We might kill an active channel... */
+ sig.lchan = lchan;
+ sig.mr = NULL;
+ osmo_signal_dispatch(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, &sig);
+ }
+
+ if (lchan->abis_ip.rtp_socket) {
+ LOGP(DRLL, LOGL_ERROR, "%s RTP Proxy Socket remained open.\n",
+ gsm_lchan_name(lchan));
+ rtp_socket_free(lchan->abis_ip.rtp_socket);
+ lchan->abis_ip.rtp_socket = NULL;
+ }
+
+ /* stop the timer */
+ osmo_timer_del(&lchan->T3101);
+
+ /* clear cached measuement reports */
+ lchan->meas_rep_idx = 0;
+ for (i = 0; i < ARRAY_SIZE(lchan->meas_rep); i++) {
+ lchan->meas_rep[i].flags = 0;
+ lchan->meas_rep[i].nr = 0;
+ }
+ for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++)
+ lchan->neigh_meas[i].arfcn = 0;
+
+ if (lchan->rqd_ref) {
+ talloc_free(lchan->rqd_ref);
+ lchan->rqd_ref = NULL;
+ lchan->rqd_ta = 0;
+ }
+
+ sig.lchan = lchan;
+ sig.bts = lchan->ts->trx->bts;
+ osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_FREED, &sig);
+
+ if (lchan->conn) {
+ LOGP(DRLL, LOGL_ERROR, "the subscriber connection should be gone.\n");
+ lchan->conn = NULL;
+ }
+
+ /* FIXME: ts_free() the timeslot, if we're the last logical
+ * channel using it */
+}
+
+/*
+ * There was an error with the TRX and we need to forget
+ * any state so that a lchan can be allocated again after
+ * the trx is fully usable.
+ *
+ * This should be called after lchan_free to force a channel
+ * be available for allocation again. This means that this
+ * method will stop the "delay after error"-timer and set the
+ * state to LCHAN_S_NONE.
+ */
+void lchan_reset(struct gsm_lchan *lchan)
+{
+ osmo_timer_del(&lchan->T3101);
+ osmo_timer_del(&lchan->T3109);
+ osmo_timer_del(&lchan->T3111);
+ osmo_timer_del(&lchan->error_timer);
+
+ lchan->type = GSM_LCHAN_NONE;
+ lchan->state = LCHAN_S_NONE;
+
+ if (lchan->abis_ip.rtp_socket) {
+ rtp_socket_free(lchan->abis_ip.rtp_socket);
+ lchan->abis_ip.rtp_socket = NULL;
+ }
+}
+
+/* Drive the release process of the lchan */
+static void _lchan_handle_release(struct gsm_lchan *lchan,
+ int sacch_deact, int mode)
+{
+ /* Release all SAPIs on the local end and continue */
+ rsl_release_sapis_from(lchan, 1, RSL_REL_LOCAL_END);
+
+ /*
+ * Shall we send a RR Release, start T3109 and wait for the
+ * release indication from the BTS or just take it down (e.g.
+ * on assignment requests)
+ */
+ if (sacch_deact) {
+ gsm48_send_rr_release(lchan);
+
+ /* Deactivate the SACCH on the BTS side */
+ rsl_deact_sacch(lchan);
+ rsl_start_t3109(lchan);
+ } else if (lchan->sapis[0] == LCHAN_SAPI_UNUSED) {
+ rsl_direct_rf_release(lchan);
+ } else {
+ rsl_release_request(lchan, 0, mode);
+ }
+}
+
+/* Consider releasing the channel now */
+int lchan_release(struct gsm_lchan *lchan, int sacch_deact, enum rsl_rel_mode mode)
+{
+ DEBUGP(DRLL, "%s starting release sequence\n", gsm_lchan_name(lchan));
+ rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ);
+
+ lchan->conn = NULL;
+ _lchan_handle_release(lchan, sacch_deact, mode);
+ return 1;
+}
+
+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) {
+ int i;
+
+ /* skip administratively deactivated tranxsceivers */
+ if (!nm_is_running(&trx->mo.nm_state) ||
+ !nm_is_running(&trx->bb_transc.mo.nm_state))
+ continue;
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ struct load_counter *pl = &cl->pchan[ts->pchan];
+ int j;
+ int subslots;
+
+ /* skip administratively deactivated timeslots */
+ if (!nm_is_running(&ts->mo.nm_state))
+ continue;
+
+ subslots = ts_subslots(ts);
+ for (j = 0; j < subslots; j++) {
+ struct gsm_lchan *lchan = &ts->lchan[j];
+
+ pl->total++;
+
+ switch (lchan->state) {
+ case LCHAN_S_NONE:
+ break;
+ default:
+ pl->used++;
+ break;
+ }
+ }
+ }
+ }
+}
+
+void network_chan_load(struct pchan_load *pl, struct gsm_network *net)
+{
+ struct gsm_bts *bts;
+
+ memset(pl, 0, sizeof(*pl));
+
+ llist_for_each_entry(bts, &net->bts_list, list)
+ bts_chan_load(pl, bts);
+}
+
diff --git a/src/libbsc/e1_config.c b/src/libbsc/e1_config.c
new file mode 100644
index 000000000..d57dec57e
--- /dev/null
+++ b/src/libbsc/e1_config.c
@@ -0,0 +1,297 @@
+/* OpenBSC E1 Input code */
+
+/* (C) 2008-2010 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 <errno.h>
+
+#include <netinet/in.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/misdn.h>
+#include <osmocom/abis/ipaccess.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/debug.h>
+#include <openbsc/abis_rsl.h>
+
+#define SAPI_L2ML 0
+#define SAPI_OML 62
+#define SAPI_RSL 0 /* 63 ? */
+
+/* The e1_reconfig_*() functions below take the configuration present in the
+ * bts/trx/ts data structures and ensure the E1 configuration reflects the
+ * timeslot/subslot/TEI configuration */
+
+int e1_reconfig_ts(struct gsm_bts_trx_ts *ts)
+{
+ struct gsm_e1_subslot *e1_link = &ts->e1_link;
+ struct e1inp_line *line;
+ struct e1inp_ts *e1_ts;
+
+ DEBUGP(DLMI, "e1_reconfig_ts(%u,%u,%u)\n", ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+ if (!e1_link->e1_ts) {
+ LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) without E1 timeslot?\n",
+ ts->nr, ts->trx->nr, ts->trx->bts->nr);
+ return 0;
+ }
+
+ line = e1inp_line_find(e1_link->e1_nr);
+ if (!line) {
+ LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) referring to "
+ "non-existing E1 line %u\n", ts->nr, ts->trx->nr,
+ ts->trx->bts->nr, e1_link->e1_nr);
+ return -ENOMEM;
+ }
+
+ if (ts_is_tch(ts)) {
+ e1_ts = &line->ts[e1_link->e1_ts-1];
+ e1inp_ts_config_trau(e1_ts, line, subch_cb);
+ subch_demux_activate(&e1_ts->trau.demux, e1_link->e1_ts_ss);
+ }
+
+ return 0;
+}
+
+int e1_reconfig_trx(struct gsm_bts_trx *trx)
+{
+ struct gsm_e1_subslot *e1_link = &trx->rsl_e1_link;
+ struct e1inp_ts *sign_ts;
+ struct e1inp_line *line;
+ struct e1inp_sign_link *rsl_link;
+ int i;
+
+ if (!e1_link->e1_ts) {
+ LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link without "
+ "timeslot?\n", trx->bts->nr, trx->nr);
+ return -EINVAL;
+ }
+
+ /* RSL Link */
+ line = e1inp_line_find(e1_link->e1_nr);
+ if (!line) {
+ LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link referring "
+ "to non-existing E1 line %u\n", trx->bts->nr,
+ trx->nr, e1_link->e1_nr);
+ return -ENOMEM;
+ }
+ sign_ts = &line->ts[e1_link->e1_ts-1];
+ e1inp_ts_config_sign(sign_ts, line);
+ /* Ericsson RBS have a per-TRX OML link in parallel to RSL */
+ 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);
+ if (!oml_link) {
+ LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) OML link creation "
+ "failed\n", trx->bts->nr, trx->nr);
+ return -ENOMEM;
+ }
+ if (trx->oml_link)
+ e1inp_sign_link_destroy(trx->oml_link);
+ trx->oml_link = oml_link;
+ }
+ rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+ trx, trx->rsl_tei, SAPI_RSL);
+ if (!rsl_link) {
+ LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link creation "
+ "failed\n", trx->bts->nr, trx->nr);
+ return -ENOMEM;
+ }
+ if (trx->rsl_link)
+ e1inp_sign_link_destroy(trx->rsl_link);
+ trx->rsl_link = rsl_link;
+
+ for (i = 0; i < TRX_NR_TS; i++)
+ e1_reconfig_ts(&trx->ts[i]);
+
+ return 0;
+}
+
+/* this is the generic callback for all ISDN-based BTS. */
+static int bts_isdn_sign_link(struct msgb *msg)
+{
+ int ret = -EINVAL;
+ struct e1inp_sign_link *link = msg->dst;
+ struct gsm_bts *bts;
+
+ switch (link->type) {
+ case E1INP_SIGN_OML:
+ bts = link->trx->bts;
+ ret = bts->model->oml_rcvmsg(msg);
+ break;
+ case E1INP_SIGN_RSL:
+ ret = abis_rsl_rcvmsg(msg);
+ break;
+ default:
+ LOGP(DLMI, LOGL_ERROR, "unknown link type %u\n", link->type);
+ break;
+ }
+ return ret;
+}
+
+struct e1inp_line_ops bts_isdn_e1inp_line_ops = {
+ .sign_link = bts_isdn_sign_link,
+};
+
+int e1_reconfig_bts(struct gsm_bts *bts)
+{
+ struct gsm_e1_subslot *e1_link = &bts->oml_e1_link;
+ struct e1inp_ts *sign_ts;
+ struct e1inp_line *line;
+ struct e1inp_sign_link *oml_link;
+ struct gsm_bts_trx *trx;
+
+ DEBUGP(DLMI, "e1_reconfig_bts(%u)\n", bts->nr);
+
+ line = e1inp_line_find(e1_link->e1_nr);
+ if (!line) {
+ LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to "
+ "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr);
+ return -ENOMEM;
+ }
+
+ if (!bts->model->e1line_bind_ops) {
+ LOGP(DLINP, LOGL_ERROR, "no callback to bind E1 line operations\n");
+ return -EINVAL;
+ }
+ if (!line->ops)
+ bts->model->e1line_bind_ops(line);
+
+ /* skip signal link initialization, this is done later for these BTS. */
+ if (bts->type == GSM_BTS_TYPE_NANOBTS ||
+ bts->type == GSM_BTS_TYPE_OSMOBTS)
+ return e1inp_line_update(line);
+
+ /* OML link */
+ if (!e1_link->e1_ts) {
+ LOGP(DLINP, LOGL_ERROR, "BTS %u OML link without timeslot?\n",
+ bts->nr);
+ return -EINVAL;
+ }
+
+ sign_ts = &line->ts[e1_link->e1_ts-1];
+ e1inp_ts_config_sign(sign_ts, line);
+ oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+ bts->c0, bts->oml_tei, SAPI_OML);
+ if (!oml_link) {
+ LOGP(DLINP, LOGL_ERROR, "BTS %u OML link creation failed\n",
+ bts->nr);
+ return -ENOMEM;
+ }
+ if (bts->oml_link)
+ e1inp_sign_link_destroy(bts->oml_link);
+ bts->oml_link = oml_link;
+
+ llist_for_each_entry(trx, &bts->trx_list, list)
+ e1_reconfig_trx(trx);
+
+ /* notify E1 input something has changed */
+ return e1inp_line_update(line);
+}
+
+#if 0
+/* do some compiled-in configuration for our BTS/E1 setup */
+int e1_config(struct gsm_bts *bts, int cardnr, int release_l2)
+{
+ struct e1inp_line *line;
+ struct e1inp_ts *sign_ts;
+ struct e1inp_sign_link *oml_link, *rsl_link;
+ struct gsm_bts_trx *trx = bts->c0;
+ int base_ts;
+
+ switch (bts->nr) {
+ case 0:
+ /* First BTS uses E1 TS 01,02,03,04,05 */
+ base_ts = HARDCODED_BTS0_TS - 1;
+ break;
+ case 1:
+ /* Second BTS uses E1 TS 06,07,08,09,10 */
+ base_ts = HARDCODED_BTS1_TS - 1;
+ break;
+ case 2:
+ /* Third BTS uses E1 TS 11,12,13,14,15 */
+ base_ts = HARDCODED_BTS2_TS - 1;
+ default:
+ return -EINVAL;
+ }
+
+ line = talloc_zero(tall_bsc_ctx, struct e1inp_line);
+ if (!line)
+ return -ENOMEM;
+
+ /* create E1 timeslots for signalling and TRAU frames */
+ e1inp_ts_config(&line->ts[base_ts+1-1], line, E1INP_TS_TYPE_SIGN);
+ e1inp_ts_config(&line->ts[base_ts+2-1], line, E1INP_TS_TYPE_TRAU);
+ e1inp_ts_config(&line->ts[base_ts+3-1], line, E1INP_TS_TYPE_TRAU);
+
+ /* create signalling links for TS1 */
+ sign_ts = &line->ts[base_ts+1-1];
+ oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+ trx, TEI_OML, SAPI_OML);
+ rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+ trx, TEI_RSL, SAPI_RSL);
+
+ /* create back-links from bts/trx */
+ bts->oml_link = oml_link;
+ trx->rsl_link = rsl_link;
+
+ /* enable subchannel demuxer on TS2 */
+ subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 1);
+ subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 2);
+ subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 3);
+
+ /* enable subchannel demuxer on TS3 */
+ subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 0);
+ subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 1);
+ subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 2);
+ subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 3);
+
+ trx = gsm_bts_trx_num(bts, 1);
+ if (trx) {
+ /* create E1 timeslots for TRAU frames of TRX1 */
+ e1inp_ts_config(&line->ts[base_ts+4-1], line, E1INP_TS_TYPE_TRAU);
+ e1inp_ts_config(&line->ts[base_ts+5-1], line, E1INP_TS_TYPE_TRAU);
+
+ /* create RSL signalling link for TRX1 */
+ sign_ts = &line->ts[base_ts+1-1];
+ rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
+ trx, TEI_RSL+1, SAPI_RSL);
+ /* create back-links from trx */
+ trx->rsl_link = rsl_link;
+
+ /* enable subchannel demuxer on TS2 */
+ subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 0);
+ subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 1);
+ subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 2);
+ subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 3);
+
+ /* enable subchannel demuxer on TS3 */
+ subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 0);
+ subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 1);
+ subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 2);
+ subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 3);
+ }
+
+ return mi_setup(cardnr, line, release_l2);
+}
+#endif
diff --git a/src/libbsc/gsm_04_08_utils.c b/src/libbsc/gsm_04_08_utils.c
new file mode 100644
index 000000000..3447d27cb
--- /dev/null
+++ b/src/libbsc/gsm_04_08_utils.c
@@ -0,0 +1,687 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0
+ * utility functions
+ */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.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 <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <openbsc/abis_rsl.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/transaction.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/bsc_api.h>
+
+/* should ip.access BTS use direct RTP streams between each other (1),
+ * or should OpenBSC always act as RTP relay/proxy in between (0) ? */
+int ipacc_rtp_direct = 1;
+
+static int gsm48_sendmsg(struct msgb *msg)
+{
+ if (msg->lchan)
+ msg->dst = msg->lchan->ts->trx->rsl_link;
+
+ msg->l3h = msg->data;
+ return rsl_data_request(msg, 0);
+}
+
+/* Section 9.1.8 / Table 9.9 */
+struct chreq {
+ uint8_t val;
+ uint8_t mask;
+ enum chreq_type type;
+};
+
+/* If SYSTEM INFORMATION TYPE 4 NECI bit == 1 */
+static const struct chreq chreq_type_neci1[] = {
+ { 0xa0, 0xe0, CHREQ_T_EMERG_CALL },
+ { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_F },
+ { 0x68, 0xfc, CHREQ_T_CALL_REEST_TCH_H },
+ { 0x6c, 0xfc, CHREQ_T_CALL_REEST_TCH_H_DBL },
+ { 0xe0, 0xe0, CHREQ_T_TCH_F },
+ { 0x40, 0xf0, CHREQ_T_VOICE_CALL_TCH_H },
+ { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H },
+ { 0x00, 0xf0, CHREQ_T_LOCATION_UPD },
+ { 0x10, 0xf0, CHREQ_T_SDCCH },
+ { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI1 },
+ { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F },
+ { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH },
+ { 0x67, 0xff, CHREQ_T_LMU },
+ { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH },
+ { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH },
+ { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH },
+ { 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE },
+ { 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE },
+ { 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE },
+ { 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE },
+ { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE },
+};
+
+/* If SYSTEM INFORMATION TYPE 4 NECI bit == 0 */
+static const struct chreq chreq_type_neci0[] = {
+ { 0xa0, 0xe0, CHREQ_T_EMERG_CALL },
+ { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_H },
+ { 0xe0, 0xe0, CHREQ_T_TCH_F },
+ { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H },
+ { 0x00, 0xe0, CHREQ_T_LOCATION_UPD },
+ { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI0 },
+ { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F },
+ { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH },
+ { 0x67, 0xff, CHREQ_T_LMU },
+ { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH },
+ { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH },
+ { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH },
+ { 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE },
+ { 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE },
+ { 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE },
+ { 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE },
+ { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE },
+};
+
+static const enum gsm_chan_t ctype_by_chreq[] = {
+ [CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_F,
+ [CHREQ_T_CALL_REEST_TCH_F] = GSM_LCHAN_TCH_F,
+ [CHREQ_T_CALL_REEST_TCH_H] = GSM_LCHAN_TCH_H,
+ [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_LCHAN_TCH_H,
+ [CHREQ_T_SDCCH] = GSM_LCHAN_SDCCH,
+ [CHREQ_T_TCH_F] = GSM_LCHAN_TCH_F,
+ [CHREQ_T_VOICE_CALL_TCH_H] = GSM_LCHAN_TCH_H,
+ [CHREQ_T_DATA_CALL_TCH_H] = GSM_LCHAN_TCH_H,
+ [CHREQ_T_LOCATION_UPD] = GSM_LCHAN_SDCCH,
+ [CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_SDCCH,
+ [CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_SDCCH,
+ [CHREQ_T_PAG_R_TCH_F] = GSM_LCHAN_TCH_F,
+ [CHREQ_T_PAG_R_TCH_FH] = GSM_LCHAN_TCH_F,
+ [CHREQ_T_LMU] = GSM_LCHAN_SDCCH,
+ [CHREQ_T_RESERVED_SDCCH] = GSM_LCHAN_SDCCH,
+ [CHREQ_T_PDCH_ONE_PHASE] = GSM_LCHAN_PDTCH,
+ [CHREQ_T_PDCH_TWO_PHASE] = GSM_LCHAN_PDTCH,
+ [CHREQ_T_RESERVED_IGNORE] = GSM_LCHAN_UNKNOWN,
+};
+
+static const enum gsm_chreq_reason_t reason_by_chreq[] = {
+ [CHREQ_T_EMERG_CALL] = GSM_CHREQ_REASON_EMERG,
+ [CHREQ_T_CALL_REEST_TCH_F] = GSM_CHREQ_REASON_CALL,
+ [CHREQ_T_CALL_REEST_TCH_H] = GSM_CHREQ_REASON_CALL,
+ [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_CHREQ_REASON_CALL,
+ [CHREQ_T_SDCCH] = GSM_CHREQ_REASON_OTHER,
+ [CHREQ_T_TCH_F] = GSM_CHREQ_REASON_OTHER,
+ [CHREQ_T_VOICE_CALL_TCH_H] = GSM_CHREQ_REASON_CALL,
+ [CHREQ_T_DATA_CALL_TCH_H] = GSM_CHREQ_REASON_OTHER,
+ [CHREQ_T_LOCATION_UPD] = GSM_CHREQ_REASON_LOCATION_UPD,
+ [CHREQ_T_PAG_R_ANY_NECI1] = GSM_CHREQ_REASON_PAG,
+ [CHREQ_T_PAG_R_ANY_NECI0] = GSM_CHREQ_REASON_PAG,
+ [CHREQ_T_PAG_R_TCH_F] = GSM_CHREQ_REASON_PAG,
+ [CHREQ_T_PAG_R_TCH_FH] = GSM_CHREQ_REASON_PAG,
+ [CHREQ_T_LMU] = GSM_CHREQ_REASON_OTHER,
+ [CHREQ_T_PDCH_ONE_PHASE] = GSM_CHREQ_REASON_PDCH,
+ [CHREQ_T_PDCH_TWO_PHASE] = GSM_CHREQ_REASON_PDCH,
+ [CHREQ_T_RESERVED_SDCCH] = GSM_CHREQ_REASON_OTHER,
+ [CHREQ_T_RESERVED_IGNORE] = GSM_CHREQ_REASON_OTHER,
+};
+
+/* verify that the two tables match */
+osmo_static_assert(sizeof(ctype_by_chreq) ==
+ sizeof(((struct gsm_network *) NULL)->ctype_by_chreq), assert_size);
+
+/*
+ * Update channel types for request based on policy. E.g. in the
+ * case of a TCH/H network/bsc use TCH/H for the emergency calls,
+ * for early assignment assign a SDCCH and some other options.
+ */
+void gsm_net_update_ctype(struct gsm_network *network)
+{
+ /* copy over the data */
+ memcpy(network->ctype_by_chreq, ctype_by_chreq, sizeof(ctype_by_chreq));
+
+ /*
+ * Use TCH/H for emergency calls when this cell allows TCH/H. Maybe it
+ * is better to iterate over the BTS/TRX and check if no TCH/F is available
+ * and then set it to TCH/H.
+ */
+ if (network->neci)
+ network->ctype_by_chreq[CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_H;
+
+ if (network->pag_any_tch) {
+ if (network->neci) {
+ network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_H;
+ network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_H;
+ } else {
+ network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_F;
+ network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_F;
+ }
+ }
+}
+
+enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, uint8_t ra)
+{
+ int i;
+ int length;
+ const struct chreq *chreq;
+
+ if (network->neci) {
+ chreq = chreq_type_neci1;
+ length = ARRAY_SIZE(chreq_type_neci1);
+ } else {
+ chreq = chreq_type_neci0;
+ length = ARRAY_SIZE(chreq_type_neci0);
+ }
+
+
+ for (i = 0; i < length; i++) {
+ const struct chreq *chr = &chreq[i];
+ if ((ra & chr->mask) == chr->val)
+ return network->ctype_by_chreq[chr->type];
+ }
+ LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST RQD 0x%02x\n", ra);
+ return GSM_LCHAN_SDCCH;
+}
+
+int get_reason_by_chreq(uint8_t ra, int neci)
+{
+ int i;
+ int length;
+ const struct chreq *chreq;
+
+ if (neci) {
+ chreq = chreq_type_neci1;
+ length = ARRAY_SIZE(chreq_type_neci1);
+ } else {
+ chreq = chreq_type_neci0;
+ length = ARRAY_SIZE(chreq_type_neci0);
+ }
+
+ for (i = 0; i < length; i++) {
+ const struct chreq *chr = &chreq[i];
+ if ((ra & chr->mask) == chr->val)
+ return reason_by_chreq[chr->type];
+ }
+ LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST REASON 0x%02x\n", ra);
+ return GSM_CHREQ_REASON_OTHER;
+}
+
+static void mr_config_for_ms(struct gsm_lchan *lchan, struct msgb *msg)
+{
+ 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);
+}
+
+/* 7.1.7 and 9.1.7: RR CHANnel RELease */
+int gsm48_send_rr_release(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RR REL");
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ uint8_t *cause;
+
+ msg->lchan = lchan;
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_CHAN_REL;
+
+ cause = msgb_put(msg, 1);
+ cause[0] = GSM48_RR_CAUSE_NORMAL;
+
+ DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d\n",
+ lchan->nr, lchan->type);
+
+ /* Send actual release request to MS */
+ return gsm48_sendmsg(msg);
+}
+
+int send_siemens_mrpci(struct gsm_lchan *lchan,
+ uint8_t *classmark2_lv)
+{
+ struct rsl_mrpci mrpci;
+
+ if (classmark2_lv[0] < 2)
+ return -EINVAL;
+
+ mrpci.power_class = classmark2_lv[1] & 0x7;
+ mrpci.vgcs_capable = classmark2_lv[2] & (1 << 1);
+ mrpci.vbs_capable = classmark2_lv[2] & (1 <<2);
+ mrpci.gsm_phase = (classmark2_lv[1]) >> 5 & 0x3;
+
+ return rsl_siemens_mrpci(lchan, &mrpci);
+}
+
+int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type)
+{
+ /* Check the size for the classmark */
+ if (length < 1 + *classmark2_lv)
+ return -1;
+
+ uint8_t *mi_lv = classmark2_lv + *classmark2_lv + 1;
+ if (length < 2 + *classmark2_lv + mi_lv[0])
+ return -2;
+
+ *mi_type = mi_lv[1] & GSM_MI_TYPE_MASK;
+ return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv);
+}
+
+int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length,
+ char *mi_string, uint8_t *mi_type)
+{
+ static const uint32_t classmark_offset =
+ offsetof(struct gsm48_pag_resp, classmark2);
+ uint8_t *classmark2_lv = (uint8_t *) &resp->classmark2;
+ return gsm48_extract_mi(classmark2_lv, length - classmark_offset,
+ mi_string, mi_type);
+}
+
+int gsm48_handle_paging_resp(struct gsm_subscriber_connection *conn,
+ struct msgb *msg, struct bsc_subscr *bsub)
+{
+ struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ uint8_t *classmark2_lv = gh->data + 1;
+
+ if (is_siemens_bts(bts))
+ send_siemens_mrpci(msg->lchan, classmark2_lv);
+
+ if (!conn->bsub) {
+ conn->bsub = bsub;
+ } else if (conn->bsub != bsub) {
+ LOGP(DRR, LOGL_ERROR,
+ "<- Channel already owned by someone else?\n");
+ bsc_subscr_put(bsub);
+ return -EINVAL;
+ } else {
+ DEBUGP(DRR, "<- Channel already owned by us\n");
+ bsc_subscr_put(bsub);
+ bsub = conn->bsub;
+ }
+
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_COMPLETED]);
+
+ /* Stop paging on the bts we received the paging response */
+ paging_request_stop(&bts->network->bts_list, conn->bts, bsub, conn,
+ msg);
+ return 0;
+}
+
+/* Chapter 9.1.9: Ciphering Mode Command */
+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;
+
+ msg->lchan = lchan;
+
+ DEBUGP(DRR, "TX CIPHERING MODE CMD\n");
+
+ 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;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_CIPH_M_CMD;
+ gh->data[0] = (want_imeisv & 0x1) << 4 | (ciph_mod_set & 0xf);
+
+ return rsl_encryption_cmd(msg);
+}
+
+static void gsm48_cell_desc(struct gsm48_cell_desc *cd,
+ const struct gsm_bts *bts)
+{
+ cd->ncc = (bts->bsic >> 3 & 0x7);
+ cd->bcc = (bts->bsic & 0x7);
+ cd->arfcn_hi = bts->c0->arfcn >> 8;
+ cd->arfcn_lo = bts->c0->arfcn & 0xff;
+}
+
+void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd,
+ const struct gsm_lchan *lchan)
+{
+ uint16_t arfcn = lchan->ts->trx->arfcn & 0x3ff;
+
+ cd->chan_nr = gsm_lchan2chan_nr(lchan);
+ if (!lchan->ts->hopping.enabled) {
+ cd->h0.tsc = gsm_ts_tsc(lchan->ts);
+ cd->h0.h = 0;
+ cd->h0.arfcn_high = arfcn >> 8;
+ cd->h0.arfcn_low = arfcn & 0xff;
+ } else {
+ cd->h1.tsc = gsm_ts_tsc(lchan->ts);
+ cd->h1.h = 1;
+ cd->h1.maio_high = lchan->ts->hopping.maio >> 2;
+ cd->h1.maio_low = lchan->ts->hopping.maio & 0x03;
+ cd->h1.hsn = lchan->ts->hopping.hsn;
+ }
+}
+
+/*! \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[in] mr multi-rate configuration to encode
+ * \param[in] modes array describing the AMR modes
+ * \returns 0 on success */
+int gsm48_multirate_config(uint8_t *lv, const struct amr_multirate_conf *mr, const struct amr_mode *modes)
+{
+ int num = 0, i;
+
+ for (i = 0; i < 8; i++) {
+ if (((mr->gsm48_ie[1] >> i) & 1))
+ num++;
+ }
+ if (num > 4) {
+ LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with too "
+ "many modes in config.\n");
+ num = 4;
+ }
+ if (num < 1) {
+ LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with no "
+ "mode in config.\n");
+ num = 1;
+ }
+
+ lv[0] = (num == 1) ? 2 : (num + 2);
+ memcpy(lv + 1, mr->gsm48_ie, 2);
+ if (num == 1)
+ return 0;
+
+ lv[3] = modes[0].threshold & 0x3f;
+ lv[4] = modes[0].hysteresis << 4;
+ if (num == 2)
+ return 0;
+ lv[4] |= (modes[1].threshold & 0x3f) >> 2;
+ lv[5] = modes[1].threshold << 6;
+ lv[5] |= (modes[1].hysteresis & 0x0f) << 2;
+ if (num == 3)
+ return 0;
+ lv[5] |= (modes[2].threshold & 0x3f) >> 4;
+ lv[6] = modes[2].threshold << 4;
+ lv[6] |= modes[2].hysteresis & 0x0f;
+
+ return 0;
+}
+
+#define GSM48_HOCMD_CCHDESC_LEN 16
+
+/* Chapter 9.1.15: Handover Command */
+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_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));
+
+ msg->lchan = old_lchan;
+ 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);
+ ho->ho_ref = ho_ref;
+ ho->power_command = power_command;
+
+ if (new_lchan->ts->hopping.enabled) {
+ struct gsm_bts *bts = new_lchan->ts->trx->bts;
+ struct gsm48_system_information_type_1 *si1;
+ uint8_t *cur;
+
+ si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1);
+ /* Copy the Cell Chan Desc (ARFCNS in this cell) */
+ msgb_put_u8(msg, GSM48_IE_CELL_CH_DESC);
+ cur = msgb_put(msg, GSM48_HOCMD_CCHDESC_LEN);
+ memcpy(cur, si1->cell_channel_description,
+ GSM48_HOCMD_CCHDESC_LEN);
+ /* Copy the Mobile Allocation */
+ msgb_tlv_put(msg, GSM48_IE_MA_BEFORE,
+ new_lchan->ts->hopping.ma_len,
+ new_lchan->ts->hopping.ma_data);
+ }
+ /* FIXME: optional bits for type of synchronization? */
+
+ 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)
+{
+ 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));
+
+ DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode);
+
+ msg->lchan = dest_lchan;
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_ASS_CMD;
+
+ /*
+ * fill the channel information element, this code
+ * should probably be shared with rsl_rx_chan_rqd(),
+ * gsm48_lchan_modify(). But beware that 10.5.2.5
+ * 10.5.2.5.a have slightly different semantic for
+ * 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);
+ ass->power_command = power_command;
+
+ /* optional: cell channel description */
+
+ msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode);
+
+ /* mobile allocation in case of hopping */
+ if (lchan->ts->hopping.enabled) {
+ msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, lchan->ts->hopping.ma_len,
+ lchan->ts->hopping.ma_data);
+ }
+
+ /* in case of multi rate we need to attach a config */
+ mr_config_for_ms(lchan, msg);
+
+ return gsm48_sendmsg(msg);
+}
+
+/* 9.1.5 Channel mode modify: Modify the mode on the MS side */
+int gsm48_lchan_modify(struct gsm_lchan *lchan, uint8_t mode)
+{
+ 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));
+
+ DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", 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);
+ cmm->mode = mode;
+
+ /* in case of multi rate we need to attach a config */
+ mr_config_for_ms(lchan, msg);
+
+ return gsm48_sendmsg(msg);
+}
+
+int gsm48_rx_rr_modif_ack(struct msgb *msg)
+{
+ int rc;
+ struct gsm48_hdr *gh = msgb_l3(msg);
+ struct gsm48_chan_mode_modify *mod =
+ (struct gsm48_chan_mode_modify *) gh->data;
+
+ DEBUGP(DRR, "CHANNEL MODE MODIFY ACK\n");
+
+ if (mod->mode != msg->lchan->tch_mode) {
+ LOGP(DRR, LOGL_ERROR, "CHANNEL MODE change failed. Wanted: %d Got: %d\n",
+ msg->lchan->tch_mode, 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;
+ }
+
+ /* We've successfully modified the MS side of the channel,
+ * now go on to modify the BTS side of the channel */
+ rc = rsl_chan_mode_modify_req(msg->lchan);
+
+ /* FIXME: we not only need to do this after mode modify, but
+ * also after channel activation */
+ if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && mod->mode != GSM48_CMODE_SIGN)
+ rsl_ipacc_crcx(msg->lchan);
+ return rc;
+}
+
+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 gsm_meas_rep_cell *mrc;
+
+ if (gh->msg_type != GSM48_MT_RR_MEAS_REP)
+ return -EINVAL;
+
+ if (data[0] & 0x80)
+ rep->flags |= MEAS_REP_F_BA1;
+ if (data[0] & 0x40)
+ rep->flags |= MEAS_REP_F_UL_DTX;
+ if ((data[1] & 0x40) == 0x00)
+ rep->flags |= MEAS_REP_F_DL_VALID;
+
+ rep->dl.full.rx_lev = data[0] & 0x3f;
+ rep->dl.sub.rx_lev = data[1] & 0x3f;
+ rep->dl.full.rx_qual = (data[2] >> 4) & 0x7;
+ rep->dl.sub.rx_qual = (data[2] >> 1) & 0x7;
+
+ rep->num_cell = ((data[3] >> 6) & 0x3) | ((data[2] & 0x01) << 2);
+ if (rep->num_cell < 1 || rep->num_cell > 6)
+ return 0;
+
+ /* 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);
+ mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5);
+ if (rep->num_cell < 2)
+ return 0;
+
+ 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);
+ mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4);
+ if (rep->num_cell < 3)
+ return 0;
+
+ 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);
+ mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3);
+ if (rep->num_cell < 4)
+ return 0;
+
+ 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);
+ mrc->bsic = data[11] >> 2;
+ if (rep->num_cell < 5)
+ return 0;
+
+ 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);
+ mrc->bsic = (data[13] >> 1) & 0x3f;
+ if (rep->num_cell < 6)
+ return 0;
+
+ 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);
+ mrc->bsic = data[15] & 0x3f;
+
+ return 0;
+}
+
+/* 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");
+
+ return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+/* 9.2.6 CM service reject */
+int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn,
+ enum gsm48_reject_value value)
+{
+ struct msgb *msg;
+
+ msg = gsm48_create_mm_serv_rej(value);
+ if (!msg) {
+ LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n");
+ return -1;
+ }
+
+ DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value);
+
+ return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
diff --git a/src/libbsc/gsm_04_80_utils.c b/src/libbsc/gsm_04_80_utils.c
new file mode 100644
index 000000000..e0db81edf
--- /dev/null
+++ b/src/libbsc/gsm_04_80_utils.c
@@ -0,0 +1,40 @@
+/* OpenBSC utility functions for 3GPP TS 04.80 */
+
+/* (C) 2016 by sysmocom s.m.f.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/gsm0480.h>
+#include <openbsc/bsc_api.h>
+
+int bsc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level,
+ const char *text)
+{
+ struct msgb *msg = gsm0480_create_ussd_notify(level, text);
+ if (!msg)
+ return -1;
+ return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int bsc_send_ussd_release_complete(struct gsm_subscriber_connection *conn)
+{
+ struct msgb *msg = gsm0480_create_ussd_release_complete();
+ if (!msg)
+ return -1;
+ return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
diff --git a/src/libbsc/handover_decision.c b/src/libbsc/handover_decision.c
new file mode 100644
index 000000000..0f07bcac6
--- /dev/null
+++ b/src/libbsc/handover_decision.c
@@ -0,0 +1,304 @@
+/* Handover Decision making for Inter-BTS (Intra-BSC) Handover. This
+ * only implements the handover algorithm/decision, but not execution
+ * of it */
+
+/* (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 <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/signal.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/handover.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+/* issue handover to a cell identified by ARFCN and BSIC */
+static int handover_to_arfcn_bsic(struct gsm_lchan *lchan,
+ uint16_t arfcn, uint8_t bsic)
+{
+ struct gsm_bts *new_bts;
+
+ /* resolve the gsm_bts structure for the best neighbor */
+ new_bts = gsm_bts_neighbor(lchan->ts->trx->bts, arfcn, bsic);
+ if (!new_bts) {
+ LOGP(DHO, LOGL_NOTICE, "unable to determine neighbor BTS "
+ "for ARFCN %u BSIC %u ?!?\n", arfcn, bsic);
+ return -EINVAL;
+ }
+
+ /* and actually try to handover to that cell */
+ return bsc_handover_start(lchan, new_bts);
+}
+
+/* did we get a RXLEV for a given cell in the given report? */
+static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr,
+ uint16_t arfcn, uint8_t bsic)
+{
+ int i;
+
+ for (i = 0; i < mr->num_cell; i++) {
+ struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+
+ /* search for matching report */
+ if (!(mrc->arfcn == arfcn && mrc->bsic == bsic))
+ continue;
+
+ mrc->flags |= MRC_F_PROCESSED;
+ return mrc->rxlev;
+ }
+ return -ENODEV;
+}
+
+/* obtain averaged rxlev for given neighbor */
+static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window)
+{
+ unsigned int i, idx;
+ int avg = 0;
+
+ idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev),
+ nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev),
+ window);
+
+ for (i = 0; i < window; i++) {
+ int j = (idx+i) % ARRAY_SIZE(nmp->rxlev);
+
+ avg += nmp->rxlev[j];
+ }
+
+ return avg / window;
+}
+
+/* find empty or evict bad neighbor */
+static struct neigh_meas_proc *find_evict_neigh(struct gsm_lchan *lchan)
+{
+ int j, worst = 999999;
+ struct neigh_meas_proc *nmp_worst = NULL;
+
+ /* first try to find an empty/unused slot */
+ for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) {
+ struct neigh_meas_proc *nmp = &lchan->neigh_meas[j];
+ if (!nmp->arfcn)
+ return nmp;
+ }
+
+ /* no empty slot found. evict worst neighbor from list */
+ for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) {
+ struct neigh_meas_proc *nmp = &lchan->neigh_meas[j];
+ int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG);
+ if (!nmp_worst || avg < worst) {
+ worst = avg;
+ nmp_worst = nmp;
+ }
+ }
+
+ return nmp_worst;
+}
+
+/* process neighbor cell measurement reports */
+static void process_meas_neigh(struct gsm_meas_rep *mr)
+{
+ int i, j, idx;
+
+ /* for each reported cell, try to update global state */
+ for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) {
+ struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j];
+ unsigned int idx;
+ int rxlev;
+
+ /* skip unused entries */
+ if (!nmp->arfcn)
+ continue;
+
+ rxlev = rxlev_for_cell_in_rep(mr, nmp->arfcn, nmp->bsic);
+ idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev);
+ if (rxlev >= 0) {
+ nmp->rxlev[idx] = rxlev;
+ nmp->last_seen_nr = mr->nr;
+ } else
+ nmp->rxlev[idx] = 0;
+ nmp->rxlev_cnt++;
+ }
+
+ /* iterate over list of reported cells, check if we did not
+ * process all of them */
+ for (i = 0; i < mr->num_cell; i++) {
+ struct gsm_meas_rep_cell *mrc = &mr->cell[i];
+ struct neigh_meas_proc *nmp;
+
+ if (mrc->flags & MRC_F_PROCESSED)
+ continue;
+
+ nmp = find_evict_neigh(mr->lchan);
+
+ nmp->arfcn = mrc->arfcn;
+ nmp->bsic = mrc->bsic;
+
+ idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev);
+ nmp->rxlev[idx] = mrc->rxlev;
+ nmp->rxlev_cnt++;
+ nmp->last_seen_nr = mr->nr;
+
+ mrc->flags |= MRC_F_PROCESSED;
+ }
+}
+
+/* attempt to do a handover */
+static int attempt_handover(struct gsm_meas_rep *mr)
+{
+ struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+ struct neigh_meas_proc *best_cell = NULL;
+ unsigned int best_better_db = 0;
+ int i, rc;
+
+ /* find the best cell in this report that is at least RXLEV_HYST
+ * better than the current serving cell */
+
+ for (i = 0; i < ARRAY_SIZE(mr->lchan->neigh_meas); i++) {
+ struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[i];
+ int avg, better;
+
+ /* skip empty slots */
+ if (nmp->arfcn == 0)
+ continue;
+
+ /* caculate average rxlev for this cell over the window */
+ avg = neigh_meas_avg(nmp, net->handover.win_rxlev_avg_neigh);
+
+ /* check if hysteresis is fulfilled */
+ if (avg < mr->dl.full.rx_lev + net->handover.pwr_hysteresis)
+ continue;
+
+ better = avg - mr->dl.full.rx_lev;
+ if (better > best_better_db) {
+ best_cell = nmp;
+ best_better_db = better;
+ }
+ }
+
+ if (!best_cell)
+ return 0;
+
+ LOGP(DHO, LOGL_INFO, "%s: Cell on ARFCN %u is better: ",
+ gsm_ts_name(mr->lchan->ts), best_cell->arfcn);
+ if (!net->handover.active) {
+ LOGPC(DHO, LOGL_INFO, "Skipping, Handover disabled\n");
+ return 0;
+ }
+
+ rc = handover_to_arfcn_bsic(mr->lchan, best_cell->arfcn, best_cell->bsic);
+ switch (rc) {
+ case 0:
+ LOGPC(DHO, LOGL_INFO, "Starting handover\n");
+ break;
+ case -ENOSPC:
+ LOGPC(DHO, LOGL_INFO, "No channel available\n");
+ break;
+ case -EBUSY:
+ LOGPC(DHO, LOGL_INFO, "Handover already active\n");
+ break;
+ default:
+ LOGPC(DHO, LOGL_ERROR, "Unknown error\n");
+ }
+ return rc;
+}
+
+/* process an already parsed measurement report and decide if we want to
+ * attempt a handover */
+static int process_meas_rep(struct gsm_meas_rep *mr)
+{
+ struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+ enum meas_rep_field dlev, dqual;
+ int av_rxlev;
+
+ /* we currently only do handover for TCH channels */
+ switch (mr->lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ break;
+ default:
+ return 0;
+ }
+
+ 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,
+ net->handover.win_rxlev_avg);
+
+ /* Interference HO */
+ if (rxlev2dbm(av_rxlev) > -85 &&
+ meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5))
+ return attempt_handover(mr);
+
+ /* Bad Quality */
+ if (meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5))
+ return attempt_handover(mr);
+
+ /* Low Level */
+ if (rxlev2dbm(av_rxlev) <= -110)
+ return attempt_handover(mr);
+
+ /* Distance */
+ if (mr->ms_l1.ta > net->handover.max_distance)
+ return attempt_handover(mr);
+
+ /* Power Budget AKA Better Cell */
+ if ((mr->nr % net->handover.pwr_interval) == 0)
+ return attempt_handover(mr);
+
+ return 0;
+
+}
+
+static int ho_dec_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct lchan_signal_data *lchan_data;
+
+ if (subsys != SS_LCHAN)
+ return 0;
+
+ lchan_data = signal_data;
+ switch (signal) {
+ case S_LCHAN_MEAS_REP:
+ process_meas_rep(lchan_data->mr);
+ break;
+ }
+
+ return 0;
+}
+
+void on_dso_load_ho_dec(void)
+{
+ osmo_signal_register_handler(SS_LCHAN, ho_dec_sig_cb, NULL);
+}
diff --git a/src/libbsc/handover_logic.c b/src/libbsc/handover_logic.c
new file mode 100644
index 000000000..4dd913b15
--- /dev/null
+++ b/src/libbsc/handover_logic.c
@@ -0,0 +1,378 @@
+/* Handover Logic for Inter-BTS (Intra-BSC) Handover. This does not
+ * actually implement the handover algorithm/decision, but executes a
+ * handover decision */
+
+/* (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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/signal.h>
+#include <osmocom/core/talloc.h>
+#include <openbsc/transaction.h>
+#include <openbsc/trau_mux.h>
+
+struct bsc_handover {
+ struct llist_head list;
+
+ struct gsm_lchan *old_lchan;
+ struct gsm_lchan *new_lchan;
+
+ struct osmo_timer_list T3103;
+
+ uint8_t ho_ref;
+};
+
+static LLIST_HEAD(bsc_handovers);
+
+static void handover_free(struct bsc_handover *ho)
+{
+ osmo_timer_del(&ho->T3103);
+ llist_del(&ho->list);
+ talloc_free(ho);
+}
+
+static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan)
+{
+ struct bsc_handover *ho;
+
+ llist_for_each_entry(ho, &bsc_handovers, list) {
+ if (ho->new_lchan == new_lchan)
+ return ho;
+ }
+
+ return NULL;
+}
+
+static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan)
+{
+ struct bsc_handover *ho;
+
+ llist_for_each_entry(ho, &bsc_handovers, list) {
+ if (ho->old_lchan == old_lchan)
+ return ho;
+ }
+
+ return NULL;
+}
+
+/*! \brief Hand over the specified logical channel to the specified new BTS.
+ * This is the main entry point for the actual handover algorithm, after the
+ * decision whether to initiate HO to a specific BTS. */
+int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts)
+{
+ struct gsm_lchan *new_lchan;
+ struct bsc_handover *ho;
+ static uint8_t ho_ref;
+ int rc;
+
+ /* don't attempt multiple handovers for the same lchan at
+ * the same time */
+ if (bsc_ho_by_old_lchan(old_lchan))
+ return -EBUSY;
+
+ DEBUGP(DHO, "(old_lchan on BTS %u, new BTS %u)\n",
+ old_lchan->ts->trx->bts->nr, bts->nr);
+
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]);
+
+ if (!old_lchan->conn) {
+ LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n");
+ return -ENOSPC;
+ }
+
+ new_lchan = lchan_alloc(bts, old_lchan->type, 0);
+ if (!new_lchan) {
+ LOGP(DHO, LOGL_NOTICE, "No free channel\n");
+ rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]);
+ return -ENOSPC;
+ }
+
+ ho = talloc_zero(tall_bsc_ctx, struct bsc_handover);
+ if (!ho) {
+ LOGP(DHO, LOGL_FATAL, "Out of Memory\n");
+ lchan_free(new_lchan);
+ return -ENOMEM;
+ }
+ ho->old_lchan = old_lchan;
+ ho->new_lchan = new_lchan;
+ ho->ho_ref = ho_ref++;
+
+ /* copy some parameters from old lchan */
+ memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr));
+ new_lchan->ms_power = old_lchan->ms_power;
+ new_lchan->bs_power = old_lchan->bs_power;
+ new_lchan->rsl_cmode = old_lchan->rsl_cmode;
+ new_lchan->tch_mode = old_lchan->tch_mode;
+ memcpy(&new_lchan->mr_ms_lv, &old_lchan->mr_ms_lv, ARRAY_SIZE(new_lchan->mr_ms_lv));
+ memcpy(&new_lchan->mr_bts_lv, &old_lchan->mr_bts_lv, ARRAY_SIZE(new_lchan->mr_bts_lv));
+
+ new_lchan->conn = old_lchan->conn;
+ new_lchan->conn->ho_lchan = new_lchan;
+
+ /* FIXME: do we have a better idea of the timing advance? */
+ rc = rsl_chan_activate_lchan(new_lchan, RSL_ACT_INTER_ASYNC, ho->ho_ref);
+ if (rc < 0) {
+ LOGP(DHO, LOGL_ERROR, "could not activate channel\n");
+ new_lchan->conn->ho_lchan = NULL;
+ new_lchan->conn = NULL;
+ talloc_free(ho);
+ lchan_free(new_lchan);
+ return rc;
+ }
+
+ rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
+ llist_add(&ho->list, &bsc_handovers);
+ /* we continue in the SS_LCHAN handler / ho_chan_activ_ack */
+
+ return 0;
+}
+
+void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan)
+{
+ struct bsc_handover *ho;
+
+ ho = bsc_ho_by_new_lchan(conn->ho_lchan);
+
+
+ if (!ho && conn->ho_lchan)
+ LOGP(DHO, LOGL_ERROR, "BUG: We lost some state.\n");
+
+ if (!ho) {
+ LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+ return;
+ }
+
+ conn->ho_lchan->conn = NULL;
+ conn->ho_lchan = NULL;
+
+ if (free_lchan)
+ lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END);
+
+ handover_free(ho);
+}
+
+/* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */
+static void ho_T3103_cb(void *_ho)
+{
+ struct bsc_handover *ho = _ho;
+ struct gsm_network *net = ho->new_lchan->ts->trx->bts->network;
+
+ DEBUGP(DHO, "HO T3103 expired\n");
+ rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT]);
+
+ ho->new_lchan->conn->ho_lchan = NULL;
+ ho->new_lchan->conn = NULL;
+ lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END);
+ handover_free(ho);
+}
+
+/* RSL has acknowledged activation of the new lchan */
+static int ho_chan_activ_ack(struct gsm_lchan *new_lchan)
+{
+ struct bsc_handover *ho;
+
+ /* we need to check if this channel activation is related to
+ * a handover at all (and if, which particular handover) */
+ ho = bsc_ho_by_new_lchan(new_lchan);
+ if (!ho)
+ return -ENODEV;
+
+ DEBUGP(DHO, "handover activate ack, send HO Command\n");
+
+ /* we can now send the 04.08 HANDOVER COMMAND to the MS
+ * using the old lchan */
+
+ gsm48_send_ho_cmd(ho->old_lchan, new_lchan, 0, ho->ho_ref);
+
+ /* start T3103. We can continue either with T3103 expiration,
+ * 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */
+ osmo_timer_setup(&ho->T3103, ho_T3103_cb, ho);
+ osmo_timer_schedule(&ho->T3103, 10, 0);
+
+ /* create a RTP connection */
+ if (is_ipaccess_bts(new_lchan->ts->trx->bts))
+ rsl_ipacc_crcx(new_lchan);
+
+ return 0;
+}
+
+/* RSL has not acknowledged activation of the new lchan */
+static int ho_chan_activ_nack(struct gsm_lchan *new_lchan)
+{
+ struct bsc_handover *ho;
+
+ ho = bsc_ho_by_new_lchan(new_lchan);
+ if (!ho) {
+ LOGP(DHO, LOGL_INFO, "ACT NACK: unable to find HO record\n");
+ return -ENODEV;
+ }
+
+ new_lchan->conn->ho_lchan = NULL;
+ new_lchan->conn = NULL;
+ handover_free(ho);
+
+ /* FIXME: maybe we should try to allocate a new LCHAN here? */
+
+ return 0;
+}
+
+/* GSM 04.08 HANDOVER COMPLETE has been received on new channel */
+static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan)
+{
+ struct gsm_network *net;
+ struct bsc_handover *ho;
+
+ ho = bsc_ho_by_new_lchan(new_lchan);
+ if (!ho) {
+ LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+ return -ENODEV;
+ }
+
+ net = new_lchan->ts->trx->bts->network;
+ LOGP(DHO, LOGL_INFO, "Subscriber %s HO from BTS %u->%u on ARFCN "
+ "%u->%u\n", subscr_name(ho->old_lchan->conn->subscr),
+ ho->old_lchan->ts->trx->bts->nr, new_lchan->ts->trx->bts->nr,
+ ho->old_lchan->ts->trx->arfcn, new_lchan->ts->trx->arfcn);
+
+ rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_COMPLETED]);
+
+ osmo_timer_del(&ho->T3103);
+
+ /* switch TRAU muxer for E1 based BTS from one channel to another */
+ if (is_e1_bts(new_lchan->conn->bts))
+ switch_trau_mux(ho->old_lchan, new_lchan);
+
+ /* Replace the ho lchan with the primary one */
+ if (ho->old_lchan != new_lchan->conn->lchan)
+ LOGP(DHO, LOGL_ERROR, "Primary lchan changed during handover.\n");
+
+ if (new_lchan != new_lchan->conn->ho_lchan)
+ LOGP(DHO, LOGL_ERROR, "Handover channel changed during this handover.\n");
+
+ new_lchan->conn->ho_lchan = NULL;
+ new_lchan->conn->lchan = new_lchan;
+ ho->old_lchan->conn = NULL;
+
+ lchan_release(ho->old_lchan, 0, RSL_REL_LOCAL_END);
+
+ handover_free(ho);
+ return 0;
+}
+
+/* GSM 04.08 HANDOVER FAIL has been received */
+static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
+{
+ struct gsm_network *net = old_lchan->ts->trx->bts->network;
+ struct bsc_handover *ho;
+ struct gsm_lchan *new_lchan;
+
+ ho = bsc_ho_by_old_lchan(old_lchan);
+ if (!ho) {
+ LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+ return -ENODEV;
+ }
+
+ rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]);
+
+ new_lchan = ho->new_lchan;
+
+ /* release the channel and forget about it */
+ ho->new_lchan->conn->ho_lchan = NULL;
+ ho->new_lchan->conn = NULL;
+ handover_free(ho);
+
+ lchan_release(new_lchan, 0, RSL_REL_LOCAL_END);
+
+
+ return 0;
+}
+
+/* GSM 08.58 HANDOVER DETECT has been received */
+static int ho_rsl_detect(struct gsm_lchan *new_lchan)
+{
+ struct bsc_handover *ho;
+
+ ho = bsc_ho_by_new_lchan(new_lchan);
+ if (!ho) {
+ LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+ return -ENODEV;
+ }
+
+ /* FIXME: do we actually want to do something here ? */
+
+ return 0;
+}
+
+static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct lchan_signal_data *lchan_data;
+ struct gsm_lchan *lchan;
+
+ lchan_data = signal_data;
+ switch (subsys) {
+ case SS_LCHAN:
+ lchan = lchan_data->lchan;
+ switch (signal) {
+ case S_LCHAN_ACTIVATE_ACK:
+ return ho_chan_activ_ack(lchan);
+ case S_LCHAN_ACTIVATE_NACK:
+ return ho_chan_activ_nack(lchan);
+ case S_LCHAN_HANDOVER_DETECT:
+ return ho_rsl_detect(lchan);
+ case S_LCHAN_HANDOVER_COMPL:
+ return ho_gsm48_ho_compl(lchan);
+ case S_LCHAN_HANDOVER_FAIL:
+ return ho_gsm48_ho_fail(lchan);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan)
+{
+ struct bsc_handover *ho;
+ ho = bsc_ho_by_new_lchan(new_lchan);
+ if (!ho)
+ return NULL;
+ return ho->old_lchan;
+}
+
+static __attribute__((constructor)) void on_dso_load_ho_logic(void)
+{
+ osmo_signal_register_handler(SS_LCHAN, ho_logic_sig_cb, NULL);
+}
diff --git a/src/libbsc/meas_proc.c b/src/libbsc/meas_proc.c
new file mode 100644
index 000000000..5b97e74ee
--- /dev/null
+++ b/src/libbsc/meas_proc.c
@@ -0,0 +1,84 @@
+/* Measurement Processing */
+
+/* (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 <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/signal.h>
+
+/* process an already parsed measurement report */
+static int process_meas_rep(struct gsm_meas_rep *mr)
+{
+ struct gsm_meas_rep_cell *mr_cell = NULL;
+ unsigned int best_better_db;
+ int i;
+
+ /* FIXME: implement actual averaging over multiple measurement
+ * reports */
+
+ /* find the best cell in this report that is at least RXLEV_HYST
+ * better than the current serving cell */
+ for (i = 0; i < mr->num_cell; i++) {
+ unsigned int better;
+ if (mr->cell[i].rxlev < mr->dl.full.rx_lev + RXLEV_HYST)
+ continue;
+
+ better = mr->cell[i].rxlev - mr->dl.full.rx_lev;
+ if (better > best_better_db) {
+ mr_cell = &mr->cell[i];
+ best_better_db = better;
+ }
+ }
+
+ if (mr_cell)
+ return handover_to_arfcn_bsic(mr->lchan, mr_cell->arfcn,
+ mr_cell->bsic);
+ return 0;
+}
+
+static int meas_proc_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct gsm_lchan *lchan;
+ struct gsm_meas_rep *mr;
+
+ if (subsys != SS_LCHAN)
+ return 0;
+
+ switch (signal) {
+ case S_LCHAN_MEAS_REP:
+ mr = signal_data;
+ process_meas_rep(mr);
+ break;
+ }
+
+ return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_meas(void)
+{
+ osmo_signal_register_handler(SS_LCHAN, meas_proc_sig_cb, NULL);
+}
diff --git a/src/libbsc/meas_rep.c b/src/libbsc/meas_rep.c
new file mode 100644
index 000000000..808103d28
--- /dev/null
+++ b/src/libbsc/meas_rep.c
@@ -0,0 +1,115 @@
+/* Measurement Report Processing */
+
+/* (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 <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+
+static int get_field(const struct gsm_meas_rep *rep,
+ enum meas_rep_field field)
+{
+ switch (field) {
+ case MEAS_REP_DL_RXLEV_FULL:
+ return rep->dl.full.rx_lev;
+ case MEAS_REP_DL_RXLEV_SUB:
+ return rep->dl.sub.rx_lev;
+ case MEAS_REP_DL_RXQUAL_FULL:
+ return rep->dl.full.rx_qual;
+ case MEAS_REP_DL_RXQUAL_SUB:
+ return rep->dl.sub.rx_qual;
+ case MEAS_REP_UL_RXLEV_FULL:
+ return rep->ul.full.rx_lev;
+ case MEAS_REP_UL_RXLEV_SUB:
+ return rep->ul.sub.rx_lev;
+ case MEAS_REP_UL_RXQUAL_FULL:
+ return rep->ul.full.rx_qual;
+ case MEAS_REP_UL_RXQUAL_SUB:
+ return rep->ul.sub.rx_qual;
+ }
+
+ return 0;
+}
+
+
+unsigned int calc_initial_idx(unsigned int array_size,
+ unsigned int meas_rep_idx,
+ unsigned int num_values)
+{
+ int offs, idx;
+
+ /* from which element do we need to start if we're interested
+ * in an average of 'num' elements */
+ offs = meas_rep_idx - num_values;
+
+ if (offs < 0)
+ idx = array_size + offs;
+ else
+ idx = offs;
+
+ return idx;
+}
+
+/* obtain an average over the last 'num' fields in the meas reps */
+int get_meas_rep_avg(const struct gsm_lchan *lchan,
+ enum meas_rep_field field, unsigned int num)
+{
+ unsigned int i, idx;
+ int avg = 0;
+
+ if (num < 1)
+ return 0;
+
+ idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+ lchan->meas_rep_idx, num);
+
+ for (i = 0; i < num; i++) {
+ int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep);
+
+ avg += get_field(&lchan->meas_rep[j], field);
+ }
+
+ return avg / num;
+}
+
+/* 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)
+{
+ unsigned int i, idx;
+ int count = 0;
+
+ idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep),
+ lchan->meas_rep_idx, m);
+
+ for (i = 0; i < m; i++) {
+ int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep);
+ int val = get_field(&lchan->meas_rep[j], field);
+
+ if (val >= be)
+ count++;
+
+ if (count >= n)
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/src/libbsc/net_init.c b/src/libbsc/net_init.c
new file mode 100644
index 000000000..bc5ed3510
--- /dev/null
+++ b/src/libbsc/net_init.c
@@ -0,0 +1,69 @@
+/* (C) 2008-2010 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 <openbsc/common_cs.h>
+#include <openbsc/osmo_bsc.h>
+#include <openbsc/bsc_msc_data.h>
+
+struct gsm_network *bsc_network_init(void *ctx,
+ uint16_t country_code,
+ uint16_t network_code,
+ mncc_recv_cb_t mncc_recv)
+{
+ struct gsm_network *net;
+
+ net = gsm_network_init(ctx, country_code, network_code, mncc_recv);
+
+ net->bsc_data = talloc_zero(net, struct osmo_bsc_data);
+ if (!net->bsc_data) {
+ talloc_free(net);
+ return NULL;
+ }
+
+ /* Init back pointer */
+ net->bsc_data->auto_off_timeout = -1;
+ net->bsc_data->network = net;
+ INIT_LLIST_HEAD(&net->bsc_data->mscs);
+
+ net->num_bts = 0;
+ net->reject_cause = GSM48_REJECT_ROAMING_NOT_ALLOWED;
+ net->T3101 = GSM_T3101_DEFAULT;
+ net->T3105 = GSM_T3105_DEFAULT;
+ net->T3113 = GSM_T3113_DEFAULT;
+ net->T3122 = GSM_T3122_DEFAULT;
+ /* FIXME: initialize all other timers! */
+
+ /* default set of handover parameters */
+ net->handover.win_rxlev_avg = 10;
+ net->handover.win_rxqual_avg = 1;
+ net->handover.win_rxlev_avg_neigh = 10;
+ net->handover.pwr_interval = 6;
+ net->handover.pwr_hysteresis = 3;
+ net->handover.max_distance = 9999;
+
+ INIT_LLIST_HEAD(&net->bts_list);
+
+ /* init statistics */
+ net->bsc_ctrs = rate_ctr_group_alloc(net, &bsc_ctrg_desc, 0);
+
+ gsm_net_update_ctype(net);
+
+ return net;
+}
+
diff --git a/src/libbsc/paging.c b/src/libbsc/paging.c
new file mode 100644
index 000000000..78e39c558
--- /dev/null
+++ b/src/libbsc/paging.c
@@ -0,0 +1,449 @@
+/* Paging helper and manager.... */
+/* (C) 2009,2013 by Holger Hans Peter Freyther <zecke@selfish.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/>.
+ *
+ */
+
+/*
+ * Relevant specs:
+ * 12.21:
+ * - 9.4.12 for CCCH Local Threshold
+ *
+ * 05.58:
+ * - 8.5.2 CCCH Load indication
+ * - 9.3.15 Paging Load
+ *
+ * Approach:
+ * - Send paging command to subscriber
+ * - On Channel Request we will remember the reason
+ * - After the ACK we will request the identity
+ * - Then we will send assign the gsm_subscriber and
+ * - and call a callback
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0502.h>
+
+#include <openbsc/bsc_subscriber.h>
+#include <openbsc/paging.h>
+#include <openbsc/debug.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_api.h>
+
+void *tall_paging_ctx;
+
+#define PAGING_TIMER 0, 500000
+
+/*
+ * 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)
+{
+ osmo_timer_del(&to_be_deleted->T3113);
+ llist_del(&to_be_deleted->entry);
+ bsc_subscr_put(to_be_deleted->bsub);
+ talloc_free(to_be_deleted);
+}
+
+static void page_ms(struct gsm_paging_request *request)
+{
+ uint8_t mi[128];
+ unsigned int mi_len;
+ unsigned int page_group;
+ struct gsm_bts *bts = request->bts;
+
+ /* the bts is down.. we will just wait for the paging to expire */
+ if (!bts->oml_link)
+ return;
+
+ log_set_context(LOG_CTX_BSC_SUBSCR, request->bsub);
+
+ LOGP(DPAG, LOGL_INFO, "Going to send paging commands: imsi: %s tmsi: "
+ "0x%08x for ch. type %d (attempt %d)\n", request->bsub->imsi,
+ request->bsub->tmsi, request->chan_type, request->attempts);
+
+ if (request->bsub->tmsi == GSM_RESERVED_TMSI)
+ mi_len = gsm48_generate_mid_from_imsi(mi, request->bsub->imsi);
+ else
+ mi_len = gsm48_generate_mid_from_tmsi(mi, request->bsub->tmsi);
+
+ page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc,
+ str_to_imsi(request->bsub->imsi));
+ gsm0808_page(bts, page_group, mi_len, mi, request->chan_type);
+ log_set_context(LOG_CTX_BSC_SUBSCR, NULL);
+}
+
+static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts)
+{
+ if (llist_empty(&paging_bts->pending_requests))
+ return;
+
+ if (!osmo_timer_pending(&paging_bts->work_timer))
+ osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER);
+}
+
+
+static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts);
+static void paging_give_credit(void *data)
+{
+ struct gsm_bts_paging_state *paging_bts = data;
+
+ LOGP(DPAG, LOGL_NOTICE, "No slots available on bts nr %d\n", paging_bts->bts->nr);
+ paging_bts->available_slots = 20;
+ paging_handle_pending_requests(paging_bts);
+}
+
+static int can_send_pag_req(struct gsm_bts *bts, int rsl_type)
+{
+ struct pchan_load pl;
+ int count;
+
+ memset(&pl, 0, sizeof(pl));
+ bts_chan_load(&pl, bts);
+
+ switch (rsl_type) {
+ case RSL_CHANNEED_TCH_F:
+ case RSL_CHANNEED_TCH_ForH:
+ goto count_tch;
+ break;
+ case RSL_CHANNEED_SDCCH:
+ goto count_sdcch;
+ break;
+ case RSL_CHANNEED_ANY:
+ default:
+ if (bts->network->pag_any_tch)
+ goto count_tch;
+ else
+ goto count_sdcch;
+ break;
+ }
+
+ return 0;
+
+ /* could available SDCCH */
+count_sdcch:
+ count = 0;
+ count += pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].total
+ - pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].used;
+ count += pl.pchan[GSM_PCHAN_CCCH_SDCCH4].total
+ - pl.pchan[GSM_PCHAN_CCCH_SDCCH4].used;
+ return bts->paging.free_chans_need > count;
+
+count_tch:
+ count = 0;
+ count += pl.pchan[GSM_PCHAN_TCH_F].total
+ - pl.pchan[GSM_PCHAN_TCH_F].used;
+ if (bts->network->neci)
+ count += pl.pchan[GSM_PCHAN_TCH_H].total
+ - pl.pchan[GSM_PCHAN_TCH_H].used;
+ return bts->paging.free_chans_need > count;
+}
+
+/*
+ * 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.
+ */
+static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts)
+{
+ struct gsm_paging_request *request = NULL;
+
+ /*
+ * 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 */
+ 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;
+ }
+
+ /* 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);
+}
+
+static void paging_worker(void *data)
+{
+ struct gsm_bts_paging_state *paging_bts = data;
+
+ paging_handle_pending_requests(paging_bts);
+}
+
+static void paging_init_if_needed(struct gsm_bts *bts)
+{
+ if (bts->paging.bts)
+ return;
+
+ bts->paging.bts = bts;
+ INIT_LLIST_HEAD(&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;
+}
+
+static int paging_pending_request(struct gsm_bts_paging_state *bts,
+ struct bsc_subscr *bsub)
+{
+ struct gsm_paging_request *req;
+
+ llist_for_each_entry(req, &bts->pending_requests, entry) {
+ if (bsub == req->bsub)
+ return 1;
+ }
+
+ return 0;
+}
+
+static void paging_T3113_expired(void *data)
+{
+ struct gsm_paging_request *req = (struct gsm_paging_request *)data;
+ void *cbfn_param;
+ gsm_cbfn *cbfn;
+ int msg;
+
+ 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));
+
+ /* must be destroyed before calling cbfn, to prevent double free */
+ rate_ctr_inc(&req->bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_EXPIRED]);
+ cbfn_param = req->cbfn_param;
+ cbfn = req->cbfn;
+
+ /* did we ever manage to page the subscriber */
+ msg = req->attempts > 0 ? GSM_PAGING_EXPIRED : GSM_PAGING_BUSY;
+
+ /* destroy it now. Do not access req afterwards */
+ paging_remove_request(&req->bts->paging, req);
+
+ if (cbfn)
+ cbfn(GSM_HOOK_RR_PAGING, msg, NULL, NULL,
+ cbfn_param);
+
+}
+
+static int _paging_request(struct gsm_bts *bts, struct bsc_subscr *bsub,
+ int type, gsm_cbfn *cbfn, void *data)
+{
+ struct gsm_bts_paging_state *bts_entry = &bts->paging;
+ struct gsm_paging_request *req;
+
+ if (paging_pending_request(bts_entry, bsub)) {
+ LOGP(DPAG, LOGL_INFO, "Paging request already pending for %s\n",
+ bsc_subscr_name(bsub));
+ return -EEXIST;
+ }
+
+ LOGP(DPAG, LOGL_DEBUG, "Start paging of subscriber %s on bts %d.\n",
+ bsc_subscr_name(bsub), bts->nr);
+ req = talloc_zero(tall_paging_ctx, struct gsm_paging_request);
+ req->bsub = bsc_subscr_get(bsub);
+ req->bts = bts;
+ req->chan_type = type;
+ req->cbfn = cbfn;
+ req->cbfn_param = data;
+ osmo_timer_setup(&req->T3113, paging_T3113_expired, req);
+ osmo_timer_schedule(&req->T3113, bts->network->T3113, 0);
+ llist_add_tail(&req->entry, &bts_entry->pending_requests);
+ paging_schedule_if_needed(bts_entry);
+
+ return 0;
+}
+
+int paging_request_bts(struct gsm_bts *bts, struct bsc_subscr *bsub,
+ int type, gsm_cbfn *cbfn, void *data)
+{
+ int rc;
+
+ /* skip all currently inactive TRX */
+ if (!trx_is_usable(bts->c0))
+ return 0;
+
+ /* maybe it is the first time we use it */
+ paging_init_if_needed(bts);
+
+ /* Trigger paging, pass any error to the caller */
+ rc = _paging_request(bts, bsub, type, cbfn, data);
+ if (rc < 0)
+ return rc;
+ return 1;
+}
+
+int paging_request(struct gsm_network *network, struct bsc_subscr *bsub,
+ int type, gsm_cbfn *cbfn, void *data)
+{
+ struct gsm_bts *bts = NULL;
+ int num_pages = 0;
+
+ rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_PAGING_ATTEMPTED]);
+
+ /* start paging subscriber on all BTS within Location Area */
+ do {
+ int rc;
+
+ bts = gsm_bts_by_lac(network, bsub->lac, bts);
+ if (!bts)
+ break;
+
+ rc = paging_request_bts(bts, bsub, type, cbfn, data);
+ if (rc < 0) {
+ paging_request_stop(&network->bts_list, NULL, bsub,
+ NULL, NULL);
+ return rc;
+ }
+ num_pages += rc;
+ } while (1);
+
+ if (num_pages == 0)
+ rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_PAGING_DETACHED]);
+
+ return num_pages;
+}
+
+
+/* we consciously ignore the type of the request here */
+static void _paging_request_stop(struct gsm_bts *bts, struct bsc_subscr *bsub,
+ struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm_bts_paging_state *bts_entry = &bts->paging;
+ struct gsm_paging_request *req, *req2;
+
+ paging_init_if_needed(bts);
+
+ llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests,
+ entry) {
+ if (req->bsub == bsub) {
+ gsm_cbfn *cbfn = req->cbfn;
+ void *param = req->cbfn_param;
+
+ /* now give up the data structure */
+ paging_remove_request(&bts->paging, req);
+ req = NULL;
+
+ if (conn && cbfn) {
+ LOGP(DPAG, LOGL_DEBUG, "Stop paging %s on bts %d, calling cbfn.\n", bsub->imsi, bts->nr);
+ cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
+ msg, conn, param);
+ } else
+ LOGP(DPAG, LOGL_DEBUG, "Stop paging %s on bts %d silently.\n", bsub->imsi, bts->nr);
+ break;
+ }
+ }
+}
+
+/* Stop paging on all other bts' */
+void paging_request_stop(struct llist_head *bts_list,
+ struct gsm_bts *_bts, struct bsc_subscr *bsub,
+ struct gsm_subscriber_connection *conn,
+ struct msgb *msg)
+{
+ struct gsm_bts *bts;
+
+ log_set_context(LOG_CTX_BSC_SUBSCR, bsub);
+
+ /* Stop this first and dispatch the request */
+ if (_bts)
+ _paging_request_stop(_bts, bsub, conn, msg);
+
+ /* Make sure to cancel this everywhere else */
+ llist_for_each_entry(bts, bts_list, list) {
+ /* Sort of an optimization. */
+ if (bts == _bts)
+ continue;
+ _paging_request_stop(bts, bsub, NULL, NULL);
+ }
+}
+
+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);
+}
+
+unsigned int paging_pending_requests_nr(struct gsm_bts *bts)
+{
+ unsigned int requests = 0;
+ struct gsm_paging_request *req;
+
+ paging_init_if_needed(bts);
+
+ llist_for_each_entry(req, &bts->paging.pending_requests, entry)
+ ++requests;
+
+ return requests;
+}
+
+/**
+ * Find any paging data for the given subscriber at the given BTS.
+ */
+void *paging_get_data(struct gsm_bts *bts, struct bsc_subscr *bsub)
+{
+ struct gsm_paging_request *req;
+
+ llist_for_each_entry(req, &bts->paging.pending_requests, entry)
+ if (req->bsub == bsub)
+ return req->cbfn_param;
+
+ return NULL;
+}
diff --git a/src/libbsc/pcu_sock.c b/src/libbsc/pcu_sock.c
new file mode 100644
index 000000000..98e12fad4
--- /dev/null
+++ b/src/libbsc/pcu_sock.c
@@ -0,0 +1,742 @@
+/* pcu_sock.c: Connect from PCU via unix domain socket */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2012 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2012 by Holger Hans Peter Freyther
+ * 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
+ * (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.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gsm/l1sap.h>
+#include <osmocom/gsm/gsm0502.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/pcu_if.h>
+#include <openbsc/pcuif_proto.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/abis_rsl.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 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",
+};
+
+/* Check if BTS has a PCU connection */
+static bool pcu_connected(struct gsm_bts *bts)
+{
+ struct pcu_sock_state *state = bts->pcu_state;
+
+ if (!state)
+ return false;
+ if (state->conn_bfd.fd <= 0)
+ return false;
+ return true;
+}
+
+/*
+ * PCU messages
+ */
+
+/* Set up an message buffer to package an pcu interface message */
+struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+
+ msg = msgb_alloc(sizeof(struct gsm_pcu_if), "pcu_sock_tx");
+ if (!msg)
+ return NULL;
+
+ msgb_put(msg, sizeof(struct gsm_pcu_if));
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ pcu_prim->msg_type = msg_type;
+ pcu_prim->bts_nr = bts_nr;
+
+ return msg;
+}
+
+/* Helper function exclusivly used by pcu_if_signal_cb() */
+static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) {
+ if (ts->pchan == GSM_PCHAN_PDCH)
+ return true;
+ if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) {
+ /* When we're busy deactivating the PDCH, we first set
+ * DEACT_PENDING, tell the PCU about it and wait for a
+ * response. So DEACT_PENDING means "no PDCH" to the PCU.
+ * Similarly, when we're activating PDCH, we set the
+ * ACT_PENDING and wait for an activation response from the
+ * PCU, so ACT_PENDING means "is PDCH". */
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return !(ts->flags & TS_F_PDCH_DEACT_PENDING);
+ else
+ return (ts->flags & TS_F_PDCH_ACT_PENDING);
+ }
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ /*
+ * When we're busy de-/activating the PDCH, we first set
+ * ts->dyn.pchan_want, tell the PCU about it and wait for a
+ * response. So only care about dyn.pchan_want here.
+ */
+ return ts->dyn.pchan_want == GSM_PCHAN_PDCH;
+ }
+ return false;
+}
+
+/* Send BTS properties to the PCU */
+static int pcu_tx_info_ind(struct gsm_bts *bts)
+{
+ struct msgb *msg;
+ 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_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ int i, j;
+
+ OSMO_ASSERT(bts);
+ OSMO_ASSERT(bts->network);
+
+ LOGP(DPCU, LOGL_INFO, "Sending info for BTS %d\n",bts->nr);
+
+ rlcc = &bts->gprs.cell.rlc_cfg;
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ 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;
+
+ /* RAI */
+ info_ind->mcc = bts->network->country_code;
+ info_ind->mnc = bts->network->network_code;
+ info_ind->lac = bts->location_area_code;
+ info_ind->rac = bts->gprs.rac;
+
+ /* NSE */
+ info_ind->nsei = bts->gprs.nse.nsei;
+ memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7);
+ memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11);
+
+ /* cell attributes */
+ info_ind->cell_id = bts->cell_identity;
+ info_ind->repeat_time = rlcc->paging.repeat_time;
+ info_ind->repeat_count = rlcc->paging.repeat_count;
+ info_ind->bvci = bts->gprs.cell.bvci;
+ info_ind->t3142 = rlcc->parameter[RLC_T3142];
+ info_ind->t3169 = rlcc->parameter[RLC_T3169];
+ info_ind->t3191 = rlcc->parameter[RLC_T3191];
+ info_ind->t3193_10ms = rlcc->parameter[RLC_T3193];
+ info_ind->t3195 = rlcc->parameter[RLC_T3195];
+ info_ind->n3101 = rlcc->parameter[RLC_N3101];
+ info_ind->n3103 = rlcc->parameter[RLC_N3103];
+ info_ind->n3105 = rlcc->parameter[RLC_N3105];
+ info_ind->cv_countdown = rlcc->parameter[CV_COUNTDOWN];
+ if (rlcc->cs_mask & (1 << GPRS_CS1))
+ info_ind->flags |= PCU_IF_FLAG_CS1;
+ if (rlcc->cs_mask & (1 << GPRS_CS2))
+ info_ind->flags |= PCU_IF_FLAG_CS2;
+ if (rlcc->cs_mask & (1 << GPRS_CS3))
+ info_ind->flags |= PCU_IF_FLAG_CS3;
+ if (rlcc->cs_mask & (1 << GPRS_CS4))
+ info_ind->flags |= PCU_IF_FLAG_CS4;
+ if (bts->gprs.mode == BTS_GPRS_EGPRS) {
+ if (rlcc->cs_mask & (1 << GPRS_MCS1))
+ info_ind->flags |= PCU_IF_FLAG_MCS1;
+ if (rlcc->cs_mask & (1 << GPRS_MCS2))
+ info_ind->flags |= PCU_IF_FLAG_MCS2;
+ if (rlcc->cs_mask & (1 << GPRS_MCS3))
+ info_ind->flags |= PCU_IF_FLAG_MCS3;
+ if (rlcc->cs_mask & (1 << GPRS_MCS4))
+ info_ind->flags |= PCU_IF_FLAG_MCS4;
+ if (rlcc->cs_mask & (1 << GPRS_MCS5))
+ info_ind->flags |= PCU_IF_FLAG_MCS5;
+ if (rlcc->cs_mask & (1 << GPRS_MCS6))
+ info_ind->flags |= PCU_IF_FLAG_MCS6;
+ if (rlcc->cs_mask & (1 << GPRS_MCS7))
+ info_ind->flags |= PCU_IF_FLAG_MCS7;
+ if (rlcc->cs_mask & (1 << GPRS_MCS8))
+ info_ind->flags |= PCU_IF_FLAG_MCS8;
+ 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"
+ info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT];
+#warning "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];
+ info_ind->nsvci[i] = nsvc->nsvci;
+ info_ind->local_port[i] = nsvc->local_port;
+ info_ind->remote_port[i] = nsvc->remote_port;
+ info_ind->remote_ip[i] = nsvc->remote_ip;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(info_ind->trx); i++) {
+ trx = gsm_bts_trx_num(bts, i);
+ if (!trx)
+ continue;
+ info_ind->trx[i].hlayer1 = 0x2342;
+ info_ind->trx[i].pdch_mask = 0;
+ info_ind->trx[i].arfcn = trx->arfcn;
+ for (j = 0; j < ARRAY_SIZE(trx->ts); j++) {
+ ts = &trx->ts[j];
+ if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
+ && ts_should_be_pdch(ts)) {
+ info_ind->trx[i].pdch_mask |= (1 << j);
+ info_ind->trx[i].tsc[j] =
+ (ts->tsc >= 0) ? ts->tsc : bts->bsic & 7;
+ LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: "
+ "available (tsc=%d arfcn=%d)\n",
+ trx->nr, ts->nr,
+ info_ind->trx[i].tsc[j],
+ info_ind->trx[i].arfcn);
+ }
+ }
+ }
+
+ return pcu_sock_send(bts, msg);
+}
+
+void pcu_info_update(struct gsm_bts *bts)
+{
+ if (pcu_connected(bts))
+ pcu_tx_info_ind(bts);
+}
+
+/* Forward rach indication to PCU */
+int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn,
+ uint8_t is_11bit, enum ph_burst_type burst_type)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ 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);
+ return -ENODEV;
+ }
+
+ LOGP(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);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ rach_ind = &pcu_prim->u.rach_ind;
+
+ rach_ind->sapi = PCU_IF_SAPI_RACH;
+ rach_ind->ra = ra;
+ rach_ind->qta = qta;
+ rach_ind->fn = fn;
+ rach_ind->is_11bit = is_11bit;
+ rach_ind->burst_type = burst_type;
+
+ return pcu_sock_send(bts, msg);
+}
+
+/* Confirm the sending of an immediate assignment to the pcu */
+int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_data_cnf_dt *data_cnf_dt;
+
+ LOGP(DPCU, LOGL_INFO, "Sending PCH confirm with direct TLLI\n");
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_DT, 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_dt->sapi = PCU_IF_SAPI_PCH;
+ data_cnf_dt->tlli = tlli;
+
+ return pcu_sock_send(bts, msg);
+}
+
+/* we need to decode the raw RR paging messsage (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)
+{
+ struct gsm48_paging1 *p1 = (struct gsm48_paging1 *) raw_rr_msg;
+ uint8_t chan_needed;
+ unsigned int mi_len;
+ uint8_t *mi;
+ int rc;
+
+ switch (p1->msg_type) {
+ case GSM48_MT_RR_PAG_REQ_1:
+ chan_needed = (p1->cneed2 << 2) | p1->cneed1;
+ mi_len = p1->data[0];
+ mi = p1->data+1;
+ LOGP(DPCU, LOGL_ERROR, "PCU Sends paging "
+ "request type %02x (chan_needed=%02x, mi_len=%u, mi=%s)\n",
+ p1->msg_type, chan_needed, mi_len,
+ osmo_hexdump_nospc(mi,mi_len));
+ /* NOTE: We will have to add 2 to mi_len and subtract 2 from
+ * the mi pointer because rsl_paging_cmd() will perform the
+ * reverse operations. This is because rsl_paging_cmd() is
+ * normally expected to chop off the element identifier (0xC0)
+ * and the length field. In our parameter, we do not have
+ * those fields included. */
+ rc = rsl_paging_cmd(bts, paging_group, mi_len+2, mi-2,
+ chan_needed, true);
+ break;
+ case GSM48_MT_RR_PAG_REQ_2:
+ case GSM48_MT_RR_PAG_REQ_3:
+ LOGP(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 "
+ "request type %02x\n", p1->msg_type);
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type,
+ struct gsm_pcu_if_data *data_req)
+{
+ uint8_t is_ptcch;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct msgb *msg;
+ char imsi_digit_buf[4];
+ uint32_t tlli = -1;
+ uint8_t pag_grp;
+ int rc = 0;
+
+ LOGP(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;
+ 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 */
+
+ if (data_req->len < 5) {
+ LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+ "invalid/small length %d\n", data_req->len);
+ break;
+ }
+ tlli = *((uint32_t *)data_req->data);
+
+ 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);
+
+ 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 (rc) {
+ msgb_free(msg);
+ rc = -EIO;
+ }
+ break;
+ default:
+ LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+ "unsupported sapi %d\n", data_req->sapi);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+static int pcu_rx(struct gsm_network *net, uint8_t msg_type,
+ struct gsm_pcu_if *pcu_prim)
+{
+ int rc = 0;
+ struct gsm_bts *bts;
+
+ /* FIXME: allow multiple BTS */
+ bts = llist_entry(net->bts_list.next, struct gsm_bts, list);
+
+ switch (msg_type) {
+ case PCU_IF_MSG_DATA_REQ:
+ case PCU_IF_MSG_PAG_REQ:
+ rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req);
+ break;
+ default:
+ LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n",
+ msg_type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/*
+ * PCU socket interface
+ */
+
+static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct pcu_sock_state *state = bts->pcu_state;
+ struct osmo_fd *conn_bfd;
+ struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data;
+
+ if (!state) {
+ if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+ LOGP(DPCU, LOGL_INFO, "PCU socket not created, "
+ "dropping message\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ conn_bfd = &state->conn_bfd;
+ if (conn_bfd->fd <= 0) {
+ if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, "
+ "dropping message\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ msgb_enqueue(&state->upqueue, msg);
+ conn_bfd->when |= BSC_FD_WRITE;
+
+ return 0;
+}
+
+static void pcu_sock_close(struct pcu_sock_state *state)
+{
+ struct osmo_fd *bfd = &state->conn_bfd;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ int i, j;
+
+ /* FIXME: allow multiple BTS */
+ bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list);
+
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n");
+
+ close(bfd->fd);
+ bfd->fd = -1;
+ osmo_fd_unregister(bfd);
+
+ /* re-enable the generation of ACCEPT for new connections */
+ state->listen_bfd.when |= BSC_FD_READ;
+
+#if 0
+ /* remove si13, ... */
+ bts->si_valid &= ~(1 << SYSINFO_TYPE_13);
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts);
+#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];
+ if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
+ && ts->pchan == GSM_PCHAN_PDCH) {
+ printf("l1sap_chan_rel(trx,gsm_lchan2chan_nr(ts->lchan));\n");
+ }
+ }
+ }
+
+ /* flush the queue */
+ while (!llist_empty(&state->upqueue)) {
+ struct msgb *msg = msgb_dequeue(&state->upqueue);
+ msgb_free(msg);
+ }
+}
+
+static int pcu_sock_read(struct osmo_fd *bfd)
+{
+ struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data;
+ struct gsm_pcu_if *pcu_prim;
+ struct msgb *msg;
+ int rc;
+
+ msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx");
+ if (!msg)
+ return -ENOMEM;
+
+ pcu_prim = (struct gsm_pcu_if *) msg->tail;
+
+ rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
+ if (rc == 0)
+ goto close;
+
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return 0;
+ goto close;
+ }
+
+ rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim);
+
+ /* as we always synchronously process the message in pcu_rx() and
+ * its callbacks, we can free the message here. */
+ msgb_free(msg);
+
+ return rc;
+
+close:
+ msgb_free(msg);
+ pcu_sock_close(state);
+ return -1;
+}
+
+static int pcu_sock_write(struct osmo_fd *bfd)
+{
+ struct pcu_sock_state *state = bfd->data;
+ int rc;
+
+ while (!llist_empty(&state->upqueue)) {
+ struct msgb *msg, *msg2;
+ struct gsm_pcu_if *pcu_prim;
+
+ /* peek at the beginning of the queue */
+ msg = llist_entry(state->upqueue.next, struct msgb, list);
+ pcu_prim = (struct gsm_pcu_if *)msg->data;
+
+ bfd->when &= ~BSC_FD_WRITE;
+
+ /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
+ if (!msgb_length(msg)) {
+ LOGP(DPCU, LOGL_ERROR, "message type (%d) with ZERO "
+ "bytes!\n", pcu_prim->msg_type);
+ goto dontsend;
+ }
+
+ /* try to send it over the socket */
+ rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+ if (rc == 0)
+ goto close;
+ if (rc < 0) {
+ if (errno == EAGAIN) {
+ bfd->when |= BSC_FD_WRITE;
+ break;
+ }
+ goto close;
+ }
+
+dontsend:
+ /* _after_ we send it, we can deueue */
+ msg2 = msgb_dequeue(&state->upqueue);
+ assert(msg == msg2);
+ msgb_free(msg);
+ }
+ return 0;
+
+close:
+ pcu_sock_close(state);
+
+ return -1;
+}
+
+static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+ int rc = 0;
+
+ if (flags & BSC_FD_READ)
+ rc = pcu_sock_read(bfd);
+ if (rc < 0)
+ return rc;
+
+ if (flags & BSC_FD_WRITE)
+ rc = pcu_sock_write(bfd);
+
+ return rc;
+}
+
+/* accept connection comming 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 sockaddr_un un_addr;
+ socklen_t len;
+ int rc;
+
+ len = sizeof(un_addr);
+ rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
+ if (rc < 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");
+ /* We already have one PCU connected, this is all we support */
+ state->listen_bfd.when &= ~BSC_FD_READ;
+ close(rc);
+ return 0;
+ }
+
+ conn_bfd->fd = rc;
+ conn_bfd->when = BSC_FD_READ;
+ conn_bfd->cb = pcu_sock_cb;
+ conn_bfd->data = state;
+
+ if (osmo_fd_register(conn_bfd) != 0) {
+ LOGP(DPCU, LOGL_ERROR, "Failed to register new connection "
+ "fd\n");
+ close(conn_bfd->fd);
+ conn_bfd->fd = -1;
+ return -1;
+ }
+
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n");
+
+ return 0;
+}
+
+/* Open connection to PCU */
+int pcu_sock_init(const char *path, struct gsm_bts *bts)
+{
+ struct pcu_sock_state *state;
+ struct osmo_fd *bfd;
+ int rc;
+
+ state = talloc_zero(NULL, struct pcu_sock_state);
+ if (!state)
+ return -ENOMEM;
+
+ INIT_LLIST_HEAD(&state->upqueue);
+ state->net = bts->network;
+ state->conn_bfd.fd = -1;
+
+ bfd = &state->listen_bfd;
+
+ bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path,
+ OSMO_SOCK_F_BIND);
+ if (bfd->fd < 0) {
+ LOGP(DPCU, LOGL_ERROR, "Could not create unix socket: %s\n",
+ strerror(errno));
+ talloc_free(state);
+ return -1;
+ }
+
+ bfd->when = BSC_FD_READ;
+ bfd->cb = pcu_sock_accept;
+ bfd->data = state;
+
+ rc = osmo_fd_register(bfd);
+ if (rc < 0) {
+ LOGP(DPCU, LOGL_ERROR, "Could not register listen fd: %d\n",
+ rc);
+ close(bfd->fd);
+ talloc_free(state);
+ return rc;
+ }
+
+ bts->pcu_state = state;
+ return 0;
+}
+
+/* Close connection to PCU */
+void pcu_sock_exit(struct gsm_bts *bts)
+{
+ struct pcu_sock_state *state = bts->pcu_state;
+ struct osmo_fd *bfd, *conn_bfd;
+
+ if (!state)
+ return;
+
+ conn_bfd = &state->conn_bfd;
+ if (conn_bfd->fd > 0)
+ pcu_sock_close(state);
+ bfd = &state->listen_bfd;
+ close(bfd->fd);
+ osmo_fd_unregister(bfd);
+ talloc_free(state);
+ bts->pcu_state = NULL;
+}
+
diff --git a/src/libbsc/rest_octets.c b/src/libbsc/rest_octets.c
new file mode 100644
index 000000000..fdab70a0c
--- /dev/null
+++ b/src/libbsc/rest_octets.c
@@ -0,0 +1,860 @@
+/* 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 <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/bitvec.h>
+#include <osmocom/gsm/bitvec_gsm.h>
+#include <openbsc/rest_octets.h>
+#include <openbsc/arfcn_range_encode.h>
+#include <openbsc/system_information.h>
+
+/* generate SI1 rest octets */
+int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net)
+{
+ struct bitvec bv;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = data;
+ bv.data_len = 1;
+
+ if (nch_pos) {
+ bitvec_set_bit(&bv, H);
+ bitvec_set_uint(&bv, *nch_pos, 5);
+ } else
+ bitvec_set_bit(&bv, L);
+
+ if (is1800_net)
+ bitvec_set_bit(&bv, L);
+ else
+ bitvec_set_bit(&bv, H);
+
+ bitvec_spare_padding(&bv, 6);
+ return bv.data_len;
+}
+
+/* Append Repeated E-UTRAN Neighbour Cell to bitvec: see 3GPP TS 44.018 Table 10.5.2.33b.1 */
+static inline void 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;
+ uint8_t rem = budget - 6, earfcn_budget; /* account for mandatory stop bit and THRESH_E-UTRAN_high */
+
+ if (budget <= 6)
+ return;
+
+ 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--;
+
+ /* 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;
+ 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);
+}
+
+static inline void append_earfcn(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget)
+{
+ int rem = budget - 25;
+ if (rem <= 0)
+ return;
+
+ OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
+
+ /* Additions in Rel-5: */
+ bitvec_set_bit(bv, H);
+ /* No 3G Additional Measurement Param. Descr. */
+ bitvec_set_bit(bv, 0);
+ /* No 3G ADDITIONAL MEASUREMENT Param. Descr. 2 */
+ bitvec_set_bit(bv, 0);
+ /* Additions in Rel-6: */
+ bitvec_set_bit(bv, H);
+ /* 3G_CCN_ACTIVE */
+ bitvec_set_bit(bv, 0);
+ /* Additions in Rel-7: */
+ bitvec_set_bit(bv, H);
+ /* No 700_REPORTING_OFFSET */
+ bitvec_set_bit(bv, 0);
+ /* No 810_REPORTING_OFFSET */
+ bitvec_set_bit(bv, 0);
+ /* Additions in Rel-8: */
+ bitvec_set_bit(bv, H);
+
+ /* Priority and E-UTRAN Parameters Description */
+ bitvec_set_bit(bv, 1);
+
+ /* No Serving Cell Priority Parameters Descr. */
+ bitvec_set_bit(bv, 0);
+ /* No 3G Priority Parameters Description */
+ bitvec_set_bit(bv, 0);
+ /* E-UTRAN Parameters Description */
+ bitvec_set_bit(bv, 1);
+
+ /* E-UTRAN_CCN_ACTIVE */
+ bitvec_set_bit(bv, 0);
+ /* E-UTRAN_Start: 9.1.54 */
+ bitvec_set_bit(bv, 1);
+ /* E-UTRAN_Stop: 9.1.54 */
+ bitvec_set_bit(bv, 1);
+
+ /* No E-UTRAN Measurement Parameters Descr. */
+ bitvec_set_bit(bv, 0);
+ /* No GPRS E-UTRAN Measurement Param. Descr. */
+ bitvec_set_bit(bv, 0);
+
+ /* Note: each of next 3 "repeated" structures might be repeated any
+ (0, 1, 2...) times - we only support 1 and 0 */
+
+ /* Repeated E-UTRAN Neighbour Cells */
+ bitvec_set_bit(bv, 1);
+
+ /* N. B: 25 bits are set in append_earfcn() - keep it in sync with budget adjustment below: */
+ append_eutran_neib_cell(bv, bts, rem);
+
+ /* 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, 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);
+}
+
+/* Append multiple FDD UARFCNs */
+static inline int append_uarfcns(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget)
+{
+ const uint16_t *u = bts->si_common.data.uarfcn_list, *sc = bts->si_common.data.scramble_list;
+ int i, j, k, rc, st = 0, a[bts->si_common.uarfcn_length];
+ uint16_t cu = u[bts->u_offset]; /* caller ensures that length is positive */
+ uint8_t rem = budget - 7, offset_diff; /* account for constant bits right away */
+
+ OSMO_ASSERT(budget <= SI2Q_MAX_LEN);
+
+ if (budget <= 7)
+ return -ENOMEM;
+
+ /* 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++) {
+ offset_diff = 0;
+ for (j = st, k = 0; j < i; j++) {
+ a[k++] = sc[j]; /* copy corresponding SCs */
+ offset_diff++; /* compute proper offset step */
+ }
+ if (u[i] != cu) { /* we've reached new UARFCN */
+ rc = append_utran_fdd_length(cu, a, bts->si_common.uarfcn_length, k);
+ if (rc < 0) { /* estimate bit length requirements */
+ return rc;
+ }
+
+ if (rem - rc <= 0)
+ break; /* we have ran out of budget in current SI2q */
+ else {
+ rem -= append_utran_fdd(bv, cu, a, k);
+ bts->u_offset += offset_diff;
+ }
+ cu = u[i];
+ st = i; /* update start position */
+ }
+ }
+
+ if (rem > 22) { /* add last UARFCN not covered by previous cycle if it could possibly fit into budget */
+ offset_diff = 0;
+ for (i = st, k = 0; i < bts->si_common.uarfcn_length; i++) {
+ a[k++] = sc[i];
+ offset_diff++;
+ }
+ rc = append_utran_fdd_length(cu, a, bts->si_common.uarfcn_length, k);
+ if (rc < 0) {
+ return rc;
+ }
+
+ if (rem - rc >= 0) {
+ rem -= append_utran_fdd(bv, cu, a, k);
+ bts->u_offset += offset_diff;
+ }
+ }
+
+ /* stop bit - end of Repeated UTRAN FDD Neighbour Cells */
+ bitvec_set_bit(bv, 0);
+
+ /* UTRAN TDD Description */
+ bitvec_set_bit(bv, 0);
+
+ return 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 */
+ bitvec_set_bit(&bv, 1);
+ /* 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) {
+ rc = append_uarfcns(&bv, bts, rc);
+ if (rc < 0) {
+ LOGP(DRR, LOGL_ERROR, "SI2quater [%u/%u]: failed to append %zu UARFCNs due to range encoding "
+ "failure: %s\n",
+ bts->si2q_index, bts->si2q_count, bts->si_common.uarfcn_length, strerror(-rc));
+ return 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 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 controlled by
+ * early_cm_ctrl above */
+ bitvec_set_bit(&bv, H);
+
+ if (si3->si2quater_indicator) {
+ bitvec_set_bit(&bv, H); /* indicator struct present */
+ bitvec_set_uint(&bv, 0, 1); /* message is sent on BCCH Norm */
+ }
+
+ bitvec_spare_padding(&bv, (bv.data_len*8)-1);
+ return bv.data_len;
+}
+
+static int append_lsa_params(struct bitvec *bv,
+ const struct gsm48_lsa_params *lsa_params)
+{
+ /* FIXME */
+ return -1;
+}
+
+/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */
+int rest_octets_si4(uint8_t *data, const struct gsm48_si_ro_info *si4, int len)
+{
+ struct bitvec bv;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = data;
+ bv.data_len = len;
+
+ /* SI4 Rest Octets O */
+ append_selection_params(&bv, &si4->selection_params);
+ append_power_offset(&bv, &si4->power_offset);
+ append_gprs_ind(&bv, &si4->gprs_ind);
+
+ if (0 /* FIXME */) {
+ /* H and SI4 Rest Octets S */
+ bitvec_set_bit(&bv, H);
+
+ /* LSA Parameters */
+ if (si4->lsa_params.present) {
+ bitvec_set_bit(&bv, H);
+ append_lsa_params(&bv, &si4->lsa_params);
+ } else
+ bitvec_set_bit(&bv, L);
+
+ /* Cell Identity */
+ if (1) {
+ bitvec_set_bit(&bv, H);
+ bitvec_set_uint(&bv, si4->cell_id, 16);
+ } else
+ bitvec_set_bit(&bv, L);
+
+ /* LSA ID Information */
+ if (0) {
+ bitvec_set_bit(&bv, H);
+ /* FIXME */
+ } else
+ bitvec_set_bit(&bv, L);
+ } else {
+ /* L and break indicator */
+ bitvec_set_bit(&bv, L);
+ bitvec_set_bit(&bv, si4->break_ind ? H : L);
+ }
+
+ return bv.data_len;
+}
+
+
+/* GSM 04.18 ETSI TS 101 503 V8.27.0 (2006-05)
+
+<SI6 rest octets> ::=
+{L | H <PCH and NCH info>}
+{L | H <VBS/VGCS options : bit(2)>}
+{ < DTM_support : bit == L > I < DTM_support : bit == H >
+< RAC : bit (8) >
+< MAX_LAPDm : bit (3) > }
+< Band indicator >
+{ L | H < GPRS_MS_TXPWR_MAX_CCH : bit (5) > }
+<implicit spare >;
+*/
+int rest_octets_si6(uint8_t *data, bool is1800_net)
+{
+ struct bitvec bv;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = data;
+ bv.data_len = 1;
+
+ /* no PCH/NCH info */
+ bitvec_set_bit(&bv, L);
+ /* no VBS/VGCS options */
+ bitvec_set_bit(&bv, L);
+ /* no DTM_support */
+ bitvec_set_bit(&bv, L);
+ /* band indicator */
+ if (is1800_net)
+ bitvec_set_bit(&bv, L);
+ else
+ bitvec_set_bit(&bv, H);
+ /* no GPRS_MS_TXPWR_MAX_CCH */
+ bitvec_set_bit(&bv, L);
+
+ bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
+ return bv.data_len;
+}
+
+/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a:
+ < GPRS Mobile Allocation IE > ::=
+ < HSN : bit (6) >
+ { 0 | 1 < RFL number list : < RFL number list struct > > }
+ { 0 < MA_LENGTH : bit (6) >
+ < MA_BITMAP: bit (val(MA_LENGTH) + 1) >
+ | 1 { 0 | 1 <ARFCN index list : < ARFCN index list struct > > } } ;
+
+ < RFL number list struct > :: =
+ < RFL_NUMBER : bit (4) >
+ { 0 | 1 < RFL number list struct > } ;
+ < ARFCN index list struct > ::=
+ < ARFCN_INDEX : bit(6) >
+ { 0 | 1 < ARFCN index list struct > } ;
+ */
+static int append_gprs_mobile_alloc(struct bitvec *bv)
+{
+ /* Hopping Sequence Number */
+ bitvec_set_uint(bv, 0, 6);
+
+ if (0) {
+ /* We want to use a RFL number list */
+ bitvec_set_bit(bv, 1);
+ /* FIXME: RFL number list */
+ } else
+ bitvec_set_bit(bv, 0);
+
+ if (0) {
+ /* We want to use a MA_BITMAP */
+ bitvec_set_bit(bv, 0);
+ /* FIXME: MA_LENGTH, MA_BITMAP, ... */
+ } else {
+ bitvec_set_bit(bv, 1);
+ if (0) {
+ /* We want to provide an ARFCN index list */
+ bitvec_set_bit(bv, 1);
+ /* FIXME */
+ } else
+ bitvec_set_bit(bv, 0);
+ }
+ return 0;
+}
+
+static int encode_t3192(unsigned int t3192)
+{
+ /* See also 3GPP TS 44.060
+ Table 12.24.2: GPRS Cell Options information element details */
+ if (t3192 == 0)
+ return 3;
+ else if (t3192 <= 80)
+ return 4;
+ else if (t3192 <= 120)
+ return 5;
+ else if (t3192 <= 160)
+ return 6;
+ else if (t3192 <= 200)
+ return 7;
+ else if (t3192 <= 500)
+ return 0;
+ else if (t3192 <= 1000)
+ return 1;
+ else if (t3192 <= 1500)
+ return 2;
+ else
+ return -EINVAL;
+}
+
+static int encode_drx_timer(unsigned int drx)
+{
+ if (drx == 0)
+ return 0;
+ else if (drx == 1)
+ return 1;
+ else if (drx == 2)
+ return 2;
+ else if (drx <= 4)
+ return 3;
+ else if (drx <= 8)
+ return 4;
+ else if (drx <= 16)
+ return 5;
+ else if (drx <= 32)
+ return 6;
+ else if (drx <= 64)
+ return 7;
+ else
+ return -EINVAL;
+}
+
+/* GPRS Cell Options as per TS 04.60 Chapter 12.24
+ < GPRS Cell Options IE > ::=
+ < NMO : bit(2) >
+ < T3168 : bit(3) >
+ < T3192 : bit(3) >
+ < DRX_TIMER_MAX: bit(3) >
+ < ACCESS_BURST_TYPE: bit >
+ < CONTROL_ACK_TYPE : bit >
+ < BS_CV_MAX: bit(4) >
+ { 0 | 1 < PAN_DEC : bit(3) >
+ < PAN_INC : bit(3) >
+ < PAN_MAX : bit(3) >
+ { 0 | 1 < Extension Length : bit(6) >
+ < bit (val(Extension Length) + 1
+ & { < Extension Information > ! { bit ** = <no string> } } ;
+ < Extension Information > ::=
+ { 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit >
+ < BEP_PERIOD : bit(4) > }
+ < PFC_FEATURE_MODE : bit >
+ < DTM_SUPPORT : bit >
+ <BSS_PAGING_COORDINATION: bit >
+ <spare bit > ** ;
+ */
+static int append_gprs_cell_opt(struct bitvec *bv,
+ const struct gprs_cell_options *gco)
+{
+ int t3192, drx_timer_max;
+
+ t3192 = encode_t3192(gco->t3192);
+ if (t3192 < 0)
+ return t3192;
+
+ drx_timer_max = encode_drx_timer(gco->drx_timer_max);
+ if (drx_timer_max < 0)
+ return drx_timer_max;
+
+ bitvec_set_uint(bv, gco->nmo, 2);
+
+ /* See also 3GPP TS 44.060
+ Table 12.24.2: GPRS Cell Options information element details */
+ bitvec_set_uint(bv, gco->t3168 / 500 - 1, 3);
+
+ bitvec_set_uint(bv, t3192, 3);
+ bitvec_set_uint(bv, drx_timer_max, 3);
+ /* ACCESS_BURST_TYPE: Hard-code 8bit */
+ bitvec_set_bit(bv, 0);
+ /* CONTROL_ACK_TYPE: */
+ bitvec_set_bit(bv, gco->ctrl_ack_type_use_block);
+ bitvec_set_uint(bv, gco->bs_cv_max, 4);
+
+ if (0) {
+ /* hard-code no PAN_{DEC,INC,MAX} */
+ bitvec_set_bit(bv, 0);
+ } else {
+ /* copied from ip.access BSC protocol trace */
+ bitvec_set_bit(bv, 1);
+ bitvec_set_uint(bv, 1, 3); /* DEC */
+ bitvec_set_uint(bv, 1, 3); /* INC */
+ bitvec_set_uint(bv, 15, 3); /* MAX */
+ }
+
+ if (!gco->ext_info_present) {
+ /* no extension information */
+ bitvec_set_bit(bv, 0);
+ } else {
+ /* extension information */
+ bitvec_set_bit(bv, 1);
+ if (!gco->ext_info.egprs_supported) {
+ /* 6bit length of extension */
+ bitvec_set_uint(bv, (1 + 3)-1, 6);
+ /* EGPRS supported in the cell */
+ bitvec_set_bit(bv, 0);
+ } else {
+ /* 6bit length of extension */
+ bitvec_set_uint(bv, (1 + 5 + 3)-1, 6);
+ /* EGPRS supported in the cell */
+ bitvec_set_bit(bv, 1);
+
+ /* 1bit EGPRS PACKET CHANNEL REQUEST */
+ if (gco->supports_egprs_11bit_rach == 0) {
+ bitvec_set_bit(bv,
+ gco->ext_info.use_egprs_p_ch_req);
+ } else {
+ bitvec_set_bit(bv, 0);
+ }
+
+ /* 4bit BEP PERIOD */
+ bitvec_set_uint(bv, gco->ext_info.bep_period, 4);
+ }
+ bitvec_set_bit(bv, gco->ext_info.pfc_supported);
+ bitvec_set_bit(bv, gco->ext_info.dtm_supported);
+ bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination);
+ }
+
+ return 0;
+}
+
+static void append_gprs_pwr_ctrl_pars(struct bitvec *bv,
+ const struct gprs_power_ctrl_pars *pcp)
+{
+ bitvec_set_uint(bv, pcp->alpha, 4);
+ bitvec_set_uint(bv, pcp->t_avg_w, 5);
+ bitvec_set_uint(bv, pcp->t_avg_t, 5);
+ bitvec_set_uint(bv, pcp->pc_meas_chan, 1);
+ bitvec_set_uint(bv, pcp->n_avg_i, 4);
+}
+
+/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */
+int rest_octets_si13(uint8_t *data, const struct gsm48_si13_info *si13)
+{
+ struct bitvec bv;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = data;
+ bv.data_len = 20;
+
+ if (0) {
+ /* No rest octets */
+ bitvec_set_bit(&bv, L);
+ } else {
+ bitvec_set_bit(&bv, H);
+ bitvec_set_uint(&bv, si13->bcch_change_mark, 3);
+ bitvec_set_uint(&bv, si13->si_change_field, 4);
+ if (1) {
+ bitvec_set_bit(&bv, 0);
+ } else {
+ bitvec_set_bit(&bv, 1);
+ bitvec_set_uint(&bv, si13->bcch_change_mark, 2);
+ append_gprs_mobile_alloc(&bv);
+ }
+ if (!si13->pbcch_present) {
+ /* PBCCH not present in cell */
+ bitvec_set_bit(&bv, 0);
+ bitvec_set_uint(&bv, si13->no_pbcch.rac, 8);
+ bitvec_set_bit(&bv, si13->no_pbcch.spgc_ccch_sup);
+ bitvec_set_uint(&bv, si13->no_pbcch.prio_acc_thr, 3);
+ bitvec_set_uint(&bv, si13->no_pbcch.net_ctrl_ord, 2);
+ append_gprs_cell_opt(&bv, &si13->cell_opts);
+ append_gprs_pwr_ctrl_pars(&bv, &si13->pwr_ctrl_pars);
+ } else {
+ /* PBCCH present in cell */
+ bitvec_set_bit(&bv, 1);
+ bitvec_set_uint(&bv, si13->pbcch.psi1_rep_per, 4);
+ /* PBCCH Descripiton */
+ bitvec_set_uint(&bv, si13->pbcch.pb, 4);
+ bitvec_set_uint(&bv, si13->pbcch.tsc, 3);
+ bitvec_set_uint(&bv, si13->pbcch.tn, 3);
+ switch (si13->pbcch.carrier_type) {
+ case PBCCH_BCCH:
+ bitvec_set_bit(&bv, 0);
+ bitvec_set_bit(&bv, 0);
+ break;
+ case PBCCH_ARFCN:
+ bitvec_set_bit(&bv, 0);
+ bitvec_set_bit(&bv, 1);
+ bitvec_set_uint(&bv, si13->pbcch.arfcn, 10);
+ break;
+ case PBCCH_MAIO:
+ bitvec_set_bit(&bv, 1);
+ bitvec_set_uint(&bv, si13->pbcch.maio, 6);
+ break;
+ }
+ }
+ /* 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/libbsc/system_information.c b/src/libbsc/system_information.c
new file mode 100644
index 000000000..dcabbbdd1
--- /dev/null
+++ b/src/libbsc/system_information.c
@@ -0,0 +1,1169 @@
+/* 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) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012 Holger Hans Peter Freyther
+ *
+ * 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 <string.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+
+#include <osmocom/core/bitvec.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/sysinfo.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/rest_octets.h>
+#include <openbsc/arfcn_range_encode.h>
+
+/*
+ * DCS1800 and PCS1900 have overlapping ARFCNs. We would need to set the
+ * ARFCN_PCS flag on the 1900 ARFCNs but this would increase cell_alloc
+ * and other arrays to make sure (ARFCN_PCS + 1024)/8 ARFCNs fit into the
+ * 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)
+{
+ enum gsm_band band = gsm_arfcn2band(arfcn);
+
+ /* normal case */
+ if (band == bts->band)
+ return 1;
+ /* deal with ARFCN_PCS not set */
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900)
+ return 1;
+
+ return 0;
+}
+
+static int is_dcs_net(const struct gsm_bts *bts)
+{
+ if (bts->band == GSM_BAND_850)
+ return 0;
+ if (bts->band == GSM_BAND_1900)
+ return 0;
+ return 1;
+}
+
+/* Return p(n) for given NR_OF_TDD_CELLS - see Table 9.1.54.1a, 3GPP TS 44.018 */
+unsigned range1024_p(unsigned n)
+{
+ switch (n) {
+ case 0: return 0;
+ case 1: return 10;
+ case 2: return 19;
+ case 3: return 28;
+ case 4: return 36;
+ case 5: return 44;
+ case 6: return 52;
+ case 7: return 60;
+ case 8: return 67;
+ case 9: return 74;
+ case 10: return 81;
+ case 11: return 88;
+ case 12: return 95;
+ case 13: return 102;
+ case 14: return 109;
+ case 15: return 116;
+ case 16: return 122;
+ default: return 0;
+ }
+}
+
+/* Return q(m) for given NR_OF_TDD_CELLS - see Table 9.1.54.1b, 3GPP TS 44.018 */
+unsigned range512_q(unsigned m)
+{
+ switch (m) {
+ case 0: return 0;
+ case 1: return 9;
+ case 2: return 17;
+ case 3: return 25;
+ case 4: return 32;
+ case 5: return 39;
+ case 6: return 46;
+ case 7: return 53;
+ case 8: return 59;
+ case 9: return 65;
+ case 10: return 71;
+ case 11: return 77;
+ case 12: return 83;
+ case 13: return 89;
+ case 14: return 95;
+ case 15: return 101;
+ case 16: return 106;
+ case 17: return 111;
+ case 18: return 116;
+ case 19: return 121;
+ case 20: return 126;
+ default: return 0;
+ }
+}
+
+size_t si2q_earfcn_count(const struct osmo_earfcn_si2q *e)
+{
+ unsigned i, ret = 0;
+
+ if (!e)
+ return 0;
+
+ for (i = 0; i < e->length; i++)
+ if (e->arfcn[i] != OSMO_EARFCN_INVALID)
+ ret++;
+
+ return ret;
+}
+
+/* generate SI2quater messages, return rest octets length of last generated message or negative error code */
+static int make_si2quaters(struct gsm_bts *bts, bool counting)
+{
+ int rc;
+ bool memory_exceeded = true;
+ struct gsm48_system_information_type_2quater *si2q;
+
+ for (bts->si2q_index = 0; bts->si2q_index < SI2Q_MAX_NUM; bts->si2q_index++) {
+ si2q = GSM_BTS_SI2Q(bts, bts->si2q_index);
+ if (counting) { /* that's legitimate if we're called for counting purpose: */
+ if (bts->si2q_count < bts->si2q_index)
+ bts->si2q_count = bts->si2q_index;
+ } else {
+ memset(si2q, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+ si2q->header.l2_plen = GSM48_LEN2PLEN(22);
+ si2q->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+ si2q->header.skip_indicator = 0;
+ si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater;
+ }
+
+ rc = rest_octets_si2quater(si2q->rest_octets, bts);
+ if (rc < 0)
+ return rc;
+
+ if (bts->u_offset >= bts->si_common.uarfcn_length &&
+ bts->e_offset >= si2q_earfcn_count(&bts->si_common.si2quater_neigh_list)) {
+ memory_exceeded = false;
+ break;
+ }
+ }
+
+ if (memory_exceeded)
+ return -ENOMEM;
+
+ return rc;
+}
+
+/* we generate SI2q rest octets twice to get proper estimation but it's one time cost anyway */
+uint8_t si2q_num(struct gsm_bts *bts)
+{
+ int rc = make_si2quaters(bts, true);
+ uint8_t num = bts->si2q_index + 1; /* number of SI2quater messages */
+
+ /* N. B: si2q_num() should NEVER be called during actualSI2q rest octets generation
+ we're not re-entrant because of the following code: */
+ bts->u_offset = 0;
+ bts->e_offset = 0;
+
+ if (rc < 0)
+ return 0xFF; /* return impossible index as an indicator of error in generating SI2quater */
+
+ return num;
+}
+
+/* 3GPP TS 44.018, Table 9.1.54.1 - prepend diversity bit to scrambling code */
+static inline uint16_t encode_fdd(uint16_t scramble, bool diversity)
+{
+ if (diversity)
+ return scramble | (1 << 9);
+ return scramble;
+}
+
+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);
+
+ if (r < 0)
+ return r;
+
+ if (e->thresh_hi && thresh_hi != e->thresh_hi)
+ r = 1;
+
+ e->thresh_hi = thresh_hi;
+
+ if (thresh_lo != EARFCN_THRESH_LOW_INVALID) {
+ if (e->thresh_lo_valid && e->thresh_lo != thresh_lo)
+ r = EARFCN_THRESH_LOW_INVALID;
+ e->thresh_lo = thresh_lo;
+ e->thresh_lo_valid = true;
+ }
+
+ if (qrx != EARFCN_QRXLV_INVALID) {
+ if (e->qrxlm_valid && e->qrxlm != qrx)
+ r = EARFCN_QRXLV_INVALID + 1;
+ e->qrxlm = qrx;
+ e->qrxlm_valid = true;
+ }
+
+ if (prio != EARFCN_PRIO_INVALID) {
+ if (e->prio_valid && e->prio != prio)
+ r = EARFCN_PRIO_INVALID;
+ e->prio = prio;
+ e->prio_valid = true;
+ }
+
+ return r;
+}
+
+int bts_uarfcn_del(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble)
+{
+ uint16_t sc0 = encode_fdd(scramble, false), sc1 = encode_fdd(scramble, true),
+ *ual = bts->si_common.data.uarfcn_list,
+ *scl = bts->si_common.data.scramble_list;
+ size_t len = bts->si_common.uarfcn_length, i;
+ for (i = 0; i < len; i++) {
+ if (arfcn == ual[i] && (sc0 == scl[i] || sc1 == scl[i])) {
+ /* we rely on the assumption that (uarfcn, scramble)
+ tuple is unique in the lists */
+ if (i != len - 1) { /* move the tail if necessary */
+ memmove(ual + i, ual + i + 1, 2 * (len - i + 1));
+ memmove(scl + i, scl + i + 1, 2 * (len - i + 1));
+ }
+ break;
+ }
+ }
+
+ if (i == len)
+ return -EINVAL;
+
+ bts->si_common.uarfcn_length--;
+ return 0;
+}
+
+int bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble, bool diversity)
+{
+ size_t len = bts->si_common.uarfcn_length, i, k = 0;
+ uint16_t scr, chk,
+ *ual = bts->si_common.data.uarfcn_list,
+ *scl = bts->si_common.data.scramble_list,
+ scramble1 = encode_fdd(scramble, true),
+ scramble0 = encode_fdd(scramble, false);
+
+ scr = diversity ? scramble1 : scramble0;
+ chk = diversity ? scramble0 : scramble1;
+
+ if (len == MAX_EARFCN_LIST)
+ return -ENOMEM;
+
+ for (i = 0; i < len; i++) /* find the position of arfcn if any */
+ if (arfcn == ual[i])
+ break;
+
+ for (k = 0; i < len; i++) {
+ if (arfcn == ual[i] && (scr == scl[i] || chk == scl[i]))
+ return -EADDRINUSE;
+ if (scr > scl[i])
+ k = i + 1;
+ }
+ /* we keep lists sorted by scramble code:
+ insert into appropriate position and move the tail */
+ if (len - k) {
+ memmove(ual + k + 1, ual + k, (len - k) * 2);
+ memmove(scl + k + 1, scl + k, (len - k) * 2);
+ }
+
+ ual[k] = arfcn;
+ scl[k] = scr;
+ bts->si_common.uarfcn_length++;
+
+ if (si2q_num(bts) <= SI2Q_MAX_NUM) {
+ bts->si2q_count = si2q_num(bts) - 1;
+ return 0;
+ }
+
+ bts_uarfcn_del(bts, arfcn, scramble);
+ return -ENOSPC;
+}
+
+static inline int use_arfcn(const struct gsm_bts *bts, const bool bis, const bool ter,
+ const bool pgsm, const int arfcn)
+{
+ if (bts->force_combined_si)
+ return !bis && !ter;
+ if (!bis && !ter && band_compatible(bts, arfcn))
+ return 1;
+ /* Correct but somehow broken with either the nanoBTS or the iPhone5 */
+ if (bis && pgsm && band_compatible(bts, arfcn) && (arfcn < 1 || arfcn > 124))
+ return 1;
+ if (ter && !band_compatible(bts, arfcn))
+ return 1;
+ return 0;
+}
+
+/* Frequency Lists as per TS 04.08 10.5.2.13 */
+
+/* 10.5.2.13.2: Bit map 0 format */
+static int freq_list_bm0_set_arfcn(uint8_t *chan_list, unsigned int arfcn)
+{
+ unsigned int byte, bit;
+
+ if (arfcn > 124 || arfcn < 1) {
+ LOGP(DRR, LOGL_ERROR, "Bitmap 0 only supports ARFCN 1...124\n");
+ return -EINVAL;
+ }
+
+ /* the bitmask is from 1..124, not from 0..123 */
+ arfcn--;
+
+ byte = arfcn / 8;
+ bit = arfcn % 8;
+
+ chan_list[GSM48_CELL_CHAN_DESC_SIZE-1-byte] |= (1 << bit);
+
+ return 0;
+}
+
+/* 10.5.2.13.7: Variable bit map format */
+static int freq_list_bmrel_set_arfcn(uint8_t *chan_list, unsigned int arfcn)
+{
+ unsigned int byte, bit;
+ unsigned int min_arfcn;
+ unsigned int bitno;
+
+ min_arfcn = (chan_list[0] & 1) << 9;
+ min_arfcn |= chan_list[1] << 1;
+ min_arfcn |= (chan_list[2] >> 7) & 1;
+
+ /* The lower end of our bitmaks is always implicitly included */
+ if (arfcn == min_arfcn)
+ return 0;
+
+ if (((arfcn - min_arfcn) & 1023) > 111) {
+ LOGP(DRR, LOGL_ERROR, "arfcn(%u) > min(%u) + 111\n", arfcn, min_arfcn);
+ return -EINVAL;
+ }
+
+ bitno = (arfcn - min_arfcn) & 1023;
+ byte = bitno / 8;
+ bit = bitno % 8;
+
+ chan_list[2 + byte] |= 1 << (7 - bit);
+
+ return 0;
+}
+
+/* generate a variable bitmap */
+static inline int enc_freq_lst_var_bitmap(uint8_t *chan_list,
+ struct bitvec *bv, const struct gsm_bts *bts,
+ bool bis, bool ter, int min, bool pgsm)
+{
+ int i;
+
+ /* set it to 'Variable bitmap format' */
+ chan_list[0] = 0x8e;
+
+ chan_list[0] |= (min >> 9) & 1;
+ chan_list[1] = (min >> 1);
+ chan_list[2] = (min & 1) << 7;
+
+ for (i = 0; i < bv->data_len*8; i++) {
+ /* see notes in bitvec2freq_list */
+ if (bitvec_get_bit_pos(bv, i)
+ && ((!bis && !ter && band_compatible(bts,i))
+ || (bis && pgsm && band_compatible(bts,i) && (i < 1 || i > 124))
+ || (ter && !band_compatible(bts, i)))) {
+ int rc = freq_list_bmrel_set_arfcn(chan_list, i);
+ if (rc < 0)
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w,
+ int f0, uint8_t *chan_list)
+{
+ /*
+ * Manipulate the ARFCN list according to the rules in J4 depending
+ * on the selected range.
+ */
+ int rc, f0_included;
+
+ range_enc_filter_arfcns(arfcns, arfcns_used, f0, &f0_included);
+
+ rc = 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);
+ default:
+ return -ERANGE;
+ };
+
+ return f0_included;
+}
+
+/* generate a frequency list with the range 512 format */
+static inline int enc_freq_lst_range(uint8_t *chan_list,
+ struct bitvec *bv, const struct gsm_bts *bts,
+ bool bis, bool ter, bool pgsm)
+{
+ int arfcns[RANGE_ENC_MAX_ARFCNS];
+ int w[RANGE_ENC_MAX_ARFCNS];
+ int arfcns_used = 0;
+ int i, range, f0;
+
+ /*
+ * Select ARFCNs according to the rules in bitvec2freq_list
+ */
+ for (i = 0; i < bv->data_len * 8; ++i) {
+ /* More ARFCNs than the maximum */
+ if (arfcns_used > ARRAY_SIZE(arfcns))
+ return -1;
+ /* Check if we can select it? */
+ if (bitvec_get_bit_pos(bv, i) && use_arfcn(bts, bis, ter, pgsm, i))
+ arfcns[arfcns_used++] = i;
+ }
+
+ /*
+ * Check if the given list of ARFCNs can be encoded.
+ */
+ range = range_enc_determine_range(arfcns, arfcns_used, &f0);
+ if (range == ARFCN_RANGE_INVALID)
+ return -2;
+
+ memset(w, 0, sizeof(w));
+ return range_encode(range, arfcns, arfcns_used, w, f0, chan_list);
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int bitvec2freq_list(uint8_t *chan_list, struct bitvec *bv,
+ const struct gsm_bts *bts, bool bis, bool ter)
+{
+ int i, rc, min = -1, max = -1, arfcns = 0;
+ bool pgsm = false;
+ memset(chan_list, 0, 16);
+
+ if (bts->band == GSM_BAND_900
+ && bts->c0->arfcn >= 1 && bts->c0->arfcn <= 124)
+ pgsm = true;
+ /* 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;
+ }
+ }
+ return 0;
+ }
+
+ for (i = 0; i < bv->data_len*8; i++) {
+ /* in case of SI2 or SI5 allow all neighbours in same band
+ * in case of SI*bis, allow neighbours in same band ouside pgsm
+ * in case of SI*ter, allow neighbours in different bands
+ */
+ if (!bitvec_get_bit_pos(bv, i))
+ continue;
+ if (!use_arfcn(bts, bis, ter, pgsm, i))
+ continue;
+ /* count the arfcns we want to carry */
+ arfcns += 1;
+
+ /* 955..1023 < 0..885 */
+ if (min < 0)
+ min = i;
+ if (i >= 955 && min < 955)
+ min = i;
+ if (i >= 955 && min >= 955 && i < min)
+ min = i;
+ if (i < 955 && min < 955 && i < min)
+ min = i;
+ if (max < 0)
+ max = i;
+ if (i < 955 && max >= 955)
+ max = i;
+ if (i >= 955 && max >= 955 && i > max)
+ max = i;
+ if (i < 955 && max < 955 && i > max)
+ max = i;
+ }
+
+ if (max == -1) {
+ /* Empty set, use 'bit map 0 format' */
+ chan_list[0] = 0;
+ return 0;
+ }
+
+ /* Now find the best encoding */
+ if (((max - min) & 1023) <= 111)
+ return enc_freq_lst_var_bitmap(chan_list, bv, bts, bis,
+ ter, min, pgsm);
+
+ /* Attempt to do the range encoding */
+ rc = enc_freq_lst_range(chan_list, bv, bts, bis, ter, pgsm);
+ if (rc >= 0)
+ return 0;
+
+ LOGP(DRR, LOGL_ERROR, "min_arfcn=%u, max_arfcn=%u, arfcns=%d "
+ "can not generate ARFCN list", min, max, arfcns);
+ return -EINVAL;
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+/* static*/ int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts)
+{
+ 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);
+
+ /* first we generate a bitvec of all TRX ARFCN's in our BTS */
+ 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];
+ /* 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);
+ }
+ }
+ }
+
+ /* then we generate a GSM 04.08 frequency list from the bitvec */
+ return bitvec2freq_list(chan_list, bv, bts, false, false);
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts,
+ bool si5, bool bis, bool ter)
+{
+ struct gsm_bts *cur_bts;
+ struct bitvec *bv;
+
+ if (si5 && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP)
+ bv = &bts->si_common.si5_neigh_list;
+ else
+ bv = &bts->si_common.neigh_list;
+
+ /* Generate list of neighbor cells if we are in automatic mode */
+ if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) {
+ /* Zero-initialize the bit-vector */
+ memset(bv->data, 0, bv->data_len);
+
+ /* first we generate a bitvec of the BCCH ARFCN's in our BSC */
+ llist_for_each_entry(cur_bts, &bts->network->bts_list, list) {
+ if (cur_bts == bts)
+ continue;
+ bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1);
+ }
+ }
+
+ /* then we generate a GSM 04.08 frequency list from the bitvec */
+ return bitvec2freq_list(chan_list, bv, bts, bis, ter);
+}
+
+static int list_arfcn(uint8_t *chan_list, uint8_t mask, char *text)
+{
+ int n = 0, i;
+ struct gsm_sysinfo_freq freq[1024];
+
+ memset(freq, 0, sizeof(freq));
+ gsm48_decode_freq_list(freq, chan_list, 16, 0xce, 1);
+ for (i = 0; i < 1024; i++) {
+ if (freq[i].mask) {
+ if (!n)
+ LOGP(DRR, LOGL_INFO, "%s", text);
+ LOGPC(DRR, LOGL_INFO, " %d", i);
+ n++;
+ }
+ }
+ if (n)
+ LOGPC(DRR, LOGL_INFO, "\n");
+
+ return n;
+}
+
+static int generate_si1(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ int rc;
+ struct gsm48_system_information_type_1 *si1 = (struct gsm48_system_information_type_1 *) GSM_BTS_SI(bts, t);
+
+ memset(si1, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+ si1->header.l2_plen = GSM48_LEN2PLEN(21);
+ si1->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+ si1->header.skip_indicator = 0;
+ si1->header.system_information = GSM48_MT_RR_SYSINFO_1;
+
+ rc = generate_cell_chan_list(si1->cell_channel_description, bts);
+ if (rc < 0)
+ return rc;
+ list_arfcn(si1->cell_channel_description, 0xce, "Serving cell:");
+
+ si1->rach_control = bts->si_common.rach_control;
+
+ /*
+ * SI1 Rest Octets (10.5.2.32), contains NCH position and band
+ * indicator but that is not in the 04.08.
+ */
+ rc = rest_octets_si1(si1->rest_octets, NULL, is_dcs_net(bts));
+
+ return sizeof(*si1) + rc;
+}
+
+static int generate_si2(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ int rc;
+ struct gsm48_system_information_type_2 *si2 = (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, t);
+
+ memset(si2, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+ si2->header.l2_plen = GSM48_LEN2PLEN(22);
+ si2->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+ si2->header.skip_indicator = 0;
+ si2->header.system_information = GSM48_MT_RR_SYSINFO_2;
+
+ rc = generate_bcch_chan_list(si2->bcch_frequency_list, bts, false, false, false);
+ if (rc < 0)
+ return rc;
+ list_arfcn(si2->bcch_frequency_list, 0xce,
+ "SI2 Neighbour cells in same band:");
+
+ si2->ncc_permitted = bts->si_common.ncc_permitted;
+ si2->rach_control = bts->si_common.rach_control;
+
+ return sizeof(*si2);
+}
+
+static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ int rc;
+ struct gsm48_system_information_type_2bis *si2b =
+ (struct gsm48_system_information_type_2bis *) GSM_BTS_SI(bts, t);
+ int n;
+
+ memset(si2b, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+ si2b->header.l2_plen = GSM48_LEN2PLEN(22);
+ si2b->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+ si2b->header.skip_indicator = 0;
+ si2b->header.system_information = GSM48_MT_RR_SYSINFO_2bis;
+
+ rc = generate_bcch_chan_list(si2b->bcch_frequency_list, bts, false, true, false);
+ if (rc < 0)
+ return rc;
+ n = list_arfcn(si2b->bcch_frequency_list, 0xce,
+ "Neighbour cells in same band, but outside P-GSM:");
+ if (n) {
+ /* indicate in SI2 and SI2bis: there is an extension */
+ struct gsm48_system_information_type_2 *si2 =
+ (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ si2->bcch_frequency_list[0] |= 0x20;
+ si2b->bcch_frequency_list[0] |= 0x20;
+ } else
+ bts->si_valid &= ~(1 << SYSINFO_TYPE_2bis);
+
+ si2b->rach_control = bts->si_common.rach_control;
+
+ return sizeof(*si2b);
+}
+
+static int generate_si2ter(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ int rc;
+ struct gsm48_system_information_type_2ter *si2t =
+ (struct gsm48_system_information_type_2ter *) GSM_BTS_SI(bts, t);
+ int n;
+
+ memset(si2t, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+ si2t->header.l2_plen = GSM48_LEN2PLEN(22);
+ si2t->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+ si2t->header.skip_indicator = 0;
+ si2t->header.system_information = GSM48_MT_RR_SYSINFO_2ter;
+
+ rc = generate_bcch_chan_list(si2t->ext_bcch_frequency_list, bts, false, false, true);
+ if (rc < 0)
+ return rc;
+ n = list_arfcn(si2t->ext_bcch_frequency_list, 0x8e,
+ "Neighbour cells in different band:");
+ if (!n)
+ bts->si_valid &= ~(1 << SYSINFO_TYPE_2ter);
+
+ return sizeof(*si2t);
+}
+
+/* SI2quater messages are optional - we only generate them when neighbor UARFCNs or EARFCNs are configured */
+static inline bool si2quater_not_needed(struct gsm_bts *bts)
+{
+ unsigned i = MAX_EARFCN_LIST;
+
+ if (bts->si_common.si2quater_neigh_list.arfcn)
+ for (i = 0; i < MAX_EARFCN_LIST; i++)
+ if (bts->si_common.si2quater_neigh_list.arfcn[i] != OSMO_EARFCN_INVALID)
+ break;
+
+ if (!bts->si_common.uarfcn_length && i == MAX_EARFCN_LIST) {
+ bts->si_valid &= ~(1 << SYSINFO_TYPE_2quater); /* mark SI2q as invalid if no (E|U)ARFCNs are present */
+ return true;
+ }
+
+ return false;
+}
+
+static int generate_si2quater(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ int rc;
+ struct gsm48_system_information_type_2quater *si2q;
+
+ if (si2quater_not_needed(bts)) /* generate rest_octets for SI2q only when necessary */
+ return GSM_MACBLOCK_LEN;
+
+ bts->u_offset = 0;
+ bts->e_offset = 0;
+ bts->si2q_index = 0;
+ bts->si2q_count = si2q_num(bts) - 1;
+
+ rc = make_si2quaters(bts, false);
+ if (rc < 0)
+ return rc;
+
+ OSMO_ASSERT(bts->si2q_count == bts->si2q_index);
+ OSMO_ASSERT(bts->si2q_count <= SI2Q_MAX_NUM);
+
+ return sizeof(*si2q) + rc;
+}
+
+static struct gsm48_si_ro_info si_info = {
+ .selection_params = {
+ .present = 0,
+ },
+ .power_offset = {
+ .present = 0,
+ },
+ .si2ter_indicator = 0,
+ .early_cm_ctrl = 1,
+ .scheduling = {
+ .present = 0,
+ },
+ .gprs_ind = {
+ .si13_position = 0,
+ .ra_colour = 0,
+ .present = 1,
+ },
+ .si2quater_indicator = 0,
+ .lsa_params = {
+ .present = 0,
+ },
+ .cell_id = 0, /* FIXME: doesn't the bts have this? */
+ .break_ind = 0,
+};
+
+static int generate_si3(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ int rc;
+ struct gsm48_system_information_type_3 *si3 = (struct gsm48_system_information_type_3 *) GSM_BTS_SI(bts, t);
+
+ memset(si3, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+ si3->header.l2_plen = GSM48_LEN2PLEN(18);
+ si3->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+ si3->header.skip_indicator = 0;
+ si3->header.system_information = GSM48_MT_RR_SYSINFO_3;
+
+ si3->cell_identity = htons(bts->cell_identity);
+ gsm48_generate_lai(&si3->lai, bts->network->country_code,
+ bts->network->network_code,
+ bts->location_area_code);
+ si3->control_channel_desc = bts->si_common.chan_desc;
+ si3->cell_options = bts->si_common.cell_options;
+ si3->cell_sel_par = bts->si_common.cell_sel_par;
+ si3->rach_control = bts->si_common.rach_control;
+
+ /* allow/disallow DTXu */
+ gsm48_set_dtx(&si3->cell_options, bts->dtxu, bts->dtxu, true);
+
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) {
+ LOGP(DRR, LOGL_INFO, "SI 2ter is included.\n");
+ si_info.si2ter_indicator = 1;
+ } else {
+ si_info.si2ter_indicator = 0;
+ }
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater)) {
+ LOGP(DRR, LOGL_INFO, "SI 2quater is included, based on %zu EARFCNs and %zu UARFCNs.\n",
+ si2q_earfcn_count(&bts->si_common.si2quater_neigh_list), bts->si_common.uarfcn_length);
+ si_info.si2quater_indicator = 1;
+ } else {
+ si_info.si2quater_indicator = 0;
+ }
+ si_info.early_cm_ctrl = bts->early_classmark_allowed;
+
+ /* SI3 Rest Octets (10.5.2.34), containing
+ 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);
+
+ return sizeof(*si3) + rc;
+}
+
+static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ int rc;
+ struct gsm48_system_information_type_4 *si4 = (struct gsm48_system_information_type_4 *) GSM_BTS_SI(bts, t);
+ struct gsm_lchan *cbch_lchan;
+ uint8_t *restoct = si4->data;
+
+ /* length of all IEs present except SI4 rest octets and l2_plen */
+ int l2_plen = sizeof(*si4) - 1;
+
+ memset(si4, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+ si4->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+ si4->header.skip_indicator = 0;
+ si4->header.system_information = GSM48_MT_RR_SYSINFO_4;
+
+ gsm48_generate_lai(&si4->lai, bts->network->country_code,
+ bts->network->network_code,
+ bts->location_area_code);
+ si4->cell_sel_par = bts->si_common.cell_sel_par;
+ si4->rach_control = bts->si_common.rach_control;
+
+ /* Optional: CBCH Channel Description + CBCH Mobile Allocation */
+ cbch_lchan = gsm_bts_get_cbch(bts);
+ if (cbch_lchan) {
+ struct gsm48_chan_desc cd;
+ gsm48_lchan2chan_desc(&cd, cbch_lchan);
+ tv_fixed_put(si4->data, GSM48_IE_CBCH_CHAN_DESC, 3,
+ (uint8_t *) &cd);
+ l2_plen += 3 + 1;
+ restoct += 3 + 1;
+ /* we don't use hopping and thus don't need a CBCH MA */
+ }
+
+ si4->header.l2_plen = GSM48_LEN2PLEN(l2_plen);
+
+ /* SI4 Rest Octets (10.5.2.35), containing
+ Optional Power offset, GPRS Indicator,
+ Cell Identity, LSA ID, Selection Parameter */
+ rc = rest_octets_si4(restoct, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - restoct);
+
+ return l2_plen + 1 + rc;
+}
+
+static int generate_si5(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ struct gsm48_system_information_type_5 *si5;
+ uint8_t *output = GSM_BTS_SI(bts, t);
+ int rc, l2_plen = 18;
+
+ 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:
+ *output++ = GSM48_LEN2PLEN(l2_plen);
+ l2_plen++;
+ break;
+ default:
+ break;
+ }
+
+ si5 = (struct gsm48_system_information_type_5 *) GSM_BTS_SI(bts, t);
+
+ /* l2 pseudo length, not part of msg: 18 */
+ si5->rr_protocol_discriminator = GSM48_PDISC_RR;
+ si5->skip_indicator = 0;
+ si5->system_information = GSM48_MT_RR_SYSINFO_5;
+ rc = generate_bcch_chan_list(si5->bcch_frequency_list, bts, true, false, false);
+ if (rc < 0)
+ return rc;
+ list_arfcn(si5->bcch_frequency_list, 0xce,
+ "SI5 Neighbour cells in same band:");
+
+ /* 04.08 9.1.37: L2 Pseudo Length of 18 */
+ return l2_plen;
+}
+
+static int generate_si5bis(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ struct gsm48_system_information_type_5bis *si5b;
+ uint8_t *output = GSM_BTS_SI(bts, t);
+ int rc, l2_plen = 18;
+ int n;
+
+ 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:
+ *output++ = GSM48_LEN2PLEN(l2_plen);
+ l2_plen++;
+ break;
+ default:
+ break;
+ }
+
+ si5b = (struct gsm48_system_information_type_5bis *) GSM_BTS_SI(bts, t);
+
+ /* l2 pseudo length, not part of msg: 18 */
+ si5b->rr_protocol_discriminator = GSM48_PDISC_RR;
+ si5b->skip_indicator = 0;
+ si5b->system_information = GSM48_MT_RR_SYSINFO_5bis;
+ rc = generate_bcch_chan_list(si5b->bcch_frequency_list, bts, true, true, false);
+ if (rc < 0)
+ return rc;
+ n = list_arfcn(si5b->bcch_frequency_list, 0xce,
+ "Neighbour cells in same band, but outside P-GSM:");
+ if (n) {
+ /* indicate in SI5 and SI5bis: there is an extension */
+ struct gsm48_system_information_type_5 *si5 =
+ (struct gsm48_system_information_type_5 *) GSM_BTS_SI(bts, SYSINFO_TYPE_5);
+ si5->bcch_frequency_list[0] |= 0x20;
+ si5b->bcch_frequency_list[0] |= 0x20;
+ } else
+ bts->si_valid &= ~(1 << SYSINFO_TYPE_5bis);
+
+ /* 04.08 9.1.37: L2 Pseudo Length of 18 */
+ return l2_plen;
+}
+
+static int generate_si5ter(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ struct gsm48_system_information_type_5ter *si5t;
+ uint8_t *output = GSM_BTS_SI(bts, t);
+ int rc, l2_plen = 18;
+ int n;
+
+ 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:
+ *output++ = GSM48_LEN2PLEN(l2_plen);
+ l2_plen++;
+ break;
+ default:
+ break;
+ }
+
+ si5t = (struct gsm48_system_information_type_5ter *) GSM_BTS_SI(bts, t);
+
+ /* l2 pseudo length, not part of msg: 18 */
+ si5t->rr_protocol_discriminator = GSM48_PDISC_RR;
+ si5t->skip_indicator = 0;
+ si5t->system_information = GSM48_MT_RR_SYSINFO_5ter;
+ rc = generate_bcch_chan_list(si5t->bcch_frequency_list, bts, true, false, true);
+ if (rc < 0)
+ return rc;
+ n = list_arfcn(si5t->bcch_frequency_list, 0x8e,
+ "Neighbour cells in different band:");
+ if (!n)
+ bts->si_valid &= ~(1 << SYSINFO_TYPE_5ter);
+
+ /* 04.08 9.1.37: L2 Pseudo Length of 18 */
+ return l2_plen;
+}
+
+static int generate_si6(enum osmo_sysinfo_type t, struct gsm_bts *bts)
+{
+ struct gsm48_system_information_type_6 *si6;
+ uint8_t *output = GSM_BTS_SI(bts, t);
+ int l2_plen = 11;
+ int rc;
+
+ 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:
+ *output++ = GSM48_LEN2PLEN(l2_plen);
+ l2_plen++;
+ break;
+ default:
+ break;
+ }
+
+ si6 = (struct gsm48_system_information_type_6 *) GSM_BTS_SI(bts, t);
+
+ /* l2 pseudo length, not part of msg: 11 */
+ si6->rr_protocol_discriminator = GSM48_PDISC_RR;
+ si6->skip_indicator = 0;
+ si6->system_information = GSM48_MT_RR_SYSINFO_6;
+ si6->cell_identity = htons(bts->cell_identity);
+ gsm48_generate_lai(&si6->lai, bts->network->country_code,
+ bts->network->network_code,
+ bts->location_area_code);
+ si6->cell_options = bts->si_common.cell_options;
+ si6->ncc_permitted = bts->si_common.ncc_permitted;
+ /* allow/disallow DTXu */
+ 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));
+
+ return l2_plen + rc;
+}
+
+static struct gsm48_si13_info si13_default = {
+ .cell_opts = {
+ .nmo = GPRS_NMO_II,
+ .t3168 = 2000,
+ .t3192 = 1500,
+ .drx_timer_max = 3,
+ .bs_cv_max = 15,
+ .ctrl_ack_type_use_block = true,
+ .ext_info_present = 0,
+ .supports_egprs_11bit_rach = 0,
+ .ext_info = {
+ /* The values below are just guesses ! */
+ .egprs_supported = 0,
+ .use_egprs_p_ch_req = 1,
+ .bep_period = 5,
+ .pfc_supported = 0,
+ .dtm_supported = 0,
+ .bss_paging_coordination = 0,
+ },
+ },
+ .pwr_ctrl_pars = {
+ .alpha = 0, /* a = 0.0 */
+ .t_avg_w = 16,
+ .t_avg_t = 16,
+ .pc_meas_chan = 0, /* downling measured on CCCH */
+ .n_avg_i = 8,
+ },
+ .bcch_change_mark = 1,
+ .si_change_field = 0,
+ .pbcch_present = 0,
+ {
+ .no_pbcch = {
+ .rac = 0, /* needs to be patched */
+ .spgc_ccch_sup = 0,
+ .net_ctrl_ord = 0,
+ .prio_acc_thr = 6,
+ },
+ },
+};
+
+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);
+ int ret;
+
+ memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+ si13->header.rr_protocol_discriminator = GSM48_PDISC_RR;
+ si13->header.skip_indicator = 0;
+ si13->header.system_information = GSM48_MT_RR_SYSINFO_13;
+
+ si13_default.no_pbcch.rac = bts->gprs.rac;
+ si13_default.no_pbcch.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_default.cell_opts.supports_egprs_11bit_rach =
+ bts->gprs.supports_egprs_11bit_rach;
+
+ ret = rest_octets_si13(si13->rest_octets, &si13_default);
+ if (ret < 0)
+ return ret;
+
+ /* length is coded in bit 2 an up */
+ si13->header.l2_plen = 0x01;
+
+ return sizeof (*si13) + ret;
+}
+
+typedef int (*gen_si_fn_t)(enum osmo_sysinfo_type t, struct gsm_bts *bts);
+
+static const gen_si_fn_t gen_si_fn[_MAX_SYSINFO_TYPE] = {
+ [SYSINFO_TYPE_1] = &generate_si1,
+ [SYSINFO_TYPE_2] = &generate_si2,
+ [SYSINFO_TYPE_2bis] = &generate_si2bis,
+ [SYSINFO_TYPE_2ter] = &generate_si2ter,
+ [SYSINFO_TYPE_2quater] = &generate_si2quater,
+ [SYSINFO_TYPE_3] = &generate_si3,
+ [SYSINFO_TYPE_4] = &generate_si4,
+ [SYSINFO_TYPE_5] = &generate_si5,
+ [SYSINFO_TYPE_5bis] = &generate_si5bis,
+ [SYSINFO_TYPE_5ter] = &generate_si5ter,
+ [SYSINFO_TYPE_6] = &generate_si6,
+ [SYSINFO_TYPE_13] = &generate_si13,
+};
+
+int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type)
+{
+ gen_si_fn_t gen_si;
+
+ 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;
+ case BTS_GPRS_NONE:
+ si_info.gprs_ind.present = 0;
+ break;
+ }
+
+ memcpy(&si_info.selection_params,
+ &bts->si_common.cell_ro_sel_par,
+ sizeof(struct gsm48_si_selection_params));
+
+ gen_si = gen_si_fn[si_type];
+ if (!gen_si)
+ return -EINVAL;
+
+ return gen_si(si_type, bts);
+}