Import upstream version 0.9.13
diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am
new file mode 100644
index 0000000..de76929
--- /dev/null
+++ b/src/libbsc/Makefile.am
@@ -0,0 +1,25 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libbsc.a
+
+libbsc_a_SOURCES =	abis_nm.c abis_nm_vty.c \
+			abis_om2000.c abis_om2000_vty.c \
+			abis_rsl.c bsc_rll.c \
+			paging.c \
+			bts_ericsson_rbs2000.c \
+			bts_ipaccess_nanobts.c \
+			bts_siemens_bs11.c \
+			bts_hsl_femtocell.c \
+			bts_unknown.c \
+			chan_alloc.c \
+			gsm_subscriber_base.c \
+			handover_decision.c handover_logic.c meas_rep.c \
+			rest_octets.c system_information.c \
+			e1_config.c \
+			transaction.c \
+			bsc_api.c bsc_msc.c bsc_vty.c \
+			gsm_04_08_utils.c \
+			bsc_init.c
+
diff --git a/src/libbsc/Makefile.in b/src/libbsc/Makefile.in
new file mode 100644
index 0000000..e5d3d20
--- /dev/null
+++ b/src/libbsc/Makefile.in
@@ -0,0 +1,504 @@
+# Makefile.in generated by automake 1.11.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008, 2009  Free Software Foundation,
+# Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+subdir = src/libbsc
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/configure.in
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/bscconfig.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+AR = ar
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_$(V))
+am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
+am__v_AR_0 = @echo "  AR    " $@;
+AM_V_at = $(am__v_at_$(V))
+am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY))
+am__v_at_0 = @
+libbsc_a_AR = $(AR) $(ARFLAGS)
+libbsc_a_LIBADD =
+am_libbsc_a_OBJECTS = abis_nm.$(OBJEXT) abis_nm_vty.$(OBJEXT) \
+	abis_om2000.$(OBJEXT) abis_om2000_vty.$(OBJEXT) \
+	abis_rsl.$(OBJEXT) bsc_rll.$(OBJEXT) paging.$(OBJEXT) \
+	bts_ericsson_rbs2000.$(OBJEXT) bts_ipaccess_nanobts.$(OBJEXT) \
+	bts_siemens_bs11.$(OBJEXT) bts_hsl_femtocell.$(OBJEXT) \
+	bts_unknown.$(OBJEXT) chan_alloc.$(OBJEXT) \
+	gsm_subscriber_base.$(OBJEXT) handover_decision.$(OBJEXT) \
+	handover_logic.$(OBJEXT) meas_rep.$(OBJEXT) \
+	rest_octets.$(OBJEXT) system_information.$(OBJEXT) \
+	e1_config.$(OBJEXT) transaction.$(OBJEXT) bsc_api.$(OBJEXT) \
+	bsc_msc.$(OBJEXT) bsc_vty.$(OBJEXT) gsm_04_08_utils.$(OBJEXT) \
+	bsc_init.$(OBJEXT)
+libbsc_a_OBJECTS = $(am_libbsc_a_OBJECTS)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_$(V))
+am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY))
+am__v_CC_0 = @echo "  CC    " $@;
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_$(V))
+am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY))
+am__v_CCLD_0 = @echo "  CCLD  " $@;
+AM_V_GEN = $(am__v_GEN_$(V))
+am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY))
+am__v_GEN_0 = @echo "  GEN   " $@;
+SOURCES = $(libbsc_a_SOURCES)
+DIST_SOURCES = $(libbsc_a_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+COVERAGE_CFLAGS = @COVERAGE_CFLAGS@
+COVERAGE_LDFLAGS = @COVERAGE_LDFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GPRS_LIBGTP = @GPRS_LIBGTP@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBOSMOCORE_CFLAGS = @LIBOSMOCORE_CFLAGS@
+LIBOSMOCORE_LIBS = @LIBOSMOCORE_LIBS@
+LIBOSMOSCCP_CFLAGS = @LIBOSMOSCCP_CFLAGS@
+LIBOSMOSCCP_LIBS = @LIBOSMOSCCP_LIBS@
+LIBOSMOVTY_CFLAGS = @LIBOSMOVTY_CFLAGS@
+LIBOSMOVTY_LIBS = @LIBOSMOVTY_LIBS@
+LIBS = @LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+SYMBOL_VISIBILITY = @SYMBOL_VISIBILITY@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build_alias = @build_alias@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host_alias = @host_alias@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+noinst_LIBRARIES = libbsc.a
+libbsc_a_SOURCES = abis_nm.c abis_nm_vty.c \
+			abis_om2000.c abis_om2000_vty.c \
+			abis_rsl.c bsc_rll.c \
+			paging.c \
+			bts_ericsson_rbs2000.c \
+			bts_ipaccess_nanobts.c \
+			bts_siemens_bs11.c \
+			bts_hsl_femtocell.c \
+			bts_unknown.c \
+			chan_alloc.c \
+			gsm_subscriber_base.c \
+			handover_decision.c handover_logic.c meas_rep.c \
+			rest_octets.c system_information.c \
+			e1_config.c \
+			transaction.c \
+			bsc_api.c bsc_msc.c bsc_vty.c \
+			gsm_04_08_utils.c \
+			bsc_init.c
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/libbsc/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --gnu src/libbsc/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+	-test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+libbsc.a: $(libbsc_a_OBJECTS) $(libbsc_a_DEPENDENCIES) 
+	$(AM_V_at)-rm -f libbsc.a
+	$(AM_V_AR)$(libbsc_a_AR) libbsc.a $(libbsc_a_OBJECTS) $(libbsc_a_LIBADD)
+	$(AM_V_at)$(RANLIB) libbsc.a
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_nm.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_nm_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_om2000.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_om2000_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/abis_rsl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_api.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_init.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_msc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_rll.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bsc_vty.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_ericsson_rbs2000.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_hsl_femtocell.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_ipaccess_nanobts.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_siemens_bs11.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bts_unknown.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chan_alloc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/e1_config.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_04_08_utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsm_subscriber_base.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handover_decision.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/handover_logic.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/meas_rep.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paging.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rest_octets.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/system_information.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/transaction.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@am__fastdepCC_FALSE@	$(AM_V_CC) @AM_BACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+	list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	mkid -fID $$unique
+tags: TAGS
+
+TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	set x; \
+	here=`pwd`; \
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: CTAGS
+CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
+		$(TAGS_FILES) $(LISP)
+	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
+	unique=`for i in $$list; do \
+	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+	  done | \
+	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	  install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	  `test -z '$(STRIP)' || \
+	    echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-noinstLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
+	clean-noinstLIBRARIES ctags distclean distclean-compile \
+	distclean-generic distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-data \
+	install-data-am install-dvi install-dvi-am install-exec \
+	install-exec-am install-html install-html-am install-info \
+	install-info-am install-man install-pdf install-pdf-am \
+	install-ps install-ps-am install-strip installcheck \
+	installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \
+	uninstall-am
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/libbsc/abis_nm.c b/src/libbsc/abis_nm.c
new file mode 100644
index 0000000..0e7fc8d
--- /dev/null
+++ b/src/libbsc/abis_nm.c
@@ -0,0 +1,3132 @@
+/* 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/types.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <osmocore/talloc.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/misdn.h>
+#include <openbsc/signal.h>
+
+#define OM_ALLOC_SIZE		1024
+#define OM_HEADROOM_SIZE	128
+#define IPACC_SEGMENT_SIZE	245
+
+/* unidirectional messages from BTS to BSC */
+static const enum abis_nm_msgtype reports[] = {
+	NM_MT_SW_ACTIVATED_REP,
+	NM_MT_TEST_REP,
+	NM_MT_STATECHG_EVENT_REP,
+	NM_MT_FAILURE_EVENT_REP,
+};
+
+/* messages without ACK/NACK */
+static const enum abis_nm_msgtype no_ack_nack[] = {
+	NM_MT_MEAS_RES_REQ,
+	NM_MT_STOP_MEAS,
+	NM_MT_START_MEAS,
+};
+
+/* Messages related to software load */
+static const enum abis_nm_msgtype sw_load_msgs[] = {
+	NM_MT_LOAD_INIT_ACK,
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_SEG_ACK,
+	NM_MT_LOAD_ABORT,
+	NM_MT_LOAD_END_ACK,
+	NM_MT_LOAD_END_NACK,
+	//NM_MT_SW_ACT_REQ,
+	NM_MT_ACTIVATE_SW_ACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_SW_ACTIVATED_REP,
+};
+
+static const enum abis_nm_msgtype nacks[] = {
+	NM_MT_LOAD_INIT_NACK,
+	NM_MT_LOAD_END_NACK,
+	NM_MT_SW_ACT_REQ_NACK,
+	NM_MT_ACTIVATE_SW_NACK,
+	NM_MT_ESTABLISH_TEI_NACK,
+	NM_MT_CONN_TERR_SIGN_NACK,
+	NM_MT_DISC_TERR_SIGN_NACK,
+	NM_MT_CONN_TERR_TRAF_NACK,
+	NM_MT_DISC_TERR_TRAF_NACK,
+	NM_MT_CONN_MDROP_LINK_NACK,
+	NM_MT_DISC_MDROP_LINK_NACK,
+	NM_MT_SET_BTS_ATTR_NACK,
+	NM_MT_SET_RADIO_ATTR_NACK,
+	NM_MT_SET_CHAN_ATTR_NACK,
+	NM_MT_PERF_TEST_NACK,
+	NM_MT_SEND_TEST_REP_NACK,
+	NM_MT_STOP_TEST_NACK,
+	NM_MT_STOP_EVENT_REP_NACK,
+	NM_MT_REST_EVENT_REP_NACK,
+	NM_MT_CHG_ADM_STATE_NACK,
+	NM_MT_CHG_ADM_STATE_REQ_NACK,
+	NM_MT_REP_OUTST_ALARMS_NACK,
+	NM_MT_CHANGEOVER_NACK,
+	NM_MT_OPSTART_NACK,
+	NM_MT_REINIT_NACK,
+	NM_MT_SET_SITE_OUT_NACK,
+	NM_MT_CHG_HW_CONF_NACK,
+	NM_MT_GET_ATTR_NACK,
+	NM_MT_SET_ALARM_THRES_NACK,
+	NM_MT_BS11_BEGIN_DB_TX_NACK,
+	NM_MT_BS11_END_DB_TX_NACK,
+	NM_MT_BS11_CREATE_OBJ_NACK,
+	NM_MT_BS11_DELETE_OBJ_NACK,
+};
+
+static const struct value_string nack_names[] = {
+	{ NM_MT_LOAD_INIT_NACK,		"SOFTWARE LOAD INIT" },
+	{ NM_MT_LOAD_END_NACK,		"SOFTWARE LOAD END" },
+	{ NM_MT_SW_ACT_REQ_NACK,	"SOFTWARE ACTIVATE REQUEST" },
+	{ NM_MT_ACTIVATE_SW_NACK,	"ACTIVATE SOFTWARE" },
+	{ NM_MT_ESTABLISH_TEI_NACK,	"ESTABLISH TEI" },
+	{ NM_MT_CONN_TERR_SIGN_NACK,	"CONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_DISC_TERR_SIGN_NACK,	"DISCONNECT TERRESTRIAL SIGNALLING" },
+	{ NM_MT_CONN_TERR_TRAF_NACK,	"CONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_DISC_TERR_TRAF_NACK,	"DISCONNECT TERRESTRIAL TRAFFIC" },
+	{ NM_MT_CONN_MDROP_LINK_NACK,	"CONNECT MULTI-DROP LINK" },
+	{ NM_MT_DISC_MDROP_LINK_NACK,	"DISCONNECT MULTI-DROP LINK" },
+	{ NM_MT_SET_BTS_ATTR_NACK,	"SET BTS ATTRIBUTE" },
+	{ NM_MT_SET_RADIO_ATTR_NACK,	"SET RADIO ATTRIBUTE" },
+	{ NM_MT_SET_CHAN_ATTR_NACK,	"SET CHANNEL ATTRIBUTE" },
+	{ NM_MT_PERF_TEST_NACK,		"PERFORM TEST" },
+	{ NM_MT_SEND_TEST_REP_NACK,	"SEND TEST REPORT" },
+	{ NM_MT_STOP_TEST_NACK,		"STOP TEST" },
+	{ NM_MT_STOP_EVENT_REP_NACK,	"STOP EVENT REPORT" },
+	{ NM_MT_REST_EVENT_REP_NACK,	"RESET EVENT REPORT" },
+	{ NM_MT_CHG_ADM_STATE_NACK,	"CHANGE ADMINISTRATIVE STATE" },
+	{ NM_MT_CHG_ADM_STATE_REQ_NACK,
+				"CHANGE ADMINISTRATIVE STATE REQUEST" },
+	{ NM_MT_REP_OUTST_ALARMS_NACK,	"REPORT OUTSTANDING ALARMS" },
+	{ NM_MT_CHANGEOVER_NACK,	"CHANGEOVER" },
+	{ NM_MT_OPSTART_NACK,		"OPSTART" },
+	{ NM_MT_REINIT_NACK,		"REINIT" },
+	{ NM_MT_SET_SITE_OUT_NACK,	"SET SITE OUTPUT" },
+	{ NM_MT_CHG_HW_CONF_NACK,	"CHANGE HARDWARE CONFIGURATION" },
+	{ NM_MT_GET_ATTR_NACK,		"GET ATTRIBUTE" },
+	{ NM_MT_SET_ALARM_THRES_NACK,	"SET ALARM THRESHOLD" },
+	{ NM_MT_BS11_BEGIN_DB_TX_NACK,	"BS11 BEGIN DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_END_DB_TX_NACK,	"BS11 END DATABASE TRANSMISSION" },
+	{ NM_MT_BS11_CREATE_OBJ_NACK,	"BS11 CREATE OBJECT" },
+	{ NM_MT_BS11_DELETE_OBJ_NACK,	"BS11 DELETE OBJECT" },
+	{ 0,				NULL }
+};
+
+/* Chapter 9.4.36 */
+static const struct value_string nack_cause_names[] = {
+	/* General Nack Causes */
+	{ NM_NACK_INCORR_STRUCT,	"Incorrect message structure" },
+	{ NM_NACK_MSGTYPE_INVAL,	"Invalid message type value" },
+	{ NM_NACK_OBJCLASS_INVAL,	"Invalid Object class value" },
+	{ NM_NACK_OBJCLASS_NOTSUPP,	"Object class not supported" },
+	{ NM_NACK_BTSNR_UNKN,		"BTS no. unknown" },
+	{ NM_NACK_TRXNR_UNKN,		"Baseband Transceiver no. unknown" },
+	{ NM_NACK_OBJINST_UNKN,		"Object Instance unknown" },
+	{ NM_NACK_ATTRID_INVAL,		"Invalid attribute identifier value" },
+	{ NM_NACK_ATTRID_NOTSUPP,	"Attribute identifier not supported" },
+	{ NM_NACK_PARAM_RANGE,		"Parameter value outside permitted range" },
+	{ NM_NACK_ATTRLIST_INCONSISTENT,"Inconsistency in attribute list" },
+	{ NM_NACK_SPEC_IMPL_NOTSUPP,	"Specified implementation not supported" },
+	{ NM_NACK_CANT_PERFORM,		"Message cannot be performed" },
+	/* Specific Nack Causes */
+	{ NM_NACK_RES_NOTIMPL,		"Resource not implemented" },
+	{ NM_NACK_RES_NOTAVAIL,		"Resource not available" },
+	{ NM_NACK_FREQ_NOTAVAIL,	"Frequency not available" },
+	{ NM_NACK_TEST_NOTSUPP,		"Test not supported" },
+	{ NM_NACK_CAPACITY_RESTR,	"Capacity restrictions" },
+	{ NM_NACK_PHYSCFG_NOTPERFORM,	"Physical configuration cannot be performed" },
+	{ NM_NACK_TEST_NOTINIT,		"Test not initiated" },
+	{ NM_NACK_PHYSCFG_NOTRESTORE,	"Physical configuration cannot be restored" },
+	{ NM_NACK_TEST_NOSUCH,		"No such test" },
+	{ NM_NACK_TEST_NOSTOP,		"Test cannot be stopped" },
+	{ NM_NACK_MSGINCONSIST_PHYSCFG,	"Message inconsistent with physical configuration" },
+	{ NM_NACK_FILE_INCOMPLETE,	"Complete file notreceived" },
+	{ NM_NACK_FILE_NOTAVAIL,	"File not available at destination" },
+	{ NM_NACK_FILE_NOTACTIVATE,	"File cannot be activate" },
+	{ NM_NACK_REQ_NOT_GRANT,	"Request not granted" },
+	{ NM_NACK_WAIT,			"Wait" },
+	{ NM_NACK_NOTH_REPORT_EXIST,	"Nothing reportable existing" },
+	{ NM_NACK_MEAS_NOTSUPP,		"Measurement not supported" },
+	{ NM_NACK_MEAS_NOTSTART,	"Measurement not started" },
+	{ 0,				NULL }
+};
+
+static const char *nack_cause_name(u_int8_t cause)
+{
+	return get_value_string(nack_cause_names, cause);
+}
+
+/* Chapter 9.4.16: Event Type */
+static const struct value_string event_type_names[] = {
+	{ NM_EVT_COMM_FAIL,		"communication failure" },
+	{ NM_EVT_QOS_FAIL,		"quality of service failure" },
+	{ NM_EVT_PROC_FAIL,		"processing failure" },
+	{ NM_EVT_EQUIP_FAIL,		"equipment failure" },
+	{ NM_EVT_ENV_FAIL,		"environment failure" },
+	{ 0,				NULL }
+};
+
+static const char *event_type_name(u_int8_t cause)
+{
+	return get_value_string(event_type_names, cause);
+}
+
+/* Chapter 9.4.63: Perceived Severity */
+static const struct value_string severity_names[] = {
+	{ NM_SEVER_CEASED,		"failure ceased" },
+	{ NM_SEVER_CRITICAL,		"critical failure" },
+	{ NM_SEVER_MAJOR,		"major failure" },
+	{ NM_SEVER_MINOR,		"minor failure" },
+	{ NM_SEVER_WARNING,		"warning level failure" },
+	{ NM_SEVER_INDETERMINATE,	"indeterminate failure" },
+	{ 0,				NULL }
+};
+
+static const char *severity_name(u_int8_t cause)
+{
+	return get_value_string(severity_names, cause);
+}
+
+/* Attributes that the BSC can set, not only get, according to Section 9.4 */
+static const enum abis_nm_attr nm_att_settable[] = {
+	NM_ATT_ADD_INFO,
+	NM_ATT_ADD_TEXT,
+	NM_ATT_DEST,
+	NM_ATT_EVENT_TYPE,
+	NM_ATT_FILE_DATA,
+	NM_ATT_GET_ARI,
+	NM_ATT_HW_CONF_CHG,
+	NM_ATT_LIST_REQ_ATTR,
+	NM_ATT_MDROP_LINK,
+	NM_ATT_MDROP_NEXT,
+	NM_ATT_NACK_CAUSES,
+	NM_ATT_OUTST_ALARM,
+	NM_ATT_PHYS_CONF,
+	NM_ATT_PROB_CAUSE,
+	NM_ATT_RAD_SUBC,
+	NM_ATT_SOURCE,
+	NM_ATT_SPEC_PROB,
+	NM_ATT_START_TIME,
+	NM_ATT_TEST_DUR,
+	NM_ATT_TEST_NO,
+	NM_ATT_TEST_REPORT,
+	NM_ATT_WINDOW_SIZE,
+	NM_ATT_SEVERITY,
+	NM_ATT_MEAS_RES,
+	NM_ATT_MEAS_TYPE,
+};
+
+const struct tlv_definition nm_att_tlvdef = {
+	.def = {
+		[NM_ATT_ABIS_CHANNEL] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_ADD_INFO] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADD_TEXT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_ADM_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_ARFCN_LIST]=		{ TLV_TYPE_TL16V },
+		[NM_ATT_AUTON_REPORT] =		{ TLV_TYPE_TV },
+		[NM_ATT_AVAIL_STATUS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_BCCH_ARFCN] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_BSIC] =			{ TLV_TYPE_TV },
+		[NM_ATT_BTS_AIR_TIMER] =	{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_I_P] =		{ TLV_TYPE_TV },
+		[NM_ATT_CCCH_L_T] =		{ TLV_TYPE_TV },
+		[NM_ATT_CHAN_COMB] =		{ TLV_TYPE_TV },
+		[NM_ATT_CONN_FAIL_CRIT] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_DEST] =			{ TLV_TYPE_TL16V },
+		[NM_ATT_EVENT_TYPE] =		{ TLV_TYPE_TV },
+		[NM_ATT_FILE_DATA] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_FILE_VERSION] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_GSM_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_HSN] =			{ TLV_TYPE_TV },
+		[NM_ATT_HW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_DESC] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_INTAVE_PARAM] =		{ TLV_TYPE_TV },
+		[NM_ATT_INTERF_BOUND] =		{ TLV_TYPE_FIXED, 6 },
+		[NM_ATT_LIST_REQ_ATTR] =	{ TLV_TYPE_TL16V },
+		[NM_ATT_MAIO] =			{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_MANUF_THRESH] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MANUF_ID] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_MAX_TA] =		{ TLV_TYPE_TV },
+		[NM_ATT_MDROP_LINK] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_MDROP_NEXT] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_NACK_CAUSES] =		{ TLV_TYPE_TV },
+		[NM_ATT_NY1] =			{ TLV_TYPE_TV },
+		[NM_ATT_OPER_STATE] =		{ TLV_TYPE_TV },
+		[NM_ATT_OVERL_PERIOD] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_PHYS_CONF] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_POWER_CLASS] =		{ TLV_TYPE_TV },
+		[NM_ATT_POWER_THRESH] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_PROB_CAUSE] =		{ TLV_TYPE_FIXED, 3 },
+		[NM_ATT_RACH_B_THRESH] =	{ TLV_TYPE_TV },
+		[NM_ATT_LDAVG_SLOTS] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_RAD_SUBC] =		{ TLV_TYPE_TV },
+		[NM_ATT_RF_MAXPOWR_R] =		{ TLV_TYPE_TV },
+		[NM_ATT_SITE_INPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SITE_OUTPUTS] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SOURCE] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SPEC_PROB] =		{ TLV_TYPE_TV },
+		[NM_ATT_START_TIME] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_T200] =			{ TLV_TYPE_FIXED, 7 },
+		[NM_ATT_TEI] =			{ TLV_TYPE_TV },
+		[NM_ATT_TEST_DUR] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_TEST_NO] =		{ TLV_TYPE_TV },
+		[NM_ATT_TEST_REPORT] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_VSWR_THRESH] =		{ TLV_TYPE_FIXED, 2 },
+		[NM_ATT_WINDOW_SIZE] = 		{ TLV_TYPE_TV },
+		[NM_ATT_TSC] =			{ TLV_TYPE_TV },
+		[NM_ATT_SW_CONFIG] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_SEVERITY] = 		{ TLV_TYPE_TV },
+		[NM_ATT_GET_ARI] =		{ TLV_TYPE_TL16V },
+		[NM_ATT_HW_CONF_CHG] = 		{ TLV_TYPE_TL16V },
+		[NM_ATT_OUTST_ALARM] =		{ TLV_TYPE_TV },
+		[NM_ATT_MEAS_RES] =		{ TLV_TYPE_TL16V },
+	},
+};
+
+static const enum abis_nm_chan_comb chcomb4pchan[] = {
+	[GSM_PCHAN_CCCH]	= NM_CHANC_mainBCCH,
+	[GSM_PCHAN_CCCH_SDCCH4]	= NM_CHANC_BCCHComb,
+	[GSM_PCHAN_TCH_F]	= NM_CHANC_TCHFull,
+	[GSM_PCHAN_TCH_H]	= NM_CHANC_TCHHalf,
+	[GSM_PCHAN_SDCCH8_SACCH8C] = NM_CHANC_SDCCH,
+	[GSM_PCHAN_PDCH]	= NM_CHANC_IPAC_PDCH,
+	[GSM_PCHAN_TCH_F_PDCH]	= NM_CHANC_IPAC_TCHFull_PDCH,
+	/* FIXME: bounds check */
+};
+
+int abis_nm_chcomb4pchan(enum gsm_phys_chan_config pchan)
+{
+	if (pchan < ARRAY_SIZE(chcomb4pchan))
+		return chcomb4pchan[pchan];
+
+	return -EINVAL;
+}
+
+int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const u_int8_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, reports, ARRAY_SIZE(reports));
+}
+
+#define MT_ACK(x)	(x+1)
+#define MT_NACK(x)	(x+2)
+
+static void fill_om_hdr(struct abis_om_hdr *oh, u_int8_t len)
+{
+	oh->mdisc = ABIS_OM_MDISC_FOM;
+	oh->placement = ABIS_OM_PLACEMENT_ONLY;
+	oh->sequence = 0;
+	oh->length = len;
+}
+
+static void fill_om_fom_hdr(struct abis_om_hdr *oh, u_int8_t len,
+			    u_int8_t msg_type, u_int8_t obj_class,
+			    u_int8_t bts_nr, u_int8_t trx_nr, u_int8_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;
+}
+
+static struct msgb *nm_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE,
+				   "OML");
+}
+
+/* Send a OML NM Message from BSC to BTS */
+static int abis_nm_queue_msg(struct gsm_bts *bts, struct msgb *msg)
+{
+	msg->trx = bts->c0;
+
+	/* 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, 0);
+	} 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);
+
+const struct value_string abis_nm_obj_class_names[] = {
+	{ NM_OC_SITE_MANAGER,	"SITE-MANAGER" },
+	{ NM_OC_BTS,		"BTS" },
+	{ NM_OC_RADIO_CARRIER,	"RADIO-CARRIER" },
+	{ NM_OC_BASEB_TRANSC,	"BASEBAND-TRANSCEIVER" },
+	{ NM_OC_CHANNEL,	"CHANNEL" },
+	{ NM_OC_BS11_ADJC,	"ADJC" },
+	{ NM_OC_BS11_HANDOVER,	"HANDOVER" },
+	{ NM_OC_BS11_PWR_CTRL,	"POWER-CONTROL" },
+	{ NM_OC_BS11_BTSE,	"BTSE" },
+	{ NM_OC_BS11_RACK,	"RACK" },
+	{ NM_OC_BS11_TEST,	"TEST" },
+	{ NM_OC_BS11_ENVABTSE,	"ENVABTSE" },
+	{ NM_OC_BS11_BPORT,	"BPORT" },
+	{ NM_OC_GPRS_NSE,	"GPRS-NSE" },
+	{ NM_OC_GPRS_CELL,	"GPRS-CELL" },
+	{ NM_OC_GPRS_NSVC,	"GPRS-NSVC" },
+	{ NM_OC_BS11,		"SIEMENSHW" },
+	{ 0,			NULL }
+};
+
+static const char *obj_class_name(u_int8_t oc)
+{
+	return get_value_string(abis_nm_obj_class_names, oc);
+}
+
+const char *nm_opstate_name(u_int8_t os)
+{
+	switch (os) {
+	case NM_OPSTATE_DISABLED:
+		return "Disabled";
+	case NM_OPSTATE_ENABLED:
+		return "Enabled";
+	case NM_OPSTATE_NULL:
+		return "NULL";
+	default:
+		return "RFU";
+	}
+}
+
+/* Chapter 9.4.7 */
+static const struct value_string avail_names[] = {
+	{ 0, 	"In test" },
+	{ 1,	"Failed" },
+	{ 2,	"Power off" },
+	{ 3,	"Off line" },
+	/* Not used */
+	{ 5,	"Dependency" },
+	{ 6,	"Degraded" },
+	{ 7,	"Not installed" },
+	{ 0xff, "OK" },
+	{ 0,	NULL }
+};
+
+const char *nm_avail_name(u_int8_t avail)
+{
+	return get_value_string(avail_names, avail);
+}
+
+static struct value_string test_names[] = {
+	/* FIXME: standard test names */
+	{ NM_IPACC_TESTNO_CHAN_USAGE, "Channel Usage" },
+	{ NM_IPACC_TESTNO_BCCH_CHAN_USAGE, "BCCH Channel Usage" },
+	{ NM_IPACC_TESTNO_FREQ_SYNC, "Frequency Synchronization" },
+	{ NM_IPACC_TESTNO_BCCH_INFO, "BCCH Info" },
+	{ NM_IPACC_TESTNO_TX_BEACON, "Transmit Beacon" },
+	{ NM_IPACC_TESTNO_SYSINFO_MONITOR, "System Info Monitor" },
+	{ NM_IPACC_TESTNO_BCCCH_MONITOR, "BCCH Monitor" },
+	{ 0, NULL }
+};
+
+const struct value_string abis_nm_adm_state_names[] = {
+	{ NM_STATE_LOCKED,	"Locked" },
+	{ NM_STATE_UNLOCKED,	"Unlocked" },
+	{ NM_STATE_SHUTDOWN,	"Shutdown" },
+	{ NM_STATE_NULL,	"NULL" },
+	{ 0, NULL }
+};
+
+const char *nm_adm_name(u_int8_t adm)
+{
+	return get_value_string(abis_nm_adm_state_names, adm);
+}
+
+int nm_is_running(struct gsm_nm_state *s) {
+	return (s->operational == NM_OPSTATE_ENABLED) && (
+		(s->availability == NM_AVSTATE_OK) ||
+		(s->availability == 0xff)
+	);
+}
+
+static void debugp_foh(struct abis_om_fom_hdr *foh)
+{
+	DEBUGP(DNM, "OC=%s(%02x) INST=(%02x,%02x,%02x) ",
+		obj_class_name(foh->obj_class), foh->obj_class,
+		foh->obj_inst.bts_nr, foh->obj_inst.trx_nr,
+		foh->obj_inst.ts_nr);
+}
+
+/* obtain the gsm_nm_state data structure for a given object instance */
+static struct gsm_nm_state *
+objclass2nmstate(struct gsm_bts *bts, u_int8_t obj_class,
+		 struct abis_om_obj_inst *obj_inst)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_nm_state *nm_state = NULL;
+
+	switch (obj_class) {
+	case NM_OC_BTS:
+		nm_state = &bts->nm_state;
+		break;
+	case NM_OC_RADIO_CARRIER:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		nm_state = &trx->nm_state;
+		break;
+	case NM_OC_BASEB_TRANSC:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		nm_state = &trx->bb_transc.nm_state;
+		break;
+	case NM_OC_CHANNEL:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		if (obj_inst->ts_nr >= TRX_NR_TS)
+			return NULL;
+		nm_state = &trx->ts[obj_inst->ts_nr].nm_state;
+		break;
+	case NM_OC_SITE_MANAGER:
+		nm_state = &bts->site_mgr.nm_state;
+		break;
+	case NM_OC_BS11:
+		switch (obj_inst->bts_nr) {
+		case BS11_OBJ_CCLK:
+			nm_state = &bts->bs11.cclk.nm_state;
+			break;
+		case BS11_OBJ_BBSIG:
+			if (obj_inst->ts_nr > bts->num_trx)
+				return NULL;
+			trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+			nm_state = &trx->bs11.bbsig.nm_state;
+			break;
+		case BS11_OBJ_PA:
+			if (obj_inst->ts_nr > bts->num_trx)
+				return NULL;
+			trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+			nm_state = &trx->bs11.pa.nm_state;
+			break;
+		default:
+			return NULL;
+		}
+	case NM_OC_BS11_RACK:
+		nm_state = &bts->bs11.rack.nm_state;
+		break;
+	case NM_OC_BS11_ENVABTSE:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse))
+			return NULL;
+		nm_state = &bts->bs11.envabtse[obj_inst->trx_nr].nm_state;
+		break;
+	case NM_OC_GPRS_NSE:
+		nm_state = &bts->gprs.nse.nm_state;
+		break;
+	case NM_OC_GPRS_CELL:
+		nm_state = &bts->gprs.cell.nm_state;
+		break;
+	case NM_OC_GPRS_NSVC:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+			return NULL;
+		nm_state = &bts->gprs.nsvc[obj_inst->trx_nr].nm_state;
+		break;
+	}
+	return nm_state;
+}
+
+/* obtain the in-memory data structure of a given object instance */
+static void *
+objclass2obj(struct gsm_bts *bts, u_int8_t obj_class,
+	     struct abis_om_obj_inst *obj_inst)
+{
+	struct gsm_bts_trx *trx;
+	void *obj = NULL;
+
+	switch (obj_class) {
+	case NM_OC_BTS:
+		obj = bts;
+		break;
+	case NM_OC_RADIO_CARRIER:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		obj = trx;
+		break;
+	case NM_OC_BASEB_TRANSC:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		obj = &trx->bb_transc;
+		break;
+	case NM_OC_CHANNEL:
+		if (obj_inst->trx_nr >= bts->num_trx) {
+			DEBUGPC(DNM, "TRX %u does not exist ", obj_inst->trx_nr);
+			return NULL;
+		}
+		trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+		if (obj_inst->ts_nr >= TRX_NR_TS)
+			return NULL;
+		obj = &trx->ts[obj_inst->ts_nr];
+		break;
+	case NM_OC_SITE_MANAGER:
+		obj = &bts->site_mgr;
+		break;
+	case NM_OC_GPRS_NSE:
+		obj = &bts->gprs.nse;
+		break;
+	case NM_OC_GPRS_CELL:
+		obj = &bts->gprs.cell;
+		break;
+	case NM_OC_GPRS_NSVC:
+		if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+			return NULL;
+		obj = &bts->gprs.nsvc[obj_inst->trx_nr];
+		break;
+	}
+	return obj;
+}
+
+/* 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, u_int8_t obj_class,
+			   struct abis_om_obj_inst *obj_inst, u_int8_t adm_state)
+{
+	struct gsm_nm_state *nm_state, new_state;
+	struct nm_statechg_signal_data nsd;
+
+	nsd.obj = objclass2obj(bts, obj_class, obj_inst);
+	if (!nsd.obj)
+		return -EINVAL;
+	nm_state = objclass2nmstate(bts, obj_class, obj_inst);
+	if (!nm_state)
+		return -1;
+
+	new_state = *nm_state;
+	new_state.administrative = adm_state;
+
+	nsd.obj_class = obj_class;
+	nsd.old_state = nm_state;
+	nsd.new_state = &new_state;
+	nsd.obj_inst = obj_inst;
+	dispatch_signal(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 gsm_bts *bts = mb->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 = 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 ", 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) ", 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 ", nm_adm_name(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 = 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;
+		dispatch_signal(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 int rx_fail_evt_rep(struct msgb *mb)
+{
+	struct abis_om_hdr *oh = msgb_l2(mb);
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	struct tlv_parsed tp;
+	const uint8_t *p_val;
+	char *p_text;
+
+	DEBUGPC(DNM, "Failure Event Report ");
+	
+	abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+	if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE))
+		DEBUGPC(DNM, "Type=%s ", event_type_name(*TLVP_VAL(&tp, NM_ATT_EVENT_TYPE)));
+	if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY))
+		DEBUGPC(DNM, "Severity=%s ", 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);
+		DEBUGPC(DNM, "Probable cause= %02X %02X %02X ", p_val[0], p_val[1], p_val[2]);
+	}
+	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 (p_text) {
+			DEBUGPC(DNM, "Additional Text=%s ", p_text);
+			talloc_free(p_text);
+		}
+	}
+
+	DEBUGPC(DNM, "\n");
+
+	return 0;
+}
+
+static int abis_nm_rcvmsg_report(struct msgb *mb)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(mb);
+	u_int8_t mt = foh->msg_type;
+
+	debugp_foh(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");
+		dispatch_signal(SS_NM, S_NM_SW_ACTIV_REP, mb);
+		break;
+	case NM_MT_FAILURE_EVENT_REP:
+		rx_fail_evt_rep(mb);
+		dispatch_signal(SS_NM, S_NM_FAIL_REP, mb);
+		break;
+	case NM_MT_TEST_REP:
+		DEBUGPC(DNM, "Test Report\n");
+		dispatch_signal(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, u_int8_t obj_class, u_int8_t i0, u_int8_t i1,
+			     u_int8_t i2, const u_int8_t *sw_desc, u_int8_t swdesc_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = swdesc_len;
+	u_int8_t *trailer;
+
+	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);
+
+	trailer = msgb_put(msg, swdesc_len);
+	memcpy(trailer, sw_desc, swdesc_len);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+static int abis_nm_parse_sw_descr(const u_int8_t *sw_descr, int sw_descr_len)
+{
+	static const struct tlv_definition sw_descr_def = {
+		.def = {
+			[NM_ATT_FILE_ID] =		{ TLV_TYPE_TL16V, },
+			[NM_ATT_FILE_VERSION] =		{ TLV_TYPE_TL16V, },
+		},
+	};
+
+	u_int8_t tag;
+	u_int16_t tag_len;
+	const u_int8_t *val;
+	int ofs = 0, len;
+
+	/* Classic TLV parsing doesn't work well with SW_DESCR because of it's
+	 * nested nature and the fact you have to assume it contains only two sub
+	 * tags NM_ATT_FILE_VERSION & NM_ATT_FILE_ID to parse it */
+
+	if (sw_descr[0] != NM_ATT_SW_DESCR) {
+		DEBUGP(DNM, "SW_DESCR attribute identifier not found!\n");
+		return -1;
+	}
+	ofs += 1;
+
+	len = tlv_parse_one(&tag, &tag_len, &val,
+		&sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs);
+	if (len < 0 || (tag != NM_ATT_FILE_ID)) {
+		DEBUGP(DNM, "FILE_ID attribute identifier not found!\n");
+		return -2;
+	}
+	ofs += len;
+
+	len = tlv_parse_one(&tag, &tag_len, &val,
+		&sw_descr_def, &sw_descr[ofs], sw_descr_len-ofs);
+	if (len < 0 || (tag != NM_ATT_FILE_VERSION)) {
+		DEBUGP(DNM, "FILE_VERSION attribute identifier not found!\n");
+		return -3;
+	}
+	ofs += len;
+
+	return ofs;
+}
+
+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 tlv_parsed tp;
+	const u_int8_t *sw_config;
+	int ret, sw_config_len, sw_descr_len;
+
+	debugp_foh(foh);
+
+	DEBUGPC(DNM, "SW Activate Request: ");
+
+	DEBUGP(DNM, "Software Activate Request, ACKing and Activating\n");
+
+	ret = abis_nm_sw_act_req_ack(mb->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));
+
+	abis_nm_tlv_parse(&tp, mb->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)) {
+		DEBUGP(DNM, "SW config not found! Can't continue.\n");
+		return -EINVAL;
+	} else {
+		DEBUGP(DNM, "Found SW config: %s\n", hexdump(sw_config, sw_config_len));
+	}
+
+		/* Use the first SW_DESCR present in SW config */
+	sw_descr_len = abis_nm_parse_sw_descr(sw_config, sw_config_len);
+	if (sw_descr_len < 0)
+		return -EINVAL;
+
+	return ipacc_sw_activate(mb->trx->bts, foh->obj_class,
+				 foh->obj_inst.bts_nr,
+				 foh->obj_inst.trx_nr,
+				 foh->obj_inst.ts_nr,
+				 sw_config, sw_descr_len);
+}
+
+/* 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 tlv_parsed tp;
+	u_int8_t adm_state;
+
+	abis_nm_tlv_parse(&tp, mb->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(mb->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 tlv_parsed tp;
+
+	DEBUGP(DNM, "LMT Event ");
+	abis_nm_tlv_parse(&tp, mb->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) {
+		u_int8_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) {
+		u_int8_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;
+}
+
+static 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, 0);
+
+		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);
+	u_int8_t mt = foh->msg_type;
+	int ret = 0;
+
+	/* check for unsolicited message */
+	if (is_report(mt))
+		return abis_nm_rcvmsg_report(mb);
+
+	if (is_in_arr(mt, sw_load_msgs, ARRAY_SIZE(sw_load_msgs)))
+		return abis_nm_rcvmsg_sw(mb);
+
+	if (is_in_arr(mt, nacks, ARRAY_SIZE(nacks))) {
+		struct nm_nack_signal_data nack_data;
+		struct tlv_parsed tp;
+
+		debugp_foh(foh);
+
+		DEBUGPC(DNM, "%s NACK ", get_value_string(nack_names, mt));
+
+		abis_nm_tlv_parse(&tp, mb->trx->bts, foh->data, oh->length-sizeof(*foh));
+		if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES))
+			DEBUGPC(DNM, "CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			DEBUGPC(DNM, "\n");
+
+		nack_data.msg = mb;
+		nack_data.mt = mt;
+		dispatch_signal(SS_NM, S_NM_NACK, &nack_data);
+		abis_nm_queue_send_next(mb->trx->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_CONN_MDROP_LINK_ACK:
+		DEBUGP(DNM, "CONN MDROP LINK ACK\n");
+		break;
+	case NM_MT_IPACC_RESTART_ACK:
+		dispatch_signal(SS_NM, S_NM_IPACC_RESTART_ACK, NULL);
+		break;
+	case NM_MT_IPACC_RESTART_NACK:
+		dispatch_signal(SS_NM, S_NM_IPACC_RESTART_NACK, NULL);
+		break;
+	case NM_MT_SET_BTS_ATTR_ACK:
+		/* The HSL wants an OPSTART _after_ the SI has been set */
+		if (mb->trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO) {
+			abis_nm_opstart(mb->trx->bts, NM_OC_BTS, 255, 255, 255);
+		}
+		break;
+	}
+
+	abis_nm_queue_send_next(mb->trx->bts);
+	return ret;
+}
+
+static int abis_nm_rx_ipacc(struct msgb *mb);
+
+static int abis_nm_rcvmsg_manuf(struct msgb *mb)
+{
+	int rc;
+	int bts_type = mb->trx->bts->type;
+
+	switch (bts_type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		rc = abis_nm_rx_ipacc(mb);
+		abis_nm_queue_send_next(mb->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)
+			return -EINVAL;
+	}
+	if (oh->sequence != 0) {
+		LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n",
+			oh->sequence);
+		return -EINVAL;
+	}
+#if 0
+	unsigned int l2_len = msg->tail - (u_int8_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);
+		return -EINVAL;
+	}
+
+	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 */
+	u_int8_t obj_class;
+	u_int8_t obj_instance[3];
+
+	u_int8_t file_id[255];
+	u_int8_t file_id_len;
+
+	u_int8_t file_version[255];
+	u_int8_t file_version_len;
+
+	u_int8_t window_size;
+	u_int8_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();
+	u_int8_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);
+	if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) {
+		fseek(stream, pos, SEEK_SET);
+		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;
+	u_int8_t 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, (u_int8_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: {
+		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 u_int8_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();
+	u_int8_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();
+	u_int8_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);
+	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(mb->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(mb->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(mb->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(mb->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(mb->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(mb->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(mb->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,
+			  u_int8_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, u_int8_t bts_port,
+		       u_int8_t ts_nr, u_int8_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, u_int8_t trx_nr,
+			  u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_t e1_subslot,
+			  u_int8_t tei)
+{
+	struct abis_om_hdr *oh;
+	struct abis_nm_channel *ch;
+	u_int8_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,
+			   u_int8_t e1_port, u_int8_t e1_timeslot, u_int8_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,
+			   u_int8_t e1_port, u_int8_t e1_timeslot,
+			   u_int8_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,
+			   u_int8_t subchan)
+{
+}
+#endif
+
+/* Chapter 8.6.1 */
+int abis_nm_set_bts_attr(struct gsm_bts *bts, u_int8_t *attr, int attr_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_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, u_int8_t *attr, int attr_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_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);
+}
+
+static int verify_chan_comb(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb)
+{
+	int i;
+
+	/* 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:
+			/* 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)
+					return -EINVAL;
+			}
+			/* not allowed for TS0 of BCCH-TRX */
+			if (ts->trx == ts->trx->bts->c0 &&
+			    ts->nr == 0)
+					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))
+					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)
+				return -EINVAL;
+			break;
+		case NM_CHANC_BCCH:
+			/* allowed only for TS 2/4/6 of C0 */
+			if (ts->trx != ts->trx->bts->c0)
+				return -EINVAL;
+			if (ts->nr != 2 && ts->nr != 4 &&
+			    ts->nr != 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:
+					return -EINVAL;
+				}
+			} else {
+				switch (chan_comb) {
+				case NM_CHANC_TCHFull:
+				case NM_CHANC_TCHHalf:
+				case NM_CHANC_IPAC_TCHFull_TCHHalf:
+					return 0;
+				default:
+					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;
+					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:
+					return 0;
+				}
+			} 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:
+					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:
+				if (ts->trx->nr == 0)
+					return 0;
+				else
+					return -EINVAL;
+			}
+			break;
+		}
+		return -EINVAL;
+	default:
+		/* unknown BTS type */
+		return 0;
+	}
+	return 0;
+}
+
+/* Chapter 8.6.3 */
+int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, u_int8_t chan_comb)
+{
+	struct gsm_bts *bts = ts->trx->bts;
+	struct abis_om_hdr *oh;
+	u_int8_t zero = 0x00;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t len = 2 + 2;
+
+	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) < 0) {
+		msgb_free(msg);
+		DEBUGP(DNM, "Invalid Channel Combination!!!\n");
+		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, bts->tsc);	/* 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, u_int8_t obj_class, u_int8_t i1,
+			u_int8_t i2, u_int8_t i3, int nack, u_int8_t *attr, int att_len)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_t msgtype = NM_MT_SW_ACT_REQ_ACK;
+	u_int8_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) {
+		u_int8_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, u_int8_t *rawmsg)
+{
+	struct msgb *msg = nm_msgb_alloc();
+	struct abis_om_hdr *oh;
+	u_int8_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, u_int8_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, u_int8_t obj_class, u_int8_t i0, u_int8_t i1, u_int8_t i2)
+{
+	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_OPSTART, obj_class, i0, i1, i2);
+
+	debugp_foh((struct abis_om_fom_hdr *) oh->data);
+	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, u_int8_t obj_class, u_int8_t i0,
+			  u_int8_t i1, u_int8_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, u_int8_t e1_port0, u_int8_t ts0,
+			    u_int8_t e1_port1, u_int8_t ts1)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_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, u_int8_t obj_class,
+			 u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t test_nr, u_int8_t auton_report, struct msgb *msg)
+{
+	struct abis_om_hdr *oh;
+
+	DEBUGP(DNM, "PEFORM TEST %s\n", get_value_string(test_names, 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 {
+	u_int16_t	year;
+	u_int8_t	month;
+	u_int8_t	day;
+	u_int8_t	hour;
+	u_int8_t	min;
+	u_int8_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, u_int8_t idx,
+				u_int8_t attr_len, const u_int8_t *attr)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_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, u_int8_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, u_int8_t idx)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_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, u_int8_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, u_int8_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 u_int8_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, u_int8_t e1_port,
+			  u_int8_t e1_timeslot, u_int8_t e1_subslot,
+			  u_int8_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, u_int8_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();
+	u_int8_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();
+	u_int8_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();
+	u_int8_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 u_int8_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, u_int8_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) {
+		u_int8_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), (u_int8_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), (u_int8_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 u_int8_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;
+	u_int8_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;
+	u_int8_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];
+	u_int8_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 */
+		strncpy(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,
+			  u_int8_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;
+
+	strncpy(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 u_int8_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 u_int8_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 u_int8_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), (u_int8_t *) &aet);
+
+	return abis_nm_sendmsg(bts, msg);
+}
+
+int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, u_int8_t bport)
+{
+	struct abis_om_hdr *oh;
+	struct msgb *msg = nm_msgb_alloc();
+	u_int8_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, u_int8_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;
+	u_int8_t idstrlen = oh->data[0];
+	struct tlv_parsed tp;
+	struct ipacc_ack_signal_data signal;
+
+	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, msg->trx->bts, foh->data, oh->length-sizeof(*foh));
+
+	debugp_foh(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(*((u_int16_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))
+			DEBUGPC(DNM, " CAUSE=%s\n",
+				nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES)));
+		else
+			DEBUGPC(DNM, "\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",
+				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",
+				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",
+				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(msg->trx->bts, foh->obj_inst.trx_nr);
+		signal.msg_type = foh->msg_type;
+		dispatch_signal(SS_NM, S_NM_IPACC_NACK, &signal);
+		break;
+	case NM_MT_IPACC_SET_NVATTR_ACK:
+		signal.trx = gsm_bts_trx_by_nr(msg->trx->bts, foh->obj_inst.trx_nr);
+		signal.msg_type = foh->msg_type;
+		dispatch_signal(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, u_int8_t msg_type,
+			 u_int8_t obj_class, u_int8_t bts_nr,
+			 u_int8_t trx_nr, u_int8_t ts_nr,
+			 u_int8_t *attr, int attr_len)
+{
+	struct msgb *msg = nm_msgb_alloc();
+	struct abis_om_hdr *oh;
+	struct abis_om_fom_hdr *foh;
+	u_int8_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, u_int8_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,
+				 u_int32_t ip, u_int16_t port, u_int8_t stream)
+{
+	struct in_addr ia;
+	u_int8_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;
+	*(u_int32_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(trx->bts, msg);
+}
+
+int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, u_int8_t obj_class,
+				u_int8_t bts_nr, u_int8_t trx_nr, u_int8_t ts_nr,
+				u_int8_t *attr, u_int8_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(u_int8_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);
+	*((u_int16_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->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(u_int8_t res)
+{
+	return get_value_string(ipacc_testres_names, res);
+}
+
+void ipac_parse_cgi(struct cell_global_id *cid, const u_int8_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(*((u_int16_t *)&buf[3]));
+	cid->ci = ntohs(*((u_int16_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, u_int8_t *buf)
+{
+	u_int8_t *cur = buf;
+	u_int16_t len;
+
+	memset(binf, 0, sizeof(*binf));
+
+	if (cur[0] != NM_IPAC_EIE_BCCH_INFO)
+		return -EINVAL;
+	cur++;
+
+	len = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	binf->info_type = ntohs(*(u_int16_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(*(u_int16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FRAME_OFFSET)
+		binf->frame_offset = ntohs(*(u_int16_t *)cur);
+	cur += 2;
+
+	if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET)
+		binf->frame_nr_offset = ntohl(*(u_int32_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_vty.c b/src/libbsc/abis_nm_vty.c
new file mode 100644
index 0000000..996a857
--- /dev/null
+++ b/src/libbsc/abis_nm_vty.c
@@ -0,0 +1,197 @@
+/* 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 <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_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 "FIXME"
+
+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(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->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(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->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_attrib_get, oml_attrib_get_cmd,
+	"attribute get <0-255>",
+	"OML Attribute Actions\n" "Get a single OML Attribute\n"
+	"OML Attribute Number\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	/* FIXME */
+	return CMD_SUCCESS;
+}
+
+DEFUN(oml_attrib_set, oml_attrib_set_cmd,
+	"attribute set <0-255> .HEX",
+	"OML Attribute Actions\n" "Set a single OML Attribute\n"
+	"OML Attribute Number\n")
+{
+	struct oml_node_state *oms = vty->index;
+
+	/* FIXME */
+	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);
+
+	install_default(OML_NODE);
+	install_element(OML_NODE, &ournode_exit_cmd);
+	install_element(OML_NODE, &oml_attrib_get_cmd);
+	install_element(OML_NODE, &oml_attrib_set_cmd);
+	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 0000000..805b844
--- /dev/null
+++ b/src/libbsc/abis_om2000.c
@@ -0,0 +1,1078 @@
+/* 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>
+#include <openbsc/e1_input.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_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_NEGOT_REQ_ACK			= 0x0104,
+	OM2K_MSGT_NEGOT_REQ_NACK		= 0x0105,
+	OM2K_MSGT_NEGOT_REQ			= 0x0106,
+};
+
+enum abis_om2k_dei {
+	OM2K_DEI_BCC				= 0x06,
+	OM2K_DEI_BSIC				= 0x09,
+	OM2K_DEI_CAL_TIME			= 0x0d,
+	OM2K_DEI_COMBINATION			= 0x0f,
+	OM2K_DEI_CON_CONN_LIST			= 0x10,
+	OM2K_DEI_END_LIST_NR			= 0x13,
+	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_IS_CONN_LIST			= 0x27,
+	OM2K_DEI_LIST_NR			= 0x28,
+	OM2K_DEI_MAIO				= 0x2b,
+	OM2K_DEI_OP_INFO			= 0x2e,
+	OM2K_DEI_POWER				= 0x2f,
+	OM2K_DEI_RX_DIVERSITY			= 0x33,
+	OM2K_DEI_TF_MODE			= 0x3a,
+	OM2K_DEI_TS_NR				= 0x3c,
+	OM2K_DEI_EXT_RANGE			= 0x47,
+	OM2K_DEI_NEGOT_REC1			= 0x90,
+	OM2K_DEI_NEGOT_REC2			= 0x91,
+	OM2K_DEI_FS_OFFSET			= 0x98,
+};
+
+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 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;
+}
+
+static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg)
+{
+	struct abis_om2k_hdr *o2h;
+	int to_trx_oml;
+
+	msg->l2h = msg->data;
+	o2h = (struct abis_om2k_hdr *) msg->l2h;
+
+	switch (o2h->mo.class) {
+	case OM2K_MO_CLS_TRXC:
+	case OM2K_MO_CLS_TX:
+	case OM2K_MO_CLS_RX:
+	case OM2K_MO_CLS_TS:
+		/* Route through per-TRX OML Link to the appropriate TRX */
+		to_trx_oml = 1;
+		msg->trx = gsm_bts_trx_by_nr(bts, o2h->mo.inst);
+		if (!msg->trx) {
+			LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to "
+				"non-existing TRX\n", om2k_mo_name(&o2h->mo));
+			return -ENODEV;
+		}
+		break;
+	default:
+		/* Route through the IXU/DXU OML Link */
+		msg->trx = bts->c0;
+		to_trx_oml = 0;
+		break;
+	}
+
+	return _abis_nm_sendmsg(msg, to_trx_oml);
+}
+
+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));
+}
+
+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 };
+const struct abis_om2k_mo om2k_mo_con = { OM2K_MO_CLS_CON, 0, 0xFF, 0 };
+const struct abis_om2k_mo om2k_mo_tf = { OM2K_MO_CLS_TF, 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);
+}
+
+int abis_om2k_tx_con_conf_req(struct gsm_bts *bts, 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, &om2k_mo_con, OM2K_MSGT_CON_CONF_REQ,
+		      2 + 2 + TLV_GROSS_LEN(len));
+
+	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_CON_CONN_LIST, len, data);
+
+	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 = 0;
+}
+
+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, 3+2);
+
+	msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, trx->arfcn);
+	msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x03); /* A+B */
+
+	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, 3+2+2+2);
+
+	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, &om2k_mo_tf, OM2K_MSGT_TF_CONF_REQ,
+			2+1+sizeof(fs_offset_undef));
+
+	msgb_tv_put(msg, OM2K_DEI_TF_MODE, OM2K_TF_MODE_STANDALONE);
+	msgb_tv_fixed_put(msg, OM2K_DEI_FS_OFFSET,
+			  sizeof(fs_offset_undef), fs_offset_undef);
+
+	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:
+		return 8;
+	default:
+		return 0;
+	}
+}
+
+/* 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;
+
+	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++ = 0x00;
+				*cur++ = i >> 8;
+				*cur++ = i & 0xff;
+			}
+		}
+	} else {
+		*cur++ = 0x00; /* TX/RX address */
+		*cur++ = ts->trx->arfcn >> 8;
+		*cur++ = ts->trx->arfcn && 0xff;
+	}
+	return (cur - list);
+}
+
+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);
+
+	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,
+			2+2+TLV_GROSS_LEN(freq_list_len)+2+2+2+2+3+2);
+
+	msgb_tv_put(msg, OM2K_DEI_COMBINATION, pchan2comb(ts->pchan));
+	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, 0x03); /* A+B */
+	msgb_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0);
+	msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */
+	/* Optional: Interference Rejection Combining */
+
+	return abis_om2k_sendmsg(ts->trx->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_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_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_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_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_STATUS_RESP:
+		break;
+	case OM2K_MSGT_START_REQ_ACK:
+	case OM2K_MSGT_CON_CONF_REQ_ACK:
+	case OM2K_MSGT_IS_CONF_REQ_ACK:
+	case OM2K_MSGT_TX_CONF_REQ_ACK:
+	case OM2K_MSGT_RX_CONF_REQ_ACK:
+	case OM2K_MSGT_TS_CONF_REQ_ACK:
+	case OM2K_MSGT_TF_CONF_REQ_ACK:
+	case OM2K_MSGT_ENABLE_REQ_ACK:
+	case OM2K_MSGT_ALARM_STATUS_REQ_ACK:
+	case OM2K_MSGT_DISABLE_REQ_ACK:
+		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/src/libbsc/abis_om2000_vty.c b/src/libbsc/abis_om2000_vty.c
new file mode 100644
index 0000000..5ebb2a3
--- /dev/null
+++ b/src/libbsc/abis_om2000_vty.c
@@ -0,0 +1,513 @@
+/* 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 	"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(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;
+}
+
+struct con_conn_group {
+	struct llist_head list;
+
+	uint8_t cg;
+	uint16_t ccp;
+	uint8_t tag;
+	uint8_t tei;
+};
+
+static void add_con_list(struct gsm_bts *bts, uint8_t cg, uint16_t ccp,
+			 uint8_t tag, uint8_t tei)
+{
+	struct con_conn_group *ent = talloc_zero(bts, struct con_conn_group);
+
+	ent->cg = cg;
+	ent->ccp = ccp;
+	ent->tag = tag;
+	ent->tei = tei;
+
+	llist_add_tail(&ent->list, &bts->rbs2000.con.conn_groups);
+}
+
+static int del_con_list(struct gsm_bts *bts, uint8_t cg, uint16_t ccp,
+			uint8_t tag, uint8_t tei)
+{
+	struct con_conn_group *grp, *grp2;
+
+	llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.con.conn_groups, list) {
+		if (grp->cg == cg && grp->ccp == ccp && grp->tag == tag
+		    && grp->tei == tei) {
+			llist_del(&grp->list);
+			talloc_free(grp);
+			return 0;
+		}
+	}
+	return -ENOENT;
+}
+
+#define CON_LIST_HELP	"CON connetiton list\n"			\
+			"Add entry to CON list\n"		\
+			"Delete entry from CON list\n"		\
+			"Connection Group Number\n"		\
+			"CON Connection Point\n"		\
+
+DEFUN(om2k_con_list_dec, om2k_con_list_dec_cmd,
+	"con-connection-list (add|del) <1-255> <0-1023> deconcentrated",
+	CON_LIST_HELP "De-concentrated in/outlet\n")
+{
+	struct oml_node_state *oms = vty->index;
+	struct gsm_bts *bts = oms->bts;
+	uint8_t cg = atoi(argv[1]);
+	uint16_t ccp = atoi(argv[2]);
+
+	if (!strcmp(argv[0], "add"))
+		add_con_list(bts, cg, ccp, 0, 0xff);
+	else {
+		if (del_con_list(bts, cg, ccp, 0, 0xff) < 0) {
+			vty_out(vty, "%% No matching CON list entry%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(om2k_con_list_tei, om2k_con_list_tei_cmd,
+	"con-connection-list (add|del) <1-255> <0-1023> tei <0-63>",
+	CON_LIST_HELP "Concentrated in/outlet with TEI\n" "TEI Number\n")
+{
+	struct oml_node_state *oms = vty->index;
+	struct gsm_bts *bts = oms->bts;
+	uint8_t cg = atoi(argv[1]);
+	uint16_t ccp = atoi(argv[2]);
+	uint8_t tei = atoi(argv[3]);
+
+	if (!strcmp(argv[0], "add"))
+		add_con_list(bts, cg, ccp, cg, tei);
+	else {
+		if (del_con_list(bts, cg, ccp, cg, tei) < 0) {
+			vty_out(vty, "%% No matching CON list entry%s",
+				VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}
+
+	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;
+}
+
+struct is_conn_group {
+	struct llist_head list;
+	uint16_t icp1;
+	uint16_t icp2;
+	uint8_t ci;
+};
+
+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 Connnection 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 (!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_is_conf_req, om2k_is_conf_req_cmd,
+	"is-conf-req",
+	"Send IS Configuration Request\n")
+{
+	struct oml_node_state *oms = vty->index;
+	struct gsm_bts *bts = oms->bts;
+	struct is_conn_group *grp;
+	unsigned int num_grps = 0, i = 0;
+	struct om2k_is_conn_grp *o2grps;
+
+	/* count number of groups in linked list */
+	llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list)
+		num_grps++;
+
+	if (!num_grps) {
+		vty_out(vty, "%% No IS connection groups configured!%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	/* allocate buffer for oml group array */
+	o2grps = 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(&o2grps[i++], grp->icp1, grp->icp2, grp->ci);
+
+	/* send the actual OML request */
+	abis_om2k_tx_is_conf_req(oms->bts, o2grps, num_grps);
+
+	talloc_free(o2grps);
+
+	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_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_rx_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;
+}
+
+void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+	struct is_conn_group *igrp;
+	struct con_conn_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-list add %u %u ",
+			cgrp->cg, cgrp->ccp);
+		if (cgrp->tei == 0xff)
+			vty_out(vty, "deconcentrated%s", VTY_NEWLINE);
+		else
+			vty_out(vty, "tei %u%s", cgrp->tei, 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);
+
+	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_conf_req_cmd);
+	install_element(OM2K_NODE, &om2k_is_conf_req_cmd);
+	install_element(OM2K_NODE, &om2k_con_list_dec_cmd);
+	install_element(OM2K_NODE, &om2k_con_list_tei_cmd);
+
+	install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd);
+
+	return 0;
+}
diff --git a/src/libbsc/abis_rsl.c b/src/libbsc/abis_rsl.c
new file mode 100644
index 0000000..9a4dc5b
--- /dev/null
+++ b/src/libbsc/abis_rsl.c
@@ -0,0 +1,1971 @@
+/* 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>
+ *
+ * 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 <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/debug.h>
+#include <osmocore/tlv.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/rtp_proxy.h>
+#include <osmocore/rsl.h>
+
+#include <osmocore/talloc.h>
+
+#define RSL_ALLOC_SIZE		1024
+#define RSL_ALLOC_HEADROOM	128
+
+#define MAX(a, b) (a) >= (b) ? (a) : (b)
+
+static int rsl_send_imm_assignment(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;
+	dispatch_signal(SS_LCHAN, sig_no, &sig);
+}
+
+static u_int8_t mdisc_by_msgtype(u_int8_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,
+				  u_int8_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;
+}
+
+/* determine logical channel based on TRX and channel number IE */
+struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, u_int8_t chan_nr)
+{
+	struct gsm_lchan *lchan;
+	u_int8_t ts_nr = chan_nr & 0x07;
+	u_int8_t cbits = chan_nr >> 3;
+	u_int8_t lch_idx;
+	struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+
+	if (cbits == 0x01) {
+		lch_idx = 0;	/* TCH/F */	
+		if (ts->pchan != GSM_PCHAN_TCH_F &&
+		    ts->pchan != GSM_PCHAN_PDCH &&
+		    ts->pchan != GSM_PCHAN_TCH_F_PDCH)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if ((cbits & 0x1e) == 0x02) {
+		lch_idx = cbits & 0x1;	/* TCH/H */
+		if (ts->pchan != GSM_PCHAN_TCH_H)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if ((cbits & 0x1c) == 0x04) {
+		lch_idx = cbits & 0x3;	/* SDCCH/4 */
+		if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if ((cbits & 0x18) == 0x08) {
+		lch_idx = cbits & 0x7;	/* SDCCH/8 */
+		if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+	} else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) {
+		lch_idx = 0;
+		if (ts->pchan != GSM_PCHAN_CCCH &&
+		    ts->pchan != GSM_PCHAN_CCCH_SDCCH4)
+			LOGP(DRSL, LOGL_ERROR, "chan_nr=0x%02x but pchan=%u\n",
+				chan_nr, ts->pchan);
+		/* FIXME: we should not return first sdcch4 !!! */
+	} else {
+		LOGP(DRSL, LOGL_ERROR, "unknown chan_nr=0x%02x\n", chan_nr);
+		return NULL;
+	}
+
+	lchan = &ts->lchan[lch_idx];
+	log_set_context(BSC_CTX_LCHAN, lchan);
+	if (lchan->conn)
+		log_set_context(BSC_CTX_SUBSCR, lchan->conn->subscr);
+
+	return lchan;
+}
+
+/* See Table 10.5.25 of GSM04.08 */
+static u_int8_t ts2chan_nr(const struct gsm_bts_trx_ts *ts, uint8_t lchan_nr)
+{
+	u_int8_t cbits, chan_nr;
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_PDCH:
+	case GSM_PCHAN_TCH_F_PDCH:
+		cbits = 0x01;
+		break;
+	case GSM_PCHAN_TCH_H:
+		cbits = 0x02;
+		cbits += lchan_nr;
+		break;
+	case GSM_PCHAN_CCCH_SDCCH4:
+		cbits = 0x04;
+		cbits += lchan_nr;
+		break;
+	case GSM_PCHAN_SDCCH8_SACCH8C:
+		cbits = 0x08;
+		cbits += lchan_nr;
+		break;
+	default:
+	case GSM_PCHAN_CCCH:
+		cbits = 0x10;
+		break;
+	}
+
+	chan_nr = (cbits << 3) | (ts->nr & 0x7);
+
+	return chan_nr;
+}
+
+u_int8_t lchan2chan_nr(const struct gsm_lchan *lchan)
+{
+	return ts2chan_nr(lchan->ts, lchan->nr);
+}
+
+/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */
+u_int64_t str_to_imsi(const char *imsi_str)
+{
+	u_int64_t ret;
+
+	ret = strtoull(imsi_str, NULL, 10);
+
+	return ret;
+}
+
+/* Table 5 Clause 7 TS 05.02 */
+unsigned int n_pag_blocks(int bs_ccch_sdcch_comb, unsigned int bs_ag_blks_res)
+{
+	if (!bs_ccch_sdcch_comb)
+		return 9 - bs_ag_blks_res;
+	else
+		return 3 - bs_ag_blks_res;
+}
+
+/* Chapter 6.5.2 of TS 05.02 */
+unsigned int get_ccch_group(u_int64_t imsi, unsigned int bs_cc_chans,
+			    unsigned int n_pag_blocks)
+{
+	return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) / n_pag_blocks;
+}
+
+/* Chapter 6.5.2 of TS 05.02 */
+unsigned int get_paging_group(u_int64_t imsi, unsigned int bs_cc_chans,
+			      int n_pag_blocks)
+{
+	return (imsi % 1000) % (bs_cc_chans * n_pag_blocks) % n_pag_blocks;
+}
+
+static struct msgb *rsl_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(RSL_ALLOC_SIZE, RSL_ALLOC_HEADROOM,
+				   "RSL");
+}
+
+#define MACBLOCK_SIZE	23
+static void pad_macblock(u_int8_t *out, const u_int8_t *in, int len)
+{
+	memcpy(out, in, len);
+
+	if (len < MACBLOCK_SIZE)
+		memset(out+len, 0x2b, MACBLOCK_SIZE-len);
+}
+
+/* Chapter 9.3.7: Encryption Information */
+static int build_encr_info(u_int8_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 u_int8_t *cause_v, u_int8_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]);
+}
+
+/* Send a BCCH_INFO message as per Chapter 8.5.1 */
+int rsl_bcch_info(struct gsm_bts_trx *trx, u_int8_t type,
+		  const u_int8_t *data, int len)
+{
+	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_BCCH_INFO);
+	dh->chan_nr = RSL_CHAN_BCCH;
+
+	msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type);
+	msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data);
+
+	msg->trx = trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_sacch_filling(struct gsm_bts_trx *trx, u_int8_t type,
+		      const u_int8_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->trx = trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_sacch_info_modify(struct gsm_lchan *lchan, u_int8_t type,
+			  const u_int8_t *data, int len)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg = rsl_msgb_alloc();
+	u_int8_t chan_nr = 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->trx = lchan->ts->trx;
+
+	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;
+	u_int8_t chan_nr = 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->trx = lchan->ts->trx;
+
+	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;
+	u_int8_t chan_nr = 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->trx = lchan->ts->trx;
+
+	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 ? */
+	if (lchan->ts->trx->bts->network->dtx_enabled)
+		cm->dtx_dtu = 0x03;
+	else
+		cm->dtx_dtu = 0x00;
+
+	/* 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:
+		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:
+		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:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* Chapter 8.4.1 */
+#if 0
+int rsl_chan_activate(struct gsm_bts_trx *trx, u_int8_t chan_nr,
+		      u_int8_t act_type,
+		      struct rsl_ie_chan_mode *chan_mode,
+		      struct rsl_ie_chan_ident *chan_ident,
+		      u_int8_t bs_power, u_int8_t ms_power,
+		      u_int8_t ta)
+{
+	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_CHAN_ACTIV);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type);
+	/* For compatibility with Phase 1 */
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(*chan_mode),
+		     (u_int8_t *) chan_mode);
+	msgb_tlv_put(msg, RSL_IE_CHAN_IDENT, 4,
+		     (u_int8_t *) chan_ident);
+#if 0
+	msgb_tlv_put(msg, RSL_IE_ENCR_INFO, 1,
+		     (u_int8_t *) &encr_info);
+#endif
+	msgb_tv_put(msg, RSL_IE_BS_POWER, bs_power);
+	msgb_tv_put(msg, RSL_IE_MS_POWER, ms_power);
+	msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta);
+
+	msg->trx = trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+#endif
+
+int rsl_chan_activate_lchan(struct gsm_lchan *lchan, u_int8_t act_type,
+			    u_int8_t ta, u_int8_t ho_ref)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+	uint8_t *len;
+
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	struct rsl_ie_chan_mode cm;
+	struct gsm48_chan_desc cd;
+
+	rc = channel_mode_from_lchan(&cm, lchan);
+	if (rc < 0)
+		return rc;
+
+	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);
+	dh->chan_nr = chan_nr;
+
+	msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type);
+	msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm),
+		     (u_int8_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_tlv_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)) {
+		u_int8_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);
+
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+		msgb_tlv_put(msg, RSL_IE_MR_CONFIG, sizeof(lchan->mr_conf),
+			     (u_int8_t *) &lchan->mr_conf);
+
+	msg->trx = lchan->ts->trx;
+
+	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;
+
+	u_int8_t chan_nr = 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),
+		     (u_int8_t *) &cm);
+
+	if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		u_int8_t encr_info[MAX_A5_KEY_LEN+2];
+		rc = build_encr_info(encr_info, lchan);
+		if (rc > 0)
+			msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info);
+	}
+
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+		msgb_tlv_put(msg, RSL_IE_MR_CONFIG, sizeof(lchan->mr_conf),
+			     (u_int8_t *) &lchan->mr_conf);
+	}
+
+	msg->trx = lchan->ts->trx;
+
+	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;
+	u_int8_t chan_nr = lchan2chan_nr(lchan);
+	u_int8_t encr_info[MAX_A5_KEY_LEN+2];
+	u_int8_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->trx = lchan->ts->trx;
+
+	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 = lchan2chan_nr(lchan);
+
+	msg->lchan = lchan;
+	msg->trx = lchan->ts->trx;
+
+	DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan));
+
+	return abis_rsl_sendmsg(msg);
+}
+
+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_NOTICE, "%s is back in operation.\n", gsm_lchan_name(lchan));
+	rsl_lchan_set_state(lchan, LCHAN_S_NONE);
+}
+
+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)
+{
+	struct abis_rsl_dchan_hdr *dh;
+	struct msgb *msg;
+	int rc;
+
+	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 = lchan2chan_nr(lchan);
+
+	msg->lchan = lchan;
+	msg->trx = lchan->ts->trx;
+
+	DEBUGP(DRSL, "%s RF Channel Release CMD due error %d\n", gsm_lchan_name(lchan), error);
+
+	if (error) {
+		/*
+		 * the nanoBTS sends RLL release indications after the channel release. This can
+		 * be a problem when we have reassigned the channel to someone else and then can
+		 * not figure out who used this channel.
+		 */
+		rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR);
+		lchan->error_timer.data = lchan;
+		lchan->error_timer.cb = error_timeout_cb;
+		bsc_schedule_timer(&lchan->error_timer,
+				   msg->trx->bts->network->T3111 + 2, 0);
+	}
+
+	rc =  abis_rsl_sendmsg(msg);
+
+	/* BTS will respond by RF CHAN REL ACK */
+#ifdef HSL_SR_1_0
+	/* The HSL Femto seems to 'forget' sending a REL ACK for TS1...TS7 */
+	if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO && lchan->ts->nr != 0)
+		rc = rsl_rx_rf_chan_rel_ack(lchan);
+#endif
+
+	return rc;
+}
+
+static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan)
+{
+
+	DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", gsm_lchan_name(lchan));
+
+	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));
+	bsc_del_timer(&lchan->T3111);
+	/* we have an error timer pending to release that */
+	if (lchan->state != LCHAN_S_REL_ERR)
+		rsl_lchan_set_state(lchan, LCHAN_S_NONE);
+	lchan_free(lchan);
+
+	return 0;
+}
+
+int rsl_paging_cmd(struct gsm_bts *bts, u_int8_t paging_group, u_int8_t len,
+		   u_int8_t *ms_ident, u_int8_t chan_needed)
+{
+	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);
+
+	msg->trx = bts->c0;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int imsi_str2bcd(u_int8_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 */
+int rsl_imm_assign_cmd(struct gsm_bts *bts, u_int8_t len, u_int8_t *val)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	u_int8_t buf[MACBLOCK_SIZE];
+
+	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, MACBLOCK_SIZE, buf);
+		break;
+	}
+
+	msg->trx = bts->c0;
+
+	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 = lchan2chan_nr(lchan);
+	msgb_tv_put(msg, RSL_IE_SIEMENS_MRPCI, *(u_int8_t *)mrpci);
+
+	DEBUGP(DRSL, "%s TX Siemens MRPCI 0x%02x\n",
+		gsm_lchan_name(lchan), *(u_int8_t *)mrpci);
+
+	msg->trx = lchan->ts->trx;
+
+	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, u_int8_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, lchan2chan_nr(msg->lchan),
+			link_id, 1);
+
+	msg->trx = msg->lchan->ts->trx;
+
+	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, u_int8_t link_id)
+{
+	struct msgb *msg;
+
+	msg = rsl_rll_simple(RSL_MT_EST_REQ, lchan2chan_nr(lchan),
+			     link_id, 0);
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+/* 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, u_int8_t link_id, u_int8_t reason)
+{
+
+	struct msgb *msg;
+
+	msg = rsl_rll_simple(RSL_MT_REL_REQ, lchan2chan_nr(lchan),
+			     link_id, 0);
+	/* 0 is normal release, 1 is local end */
+	msgb_tv_put(msg, RSL_IE_RELEASE_MODE, reason);
+
+	/* FIXME: start some timer in case we don't receive a REL ACK ? */
+
+	msg->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_lchan_set_state(struct gsm_lchan *lchan, int 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);
+
+	/* 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;
+
+	if (msg->lchan->state != LCHAN_S_ACT_REQ)
+		LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n",
+			gsm_lchan_name(msg->lchan),
+			gsm_lchans_name(msg->lchan->state));
+	rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE);
+
+	if (msg->lchan->rqd_ref) {
+		rsl_send_imm_assignment(msg->lchan);
+		talloc_free(msg->lchan->rqd_ref);
+		msg->lchan->rqd_ref = NULL;
+		msg->lchan->rqd_ta = 0;
+	}
+
+	send_lchan_signal(S_LCHAN_ACTIVATE_ACK, msg->lchan, NULL);
+
+	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;
+
+	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 u_int8_t *cause = TLVP_VAL(&tp, RSL_IE_CAUSE);
+		print_rsl_cause(LOGL_ERROR, cause,
+				TLVP_LEN(&tp, RSL_IE_CAUSE));
+		if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC)
+			rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
+	} else
+		rsl_lchan_set_state(msg->lchan, LCHAN_S_NONE);
+
+	LOGPC(DRSL, LOGL_ERROR, "\n");
+
+	send_lchan_signal(S_LCHAN_ACTIVATE_NACK, msg->lchan, NULL);
+
+	lchan_free(msg->lchan);
+	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;
+
+	/* FIXME: print which channel */
+	LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL: RELEASING ",
+	     gsm_lchan_name(msg->lchan));
+
+	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");
+	/* FIXME: only free it after channel release ACK */
+	counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rf_fail);
+	return rsl_rf_chan_release(msg->lchan, 1);
+}
+
+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_meas_rep *mr)
+{
+	int i;
+
+	DEBUGP(DMEAS, "MEASUREMENT RESULT NR=%d ", 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);
+	u_int8_t len;
+	const u_int8_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))
+		mr->ms_timing_offset =
+			*TLVP_VAL(&tp, RSL_IE_MS_TIMING_OFFSET);
+
+	if (TLVP_PRESENT(&tp, RSL_IE_L1_INFO)) {
+		val = TLVP_VAL(&tp, RSL_IE_L1_INFO);
+		mr->flags |= MEAS_REP_F_MS_L1;
+		mr->ms_l1.pwr = ms_pwr_dbm(msg->trx->bts->band, val[0] >> 3);
+		if (val[0] & 0x04)
+			mr->flags |= MEAS_REP_F_FPC;
+		mr->ms_l1.ta = val[1];
+	}
+	if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+		msg->l3h = (u_int8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO);
+		rc = gsm48_parse_meas_rep(mr, msg);
+		if (rc < 0)
+			return rc;
+	}
+
+	print_meas_rep(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 int abis_rsl_rx_dchan(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+	char *ts_name;
+
+	msg->lchan = lchan_lookup(msg->trx, rslh->chan_nr);
+	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);
+		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:
+		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:
+		DEBUGPC(DRSL, "%s IPAC PDCH ACT ACK\n", ts_name);
+		msg->lchan->ts->flags |= TS_F_PDCH_MODE;
+		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);
+		msg->lchan->ts->flags &= ~TS_F_PDCH_MODE;
+		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;
+
+	LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT ", gsm_trx_name(msg->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);
+	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(msg->trx));
+		break;
+	case RSL_MT_OVERLOAD:
+		/* indicate CCCH / ACCH / processor overload */
+		LOGP(DRSL, LOGL_ERROR, "%s CCCH/ACCH/CPU Overload\n",
+		     gsm_trx_name(msg->trx));
+		break;
+	default:
+		LOGP(DRSL, LOGL_NOTICE, "%s Unknown Abis RSL TRX message "
+			"type 0x%02x\n", gsm_trx_name(msg->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;
+
+	rsl_rf_chan_release(lchan, 1);
+}
+
+/* If T3111 expires, we will send the RF Channel Request */
+static void t3111_expired(void *data)
+{
+	struct gsm_lchan *lchan = data;
+
+	rsl_rf_chan_release(lchan, 0);
+}
+
+#define GSM48_LEN2PLEN(a)	(((a) << 2) | 1)
+
+/* 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;
+	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;
+
+	return rsl_imm_assign_cmd(bts, sizeof(iar), (uint8_t *) iar);
+}
+
+/* MS has requested a channel on the RACH */
+static int rsl_rx_chan_rqd(struct msgb *msg)
+{
+	struct gsm_bts *bts = msg->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;
+	u_int8_t rqd_ta;
+	int is_lu;
+
+	u_int16_t arfcn;
+	u_int8_t ts_number, 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 type (SDCCH/TCH_F/TCH_H) based on
+	 * request reference RA */
+	lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra);
+	chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci);
+
+	counter_inc(bts->network->stats.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);
+		counter_inc(bts->network->stats.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 -ENOMEM;
+	}
+
+	if (lchan->state != LCHAN_S_NONE)
+		LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel "
+		     "in state %s\n", gsm_lchan_name(lchan),
+		     gsm_lchans_name(lchan->state));
+	rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+	/* 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;
+
+	ts_number = lchan->ts->nr;
+	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;
+
+	/* FIXME: Start another timer or assume the BTS sends a ACK/NACK? */
+	rsl_chan_activate_lchan(lchan, 0x00, rqd_ta, 0);
+
+	DEBUGP(DRSL, "%s Activating ARFCN(%u) SS(%u) lctype %s "
+		"r=%s ra=0x%02x\n", gsm_lchan_name(lchan), arfcn, subch,
+		gsm_lchant_name(lchan->type), gsm_chreq_name(chreq_reason),
+		rqd_ref->ra);
+	return 0;
+}
+
+static int rsl_send_imm_assignment(struct gsm_lchan *lchan)
+{
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	u_int8_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 */
+	lchan->T3101.cb = t3101_expired;
+	lchan->T3101.data = lchan;
+	bsc_schedule_timer(&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, (u_int8_t *) ia);
+}
+
+/* MS has requested a channel on the RACH */
+static int rsl_rx_ccch_load(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	u_int16_t pg_buf_space;
+	u_int16_t rach_slot_count = -1;
+	u_int16_t rach_busy_count = -1;
+	u_int16_t rach_access_count = -1;
+
+	switch (rslh->data[0]) {
+	case RSL_IE_PAGING_LOAD:
+		pg_buf_space = rslh->data[1] << 8 | rslh->data[2];
+		if (is_ipaccess_bts(msg->trx->bts) && pg_buf_space == 0xffff) {
+			/* paging load below configured threshold, use 50 as default */
+			pg_buf_space = 50;
+		}
+		paging_update_buffer_space(msg->trx->bts, pg_buf_space);
+		break;
+	case RSL_IE_RACH_LOAD:
+		if (msg->data_len >= 7) {
+			rach_slot_count = rslh->data[2] << 8 | rslh->data[3];
+			rach_busy_count = rslh->data[4] << 8 | rslh->data[5];
+			rach_access_count = rslh->data[6] << 8 | rslh->data[7];
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int abis_rsl_rx_cchan(struct msgb *msg)
+{
+	struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg);
+	int rc = 0;
+
+	msg->lchan = lchan_lookup(msg->trx, rslh->chan_nr);
+
+	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;
+	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 abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	u_int8_t *rlm_cause = rllh->data;
+
+	LOGP(DRLL, LOGL_ERROR, "%s ERROR INDICATION cause=%s\n",
+		gsm_lchan_name(msg->lchan),
+		rsl_rlm_cause_name(rlm_cause[1]));
+
+	rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND);
+
+	if (rlm_cause[1] == RLL_CAUSE_T200_EXPIRED) {
+		counter_inc(msg->lchan->ts->trx->bts->network->stats.chan.rll_err);
+		return rsl_rf_chan_release(msg->lchan, 1);
+	}
+
+	return 0;
+}
+
+static void rsl_handle_release(struct gsm_lchan *lchan)
+{
+	int sapi;
+	struct gsm_bts *bts;
+
+	/* maybe we have only brought down one RLL */
+	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;
+	}
+
+
+
+	/* wait a bit to send the RF Channel Release */
+	lchan->T3111.cb = t3111_expired;
+	lchan->T3111.data = lchan;
+	bts = lchan->ts->trx->bts;
+	bsc_schedule_timer(&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 abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	int rc = 0;
+	char *ts_name;
+	u_int8_t sapi = rllh->link_id & 7;
+
+	msg->lchan = lchan_lookup(msg->trx, rllh->chan_nr);
+	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;
+		bsc_del_timer(&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);
+		rsl_lchan_rll_release(msg->lchan, rllh->link_id);
+		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);
+		rsl_lchan_rll_release(msg->lchan, rllh->link_id);
+		break;
+	case RSL_MT_ERROR_IND:
+		rc = rsl_rx_rll_err_ind(msg);
+		break;
+	case RSL_MT_UNIT_DATA_IND:
+		LOGP(DRLL, LOGL_NOTICE, "unimplemented Abis RLL message "
+			"type 0x%02x\n", rllh->c.msg_type);
+		break;
+	default:
+		LOGP(DRLL, LOGL_NOTICE, "unknown Abis RLL message "
+			"type 0x%02x\n", rllh->c.msg_type);
+	}
+	return rc;
+}
+
+static u_int8_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;
+		}
+	case GSM48_CMODE_SPEECH_EFR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return 0x01;
+		/* there's no half-rate EFR */
+		default:
+			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;
+		}
+	default:
+		break;
+	}
+	LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access speech mode for "
+		"tch_mode == 0x%02x\n", lchan->tch_mode);
+	return 0;
+}
+
+static u_int8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan)
+{
+	struct gsm_network *net = lchan->ts->trx->bts->network;
+
+	/* allow to hardcode the rtp payload */
+	if (net->hardcoded_rtp_payload != 0)
+		return net->hardcoded_rtp_payload;
+
+	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;
+		}
+	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;
+		}
+	case GSM48_CMODE_SPEECH_AMR:
+		switch (lchan->type) {
+		case GSM_LCHAN_TCH_F:
+			return RTP_PT_AMR_FULL;
+		case GSM_LCHAN_TCH_H:
+			return RTP_PT_AMR_HALF;
+		default:
+			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;
+	u_int16_t port, conn_id;
+
+	if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_IP)) {
+		ip.s_addr = *((u_int32_t *) TLVP_VAL(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 = *((u_int16_t *) TLVP_VAL(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 = *((u_int16_t *) TLVP_VAL(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 = *((u_int32_t *) TLVP_VAL(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 = *((u_int16_t *) TLVP_VAL(tv, RSL_IE_IPAC_REMOTE_PORT));
+		port = ntohs(port);
+		DEBUGPC(DRSL, "REMOTE_PORT=%u ", port);
+		lchan->abis_ip.connect_port = port;
+	}
+}
+
+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 = 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->trx = lchan->ts->trx;
+
+	return abis_rsl_sendmsg(msg);
+}
+
+int rsl_ipacc_mdcx(struct gsm_lchan *lchan, u_int32_t ip, u_int16_t port,
+		   u_int8_t rtp_payload2)
+{
+	struct msgb *msg = rsl_msgb_alloc();
+	struct abis_rsl_dchan_hdr *dh;
+	u_int32_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 = 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 = (u_int32_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->trx = lchan->ts->trx;
+
+	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;
+	u_int8_t msg_type;
+
+	if (act)
+		msg_type = RSL_MT_IPAC_PDCH_ACT;
+	else
+		msg_type = RSL_MT_IPAC_PDCH_DEACT;
+
+	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 = ts2chan_nr(ts, 0);
+
+	DEBUGP(DRSL, "%s IPAC_PDCH_%sACT\n", gsm_ts_name(ts),
+		act ? "" : "DE");
+
+	msg->trx = ts->trx;
+
+	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);
+
+	dispatch_signal(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);
+	dispatch_signal(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));
+
+	dispatch_signal(SS_ABISIP, S_ABISIP_DLCX_IND, msg->lchan);
+
+	return 0;
+}
+
+static int abis_rsl_rx_ipacc(struct msgb *msg)
+{
+	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg);
+	char *ts_name;
+	int rc = 0;
+
+	msg->lchan = lchan_lookup(msg->trx, rllh->chan_nr);
+	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;
+}
+
+
+/* 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));
+		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);
+		return -EINVAL;
+	}
+	msgb_free(msg);
+	return rc;
+}
+
+/* From Table 10.5.33 of GSM 04.08 */
+int rsl_number_of_paging_subchannels(struct gsm_bts *bts)
+{
+	if (bts->si_common.chan_desc.ccch_conf == RSL_BCCH_CCCH_CONF_1_C) {
+		return MAX(1, (3 - bts->si_common.chan_desc.bs_ag_blks_res))
+			* (bts->si_common.chan_desc.bs_pa_mfrms + 2);
+	} else {
+		return (9 - bts->si_common.chan_desc.bs_ag_blks_res)
+			* (bts->si_common.chan_desc.bs_pa_mfrms + 2);
+	}
+}
+
+int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number,
+		       uint8_t 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->chan_nr = RSL_CHAN_SDCCH4_ACCH; /* TODO: check the chan config */
+
+	msgb_tv_put(cb_cmd, RSL_IE_CB_CMD_TYPE, cb_command);
+	msgb_tlv_put(cb_cmd, RSL_IE_SMSCB_MSG, len, data);
+
+	cb_cmd->trx = bts->c0;
+
+	return abis_rsl_sendmsg(cb_cmd);
+}
diff --git a/src/libbsc/bsc_api.c b/src/libbsc/bsc_api.c
new file mode 100644
index 0000000..0f09aec
--- /dev/null
+++ b/src/libbsc/bsc_api.c
@@ -0,0 +1,675 @@
+/* GSM 08.08 like API for OpenBSC. The bridge from MSC to BSC */
+
+/* (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 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 <osmocore/protocol/gsm_08_08.h>
+
+#include <osmocore/talloc.h>
+
+#define GSM0808_T10_VALUE    6, 0
+
+static LLIST_HEAD(sub_connections);
+
+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 u_int8_t lchan_to_chosen_channel(struct gsm_lchan *lchan)
+{
+	u_int8_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:
+		LOGP(DMSC, LOGL_ERROR, "Unknown lchan type: %p\n", lchan);
+		break;
+	}
+
+	return channel_mode << 4 | channel;
+}
+
+static u_int8_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, "Assigment T10 timeout on %p\n", conn);
+
+	/* normal release on the secondary channel */
+	lchan_release(conn->secondary_lchan, 0, 1);
+	conn->secondary_lchan = NULL;
+
+	/* inform them about the failure */
+	api = conn->bts->network->bsc_api;
+	api->assign_fail(conn, GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL);
+}
+
+/*
+ * 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;
+
+	/* 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) {
+		new_lchan->mr_conf.ver = 1;
+		new_lchan->mr_conf.icmi = 1;
+		new_lchan->mr_conf.m5_90 = 1;
+	}
+
+	if (rsl_chan_activate_lchan(new_lchan, 0x1, 0, 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 *subscr_con_allocate(struct gsm_lchan *lchan)
+{
+	struct gsm_subscriber_connection *conn;
+
+	conn = talloc_zero(lchan->ts->trx->bts->network, struct gsm_subscriber_connection);
+	if (!conn)
+		return NULL;
+
+	/* Configure the time and start it so it will be closed */
+	conn->lchan = lchan;
+	conn->bts = lchan->ts->trx->bts;
+	lchan->conn = conn;
+	llist_add_tail(&conn->entry, &sub_connections);
+	return conn;
+}
+
+/* TODO: move subscriber put here... */
+void 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;
+}
+
+int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn,
+			struct msgb *msg, int link_id, int allow_sach)
+{
+	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->trx = msg->lchan->ts->trx;
+
+	/* If we are on a TCH and need to submit a SMS (on SAPI=3) we need to use the SACH */
+	if (allow_sach && sapi != 0) {
+		if (conn->lchan->type == GSM_LCHAN_TCH_F || conn->lchan->type == GSM_LCHAN_TCH_H)
+			link_id |= 0x40;
+	}
+
+	msg->l3h = msg->data;
+	if (conn->lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) {
+		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 {
+		return rsl_data_request(msg, link_id);
+	}
+}
+
+/**
+ * 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 and that when we have a TCH it is capable of
+ * handling the audio codec. On top of that it is assumed that we are using
+ * AMR 5.9 when assigning a TCH/H.
+ */
+int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate)
+{
+	struct bsc_api *api;
+	api = conn->bts->network->bsc_api;
+
+	if (conn->lchan->type == GSM_LCHAN_SDCCH) {
+		if (handle_new_assignment(conn, chan_mode, full_rate) != 0)
+			goto error;
+	} else {
+		LOGP(DMSC, LOGL_NOTICE,
+			"Sending ChanModify for speech %d %d\n", chan_mode, full_rate);
+		if (chan_mode == GSM48_CMODE_SPEECH_AMR) {
+			conn->lchan->mr_conf.ver = 1;
+			conn->lchan->mr_conf.icmi = 1;
+			conn->lchan->mr_conf.m5_90 = 1;
+		}
+
+		gsm48_lchan_modify(conn->lchan, chan_mode);
+	}
+
+	/* we will now start the timer to complete the assignment */
+	conn->T10.cb = assignment_t10_timeout;
+	conn->T10.data = conn;
+	bsc_schedule_timer(&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);
+}
+
+static void handle_ass_compl(struct gsm_subscriber_connection *conn,
+			     struct msgb *msg)
+{
+	struct gsm48_hdr *gh;
+	struct bsc_api *api = conn->bts->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: %lu\n",
+		     msgb_l3len(msg) - sizeof(*gh));
+		return;
+	}
+
+	/* swap channels */
+	bsc_del_timer(&conn->T10);
+
+	lchan_release(conn->lchan, 0, 1);
+	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->bts->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 */
+	bsc_del_timer(&conn->T10);
+	lchan_release(conn->secondary_lchan, 0, 1);
+	conn->secondary_lchan = NULL;
+
+	gh = msgb_l3(msg);
+	if (msgb_l3len(msg) - sizeof(*gh) != 1) {
+		LOGP(DMSC, LOGL_ERROR, "assignemnt failure unhandled: %lu\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 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;
+	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 = gh->proto_discr & 0x0f;
+	switch (pdisc) {
+	case GSM48_PDISC_RR:
+		switch (gh->msg_type) {
+		case GSM48_MT_RR_CIPH_M_COMPL:
+			if (api->cipher_mode_compl)
+				return 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:
+			bsc_del_timer(&conn->T10);
+			rc = gsm48_rx_rr_modif_ack(msg);
+			if (rc < 0 && api->assign_fail) {
+				api->assign_fail(conn,
+						 GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE,
+						 NULL);
+			} else if (rc >= 0 && api->assign_compl)
+				api->assign_compl(conn, 0,
+						  lchan_to_chosen_channel(conn->lchan),
+						  conn->lchan->encr.alg_id,
+						  chan_mode_to_speech(conn->lchan));
+			return;
+			break;
+		}
+		break;
+	case GSM48_PDISC_MM:
+		break;
+	}
+
+	/* default case */
+	if (api->dtap)
+		api->dtap(conn, link_id, msg);
+}
+
+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) {
+		dispatch_dtap(lchan->conn, link_id, msg);
+	} else {
+		rc = BSC_API_CONN_POL_REJECT;
+		lchan->conn = subscr_con_allocate(msg->lchan);
+		if (!lchan->conn) {
+			lchan_release(lchan, 0, 0);
+			return -1;
+		}
+
+		rc = api->compl_l3(lchan->conn, msg, 0);
+
+		if (rc != BSC_API_CONN_POL_ACCEPT) {
+			lchan->conn->lchan = NULL;
+			subscr_con_free(lchan->conn);
+			lchan_release(lchan, 0, 0);
+		}
+	}
+
+	return 0;
+}
+
+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, 1);
+
+	if (conn->lchan)
+		lchan_release(conn->lchan, 1, 0);
+
+	conn->lchan = NULL;
+	conn->secondary_lchan = NULL;
+	conn->ho_lchan = NULL;
+	conn->bts = NULL;
+
+	bsc_del_timer(&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->bts->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) {
+		bsc_del_timer(&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)
+		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, 0x3);
+}
+
+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)
+{
+	register_signal_handler(SS_LCHAN, bsc_handle_lchan_signal, NULL);
+}
diff --git a/src/libbsc/bsc_init.c b/src/libbsc/bsc_init.c
new file mode 100644
index 0000000..0072bb6
--- /dev/null
+++ b/src/libbsc/bsc_init.c
@@ -0,0 +1,466 @@
+/* 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 <osmocore/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 <openbsc/system_information.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/chan_alloc.h>
+#include <osmocore/talloc.h>
+#include <openbsc/ipaccess.h>
+
+/* global pointer to the gsm network data structure */
+extern struct gsm_network *bsc_gsmnet;
+
+static void patch_nm_tables(struct gsm_bts *bts);
+
+/* Callback function for NACK on the OML NM */
+static int oml_msg_nack(struct nm_nack_signal_data *nack)
+{
+	int i;
+
+	if (nack->mt == NM_MT_SET_BTS_ATTR_NACK) {
+
+		LOGP(DNM, LOGL_FATAL, "Failed to set BTS attributes. That is fatal. "
+				"Was the bts type and frequency properly specified?\n");
+		exit(-1);
+	} else {
+		LOGP(DNM, LOGL_ERROR, "Got a NACK going to drop the OML links.\n");
+		for (i = 0; i < bsc_gsmnet->num_bts; ++i) {
+			struct gsm_bts *bts = gsm_bts_num(bsc_gsmnet, i);
+			if (is_ipaccess_bts(bts))
+				ipaccess_drop_oml(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);
+		dispatch_signal(SS_GLOBAL, S_GLOBAL_BTS_CLOSE_OM, bts);
+	}
+
+	return 0;
+}
+
+static int generate_and_rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i)
+{
+	struct gsm_bts *bts = trx->bts;
+	int si_len, rc, j;
+
+	/* Only generate SI if this SI is not in "static" (user-defined) mode */
+	if (!(bts->si_mode_static & (1 << i))) {
+		rc = gsm_generate_si(bts, i);
+		if (rc < 0)
+			return rc;
+		si_len = rc;
+	}
+
+	DEBUGP(DRR, "SI%s: %s\n", gsm_sitype_name(i),
+		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:
+		if (trx->bts->type == GSM_BTS_TYPE_HSL_FEMTO) {
+			/* HSL has mistaken SACCH INFO MODIFY for SACCH FILLING,
+			 * so we need a special workaround here */
+			/* This assumes a combined BCCH and TCH on TS1...7 */
+			for (j = 0; j < 4; j++)
+				rsl_sacch_info_modify(&trx->ts[0].lchan[j],
+						      gsm_sitype2rsl(i),
+						      GSM_BTS_SI(bts, i), si_len);
+			for (j = 1; j < 8; j++) {
+				rsl_sacch_info_modify(&trx->ts[j].lchan[0],
+						      gsm_sitype2rsl(i),
+						      GSM_BTS_SI(bts, i), si_len);
+				rsl_sacch_info_modify(&trx->ts[j].lchan[1],
+						      gsm_sitype2rsl(i),
+						      GSM_BTS_SI(bts, i), si_len);
+			}
+		} else
+			rc = rsl_sacch_filling(trx, gsm_sitype2rsl(i),
+					       GSM_BTS_SI(bts, i), rc);
+		break;
+	default:
+		rc = rsl_bcch_info(trx, gsm_sitype2rsl(i),
+				   GSM_BTS_SI(bts, i), rc);
+		break;
+	}
+
+	return rc;
+}
+
+/* set all system information types */
+static int set_system_infos(struct gsm_bts_trx *trx)
+{
+	int i, rc;
+	struct gsm_bts *bts = trx->bts;
+
+	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;
+
+	/* First, we determine which of the SI messages we actually need */
+
+	if (trx == bts->c0) {
+		/* 1...4 are always present on a C0 TRX */
+		for (i = SYSINFO_TYPE_1; i <= SYSINFO_TYPE_4; i++)
+			bts->si_valid |= (1 << i);
+
+		/* 13 is always present on a C0 TRX of a GPRS BTS */
+		if (bts->gprs.mode != BTS_GPRS_NONE)
+			bts->si_valid |= (1 << SYSINFO_TYPE_13);
+	}
+
+	/* 5 and 6 are always present on every TRX */
+	bts->si_valid |= (1 << SYSINFO_TYPE_5);
+	bts->si_valid |= (1 << SYSINFO_TYPE_6);
+
+	/* Second, we generate and send the selected SI via RSL */
+	for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) {
+		if (!(bts->si_valid & (1 << i)))
+			continue;
+
+		rc = generate_and_rsl_si(trx, i);
+		if (rc < 0)
+			goto err_out;
+	}
+
+	return 0;
+err_out:
+	LOGP(DRR, LOGL_ERROR, "Cannot generate SI %u for BTS %u, most likely "
+		"a problem with neighbor cell list generation\n",
+		i, bts->nr);
+	return rc;
+}
+
+/* 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 = 1; 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 = 1; i < 1024; i++) {
+		if (!bitvec_get_bit_pos(cell_chan, i))
+			continue;
+		/* set the corresponding bit in the MA */
+		bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan;
+		if (bitvec_get_bit_pos(ts_arfcn, i))
+			bitvec_set_bit_pos(ma, bitnum, 1);
+		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 TSC=%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, trx->bts->tsc);
+	set_system_infos(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;
+
+	if (subsys != SS_INPUT)
+		return -EINVAL;
+
+	switch (signal) {
+	case S_INP_TEI_UP:
+		if (isd->link_type == E1INP_SIGN_RSL)
+			bootstrap_rsl(trx);
+		break;
+	case S_INP_TEI_DN:
+		LOGP(DMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", isd->link_type, trx);
+
+		if (isd->link_type == E1INP_SIGN_OML)
+			counter_inc(trx->bts->network->stats.bts.oml_fail);
+		else if (isd->link_type == E1INP_SIGN_RSL)
+			counter_inc(trx->bts->network->stats.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]);
+			}
+
+			ts->nm_state.operational = 0;
+			ts->nm_state.availability = 0;
+		}
+
+		trx->nm_state.operational = 0;
+		trx->nm_state.availability = 0;
+		trx->bb_transc.nm_state.operational = 0;
+		trx->bb_transc.nm_state.availability = 0;
+
+		abis_nm_clear_queue(trx->bts);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int bootstrap_bts(struct gsm_bts *bts)
+{
+	int i, n;
+
+	/* 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 < 1 ||
+		   (bts->c0->arfcn > 124 && bts->c0->arfcn < 955) ||
+		    bts->c0->arfcn > 1023)  {
+			LOGP(DNM, LOGL_ERROR, "GSM900 channel must be between 1-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 */
+	bts->si_common.chan_desc.att = 1;
+	bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5;
+	bts->si_common.chan_desc.bs_ag_blks_res = 1;
+
+	/* 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;
+		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;
+	}
+
+	/* some defaults for our system information */
+	bts->si_common.cell_options.radio_link_timeout = 7; /* 12 */
+
+	/* allow/disallow DTXu */
+	if (bts->network->dtx_enabled)
+		bts->si_common.cell_options.dtx = 0;
+	else
+		bts->si_common.cell_options.dtx = 2;
+
+	bts->si_common.cell_options.pwrc = 0; /* PWRC not set */
+
+	bts->si_common.cell_sel_par.acs = 0;
+
+	bts->si_common.ncc_permitted = 0xff;
+
+	paging_init(bts);
+
+	return 0;
+}
+
+int bsc_bootstrap_network(int (*mncc_recv)(struct gsm_network *, struct msgb *),
+			  const char *config_file)
+{
+	struct telnet_connection dummy_conn;
+	struct gsm_bts *bts;
+	int rc;
+
+	/* initialize our data structures */
+	bsc_gsmnet = gsm_network_init(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");
+
+	/* our vty command code expects vty->priv to point to a telnet_connection */
+	dummy_conn.priv = bsc_gsmnet;
+	rc = vty_read_config_file(config_file, &dummy_conn);
+	if (rc < 0) {
+		LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s'\n", config_file);
+		return rc;
+	}
+
+	rc = telnet_init(tall_bsc_ctx, bsc_gsmnet, 4242);
+	if (rc < 0)
+		return rc;
+
+	register_signal_handler(SS_NM, nm_sig_cb, NULL);
+	register_signal_handler(SS_INPUT, inp_sig_cb, NULL);
+
+	llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+		rc = bootstrap_bts(bts);
+
+		switch (bts->type) {
+		case GSM_BTS_TYPE_NANOBTS:
+		case GSM_BTS_TYPE_HSL_FEMTO:
+			break;
+		default:
+			rc = e1_reconfig_bts(bts);
+			break;
+		}
+
+		if (rc < 0) {
+			fprintf(stderr, "Error in E1 input driver setup\n");
+			exit (1);
+		}
+	}
+
+	/* initialize nanoBTS support omce */
+	rc = ipaccess_setup(bsc_gsmnet);
+	rc = hsl_setup(bsc_gsmnet);
+
+	return 0;
+}
diff --git a/src/libbsc/bsc_msc.c b/src/libbsc/bsc_msc.c
new file mode 100644
index 0000000..508697a
--- /dev/null
+++ b/src/libbsc/bsc_msc.c
@@ -0,0 +1,259 @@
+/* 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 <openbsc/ipaccess.h>
+
+#include <osmocore/write_queue.h>
+#include <osmocore/talloc.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 bsc_fd *fd;
+
+	fd = &con->write_queue.bfd;
+
+	close(fd->fd);
+	fd->fd = -1;
+	fd->cb = write_queue_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 Connection timeout.\n");
+	bsc_msc_lost(con);
+}
+
+/* called in the case of a non blocking connect */
+static int msc_connection_connect(struct bsc_fd *fd, unsigned int what)
+{
+	int rc;
+	int val;
+	struct bsc_msc_connection *con;
+	struct write_queue *queue;
+
+	socklen_t len = sizeof(val);
+
+	if ((what & BSC_FD_WRITE) == 0) {
+		LOGP(DMSC, LOGL_ERROR, "Callback but not writable.\n");
+		return -1;
+	}
+
+	queue = container_of(fd, struct write_queue, bfd);
+	con = container_of(queue, struct bsc_msc_connection, write_queue);
+
+	/* From here on we will either be connected or reconnect */
+	bsc_del_timer(&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 socket failed.\n");
+		goto error;
+	}
+	if (val != 0) {
+		LOGP(DMSC, LOGL_ERROR, "Not connected to the MSC: %d\n", val);
+		goto error;
+	}
+
+
+	/* go to full operation */
+	fd->cb = write_queue_bfd_cb;
+	fd->when = BSC_FD_READ | BSC_FD_EXCEPT;
+
+	con->is_connected = 1;
+	LOGP(DMSC, LOGL_NOTICE, "(Re)Connected to the MSC.\n");
+	if (con->connected)
+		con->connected(con);
+	return 0;
+
+error:
+	bsc_unregister_fd(fd);
+	connection_loss(con);
+	return -1;
+}
+static void setnonblocking(struct bsc_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_fd *fd;
+	struct sockaddr_in sin;
+	int on = 1, ret;
+
+	LOGP(DMSC, LOGL_NOTICE, "Attempting to connect MSC at %s:%d\n", con->ip, con->port);
+
+	con->is_connected = 0;
+
+	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,
+			 &con->prio, sizeof(con->prio));
+	if (ret != 0)
+		LOGP(DMSC, LOGL_ERROR, "Failed to set prio to %d. %s\n",
+		     con->prio, strerror(errno));
+
+	memset(&sin, 0, sizeof(sin));
+	sin.sin_family = AF_INET;
+	sin.sin_port = htons(con->port);
+	inet_aton(con->ip, &sin.sin_addr);
+
+	setsockopt(fd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+	ret = connect(fd->fd, (struct sockaddr *) &sin, sizeof(sin));
+
+	if (ret == -1 && errno == EINPROGRESS) {
+		LOGP(DMSC, LOGL_ERROR, "MSC Connection in progress\n");
+		fd->when = BSC_FD_WRITE;
+		fd->cb = msc_connection_connect;
+		con->timeout_timer.cb = msc_con_timeout;
+		con->timeout_timer.data = con;
+		bsc_schedule_timer(&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 = write_queue_bfd_cb;
+		con->is_connected = 1;
+		if (con->connected)
+			con->connected(con);
+	}
+
+	ret = bsc_register_fd(fd);
+	if (ret < 0) {
+		perror("Registering the fd failed");
+		close(fd->fd);
+		return ret;
+	}
+
+	return ret;
+}
+
+struct bsc_msc_connection *bsc_msc_create(const char *ip, int port, int prio)
+{
+	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->ip = ip;
+	con->port = port;
+	con->prio = prio;
+	write_queue_init(&con->write_queue, 100);
+	return con;
+}
+
+void bsc_msc_lost(struct bsc_msc_connection *con)
+{
+	write_queue_clear(&con->write_queue);
+	bsc_del_timer(&con->timeout_timer);
+
+	if (con->write_queue.bfd.fd >= 0)
+		bsc_unregister_fd(&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.\n");
+	bsc_msc_connect(con);
+}
+
+void bsc_msc_schedule_connect(struct bsc_msc_connection *con)
+{
+	LOGP(DMSC, LOGL_NOTICE, "Attempting to reconnect to the MSC.\n");
+	con->reconnect_timer.cb = reconnect_msc;
+	con->reconnect_timer.data = con;
+	bsc_schedule_timer(&con->reconnect_timer, 5, 0);
+}
+
+struct msgb *bsc_msc_id_get_resp(const char *token)
+{
+	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;
+	}
+
+	msg->l2h = msgb_v_put(msg, IPAC_MSGT_ID_RESP);
+	msgb_l16tv_put(msg, strlen(token) + 1,
+			IPAC_IDTAG_UNITNAME, (u_int8_t *) token);
+	return msg;
+}
diff --git a/src/libbsc/bsc_rll.c b/src/libbsc/bsc_rll.c
new file mode 100644
index 0000000..722f3fa
--- /dev/null
+++ b/src/libbsc/bsc_rll.c
@@ -0,0 +1,141 @@
+/* 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 <osmocore/talloc.h>
+#include <osmocore/timer.h>
+#include <osmocore/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 timer_list timer;
+
+	struct gsm_lchan *lchan;
+	u_int8_t link_id;
+
+	void (*cb)(struct gsm_lchan *lchan, u_int8_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, u_int8_t sapi,
+		  void (*cb)(struct gsm_lchan *, u_int8_t, void *,
+			     enum bsc_rllr_ind),
+		  void *data)
+{
+	struct bsc_rll_req *rllr = talloc_zero(tall_bsc_ctx, struct bsc_rll_req);
+	u_int8_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);
+
+	rllr->timer.cb = &timer_cb;
+	rllr->timer.data = rllr;
+
+	bsc_schedule_timer(&rllr->timer, 10, 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, u_int8_t link_id, u_int8_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)) {
+			bsc_del_timer(&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) {
+			bsc_del_timer(&rllr->timer);
+			complete_rllr(rllr, BSC_RLLR_IND_ERR_IND);
+		}
+	}
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_rll(void)
+{
+	register_signal_handler(SS_CHALLOC, rll_lchan_signal, NULL);
+}
diff --git a/src/libbsc/bsc_vty.c b/src/libbsc/bsc_vty.c
new file mode 100644
index 0000000..c0909db
--- /dev/null
+++ b/src/libbsc/bsc_vty.c
@@ -0,0 +1,2799 @@
+/* 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 <unistd.h>
+#include <sys/types.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/telnet_interface.h>
+
+#include <arpa/inet.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_om2000.h>
+#include <osmocore/utils.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/vty.h>
+#include <openbsc/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/osmo_msc_data.h>
+#include <openbsc/osmo_bsc_rf.h>
+
+#include "../../bscconfig.h"
+
+/* 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 }
+};
+
+struct cmd_node net_node = {
+	GSMNET_NODE,
+	"%s(network)#",
+	1,
+};
+
+struct cmd_node bts_node = {
+	BTS_NODE,
+	"%s(bts)#",
+	1,
+};
+
+struct cmd_node trx_node = {
+	TRX_NODE,
+	"%s(trx)#",
+	1,
+};
+
+struct cmd_node ts_node = {
+	TS_NODE,
+	"%s(ts)#",
+	1,
+};
+
+extern struct gsm_network *bsc_gsmnet;
+
+struct gsm_network *gsmnet_from_vty(struct vty *v)
+{
+	/* In case we read from the config file, the vty->priv cannot
+	 * point to a struct telnet_connection, and thus conn->priv
+	 * will not point to the gsm_network structure */
+#if 0
+	struct telnet_connection *conn = v->priv;
+	return (struct gsm_network *) conn->priv;
+#else
+	return bsc_gsmnet;
+#endif
+}
+
+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 %u, Avail '%s'%s",
+		nm_opstate_name(nms->operational), nms->administrative,
+		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%s",
+		gsm_auth_policy_name(net->auth_policy), 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->msc_data)
+		vty_out(vty, "  Last RF Command: %s%s",
+			net->msc_data->rf_ctl->last_state_command,
+			VTY_NEWLINE);
+}
+
+DEFUN(show_net, 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, TSC %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->tsc,
+		bts->num_trx, VTY_NEWLINE);
+	vty_out(vty, "Description: %s%s",
+		bts->description ? bts->description : "(null)", 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);
+	vty_out(vty, "System Information present: 0x%08x, static: 0x%08x%s",
+		bts->si_valid, bts->si_mode_static, 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_HSL_FEMTO)
+		vty_out(vty, "  Serial Number: %lu%s", bts->hsl.serno, VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &bts->nm_state);
+	vty_out(vty, "  Site Mgr NM State: ");
+	net_dump_nmstate(vty, &bts->site_mgr.nm_state);
+	vty_out(vty, "  Paging: FIXME pending requests, %u free slots%s",
+		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 [number]",
+	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->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->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 routing area %u%s", bts->gprs.rac,
+		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);
+	}
+}
+
+static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	int i;
+
+	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);
+	vty_out(vty, "  training_sequence_code %u%s", bts->tsc, VTY_NEWLINE);
+	vty_out(vty, "  base_station_id_code %u%s", bts->bsic, VTY_NEWLINE);
+	vty_out(vty, "  ms max power %u%s", bts->ms_max_power, VTY_NEWLINE);
+	vty_out(vty, "  cell reselection hysteresis %u%s",
+		bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE);
+	vty_out(vty, "  rxlev access min %u%s",
+		bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE);
+
+	if (bts->si_common.cell_ro_sel_par.present) {
+		struct gsm48_si_selection_params *sp;
+		sp = &bts->si_common.cell_ro_sel_par;
+
+		if (sp->cbq)
+			vty_out(vty, "  cell bar qualify %u%s",
+				sp->cbq, VTY_NEWLINE);
+
+		if (sp->cell_resel_off)
+			vty_out(vty, "  cell reselection offset %u%s",
+				sp->cell_resel_off*2, VTY_NEWLINE);
+
+		if (sp->temp_offs == 7)
+			vty_out(vty, "  temporary offset infinite%s",
+				VTY_NEWLINE);
+		else if (sp->temp_offs)
+			vty_out(vty, "  temporary offset %u%s",
+				sp->temp_offs*10, VTY_NEWLINE);
+
+		if (sp->penalty_time == 31)
+			vty_out(vty, "  penalty time reserved%s",
+				VTY_NEWLINE);
+		else if (sp->penalty_time)
+			vty_out(vty, "  penalty time %u%s",
+				(sp->penalty_time*20)+20, VTY_NEWLINE);
+	}
+
+	if (bts->si_common.chan_desc.t3212)
+		vty_out(vty, "  periodic location update %u%s",
+			bts->si_common.chan_desc.t3212 * 6, 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);
+
+	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);
+	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),
+				hexdump_nospc(bts->si_buf[i], sizeof(bts->si_buf[i])),
+				VTY_NEWLINE);
+		}
+	}
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		vty_out(vty, "  ip.access unit_id %u %u%s",
+			bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE);
+		vty_out(vty, "  oml ip.access stream_id %u%s", bts->oml_tei, VTY_NEWLINE);
+		break;
+	case GSM_BTS_TYPE_HSL_FEMTO:
+		vty_out(vty, "  hsl serial-number %lu%s", bts->hsl.serno, VTY_NEWLINE);
+		break;
+	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);
+		}
+	}
+
+	config_write_bts_gprs(vty, bts);
+
+	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 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);
+	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, " dtx-used %u%s", gsmnet->dtx_enabled, VTY_NEWLINE);
+	vty_out(vty, " subscriber-keep-in-ram %d%s",
+		gsmnet->keep_subscr, 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->nm_state);
+	vty_out(vty, "  Baseband Transceiver NM State: ");
+	net_dump_nmstate(vty, &trx->bb_transc.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 [bts_nr] [trx_nr]",
+	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",
+		ts->trx->bts->nr, ts->trx->nr, ts->nr,
+		gsm_pchan_name(ts->pchan));
+	if (ts->pchan == GSM_PCHAN_TCH_F_PDCH)
+		vty_out(vty, " (%s mode)",
+			ts->flags & TS_F_PDCH_MODE ? "PDCH" : "TCH/F");
+	vty_out(vty, "%s", VTY_NEWLINE);
+	vty_out(vty, "  NM State: ");
+	net_dump_nmstate(vty, &ts->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 [bts_nr] [trx_nr] [ts_nr]",
+	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 (subscr->name)
+		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
+	if (subscr->extension)
+		vty_out(vty, "    Extension: %s%s", subscr->extension,
+			VTY_NEWLINE);
+	if (subscr->imsi)
+		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 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: %u%s", prefix,
+			mr->ms_timing_offset, VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_MS_L1)
+		vty_out(vty, "%s  L1 MS Power: %u dBm, Timing Advance: %u%s",
+			prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE);
+	if (mr->flags & MEAS_REP_F_DL_VALID)
+		meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl");
+	meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul");
+}
+
+static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+	int idx;
+
+	vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s",
+		lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE);
+	vty_out(vty, "  Connection: %u, State: %s%s",
+		lchan->conn ? 1: 0,
+		gsm_lchans_name(lchan->state), 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);
+	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, Lchan %u, Type %s - "
+		"L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s",
+		lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		lchan->nr, gsm_lchant_name(lchan->type), mr->ms_l1.pwr,
+		rxlev2dbm(mr->dl.full.rx_lev),
+		rxlev2dbm(mr->ul.full.rx_lev),
+		VTY_NEWLINE);
+}
+
+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 >= 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;
+		}
+		ts = &trx->ts[ts_nr];
+	}
+	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);
+		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];
+				for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN;
+				     lchan_nr++) {
+					lchan = &ts->lchan[lchan_nr];
+					if (lchan->type == GSM_LCHAN_NONE)
+						continue;
+					dump_cb(vty, lchan);
+				}
+			}
+		}
+	}
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(show_lchan,
+      show_lchan_cmd,
+      "show lchan [bts_nr] [trx_nr] [ts_nr] [lchan_nr]",
+	SHOW_STR "Display information about a logical channel\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+	"Logical Channel Number\n")
+
+{
+	return lchan_summary(vty, argc, argv, lchan_dump_full_vty);
+}
+
+DEFUN(show_lchan_summary,
+      show_lchan_summary_cmd,
+      "show lchan summary [bts_nr] [trx_nr] [ts_nr] [lchan_nr]",
+	SHOW_STR "Display information about a logical channel\n"
+	"BTS Number\n" "TRX Number\n" "Timeslot Number\n"
+	"Logical Channel Number\n")
+{
+	return lchan_summary(vty, argc, argv, lchan_dump_short_vty);
+}
+
+static void e1drv_dump_vty(struct vty *vty, struct e1inp_driver *drv)
+{
+	vty_out(vty, "E1 Input Driver %s%s", drv->name, VTY_NEWLINE);
+}
+
+DEFUN(show_e1drv,
+      show_e1drv_cmd,
+      "show e1_driver",
+	SHOW_STR "Display information about available E1 drivers\n")
+{
+	struct e1inp_driver *drv;
+
+	llist_for_each_entry(drv, &e1inp_driver_list, list)
+		e1drv_dump_vty(vty, drv);
+
+	return CMD_SUCCESS;
+}
+
+static void e1line_dump_vty(struct vty *vty, struct e1inp_line *line)
+{
+	vty_out(vty, "E1 Line Number %u, Name %s, Driver %s%s",
+		line->num, line->name ? line->name : "",
+		line->driver->name, VTY_NEWLINE);
+}
+
+DEFUN(show_e1line,
+      show_e1line_cmd,
+      "show e1_line [line_nr]",
+	SHOW_STR "Display information about a E1 line\n"
+	"E1 Line Number\n")
+{
+	struct e1inp_line *line;
+
+	if (argc >= 1) {
+		int num = atoi(argv[0]);
+		llist_for_each_entry(line, &e1inp_line_list, list) {
+			if (line->num == num) {
+				e1line_dump_vty(vty, line);
+				return CMD_SUCCESS;
+			}
+		}
+		return CMD_WARNING;
+	}	
+	
+	llist_for_each_entry(line, &e1inp_line_list, list)
+		e1line_dump_vty(vty, line);
+
+	return CMD_SUCCESS;
+}
+
+static void e1ts_dump_vty(struct vty *vty, struct e1inp_ts *ts)
+{
+	if (ts->type == E1INP_TS_TYPE_NONE)
+		return;
+	vty_out(vty, "E1 Timeslot %2u of Line %u is Type %s%s",
+		ts->num, ts->line->num, e1inp_tstype_name(ts->type),
+		VTY_NEWLINE);
+}
+
+DEFUN(show_e1ts,
+      show_e1ts_cmd,
+      "show e1_timeslot [line_nr] [ts_nr]",
+	SHOW_STR "Display information about a E1 timeslot\n"
+	"E1 Line Number\n" "E1 Timeslot Number\n")
+{
+	struct e1inp_line *line = NULL;
+	struct e1inp_ts *ts;
+	int ts_nr;
+
+	if (argc == 0) {
+		llist_for_each_entry(line, &e1inp_line_list, list) {
+			for (ts_nr = 0; ts_nr < NUM_E1_TS; ts_nr++) {
+				ts = &line->ts[ts_nr];
+				e1ts_dump_vty(vty, ts);
+			}
+		}
+		return CMD_SUCCESS;
+	}
+	if (argc >= 1) {
+		int num = atoi(argv[0]);
+		llist_for_each_entry(line, &e1inp_line_list, list) {
+			if (line->num == num)
+				break;
+		}
+		if (!line || line->num != num) {
+			vty_out(vty, "E1 line %s is invalid%s",
+				argv[0], VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+	}	
+	if (argc >= 2) {
+		ts_nr = atoi(argv[1]);
+		if (ts_nr > NUM_E1_TS) {
+			vty_out(vty, "E1 timeslot %s is invalid%s",
+				argv[1], VTY_NEWLINE);
+			return CMD_WARNING;
+		}
+		ts = &line->ts[ts_nr];
+		e1ts_dump_vty(vty, ts);
+		return CMD_SUCCESS;
+	} else {
+		for (ts_nr = 0; ts_nr < NUM_E1_TS; ts_nr++) {
+			ts = &line->ts[ts_nr];
+			e1ts_dump_vty(vty, ts);
+		}
+		return CMD_SUCCESS;
+	}
+	return CMD_SUCCESS;
+}
+
+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);
+	subscr_dump_vty(vty, pag->subscr);
+}
+
+static void bts_paging_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+	struct gsm_paging_request *pag;
+
+	llist_for_each_entry(pag, &bts->paging.pending_requests, entry)
+		paging_dump_vty(vty, pag);
+}
+
+DEFUN(show_paging,
+      show_paging_cmd,
+      "show paging [bts_nr]",
+	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;
+}
+
+#define NETWORK_STR "Configure the GSM network\n"
+
+DEFUN(cfg_net,
+      cfg_net_cmd,
+      "network", NETWORK_STR)
+{
+	vty->index = gsmnet_from_vty(vty);
+	vty->node = GSMNET_NODE;
+
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_net_ncc,
+      cfg_net_ncc_cmd,
+      "network country code <1-999>",
+      "Set the GSM network country code")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->country_code = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_mnc,
+      cfg_net_mnc_cmd,
+      "mobile network code <1-999>",
+      "Set the GSM mobile network code")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->network_code = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_name_short,
+      cfg_net_name_short_cmd,
+      "short name NAME",
+      "Set the short GSM network name")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	bsc_replace_string(gsmnet, &gsmnet->name_short, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_name_long,
+      cfg_net_name_long_cmd,
+      "long name NAME",
+      "Set the long GSM network name")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	bsc_replace_string(gsmnet, &gsmnet->name_long, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_auth_policy,
+      cfg_net_auth_policy_cmd,
+      "auth policy (closed|accept-all|token)",
+	"Authentication (not cryptographic)\n"
+	"Set the GSM network authentication policy\n"
+	"Require the MS to be activated in HLR\n"
+	"Accept all MS, whether in HLR or not\n"
+	"Use SMS-token based authentication\n")
+{
+	enum gsm_auth_policy policy = gsm_auth_policy_parse(argv[0]);
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->auth_policy = policy;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_reject_cause,
+      cfg_net_reject_cause_cmd,
+      "location updating reject cause <2-111>",
+      "Set the reject cause of location updating reject\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->reject_cause = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_encryption,
+      cfg_net_encryption_cmd,
+      "encryption a5 (0|1|2)",
+	"Encryption options\n"
+	"A5 encryption\n" "A5/0: No encryption\n"
+	"A5/1: Encryption\n" "A5/2: Export-grade Encryption\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->a5_encryption= atoi(argv[0]);
+
+	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;
+}
+
+DEFUN(cfg_net_rrlp_mode, cfg_net_rrlp_mode_cmd,
+      "rrlp mode (none|ms-based|ms-preferred|ass-preferred)",
+	"Radio Resource Location Protocol\n"
+	"Set the Radio Resource Location Protocol Mode\n"
+	"Don't send RRLP request\n"
+	"Request MS-based location\n"
+	"Request any location, prefer MS-based\n"
+	"Request any location, prefer MS-assisted\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->rrlp.mode = rrlp_mode_parse(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_mm_info, cfg_net_mm_info_cmd,
+      "mm info (0|1)",
+	"Whether to send MM INFO after LOC UPD ACCEPT")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->send_mm_info = atoi(argv[0]);
+
+	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"
+
+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")
+{
+	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")
+{
+	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
+	"How many RxQual measurements are used for averaging")
+{
+	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)")
+{
+	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")
+{
+	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")
+{
+	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")
+{
+	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)							\
+{								\
+	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.")
+DECLARE_TIMER(3103, "Set the timeout value for HANDOVER.")
+DECLARE_TIMER(3105, "Currently not used.")
+DECLARE_TIMER(3107, "Currently not used.")
+DECLARE_TIMER(3109, "Currently not used.")
+DECLARE_TIMER(3111, "Set the RSL timeout to wait before releasing the RF Channel.")
+DECLARE_TIMER(3113, "Set the time to try paging a subscriber.")
+DECLARE_TIMER(3115, "Currently not used.")
+DECLARE_TIMER(3117, "Currently not used.")
+DECLARE_TIMER(3119, "Currently not used.")
+DECLARE_TIMER(3122, "Waiting time (seconds) after IMM ASS REJECT")
+DECLARE_TIMER(3141, "Currently not used.")
+
+DEFUN(cfg_net_dtx,
+      cfg_net_dtx_cmd,
+      "dtx-used (0|1)",
+      "Enable the usage of DTX.\n"
+      "DTX is enabled/disabled")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->dtx_enabled = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_net_subscr_keep,
+      cfg_net_subscr_keep_cmd,
+      "subscriber-keep-in-ram (0|1)",
+      "Keep unused subscribers in RAM.\n"
+      "Delete unused subscribers\n" "Keep unused subscribers\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->keep_subscr = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+/* per-BTS configuration */
+DEFUN(cfg_bts,
+      cfg_bts_cmd,
+      "bts BTS_NR",
+      "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(gsmnet, GSM_BTS_TYPE_UNKNOWN,
+				    HARDCODED_TSC, 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",
+      "Set the BTS type\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int rc;
+
+	rc = gsm_set_bts_type(bts, parse_btstype(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")
+{
+	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_ci,
+      cfg_bts_ci_cmd,
+      "cell_identity <0-65535>",
+      "Set the Cell identity of this BTS\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")
+{
+	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;
+}
+
+
+DEFUN(cfg_bts_tsc,
+      cfg_bts_tsc_cmd,
+      "training_sequence_code <0-255>",
+      "Set the Training Sequence Code (TSC) of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int tsc = atoi(argv[0]);
+
+	if (tsc < 0 || tsc > 0xff) {
+		vty_out(vty, "%% TSC %d is not in the valid range (0-255)%s",
+			tsc, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	bts->tsc = tsc;
+
+	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")
+{
+	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>",
+      "Set the ip.access BTS Unit ID of this 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_serno,
+      cfg_bts_serno_cmd,
+      "hsl serial-number STRING",
+      "Set the HSL Serial Number of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+
+	if (bts->type != GSM_BTS_TYPE_HSL_FEMTO) {
+		vty_out(vty, "%% BTS is not of HSL type%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	bts->hsl.serno = strtoul(argv[0], NULL, 10);
+
+	return CMD_SUCCESS;
+}
+
+#define OML_STR	"Organization & Maintenance Link\n"
+#define IPA_STR "ip.access Specific Options\n"
+
+DEFUN(cfg_bts_stream_id,
+      cfg_bts_stream_id_cmd,
+      "oml ip.access stream_id <0-255>",
+	OML_STR IPA_STR
+      "Set the ip.access Stream ID of the OML link of this BTS\n")
+{
+	struct gsm_bts *bts = vty->index;
+	int stream_id = atoi(argv[0]);
+
+	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;
+
+	return CMD_SUCCESS;
+}
+
+#define OML_E1_STR OML_STR "E1 Line\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 interface to be used for OML\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")
+{
+	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")
+{
+	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")
+{
+	struct gsm_bts *bts = vty->index;
+	bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0]));
+	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 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")
+{
+	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?")
+{
+	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)",
+      "Should this cell allow emergency calls?")
+{
+	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_ms_max_power, cfg_bts_ms_max_power_cmd,
+      "ms max power <0-40>",
+      "Maximum transmit power of the MS")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->ms_max_power = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd,
+      "cell reselection hysteresis <0-14>",
+      "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 (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 Bar Qualify")
+{
+	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 Re-Selection 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.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 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",
+	"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 in seconds (by 20s increments)")
+{
+	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",
+	"Set cell selection penalty time to reserved value 31\n"
+		"(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 <0-1530>",
+      "Periodic Location Updating Interval in Minutes")
+{
+	struct gsm_bts *bts = vty->index;
+
+	bts->si_common.chan_desc.t3212 = atoi(argv[0]) / 6;
+
+	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")
+{
+	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")
+{
+	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")
+{
+	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")
+{
+	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")
+{
+	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 FREE_NR",
+      "Only page when having a certain amount of free slots. -1 to disable")
+{
+	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")
+{
+	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_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]);
+
+	if (mode != BTS_GPRS_NONE &&
+	    !gsm_bts_has_feature(bts, BTS_FEAT_GPRS)) {
+		vty_out(vty, "This BTS type does not support %s%s", argv[0],
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	if (mode == BTS_GPRS_EGPRS &&
+	    !gsm_bts_has_feature(bts, BTS_FEAT_EGPRS)) {
+		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;
+}
+
+#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(bts->si_buf[type], 0x2b, sizeof(bts->si_buf[type]));
+
+	/* Parse the user-specified SI in hex format, [partially] overwriting padding */
+	rc = hexparse(argv[1], bts->si_buf[type], sizeof(bts->si_buf[0]));
+	if (rc < 0 || rc > sizeof(bts->si_buf[0])) {
+		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_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-1024>",
+	"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;
+}
+
+DEFUN(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd,
+	"si5 neighbor-list (add|del) arfcn <0-1024>",
+	"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;
+}
+
+#define TRX_TEXT "Radio Transceiver\n"
+
+/* per TRX configuration */
+DEFUN(cfg_trx,
+      cfg_trx_cmd,
+      "trx TRX_NR",
+	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-1024>",
+      "Set the ARFCN for this TRX\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 dB\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 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)",
+      "E1 interface 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>",
+      "Set the TEI to be used for RSL")
+{
+	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)",
+      "Turn off RF of the TRX.\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")
+{
+	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",
+      "Physical Channel configuration (TCH/SDCCH/...)")
+{
+	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;
+}
+
+#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_bts_has_feature(ts->trx->bts, 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")
+{
+	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")
+{
+	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 sub-slot connected to this on-air timeslot")
+{
+	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",
+		counter_get(net->stats.chreq.total),
+		counter_get(net->stats.chreq.no_channel), VTY_NEWLINE);
+	vty_out(vty, "Channel Failures        : %lu rf_failures, %lu rll failures%s",
+		counter_get(net->stats.chan.rf_fail),
+		counter_get(net->stats.chan.rll_err), VTY_NEWLINE);
+	vty_out(vty, "Paging                  : %lu attempted, %lu complete, %lu expired%s",
+		counter_get(net->stats.paging.attempted),
+		counter_get(net->stats.paging.completed),
+		counter_get(net->stats.paging.expired), VTY_NEWLINE);
+	vty_out(vty, "BTS failures            : %lu OML, %lu RSL%s",
+		counter_get(net->stats.bts.oml_fail),
+		counter_get(net->stats.bts.rsl_fail), VTY_NEWLINE);
+}
+
+DEFUN(logging_fltr_imsi,
+      logging_fltr_imsi_cmd,
+      "logging filter imsi IMSI",
+	LOGGING_STR FILTER_STR
+      "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
+{
+	struct log_target *tgt = osmo_log_vty2tgt(vty);
+
+	if (!tgt)
+		return CMD_WARNING;
+
+	log_set_imsi_filter(tgt, argv[0]);
+	return CMD_SUCCESS;
+}
+
+
+DEFUN(drop_bts,
+      drop_bts_cmd,
+      "drop bts connection <0-65535> (oml|rsl)",
+      "Debug/Simulation command to drop ipaccess BTS\n"
+      "BTS NR\n" "Connection Type\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(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 *bts;
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	int bts_nr = atoi(argv[0]);
+	int trx_nr = atoi(argv[1]);
+	int ts_nr = atoi(argv[2]);
+	int activate;
+
+	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 (!is_ipaccess_bts(bts)) {
+		vty_out(vty, "%% This command only works for ipaccess BTS%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	trx = gsm_bts_trx_num(bts, trx_nr);
+	if (!trx) {
+		vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	ts = &trx->ts[ts_nr];
+	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;
+
+}
+
+extern int bsc_vty_init_extra(void);
+extern const char *openbsc_copyright;
+
+int bsc_vty_init(void)
+{
+	install_element_ve(&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(&logging_fltr_imsi_cmd);
+
+	install_element_ve(&show_e1drv_cmd);
+	install_element_ve(&show_e1line_cmd);
+	install_element_ve(&show_e1ts_cmd);
+
+	install_element_ve(&show_paging_cmd);
+
+	logging_vty_add_cmds();
+	install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
+
+	install_element(CONFIG_NODE, &cfg_net_cmd);
+	install_node(&net_node, config_write_net);
+	install_default(GSMNET_NODE);
+	install_element(GSMNET_NODE, &ournode_exit_cmd);
+	install_element(GSMNET_NODE, &ournode_end_cmd);
+	install_element(GSMNET_NODE, &cfg_net_ncc_cmd);
+	install_element(GSMNET_NODE, &cfg_net_mnc_cmd);
+	install_element(GSMNET_NODE, &cfg_net_name_short_cmd);
+	install_element(GSMNET_NODE, &cfg_net_name_long_cmd);
+	install_element(GSMNET_NODE, &cfg_net_auth_policy_cmd);
+	install_element(GSMNET_NODE, &cfg_net_reject_cause_cmd);
+	install_element(GSMNET_NODE, &cfg_net_encryption_cmd);
+	install_element(GSMNET_NODE, &cfg_net_neci_cmd);
+	install_element(GSMNET_NODE, &cfg_net_rrlp_mode_cmd);
+	install_element(GSMNET_NODE, &cfg_net_mm_info_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_subscr_keep_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);
+	install_default(BTS_NODE);
+	install_element(BTS_NODE, &ournode_exit_cmd);
+	install_element(BTS_NODE, &ournode_end_cmd);
+	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_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_serno_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_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_ms_max_power_cmd);
+	install_element(BTS_NODE, &cfg_bts_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_gprs_mode_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_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_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_trx_cmd);
+	install_node(&trx_node, dummy_config_write);
+	install_default(TRX_NODE);
+	install_element(TRX_NODE, &ournode_exit_cmd);
+	install_element(TRX_NODE, &ournode_end_cmd);
+	install_element(TRX_NODE, &cfg_trx_arfcn_cmd);
+	install_element(TRX_NODE, &cfg_description_cmd);
+	install_element(TRX_NODE, &cfg_no_description_cmd);
+	install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+	install_element(TRX_NODE, &cfg_trx_max_power_red_cmd);
+	install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd);
+	install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd);
+	install_element(TRX_NODE, &cfg_trx_rf_locked_cmd);
+
+	install_element(TRX_NODE, &cfg_ts_cmd);
+	install_node(&ts_node, dummy_config_write);
+	install_default(TS_NODE);
+	install_element(TS_NODE, &ournode_exit_cmd);
+	install_element(TS_NODE, &ournode_end_cmd);
+	install_element(TS_NODE, &cfg_ts_pchan_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, &pdch_act_cmd);
+
+	abis_nm_vty_init();
+	abis_om2k_vty_init();
+	e1inp_vty_init();
+
+	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 0000000..27d5ce6
--- /dev/null
+++ b/src/libbsc/bts_ericsson_rbs2000.c
@@ -0,0 +1,164 @@
+/* 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 "../libabis/input/lapd.h"
+
+static void bootstrap_om_bts(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 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 (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)
+				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_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;
+}
+
+static void config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+	abis_om2k_config_write_bts(vty, bts);
+}
+
+static struct gsm_bts_model model_rbs2k = {
+	.type = GSM_BTS_TYPE_RBS2000,
+	.name = "rbs2000",
+	.oml_rcvmsg = &abis_om2k_rcvmsg,
+	.config_write_bts = &config_write_bts,
+};
+
+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/src/libbsc/bts_hsl_femtocell.c b/src/libbsc/bts_hsl_femtocell.c
new file mode 100644
index 0000000..e01634c
--- /dev/null
+++ b/src/libbsc/bts_hsl_femtocell.c
@@ -0,0 +1,162 @@
+/* OpenBSC support code for HSL Femtocell */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2011 by OnWaves
+ *
+ * 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 <arpa/inet.h>
+
+#include <osmocore/tlv.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+#include <openbsc/e1_input.h>
+
+static struct gsm_bts_model model_hslfemto = {
+	.type = GSM_BTS_TYPE_HSL_FEMTO,
+	.nm_att_tlvdef = {
+		.def = {
+			/* no HSL specific OML attributes that we know of */
+		},
+	},
+};
+
+
+static const uint8_t l1_msg[] = {
+#ifdef HSL_SR_1_0
+	0x80, 0x8a,
+#else
+	0x81, 0x8a,
+#endif
+		0xC4, 0x0b,
+};
+
+static const uint8_t conn_trau_msg[] = {
+#ifdef HSL_SR_1_0
+	0x80, 0x81,
+#else
+	0x81, 0x81,
+#endif
+		0xC1, 16,
+			0x02, 0x00, 0x00, 0x00, 0xC0, 0xA8, 0xEA, 0x01,
+			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t conn_trau_msg2[] = {
+#ifdef HSL_SR_1_0
+	0x80, 0x81,
+#else
+	0x81, 0x81,
+#endif
+		0xC1, 16,
+			0x02, 0x00, 0xd4, 0x07, 0xC0, 0xA8, 0xEA, 0x01,
+			0x38, 0xA4, 0x45, 0x00, 0x04, 0x59, 0x40, 0x00
+};
+
+static uint8_t oml_arfcn_bsic[] = {
+#ifdef HSL_SR_1_0
+	0x81, 0x80, 0x00, 10,
+#else
+	0x80, 0x80, 0x00, 10,
+#endif
+		NM_MT_SET_BTS_ATTR, NM_OC_BTS, 0xff, 0xff, 0xff,
+			NM_ATT_BCCH_ARFCN, 0x03, 0x67,
+			NM_ATT_BSIC, 0x00
+};
+
+static inline struct msgb *hsl_alloc_msgb(void)
+{
+	return msgb_alloc_headroom(1024, 127, "HSL");
+}
+
+static int hslfemto_bootstrap_om(struct gsm_bts *bts)
+{
+	struct msgb *msg;
+	uint8_t *cur;
+
+	msg = hsl_alloc_msgb();
+	cur = msgb_put(msg, sizeof(l1_msg));
+	memcpy(msg->data, l1_msg, sizeof(l1_msg));
+	msg->trx = bts->c0;
+	abis_rsl_sendmsg(msg);
+
+#if 1
+	msg = hsl_alloc_msgb();
+	cur = msgb_put(msg, sizeof(conn_trau_msg));
+	memcpy(msg->data, conn_trau_msg, sizeof(conn_trau_msg));
+	msg->trx = bts->c0;
+	abis_rsl_sendmsg(msg);
+#endif
+	msg = hsl_alloc_msgb();
+	cur = msgb_put(msg, sizeof(conn_trau_msg2));
+	memcpy(msg->data, conn_trau_msg2, sizeof(conn_trau_msg2));
+	msg->trx = bts->c0;
+	abis_rsl_sendmsg(msg);
+
+	*((uint16_t *)oml_arfcn_bsic+10) = htons(bts->c0->arfcn);
+	oml_arfcn_bsic[13] = bts->bsic;
+
+	msg = hsl_alloc_msgb();
+	cur = msgb_put(msg, sizeof(oml_arfcn_bsic));
+	memcpy(msg->data, oml_arfcn_bsic, sizeof(oml_arfcn_bsic));
+	msg->trx = bts->c0;
+	_abis_nm_sendmsg(msg, 0);
+
+	/* Delay the OPSTART until after SI have been set via RSL */
+	//abis_nm_opstart(bts, NM_OC_BTS, 255, 255, 255);
+
+	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:
+			hslfemto_bootstrap_om(isd->trx->bts);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+int bts_model_hslfemto_init(void)
+{
+	model_hslfemto.features.data = &model_hslfemto._features_data[0];
+	model_hslfemto.features.data_len = sizeof(model_hslfemto._features_data);
+
+	gsm_btsmodel_set_feature(&model_hslfemto, BTS_FEAT_GPRS);
+	gsm_btsmodel_set_feature(&model_hslfemto, BTS_FEAT_EGPRS);
+
+	register_signal_handler(SS_INPUT, inp_sig_cb, NULL);
+
+	return gsm_bts_model_register(&model_hslfemto);
+}
diff --git a/src/libbsc/bts_ipaccess_nanobts.c b/src/libbsc/bts_ipaccess_nanobts.c
new file mode 100644
index 0000000..25dc0c8
--- /dev/null
+++ b/src/libbsc/bts_ipaccess_nanobts.c
@@ -0,0 +1,447 @@
+/* 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 <sys/types.h>
+#include <arpa/inet.h>
+
+#include <osmocore/tlv.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/signal.h>
+#include <openbsc/abis_nm.h>
+
+static struct gsm_bts_model model_nanobts = {
+	.type = GSM_BTS_TYPE_NANOBTS,
+	.name = "nanobts",
+	.oml_rcvmsg = &abis_nm_rcvmsg,
+	.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 },
+		},
+	},
+};
+
+static unsigned char nanobts_attr_bts[] = {
+	NM_ATT_INTERF_BOUND, 0x55, 0x5b, 0x61, 0x67, 0x6d, 0x73,
+	/* interference avg. period in numbers of SACCH multifr */
+	NM_ATT_INTAVE_PARAM, 0x06,
+	/* conn fail based on SACCH error rate */
+	NM_ATT_CONN_FAIL_CRIT, 0x00, 0x02, 0x01, 0x10,
+	NM_ATT_T200, 0x1e, 0x24, 0x24, 0xa8, 0x34, 0x21, 0xa8,
+	NM_ATT_MAX_TA, 0x3f,
+	NM_ATT_OVERL_PERIOD, 0x00, 0x01, 10, /* seconds */
+	NM_ATT_CCCH_L_T, 10, /* percent */
+	NM_ATT_CCCH_L_I_P, 1, /* seconds */
+	NM_ATT_RACH_B_THRESH, 10, /* busy threshold in - dBm */
+	NM_ATT_LDAVG_SLOTS, 0x03, 0xe8, /* rach load averaging 1000 slots */
+	NM_ATT_BTS_AIR_TIMER, 128, /* miliseconds */
+	NM_ATT_NY1, 10, /* 10 retransmissions of physical config */
+	NM_ATT_BCCH_ARFCN, HARDCODED_ARFCN >> 8, HARDCODED_ARFCN & 0xff,
+	NM_ATT_BSIC, HARDCODED_BSIC,
+	NM_ATT_IPACC_CGI, 0, 7,  0x00, 0xf1, 0x10, 0x00, 0x01, 0x00, 0x00,
+};
+
+static unsigned char nanobts_attr_radio[] = {
+	NM_ATT_RF_MAXPOWR_R, 0x0c, /* number of -2dB reduction steps / Pn */
+	NM_ATT_ARFCN_LIST, 0x00, 0x02, HARDCODED_ARFCN >> 8, HARDCODED_ARFCN & 0xff,
+};
+
+static unsigned char nanobts_attr_nse[] = {
+	NM_ATT_IPACC_NSEI, 0, 2,  0x03, 0x9d, /* NSEI 925 */
+	NM_ATT_IPACC_NS_CFG, 0, 7,  3,  /* (un)blocking timer (Tns-block) */
+				    3,  /* (un)blocking retries */
+				    3,  /* reset timer (Tns-reset) */
+				    3,  /* reset retries */
+				    30,  /* test timer (Tns-test) */
+				    3,  /* alive timer (Tns-alive) */
+				    10, /* alive retrires */
+	NM_ATT_IPACC_BSSGP_CFG, 0, 11,
+				    3,  /* blockimg timer (T1) */
+				    3,  /* blocking retries */
+				    3,  /* unblocking retries */
+				    3,  /* reset timer */
+				    3,  /* reset retries */
+				    10, /* suspend timer (T3) in 100ms */
+				    3,  /* suspend retries */
+				    10, /* resume timer (T4) in 100ms */
+				    3,  /* resume retries */
+				    10, /* capability update timer (T5) */
+				    3,  /* capability update retries */
+};
+
+static unsigned char nanobts_attr_cell[] = {
+	NM_ATT_IPACC_RAC, 0, 1,  1, /* routing area code */
+	NM_ATT_IPACC_GPRS_PAGING_CFG, 0, 2,
+		5,	/* repeat time (50ms) */
+		3,	/* repeat count */
+	NM_ATT_IPACC_BVCI, 0, 2,  0x03, 0x9d, /* BVCI 925 */
+	NM_ATT_IPACC_RLC_CFG, 0, 9,
+		20, 	/* T3142 */
+		5, 	/* T3169 */
+		5,	/* T3191 */
+		200,	/* T3193 */
+		5,	/* T3195 */
+		10,	/* N3101 */
+		4,	/* N3103 */
+		8,	/* N3105 */
+		15,	/* RLC CV countdown */
+	NM_ATT_IPACC_CODING_SCHEMES, 0, 2,  0x0f, 0x00,	/* CS1..CS4 */
+	NM_ATT_IPACC_RLC_CFG_2, 0, 5,
+		0x00, 250,	/* T downlink TBF extension (0..500) */
+		0x00, 250,	/* T uplink TBF extension (0..500) */
+		2,	/* CS2 */
+#if 0
+	/* EDGE model only, breaks older models.
+	 * Should inquire the BTS capabilities */
+	NM_ATT_IPACC_RLC_CFG_3, 0, 1,
+		2,	/* MCS2 */
+#endif
+};
+
+static unsigned char nanobts_attr_nsvc0[] = {
+	NM_ATT_IPACC_NSVCI, 0, 2,  0x03, 0x9d, /* 925 */
+	NM_ATT_IPACC_NS_LINK_CFG, 0, 8,
+		0x59, 0xd8, /* remote udp port (23000) */
+		192, 168, 100, 11, /* remote ip address */
+		0x59, 0xd8, /* local udp port (23000) */
+};
+
+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));
+}
+
+/*
+ * Patch the various SYSTEM INFORMATION tables to update
+ * the LAI
+ */
+static void patch_nm_tables(struct gsm_bts *bts)
+{
+	u_int8_t arfcn_low = bts->c0->arfcn & 0xff;
+	u_int8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f;
+
+	/* patch ARFCN into BTS Attributes */
+	nanobts_attr_bts[42] &= 0xf0;
+	nanobts_attr_bts[42] |= arfcn_high;
+	nanobts_attr_bts[43] = arfcn_low;
+
+	/* patch the RACH attributes */
+	if (bts->rach_b_thresh != -1) {
+		nanobts_attr_bts[33] = bts->rach_b_thresh & 0xff;
+	}
+
+	if (bts->rach_ldavg_slots != -1) {
+		u_int8_t avg_high = bts->rach_ldavg_slots & 0xff;
+		u_int8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f;
+
+		nanobts_attr_bts[35] = avg_high;
+		nanobts_attr_bts[36] = avg_low;
+	}
+
+	/* patch BSIC */
+	nanobts_attr_bts[sizeof(nanobts_attr_bts)-11] = bts->bsic;
+
+	/* patch CGI */
+	abis_nm_ipaccess_cgi(nanobts_attr_bts+sizeof(nanobts_attr_bts)-7, bts);
+
+	/* patch the power reduction */
+	nanobts_attr_radio[1] = bts->c0->max_power_red / 2;
+
+	/* patch NSEI */
+	nanobts_attr_nse[3] = bts->gprs.nse.nsei >> 8;
+	nanobts_attr_nse[4] = bts->gprs.nse.nsei & 0xff;
+	memcpy(nanobts_attr_nse+8, bts->gprs.nse.timer,
+		ARRAY_SIZE(bts->gprs.nse.timer));
+	memcpy(nanobts_attr_nse+18, bts->gprs.cell.timer,
+		ARRAY_SIZE(bts->gprs.cell.timer));
+
+	/* patch NSVCI */
+	nanobts_attr_nsvc0[3] = bts->gprs.nsvc[0].nsvci >> 8;
+	nanobts_attr_nsvc0[4] = bts->gprs.nsvc[0].nsvci & 0xff;
+
+	/* patch IP address as SGSN IP */
+	patch_16(nanobts_attr_nsvc0 + 8, 
+			htons(bts->gprs.nsvc[0].remote_port));
+	patch_32(nanobts_attr_nsvc0 + 10,
+			htonl(bts->gprs.nsvc[0].remote_ip));
+	patch_16(nanobts_attr_nsvc0 + 14,
+			htons(bts->gprs.nsvc[0].local_port));
+
+	/* patch BVCI */
+	nanobts_attr_cell[12] = bts->gprs.cell.bvci >> 8;
+	nanobts_attr_cell[13] = bts->gprs.cell.bvci & 0xff;
+	/* patch RAC */
+	nanobts_attr_cell[3] = bts->gprs.rac;
+
+	if (bts->gprs.mode == BTS_GPRS_EGPRS) {
+		/* patch EGPRS coding schemes MCS 1..9 */
+		nanobts_attr_cell[29] = 0x8f;
+		nanobts_attr_cell[30] = 0xff;
+	}
+}
+
+
+/* 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)
+{
+	u_int8_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;
+
+	/* 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) {
+			patch_nm_tables(bts);
+			abis_nm_set_bts_attr(bts, nanobts_attr_bts,
+					     sizeof(nanobts_attr_bts));
+			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) {
+			patch_nm_tables(trx->bts);
+			enum abis_nm_chan_comb ccomb =
+						abis_nm_chcomb4pchan(ts->pchan);
+			abis_nm_set_channel_attr(ts, ccomb);
+			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);
+		}
+		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) {
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  0xff, 0xff, nanobts_attr_nse,
+						  sizeof(nanobts_attr_nse));
+			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) {
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  0, 0xff, nanobts_attr_cell,
+						  sizeof(nanobts_attr_cell));
+			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) {
+			abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr,
+						  nsvc->id, 0xff,
+						  nanobts_attr_nsvc0,
+						  sizeof(nanobts_attr_nsvc0));
+			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 gsm_bts *bts = mb->trx->bts;
+	struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+
+	if (!trx)
+		return -EINVAL;
+
+	if (trx->bts->type != GSM_BTS_TYPE_NANOBTS)
+		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, 0, 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->nm_state.administrative;
+		/* Patch ARFCN into radio attribute */
+		nanobts_attr_radio[5] &= 0xf0;
+		nanobts_attr_radio[5] |= trx->arfcn >> 8;
+		nanobts_attr_radio[6] = trx->arfcn & 0xff;
+		abis_nm_set_radio_attr(trx, nanobts_attr_radio,
+				       sizeof(nanobts_attr_radio));
+		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 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;
+}
+
+int bts_model_nanobts_init(void)
+{
+	model_nanobts.features.data = &model_nanobts._features_data[0];
+	model_nanobts.features.data_len = sizeof(model_nanobts._features_data);
+
+	gsm_btsmodel_set_feature(&model_nanobts, BTS_FEAT_GPRS);
+	gsm_btsmodel_set_feature(&model_nanobts, BTS_FEAT_EGPRS);
+
+	register_signal_handler(SS_NM, nm_sig_cb, NULL);
+
+	return gsm_bts_model_register(&model_nanobts);
+}
diff --git a/src/libbsc/bts_siemens_bs11.c b/src/libbsc/bts_siemens_bs11.c
new file mode 100644
index 0000000..5a5f883
--- /dev/null
+++ b/src/libbsc/bts_siemens_bs11.c
@@ -0,0 +1,591 @@
+/* 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 <sys/types.h>
+
+#include <osmocore/tlv.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_nm.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/signal.h>
+
+static struct gsm_bts_model model_bs11 = {
+	.type = GSM_BTS_TYPE_BS11,
+	.name = "bs11",
+	.oml_rcvmsg = &abis_nm_rcvmsg,
+	.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)
+{
+	u_int8_t arfcn_low = bts->c0->arfcn & 0xff;
+	u_int8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f;
+
+	/* 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) {
+		u_int8_t avg_high = bts->rach_ldavg_slots & 0xff;
+		u_int8_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;
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+		abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts,
+					e1l->e1_ts_ss);
+		break;
+	default:
+		break;
+	}
+}
+
+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 {
+			u_int8_t trx1_attr_radio[sizeof(bs11_attr_radio)];
+			u_int8_t arfcn_low = trx->arfcn & 0xff;
+			u_int8_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_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_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_BS11)
+				bootstrap_om_bs11(isd->trx->bts);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+int bts_model_bs11_init(void)
+{
+	model_bs11.features.data = &model_bs11._features_data[0];
+	model_bs11.features.data_len = sizeof(model_bs11._features_data);
+
+	gsm_btsmodel_set_feature(&model_bs11, BTS_FEAT_HOPPING);
+	gsm_btsmodel_set_feature(&model_bs11, 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_bs11);
+}
diff --git a/src/libbsc/bts_unknown.c b/src/libbsc/bts_unknown.c
new file mode 100644
index 0000000..f954599
--- /dev/null
+++ b/src/libbsc/bts_unknown.c
@@ -0,0 +1,41 @@
+/* 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 <sys/types.h>
+
+#include <openbsc/gsm_data.h>
+#include <osmocore/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 0000000..167381b
--- /dev/null
+++ b/src/libbsc/chan_alloc.c
@@ -0,0 +1,507 @@
+/* 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/signal.h>
+
+#include <osmocore/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->nm_state))
+			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->nm_state) ||
+		    !nm_is_running(&trx->bb_transc.nm_state))
+			return 0;
+	}
+
+	return 1;
+}
+
+struct gsm_bts_trx_ts *ts_c0_alloc(struct gsm_bts *bts,
+				   enum gsm_phys_chan_config pchan)
+{
+	struct gsm_bts_trx *trx = bts->c0;
+	struct gsm_bts_trx_ts *ts = &trx->ts[0];
+
+	if (pchan != GSM_PCHAN_CCCH &&
+	    pchan != GSM_PCHAN_CCCH_SDCCH4)
+		return NULL;
+
+	if (ts->pchan != GSM_PCHAN_NONE)
+		return NULL;
+
+	ts->pchan = pchan;
+
+	return ts;
+}
+
+/* Allocate a physical channel (TS) */
+struct gsm_bts_trx_ts *ts_alloc(struct gsm_bts *bts,
+				enum gsm_phys_chan_config pchan)
+{
+	int j;
+	struct gsm_bts_trx *trx;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		int from, to;
+
+		if (!trx_is_usable(trx))
+			continue;
+
+		/* the following constraints are pure policy,
+		 * no requirement to put this restriction in place */
+		if (trx == bts->c0) {
+			/* On the first TRX we run one CCCH and one SDCCH8 */
+			switch (pchan) {
+			case GSM_PCHAN_CCCH:
+			case GSM_PCHAN_CCCH_SDCCH4:
+				from = 0; to = 0;
+				break;
+			case GSM_PCHAN_TCH_F:
+			case GSM_PCHAN_TCH_H:
+				from = 1; to = 7;
+				break;
+			case GSM_PCHAN_SDCCH8_SACCH8C:
+			default:
+				return NULL;
+			}
+		} else {
+			/* Every secondary TRX is configured for TCH/F
+			 * and TCH/H only */
+			switch (pchan) {
+			case GSM_PCHAN_SDCCH8_SACCH8C:
+				from = 1; to = 1;
+			case GSM_PCHAN_TCH_F:
+			case GSM_PCHAN_TCH_H:
+				from = 1; to = 7;
+				break;
+			default:
+				return NULL;
+			}
+		}
+
+		for (j = from; j <= to; j++) {
+			struct gsm_bts_trx_ts *ts = &trx->ts[j];
+
+			if (!ts_is_usable(ts))
+				continue;
+
+			if (ts->pchan == GSM_PCHAN_NONE) {
+				ts->pchan = pchan;
+				/* set channel attribute on OML */
+				abis_nm_set_channel_attr(ts, abis_nm_chcomb4pchan(pchan));
+				return ts;
+			}
+		}
+	}
+	return NULL;
+}
+
+/* Free a physical channel (TS) */
+void ts_free(struct gsm_bts_trx_ts *ts)
+{
+	ts->pchan = GSM_PCHAN_NONE;
+}
+
+static const u_int8_t subslots_per_pchan[] = {
+	[GSM_PCHAN_NONE] = 0,
+	[GSM_PCHAN_CCCH] = 0,
+	[GSM_PCHAN_CCCH_SDCCH4] = 4,
+	[GSM_PCHAN_TCH_F] = 1,
+	[GSM_PCHAN_TCH_H] = 2,
+	[GSM_PCHAN_SDCCH8_SACCH8C] = 8,
+	/* FIXME: what about dynamic TCH_F_TCH_H ? */
+	[GSM_PCHAN_TCH_F_PDCH] = 1,
+};
+
+static struct gsm_lchan *
+_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan)
+{
+	struct gsm_bts_trx_ts *ts;
+	int j, ss;
+
+	if (!trx_is_usable(trx))
+		return NULL;
+
+	for (j = 0; j < 8; j++) {
+		ts = &trx->ts[j];
+		if (!ts_is_usable(ts))
+			continue;
+		/* ip.access dynamic TCH/F + PDCH combination */
+		if (ts->pchan == GSM_PCHAN_TCH_F_PDCH &&
+		    pchan == GSM_PCHAN_TCH_F) {
+			/* we can only consider such a dynamic channel
+			 * if the PDCH is currently inactive */
+			if (ts->flags & TS_F_PDCH_MODE)
+				continue;
+		} else if (ts->pchan != pchan)
+			continue;
+		/* check if all sub-slots are allocated yet */
+		for (ss = 0; ss < subslots_per_pchan[pchan]; 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_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
+{
+	struct gsm_bts_trx *trx;
+	struct gsm_bts_trx_ts *ts;
+	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);
+			if (lc)
+				return lc;
+		}
+	} else {
+		llist_for_each_entry(trx, &bts->trx_list, list) {
+			lc = _lc_find_trx(trx, pchan);
+			if (lc)
+				return lc;
+		}
+	}
+
+	/* we cannot allocate more of these */
+	if (pchan == GSM_PCHAN_CCCH_SDCCH4)
+		return NULL;
+
+	/* if we've reached here, we need to allocate a new physical
+	 * channel for the logical channel type requested */
+	ts = ts_alloc(bts, pchan);
+	if (!ts) {
+		/* no more radio resources */
+		return NULL;
+	}
+	return &ts->lchan[0];
+}
+
+/* Allocate a logical channel */
+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, second;
+
+	switch (type) {
+	case GSM_LCHAN_SDCCH:
+		if (bts->chan_alloc_reverse) {
+			first = GSM_PCHAN_SDCCH8_SACCH8C;
+			second = GSM_PCHAN_CCCH_SDCCH4;
+		} else {
+			first = GSM_PCHAN_CCCH_SDCCH4;
+			second = GSM_PCHAN_SDCCH8_SACCH8C;
+		}
+
+		lchan = _lc_find_bts(bts, first);
+		if (lchan == NULL)
+			lchan = _lc_find_bts(bts, second);
+
+		/* allow to assign bigger channels */
+		if (allow_bigger) {
+			if (lchan == NULL) {
+				lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H);
+				type = GSM_LCHAN_TCH_H;
+			}
+
+			if (lchan == NULL) {
+				lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+				type = GSM_LCHAN_TCH_F;
+			}
+		}
+		break;
+	case GSM_LCHAN_TCH_F:
+		lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F);
+		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);
+			type = GSM_LCHAN_TCH_F;
+		}
+		break;
+	default:
+		LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type);
+	}
+
+	if (lchan) {
+		lchan->type = type;
+
+		/* clear sapis */
+		memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis));
+
+		/* clear multi rate config */
+		memset(&lchan->mr_conf, 0, sizeof(lchan->mr_conf));
+	} else {
+		struct challoc_signal_data sig;
+		sig.bts = bts;
+		sig.type = type;
+		dispatch_signal(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;
+		dispatch_signal(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, &sig);
+	}
+
+
+	/* stop the timer */
+	bsc_del_timer(&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;
+	dispatch_signal(SS_CHALLOC, S_CHALLOC_FREED, &sig);
+
+	if (lchan->conn) {
+		LOGP(DRLL, LOGL_ERROR, "the subscriber connection should be gone.\n");
+		lchan->conn = NULL;
+	}
+
+	lchan->sach_deact = 0;
+	lchan->release_reason = 0;
+
+	/* 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)
+{
+	bsc_del_timer(&lchan->T3101);
+	bsc_del_timer(&lchan->T3111);
+	bsc_del_timer(&lchan->error_timer);
+
+	lchan->type = GSM_LCHAN_NONE;
+	lchan->state = LCHAN_S_NONE;
+}
+
+/* release the next allocated SAPI or return 0 */
+static int _lchan_release_next_sapi(struct gsm_lchan *lchan)
+{
+	int sapi;
+
+	for (sapi = 1; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) {
+		u_int8_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, lchan->release_reason);
+		return 0;
+	}
+
+	return 1;
+}
+
+/* Drive the release process of the lchan */
+static void _lchan_handle_release(struct gsm_lchan *lchan)
+{
+	/* Ask for SAPI != 0 to be freed first and stop if we need to wait */
+	if (_lchan_release_next_sapi(lchan) == 0)
+		return;
+
+	if (lchan->sach_deact) {
+		gsm48_send_rr_release(lchan);
+		return;
+	}
+
+	rsl_release_request(lchan, 0, lchan->release_reason);
+	rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ);
+}
+
+/* called from abis rsl */
+int rsl_lchan_rll_release(struct gsm_lchan *lchan, u_int8_t link_id)
+{
+	if (lchan->state != LCHAN_S_REL_REQ)
+		return -1;
+
+	if ((link_id & 0x7) != 0)
+		_lchan_handle_release(lchan);
+	return 0;
+}
+
+/* Consider releasing the channel now */
+int lchan_release(struct gsm_lchan *lchan, int sach_deact, int reason)
+{
+	DEBUGP(DRLL, "%s starting release sequence\n", gsm_lchan_name(lchan));
+	rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ);
+
+	lchan->conn = NULL;
+	lchan->release_reason = reason;
+	lchan->sach_deact = sach_deact;
+	_lchan_handle_release(lchan);
+	return 1;
+}
+
+static struct gsm_lchan* lchan_find(struct gsm_bts *bts, struct gsm_subscriber *subscr) {
+	struct gsm_bts_trx *trx;
+	int ts_no, lchan_no;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		for (ts_no = 0; ts_no < 8; ++ts_no) {
+			for (lchan_no = 0; lchan_no < TS_MAX_LCHAN; ++lchan_no) {
+				struct gsm_lchan *lchan =
+					&trx->ts[ts_no].lchan[lchan_no];
+				if (lchan->conn && subscr == lchan->conn->subscr)
+					return lchan;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr)
+{
+	struct gsm_bts *bts;
+	struct gsm_network *net = subscr->net;
+	struct gsm_lchan *lchan;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		lchan = lchan_find(bts, subscr);
+		if (lchan)
+			return lchan->conn;
+	}
+
+	return NULL;
+}
+
+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->nm_state) ||
+		    !nm_is_running(&trx->bb_transc.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;
+
+			/* skip administratively deactivated timeslots */
+			if (!nm_is_running(&ts->nm_state))
+				continue;
+
+			for (j = 0; j < subslots_per_pchan[ts->pchan]; 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 0000000..958839d
--- /dev/null
+++ b/src/libbsc/e1_config.c
@@ -0,0 +1,296 @@
+/* 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 <openbsc/e1_input.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/misdn.h>
+#include <openbsc/ipaccess.h>
+#include <osmocore/talloc.h>
+#include <openbsc/debug.h>
+
+#define SAPI_L2ML	0
+#define SAPI_OML	62
+#define SAPI_RSL	0	/* 63 ? */
+
+/* The e1_reconfig_*() functions below tale 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(DMI, "e1_reconfig_ts(%u,%u,%u)\n", ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+	if (!e1_link->e1_ts) {
+		LOGP(DINP, LOGL_ERROR, "TS (%u/%u/%u) without E1 timeslot?\n",
+		     ts->nr, ts->trx->nr, ts->trx->bts->nr);
+		return 0;
+	}
+
+	line = e1inp_line_get(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DINP, 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;
+	}
+
+	switch (ts->pchan) {
+	case GSM_PCHAN_TCH_F:
+	case GSM_PCHAN_TCH_H:
+		e1_ts = &line->ts[e1_link->e1_ts-1];
+		e1inp_ts_config(e1_ts, line, E1INP_TS_TYPE_TRAU);
+		subch_demux_activate(&e1_ts->trau.demux, e1_link->e1_ts_ss);
+		break;
+	default:
+		break;
+	}
+
+	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(DINP, LOGL_ERROR, "TRX (%u/%u) RSL link without "
+		     "timeslot?\n", trx->bts->nr, trx->nr);
+		return -EINVAL;
+	}
+
+	/* RSL Link */
+	line = e1inp_line_get(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DINP, 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_ts, line, E1INP_TS_TYPE_SIGN);
+	/* 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(DINP, 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(DINP, 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;
+}
+
+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(DMI, "e1_reconfig_bts(%u)\n", bts->nr);
+
+	if (!e1_link->e1_ts) {
+		LOGP(DINP, LOGL_ERROR, "BTS %u OML link without timeslot?\n",
+		     bts->nr);
+		return -EINVAL;
+	}
+
+	/* OML link */
+	line = e1inp_line_get(e1_link->e1_nr);
+	if (!line) {
+		LOGP(DINP, LOGL_ERROR, "BTS %u OML link referring to "
+		     "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr);
+		return -ENOMEM;
+	}
+	sign_ts = &line->ts[e1_link->e1_ts-1];
+	e1inp_ts_config(sign_ts, line, E1INP_TS_TYPE_SIGN);
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  bts->c0, bts->oml_tei, SAPI_OML);
+	if (!oml_link) {
+		LOGP(DINP, 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
+
+/* configure pseudo E1 line in ip.access style and connect to BTS */
+int ia_config_connect(struct gsm_bts *bts, struct sockaddr_in *sin)
+{
+	struct e1inp_line *line;
+	struct e1inp_ts *sign_ts, *rsl_ts;
+	struct e1inp_sign_link *oml_link, *rsl_link;
+
+	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[1-1], line, E1INP_TS_TYPE_SIGN);
+	e1inp_ts_config(&line->ts[2-1], line, E1INP_TS_TYPE_SIGN);
+
+	/* create signalling links for TS1 */
+	sign_ts = &line->ts[1-1];
+	rsl_ts = &line->ts[2-1];
+	oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML,
+					  bts->c0, 0xff, 0);
+	rsl_link = e1inp_sign_link_create(rsl_ts, E1INP_SIGN_RSL,
+					  bts->c0, 0, 0);
+
+	/* create back-links from bts/trx */
+	bts->oml_link = oml_link;
+	bts->c0->rsl_link = rsl_link;
+
+	/* default port at BTS for incoming connections is 3006 */
+	if (sin->sin_port == 0)
+		sin->sin_port = htons(3006);
+
+	return ipaccess_connect(line, sin);
+}
diff --git a/src/libbsc/gsm_04_08_utils.c b/src/libbsc/gsm_04_08_utils.c
new file mode 100644
index 0000000..6d12cc0
--- /dev/null
+++ b/src/libbsc/gsm_04_08_utils.c
@@ -0,0 +1,655 @@
+/* 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 <osmocore/msgb.h>
+#include <osmocore/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>
+
+/* 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->trx = msg->lchan->ts->trx;
+
+	msg->l3h = msg->data;
+	return rsl_data_request(msg, 0);
+}
+
+/* Section 9.1.8 / Table 9.9 */
+struct chreq {
+	u_int8_t val;
+	u_int8_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 },
+	{ 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 },
+	{ 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_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_RESERVED_SDCCH]	= GSM_CHREQ_REASON_OTHER,
+	[CHREQ_T_RESERVED_IGNORE]	= GSM_CHREQ_REASON_OTHER,
+};
+
+/* verify that the two tables match */
+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, u_int8_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;
+}
+
+enum gsm_chreq_reason_t get_reason_by_chreq(u_int8_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;
+}
+
+/* 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();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	u_int8_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 */
+	gsm48_sendmsg(msg);
+	/* FIXME: Start Timer T3109 */
+
+	/* Deactivate the SACCH on the BTS side */
+	return rsl_deact_sacch(lchan);
+}
+
+int send_siemens_mrpci(struct gsm_lchan *lchan,
+		       u_int8_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;
+
+	u_int8_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, u_int8_t *mi_type)
+{
+	static const uint32_t classmark_offset =
+		offsetof(struct gsm48_pag_resp, classmark2);
+	u_int8_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 gsm_subscriber *subscr)
+{
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t *classmark2_lv = gh->data + 1;
+
+	if (is_siemens_bts(bts))
+		send_siemens_mrpci(msg->lchan, classmark2_lv);
+
+	if (!conn->subscr) {
+		conn->subscr = subscr;
+	} else if (conn->subscr != subscr) {
+		LOGP(DRR, LOGL_ERROR, "<- Channel already owned by someone else?\n");
+		subscr_put(subscr);
+		return -EINVAL;
+	} else {
+		DEBUGP(DRR, "<- Channel already owned by us\n");
+		subscr_put(subscr);
+		subscr = conn->subscr;
+	}
+
+	counter_inc(bts->network->stats.paging.completed);
+
+	/* Stop paging on the bts we received the paging response */
+	paging_request_stop(conn->bts, subscr, 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();
+	struct gsm48_hdr *gh;
+	u_int8_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)
+{
+	u_int16_t arfcn = lchan->ts->trx->arfcn & 0x3ff;
+
+	cd->chan_nr = lchan2chan_nr(lchan);
+	if (!lchan->ts->hopping.enabled) {
+		cd->h0.tsc = lchan->ts->trx->bts->tsc;
+		cd->h0.h = 0;
+		cd->h0.arfcn_high = arfcn >> 8;
+		cd->h0.arfcn_low = arfcn & 0xff;
+	} else {
+		cd->h1.tsc = lchan->ts->trx->bts->tsc;
+		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;
+	}
+}
+
+#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,
+		      u_int8_t power_command, u_int8_t ho_ref)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	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, u_int8_t power_command)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	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_tx_chan_mode_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 */
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+		if (lchan->mr_conf.ver == 0) {
+			LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec "
+				"without multirate config.\n");
+		} else {
+			u_int8_t *data = msgb_put(msg, 4);
+			data[0] = GSM48_IE_MUL_RATE_CFG;
+			data[1] = 0x2;
+			memcpy(&data[2], &lchan->mr_conf, 2);
+		}
+	}
+
+	return gsm48_sendmsg(msg);
+}
+
+/* 9.1.5 Channel mode modify: Modify the mode on the MS side */
+int gsm48_tx_chan_mode_modify(struct gsm_lchan *lchan, u_int8_t mode)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	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 */
+	if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+		if (lchan->mr_conf.ver == 0) {
+			LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec "
+				"without multirate config.\n");
+		} else {
+			u_int8_t *data = msgb_put(msg, 4);
+			data[0] = GSM48_IE_MUL_RATE_CFG;
+			data[1] = 0x2;
+			memcpy(&data[2], &lchan->mr_conf, 2);
+		}
+	}
+
+	return gsm48_sendmsg(msg);
+}
+
+int gsm48_lchan_modify(struct gsm_lchan *lchan, u_int8_t lchan_mode)
+{
+	int rc;
+
+	rc = gsm48_tx_chan_mode_modify(lchan, lchan_mode);
+	if (rc < 0)
+		return rc;
+
+	return rc;
+}
+
+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);
+	u_int8_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[3] >> 4) & 0x7;
+	rep->dl.sub.rx_qual = (data[3] >> 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;
+}
+
+struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value)
+{
+	struct msgb *msg;
+	struct gsm48_hdr *gh;
+
+	msg = gsm48_msgb_alloc();
+	if (!msg)
+		return NULL;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_CM_SERV_REJ;
+	gh->data[0] = value;
+
+	return msg;
+}
+
+struct msgb *gsm48_create_loc_upd_rej(uint8_t cause)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *msg;
+
+	msg = gsm48_msgb_alloc();
+	if (!msg)
+		return NULL;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_LOC_UPD_REJECT;
+	gh->data[0] = cause;
+	return msg;
+}
diff --git a/src/libbsc/gsm_subscriber_base.c b/src/libbsc/gsm_subscriber_base.c
new file mode 100644
index 0000000..caf84e7
--- /dev/null
+++ b/src/libbsc/gsm_subscriber_base.c
@@ -0,0 +1,149 @@
+/* The concept of a subscriber as seen by the BSC */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-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 <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+LLIST_HEAD(active_subscribers);
+void *tall_subscr_ctx;
+
+/* for the gsm_subscriber.c */
+struct llist_head *subscr_bsc_active_subscriber(void)
+{
+	return &active_subscribers;
+}
+
+
+char *subscr_name(struct gsm_subscriber *subscr)
+{
+	if (strlen(subscr->name))
+		return subscr->name;
+
+	return subscr->imsi;
+}
+
+struct gsm_subscriber *subscr_alloc(void)
+{
+	struct gsm_subscriber *s;
+
+	s = talloc_zero(tall_subscr_ctx, struct gsm_subscriber);
+	if (!s)
+		return NULL;
+
+	llist_add_tail(&s->entry, &active_subscribers);
+	s->use_count = 1;
+	s->tmsi = GSM_RESERVED_TMSI;
+
+	INIT_LLIST_HEAD(&s->requests);
+
+	return s;
+}
+
+static void subscr_free(struct gsm_subscriber *subscr)
+{
+	llist_del(&subscr->entry);
+	talloc_free(subscr);
+}
+
+struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr)
+{
+	subscr->use_count++;
+	DEBUGP(DREF, "subscr %s usage increases usage to: %d\n",
+			subscr->extension, subscr->use_count);
+	return subscr;
+}
+
+struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr)
+{
+	subscr->use_count--;
+	DEBUGP(DREF, "subscr %s usage decreased usage to: %d\n",
+			subscr->extension, subscr->use_count);
+	if (subscr->use_count <= 0 && !subscr->net->keep_subscr)
+		subscr_free(subscr);
+	return NULL;
+}
+
+struct gsm_subscriber *subscr_get_or_create(struct gsm_network *net,
+					    const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0 && subscr->net == net)
+			return subscr_get(subscr);
+	}
+
+	subscr = subscr_alloc();
+	if (!subscr)
+		return NULL;
+
+	strcpy(subscr->imsi, imsi);
+	subscr->net = net;
+	return subscr;
+}
+
+struct gsm_subscriber *subscr_active_by_tmsi(struct gsm_network *net, uint32_t tmsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (subscr->tmsi == tmsi && subscr->net == net)
+			return subscr_get(subscr);
+	}
+
+	return NULL;
+}
+
+struct gsm_subscriber *subscr_active_by_imsi(struct gsm_network *net, const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0 && subscr->net == net)
+			return subscr_get(subscr);
+	}
+
+	return NULL;
+}
+
+int subscr_purge_inactive(struct gsm_network *net)
+{
+	struct gsm_subscriber *subscr, *tmp;
+	int purged = 0;
+
+	llist_for_each_entry_safe(subscr, tmp, subscr_bsc_active_subscriber(), entry) {
+		if (subscr->net == net && subscr->use_count <= 0) {
+			subscr_free(subscr);
+			purged += 1;
+		}
+	}
+
+	return purged;
+}
diff --git a/src/libbsc/handover_decision.c b/src/libbsc/handover_decision.c
new file mode 100644
index 0000000..d3f843a
--- /dev/null
+++ b/src/libbsc/handover_decision.c
@@ -0,0 +1,297 @@
+/* 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 <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/meas_rep.h>
+#include <openbsc/signal.h>
+#include <osmocore/talloc.h>
+#include <openbsc/handover.h>
+#include <osmocore/gsm_utils.h>
+
+/* issue handover to a cell identified by ARFCN and BSIC */
+static int handover_to_arfcn_bsic(struct gsm_lchan *lchan,
+				  u_int16_t arfcn, u_int8_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,
+				 u_int16_t arfcn, u_int8_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;
+
+	/* 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 (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;
+	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;
+	}
+
+	/* 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, MEAS_REP_DL_RXLEV_FULL,
+				    net->handover.win_rxlev_avg);
+
+	/* Interference HO */
+	if (rxlev2dbm(av_rxlev) > -85 &&
+	    meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL,
+				   3, 4, 5))
+		return attempt_handover(mr);
+
+	/* Bad Quality */
+	if (meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL,
+				   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)
+{
+	register_signal_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 0000000..c2e3f8c
--- /dev/null
+++ b/src/libbsc/handover_logic.c
@@ -0,0 +1,393 @@
+/* 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 <osmocore/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocore/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 <osmocore/talloc.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+struct bsc_handover {
+	struct llist_head list;
+
+	struct gsm_lchan *old_lchan;
+	struct gsm_lchan *new_lchan;
+
+	struct timer_list T3103;
+
+	u_int8_t ho_ref;
+};
+
+static LLIST_HEAD(bsc_handovers);
+
+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;
+}
+
+/* Hand over the specified logical channel to the specified new BTS.
+ * This is the main entry point for the actual handover algorithm,
+ * after it has decided it wants 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 u_int8_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);
+
+	counter_inc(bts->network->stats.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");
+		counter_inc(bts->network->stats.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;
+
+	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, 0,
+				     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, 1);
+
+	bsc_del_timer(&ho->T3103);
+	llist_del(&ho->list);
+	talloc_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");
+	counter_inc(net->stats.handover.timeout);
+
+	ho->new_lchan->conn->ho_lchan = NULL;
+	ho->new_lchan->conn = NULL;
+	lchan_release(ho->new_lchan, 0, 1);
+	llist_del(&ho->list);
+	talloc_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;
+	int rc;
+
+	/* 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 */
+
+	rc = 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 */
+	ho->T3103.cb = ho_T3103_cb;
+	ho->T3103.data = ho;
+	bsc_schedule_timer(&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_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	new_lchan->conn->ho_lchan = NULL;
+	new_lchan->conn = NULL;
+	llist_del(&ho->list);
+	talloc_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);
+
+	counter_inc(net->stats.handover.completed);
+
+	bsc_del_timer(&ho->T3103);
+
+	/* 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;
+
+	rsl_lchan_set_state(ho->old_lchan, LCHAN_S_INACTIVE);
+	lchan_release(ho->old_lchan, 0, 1);
+
+	/* do something to re-route the actual speech frames ! */
+
+	llist_del(&ho->list);
+	talloc_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;
+
+	ho = bsc_ho_by_old_lchan(old_lchan);
+	if (!ho) {
+		LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+		return -ENODEV;
+	}
+
+	counter_inc(net->stats.handover.failed);
+
+	bsc_del_timer(&ho->T3103);
+	llist_del(&ho->list);
+
+	/* release the channel and forget about it */
+	ho->new_lchan->conn->ho_lchan = NULL;
+	ho->new_lchan->conn = NULL;
+	lchan_release(ho->new_lchan, 0, 1);
+
+	talloc_free(ho);
+
+	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_ipac_crcx_ack(struct gsm_lchan *new_lchan)
+{
+	struct bsc_handover *ho;
+	struct ho_signal_data sig_ho;
+
+	ho = bsc_ho_by_new_lchan(new_lchan);
+	if (!ho) {
+		/* it is perfectly normal, we have CRCX even in non-HO cases */
+		return 0;
+	}
+
+	sig_ho.old_lchan = ho->old_lchan;
+	sig_ho.new_lchan = new_lchan;
+	dispatch_signal(SS_HO, S_HANDOVER_ACK, &sig_ho);
+	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;
+	case SS_ABISIP:
+		lchan = signal_data;
+		switch (signal) {
+		case S_ABISIP_CRCX_ACK:
+			return ho_ipac_crcx_ack(lchan);
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_ho_logic(void)
+{
+	register_signal_handler(SS_LCHAN, ho_logic_sig_cb, NULL);
+	register_signal_handler(SS_ABISIP, ho_logic_sig_cb, NULL);
+}
diff --git a/src/libbsc/meas_rep.c b/src/libbsc/meas_rep.c
new file mode 100644
index 0000000..788a9ba
--- /dev/null
+++ b/src/libbsc/meas_rep.c
@@ -0,0 +1,116 @@
+/* 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 <sys/types.h>
+
+#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/paging.c b/src/libbsc/paging.c
new file mode 100644
index 0000000..6502545
--- /dev/null
+++ b/src/libbsc/paging.c
@@ -0,0 +1,395 @@
+/* Paging helper and manager.... */
+/* (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/>.
+ *
+ */
+
+/*
+ * 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 <openbsc/paging.h>
+#include <osmocore/talloc.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
+
+static unsigned int calculate_group(struct gsm_bts *bts, struct gsm_subscriber *subscr)
+{
+	int ccch_conf;
+	int bs_cc_chans;
+	int blocks;
+	unsigned int group;
+	
+	ccch_conf = bts->si_common.chan_desc.ccch_conf;
+	bs_cc_chans = rsl_ccch_conf_to_bs_cc_chans(ccch_conf);
+	/* code word + 2, as 2 channels equals 0x0 */
+	blocks = rsl_number_of_paging_subchannels(bts);
+	group = get_paging_group(str_to_imsi(subscr->imsi),
+					bs_cc_chans, blocks);
+	return group;
+}
+
+/*
+ * 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)
+{
+	bsc_del_timer(&to_be_deleted->T3113);
+	llist_del(&to_be_deleted->entry);
+	subscr_put(to_be_deleted->subscr);
+	talloc_free(to_be_deleted);
+}
+
+static void page_ms(struct gsm_paging_request *request)
+{
+	u_int8_t mi[128];
+	unsigned int mi_len;
+	unsigned int page_group;
+
+	LOGP(DPAG, LOGL_INFO, "Going to send paging commands: imsi: '%s' tmsi: '0x%x'\n",
+		request->subscr->imsi, request->subscr->tmsi);
+
+	if (request->subscr->tmsi == GSM_RESERVED_TMSI)
+		mi_len = gsm48_generate_mid_from_imsi(mi, request->subscr->imsi);
+	else
+		mi_len = gsm48_generate_mid_from_tmsi(mi, request->subscr->tmsi);
+
+	page_group = calculate_group(request->bts, request->subscr);
+	gsm0808_page(request->bts, page_group, mi_len, mi, request->chan_type);
+}
+
+static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts)
+{
+	if (llist_empty(&paging_bts->pending_requests))
+		return;
+
+	if (!bsc_timer_pending(&paging_bts->work_timer))
+		bsc_schedule_timer(&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) {
+		paging_bts->credit_timer.cb = paging_give_credit;
+		paging_bts->credit_timer.data = paging_bts;
+		bsc_schedule_timer(&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:
+	bsc_schedule_timer(&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);
+}
+
+void paging_init(struct gsm_bts *bts)
+{
+	bts->paging.bts = bts;
+	INIT_LLIST_HEAD(&bts->paging.pending_requests);
+	bts->paging.work_timer.cb = paging_worker;
+	bts->paging.work_timer.data = &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 gsm_subscriber *subscr) {
+	struct gsm_paging_request *req;
+
+	llist_for_each_entry(req, &bts->pending_requests, entry) {
+		if (subscr == req->subscr)
+			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;
+
+	LOGP(DPAG, LOGL_INFO, "T3113 expired for request %p (%s)\n",
+		req, req->subscr->imsi);
+
+	/* must be destroyed before calling cbfn, to prevent double free */
+	counter_inc(req->bts->network->stats.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 gsm_subscriber *subscr,
+			    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, subscr)) {
+		LOGP(DPAG, LOGL_INFO, "Paging request already pending for %s\n", subscr->imsi);
+		return -EEXIST;
+	}
+
+	LOGP(DPAG, LOGL_DEBUG, "Start paging of subscriber %llu on bts %d.\n",
+		subscr->id, bts->nr);
+	req = talloc_zero(tall_paging_ctx, struct gsm_paging_request);
+	req->subscr = subscr_get(subscr);
+	req->bts = bts;
+	req->chan_type = type;
+	req->cbfn = cbfn;
+	req->cbfn_param = data;
+	req->T3113.cb = paging_T3113_expired;
+	req->T3113.data = req;
+	bsc_schedule_timer(&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(struct gsm_network *network, struct gsm_subscriber *subscr,
+		   int type, gsm_cbfn *cbfn, void *data)
+{
+	struct gsm_bts *bts = NULL;
+	int num_pages = 0;
+
+	counter_inc(network->stats.paging.attempted);
+
+	/* start paging subscriber on all BTS within Location Area */
+	do {
+		int rc;
+
+		bts = gsm_bts_by_lac(network, subscr->lac, bts);
+		if (!bts)
+			break;
+
+		/* skip all currently inactive TRX */
+		if (!trx_is_usable(bts->c0))
+			continue;
+
+		num_pages++;
+
+		/* Trigger paging, pass any error to caller */
+		rc = _paging_request(bts, subscr, type, cbfn, data);
+		if (rc < 0)
+			return rc;
+	} while (1);
+
+	if (num_pages == 0)
+		counter_inc(network->stats.paging.detached);
+
+	return num_pages;
+}
+
+
+/* we consciously ignore the type of the request here */
+static void _paging_request_stop(struct gsm_bts *bts, struct gsm_subscriber *subscr,
+				 struct gsm_subscriber_connection *conn,
+				 struct msgb *msg)
+{
+	struct gsm_bts_paging_state *bts_entry = &bts->paging;
+	struct gsm_paging_request *req, *req2;
+
+	llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests,
+				 entry) {
+		if (req->subscr == subscr) {
+			if (conn && req->cbfn) {
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d, calling cbfn.\n", bts->nr);
+				req->cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
+					  msg, conn, req->cbfn_param);
+			} else
+				LOGP(DPAG, LOGL_DEBUG, "Stop paging on bts %d silently.\n", bts->nr);
+			paging_remove_request(&bts->paging, req);
+			break;
+		}
+	}
+}
+
+/* Stop paging on all other bts' */
+void paging_request_stop(struct gsm_bts *_bts, struct gsm_subscriber *subscr,
+			 struct gsm_subscriber_connection *conn,
+			 struct msgb *msg)
+{
+	struct gsm_bts *bts = NULL;
+
+	if (_bts)
+		_paging_request_stop(_bts, subscr, conn, msg);
+
+	do {
+		/*
+		 * FIXME: Don't use the lac of the subscriber...
+		 * as it might have magically changed the lac.. use the
+		 * location area of the _bts as reconfiguration of the
+		 * network is probably happening less often.
+		 */
+		bts = gsm_bts_by_lac(subscr->net, subscr->lac, bts);
+		if (!bts)
+			break;
+
+		/* Stop paging */
+		if (bts != _bts)
+			_paging_request_stop(bts, subscr, NULL, NULL);
+	} while (1);
+}
+
+void paging_update_buffer_space(struct gsm_bts *bts, u_int16_t free_slots)
+{
+	bsc_del_timer(&bts->paging.credit_timer);
+	bts->paging.available_slots = free_slots;
+	paging_schedule_if_needed(&bts->paging);
+}
diff --git a/src/libbsc/rest_octets.c b/src/libbsc/rest_octets.c
new file mode 100644
index 0000000..084f144
--- /dev/null
+++ b/src/libbsc/rest_octets.c
@@ -0,0 +1,432 @@
+/* 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 <openbsc/gsm_data.h>
+#include <osmocore/bitvec.h>
+#include <openbsc/rest_octets.h>
+
+/* generate SI1 rest octets */
+int rest_octets_si1(u_int8_t *data, u_int8_t *nch_pos)
+{
+	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);
+
+	bitvec_spare_padding(&bv, 7);
+	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(u_int8_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);
+
+	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(u_int8_t *data, const struct gsm48_si_ro_info *si4)
+{
+	struct bitvec bv;
+
+	memset(&bv, 0, sizeof(bv));
+	bv.data = data;
+	bv.data_len = 10; /* FIXME: up to ? */
+
+	/* 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;
+}
+
+/* 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)
+{
+	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);
+	bitvec_set_uint(bv, gco->t3168 / 500, 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: Hard-code to RLC/MAC control block */
+	bitvec_set_bit(bv, 1);
+	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 */
+			bitvec_set_bit(bv, gco->ext_info.use_egprs_p_ch_req);
+			/* 4bit BEP PERIOD */
+			bitvec_set_uint(bv, gco->ext_info.bep_period, 4);
+		}
+		bitvec_set_bit(bv, gco->ext_info.pfc_supported);
+		bitvec_set_bit(bv, gco->ext_info.dtm_supported);
+		bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination);
+	}
+
+	return 0;
+}
+
+static void append_gprs_pwr_ctrl_pars(struct bitvec *bv,
+				      const struct gprs_power_ctrl_pars *pcp)
+{
+	bitvec_set_uint(bv, pcp->alpha, 4);
+	bitvec_set_uint(bv, pcp->t_avg_w, 5);
+	bitvec_set_uint(bv, pcp->t_avg_t, 5);
+	bitvec_set_uint(bv, pcp->pc_meas_chan, 1);
+	bitvec_set_uint(bv, pcp->n_avg_i, 4);
+}
+
+/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */
+int rest_octets_si13(u_int8_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 0000000..dc71938
--- /dev/null
+++ b/src/libbsc/system_information.c
@@ -0,0 +1,616 @@
+/* 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>
+ *
+ * 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 <sys/types.h>
+#include <netinet/in.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/rest_octets.h>
+#include <osmocore/bitvec.h>
+#include <osmocore/utils.h>
+#include <openbsc/debug.h>
+
+#define GSM48_CELL_CHAN_DESC_SIZE	16
+#define GSM_MACBLOCK_PADDING		0x2b
+
+/* verify the sizes of the system information type structs */
+
+/* rest octets are not part of the struct */
+static_assert(sizeof(struct gsm48_system_information_type_header) == 3, _si_header_size);
+static_assert(sizeof(struct gsm48_rach_control) == 3, _si_rach_control);
+static_assert(sizeof(struct gsm48_system_information_type_1) == 22, _si1_size);
+static_assert(sizeof(struct gsm48_system_information_type_2) == 23, _si2_size);
+static_assert(sizeof(struct gsm48_system_information_type_3) == 19, _si3_size);
+static_assert(sizeof(struct gsm48_system_information_type_4) == 13, _si4_size);
+
+/* bs11 forgot the l2 len, 0-6 rest octets */
+static_assert(sizeof(struct gsm48_system_information_type_5) == 18, _si5_size);
+static_assert(sizeof(struct gsm48_system_information_type_6) == 11, _si6_size);
+
+static_assert(sizeof(struct gsm48_system_information_type_13) == 3, _si13_size);
+
+/* 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(u_int8_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(u_int8_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) {
+		LOGP(DRR, LOGL_ERROR, "arfcn(%u) < min(%u)\n", arfcn, min_arfcn);
+		return -EINVAL;
+	}
+	if (arfcn > min_arfcn + 111) {
+		LOGP(DRR, LOGL_ERROR, "arfcn(%u) > min(%u) + 111\n", arfcn, min_arfcn);
+		return -EINVAL;
+	}
+
+	bitno = (arfcn - min_arfcn);
+	byte = bitno / 8;
+	bit = bitno % 8;
+
+	chan_list[2 + byte] |= 1 << (7 - bit);
+
+	return 0;
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int bitvec2freq_list(u_int8_t *chan_list, struct bitvec *bv,
+			    const struct gsm_bts *bts)
+{
+	int i, rc, min = 1024, max = -1;
+
+	memset(chan_list, 0, 16);
+
+	/* GSM900-only handsets only support 'bit map 0 format' */
+	if (bts->band == GSM_BAND_900) {
+		chan_list[0] = 0;
+
+		for (i = 0; i < bv->data_len*8; i++) {
+			if (bitvec_get_bit_pos(bv, i)) {
+				rc = freq_list_bm0_set_arfcn(chan_list, i);
+				if (rc < 0)
+					return rc;
+			}
+		}
+		return 0;
+	}
+
+	/* We currently only support the 'Variable bitmap format' */
+	chan_list[0] = 0x8e;
+
+	for (i = 0; i < bv->data_len*8; i++) {
+		if (bitvec_get_bit_pos(bv, i)) {
+			if (i < min)
+				min = i;
+			if (i > max)
+				max = i;
+		}
+	}
+
+	if (max == -1) {
+		/* Empty set, use 'bit map 0 format' */
+		chan_list[0] = 0;
+		return 0;
+	}
+
+	if ((max - min) > 111) {
+		LOGP(DRR, LOGL_ERROR, "min_arfcn=%u, max_arfcn=%u, "
+			"distance > 111\n", min, max);
+		return -EINVAL;
+	}
+
+	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++) {
+		if (bitvec_get_bit_pos(bv, i)) {
+			rc = freq_list_bmrel_set_arfcn(chan_list, i);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+	return 0;
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int generate_cell_chan_list(u_int8_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);
+}
+
+/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */
+static int generate_bcch_chan_list(u_int8_t *chan_list, struct gsm_bts *bts, int si5)
+{
+	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);
+}
+
+static int generate_si1(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_1 *si1 =
+		(struct gsm48_system_information_type_1 *) output;
+
+	memset(si1, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si1->header.l2_plen = (21 << 2) | 1;
+	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;
+
+	si1->rach_control = bts->si_common.rach_control;
+
+	/* SI1 Rest Octets (10.5.2.32), contains NCH position */
+	rc = rest_octets_si1(si1->rest_octets, NULL);
+
+	return sizeof(*si1) + rc;
+}
+
+static int generate_si2(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_2 *si2 =
+		(struct gsm48_system_information_type_2 *) output;
+
+	memset(si2, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si2->header.l2_plen = (22 << 2) | 1;
+	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, 0);
+	if (rc < 0)
+		return rc;
+
+	si2->ncc_permitted = bts->si_common.ncc_permitted;
+	si2->rach_control = bts->si_common.rach_control;
+
+	return sizeof(*si2);
+}
+
+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,
+	},
+	.lsa_params = {
+		.present = 0,
+	},
+	.cell_id = 0,	/* FIXME: doesn't the bts have this? */
+	.break_ind = 0,
+};
+
+static int generate_si3(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_3 *si3 =
+		(struct gsm48_system_information_type_3 *) output;
+
+	memset(si3, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+
+	si3->header.l2_plen = (18 << 2) | 1;
+	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;
+
+	/* 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(u_int8_t *output, struct gsm_bts *bts)
+{
+	int rc;
+	struct gsm48_system_information_type_4 *si4 =
+		(struct gsm48_system_information_type_4 *) output;
+
+	/* 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 */
+
+	si4->header.l2_plen = (l2_plen << 2) | 1;
+
+	/* SI4 Rest Octets (10.5.2.35), containing
+		Optional Power offset, GPRS Indicator,
+		Cell Identity, LSA ID, Selection Parameter */
+	rc = rest_octets_si4(si4->data, &si_info);
+
+	return sizeof(*si4) + rc;
+}
+
+static int generate_si5(u_int8_t *output, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_5 *si5;
+	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_HSL_FEMTO:
+		*output++ = (l2_plen << 2) | 1;
+		l2_plen++;
+		break;
+	default:
+		break;
+	}
+
+	si5 = (struct gsm48_system_information_type_5 *) output;
+
+	/* 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, 1);
+	if (rc < 0)
+		return rc;
+
+	/* 04.08 9.1.37: L2 Pseudo Length of 18 */
+	return l2_plen;
+}
+
+static int generate_si6(u_int8_t *output, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_6 *si6;
+	int l2_plen = 11;
+
+	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_HSL_FEMTO:
+		*output++ = (l2_plen << 2) | 1;
+		l2_plen++;
+		break;
+	default:
+		break;
+	}
+
+	si6 = (struct gsm48_system_information_type_6 *) output;
+
+	/* 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;
+
+	/* SI6 Rest Octets: 10.5.2.35a: PCH / NCH info, VBS/VGCS options */
+
+	return l2_plen;
+}
+
+static struct gsm48_si13_info si13_default = {
+	.cell_opts = {
+		.nmo 		= GPRS_NMO_II,
+		.t3168		= 2000,
+		.t3192		= 200,
+		.drx_timer_max	= 3,
+		.bs_cv_max	= 15,
+		.ext_info_present = 1,
+		.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		= 10,	/* a = 1.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(u_int8_t *output, struct gsm_bts *bts)
+{
+	struct gsm48_system_information_type_13 *si13 =
+		(struct gsm48_system_information_type_13 *) output;
+	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;
+
+	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;
+}
+
+static const uint8_t sitype2rsl[_MAX_SYSINFO_TYPE] = {
+	[SYSINFO_TYPE_1]	= RSL_SYSTEM_INFO_1,
+	[SYSINFO_TYPE_2]	= RSL_SYSTEM_INFO_2,
+	[SYSINFO_TYPE_3]	= RSL_SYSTEM_INFO_3,
+	[SYSINFO_TYPE_4]	= RSL_SYSTEM_INFO_4,
+	[SYSINFO_TYPE_5]	= RSL_SYSTEM_INFO_5,
+	[SYSINFO_TYPE_6]	= RSL_SYSTEM_INFO_6,
+	[SYSINFO_TYPE_7]	= RSL_SYSTEM_INFO_7,
+	[SYSINFO_TYPE_8]	= RSL_SYSTEM_INFO_8,
+	[SYSINFO_TYPE_9]	= RSL_SYSTEM_INFO_9,
+	[SYSINFO_TYPE_10]	= RSL_SYSTEM_INFO_10,
+	[SYSINFO_TYPE_13]	= RSL_SYSTEM_INFO_13,
+	[SYSINFO_TYPE_16]	= RSL_SYSTEM_INFO_16,
+	[SYSINFO_TYPE_17]	= RSL_SYSTEM_INFO_17,
+	[SYSINFO_TYPE_18]	= RSL_SYSTEM_INFO_18,
+	[SYSINFO_TYPE_19]	= RSL_SYSTEM_INFO_19,
+	[SYSINFO_TYPE_20]	= RSL_SYSTEM_INFO_20,
+	[SYSINFO_TYPE_2bis]	= RSL_SYSTEM_INFO_2bis,
+	[SYSINFO_TYPE_2ter]	= RSL_SYSTEM_INFO_2ter,
+	[SYSINFO_TYPE_2quater]	= RSL_SYSTEM_INFO_2quater,
+	[SYSINFO_TYPE_5bis]	= RSL_SYSTEM_INFO_5bis,
+	[SYSINFO_TYPE_5ter]	= RSL_SYSTEM_INFO_5ter,
+};
+
+static const uint8_t rsl2sitype[0xff] = {
+	[RSL_SYSTEM_INFO_1] = SYSINFO_TYPE_1,
+	[RSL_SYSTEM_INFO_2] = SYSINFO_TYPE_2,
+	[RSL_SYSTEM_INFO_3] = SYSINFO_TYPE_3,
+	[RSL_SYSTEM_INFO_4] = SYSINFO_TYPE_4,
+	[RSL_SYSTEM_INFO_5] = SYSINFO_TYPE_5,
+	[RSL_SYSTEM_INFO_6] = SYSINFO_TYPE_6,
+	[RSL_SYSTEM_INFO_7] = SYSINFO_TYPE_7,
+	[RSL_SYSTEM_INFO_8] = SYSINFO_TYPE_8,
+	[RSL_SYSTEM_INFO_9] = SYSINFO_TYPE_9,
+	[RSL_SYSTEM_INFO_10] = SYSINFO_TYPE_10,
+	[RSL_SYSTEM_INFO_13] = SYSINFO_TYPE_13,
+	[RSL_SYSTEM_INFO_16] = SYSINFO_TYPE_16,
+	[RSL_SYSTEM_INFO_17] = SYSINFO_TYPE_17,
+	[RSL_SYSTEM_INFO_18] = SYSINFO_TYPE_18,
+	[RSL_SYSTEM_INFO_19] = SYSINFO_TYPE_19,
+	[RSL_SYSTEM_INFO_20] = SYSINFO_TYPE_20,
+	[RSL_SYSTEM_INFO_2bis] = SYSINFO_TYPE_2bis,
+	[RSL_SYSTEM_INFO_2ter] = SYSINFO_TYPE_2ter,
+	[RSL_SYSTEM_INFO_2quater] = SYSINFO_TYPE_2quater,
+	[RSL_SYSTEM_INFO_5bis] = SYSINFO_TYPE_5bis,
+	[RSL_SYSTEM_INFO_5ter] = SYSINFO_TYPE_5ter,
+};
+
+typedef int (*gen_si_fn_t)(uint8_t *output, 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_3] = &generate_si3,
+	[SYSINFO_TYPE_4] = &generate_si4,
+	[SYSINFO_TYPE_5] = &generate_si5,
+	[SYSINFO_TYPE_6] = &generate_si6,
+	[SYSINFO_TYPE_13] = &generate_si13,
+};
+
+const struct value_string osmo_sitype_strs[_MAX_SYSINFO_TYPE] = {
+	{ SYSINFO_TYPE_1,	"1" },
+	{ SYSINFO_TYPE_2,	"2" },
+	{ SYSINFO_TYPE_3,	"3" },
+	{ SYSINFO_TYPE_4,	"4" },
+	{ SYSINFO_TYPE_5,	"5" },
+	{ SYSINFO_TYPE_6,	"6" },
+	{ SYSINFO_TYPE_7,	"7" },
+	{ SYSINFO_TYPE_8,	"8" },
+	{ SYSINFO_TYPE_9,	"9" },
+	{ SYSINFO_TYPE_10,	"10" },
+	{ SYSINFO_TYPE_13,	"13" },
+	{ SYSINFO_TYPE_16,	"16" },
+	{ SYSINFO_TYPE_17,	"17" },
+	{ SYSINFO_TYPE_18,	"18" },
+	{ SYSINFO_TYPE_19,	"19" },
+	{ SYSINFO_TYPE_20,	"20" },
+	{ SYSINFO_TYPE_2bis,	"2bis" },
+	{ SYSINFO_TYPE_2ter,	"2ter" },
+	{ SYSINFO_TYPE_2quater,	"2quater" },
+	{ SYSINFO_TYPE_5bis,	"5bis" },
+	{ SYSINFO_TYPE_5ter,	"5ter" },
+	{ 0, NULL }
+};
+
+uint8_t gsm_sitype2rsl(enum osmo_sysinfo_type si_type)
+{
+	return sitype2rsl[si_type];
+}
+
+const char *gsm_sitype_name(enum osmo_sysinfo_type si_type)
+{
+	return get_value_string(osmo_sitype_strs, si_type);
+}
+
+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(bts->si_buf[si_type], bts);
+}
diff --git a/src/libbsc/transaction.c b/src/libbsc/transaction.c
new file mode 100644
index 0000000..9b4af1a
--- /dev/null
+++ b/src/libbsc/transaction.c
@@ -0,0 +1,152 @@
+/* GSM 04.07 Transaction handling */
+
+/* (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/transaction.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/mncc.h>
+#include <openbsc/debug.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/mncc.h>
+#include <openbsc/paging.h>
+#include <openbsc/osmo_msc.h>
+
+void *tall_trans_ctx;
+
+void _gsm48_cc_trans_free(struct gsm_trans *trans);
+
+struct gsm_trans *trans_find_by_id(struct gsm_subscriber *subscr,
+				   u_int8_t proto, u_int8_t trans_id)
+{
+	struct gsm_trans *trans;
+	struct gsm_network *net = subscr->net;
+
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->subscr == subscr &&
+		    trans->protocol == proto &&
+		    trans->transaction_id == trans_id)
+			return trans;
+	}
+	return NULL;
+}
+
+struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
+					u_int32_t callref)
+{
+	struct gsm_trans *trans;
+
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->callref == callref)
+			return trans;
+	}
+	return NULL;
+}
+
+struct gsm_trans *trans_alloc(struct gsm_subscriber *subscr,
+			      u_int8_t protocol, u_int8_t trans_id,
+			      u_int32_t callref)
+{
+	struct gsm_trans *trans;
+
+	DEBUGP(DCC, "subscr=%p, subscr->net=%p\n", subscr, subscr->net);
+
+	trans = talloc_zero(tall_trans_ctx, struct gsm_trans);
+	if (!trans)
+		return NULL;
+
+	trans->subscr = subscr;
+	subscr_get(trans->subscr);
+
+	trans->protocol = protocol;
+	trans->transaction_id = trans_id;
+	trans->callref = callref;
+
+	llist_add_tail(&trans->entry, &subscr->net->trans_list);
+
+	return trans;
+}
+
+void trans_free(struct gsm_trans *trans)
+{
+	switch (trans->protocol) {
+	case GSM48_PDISC_CC:
+		_gsm48_cc_trans_free(trans);
+		break;
+	case GSM48_PDISC_SMS:
+		_gsm411_sms_trans_free(trans);
+		break;
+	}
+
+	/* FIXME: implement a sane way to stop this. */
+	if (!trans->conn && trans->paging_request) {
+		LOGP(DNM, LOGL_ERROR,
+		     "Transaction freed while paging for sub: %llu\n",
+		     trans->subscr->id);
+		trans->paging_request = NULL;
+	}
+
+	if (trans->subscr)
+		subscr_put(trans->subscr);
+
+	llist_del(&trans->entry);
+
+	if (trans->conn)
+		msc_release_connection(trans->conn);
+
+
+	talloc_free(trans);
+}
+
+/* allocate an unused transaction ID for the given subscriber
+ * in the given protocol using the ti_flag specified */
+int trans_assign_trans_id(struct gsm_subscriber *subscr,
+			  u_int8_t protocol, u_int8_t ti_flag)
+{
+	struct gsm_network *net = subscr->net;
+	struct gsm_trans *trans;
+	unsigned int used_tid_bitmask = 0;
+	int i, j, h;
+
+	if (ti_flag)
+		ti_flag = 0x8;
+
+	/* generate bitmask of already-used TIDs for this (subscr,proto) */
+	llist_for_each_entry(trans, &net->trans_list, entry) {
+		if (trans->subscr != subscr ||
+		    trans->protocol != protocol ||
+		    trans->transaction_id == 0xff)
+			continue;
+		used_tid_bitmask |= (1 << trans->transaction_id);
+	}
+
+	/* find a new one, trying to go in a 'circular' pattern */
+	for (h = 6; h > 0; h--)
+		if (used_tid_bitmask & (1 << (h | ti_flag)))
+			break;
+	for (i = 0; i < 7; i++) {
+		j = ((h + i) % 7) | ti_flag;
+		if ((used_tid_bitmask & (1 << j)) == 0)
+			return j;
+	}
+
+	return -1;
+}
+