Merge branch 'rbs2000'
diff --git a/openbsc/include/openbsc/abis_om2000.h b/openbsc/include/openbsc/abis_om2000.h
new file mode 100644
index 0000000..f15a50d
--- /dev/null
+++ b/openbsc/include/openbsc/abis_om2000.h
@@ -0,0 +1,59 @@
+#ifndef OPENBSC_ABIS_OM2K_H
+#define OPENBSC_ABIS_OM2K_H
+/* 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 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/>.
+ *
+ */
+
+struct abis_om2k_mo {
+	uint8_t class;
+	uint8_t bts;
+	uint8_t assoc_so;
+	uint8_t inst;
+} __attribute__ ((packed));
+
+struct om2k_is_conn_grp {
+	uint16_t icp1;
+	uint16_t icp2;
+	uint8_t cont_idx;
+} __attribute__ ((packed));
+
+extern const struct value_string om2k_mo_class_short_vals[];
+
+int abis_om2k_rcvmsg(struct msgb *msg);
+
+extern const struct abis_om2k_mo om2k_mo_cf;
+
+int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo);
+int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo,
+			 uint8_t operational);
+int abis_om2k_tx_is_conf_req(struct gsm_bts *bts, struct om2k_is_conn_grp *cg,
+			     unsigned int num_cg);
+
+int abis_om2k_vty_init(void);
+
+#endif /* OPENBCS_ABIS_OM2K_H */
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index e308ca4..a983027 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -421,6 +421,7 @@
 	GSM_BTS_TYPE_UNKNOWN,
 	GSM_BTS_TYPE_BS11,
 	GSM_BTS_TYPE_NANOBTS,
+	GSM_BTS_TYPE_RBS2000,
 };
 
 struct gsm_bts_model {
diff --git a/openbsc/include/openbsc/signal.h b/openbsc/include/openbsc/signal.h
index 866fa29..0273586 100644
--- a/openbsc/include/openbsc/signal.h
+++ b/openbsc/include/openbsc/signal.h
@@ -145,6 +145,8 @@
 	S_INP_TEI_UP,
 	S_INP_TEI_DN,
 	S_INP_LINE_INIT,
+	S_INP_LINE_ALARM,
+	S_INP_LINE_NOALARM,
 };
 
 struct gsm_subscriber;
diff --git a/openbsc/include/openbsc/vty.h b/openbsc/include/openbsc/vty.h
index 1be81a7..8c38313 100644
--- a/openbsc/include/openbsc/vty.h
+++ b/openbsc/include/openbsc/vty.h
@@ -33,6 +33,7 @@
 	NAT_NODE,
 	NAT_BSC_NODE,
 	MSC_NODE,
+	OM2K_NODE,
 };
 
 extern int bsc_vty_is_config_node(struct vty *vty, int node);
diff --git a/openbsc/src/Makefile.am b/openbsc/src/Makefile.am
index 3f21406..077054c 100644
--- a/openbsc/src/Makefile.am
+++ b/openbsc/src/Makefile.am
@@ -16,14 +16,14 @@
 sbin_PROGRAMS = bsc_hack bs11_config isdnsync bsc_mgcp
 noinst_LIBRARIES = libbsc.a libmsc.a libvty.a libmgcp.a
 
-libbsc_a_SOURCES = abis_rsl.c abis_nm.c gsm_data.c gsm_04_08_utils.c \
-		chan_alloc.c debug.c socket.c abis_nm_vty.c \
+libbsc_a_SOURCES = abis_rsl.c abis_nm.c abis_om2000.c gsm_data.c gsm_04_08_utils.c \
+		chan_alloc.c debug.c socket.c abis_nm_vty.c abis_om2000_vty.c \
 		gsm_subscriber_base.c subchan_demux.c bsc_rll.c transaction.c \
 		trau_frame.c trau_mux.c paging.c \
 		e1_config.c e1_input.c e1_input_vty.c \
 		input/misdn.c input/ipaccess.c input/dahdi.c input/lapd.c \
 		handover_logic.c talloc_ctx.c system_information.c rest_octets.c \
-		bts_siemens_bs11.c bts_ipaccess_nanobts.c mncc_upqueue.c \
+		bts_siemens_bs11.c bts_ipaccess_nanobts.c bts_ericsson_rbs2000.c mncc_upqueue.c \
 		bts_unknown.c bsc_version.c bsc_api.c bsc_vty.c meas_rep.c gsm_04_80.c
 
 libmsc_a_SOURCES = gsm_subscriber.c db.c \
diff --git a/openbsc/src/abis_om2000.c b/openbsc/src/abis_om2000.c
new file mode 100644
index 0000000..ab224f9
--- /dev/null
+++ b/openbsc/src/abis_om2000.c
@@ -0,0 +1,811 @@
+/* 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 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 <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <osmocore/utils.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/signal.h>
+
+#define OM_ALLOC_SIZE		1024
+#define OM_HEADROOM_SIZE	128
+
+/* 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_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_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_NEGOT_REQ_ACK			= 0x0104,
+	OM2K_MSGT_NEGOT_REQ_NACK		= 0x0105,
+	OM2K_MSGT_NEGOT_REQ			= 0x0106,
+};
+
+enum abis_om2k_dei {
+	OM2K_DEI_CAL_TIME			= 0x0d,
+	OM2K_DEI_END_LIST_NR			= 0x13,
+	OM2K_DEI_IS_CONN_LIST			= 0x27,
+	OM2K_DEI_LIST_NR			= 0x28,
+	OM2K_DEI_OP_INFO			= 0x2e,
+	OM2K_DEI_NEGOT_REC1			= 0x90,
+	OM2K_DEI_NEGOT_REC2			= 0x91,
+};
+
+enum abis_om2k_mo_cls {
+	OM2K_MO_CLS_TRXC			= 0x01,
+	OM2K_MO_CLS_TS				= 0x03,
+	OM2K_MO_CLS_TF				= 0x04,
+	OM2K_MO_CLS_IS				= 0x05,
+	OM2K_MO_CLS_CON				= 0x06,
+	OM2K_MO_CLS_DP				= 0x07,
+	OM2K_MO_CLS_CF				= 0x0a,
+	OM2K_MO_CLS_TX				= 0x0b,
+	OM2K_MO_CLS_RX				= 0x0c,
+};
+
+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 Rejecte" },
+	{ 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 }
+};
+
+static struct msgb *om2k_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+				   "OM2000");
+}
+
+static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+	msg->trx = bts->c0;
+
+	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, uint8_t attr_len)
+{
+	o2h->om.mdisc = ABIS_OM_MDISC_FOM;
+	o2h->om.placement = ABIS_OM_PLACEMENT_ONLY;
+	o2h->om.sequence = 0;
+	o2h->om.length = 6 + attr_len;
+	o2h->msg_type = htons(msg_type);
+	memcpy(&o2h->mo, mo, sizeof(o2h->mo));
+}
+
+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;
+}
+
+const struct abis_om2k_mo om2k_mo_cf = { OM2K_MO_CLS_CF, 0, 0xFF, 0 };
+const struct abis_om2k_mo om2k_mo_is = { OM2K_MO_CLS_IS, 0, 0xFF, 0 };
+
+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, &om2k_mo_cf, OM2K_MSGT_CAL_TIME_RESP, 7);
+
+	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, 0);
+
+	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, 2);
+
+	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));
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+int abis_om2k_tx_is_conf_req(struct gsm_bts *bts, struct om2k_is_conn_grp *cg,
+			     unsigned int num_cg )
+{
+	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, &om2k_mo_is, OM2K_MSGT_IS_CONF_REQ,
+		      2 + 2 + TLV_GROSS_LEN(num_cg * sizeof(*cg)));
+
+	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_cg * sizeof(*cg), (uint8_t *)cg);
+
+	return abis_om2k_sendmsg(bts, msg);
+}
+
+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, 2+len);
+
+	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 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(msg->trx->bts, &o2h->mo, out_buf, out_cur - out_buf);
+}
+
+static int om2k_rx_start_res(struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+	int rc;
+
+	rc = abis_om2k_tx_simple(msg->trx->bts, &o2h->mo, OM2K_MSGT_START_RES_ACK);
+	rc = abis_om2k_tx_op_info(msg->trx->bts, &o2h->mo, 1);
+
+	return rc;
+}
+
+static int om2k_rx_op_info_ack(struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h = msgb_l2(msg);
+
+	/* FIXME: update Operational state in our structures */
+
+	return 0;
+}
+
+int abis_om2k_rcvmsg(struct msgb *msg)
+{
+	struct gsm_bts *bts = msg->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);
+	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),
+		hexdump(msg->l2h, msgb_l2len(msg)));
+
+	switch (msg_type) {
+	case OM2K_MSGT_CAL_TIME_REQ:
+		rc = abis_om2k_cal_time_resp(bts);
+		break;
+	case OM2K_MSGT_FAULT_REP:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_START_REQ);
+		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:
+		rc = om2k_rx_start_res(msg);
+		break;
+	case OM2K_MSGT_OP_INFO_ACK:
+		rc = om2k_rx_op_info_ack(msg);
+		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_CONNECT_COMPL:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RESET_CMD);
+		break;
+	case OM2K_MSGT_RESET_COMPL:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_START_REQ);
+		break;
+	case OM2K_MSGT_ENABLE_RES:
+		rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_ENABLE_RES_ACK);
+		break;
+	case OM2K_MSGT_START_REQ_ACK:
+		break;
+	case OM2K_MSGT_STATUS_RESP:
+		break;
+	default:
+		LOGP(DNM, LOGL_NOTICE, "Rx unhandled OM2000 msg %s\n",
+			get_value_string(om2k_msgcode_vals, msg_type));
+	}
+
+	msgb_free(msg);
+	return rc;
+}
diff --git a/openbsc/src/abis_om2000_vty.c b/openbsc/src/abis_om2000_vty.c
new file mode 100644
index 0000000..be843c5
--- /dev/null
+++ b/openbsc/src/abis_om2000_vty.c
@@ -0,0 +1,278 @@
+/* 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 <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/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,
+};
+
+struct oml_node_state {
+	struct gsm_bts *bts;
+	struct abis_om2k_mo mo;
+};
+
+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 "FIXME"
+
+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(bsc_gsmnet, 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(bsc_gsmnet, 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")
+{
+	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;
+}
+
+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;
+}
+
+DEFUN(om2k_is_conf_req, om2k_is_conf_req_cmd,
+	"is-conf-req",
+	"IS Configuration Request\n")
+{
+	struct oml_node_state *oms = vty->index;
+	struct om2k_is_conn_grp grps[6];
+
+	/* TRX 0 */
+	om2k_fill_is_conn_grp(&grps[0], 512,  4, 4);
+	om2k_fill_is_conn_grp(&grps[1], 516,  8, 4);
+	om2k_fill_is_conn_grp(&grps[2], 520, 12, 4);
+	/* TRX 1 */
+	om2k_fill_is_conn_grp(&grps[3], 524, 16, 4);
+	om2k_fill_is_conn_grp(&grps[4], 528, 20, 4);
+	om2k_fill_is_conn_grp(&grps[5], 532, 24, 4);
+
+	abis_om2k_tx_is_conf_req(oms->bts, grps, ARRAY_SIZE(grps));
+	return CMD_SUCCESS;
+}
+
+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);
+
+	install_default(OM2K_NODE);
+	install_element(OM2K_NODE, &ournode_exit_cmd);
+	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_is_conf_req_cmd);
+
+	return 0;
+}
diff --git a/openbsc/src/bsc_hack.c b/openbsc/src/bsc_hack.c
index dacaad3..14e5d86 100644
--- a/openbsc/src/bsc_hack.c
+++ b/openbsc/src/bsc_hack.c
@@ -237,6 +237,7 @@
 	bts_model_unknown_init();
 	bts_model_bs11_init();
 	bts_model_nanobts_init();
+	bts_model_rbs2k_init();
 
 	e1inp_init();
 
diff --git a/openbsc/src/bsc_vty.c b/openbsc/src/bsc_vty.c
index 739d9aa..3d7d69b 100644
--- a/openbsc/src/bsc_vty.c
+++ b/openbsc/src/bsc_vty.c
@@ -2702,6 +2702,7 @@
 	install_element(ENABLE_NODE, &pdch_act_cmd);
 
 	abis_nm_vty_init();
+	abis_om2k_vty_init();
 	e1inp_vty_init();
 
 	bsc_vty_init_extra();
diff --git a/openbsc/src/bts_ericsson_rbs2000.c b/openbsc/src/bts_ericsson_rbs2000.c
new file mode 100644
index 0000000..5ad47b2
--- /dev/null
+++ b/openbsc/src/bts_ericsson_rbs2000.c
@@ -0,0 +1,148 @@
+/* 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 <sys/types.h>
+
+#include <osmocore/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_om2000.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/signal.h>
+
+#include "input/lapd.h"
+
+static struct gsm_bts_model model_rbs2k = {
+	.type = GSM_BTS_TYPE_RBS2000,
+	.name = "rbs2000",
+	.oml_rcvmsg = &abis_om2k_rcvmsg,
+};
+
+static void bootstrap_om_rbs2k(struct gsm_bts *bts)
+{
+	LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr);
+	abis_om2k_tx_start_req(bts, &om2k_mo_cf);
+	/* 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 (start)
+				lapd_sap_start(ts->driver.dahdi.lapd, link->tei, link->sapi);
+			else
+				lapd_sap_stop(ts->driver.dahdi.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_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;
+
+	if (subsys != SS_INPUT)
+		return 0;
+
+	switch (signal) {
+	case S_INP_TEI_UP:
+		switch (isd->link_type) {
+		case E1INP_SIGN_OML:
+			if (isd->trx->bts->type == GSM_BTS_TYPE_RBS2000) {
+				bootstrap_om_rbs2k(isd->trx->bts);
+			}
+			break;
+		}
+		break;
+	case S_INP_LINE_INIT:
+		/* Right now Ericsson RBS are only supported on DAHDI */
+		if (strcasecmp(isd->line->driver->name, "DAHDI"))
+			break;
+		start_sabm_in_line(isd->line, 1);
+		break;
+	case S_INP_LINE_ALARM:
+		if (strcasecmp(isd->line->driver->name, "DAHDI"))
+			break;
+		start_sabm_in_line(isd->line, 0);
+		break;
+	case S_INP_LINE_NOALARM:
+		if (strcasecmp(isd->line->driver->name, "DAHDI"))
+			break;
+		start_sabm_in_line(isd->line, 1);
+		break;
+	}
+
+	return 0;
+}
+
+int bts_model_rbs2k_init(void)
+{
+	model_rbs2k.features.data = &model_rbs2k._features_data[0];
+	model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data);
+
+	gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HOPPING);
+	gsm_btsmodel_set_feature(&model_rbs2k, BTS_FEAT_HSCSD);
+
+	register_signal_handler(SS_INPUT, inp_sig_cb, NULL);
+	register_signal_handler(SS_GLOBAL, gbl_sig_cb, NULL);
+
+	return gsm_bts_model_register(&model_rbs2k);
+}
diff --git a/openbsc/src/common_vty.c b/openbsc/src/common_vty.c
index 695d4c3..25ab3c0 100644
--- a/openbsc/src/common_vty.c
+++ b/openbsc/src/common_vty.c
@@ -67,6 +67,7 @@
 		}
 		break;
 	case OML_NODE:
+	case OM2K_NODE:
 		vty->node = ENABLE_NODE;
 		talloc_free(vty->index);
 		vty->index = NULL;
@@ -145,6 +146,7 @@
 		vty->index = NULL;
 		break;
 	case OML_NODE:
+	case OM2K_NODE:
 		vty->node = ENABLE_NODE;
 		talloc_free(vty->index);
 		vty->index = NULL;
@@ -196,6 +198,7 @@
 	switch (node) {
 	/* add items that are not config */
 	case OML_NODE:
+	case OM2K_NODE:
 	case SUBSCR_NODE:
 	case CONFIG_NODE:
 		return 0;
diff --git a/openbsc/src/e1_config.c b/openbsc/src/e1_config.c
index dd81992..db290cb 100644
--- a/openbsc/src/e1_config.c
+++ b/openbsc/src/e1_config.c
@@ -100,6 +100,11 @@
 	}
 	sign_ts = &line->ts[e1_link->e1_ts-1];
 	e1inp_ts_config(sign_ts, line, E1INP_TS_TYPE_SIGN);
+	if (trx->bts->type == GSM_BTS_TYPE_RBS2000) {
+		/* FIXME: where to put the reference of the per-TRX OML? */
+		e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, trx,
+					trx->rsl_tei, SAPI_OML);
+	}
 	rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL,
 					  trx, trx->rsl_tei, SAPI_RSL);
 	if (!rsl_link) {
diff --git a/openbsc/src/gsm_data.c b/openbsc/src/gsm_data.c
index 5ae5258..29fa16e 100644
--- a/openbsc/src/gsm_data.c
+++ b/openbsc/src/gsm_data.c
@@ -412,6 +412,7 @@
 	{ GSM_BTS_TYPE_UNKNOWN,	"unknown" },
 	{ GSM_BTS_TYPE_BS11,	"bs11" },
 	{ GSM_BTS_TYPE_NANOBTS,	"nanobts" },
+	{ GSM_BTS_TYPE_RBS2000,	"rbs2000" },
 	{ 0,			NULL }
 };
 
diff --git a/openbsc/src/input/dahdi.c b/openbsc/src/input/dahdi.c
index c142b8a..56851ee 100644
--- a/openbsc/src/input/dahdi.c
+++ b/openbsc/src/input/dahdi.c
@@ -69,6 +69,7 @@
 static void handle_dahdi_exception(struct e1inp_ts *ts)
 {
 	int rc, evt;
+	struct input_signal_data isd;
 
 	rc = ioctl(ts->driver.dahdi.fd.fd, DAHDI_GETEVENT, &evt);
 	if (rc < 0)
@@ -78,12 +79,16 @@
 		ts->line->num, ts->line->name, ts->num,
 		get_value_string(dahdi_evt_names, evt));
 
+	isd.line = ts->line;
+
 	switch (evt) {
 	case DAHDI_EVENT_ALARM:
-		/* FIXME: we should notify the code that the line is gone */
+		/* we should notify the code that the line is gone */
+		dispatch_signal(SS_INPUT, S_INP_LINE_ALARM, &isd);
 		break;
 	case DAHDI_EVENT_NOALARM:
-		/* FIXME: alarm has gone, we should re-start the SABM requests */
+		/* alarm has gone, we should re-start the SABM requests */
+		dispatch_signal(SS_INPUT, S_INP_LINE_NOALARM, &isd);
 		break;
 	}
 }
diff --git a/openbsc/src/input/lapd.c b/openbsc/src/input/lapd.c
index 0c3b469..9bfc2cb 100644
--- a/openbsc/src/input/lapd.c
+++ b/openbsc/src/input/lapd.c
@@ -1,9 +1,9 @@
 /* OpenBSC minimal LAPD implementation */
 
 /* (C) 2009 by oystein@homelien.no
- * (C) 2011 by Harald Welte <laforge@gnumonks.org>
  * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
  * (C) 2010 by Digium and Matthew Fredrickson <creslin@digium.com>
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
  *
  * All Rights Reserved
  *
@@ -23,6 +23,12 @@
  *
  */
 
+/* TODO:
+	* detect RR timeout and set SAP state back to SABM_RETRANSMIT
+	* use of value_string
+	* further code cleanup (spaghetti)
+ */
+
 #include <stdio.h>
 #include <string.h>
 #include <assert.h>
@@ -33,13 +39,14 @@
 #include <osmocore/linuxlist.h>
 #include <osmocore/talloc.h>
 #include <osmocore/msgb.h>
+#include <osmocore/timer.h>
 #include <openbsc/debug.h>
 
+#define SABM_INTERVAL		0, 300000
+
 typedef enum {
 	LAPD_TEI_NONE = 0,
-
 	LAPD_TEI_ASSIGNED,
-
 	LAPD_TEI_ACTIVE,
 } lapd_tei_state;
 
@@ -93,10 +100,24 @@
 
 };
 
+enum lapd_sap_state {
+	SAP_STATE_INACTIVE,
+	SAP_STATE_SABM_RETRANS,
+	SAP_STATE_ACTIVE,
+};
+
+const char *lapd_sap_states[] = {
+	"INACTIVE",
+	"SABM_RETRANS",
+	"ACTIVE",
+};
+
 const char *lapd_msg_types = "?ISU";
 
+/* structure representing an allocated TEI within a LAPD instance */
 struct lapd_tei {
 	struct llist_head list;
+	struct lapd_instance *li;
 
 	uint8_t tei;
 	/* A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ V(S). */
@@ -104,6 +125,19 @@
 	int va;			/* last acked by peer */
 	int vr;			/* next expected to be received */
 	lapd_tei_state state;
+
+	struct llist_head sap_list;
+};
+
+/* Structure representing a SAP within a TEI. We use this for TE-mode to
+ * re-transmit SABM */
+struct lapd_sap {
+	struct llist_head list;
+	struct lapd_tei *tei;
+	uint8_t sapi;
+	enum lapd_sap_state state;
+
+	struct timer_list sabme_timer;	/* timer to re-transmit SABM message */
 };
 
 /* 3.5.2.2   Send state variable V(S)
@@ -142,6 +176,7 @@
  * correctly received all I frames numbered up to and including N(R) − 1.
  */
 
+/* Resolve TEI structure from given numeric TEI */
 static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei)
 {
 	struct lapd_tei *lt;
@@ -155,28 +190,87 @@
 
 static void lapd_tei_set_state(struct lapd_tei *teip, int newstate)
 {
-	DEBUGP(DMI, "state change on tei %d: %s -> %s\n", teip->tei,
+	DEBUGP(DMI, "state change on TEI %d: %s -> %s\n", teip->tei,
 		   lapd_tei_states[teip->state], lapd_tei_states[newstate]);
 	teip->state = newstate;
 };
 
-
-int lapd_tei_alloc(struct lapd_instance *li, uint8_t tei)
+/* Allocate a new TEI */
+struct lapd_tei *lapd_tei_alloc(struct lapd_instance *li, uint8_t tei)
 {
 	struct lapd_tei *teip;
 
 	teip = talloc_zero(li, struct lapd_tei);
 	if (!teip)
-		return -ENOMEM;
+		return NULL;
 
+	teip->li = li;
 	teip->tei = tei;
 	llist_add(&teip->list, &li->tei_list);
+	INIT_LLIST_HEAD(&teip->sap_list);
 
 	lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
 
-	return 0;
+	return teip;
 }
 
+/* Find a SAP within a given TEI */
+static struct lapd_sap *lapd_sap_find(struct lapd_tei *teip, uint8_t sapi)
+{
+	struct lapd_sap *sap;
+
+	llist_for_each_entry(sap, &teip->sap_list, list) {
+		if (sap->sapi == sapi)
+			return sap;
+	}
+
+	return NULL;
+}
+
+static void sabme_timer_cb(void *_sap);
+
+/* Allocate a new SAP within a given TEI */
+static struct lapd_sap *lapd_sap_alloc(struct lapd_tei *teip, uint8_t sapi)
+{
+	struct lapd_sap *sap = talloc_zero(teip, struct lapd_sap);
+
+	LOGP(DMI, LOGL_INFO, "Allocating SAP for SAPI=%u / TEI=%u\n",
+		sapi, teip->tei);
+
+	sap->sapi = sapi;
+	sap->tei = teip;
+	sap->sabme_timer.cb = &sabme_timer_cb;
+	sap->sabme_timer.data = sap;
+
+	llist_add(&sap->list, &teip->sap_list);
+
+	return sap;
+}
+
+static void lapd_sap_set_state(struct lapd_tei *teip, uint8_t sapi,
+				enum lapd_sap_state newstate)
+{
+	struct lapd_sap *sap = lapd_sap_find(teip, sapi);
+	if (!sap)
+		return;
+
+	DEBUGP(DMI, "state change on TEI %u / SAPI %u: %s -> %s\n", teip->tei,
+		sapi, lapd_sap_states[sap->state], lapd_sap_states[newstate]);
+	switch (sap->state) {
+	case SAP_STATE_SABM_RETRANS:
+		if (newstate != SAP_STATE_SABM_RETRANS)
+			bsc_del_timer(&sap->sabme_timer);
+		break;
+	default:
+		if (newstate == SAP_STATE_SABM_RETRANS)
+			bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL);
+		break;
+	}
+
+	sap->state = newstate;
+};
+
+/* Input function into TEI manager */
 static void lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len)
 {
 	uint8_t entity = data[0];
@@ -214,6 +308,7 @@
 	};
 };
 
+/* General input function for any data received for this LAPD instance */
 uint8_t *lapd_receive(struct lapd_instance *li, uint8_t * data, unsigned int len,
 		      int *ilen, lapd_mph_type *prim)
 {
@@ -391,6 +486,7 @@
 		teip->vr = 0;
 		teip->va = 0;
 		lapd_tei_set_state(teip, LAPD_TEI_ACTIVE);
+		lapd_sap_set_state(teip, sapi, SAP_STATE_ACTIVE);
 		*prim = LAPD_MPH_ACTIVATE_IND;
 		break;
 	case LAPD_CMD_RR:
@@ -478,7 +574,8 @@
 	return NULL;
 };
 
-int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
+/* low-level function to send a single SABM message */
+static int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
 {
 	struct msgb *msg = msgb_alloc_headroom(1024, 128, "LAPD SABM");
 	if (!msg)
@@ -486,10 +583,6 @@
 
 	DEBUGP(DMI, "Sending SABM for TEI=%u, SAPI=%u\n", tei, sapi);
 
-	/* make sure we know the TEI at the time the response comes in */
-	if (!teip_from_tei(li, tei))
-		lapd_tei_alloc(li, tei);
-
 	msgb_put_u8(msg, (sapi << 2) | (li->network_side ? 2 : 0));
 	msgb_put_u8(msg, (tei << 1) | 1);
 	msgb_put_u8(msg, 0x7F);
@@ -501,6 +594,61 @@
 	return 0;
 }
 
+/* timer call-back function for SABM re-transmission */
+static void sabme_timer_cb(void *_sap)
+{
+	struct lapd_sap *sap = _sap;
+
+	lapd_send_sabm(sap->tei->li, sap->tei->tei, sap->sapi);
+
+	if (sap->state == SAP_STATE_SABM_RETRANS)
+		bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL);
+}
+
+/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
+{
+	struct lapd_sap *sap;
+	struct lapd_tei *teip;
+
+	teip = teip_from_tei(li, tei);
+	if (!teip)
+		teip = lapd_tei_alloc(li, tei);
+
+	sap = lapd_sap_find(teip, sapi);
+	if (sap)
+		return -EEXIST;
+
+	sap = lapd_sap_alloc(teip, sapi);
+
+	lapd_sap_set_state(teip, sapi, SAP_STATE_SABM_RETRANS);
+
+	return 0;
+}
+
+/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
+{
+	struct lapd_tei *teip;
+	struct lapd_sap *sap;
+
+	teip = teip_from_tei(li, tei);
+	if (!teip)
+		return -ENODEV;
+
+	sap = lapd_sap_find(teip, sapi);
+	if (!sap)
+		return -ENODEV;
+
+	lapd_sap_set_state(teip, sapi, SAP_STATE_INACTIVE);
+
+	llist_del(&sap->list);
+	talloc_free(sap);
+
+	return 0;
+}
+
+/* Transmit Data (I-Frame) on the given LAPD Instance / TEI / SAPI */
 void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi,
 		   uint8_t *data, unsigned int len)
 {
@@ -530,6 +678,7 @@
 	li->transmit_cb(buf, len, li->cbdata);
 };
 
+/* Allocate a new LAPD instance */
 struct lapd_instance *lapd_instance_alloc(int network_side,
 					  void (*tx_cb)(uint8_t *data, int len,
 							void *cbdata), void *cbdata)
diff --git a/openbsc/src/input/lapd.h b/openbsc/src/input/lapd.h
index 724d0cf..fd11eda 100644
--- a/openbsc/src/input/lapd.h
+++ b/openbsc/src/input/lapd.h
@@ -36,6 +36,11 @@
 					  void (*tx_cb)(uint8_t *data, int len,
 							void *cbdata), void *cbdata);
 
-int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
+
+/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
+
+/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
+int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
 
 #endif /* OPENBSC_LAPD_H */