[PATCH] osmo-bts[master]: VIRT-PHY: Initial check-in of a new virtual BTS

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

Harald Welte gerrit-no-reply at lists.osmocom.org
Thu Jul 13 19:34:24 UTC 2017


Hello Jenkins Builder,

I'd like you to reexamine a change.  Please visit

    https://gerrit.osmocom.org/3242

to look at the new patch set (#4).

VIRT-PHY: Initial check-in of a new virtual BTS

This patch adds a virtual physical layer designed to simulate the
Um air interface between BTS and MS.  It does so by encapsulating MAC
blocks (Layer 2 PDUs) via GSMTAP and sending them through multicast UDP
streams, both in uplink and in downlink.

The purpose of this is enable testing without any radio hardware or
related licenses.

OsmocomBB has recently received as similar patch-set, adding a virty_phy
executable that can be run on a PC instead of the classic 'layer1'
firmware on a real phone.

Using GSMTAP means that one can use unmodified wireshark to decode the
messages exchanged on the virtual Um layer.

This code was originally started by Harald in January 2016, continued
by Sebastian Stumpf in late 2016 and early 2017, and finally completed
by Harald in July 2017.

Change-Id: I1bf7670975b1e367c1c62983020865a043542622
---
M .gitignore
M configure.ac
M include/osmo-bts/gsm_data.h
M include/osmo-bts/phy_link.h
M src/Makefile.am
A src/osmo-bts-virtual/Makefile.am
A src/osmo-bts-virtual/bts_model.c
A src/osmo-bts-virtual/l1_if.c
A src/osmo-bts-virtual/l1_if.h
A src/osmo-bts-virtual/main.c
A src/osmo-bts-virtual/osmo_mcast_sock.c
A src/osmo-bts-virtual/osmo_mcast_sock.h
A src/osmo-bts-virtual/scheduler_virtbts.c
A src/osmo-bts-virtual/virtual_um.c
A src/osmo-bts-virtual/virtual_um.h
A src/osmo-bts-virtual/virtualbts_vty.c
16 files changed, 1,890 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.osmocom.org:29418/osmo-bts refs/changes/42/3242/4

diff --git a/.gitignore b/.gitignore
index 19ca274..a8c0ece 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,8 @@
 
 src/osmo-bts-octphy/osmo-bts-octphy
 
+src/osmo-bts-virtual/osmo-bts-virtual
+
 tests/atconfig
 tests/package.m4
 tests/agch/agch_test
diff --git a/configure.ac b/configure.ac
index 64231b3..0ceb8eb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -178,6 +178,7 @@
 AC_OUTPUT(
     src/Makefile
     src/common/Makefile
+    src/osmo-bts-virtual/Makefile
     src/osmo-bts-sysmo/Makefile
     src/osmo-bts-litecell15/Makefile
     src/osmo-bts-trx/Makefile
diff --git a/include/osmo-bts/gsm_data.h b/include/osmo-bts/gsm_data.h
index c513b27..aeac4b2 100644
--- a/include/osmo-bts/gsm_data.h
+++ b/include/osmo-bts/gsm_data.h
@@ -114,6 +114,12 @@
 	struct {
 		char *sock_path;
 	} pcu;
+
+	struct {
+		uint32_t last_fn;
+		struct timeval tv_clock;
+		struct osmo_timer_list fn_timer;
+	} vbts;
 };
 
 enum lchan_ciph_state {
diff --git a/include/osmo-bts/phy_link.h b/include/osmo-bts/phy_link.h
index 4c7ff34..d8d3c6b 100644
--- a/include/osmo-bts/phy_link.h
+++ b/include/osmo-bts/phy_link.h
@@ -10,11 +10,13 @@
 #include "btsconfig.h"
 
 struct gsm_bts_trx;
+struct virt_um_inst;
 
 enum phy_link_type {
 	PHY_LINK_T_NONE,
 	PHY_LINK_T_SYSMOBTS,
 	PHY_LINK_T_OSMOTRX,
+	PHY_LINK_T_VIRTUAL,
 };
 
 enum phy_link_state {
@@ -46,6 +48,14 @@
 			uint32_t clock_advance;
 			uint32_t rts_advance;
 		} osmotrx;
+		struct {
+			char *mcast_dev;		/* Network device for multicast */
+			char *bts_mcast_group;		/* BTS are listening to this group */
+			uint16_t bts_mcast_port;
+			char *ms_mcast_group;		/* MS are listening to this group */
+			uint16_t ms_mcast_port;
+			struct virt_um_inst *virt_um;
+		} virt;
 		struct {
 			/* MAC address of the PHY */
 			struct sockaddr_ll phy_addr;
@@ -99,6 +109,9 @@
 			bool sw_act_reported;
 		} osmotrx;
 		struct {
+			struct l1sched_trx sched;
+		} virt;
+		struct {
 			/* logical transceiver number within one PHY */
 			uint32_t trx_id;
 			/* trx lock state variable */
diff --git a/src/Makefile.am b/src/Makefile.am
index e7610fe..4f3f760 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = common
+SUBDIRS = common osmo-bts-virtual
 
 if ENABLE_SYSMOBTS
 SUBDIRS += osmo-bts-sysmo
diff --git a/src/osmo-bts-virtual/Makefile.am b/src/osmo-bts-virtual/Makefile.am
new file mode 100644
index 0000000..30069d4
--- /dev/null
+++ b/src/osmo-bts-virtual/Makefile.am
@@ -0,0 +1,10 @@
+AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS)
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) -Iinclude
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS)
+
+noinst_HEADERS = l1_if.h osmo_mcast_sock.h virtual_um.h
+
+bin_PROGRAMS = osmo-bts-virtual
+
+osmo_bts_virtual_SOURCES = main.c bts_model.c virtualbts_vty.c scheduler_virtbts.c l1_if.c virtual_um.c osmo_mcast_sock.c
+osmo_bts_virtual_LDADD = $(top_builddir)/src/common/libbts.a $(top_builddir)/src/common/libl1sched.a $(COMMON_LDADD)
diff --git a/src/osmo-bts-virtual/bts_model.c b/src/osmo-bts-virtual/bts_model.c
new file mode 100644
index 0000000..5293cc3
--- /dev/null
+++ b/src/osmo-bts-virtual/bts_model.c
@@ -0,0 +1,169 @@
+/* (C) 2015 by Harald Welte <laforge at 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/codec/codec.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+
+/* TODO: check if dummy method is sufficient, else implement */
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+	return -1;
+}
+
+/* TODO: check if dummy method is sufficient, else implement */
+int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr,
+		     int8_t *cmi, enum osmo_amr_type *ft, enum osmo_amr_quality *bfi, int8_t *sti)
+{
+	return -1;
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+	return 0;
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+	return 0;
+}
+
+int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
+			struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
+			void *obj)
+{
+	return 0;
+}
+
+static uint8_t vbts_set_bts(struct gsm_bts *bts)
+{
+	struct gsm_bts_trx *trx;
+	uint8_t tn;
+
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+		oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+
+		for (tn = 0; tn < TRX_NR_TS; tn++)
+			oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY);
+
+		/* report availability of trx to the bts. this will trigger the rsl connection */
+		oml_mo_tx_sw_act_rep(&trx->mo);
+		oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+	}
+	return 0;
+}
+
+static uint8_t vbts_set_trx(struct gsm_bts_trx *trx)
+{
+	return 0;
+}
+
+static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts)
+{
+	struct phy_instance *pinst = trx_phy_instance(ts->trx);
+	int rc;
+
+	rc = trx_sched_set_pchan(&pinst->u.virt.sched, ts->nr, ts->pchan);
+	if (rc)
+		return NM_NACK_RES_NOTAVAIL;
+
+	return 0;
+}
+
+int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg,
+			struct tlv_parsed *new_attr, int kind, void *obj)
+{
+	struct abis_om_fom_hdr *foh = msgb_l3(msg);
+	int cause = 0;
+
+	switch (foh->msg_type) {
+	case NM_MT_SET_BTS_ATTR:
+		cause = vbts_set_bts(obj);
+		break;
+	case NM_MT_SET_RADIO_ATTR:
+		cause = vbts_set_trx(obj);
+		break;
+	case NM_MT_SET_CHAN_ATTR:
+		cause = vbts_set_ts(obj);
+		break;
+	}
+	return oml_fom_ack_nack(msg, cause);
+}
+
+/* MO: TS 12.21 Managed Object */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj)
+{
+	int rc;
+
+	switch (mo->obj_class) {
+	case NM_OC_RADIO_CARRIER:
+	case NM_OC_CHANNEL:
+	case NM_OC_SITE_MANAGER:
+	case NM_OC_BASEB_TRANSC:
+	case NM_OC_BTS:
+		oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+		rc = oml_mo_opstart_ack(mo);
+		break;
+		/* TODO: gprs support */
+	case NM_OC_GPRS_NSE:
+	case NM_OC_GPRS_CELL:
+	case NM_OC_GPRS_NSVC:
+	default:
+		rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
+	}
+	return rc;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+			    void *obj, uint8_t adm_state)
+{
+	mo->nm_state.administrative = adm_state;
+	return oml_mo_statechg_ack(mo);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+	return 0;
+}
+
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+	return 0;
+}
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+	return 0;
+}
diff --git a/src/osmo-bts-virtual/l1_if.c b/src/osmo-bts-virtual/l1_if.c
new file mode 100644
index 0000000..a931916
--- /dev/null
+++ b/src/osmo-bts-virtual/l1_if.c
@@ -0,0 +1,465 @@
+/* Virtual BTS layer 1 primitive handling and interface
+ *
+ * Copyright (C) 2015-2017 Harald Welte <laforge at gnumonks.org>
+ * Copyright (C) 2017 Sebastian Stumpf <sebastian.stumpf87 at googlemail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/rsl.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/scheduler.h>
+#include "virtual_um.h"
+
+extern int vbts_sched_start(struct gsm_bts *bts);
+
+static struct phy_instance *phy_instance_by_arfcn(struct phy_link *plink, uint16_t arfcn)
+{
+	struct phy_instance *pinst;
+
+	llist_for_each_entry(pinst, &plink->instances, list) {
+		if (pinst->trx && pinst->trx->arfcn == arfcn)
+			return pinst;
+	}
+
+	return NULL;
+}
+
+/**
+ * Callback to handle incoming messages from the MS.
+ * The incoming message should be GSM_TAP encapsulated.
+ * TODO: implement all channels
+ */
+static void virt_um_rcv_cb(struct virt_um_inst *vui, struct msgb *msg)
+{
+	struct phy_link *plink = (struct phy_link *)vui->priv;
+	struct gsmtap_hdr *gh = msgb_l1(msg);
+	uint32_t fn = ntohl(gh->frame_number);	/* frame number of the rcv msg */
+	uint16_t arfcn = ntohs(gh->arfcn); 	/* arfcn of the cell we currently camp on */
+	uint8_t gsmtap_chantype = gh->sub_type; /* gsmtap channel type */
+	uint8_t signal_dbm = gh->signal_dbm;	/* signal strength in dBm */
+	//uint8_t snr = gh->snr_db;		/* signal noise ratio in dB */
+	uint8_t subslot = gh->sub_slot;		/* multiframe subslot to send msg in (tch -> 0-26, bcch/ccch -> 0-51) */
+	uint8_t timeslot = gh->timeslot;	/* tdma timeslot to send in (0-7) */
+	uint8_t rsl_chantype;			/* rsl chan type (8.58, 9.3.1) */
+	uint8_t link_id;			/* rsl link id tells if this is an ssociated or dedicated link */
+	uint8_t chan_nr;			/* encoded rsl channel type, timeslot and mf subslot */
+	struct phy_instance *pinst;
+	struct osmo_phsap_prim l1sap;
+
+	memset(&l1sap, 0, sizeof(l1sap));
+	/* get rid of l1 gsmtap hdr */
+	msg->l2h = msgb_pull(msg, sizeof(*gh));
+
+	/* convert gsmtap chan to RSL chan and link id */
+	chantype_gsmtap2rsl(gsmtap_chantype, &rsl_chantype, &link_id);
+	chan_nr = rsl_enc_chan_nr(rsl_chantype, subslot, timeslot);
+
+	/* ... or not uplink */
+	if (!(arfcn & GSMTAP_ARFCN_F_UPLINK)) {
+		LOGP(DL1P, LOGL_NOTICE, "Ignoring incoming msg - no uplink flag\n");
+		goto nomessage;
+	}
+
+	/* Generally ignore all msgs that are either not received with the right ARFCN... */
+	pinst = phy_instance_by_arfcn(plink, arfcn & GSMTAP_ARFCN_MASK);
+	if (!pinst) {
+		LOGP(DL1P, LOGL_NOTICE, "Ignoring incoming msg - msg ARFCN=%d not part of BTS\n",
+		     arfcn & GSMTAP_ARFCN_MASK);
+		goto nomessage;
+	}
+
+	/* switch case with removed ACCH flag */
+	switch ((gsmtap_chantype & ~GSMTAP_CHANNEL_ACCH) & 0xff) {
+	case GSMTAP_CHANNEL_RACH:
+		/* generate primitive for upper layer
+		 * see 04.08 - 3.3.1.3.1: the IMMEDIATE_ASSIGNMENT coming back from the network has to be
+		 * sent with the same ra reference as in the CHANNEL_REQUEST that was received */
+		osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, msg);
+
+		l1sap.u.rach_ind.chan_nr = chan_nr;
+		/* TODO: 11bit RACH */
+		l1sap.u.rach_ind.ra = msgb_pull_u8(msg); /* directly after gh hdr comes ra */
+		l1sap.u.rach_ind.acc_delay = 0; /* probably not used in virt um */
+		l1sap.u.rach_ind.is_11bit = 0;
+		l1sap.u.rach_ind.fn = fn;
+		l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_NONE; /* FIXME: what comes here */
+		break;
+	case GSMTAP_CHANNEL_TCH_F:
+	case GSMTAP_CHANNEL_TCH_H:
+#if 0
+		/* TODO: handle voice messages */
+		if (!facch && ! tch_acch) {
+			osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, msg);
+		}
+#endif
+	case GSMTAP_CHANNEL_SDCCH4:
+	case GSMTAP_CHANNEL_SDCCH8:
+		osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_DATA,
+		               PRIM_OP_INDICATION, msg);
+		l1sap.u.data.chan_nr = chan_nr;
+		l1sap.u.data.link_id = link_id;
+		l1sap.u.data.fn = fn;
+		l1sap.u.data.rssi = 0; /* Radio Signal Strength Indicator. Best -> 0 */
+		l1sap.u.data.ber10k = 0; /* Bit Error Rate in 0.01%. Best -> 0 */
+		l1sap.u.data.ta_offs_qbits = 0; /* Burst time of arrival in quarter bits. Probably used for Timing Advance calc. Best -> 0 */
+		l1sap.u.data.lqual_cb = 10 * signal_dbm; /* Link quality in centiBel = 10 * dB. */
+		l1sap.u.data.pdch_presence_info = PRES_INFO_UNKNOWN;
+		break;
+	case GSMTAP_CHANNEL_AGCH:
+	case GSMTAP_CHANNEL_PCH:
+	case GSMTAP_CHANNEL_BCCH:
+		LOGP(DL1P, LOGL_NOTICE, "Ignore incoming msg - channel type downlink only!\n");
+		goto nomessage;
+	case GSMTAP_CHANNEL_SDCCH:
+	case GSMTAP_CHANNEL_CCCH:
+	case GSMTAP_CHANNEL_PACCH:
+	case GSMTAP_CHANNEL_PDCH:
+	case GSMTAP_CHANNEL_PTCCH:
+	case GSMTAP_CHANNEL_CBCH51:
+	case GSMTAP_CHANNEL_CBCH52:
+		LOGP(DL1P, LOGL_NOTICE, "Ignore incoming msg - channel type not supported!\n");
+		goto nomessage;
+	default:
+		LOGP(DL1P, LOGL_NOTICE, "Ignore incoming msg - channel type unknown\n");
+		goto nomessage;
+	}
+
+	/* forward primitive, forwarded msg will not be freed */
+#warning "we cannot just pass a l1sap primitive on the stack!!!"
+	l1sap_up(pinst->trx, &l1sap);
+	DEBUGP(DL1P, "Message forwarded to layer 2.\n");
+	return;
+
+nomessage:
+	talloc_free(msg);
+}
+
+/* called by common part once OML link is established */
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+	return 0;
+}
+
+/* called by bts_main to initialize physical link */
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+	struct phy_instance *pinst;
+
+	//OSMO_ASSERT(plink->type == PHY_LINK_T_VIRTUAL);
+
+	if (plink->u.virt.virt_um)
+		virt_um_destroy(plink->u.virt.virt_um);
+
+	phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+	if (!plink->u.virt.bts_mcast_group)
+		plink->u.virt.bts_mcast_group = DEFAULT_BTS_MCAST_GROUP;
+
+	if (!plink->u.virt.bts_mcast_port)
+		plink->u.virt.bts_mcast_port = DEFAULT_BTS_MCAST_PORT;
+
+	if (!plink->u.virt.ms_mcast_group)
+		plink->u.virt.ms_mcast_group = DEFAULT_MS_MCAST_GROUP;
+
+	if (!plink->u.virt.ms_mcast_port)
+		plink->u.virt.ms_mcast_port = DEFAULT_MS_MCAST_PORT;
+
+	plink->u.virt.virt_um = virt_um_init(plink, plink->u.virt.ms_mcast_group, plink->u.virt.ms_mcast_port,
+					     plink->u.virt.bts_mcast_group, plink->u.virt.bts_mcast_port,
+					     virt_um_rcv_cb);
+	/* set back reference to plink */
+	plink->u.virt.virt_um->priv = plink;
+	if (!plink->u.virt.virt_um) {
+		phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+		return -1;
+	}
+
+	/* iterate over list of PHY instances and initialize the scheduler */
+	llist_for_each_entry(pinst, &plink->instances, list) {
+		trx_sched_init(&pinst->u.virt.sched, pinst->trx);
+		/* Only start the scheduler for the transceiver on C0.
+		 * If we have multiple tranceivers, CCCH is always on C0
+		 * and has to be auto active */
+		/* Other TRX are activated via OML by a PRIM_INFO_MODIFY
+		 * / PRIM_INFO_ACTIVATE */
+		if (pinst->trx && pinst->trx == pinst->trx->bts->c0) {
+			vbts_sched_start(pinst->trx->bts);
+			/* init lapdm layer 3 callback for the trx on timeslot 0 == BCCH */
+			lchan_init_lapdm(&pinst->trx->ts[0].lchan[CCCH_LCHAN]);
+			/* FIXME: This is probably the wrong location to set the CCCH to active... the OML link def. needs to be reworked and fixed. */
+			pinst->trx->ts[0].lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML;
+			lchan_set_state(&pinst->trx->ts[0].lchan[CCCH_LCHAN], LCHAN_S_ACTIVE);
+		}
+	}
+
+	/* this will automatically update the MO state of all associated TRX objects */
+	phy_link_state_set(plink, PHY_LINK_CONNECTED);
+
+	return 0;
+}
+
+
+/*
+ * primitive handling
+ */
+
+/* enable ciphering */
+static int l1if_set_ciphering(struct gsm_lchan *lchan, uint8_t chan_nr, int downlink)
+{
+	struct gsm_bts_trx *trx = lchan->ts->trx;
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct l1sched_trx *sched = &pinst->u.virt.sched;
+
+	/* ciphering already enabled in both directions */
+	if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF)
+		return -EINVAL;
+
+	if (!downlink) {
+		/* set uplink */
+		trx_sched_set_cipher(sched, chan_nr, 0, lchan->encr.alg_id - 1,
+			lchan->encr.key, lchan->encr.key_len);
+		lchan->ciph_state = LCHAN_CIPH_RX_CONF;
+	} else {
+		/* set downlink and also set uplink, if not already */
+		if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) {
+			trx_sched_set_cipher(sched, chan_nr, 0,
+				lchan->encr.alg_id - 1, lchan->encr.key,
+				lchan->encr.key_len);
+		}
+		trx_sched_set_cipher(sched, chan_nr, 1, lchan->encr.alg_id - 1,
+			lchan->encr.key, lchan->encr.key_len);
+		lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+	}
+
+	return 0;
+}
+
+static int mph_info_chan_confirm(struct gsm_bts_trx *trx, uint8_t chan_nr,
+	enum osmo_mph_info_type type, uint8_t cause)
+{
+	struct osmo_phsap_prim l1sap;
+
+	memset(&l1sap, 0, sizeof(l1sap));
+	osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
+		NULL);
+	l1sap.u.info.type = type;
+	l1sap.u.info.u.act_cnf.chan_nr = chan_nr;
+	l1sap.u.info.u.act_cnf.cause = cause;
+
+	return l1sap_up(trx, &l1sap);
+}
+
+int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn)
+{
+	struct osmo_phsap_prim l1sap;
+
+	memset(&l1sap, 0, sizeof(l1sap));
+	osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+		PRIM_OP_INDICATION, NULL);
+	l1sap.u.info.type = PRIM_INFO_TIME;
+	l1sap.u.info.u.time_ind.fn = fn;
+
+	if (!bts->c0)
+		return -EINVAL;
+
+	return l1sap_up(bts->c0, &l1sap);
+}
+
+
+static void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, float ta,
+				float ber, float rssi)
+{
+	memset(l1sap, 0, sizeof(*l1sap));
+	osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_MPH_INFO,
+		PRIM_OP_INDICATION, NULL);
+	l1sap->u.info.type = PRIM_INFO_MEAS;
+	l1sap->u.info.u.meas_ind.chan_nr = chan_nr;
+	l1sap->u.info.u.meas_ind.ta_offs_qbits = (int16_t)(ta*4);
+	l1sap->u.info.u.meas_ind.ber10k = (unsigned int) (ber * 10000);
+	l1sap->u.info.u.meas_ind.inv_rssi = (uint8_t) (rssi * -1);
+}
+
+int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr,
+	int n_errors, int n_bits_total, float rssi, float toa)
+{
+	struct gsm_lchan *lchan = &trx->ts[tn].lchan[l1sap_chan2ss(chan_nr)];
+	struct osmo_phsap_prim l1sap;
+	/* 100% BER is n_bits_total is 0 */
+	float ber = n_bits_total==0 ? 1.0 : (float)n_errors / (float)n_bits_total;
+
+	LOGP(DMEAS, LOGL_DEBUG, "RX L1 frame %s fn=%u chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS "
+		"ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa=%.2f\n",
+		gsm_lchan_name(lchan), fn, chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power),
+		rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa);
+
+	l1if_fill_meas_res(&l1sap, chan_nr, lchan->rqd_ta + toa, ber, rssi);
+
+	return l1sap_up(trx, &l1sap);
+}
+
+
+
+/* primitive from common part */
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+	struct phy_instance *pinst = trx_phy_instance(trx);
+	struct l1sched_trx *sched = &pinst->u.virt.sched;
+	struct msgb *msg = l1sap->oph.msg;
+	uint8_t chan_nr;
+	uint8_t tn, ss;
+	int rc = 0;
+	struct gsm_lchan *lchan;
+
+	switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+	case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+		if (!msg)
+			break;
+		/* put data into scheduler's queue */
+		return trx_sched_ph_data_req(sched, l1sap);
+	case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+		if (!msg)
+			break;
+		/* put data into scheduler's queue */
+		return trx_sched_tch_req(sched, l1sap);
+	case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+		switch (l1sap->u.info.type) {
+		case PRIM_INFO_ACT_CIPH:
+			chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+			tn = L1SAP_CHAN2TS(chan_nr);
+			ss = l1sap_chan2ss(chan_nr);
+			lchan = &trx->ts[tn].lchan[ss];
+			if (l1sap->u.info.u.ciph_req.uplink)
+				l1if_set_ciphering(lchan, chan_nr, 0);
+			if (l1sap->u.info.u.ciph_req.downlink)
+				l1if_set_ciphering(lchan, chan_nr, 1);
+			break;
+		case PRIM_INFO_ACTIVATE:
+		case PRIM_INFO_DEACTIVATE:
+		case PRIM_INFO_MODIFY:
+			chan_nr = l1sap->u.info.u.act_req.chan_nr;
+			tn = L1SAP_CHAN2TS(chan_nr);
+			ss = l1sap_chan2ss(chan_nr);
+			lchan = &trx->ts[tn].lchan[ss];
+			/* we receive a channel activation request from the BSC,
+			 * e.g. as a response to a channel req on RACH */
+			if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) {
+				if ((chan_nr & 0x80)) {
+					LOGP(DL1C, LOGL_ERROR, "Cannot activate"
+						" chan_nr 0x%02x\n", chan_nr);
+					break;
+				}
+				/* activate dedicated channel */
+				trx_sched_set_lchan(sched, chan_nr, LID_DEDIC, 1);
+				/* activate associated channel */
+				trx_sched_set_lchan(sched, chan_nr, LID_SACCH, 1);
+				/* set mode */
+				trx_sched_set_mode(sched, chan_nr,
+					lchan->rsl_cmode, lchan->tch_mode,
+					lchan->tch.amr_mr.num_modes,
+					lchan->tch.amr_mr.bts_mode[0].mode,
+					lchan->tch.amr_mr.bts_mode[1].mode,
+					lchan->tch.amr_mr.bts_mode[2].mode,
+					lchan->tch.amr_mr.bts_mode[3].mode,
+					amr_get_initial_mode(lchan),
+					(lchan->ho.active == 1));
+				/* init lapdm */
+				lchan_init_lapdm(lchan);
+				/* set lchan active */
+				lchan_set_state(lchan, LCHAN_S_ACTIVE);
+				/* set initial ciphering */
+				l1if_set_ciphering(lchan, chan_nr, 0);
+				l1if_set_ciphering(lchan, chan_nr, 1);
+				if (lchan->encr.alg_id)
+					lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+				else
+					lchan->ciph_state = LCHAN_CIPH_NONE;
+
+				/* confirm */
+				mph_info_chan_confirm(trx, chan_nr,
+					PRIM_INFO_ACTIVATE, 0);
+				break;
+			}
+			if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+				/* change mode */
+				trx_sched_set_mode(sched, chan_nr,
+					lchan->rsl_cmode, lchan->tch_mode,
+					lchan->tch.amr_mr.num_modes,
+					lchan->tch.amr_mr.bts_mode[0].mode,
+					lchan->tch.amr_mr.bts_mode[1].mode,
+					lchan->tch.amr_mr.bts_mode[2].mode,
+					lchan->tch.amr_mr.bts_mode[3].mode,
+					amr_get_initial_mode(lchan),
+					0);
+				break;
+			}
+			if ((chan_nr & 0x80)) {
+				LOGP(DL1C, LOGL_ERROR, "Cannot deactivate "
+					"chan_nr 0x%02x\n", chan_nr);
+				break;
+			}
+			/* deactivate associated channel */
+			trx_sched_set_lchan(sched, chan_nr, 0x40, 0);
+			if (!l1sap->u.info.u.act_req.sacch_only) {
+				/* set lchan inactive */
+				lchan_set_state(lchan, LCHAN_S_NONE);
+				/* deactivate dedicated channel */
+				trx_sched_set_lchan(sched, chan_nr, 0x00, 0);
+				/* confirm only on dedicated channel */
+				mph_info_chan_confirm(trx, chan_nr,
+					PRIM_INFO_DEACTIVATE, 0);
+				lchan->ciph_state = 0; /* FIXME: do this in common/\*.c */
+			}
+			break;
+		default:
+			LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
+				l1sap->u.info.type);
+			rc = -EINVAL;
+			goto done;
+		}
+		break;
+	default:
+		LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
+			l1sap->oph.primitive, l1sap->oph.operation);
+		rc = -EINVAL;
+		goto done;
+	}
+
+done:
+	if (msg)
+		msgb_free(msg);
+	return rc;
+}
diff --git a/src/osmo-bts-virtual/l1_if.h b/src/osmo-bts-virtual/l1_if.h
new file mode 100644
index 0000000..6a843b3
--- /dev/null
+++ b/src/osmo-bts-virtual/l1_if.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/scheduler.h>
+
+#include "virtual_um.h"
+
+struct vbts_l1h {
+	struct gsm_bts_trx	*trx;
+	struct l1sched_trx	l1s;
+	struct virt_um_inst	*virt_um;
+};
+
+struct vbts_l1h *l1if_open(struct gsm_bts_trx *trx);
+void l1if_close(struct vbts_l1h *l1h);
+void l1if_reset(struct vbts_l1h *l1h);
+
+int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn);
+
+int vbts_sched_start(struct gsm_bts *bts);
diff --git a/src/osmo-bts-virtual/main.c b/src/osmo-bts-virtual/main.c
new file mode 100644
index 0000000..6ceeaec
--- /dev/null
+++ b/src/osmo-bts-virtual/main.c
@@ -0,0 +1,126 @@
+/* Main program for Virtual OsmoBTS */
+
+/* (C) 2015 by Harald Welte <laforge at 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/phy_link.h>
+#include "virtual_um.h"
+
+/* dummy, since no direct dsp support */
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+	return 0;
+}
+
+int bts_model_init(struct gsm_bts *bts)
+{
+	struct gsm_bts_role_bts *btsb;
+
+	btsb = bts_role_bts(bts);
+	btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+	bts_model_vty_init(bts);
+
+	return 0;
+}
+
+void bts_model_print_help()
+{
+}
+
+int bts_model_handle_options(int argc, char **argv)
+{
+	int num_errors = 0;
+
+	while (1) {
+		int option_idx = 0, c;
+		static const struct option long_options[] = {
+			/* specific to this hardware */
+			{ 0, 0, 0, 0 }
+		};
+
+		c = getopt_long(argc, argv, "",
+				long_options, &option_idx);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		default:
+			num_errors++;
+			break;
+		}
+	}
+
+	return num_errors;
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+	/* for now, we simply terminate the program and re-spawn */
+	bts_shutdown(bts, "Abis close");
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+	return -ENOTSUP;
+}
+
+int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan)
+{
+	return -ENOTSUP;
+}
+
+int main(int argc, char **argv)
+{
+	return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.c b/src/osmo-bts-virtual/osmo_mcast_sock.c
new file mode 100644
index 0000000..f092a73
--- /dev/null
+++ b/src/osmo-bts-virtual/osmo_mcast_sock.c
@@ -0,0 +1,112 @@
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <talloc.h>
+#include <unistd.h>
+#include "osmo_mcast_sock.h"
+
+/* server socket is what we use for transmission. It is not subscribed
+ * to a multicast group or locally bound, but it is just a normal UDP
+ * socket that's connected to the remote mcast group + port */
+int mcast_server_sock_setup(struct osmo_fd *ofd, const char* tx_mcast_group,
+			    uint16_t tx_mcast_port, bool loopback)
+{
+	int rc;
+	unsigned int flags = OSMO_SOCK_F_CONNECT;
+
+	if (!loopback)
+		flags |= OSMO_SOCK_F_NO_MCAST_LOOP;
+
+	/* setup mcast server socket */
+	rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+				tx_mcast_group, tx_mcast_port, flags);
+	if (rc < 0) {
+		perror("Failed to create Multicast Server Socket");
+		return rc;
+	}
+
+	return 0;
+}
+
+/* the client socket is what we use for reception.  It is a UDP socket
+ * that's bound to the GSMTAP UDP port and subscribed to the respective
+ * multicast group */
+int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port,
+			    int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+			    void *osmo_fd_data)
+{
+	int rc;
+
+	ofd->cb = fd_rx_cb;
+	ofd->when = BSC_FD_READ;
+	ofd->data = osmo_fd_data;
+
+	/* Create mcast client socket */
+	rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+				NULL, mcast_port, OSMO_SOCK_F_BIND|OSMO_SOCK_F_NO_MCAST_ALL);
+	if (rc < 0) {
+		perror("Could not create mcast client socket");
+		return rc;
+	}
+
+	/* Configure and join the multicast group */
+	rc = osmo_sock_mcast_subscribe(ofd->fd, mcast_group);
+	if (rc < 0) {
+		perror("Failed to join to mcast goup");
+		osmo_fd_close(ofd);
+		return rc;
+	}
+
+	return 0;
+}
+
+struct mcast_bidir_sock *
+mcast_bidir_sock_setup(void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port,
+			const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback,
+			int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+			void *osmo_fd_data)
+{
+	struct mcast_bidir_sock *bidir_sock = talloc(ctx, struct mcast_bidir_sock);
+	int rc;
+
+	if (!bidir_sock)
+		return NULL;
+
+	rc = mcast_client_sock_setup(&bidir_sock->rx_ofd, rx_mcast_group, rx_mcast_port,
+				fd_rx_cb, osmo_fd_data);
+	if (rc < 0) {
+		talloc_free(bidir_sock);
+		return NULL;
+	}
+	rc = mcast_server_sock_setup(&bidir_sock->tx_ofd, tx_mcast_group, tx_mcast_port, loopback);
+	if (rc < 0) {
+		osmo_fd_close(&bidir_sock->rx_ofd);
+		talloc_free(bidir_sock);
+		return NULL;
+	}
+	return bidir_sock;
+
+}
+
+int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data,
+                        unsigned int data_len)
+{
+	return send(bidir_sock->tx_ofd.fd, data, data_len, 0);
+}
+
+int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len)
+{
+	return recv(bidir_sock->rx_ofd.fd, buf, buf_len, 0);
+}
+
+void mcast_bidir_sock_close(struct mcast_bidir_sock *bidir_sock)
+{
+	osmo_fd_close(&bidir_sock->tx_ofd);
+	osmo_fd_close(&bidir_sock->rx_ofd);
+	talloc_free(bidir_sock);
+}
diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.h b/src/osmo-bts-virtual/osmo_mcast_sock.h
new file mode 100644
index 0000000..aa2013c
--- /dev/null
+++ b/src/osmo-bts-virtual/osmo_mcast_sock.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <netinet/in.h>
+#include <osmocom/core/select.h>
+
+struct mcast_bidir_sock {
+	struct osmo_fd tx_ofd;
+	struct osmo_fd rx_ofd;
+};
+
+struct mcast_bidir_sock *mcast_bidir_sock_setup(void *ctx,
+                const char *tx_mcast_group, uint16_t tx_mcast_port,
+                const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback,
+                int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+                void *osmo_fd_data);
+
+int mcast_server_sock_setup(struct osmo_fd *ofd, const char *tx_mcast_group,
+			    uint16_t tx_mcast_port, bool loopback);
+
+int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port,
+			    int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+			    void *osmo_fd_data);
+
+int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data, unsigned int data_len);
+int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len);
+void mcast_bidir_sock_close(struct mcast_bidir_sock* bidir_sock);
+
diff --git a/src/osmo-bts-virtual/scheduler_virtbts.c b/src/osmo-bts-virtual/scheduler_virtbts.c
new file mode 100644
index 0000000..4b4def7
--- /dev/null
+++ b/src/osmo-bts-virtual/scheduler_virtbts.c
@@ -0,0 +1,633 @@
+/* Scheduler worker functiosn for Virtua OsmoBTS */
+
+/* (C) 2015-2017 by Harald Welte <laforge at gnumonks.org>
+ * (C) 2017 Sebastian Stumpf <sebastian.stumpf87 at googlemail.com>
+ *
+ * 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 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 <ctype.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/gsm/rsl.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+#include "virtual_um.h"
+#include "l1_if.h"
+
+static const char *gsmtap_hdr_stringify(const struct gsmtap_hdr *gh)
+{
+	static char buf[256];
+	snprintf(buf, sizeof(buf), "(ARFCN=%u, ts=%u, ss=%u, type=%u/%u)",
+		 gh->arfcn & GSMTAP_ARFCN_MASK, gh->timeslot, gh->sub_slot, gh->type, gh->sub_type);
+	return buf;
+}
+
+/**
+ * Send a message over the virtual um interface.
+ * This will at first wrap the msg with a GSMTAP header and then write it to the declared multicast socket.
+ * TODO: we might want to remove unused argument uint8_t tn
+ */
+static void tx_to_virt_um(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+			  enum trx_chan_type chan, struct msgb *msg)
+{
+	const struct trx_chan_desc *chdesc = &trx_chan_desc[chan];
+	struct msgb *outmsg;			/* msg to send with gsmtap header prepended */
+	uint16_t arfcn = l1t->trx->arfcn;	/* ARFCN of the tranceiver the message is send with */
+	uint8_t signal_dbm = 63;		/* signal strength, 63 is best */
+	uint8_t snr = 63;			/* signal noise ratio, 63 is best */
+	uint8_t *data = msgb_l2(msg);		/* data to transmit (whole message without l1 header) */
+	uint8_t data_len = msgb_l2len(msg);	/* length of data */
+	uint8_t rsl_chantype;			/* RSL chan type (TS 08.58, 9.3.1) */
+	uint8_t subslot;			/* multiframe subslot to send msg in (tch -> 0-26, bcch/ccch -> 0-51) */
+	uint8_t timeslot;			/* TDMA timeslot to send in (0-7) */
+	uint8_t gsmtap_chantype;		/* the GSMTAP channel */
+
+	rsl_dec_chan_nr(chdesc->chan_nr, &rsl_chantype, &subslot, &timeslot);
+	/* in Osmocom, AGCH is only sent on ccch block 0. no idea why. this seems to cause false GSMTAP channel
+	 * types for agch and pch. */
+	if (rsl_chantype == RSL_CHAN_PCH_AGCH && L1SAP_FN2CCCHBLOCK(fn) == 0)
+		gsmtap_chantype = GSMTAP_CHANNEL_PCH;
+	else
+		gsmtap_chantype = chantype_rsl2gsmtap(rsl_chantype, chdesc->link_id); /* the logical channel type */
+
+	outmsg = gsmtap_makemsg(arfcn, timeslot, gsmtap_chantype, subslot, fn, signal_dbm, snr, data, data_len);
+	if (outmsg) {
+		struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+		struct gsmtap_hdr *gh = (struct gsmtap_hdr *)msgb_data(outmsg);
+
+		if (virt_um_write_msg(pinst->phy_link->u.virt.virt_um, outmsg) == -1)
+			LOGP(DL1P, LOGL_ERROR, "%s GSMTAP msg could not send to virtual Um\n", gsmtap_hdr_stringify(gh));
+		else
+			DEBUGP(DL1C, "%s Sending GSMTAP message to virtual Um\n", gsmtap_hdr_stringify(gh));
+	} else
+		LOGP(DL1C, LOGL_ERROR, "GSMTAP msg could not be created!\n");
+
+	/* free incoming message */
+	msgb_free(msg);
+}
+
+/*
+ * TX on downlink
+ */
+
+/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */
+ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	return NULL;
+}
+
+ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	return NULL;
+}
+
+ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	return NULL;
+}
+
+ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	struct msgb *msg;
+
+	if (bid > 0)
+		return NULL;
+
+	/* get mac block from queue */
+	msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+	if (!msg) {
+		LOGP(DL1P, LOGL_INFO, "%s has not been served !! No prim for "
+			"trx=%u ts=%u at fn=%u to transmit.\n", 
+			trx_chan_desc[chan].name, l1t->trx->nr, tn, fn);
+		return NULL;
+	}
+
+	/* check validity of message */
+	if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) {
+		LOGP(DL1P, LOGL_FATAL, "Prim not 23 bytes, please FIX! "
+			"(len=%d)\n", msgb_l2len(msg));
+		/* free message */
+		msgb_free(msg);
+		return NULL;
+	}
+
+	/* transmit the msg received on dl from bsc to layer1 (virt Um) */
+	tx_to_virt_um(l1t, tn, fn, chan, msg);
+
+	return NULL;
+}
+
+ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	struct msgb *msg = NULL; /* make GCC happy */
+
+	if (bid > 0)
+		return NULL;
+
+	/* get mac block from queue */
+	msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+	if (!msg) {
+		LOGP(DL1P, LOGL_INFO, "%s has not been served !! No prim for "
+			"trx=%u ts=%u at fn=%u to transmit.\n", 
+			trx_chan_desc[chan].name, l1t->trx->nr, tn, fn);
+		return NULL;
+	}
+
+	tx_to_virt_um(l1t, tn, fn, chan, msg);
+
+	return NULL;
+}
+
+static void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch,
+	struct msgb **_msg_facch, int codec_mode_request)
+{
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+	struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL;
+	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+	uint8_t rsl_cmode = chan_state->rsl_cmode;
+	uint8_t tch_mode = chan_state->tch_mode;
+	struct osmo_phsap_prim *l1sap;
+#if 0
+	/* handle loss detection of received TCH frames */
+	if (rsl_cmode == RSL_CMOD_SPD_SPEECH
+	 && ++(chan_state->lost) > 5) {
+		uint8_t tch_data[GSM_FR_BYTES];
+		int len;
+
+		LOGP(DL1P, LOGL_NOTICE, "Missing TCH bursts detected, sending "
+			"BFI for %s\n", trx_chan_desc[chan].name);
+
+		/* indicate bad frame */
+		switch (tch_mode) {
+		case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+			if (chan != TRXC_TCHF) { /* HR */
+				tch_data[0] = 0x70; /* F = 0, FT = 111 */
+				memset(tch_data + 1, 0, 14);
+				len = 15;
+				break;
+			}
+			memset(tch_data, 0, GSM_FR_BYTES);
+			len = GSM_FR_BYTES;
+			break;
+		case GSM48_CMODE_SPEECH_EFR: /* EFR */
+			if (chan != TRXC_TCHF)
+				goto inval_mode1;
+			memset(tch_data, 0, GSM_EFR_BYTES);
+			len = GSM_EFR_BYTES;
+			break;
+		case GSM48_CMODE_SPEECH_AMR: /* AMR */
+			len = amr_compose_payload(tch_data,
+				chan_state->codec[chan_state->dl_cmr],
+				chan_state->codec[chan_state->dl_ft], 1);
+			if (len < 2)
+				break;
+			memset(tch_data + 2, 0, len - 2);
+			_sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len);
+			break;
+		default:
+inval_mode1:
+			LOGP(DL1P, LOGL_ERROR, "TCH mode invalid, please "
+				"fix!\n");
+			len = 0;
+		}
+		if (len)
+			_sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len);
+	}
+#endif
+
+	/* get frame and unlink from queue */
+	msg1 = _sched_dequeue_prim(l1t, tn, fn, chan);
+	msg2 = _sched_dequeue_prim(l1t, tn, fn, chan);
+	if (msg1) {
+		l1sap = msgb_l1sap_prim(msg1);
+		if (l1sap->oph.primitive == PRIM_TCH) {
+			msg_tch = msg1;
+			if (msg2) {
+				l1sap = msgb_l1sap_prim(msg2);
+				if (l1sap->oph.primitive == PRIM_TCH) {
+					LOGP(DL1P, LOGL_FATAL, "TCH twice, "
+						"please FIX! ");
+					msgb_free(msg2);
+				} else
+					msg_facch = msg2;
+			}
+		} else {
+			msg_facch = msg1;
+			if (msg2) {
+				l1sap = msgb_l1sap_prim(msg2);
+				if (l1sap->oph.primitive != PRIM_TCH) {
+					LOGP(DL1P, LOGL_FATAL, "FACCH twice, "
+						"please FIX! ");
+					msgb_free(msg2);
+				} else
+					msg_tch = msg2;
+			}
+		}
+	} else if (msg2) {
+		l1sap = msgb_l1sap_prim(msg2);
+		if (l1sap->oph.primitive == PRIM_TCH)
+			msg_tch = msg2;
+		else
+			msg_facch = msg2;
+	}
+
+	/* check validity of message */
+	if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) {
+		LOGP(DL1P, LOGL_FATAL, "Prim not 23 bytes, please FIX! "
+			"(len=%d)\n", msgb_l2len(msg_facch));
+		/* free message */
+		msgb_free(msg_facch);
+		msg_facch = NULL;
+	}
+
+	/* check validity of message, get AMR ft and cmr */
+	if (!msg_facch && msg_tch) {
+		int len;
+#if 0
+		uint8_t bfi, cmr_codec, ft_codec;
+		int cmr, ft, i;
+#endif
+
+		if (rsl_cmode != RSL_CMOD_SPD_SPEECH) {
+			LOGP(DL1P, LOGL_NOTICE, "%s Dropping speech frame, "
+				"because we are not in speech mode trx=%u "
+				"ts=%u at fn=%u.\n", trx_chan_desc[chan].name,
+				l1t->trx->nr, tn, fn);
+			goto free_bad_msg;
+		}
+
+		switch (tch_mode) {
+		case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+			if (chan != TRXC_TCHF) { /* HR */
+				len = 15;
+				if (msgb_l2len(msg_tch) >= 1
+				 && (msg_tch->l2h[0] & 0xf0) != 0x00) {
+					LOGP(DL1P, LOGL_NOTICE, "%s "
+						"Transmitting 'bad "
+						"HR frame' trx=%u ts=%u at "
+						"fn=%u.\n",
+						trx_chan_desc[chan].name,
+						l1t->trx->nr, tn, fn);
+					goto free_bad_msg;
+				}
+				break;
+			}
+			len = GSM_FR_BYTES;
+			if (msgb_l2len(msg_tch) >= 1
+			 && (msg_tch->l2h[0] >> 4) != 0xd) {
+				LOGP(DL1P, LOGL_NOTICE, "%s Transmitting 'bad "
+					"FR frame' trx=%u ts=%u at fn=%u.\n",
+					trx_chan_desc[chan].name,
+					l1t->trx->nr, tn, fn);
+				goto free_bad_msg;
+			}
+			break;
+		case GSM48_CMODE_SPEECH_EFR: /* EFR */
+			if (chan != TRXC_TCHF)
+				goto inval_mode2;
+			len = GSM_EFR_BYTES;
+			if (msgb_l2len(msg_tch) >= 1
+			 && (msg_tch->l2h[0] >> 4) != 0xc) {
+				LOGP(DL1P, LOGL_NOTICE, "%s Transmitting 'bad "
+					"EFR frame' trx=%u ts=%u at fn=%u.\n",
+					trx_chan_desc[chan].name,
+					l1t->trx->nr, tn, fn);
+				goto free_bad_msg;
+			}
+			break;
+		case GSM48_CMODE_SPEECH_AMR: /* AMR */
+#if 0
+			len = amr_decompose_payload(msg_tch->l2h,
+				msgb_l2len(msg_tch), &cmr_codec, &ft_codec,
+				&bfi);
+			cmr = -1;
+			ft = -1;
+			for (i = 0; i < chan_state->codecs; i++) {
+				if (chan_state->codec[i] == cmr_codec)
+					cmr = i;
+				if (chan_state->codec[i] == ft_codec)
+					ft = i;
+			}
+			if (cmr >= 0) { /* new request */
+				chan_state->dl_cmr = cmr;
+				/* disable AMR loop */
+				trx_loop_amr_set(chan_state, 0);
+			} else {
+				/* enable AMR loop */
+				trx_loop_amr_set(chan_state, 1);
+			}
+			if (ft < 0) {
+				LOGP(DL1P, LOGL_ERROR, "%s Codec (FT = %d) "
+					" of RTP frame not in list. "
+					"trx=%u ts=%u\n",
+					trx_chan_desc[chan].name, ft_codec,
+					l1t->trx->nr, tn);
+				goto free_bad_msg;
+			}
+			if (codec_mode_request && chan_state->dl_ft != ft) {
+				LOGP(DL1P, LOGL_NOTICE, "%s Codec (FT = %d) "
+					" of RTP cannot be changed now, but in "
+					"next frame. trx=%u ts=%u\n",
+					trx_chan_desc[chan].name, ft_codec,
+					l1t->trx->nr, tn);
+				goto free_bad_msg;
+			}
+			chan_state->dl_ft = ft;
+			if (bfi) {
+				LOGP(DL1P, LOGL_NOTICE, "%s Transmitting 'bad "
+					"AMR frame' trx=%u ts=%u at fn=%u.\n",
+					trx_chan_desc[chan].name,
+					l1t->trx->nr, tn, fn);
+				goto free_bad_msg;
+			}
+#else
+			LOGP(DL1P, LOGL_ERROR, "AMR not supported!\n");
+			goto free_bad_msg;
+#endif
+			break;
+		default:
+inval_mode2:
+			LOGP(DL1P, LOGL_ERROR, "TCH mode invalid, please "
+				"fix!\n");
+			goto free_bad_msg;
+		}
+		if (len < 0) {
+			LOGP(DL1P, LOGL_ERROR, "Cannot send invalid AMR "
+				"payload\n");
+			goto free_bad_msg;
+		}
+		if (msgb_l2len(msg_tch) != len) {
+			LOGP(DL1P, LOGL_ERROR, "Cannot send payload with "
+				"invalid length! (expecing %d, received %d)\n",
+				len, msgb_l2len(msg_tch));
+free_bad_msg:
+			/* free message */
+			msgb_free(msg_tch);
+			msg_tch = NULL;
+			goto send_frame;
+		}
+	}
+
+send_frame:
+	*_msg_tch = msg_tch;
+	*_msg_facch = msg_facch;
+}
+
+ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	struct msgb *msg_tch = NULL, *msg_facch = NULL;
+
+	if (bid > 0)
+		return NULL;
+
+	tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch,
+		(((fn + 4) % 26) >> 2) & 1);
+
+	/* no message at all */
+	if (!msg_tch && !msg_facch) {
+		LOGP(DL1P, LOGL_INFO, "%s has not been served !! No prim for "
+			"trx=%u ts=%u at fn=%u to transmit.\n", 
+			trx_chan_desc[chan].name, l1t->trx->nr, tn, fn);
+		goto send_burst;
+	}
+
+	if (msg_facch) {
+		tx_to_virt_um(l1t, tn, fn, chan, msg_facch);
+		msgb_free(msg_tch);
+	} else
+		tx_to_virt_um(l1t, tn, fn, chan, msg_tch);
+
+send_burst:
+
+	return NULL;
+}
+
+ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	struct msgb *msg_tch = NULL, *msg_facch = NULL;
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+	//uint8_t tch_mode = chan_state->tch_mode;
+
+	/* send burst, if we already got a frame */
+	if (bid > 0)
+		return NULL;
+
+	/* get TCH and/or FACCH */
+	tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch,
+		(((fn + 4) % 26) >> 2) & 1);
+
+	/* check for FACCH alignment */
+	if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) {
+		LOGP(DL1P, LOGL_ERROR, "%s Cannot transmit FACCH starting on "
+			"even frames, please fix RTS!\n",
+			trx_chan_desc[chan].name);
+		msgb_free(msg_facch);
+		msg_facch = NULL;
+	}
+
+	/* no message at all */
+	if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) {
+		LOGP(DL1P, LOGL_INFO, "%s has not been served !! No prim for "
+			"trx=%u ts=%u at fn=%u to transmit.\n", 
+			trx_chan_desc[chan].name, l1t->trx->nr, tn, fn);
+		goto send_burst;
+	}
+
+	if (msg_facch) {
+		tx_to_virt_um(l1t, tn, fn, chan, msg_facch);
+		msgb_free(msg_tch);
+	} else
+		tx_to_virt_um(l1t, tn, fn, chan, msg_tch);
+
+send_burst:
+	return NULL;
+}
+
+
+/***********************************************************************
+ * RX on uplink (indication to upper layer)
+ ***********************************************************************/
+
+/* we don't use those functions, as we feed the MAC frames from GSMTAP
+ * directly into the L1SAP, bypassing the TDMA multiplex logic oriented
+ * towards receiving bursts */
+
+int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+	int8_t rssi, float toa)
+{
+	return 0;
+}
+
+/*! \brief a single burst was received by the PHY, process it */
+int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+	int8_t rssi, float toa)
+{
+	return 0;
+}
+
+int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+	int8_t rssi, float toa)
+{
+	return 0;
+}
+
+int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+	int8_t rssi, float toa)
+{
+	return 0;
+}
+
+int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+	enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits,
+	int8_t rssi, float toa)
+{
+	return 0;
+}
+
+void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate)
+{
+}
+
+/***********************************************************************
+ * main scheduler function
+ ***********************************************************************/
+
+#define RTS_ADVANCE		5	/* about 20ms */
+#define FRAME_DURATION_uS	4615
+
+static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn)
+{
+	struct gsm_bts_trx *trx;
+
+	/* send time indication */
+	/* update model with new frame number, lot of stuff happening, measurements of timeslots */
+	/* saving GSM time in BTS model, and more */
+	l1if_mph_time_ind(bts, fn);
+
+	/* advance the frame number? */
+	llist_for_each_entry(trx, &bts->trx_list, list) {
+		struct phy_instance *pinst = trx_phy_instance(trx);
+		struct l1sched_trx *l1t = &pinst->u.virt.sched;
+		int tn;
+		uint16_t nbits;
+
+		/* do for each of the 8 timeslots */
+		for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
+			/* Generate RTS indication to higher layers */
+			/* This will basically do 2 things (check l1_if:bts_model_l1sap_down):
+			 * 1) Get pending messages from layer 2 (from the lapdm queue)
+			 * 2) Process the messages
+			 *    --> Handle and process non-transparent RSL-Messages (activate channel, )
+			 *    --> Forward transparent RSL-DATA-Messages to the ms by appending them to
+			 *        the l1-dl-queue */
+			_sched_rts(l1t, tn, (fn + RTS_ADVANCE) % GSM_HYPERFRAME);
+			/* schedule transmit backend functions */
+			/* Process data in the l1-dlqueue and forward it
+			 * to MS */
+			/* the returned bits are not used here, the routines called will directly forward their
+			 * bits to the virt Um */
+			_sched_dl_burst(l1t, tn, fn, &nbits);
+		}
+	}
+
+	return 0;
+}
+
+static void vbts_fn_timer_cb(void *data)
+{
+	struct gsm_bts *bts = data;
+	struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+	struct timeval tv_now;
+	struct timeval *tv_clock = &btsb->vbts.tv_clock;
+	int32_t elapsed_us;
+
+	gettimeofday(&tv_now, NULL);
+
+	/* check how much time elapsed till the last timer callback call.
+	 * this value should be about 4.615 ms (a bit greater) as this is the scheduling interval */
+	elapsed_us = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000
+	                + (tv_now.tv_usec - tv_clock->tv_usec);
+
+	/* not so good somehow a lot of time passed between two timer callbacks */
+	if (elapsed_us > 2 *FRAME_DURATION_uS)
+		LOGP(DL1P, LOGL_NOTICE, "vbts_fn_timer_cb after %d us\n", elapsed_us);
+
+	/* schedule the current frame/s (fn = frame number)
+	 * this loop will be called at least once, but can also be executed
+	 * multiple times if more than one frame duration (4615us) passed till the last callback */
+	while (elapsed_us > FRAME_DURATION_uS / 2) {
+		const struct timeval tv_frame = {
+			.tv_sec = 0,
+			.tv_usec = FRAME_DURATION_uS,
+		};
+		timeradd(tv_clock, &tv_frame, tv_clock);
+		/* increment the frame number in the BTS model instance */
+		btsb->vbts.last_fn = (btsb->vbts.last_fn + 1) % GSM_HYPERFRAME;
+		vbts_sched_fn(bts, btsb->vbts.last_fn);
+		elapsed_us -= FRAME_DURATION_uS;
+	}
+
+	/* re-schedule the timer */
+	/* timer is set to frame duration - elapsed time to guarantee that this cb method will be
+	 * periodically executed every 4.615ms */
+	osmo_timer_schedule(&btsb->vbts.fn_timer, 0, FRAME_DURATION_uS - elapsed_us);
+}
+
+int vbts_sched_start(struct gsm_bts *bts)
+{
+	struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+	LOGP(DL1P, LOGL_NOTICE, "starting VBTS scheduler\n");
+
+	memset(&btsb->vbts.fn_timer, 0, sizeof(btsb->vbts.fn_timer));
+	btsb->vbts.fn_timer.cb = vbts_fn_timer_cb;
+	btsb->vbts.fn_timer.data = bts;
+
+	gettimeofday(&btsb->vbts.tv_clock, NULL);
+	/* trigger the first timer after 4615us (a frame duration) */
+	osmo_timer_schedule(&btsb->vbts.fn_timer, 0, FRAME_DURATION_uS);
+
+	return 0;
+}
diff --git a/src/osmo-bts-virtual/virtual_um.c b/src/osmo-bts-virtual/virtual_um.c
new file mode 100644
index 0000000..e6b9615
--- /dev/null
+++ b/src/osmo-bts-virtual/virtual_um.c
@@ -0,0 +1,92 @@
+/* Routines for a Virtual Um interface over GSMTAP/UDP */
+
+/* (C) 2015 by Harald Welte <laforge at 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include "osmo_mcast_sock.h"
+#include "virtual_um.h"
+#include <unistd.h>
+
+/**
+ * Virtual UM interface file descriptor callback.
+ * Should be called by select.c when the fd is ready for reading.
+ */
+static int virt_um_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	struct virt_um_inst *vui = ofd->data;
+
+	if (what & BSC_FD_READ) {
+		struct msgb *msg = msgb_alloc(VIRT_UM_MSGB_SIZE, "Virtual UM Rx");
+		int rc;
+
+		/* read message from fd into message buffer */
+		rc = mcast_bidir_sock_rx(vui->mcast_sock, msgb_data(msg), msgb_tailroom(msg));
+		if (rc > 0) {
+			msgb_put(msg, rc);
+			msg->l1h = msgb_data(msg);
+			/* call the l1 callback function for a received msg */
+			vui->recv_cb(vui, msg);
+		} else {
+			/* FIXME: this kind of error handling might be a bit harsh */
+			vui->recv_cb(vui, NULL);
+			osmo_fd_close(ofd);
+		}
+	}
+
+	return 0;
+}
+
+struct virt_um_inst *virt_um_init(void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port,
+				  char *rx_mcast_group, uint16_t rx_mcast_port,
+				  void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg))
+{
+	struct virt_um_inst *vui = talloc_zero(ctx, struct virt_um_inst);
+	vui->mcast_sock = mcast_bidir_sock_setup(ctx, tx_mcast_group, tx_mcast_port,
+						 rx_mcast_group, rx_mcast_port, 1, virt_um_fd_cb, vui);
+	vui->recv_cb = recv_cb;
+
+	return vui;
+
+}
+
+void virt_um_destroy(struct virt_um_inst *vui)
+{
+	mcast_bidir_sock_close(vui->mcast_sock);
+	talloc_free(vui);
+}
+
+/**
+ * Write msg to to multicast socket and free msg afterwards
+ */
+int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg)
+{
+	int rc;
+
+	rc = mcast_bidir_sock_tx(vui->mcast_sock, msgb_data(msg),
+	                msgb_length(msg));
+	msgb_free(msg);
+
+	return rc;
+}
diff --git a/src/osmo-bts-virtual/virtual_um.h b/src/osmo-bts-virtual/virtual_um.h
new file mode 100644
index 0000000..6e7c384
--- /dev/null
+++ b/src/osmo-bts-virtual/virtual_um.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+#include "osmo_mcast_sock.h"
+
+#define VIRT_UM_MSGB_SIZE	256
+#define DEFAULT_MS_MCAST_GROUP "224.0.0.1"
+#define DEFAULT_MS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */
+#define DEFAULT_BTS_MCAST_GROUP "225.0.0.1"
+#define DEFAULT_BTS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */
+
+struct virt_um_inst {
+	void *priv;
+	struct mcast_bidir_sock *mcast_sock;
+	void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg);
+};
+
+struct virt_um_inst *virt_um_init(
+                void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port,
+                char *rx_mcast_group, uint16_t rx_mcast_port,
+                void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg));
+
+void virt_um_destroy(struct virt_um_inst *vui);
+
+int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg);
diff --git a/src/osmo-bts-virtual/virtualbts_vty.c b/src/osmo-bts-virtual/virtualbts_vty.c
new file mode 100644
index 0000000..45c1086
--- /dev/null
+++ b/src/osmo-bts-virtual/virtualbts_vty.c
@@ -0,0 +1,185 @@
+/* VTY interface for virtual OsmoBTS */
+
+/* (C) 2015-2017 by Harald Welte <laforge at gnumonks.org>
+ * (C) 2017 Sebastian Stumpf <sebastian.stumpf87 at googlemail.com>
+ * 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 <ctype.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/vty.h>
+#include "virtual_um.h"
+
+#define TRX_STR "Transceiver related commands\n" "TRX number\n"
+
+#define SHOW_TRX_STR				\
+	SHOW_STR				\
+	TRX_STR
+
+static struct gsm_bts *vty_bts;
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+}
+
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+	if (plink->u.virt.mcast_dev)
+		vty_out(vty, " virtual-um net-device %s%s",
+			plink->u.virt.mcast_dev, VTY_NEWLINE);
+	if (strcmp(plink->u.virt.ms_mcast_group, DEFAULT_BTS_MCAST_GROUP))
+		vty_out(vty, " virtual-um ms-multicast-group %s%s",
+			plink->u.virt.ms_mcast_group, VTY_NEWLINE);
+	if (plink->u.virt.ms_mcast_port)
+		vty_out(vty, " virtual-um ms-udp-port %u%s",
+			plink->u.virt.ms_mcast_port, VTY_NEWLINE);
+	if (strcmp(plink->u.virt.bts_mcast_group, DEFAULT_MS_MCAST_GROUP))
+		vty_out(vty, " virtual-um bts-multicast-group %s%s",
+			plink->u.virt.bts_mcast_group, VTY_NEWLINE);
+	if (plink->u.virt.bts_mcast_port)
+		vty_out(vty, " virtual-um bts-udp-port %u%s",
+			plink->u.virt.bts_mcast_port, VTY_NEWLINE);
+
+}
+
+#define VUM_STR	"Virtual Um layer\n"
+
+DEFUN(cfg_phy_ms_mcast_group, cfg_phy_ms_mcast_group_cmd,
+	"virtual-um ms-multicast-group GROUP",
+	VUM_STR "Configure the MS multicast group\n")
+{
+	struct phy_link *plink = vty->index;
+
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+		VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	osmo_talloc_replace_string(plink, &plink->u.virt.ms_mcast_group, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_ms_mcast_port, cfg_phy_ms_mcast_port_cmd,
+	"virtual-um ms-udp-port <0-65535>",
+	VUM_STR "Configure the MS UDP port\n")
+{
+	struct phy_link *plink = vty->index;
+
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	plink->u.virt.ms_mcast_port = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_bts_mcast_group, cfg_phy_bts_mcast_group_cmd,
+	"virtual-um bts-multicast-group GROUP",
+	VUM_STR "Configure the BTS multicast group\n")
+{
+	struct phy_link *plink = vty->index;
+
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+		VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	osmo_talloc_replace_string(plink, &plink->u.virt.bts_mcast_group, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_bts_mcast_port, cfg_phy_bts_mcast_port_cmd,
+	"virtual-um bts-udp-port <0-65535>",
+	VUM_STR "Configure the BTS UDP port\n")
+{
+	struct phy_link *plink = vty->index;
+
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	plink->u.virt.bts_mcast_port = atoi(argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_mcast_dev, cfg_phy_mcast_dev_cmd,
+	"virtual-um net-device NETDEV",
+	VUM_STR "Configure the network device\n")
+{
+	struct phy_link *plink = vty->index;
+
+	if (plink->state != PHY_LINK_SHUTDOWN) {
+		vty_out(vty, "Can only reconfigure a PHY link that is down%s",
+			VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	osmo_talloc_replace_string(plink, &plink->u.virt.mcast_dev, argv[0]);
+
+	return CMD_SUCCESS;
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+	vty_bts = bts;
+
+	install_element(PHY_NODE, &cfg_phy_ms_mcast_group_cmd);
+	install_element(PHY_NODE, &cfg_phy_ms_mcast_port_cmd);
+	install_element(PHY_NODE, &cfg_phy_bts_mcast_group_cmd);
+	install_element(PHY_NODE, &cfg_phy_bts_mcast_port_cmd);
+	install_element(PHY_NODE, &cfg_phy_mcast_dev_cmd);
+
+	return 0;
+}

-- 
To view, visit https://gerrit.osmocom.org/3242
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: newpatchset
Gerrit-Change-Id: I1bf7670975b1e367c1c62983020865a043542622
Gerrit-PatchSet: 4
Gerrit-Project: osmo-bts
Gerrit-Branch: master
Gerrit-Owner: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: Jenkins Builder



More information about the gerrit-log mailing list