[PATCH] osmocom-bb[master]: VIRT-PHY: Initial commit of virt-phy to work with osmo-bts v...

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
Wed Jul 12 21:26:37 UTC 2017


Hello Jenkins Builder,

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

    https://gerrit.osmocom.org/3197

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

VIRT-PHY: Initial commit of virt-phy to work with osmo-bts virt-phy.

This patch implements a virtual physical layer replacing the air
interface. The purpose is to get rid of the hardware requirements
and be able to start testing and implementing layer 2 communication
functionality on one machine. Multicast sockets are used to enable
bidirectional communication between the BTS and the MS process.
The GSMTAP protocol designed for wireshark capturing is used to
encapsulate the payload on the virtual physical layer.
The virtual physical layer on the osmocom-bb side implements the
L1CTL interface to the layer23 apps like mobile.

* Working mcast socket communication and extraction of its
  functionality.
* Basic handlers for file descriptor callbacks from incoming L1CTL
  messages and extraction of that functionality to a l1ctl socket class.
* Multiplexing to different routines depending on incoming L1CTL
  message type.
* Uses virt_um and osmocom_mcast_sock implementation from osmo-bts
  virt-phy.
* Ecapsulation and parsing methods to and from GSMTAP messages.
* Basic handlers for file descriptor callbacks from incoming mcast
  messages on the virtual um.
* Multiplexing to different channel routines based on GSMTAP header
  channel type.
* Example configuration for l23 app mobile using virtual test sim.

Change-Id: I203c8ec58326e52a09603a37232fce7ae3641415
---
A .gitignore
M src/Makefile
A src/host/virt_phy/.gitignore
A src/host/virt_phy/Makefile.am
A src/host/virt_phy/README
A src/host/virt_phy/configure.ac
A src/host/virt_phy/example_configs/osmocom-bb-mobile.cfg
A src/host/virt_phy/include/layer1/mframe_sched.h
A src/host/virt_phy/include/layer1/sync.h
A src/host/virt_phy/include/layer1/tdma_sched.h
A src/host/virt_phy/src/Makefile.am
A src/host/virt_phy/src/gsmtapl1_if.c
A src/host/virt_phy/src/gsmtapl1_if.h
A src/host/virt_phy/src/l1ctl_sap.c
A src/host/virt_phy/src/l1ctl_sap.h
A src/host/virt_phy/src/l1ctl_sock.c
A src/host/virt_phy/src/l1ctl_sock.h
A src/host/virt_phy/src/logging.c
A src/host/virt_phy/src/logging.h
A src/host/virt_phy/src/osmo_mcast_sock.c
A src/host/virt_phy/src/osmo_mcast_sock.h
A src/host/virt_phy/src/virt_l1_model.c
A src/host/virt_phy/src/virt_l1_model.h
A src/host/virt_phy/src/virtphy.c
A src/host/virt_phy/src/virtual_um.c
A src/host/virt_phy/src/virtual_um.h
26 files changed, 2,515 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/97/3197/2

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..363f656
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+*.o
+*.a
+Makefile.in
+Makefile
+.deps
+
+build-target
+aclocal.m4
+autom4te.cache
+config.log
+config.status
+config.guess
+config.sub
+configure
+compile
+depcomp
+install-sh
+missing
+stamp-h1
+core
+core.*
+
+# Backups, vi, merges
+*~
+*.sw?
+*.orig
+*.sav
+
+# development environment
+/.autotools
+/.cproject
+/.project
+/.settings/
+
diff --git a/src/Makefile b/src/Makefile
index 00231a6..e83175c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -12,7 +12,8 @@
 TOPDIR=$(shell pwd)
 
 all: libosmocore-target nofirmware firmware mtk-firmware
-nofirmware: layer23 osmocon gsmmap
+
+nofirmware: layer23 osmocon gsmmap virtphy
 
 libosmocore-target: shared/libosmocore/build-target/src/.libs/libosmocore.a
 
@@ -45,6 +46,15 @@
 host/osmocon/osmocon: host/osmocon/Makefile
 	make -C host/osmocon
 
+.PHONY: virtphy
+virtphy: host/virt_phy/Makefile
+	cd host/virt_phy && make
+
+host/virt_phy/configure: host/virt_phy/configure.ac
+	cd host/virt_phy && autoreconf -i
+	
+host/virt_phy/Makefile: host/virt_phy/configure
+	cd host/virt_phy && ./configure $(HOST_CONFARGS)
 
 .PHONY: gsmmap
 gsmmap: host/gsmmap/gsmmap
@@ -86,6 +96,7 @@
 	make -C host/layer23 $@
 	make -C host/osmocon $@
 	make -C host/gsmmap $@
+	make -C host/virt_phy $@
 	make -C target/firmware $@
 	make -C target/firmware -f Makefile.mtk $@
 
@@ -94,5 +105,6 @@
 	make -C host/layer23 $@
 	make -C host/osmocon $@
 	make -C host/gsmmap $@
+	make -C host/virt_phy $@
 # 'firmware' also handles 'mtk-firmware'
 	make -C target/firmware $@
diff --git a/src/host/virt_phy/.gitignore b/src/host/virt_phy/.gitignore
new file mode 100644
index 0000000..1e19c7f
--- /dev/null
+++ b/src/host/virt_phy/.gitignore
@@ -0,0 +1,3 @@
+config.h
+config.h.in
+src/virtphy
\ No newline at end of file
diff --git a/src/host/virt_phy/Makefile.am b/src/host/virt_phy/Makefile.am
new file mode 100644
index 0000000..515d51b
--- /dev/null
+++ b/src/host/virt_phy/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = src
+dist_doc_DATA = README
\ No newline at end of file
diff --git a/src/host/virt_phy/README b/src/host/virt_phy/README
new file mode 100644
index 0000000..a780664
--- /dev/null
+++ b/src/host/virt_phy/README
@@ -0,0 +1,2 @@
+This is the package for the Osmocom virtual physical layer.
+A layer 1 implementation satisfying the L1CTL interface towards the l23 application (e.g. mobile).
\ No newline at end of file
diff --git a/src/host/virt_phy/configure.ac b/src/host/virt_phy/configure.ac
new file mode 100644
index 0000000..bcdbf91
--- /dev/null
+++ b/src/host/virt_phy/configure.ac
@@ -0,0 +1,27 @@
+dnl Process this file with autoconf to produce a configure script
+AC_INIT([virtphy], 0.0.0)
+AM_CONFIG_HEADER([config.h])
+AM_INIT_AUTOMAKE([foreign dist-bzip2])
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+
+dnl checks for libraries
+PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore)
+PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm)
+
+dnl checks for header files
+AC_HEADER_STDC
+
+dnl Checks for typedefs, structures and compiler characteristics
+
+AC_CONFIG_FILES([
+ Makefile
+ src/Makefile
+])
+AC_OUTPUT
diff --git a/src/host/virt_phy/example_configs/osmocom-bb-mobile.cfg b/src/host/virt_phy/example_configs/osmocom-bb-mobile.cfg
new file mode 100644
index 0000000..382a09d
--- /dev/null
+++ b/src/host/virt_phy/example_configs/osmocom-bb-mobile.cfg
@@ -0,0 +1,67 @@
+!
+! OsmocomBB (0.0.0) configuration saved from vty
+!!
+!
+line vty
+ no login
+!
+gps device /dev/ttyACM0
+gps baudrate default
+no gps enable
+!
+no hide-default
+!
+ms 1
+ layer2-socket /tmp/osmocom_l2
+ sap-socket /tmp/osmocom_sap
+ sim test
+ network-selection-mode auto
+ imei 123456789012345 0
+ imei-fixed
+ no emergency-imsi
+ no sms-service-center
+ no call-waiting
+ no auto-answer
+ no force-rekey
+ no clip
+ no clir
+ tx-power auto
+ no simulated-delay
+ no stick
+ location-updating
+ neighbour-measurement
+ codec full-speed prefer
+ codec half-speed
+ no abbrev
+ support
+  sms
+  a5/1
+  a5/2
+  p-gsm
+  e-gsm
+  r-gsm
+  no gsm-850
+  dcs
+  no pcs
+  class-900 4
+  class-850 4
+  class-dcs 1
+  class-pcs 1
+  channel-capability sdcch+tchf+tchh
+  full-speech-v1
+  full-speech-v2
+  half-speech-v1
+  min-rxlev -106
+  dsc-max 90
+  no skip-max-per-band
+ exit
+ test-sim
+  imsi 901700000000403
+  ki comp128 12 34 56 78 90 1b cd ef 12 34 56 78 90 ab cd ef 
+  no barred-access
+  no rplmn
+  hplmn-search foreign-country
+ exit
+ no shutdown
+exit
+!
diff --git a/src/host/virt_phy/include/layer1/mframe_sched.h b/src/host/virt_phy/include/layer1/mframe_sched.h
new file mode 100644
index 0000000..ecdb1ec
--- /dev/null
+++ b/src/host/virt_phy/include/layer1/mframe_sched.h
@@ -0,0 +1,68 @@
+#ifndef _L1_MFRAME_SCHED_H
+#define _L1_MFRAME_SCHED_H
+
+#include <stdint.h>
+
+enum mframe_task {
+	MF_TASK_BCCH_NORM,
+	MF_TASK_BCCH_EXT,
+	MF_TASK_CCCH,
+	MF_TASK_CCCH_COMB,
+
+	MF_TASK_SDCCH4_0,
+	MF_TASK_SDCCH4_1,
+	MF_TASK_SDCCH4_2,
+	MF_TASK_SDCCH4_3,
+
+	MF_TASK_SDCCH8_0,
+	MF_TASK_SDCCH8_1,
+	MF_TASK_SDCCH8_2,
+	MF_TASK_SDCCH8_3,
+	MF_TASK_SDCCH8_4,
+	MF_TASK_SDCCH8_5,
+	MF_TASK_SDCCH8_6,
+	MF_TASK_SDCCH8_7,
+
+	MF_TASK_TCH_F_EVEN,
+	MF_TASK_TCH_F_ODD,
+	MF_TASK_TCH_H_0,
+	MF_TASK_TCH_H_1,
+
+	MF_TASK_NEIGH_PM51_C0T0,
+	MF_TASK_NEIGH_PM51,
+	MF_TASK_NEIGH_PM26E,
+	MF_TASK_NEIGH_PM26O,
+
+	/* Test task: send Normal Burst in all timeslots */
+	MF_TASK_UL_ALL_NB,
+};
+
+enum mf_sched_item_flag {
+	MF_F_SACCH	= (1 << 0),
+};
+
+/* The scheduler itself */
+struct mframe_scheduler {
+	uint32_t tasks;
+	uint32_t tasks_tgt;
+	uint32_t safe_fn;
+};
+
+uint8_t mframe_task2chan_nr(enum mframe_task mft, uint8_t ts);
+
+/* Enable a specific task */
+void mframe_enable(enum mframe_task task_id);
+
+/* Disable a specific task */
+void mframe_disable(enum mframe_task task_id);
+
+/* Replace the current active set by the new one */
+void mframe_set(uint32_t tasks);
+
+/* Schedule mframe_sched_items according to current MF TASK list */
+void mframe_schedule(void);
+
+/* reset the scheduler, disabling all tasks */
+void mframe_reset(void);
+
+#endif /* _MFRAME_SCHED_H */
diff --git a/src/host/virt_phy/include/layer1/sync.h b/src/host/virt_phy/include/layer1/sync.h
new file mode 100644
index 0000000..dae85a1
--- /dev/null
+++ b/src/host/virt_phy/include/layer1/sync.h
@@ -0,0 +1,204 @@
+#ifndef _L1_SYNC_H
+#define _L1_SYNC_H
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <layer1/tdma_sched.h>
+#include <layer1/mframe_sched.h>
+#include <l1ctl_proto.h>
+
+/* structure representing L1 sync information about a cell */
+struct l1_cell_info {
+	/* on which ARFCN (+band) is the cell? */
+	uint16_t	arfcn;
+	/* what's the BSIC of the cell (from SCH burst decoding) */
+	uint8_t		bsic;
+	/* Combined or non-combined CCCH */
+	uint8_t		ccch_mode; /* enum ccch_mode */
+	/* whats the delta of the cells current GSM frame number
+	 * compared to our current local frame number */
+	int32_t		fn_offset;
+	/* how much does the TPU need adjustment (delta) to synchronize
+	 * with the cells burst */
+	uint32_t	time_alignment;
+	/* FIXME: should we also store the AFC value? */
+};
+
+enum l1s_chan {
+	L1S_CHAN_MAIN,
+	L1S_CHAN_SACCH,
+	L1S_CHAN_TRAFFIC,
+	_NUM_L1S_CHAN
+};
+
+enum l1_compl {
+	L1_COMPL_FB,
+	L1_COMPL_RACH,
+	L1_COMPL_TX_NB,
+	L1_COMPL_TX_TCH,
+};
+
+typedef void l1_compl_cb(enum l1_compl c);
+
+#define L1S_NUM_COMPL		32
+#define L1S_NUM_NEIGH_CELL	6
+
+struct l1s_h0 {
+	uint16_t arfcn;
+};
+
+struct l1s_h1 {
+	uint8_t hsn;
+	uint8_t maio;
+	uint8_t n;
+	uint16_t ma[64];
+};
+
+struct l1s_state {
+	struct gsm_time	current_time;	/* current GSM time */
+	struct gsm_time	next_time;	/* GSM time at next TMDMA irq */
+
+	/* the cell on which we are camping right now */
+	struct l1_cell_info serving_cell;
+
+	/* neighbor cell sync info */
+	struct l1_cell_info neigh_cell[L1S_NUM_NEIGH_CELL];
+
+	/* TDMA scheduler */
+	struct tdma_scheduler tdma_sched;
+
+	/* Multiframe scheduler */
+	struct mframe_scheduler mframe_sched;
+
+	/* The current TPU offset register */
+	uint32_t	tpu_offset;
+	int32_t		tpu_offset_correction;
+
+	/* TX parameters */
+	int8_t		ta;
+	uint8_t		tx_power;
+
+	/* TCH */
+	uint8_t		tch_mode;
+	uint8_t		tch_sync;
+	uint8_t		audio_mode;
+
+	/* Transmit queues of pending packets for main DCCH and ACCH */
+	struct llist_head tx_queue[_NUM_L1S_CHAN];
+	struct msgb *tx_meas;
+
+	/* Which L1A completions are scheduled right now */
+	uint32_t scheduled_compl;
+	/* callbacks for each of the completions */
+	l1_compl_cb *completion[L1S_NUM_COMPL];
+
+	/* Structures below are for L1-task specific parameters, used
+	 * to communicate between l1-sync and l1-async (l23_api) */
+	struct {
+		uint8_t mode;	/* FB_MODE 0/1 */
+	} fb;
+
+	struct {
+		/* power measurement l1 task */
+		unsigned int mode;
+		union {
+			struct {
+				uint16_t arfcn_start;
+				uint16_t arfcn_next;
+				uint16_t arfcn_end;
+			} range;
+		};
+		struct msgb *msg;
+	} pm;
+
+	struct {
+		uint8_t		ra;
+	} rach;
+
+	struct {
+		enum {
+			GSM_DCHAN_NONE = 0,
+			GSM_DCHAN_SDCCH_4,
+			GSM_DCHAN_SDCCH_8,
+			GSM_DCHAN_TCH_H,
+			GSM_DCHAN_TCH_F,
+			GSM_DCHAN_UNKNOWN,
+		} type;
+
+		uint8_t scn;
+		uint8_t tsc;
+		uint8_t tn;
+		uint8_t h;
+
+		union {
+			struct l1s_h0 h0;
+			struct l1s_h1 h1;
+		};
+
+		uint8_t st_tsc;
+		uint8_t st_tn;
+		uint8_t st_h;
+
+		union {
+			struct l1s_h0 st_h0;
+			struct l1s_h1 st_h1;
+		};
+	} dedicated;
+
+	/* neighbour cell power measurement process */
+	struct {
+		uint8_t n, second;
+		uint8_t pos;
+		uint8_t running;
+		uint16_t band_arfcn[64];
+		uint8_t tn[64];
+		uint8_t	level[64];
+	} neigh_pm;
+};
+
+extern struct l1s_state l1s;
+
+struct l1s_meas_hdr {
+	uint16_t snr;		/* signal/noise ratio */
+	int16_t toa_qbit;	/* time of arrival (qbits) */
+	int16_t pm_dbm8;	/* power level in dbm/8 */
+	int16_t freq_err; 	/* Frequency error in Hz */
+};
+
+int16_t l1s_snr_int(uint16_t snr);
+uint16_t l1s_snr_fract(uint16_t snr);
+
+void l1s_dsp_abort(void);
+
+void l1s_tx_apc_helper(uint16_t arfcn);
+
+/* schedule a completion */
+void l1s_compl_sched(enum l1_compl c);
+
+void l1s_init(void);
+
+/* reset the layer1 as part of synchronizing to a new cell */
+void l1s_reset(void);
+
+/* init.c */
+void layer1_init(void);
+
+/* A debug macro to print every TDMA frame */
+#ifdef DEBUG_EVERY_TDMA
+#define putchart(x) putchar(x)
+#else
+#define putchart(x)
+#endif
+
+/* Convert an angle in fx1.15 notatinon into Hz */
+#define BITFREQ_DIV_2PI		43104	/* 270kHz / 2 * pi */
+#define BITFREQ_DIV_PI		86208	/* 270kHz / pi */
+#define ANG2FREQ_SCALING	(2<<15)	/* 2^15 scaling factor for fx1.15 */
+#define ANGLE_TO_FREQ(angle)	((int16_t)angle * BITFREQ_DIV_PI / ANG2FREQ_SCALING)
+
+void l1s_reset_hw(void);
+void synchronize_tdma(struct l1_cell_info *cinfo);
+void l1s_time_inc(struct gsm_time *time, uint32_t delta_fn);
+void l1s_time_dump(const struct gsm_time *time);
+
+#endif /* _L1_SYNC_H */
diff --git a/src/host/virt_phy/include/layer1/tdma_sched.h b/src/host/virt_phy/include/layer1/tdma_sched.h
new file mode 100644
index 0000000..f58d59b
--- /dev/null
+++ b/src/host/virt_phy/include/layer1/tdma_sched.h
@@ -0,0 +1,73 @@
+#ifndef _L1_TDMA_SCHED_H
+#define _L1_TDMA_SCHED_H
+
+#include <stdint.h>
+
+/* TDMA scheduler */
+
+/* The idea of this scheduler is that we have a circular buffer of buckets,
+ * where each bucket corresponds to one future TDMA frame [interrupt]. Each
+ * bucket contains of a list of callbacks which are executed when the bucket
+ * index reaches that particular bucket. */
+
+#define TDMASCHED_NUM_FRAMES	25
+#define TDMASCHED_NUM_CB	8
+
+#define TDMA_IFLG_TPU		(1<<0)
+#define TDMA_IFLG_DSP		(1<<1)
+
+typedef int tdma_sched_cb(uint8_t p1, uint8_t p2, uint16_t p3);
+
+/* A single item in a TDMA scheduler bucket */
+struct tdma_sched_item {
+	tdma_sched_cb *cb;
+	uint8_t p1;
+	uint8_t p2;
+	uint16_t p3;
+	int16_t prio;
+	uint16_t flags;		/* TDMA_IFLG_xxx */
+};
+
+/* A bucket inside the TDMA scheduler */
+struct tdma_sched_bucket {
+	struct tdma_sched_item item[TDMASCHED_NUM_CB];
+	uint8_t num_items;
+};
+
+/* The scheduler itself, consisting of buckets and a current index */
+struct tdma_scheduler {
+	struct tdma_sched_bucket bucket[TDMASCHED_NUM_FRAMES];
+	uint8_t cur_bucket;
+};
+
+/* Schedule an item at 'frame_offset' TDMA frames in the future */
+int tdma_schedule(uint8_t frame_offset, tdma_sched_cb *cb,
+                  uint8_t p1, uint8_t p2, uint16_t p3, int16_t prio);
+
+/* Schedule a set of items starting from 'frame_offset' TDMA frames in the future */
+int tdma_schedule_set(uint8_t frame_offset, const struct tdma_sched_item *item_set, uint16_t p3);
+
+/* Scan current frame scheduled items for flags */
+uint16_t tdma_sched_flag_scan(void);
+
+/* Execute pre-scheduled events for current frame */
+int tdma_sched_execute(void);
+
+/* Advance TDMA scheduler to the next bucket */
+void tdma_sched_advance(void);
+
+/* reset the scheduler; erase all scheduled items */
+void tdma_sched_reset(void);
+
+/* debug function: print number of entries of all TDMA buckets */
+void tdma_sched_dump(void);
+
+
+extern int tdma_end_set(uint8_t p1, uint8_t p2, uint16_t p3);
+#define SCHED_ITEM(x, p, y, z)		{ .cb = x, .p1 = y, .p2 = z, .prio = p, .flags = 0 }
+#define SCHED_ITEM_DT(x, p, y, z)	{ .cb = x, .p1 = y, .p2 = z, .prio = p, \
+					  .flags = TDMA_IFLG_TPU | TDMA_IFLG_DSP }
+#define SCHED_END_FRAME()		{ .cb = NULL, .p1 = 0, .p2 = 0 }
+#define SCHED_END_SET()			{ .cb = &tdma_end_set, .p1 = 0, .p2 = 0 }
+
+#endif /* _L1_TDMA_SCHED_H */
diff --git a/src/host/virt_phy/src/Makefile.am b/src/host/virt_phy/src/Makefile.am
new file mode 100644
index 0000000..a110435
--- /dev/null
+++ b/src/host/virt_phy/src/Makefile.am
@@ -0,0 +1,19 @@
+OSMOCOM_DIR = /home/basti/Osmocom
+OSMO_BB_DIR = $(OSMOCOM_DIR)/osmocom-bb
+OSMO_BTS_DIR = $(OSMOCOM_DIR)/osmo-bts
+OPENBSC_DIR = $(OSMOCOM_DIR)/openbsc
+
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS)
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OSMO_BB_DIR)/include -I$(OSMO_BTS_DIR)/include -I$(OPENBSC_DIR)/openbsc/include -I$(OSMO_BB_DIR)/src/host/layer23/include
+# TODO: somehow this include path causes errors, thus the needed files are copied into $(top_srcdir)/include. Would be better to include directly from $(OSMO_BB_DIR)/src/target/firmware/include to avoid redundancy.
+# -I$(OSMO_BB_DIR)/src/target/firmware/include
+
+CFLAGS = -g -O0
+
+sbin_PROGRAMS = virtphy
+virtphy_SOURCES = virtphy.c l1ctl_sock.c l1ctl_sap.c gsmtapl1_if.c logging.c virt_l1_model.c virtual_um.c osmo_mcast_sock.c
+virtphy_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS)
+
+# debug output
+all:
+	$(info $$AM_CPPFLAGS is [${AM_CPPFLAGS}])
diff --git a/src/host/virt_phy/src/gsmtapl1_if.c b/src/host/virt_phy/src/gsmtapl1_if.c
new file mode 100644
index 0000000..11214cf
--- /dev/null
+++ b/src/host/virt_phy/src/gsmtapl1_if.c
@@ -0,0 +1,282 @@
+/* GSMTAP layer1 is transmits gsmtap messages over a virtual layer 1.*/
+
+/* (C) 2016 Sebastian Stumpf
+ *
+ * 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/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/core/msgb.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <l1ctl_proto.h>
+
+#include "virtual_um.h"
+#include "l1ctl_sock.h"
+#include "virt_l1_model.h"
+#include "l1ctl_sap.h"
+#include "gsmtapl1_if.h"
+#include "logging.h"
+
+static struct l1_model_ms *l1_model_ms = NULL;
+
+// for debugging
+static const struct value_string gsmtap_channels [22] = {
+	{ GSMTAP_CHANNEL_UNKNOWN,	"UNKNOWN" },
+	{ GSMTAP_CHANNEL_BCCH,		"BCCH" },
+	{ GSMTAP_CHANNEL_CCCH,		"CCCH" },
+	{ GSMTAP_CHANNEL_RACH,		"RACH" },
+	{ GSMTAP_CHANNEL_AGCH,		"AGCH" },
+	{ GSMTAP_CHANNEL_PCH,		"PCH" },
+	{ GSMTAP_CHANNEL_SDCCH,		"SDCCH" },
+	{ GSMTAP_CHANNEL_SDCCH4,	"SDCCH/4" },
+	{ GSMTAP_CHANNEL_SDCCH8,	"SDCCH/8" },
+	{ GSMTAP_CHANNEL_TCH_F,		"FACCH/F" },
+	{ GSMTAP_CHANNEL_TCH_H,		"FACCH/H" },
+	{ GSMTAP_CHANNEL_PACCH,		"PACCH" },
+	{ GSMTAP_CHANNEL_CBCH52,    	"CBCH" },
+	{ GSMTAP_CHANNEL_PDCH,      	"PDCH" },
+	{ GSMTAP_CHANNEL_PTCCH,    	"PTTCH" },
+	{ GSMTAP_CHANNEL_CBCH51,    	"CBCH" },
+        { GSMTAP_CHANNEL_ACCH|
+	  GSMTAP_CHANNEL_SDCCH,		"LSACCH" },
+	{ GSMTAP_CHANNEL_ACCH|
+	  GSMTAP_CHANNEL_SDCCH4,	"SACCH/4" },
+	{ GSMTAP_CHANNEL_ACCH|
+	  GSMTAP_CHANNEL_SDCCH8,	"SACCH/8" },
+	{ GSMTAP_CHANNEL_ACCH|
+	  GSMTAP_CHANNEL_TCH_F,		"SACCH/F" },
+	{ GSMTAP_CHANNEL_ACCH|
+	  GSMTAP_CHANNEL_TCH_H,		"SACCH/H" },
+	{ 0,				NULL },
+};
+// for debugging
+static const struct value_string gsmtap_types [10] = {
+	{ GSMTAP_TYPE_UM,		"GSM Um (MS<->BTS)" },
+	{ GSMTAP_TYPE_ABIS,		"GSM Abis (BTS<->BSC)" },
+	{ GSMTAP_TYPE_UM_BURST,		"GSM Um burst (MS<->BTS)" },
+	{ GSMTAP_TYPE_SIM,		"SIM" },
+	{ GSMTAP_TYPE_TETRA_I1, 	"TETRA V+D"},
+	{ GSMTAP_TYPE_WMX_BURST,	"WiMAX burst" },
+	{ GSMTAP_TYPE_GMR1_UM, 		"GMR-1 air interfeace (MES-MS<->GTS)" },
+	{ GSMTAP_TYPE_UMTS_RLC_MAC,	"UMTS RLC/MAC" },
+	{ GSMTAP_TYPE_UMTS_RRC,		"UMTS RRC" },
+	{ 0,				NULL },
+};
+
+void gsmtapl1_init(struct l1_model_ms *model)
+{
+	l1_model_ms = model;
+}
+
+/**
+ * Append a gsmtap header to msg and send it over the virt um.
+ */
+void gsmtapl1_tx_to_virt_um_inst(struct virt_um_inst *vui, struct msgb *msg)
+{
+	struct l1ctl_hdr *l1hdr = (struct l1ctl_hdr *)msg->l1h;
+	struct l1ctl_info_dl *l1dl = (struct l1ctl_info_dl *)msg->data;
+	uint8_t ss = 0;
+	uint8_t gsmtap_chan;
+	struct msgb *outmsg;
+
+	switch (l1hdr->msg_type) {
+	case L1CTL_DATA_REQ:
+		// TODO: check what data request and set gsmtap_chan depending on that
+		gsmtap_chan = 0;
+		break;
+	}
+	outmsg = gsmtap_makemsg(l1dl->band_arfcn, l1dl->chan_nr, gsmtap_chan,
+	                ss, l1dl->frame_nr, 0, 0, msgb_l2(msg),
+	                msgb_l2len(msg));
+	if (outmsg) {
+		struct gsmtap_hdr *gh = (struct gsmtap_hdr *)outmsg->l1h;
+		virt_um_write_msg(vui, outmsg);
+		DEBUGP(DVIRPHY,
+		                "Sending gsmtap msg to virt um - (arfcn=%u, type=%u, subtype=%u, timeslot=%u, subslot=%u)\n",
+		                gh->arfcn, gh->type, gh->sub_type, gh->timeslot,
+		                gh->sub_slot);
+	} else {
+		LOGP(DVIRPHY, LOGL_ERROR, "Gsmtap msg could not be created!\n");
+	}
+
+	/* free message */
+	msgb_free(msg);
+}
+
+/**
+ * @see void gsmtapl1_tx_to_virt_um(struct virt_um_inst *vui, struct msgb *msg).
+ */
+void gsmtapl1_tx_to_virt_um(struct msgb *msg)
+{
+	gsmtapl1_tx_to_virt_um_inst(l1_model_ms->vui, msg);
+}
+
+/* This is the header as it is used by gsmtap peer virtual layer 1.
+struct gsmtap_hdr {
+	guint8 version;		// version, set to 0x01 currently
+	guint8 hdr_len;		// length in number of 32bit words
+	guint8 type;		// see GSMTAP_TYPE_*
+	guint8 timeslot;	// timeslot (0..7 on Um)
+	guint16 arfcn;		// ARFCN (frequency)
+	gint8 signal_dbm;	// signal level in dBm
+	gint8 snr_db;		// signal/noise ratio in dB
+	guint32 frame_number;	// GSM Frame Number (FN)
+	guint8 sub_type;	// Type of burst/channel, see above
+	guint8 antenna_nr;	// Antenna Number
+	guint8 sub_slot;	// sub-slot within timeslot
+	guint8 res;		// reserved for future use (RFU)
+}
+ */
+
+/**
+ * Receive a gsmtap message from the virt um.
+ */
+void gsmtapl1_rx_from_virt_um_inst_cb(struct virt_um_inst *vui,
+                                      struct msgb *msg)
+{
+	if (msg) {
+		struct gsmtap_hdr *gh;
+		struct l1ctl_info_dl *l1dl;
+		struct msgb *l1ctl_msg = NULL;
+		struct l1ctl_data_ind * l1di;
+
+		msg->l1h = msgb_data(msg);
+		msg->l2h = msgb_pull(msg, sizeof(*gh));
+		gh = msgb_l1(msg);
+
+		DEBUGP(DVIRPHY,
+		                "Receiving gsmtap msg from virt um - (arfcn=%u, framenumber=%u, type=%s, subtype=%s, timeslot=%u, subslot=%u)\n",
+		                ntohs(gh->arfcn), ntohl(gh->frame_number), get_value_string(gsmtap_types, gh->type), get_value_string(gsmtap_channels, gh->sub_type), gh->timeslot,
+		                gh->sub_slot);
+
+		// compose the l1ctl message for layer 2
+		switch (gh->sub_type) {
+		case GSMTAP_CHANNEL_RACH:
+			LOGP(DL1C, LOGL_NOTICE,
+			                "Ignoring gsmtap msg from virt um - channel type is uplink only!\n");
+			break;
+		case GSMTAP_CHANNEL_SDCCH:
+		case GSMTAP_CHANNEL_SDCCH4:
+		case GSMTAP_CHANNEL_SDCCH8:
+			l1ctl_msg = l1ctl_msgb_alloc(L1CTL_DATA_IND);
+			// TODO: implement channel handling
+			break;
+		case GSMTAP_CHANNEL_TCH_F:
+			l1ctl_msg = l1ctl_msgb_alloc(L1CTL_TRAFFIC_IND);
+			// TODO: implement channel handling
+			break;
+		case GSMTAP_CHANNEL_AGCH:
+		case GSMTAP_CHANNEL_PCH:
+		case GSMTAP_CHANNEL_BCCH:
+			l1ctl_msg = l1ctl_msgb_alloc(L1CTL_DATA_IND);
+			l1dl = (struct l1ctl_info_dl *) msgb_put(l1ctl_msg, sizeof(struct l1ctl_info_dl));
+			l1di = (struct l1ctl_data_ind *) msgb_put(l1ctl_msg, sizeof(struct l1ctl_data_ind));
+
+			l1dl->band_arfcn = htons(ntohs(gh->arfcn));
+			l1dl->link_id = gh->timeslot;
+			// see GSM 8.58 -> 9.3.1 for channel number encoding
+			l1dl->chan_nr = rsl_enc_chan_nr(chantype_gsmtap2rsl(gh->sub_type), gh->sub_slot, gh->timeslot);
+			l1dl->frame_nr = htonl(ntohl(gh->frame_number));
+			l1dl->snr = gh->snr_db;
+			l1dl->rx_level = gh->signal_dbm;
+			l1dl->num_biterr = 0;
+			l1dl->fire_crc = 0;
+
+			memcpy(l1di->data, msgb_data(msg), msgb_length(msg));
+
+			break;
+		case GSMTAP_CHANNEL_CCCH:
+		case GSMTAP_CHANNEL_TCH_H:
+		case GSMTAP_CHANNEL_PACCH:
+		case GSMTAP_CHANNEL_PDCH:
+		case GSMTAP_CHANNEL_PTCCH:
+		case GSMTAP_CHANNEL_CBCH51:
+		case GSMTAP_CHANNEL_CBCH52:
+			LOGP(DL1C, LOGL_NOTICE,
+			                "Ignoring gsmtap msg from virt um - channel type not supported!\n");
+			break;
+		default:
+			LOGP(DL1C, LOGL_NOTICE,
+			                "Ignoring gsmtap msg from virt um - channel type unknown.\n");
+			break;
+		}
+
+		/* forward l1ctl message to l2 */
+		if(l1ctl_msg) {
+			l1ctl_sap_tx_to_l23(l1ctl_msg);
+		}
+
+		// handle memory deallocation
+		talloc_free(msg);
+	}
+}
+
+/**
+ * @see void gsmtapl1_rx_from_virt_um_cb(struct virt_um_inst *vui, struct msgb msg).
+ */
+void gsmtapl1_rx_from_virt_um(struct msgb *msg)
+{
+	gsmtapl1_rx_from_virt_um_inst_cb(l1_model_ms->vui, msg);
+}
+
+/*! \brief convert GSMTAP channel type to RSL channel number
+ *  \param[in] rsl_chantype RSL channel type
+ *  \param[in] link_id RSL link identifier
+ *  \returns GSMTAP channel type
+ */
+uint8_t chantype_gsmtap2rsl(uint8_t gsmtap_chantype)
+{
+	// TODO: proper retval for unknown channel
+	uint8_t ret = 0;
+
+	switch (gsmtap_chantype) {
+	case GSMTAP_CHANNEL_TCH_F:
+		ret = RSL_CHAN_Bm_ACCHs;
+		break;
+	case GSMTAP_CHANNEL_TCH_H:
+		ret = RSL_CHAN_Lm_ACCHs;
+		break;
+	case GSMTAP_CHANNEL_SDCCH4:
+		ret = RSL_CHAN_SDCCH4_ACCH;
+		break;
+	case GSMTAP_CHANNEL_SDCCH8:
+		ret = RSL_CHAN_SDCCH8_ACCH;
+		break;
+	case GSMTAP_CHANNEL_BCCH:
+		ret = RSL_CHAN_BCCH;
+		break;
+	case GSMTAP_CHANNEL_RACH:
+		ret = RSL_CHAN_RACH;
+		break;
+	case GSMTAP_CHANNEL_PCH:
+	case GSMTAP_CHANNEL_AGCH:
+		ret = RSL_CHAN_PCH_AGCH;
+		break;
+	}
+
+	// TODO: check how to handle this...
+//	if (link_id & 0x40)
+//		ret |= GSMTAP_CHANNEL_ACCH;
+
+	return ret;
+}
diff --git a/src/host/virt_phy/src/gsmtapl1_if.h b/src/host/virt_phy/src/gsmtapl1_if.h
new file mode 100644
index 0000000..8c7491c
--- /dev/null
+++ b/src/host/virt_phy/src/gsmtapl1_if.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/gsmtap.h>
+
+#include "l1ctl_sock.h"
+#include "virtual_um.h"
+#include "virt_l1_model.h"
+
+void gsmtapl1_init(struct l1_model_ms *model);
+
+void gsmtapl1_rx_from_virt_um_inst_cb(struct virt_um_inst *vui, struct msgb *msg);
+void gsmtapl1_rx_from_virt_um(struct msgb *msg);
+
+void gsmtapl1_tx_to_virt_um_inst(struct virt_um_inst *vui, struct msgb *msg);
+void gsmtapl1_tx_to_virt_um(struct msgb *msg);
+
+uint8_t chantype_gsmtap2rsl(uint8_t gsmtap_chantype);
diff --git a/src/host/virt_phy/src/l1ctl_sap.c b/src/host/virt_phy/src/l1ctl_sap.c
new file mode 100644
index 0000000..46121ed
--- /dev/null
+++ b/src/host/virt_phy/src/l1ctl_sap.c
@@ -0,0 +1,833 @@
+/* L1CTL SAP implementation.  */
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include <stdio.h>
+#include <l1ctl_proto.h>
+#include <netinet/in.h>
+
+#include "virtual_um.h"
+#include "l1ctl_sock.h"
+#include "virt_l1_model.h"
+#include "l1ctl_sap.h"
+#include "logging.h"
+
+static struct l1_model_ms *l1_model_ms = NULL;
+
+/**
+ * @brief Init the SAP.
+ */
+void l1ctl_sap_init(struct l1_model_ms *model)
+{
+	l1_model_ms = model;
+}
+
+/**
+ * @brief L1CTL handler called for received messages from L23.
+ *
+ * Enqueues the message into the rx queue.
+ */
+void l1ctl_sap_rx_from_l23_inst_cb(struct l1ctl_sock_inst *lsi, struct msgb *msg)
+{
+	if (msg) {
+		DEBUGP(DL1C, "Message incoming from layer 2: %s\n",
+		                osmo_hexdump(msg->data, msg->len));
+		l1ctl_sap_handler(msg);
+	}
+}
+/**
+ * @see l1ctl_sap_rx_from_l23_cb(struct l1ctl_sock_inst *lsi, struct msgb *msg).
+ */
+void l1ctl_sap_rx_from_l23(struct msgb *msg)
+{
+	l1ctl_sap_rx_from_l23_inst_cb(l1_model_ms->lsi, msg);
+}
+
+/**
+ * @brief Send a l1ctl message to layer 23.
+ *
+ * This will forward the message as it is to the upper layer.
+ */
+void l1ctl_sap_tx_to_l23_inst(struct l1ctl_sock_inst *lsi, struct msgb *msg)
+{
+	uint16_t *len;
+	/* prepend 16bit length before sending */
+	len = (uint16_t *) msgb_push(msg, sizeof(*len));
+	*len = htons(msg->len - sizeof(*len));
+
+	if(l1ctl_sock_write_msg(lsi, msg) == -1 ) {
+		//DEBUGP(DL1C, "Error writing to layer2 socket");
+	}
+}
+
+/**
+ * @see void l1ctl_sap_tx_to_l23(struct l1ctl_sock_inst *lsi, struct msgb *msg).
+ */
+void l1ctl_sap_tx_to_l23(struct msgb *msg)
+{
+	l1ctl_sap_tx_to_l23_inst(l1_model_ms->lsi, msg);
+}
+
+/**
+ * @brief Allocates a msgb with set l1ctl header and room for a l3 header.
+ *
+ * @param [in] msg_type L1CTL primitive message type set to l1ctl_hdr.
+ * @return the allocated message.
+ *
+ * The message looks as follows:
+ * # headers
+ * [l1ctl_hdr]		: initialized. msgb->l1h points here
+ * [spare-bytes]	: L3_MSG_HEAD bytes reserved for l3 header
+ * # data
+ * [spare-bytes]	: L3_MSG_DATA bytes reserved for data. msgb->tail points here. msgb->data points here.
+ */
+struct msgb *l1ctl_msgb_alloc(uint8_t msg_type)
+{
+	struct msgb *msg;
+	struct l1ctl_hdr *l1h;
+	msg = msgb_alloc_headroom(L3_MSG_SIZE, L3_MSG_HEAD, "l1ctl");
+	if (!msg) {
+		while (1) {
+			puts("OOPS. Out of buffers...\n");
+		}
+
+		return NULL;
+	}
+	l1h = (struct l1ctl_hdr *)msgb_put(msg, sizeof(*l1h));
+	l1h->msg_type = msg_type;
+	l1h->flags = 0;
+
+	msg->l1h = (uint8_t *)l1h;
+
+	return msg;
+}
+
+/**
+ * @brief Allocates a msgb with set l1ctl header and room for a l3 header and puts l1ctl_info_dl to the msgb data.
+ *
+ * @param [in] msg_type L1CTL primitive message type set to l1ctl_hdr.
+ * @param [in] fn framenumber put into l1ctl_info_dl.
+ * @param [in] snr time slot number put into l1ctl_info_dl.
+ * @param [in] arfcn arfcn put into l1ctl_info_dl.
+ * @return the allocated message.
+ *
+ * The message looks as follows:
+ * # headers
+ * [l1ctl_hdr]		: initialized. msgb->l1h points here
+ * [spare-bytes]	: L3_MSG_HEAD bytes reserved for l3 header
+ * # data
+ * [l1ctl_info_dl]	: initialized with params. msgb->data points here.
+ * [spare-bytes]	: L3_MSG_DATA bytes reserved for data. msgb->tail points here.
+ */
+struct msgb *l1ctl_create_l2_msg(int msg_type, uint32_t fn, uint16_t snr,
+                                 uint16_t arfcn)
+{
+	struct l1ctl_info_dl *dl;
+	struct msgb *msg = l1ctl_msgb_alloc(msg_type);
+
+	dl = (struct l1ctl_info_dl *)msgb_put(msg, sizeof(*dl));
+	dl->frame_nr = htonl(fn);
+	dl->snr = snr;
+	dl->band_arfcn = htons(arfcn);
+
+	return msg;
+}
+
+/**
+ * @brief General handler for incoming L1CTL messages from layer 2/3.
+ *
+ * This handler will dequeue the rx queue (if !empty) and call the specific routine for the dequeued l1ctl message.
+ *
+ */
+void l1ctl_sap_handler(struct msgb *msg)
+{
+//	struct msgb *msg;
+	struct l1ctl_hdr *l1h;
+	unsigned long flags;
+
+	if (!msg)
+		return;
+
+	l1h = (struct l1ctl_hdr *)msg->data;
+
+	if (sizeof(*l1h) > msg->len) {
+		LOGP(DL1C, LOGL_NOTICE, "Short message. %u\n", msg->len);
+		goto exit_msgbfree;
+	}
+
+	switch (l1h->msg_type) {
+	case L1CTL_FBSB_REQ:
+		l1ctl_rx_fbsb_req(msg);
+		break;
+	case L1CTL_DM_EST_REQ:
+		l1ctl_rx_dm_est_req(msg);
+		break;
+	case L1CTL_DM_REL_REQ:
+		l1ctl_rx_dm_rel_req(msg);
+		break;
+	case L1CTL_PARAM_REQ:
+		l1ctl_rx_param_req(msg);
+		break;
+	case L1CTL_DM_FREQ_REQ:
+		l1ctl_rx_dm_freq_req(msg);
+		break;
+	case L1CTL_CRYPTO_REQ:
+		l1ctl_rx_crypto_req(msg);
+		break;
+	case L1CTL_RACH_REQ:
+		l1ctl_rx_rach_req(msg);
+		break;
+	case L1CTL_DATA_REQ:
+		l1ctl_rx_data_req(msg);
+		/* we have to keep the msgb, not free it! */
+		goto exit_nofree;
+	case L1CTL_PM_REQ:
+		l1ctl_rx_pm_req(msg);
+		break;
+	case L1CTL_RESET_REQ:
+		l1ctl_rx_reset_req(msg);
+		break;
+	case L1CTL_CCCH_MODE_REQ:
+		l1ctl_rx_ccch_mode_req(msg);
+		break;
+	case L1CTL_TCH_MODE_REQ:
+		l1ctl_rx_tch_mode_req(msg);
+		break;
+	case L1CTL_NEIGH_PM_REQ:
+		l1ctl_rx_neigh_pm_req(msg);
+		break;
+	case L1CTL_TRAFFIC_REQ:
+		l1ctl_rx_traffic_req(msg);
+		/* we have to keep the msgb, not free it! */
+		goto exit_nofree;
+	case L1CTL_SIM_REQ:
+		l1ctl_rx_sim_req(msg);
+		break;
+	}
+
+	exit_msgbfree: msgb_free(msg);
+	exit_nofree: return;
+}
+
+/***************************************************************
+ * L1CTL RX ROUTINES *******************************************
+ ***************************************************************/
+
+/**
+ * @brief Handler for received L1CTL_FBSB_REQ from L23.
+ *
+ * -- frequency burst synchronisation burst request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Transmit frequency control and synchronisation bursts on FCCH and SCH to calibrate transceiver and search for base stations.
+ * Sync to a given arfcn.
+ *
+ * Note: Not needed for virtual physical layer.
+ * TODO: Could be used to bind/connect to different virtual_bts sockets with a arfcn-socket mapping.
+ */
+void l1ctl_rx_fbsb_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_fbsb_req *sync_req = (struct l1ctl_fbsb_req *)l1h->data;
+
+	DEBUGP(DL1C,
+	                "Received and handled from l23 - L1CTL_FBSB_REQ (arfcn=%u, flags=0x%x)\n",
+	                ntohs(sync_req->band_arfcn), sync_req->flags);
+
+	l1ctl_tx_fbsb_conf(0, ntohs(sync_req->band_arfcn));
+}
+
+/**
+ * @brief Handler for received L1CTL_DM_EST_REQ from L23.
+ *
+ * -- dedicated mode established request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Handle state change from idle to dedicated mode.
+ *
+ * TODO: Implement this handler routine!
+ */
+void l1ctl_rx_dm_est_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data;
+	struct l1ctl_dm_est_req *est_req =
+	                (struct l1ctl_dm_est_req *)ul->payload;
+
+	DEBUGP(DL1C,
+	                "Received and handled from l23 - L1CTL_DM_EST_REQ (arfcn=%u, chan_nr=0x%02x, tsc=%u)\n",
+	                ntohs(est_req->h0.band_arfcn), ul->chan_nr,
+	                est_req->tsc);
+
+//	/* disable neighbour cell measurement of C0 TS 0 */
+//	mframe_disable(MF_TASK_NEIGH_PM51_C0T0);
+//
+//	/* configure dedicated channel state */
+//	l1s.dedicated.type = chan_nr2dchan_type(ul->chan_nr);
+//	l1s.dedicated.tsc  = est_req->tsc;
+//	l1s.dedicated.tn   = ul->chan_nr & 0x7;
+//	l1s.dedicated.h    = est_req->h;
+//
+//	if (est_req->h) {
+//		int i;
+//		l1s.dedicated.h1.hsn  = est_req->h1.hsn;
+//		l1s.dedicated.h1.maio = est_req->h1.maio;
+//		l1s.dedicated.h1.n    = est_req->h1.n;
+//		for (i=0; i<est_req->h1.n; i++)
+//			l1s.dedicated.h1.ma[i] = ntohs(est_req->h1.ma[i]);
+//	} else {
+//		l1s.dedicated.h0.arfcn = ntohs(est_req->h0.band_arfcn);
+//	}
+//
+//	/* TCH config */
+//	if (chan_nr_is_tch(ul->chan_nr)) {
+//		/* Mode */
+//		l1a_tch_mode_set(est_req->tch_mode);
+//		l1a_audio_mode_set(est_req->audio_mode);
+//
+//		/* Sync */
+//		l1s.tch_sync = 1;	/* can be set without locking */
+//
+//		/* Audio path */
+//		audio_set_enabled(est_req->tch_mode, est_req->audio_mode);
+//	}
+//
+//	/* figure out which MF tasks to enable */
+//	l1a_mftask_set(chan_nr2mf_task_mask(ul->chan_nr, NEIGH_MODE_PM));
+}
+
+/**
+ * @brief Handler for received L1CTL_DM_FREQ_REQ from L23.
+ *
+ * -- dedicated mode frequency request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Handle frequency change in dedicated mode. E.g. used for frequency hopping.
+ *
+ * Note: Not needed for virtual physical layer.
+ */
+void l1ctl_rx_dm_freq_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data;
+	struct l1ctl_dm_freq_req *freq_req =
+	                (struct l1ctl_dm_freq_req *)ul->payload;
+
+	DEBUGP(DL1C,
+	                "Received and ignored from l23 - L1CTL_DM_FREQ_REQ (arfcn=%u, tsc=%u)\n",
+	                ntohs(freq_req->h0.band_arfcn), freq_req->tsc);
+}
+
+/**
+ * @brief Handler for received L1CTL_CRYPTO_REQ from L23.
+ *
+ * -- cryptographic request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Configure the key and algorithm used for cryptographic operations in the DSP (Digital Signal Processor).
+ *
+ * Note: in the virtual physical layer the cryptographic operations are not handled in the DSP.
+ *
+ * TODO: Implement cryptographic operations for virtual um!
+ * TODO: Implement this handler routine!
+ */
+void l1ctl_rx_crypto_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data;
+	struct l1ctl_crypto_req *cr = (struct l1ctl_crypto_req *)ul->payload;
+	uint8_t key_len = msg->len - sizeof(*l1h) - sizeof(*ul) - sizeof(*cr);
+
+	DEBUGP(DL1C,
+	                "Received and handled from l23 - L1CTL_CRYPTO_REQ (algo=A5/%u, len=%u)\n",
+	                cr->algo, key_len);
+
+//	if (cr->algo && key_len != 8) {
+//		DEBUGP(DL1C, "L1CTL_CRYPTO_REQ -> Invalid key\n");
+//		return;
+//	}
+//
+//	dsp_load_ciph_param(cr->algo, cr->key);
+}
+
+/**
+ * @brief Handler for received L1CTL_DM_REL_REQ from L23.
+ *
+ * -- dedicated mode release request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Handle state change from dedicated to idle mode. Flush message buffers of dedicated channel.
+ *
+ * TODO: Implement this handler routine!
+ */
+void l1ctl_rx_dm_rel_req(struct msgb *msg)
+{
+//	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+
+	DEBUGP(DL1C, "Received and ignored from l23 - L1CTL_DM_REL_REQ\n");
+//	l1a_mftask_set(0);
+//	l1s.dedicated.type = GSM_DCHAN_NONE;
+//	l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_MAIN]);
+//	l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_SACCH]);
+//	l1a_txq_msgb_flush(&l1s.tx_queue[L1S_CHAN_TRAFFIC]);
+//	l1a_meas_msgb_set(NULL);
+//	dsp_load_ciph_param(0, NULL);
+//	l1a_tch_mode_set(GSM48_CMODE_SIGN);
+//	audio_set_enabled(GSM48_CMODE_SIGN, 0);
+//	l1s.neigh_pm.n = 0;
+}
+
+/**
+ * @brief Handler for received L1CTL_PARAM_REQ from L23.
+ *
+ * -- parameter request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Configure transceiver parameters timing advance value and sending power.
+ *
+ * Note: Not needed for virtual physical layer.
+ */
+void l1ctl_rx_param_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data;
+	struct l1ctl_par_req *par_req = (struct l1ctl_par_req *)ul->payload;
+
+	DEBUGP(DL1C,
+	                "Received and ignored from l23 - L1CTL_PARAM_REQ (ta=%d, tx_power=%d)\n",
+	                par_req->ta, par_req->tx_power);
+}
+
+/**
+ * @brief Handler for received L1CTL_RACH_REQ from L23.
+ *
+ * -- random access channel request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Transmit RACH request on RACH.
+ *
+ * TODO: Implement this handler routine!
+ */
+void l1ctl_rx_rach_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data;
+	struct l1ctl_rach_req *rach_req = (struct l1ctl_rach_req *)ul->payload;
+
+	DEBUGP(DL1C,
+	                "Received and handled from l23 - L1CTL_RACH_REQ (ra=0x%02x, offset=%d combined=%d)\n",
+	                rach_req->ra, ntohs(rach_req->offset),
+	                rach_req->combined);
+
+//	l1a_rach_req(ntohs(rach_req->offset), rach_req->combined,
+//		rach_req->ra);
+}
+
+/**
+ * @brief Handler for received L1CTL_DATA_REQ from L23.
+ *
+ * -- data request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Transmit message on a signalling channel. FACCH/SDCCH or SACCH depending on the headers set link id (TS 8.58 - 9.3.2).
+ *
+ * TODO: Implement this handler routine!
+ */
+void l1ctl_rx_data_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data;
+	struct l1ctl_data_ind *data_ind = (struct l1ctl_data_ind *)ul->payload;
+	struct llist_head *tx_queue;
+
+	DEBUGP(DL1C,
+	                "Received and handled from l23 - L1CTL_DATA_REQ (link_id=0x%02x)\n",
+	                ul->link_id);
+
+//	msg->l3h = data_ind->data;
+//	if (ul->link_id & 0x40) {
+//		struct gsm48_hdr *gh = (struct gsm48_hdr *)(data_ind->data + 5);
+//		if (gh->proto_discr == GSM48_PDISC_RR
+//		 && gh->msg_type == GSM48_MT_RR_MEAS_REP) {
+//			DEBUGP(DL1C, "updating measurement report\n");
+//			l1a_meas_msgb_set(msg);
+//			return;
+//		}
+//		tx_queue = &l1s.tx_queue[L1S_CHAN_SACCH];
+//	} else
+//		tx_queue = &l1s.tx_queue[L1S_CHAN_MAIN];
+//
+//	DEBUGP(DL1C, "ul=%p, ul->payload=%p, data_ind=%p, data_ind->data=%p l3h=%p\n",
+//		ul, ul->payload, data_ind, data_ind->data, msg->l3h);
+//
+//	l1a_txq_msgb_enq(tx_queue, msg);
+}
+
+/**
+ * @brief Handler for received L1CTL_PM_REQ from L23.
+ *
+ * -- power measurement request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Process power measurement for a given range of arfcns to calculate signal power and connection quality.
+ *
+ * Note: We do not need to calculate that for the virtual physical layer, but l23 apps can expect a response. So this response is mocked here.
+ */
+void l1ctl_rx_pm_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_pm_req *pm_req = (struct l1ctl_pm_req *)l1h->data;
+	struct msgb *resp_msg = l1ctl_msgb_alloc(L1CTL_PM_CONF);
+	uint16_t arfcn_next;
+	// convert to host order
+	pm_req->range.band_arfcn_from = ntohs(pm_req->range.band_arfcn_from);
+	pm_req->range.band_arfcn_to = ntohs(pm_req->range.band_arfcn_to);
+
+	DEBUGP(DL1C, "Received from l23 - L1CTL_PM_REQ TYPE=%u, FROM=%d, TO=%d\n",
+	                pm_req->type, pm_req->range.band_arfcn_from, pm_req->range.band_arfcn_to);
+
+	for(arfcn_next = pm_req->range.band_arfcn_from; arfcn_next <= pm_req->range.band_arfcn_to; ++arfcn_next) {
+		struct l1ctl_pm_conf *pm_conf = (struct l1ctl_pm_conf *)msgb_put(resp_msg, sizeof(*pm_conf));
+		pm_conf->band_arfcn = htons(arfcn_next);
+		// rxlev 63 is great, 0 is bad the two values are probably min and max
+		pm_conf->pm[0] = 63;
+		pm_conf->pm[1] = 63;
+		if(arfcn_next == pm_req->range.band_arfcn_to) {
+			struct l1ctl_hdr *resp_l1h = resp_msg->l1h;
+			resp_l1h->flags |= L1CTL_F_DONE;
+		}
+		// no more space in msgb, flush to l2
+		if(msgb_tailroom(resp_msg) < sizeof(*pm_conf)) {
+			l1ctl_sap_tx_to_l23(resp_msg);
+			resp_msg = l1ctl_msgb_alloc(L1CTL_PM_CONF);
+		}
+	}
+	if(resp_msg) {
+		l1ctl_sap_tx_to_l23(resp_msg);
+	}
+}
+
+/**
+ * @brief Handler for received L1CTL_RESET_REQ from L23.
+ *
+ * -- reset request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Reset layer 1 (state machine, scheduler, transceiver) depending on the reset type.
+ *
+ */
+void l1ctl_rx_reset_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_reset *reset_req = (struct l1ctl_reset *)l1h->data;
+
+	switch (reset_req->type) {
+	case L1CTL_RES_T_FULL:
+		DEBUGP(DL1C,
+		                "Received and handled from l23 - L1CTL_RESET_REQ (type=FULL)\n");
+//		l1s_reset();
+//		l1s_reset_hw();
+//		audio_set_enabled(GSM48_CMODE_SIGN, 0);
+		l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type);
+		break;
+	case L1CTL_RES_T_SCHED:
+		DEBUGP(DL1C,
+		                "Received and handled from l23 - L1CTL_RESET_REQ (type=SCHED)\n");
+//		sched_gsmtime_reset();
+		l1ctl_tx_reset(L1CTL_RESET_CONF, reset_req->type);
+		break;
+	default:
+		LOGP(DL1C, LOGL_ERROR,
+		                "Received and ignored from l23 - L1CTL_RESET_REQ (type=unknown)\n");
+		break;
+	}
+}
+
+/**
+ * @brief Handler for received L1CTL_CCCH_MODE_REQ from L23.
+ *
+ * -- common control channel mode request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Configure CCCH combined / non-combined mode.
+ *
+ * TODO: Implement this handler routine!
+ */
+void l1ctl_rx_ccch_mode_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_ccch_mode_req *ccch_mode_req =
+	                (struct l1ctl_ccch_mode_req *)l1h->data;
+	uint8_t ccch_mode = ccch_mode_req->ccch_mode;
+
+	DEBUGP(DL1C, "Received and handled from l23 - L1CTL_CCCH_MODE_REQ\n");
+
+	l1_model_ms->state->serving_cell.ccch_mode = ccch_mode;
+
+	// check if more has to be done here
+
+	l1ctl_tx_ccch_mode_conf(ccch_mode);
+
+//	/* pre-set the CCCH mode */
+//	l1s.serving_cell.ccch_mode = ccch_mode;
+//
+//	/* Update task */
+//	mframe_disable(MF_TASK_CCCH_COMB);
+//	mframe_disable(MF_TASK_CCCH);
+//
+//	if (ccch_mode == CCCH_MODE_COMBINED)
+//		mframe_enable(MF_TASK_CCCH_COMB);
+//	else if (ccch_mode == CCCH_MODE_NON_COMBINED)
+//		mframe_enable(MF_TASK_CCCH);
+//
+//	l1ctl_tx_ccch_mode_conf(ccch_mode);
+}
+
+/**
+ * @brief Handler for received L1CTL_TCH_MODE_REQ from L23.
+ *
+ * -- traffic channel mode request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Configure TCH mode and audio mode.
+ *
+ * TODO: Implement this handler routine!
+ */
+void l1ctl_rx_tch_mode_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_tch_mode_req *tch_mode_req =
+	                (struct l1ctl_tch_mode_req *)l1h->data;
+	uint8_t tch_mode = tch_mode_req->tch_mode;
+	uint8_t audio_mode = tch_mode_req->audio_mode;
+
+	DEBUGP(DL1C,
+	                "Received and handled from l23 - L1CTL_TCH_MODE_REQ (tch_mode=0x%02x audio_mode=0x%02x)\n",
+	                tch_mode, audio_mode);
+//	tch_mode = l1a_tch_mode_set(tch_mode);
+//	audio_mode = l1a_audio_mode_set(audio_mode);
+//
+//	audio_set_enabled(tch_mode, audio_mode);
+//
+//	l1s.tch_sync = 1; /* Needed for audio to work */
+//
+//	l1ctl_tx_tch_mode_conf(tch_mode, audio_mode);
+}
+
+/**
+ * @brief Handler for received L1CTL_NEIGH_PM_REQ from L23.
+ *
+ * -- neighbor power measurement request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Update the maintained list of neighbor cells used in neighbor cell power measurement.
+ * The neighbor cell description is one of the info messages sent by the BTS on BCCH.
+ * This method will also enable neighbor measurement in the multiframe scheduler.
+ *
+ * Note: Not needed for virtual physical layer.
+ */
+void l1ctl_rx_neigh_pm_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_neigh_pm_req *pm_req =
+	                (struct l1ctl_neigh_pm_req *)l1h->data;
+
+	DEBUGP(DL1C,
+	                "Received and ignored from l23 - L1CTL_NEIGH_PM_REQ new list with %u entries\n",
+	                pm_req->n);
+}
+
+/**
+ * @brief Handler for received L1CTL_TRAFFIC_REQ from L23.
+ *
+ * -- traffic request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Enqueue the message (traffic frame) to the L1 state machine's transmit queue.
+ * Will drop the traffic frame at queue sizes >= 4.
+ *
+ * TODO: Implement this handler routine!
+ */
+void l1ctl_rx_traffic_req(struct msgb *msg)
+{
+	struct l1ctl_hdr *l1h = (struct l1ctl_hdr *)msg->data;
+	struct l1ctl_info_ul *ul = (struct l1ctl_info_ul *)l1h->data;
+	struct l1ctl_traffic_req *tr = (struct l1ctl_traffic_req *)ul->payload;
+	int num = 0;
+
+	DEBUGP(DL1C, "Received and handled from l23 - L1CTL_TRAFFIC_REQ\n");
+
+//	msg->l2h = tr->data;
+
+//	num = l1a_txq_msgb_count(&l1s.tx_queue[L1S_CHAN_TRAFFIC]);
+//	if (num >= 4) {
+//		DEBUGP(DL1C, "dropping traffic frame\n");
+//		msgb_free(msg);
+//		return;
+//	}
+//
+//	l1a_txq_msgb_enq(&l1s.tx_queue[L1S_CHAN_TRAFFIC], msg);
+}
+
+/**
+ * @brief Handler for received L1CTL_SIM_REQ from L23.
+ *
+ * -- sim request --
+ *
+ * @param [in] msg the received message.
+ *
+ * Forward and a sim request to the SIM APDU.
+ *
+ * Note: Not needed for virtual layer. Please configure layer23 application to use test-sim implementation.
+ * ms <x>
+ * --------
+ * sim test
+ * test-sim
+ *  imsi <xxxxxxxxxxxxxxx>
+ *  ki comp128 <xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx>
+ * --------
+ */
+void l1ctl_rx_sim_req(struct msgb *msg)
+{
+	uint16_t len = msg->len - sizeof(struct l1ctl_hdr);
+	uint8_t *data = msg->data + sizeof(struct l1ctl_hdr);
+
+	DEBUGP(DL1C,
+	                "Received and ignored from l23 - SIM Request length: %u, data: %s: ",
+	                len, osmo_hexdump(data, sizeof(data)));
+
+}
+
+/***************************************************************
+ * L1CTL TX ROUTINES *******************************************
+ ***************************************************************/
+
+/**
+ * @brief Transmit L1CTL_RESET_IND or L1CTL_RESET_CONF to layer 23.
+ *
+ * -- reset indication / confirm --
+ *
+ * @param [in] msg_type L1CTL primitive message type.
+ * @param [in] reset_type reset type (full, boot or just scheduler reset).
+ */
+void l1ctl_tx_reset(uint8_t msg_type, uint8_t reset_type)
+{
+	struct msgb *msg = l1ctl_msgb_alloc(msg_type);
+	struct l1ctl_reset *reset_resp;
+	reset_resp = (struct l1ctl_reset *)msgb_put(msg, sizeof(*reset_resp));
+	reset_resp->type = reset_type;
+
+	DEBUGP(DL1C, "Sending to l23 - %s (reset_type: %u)\n",
+	       	       getL1ctlPrimName(msg_type), reset_type);
+	l1ctl_sap_tx_to_l23(msg);
+}
+
+/**
+ * @brief Transmit L1CTL msg of a given type to layer 23.
+ *
+ * @param [in] msg_type L1CTL primitive message type.
+ */
+void l1ctl_tx_msg(uint8_t msg_type)
+{
+	struct msgb *msg = l1ctl_msgb_alloc(msg_type);
+	DEBUGP(DL1C, "Sending to l23 - %s\n", getL1ctlPrimName(msg_type));
+	l1ctl_sap_tx_to_l23(msg);
+}
+
+/**
+ * @brief Transmit L1CTL_FBSB_CONF to l23.
+ *
+ * -- frequency burst synchronisation burst confirm --
+ *
+ * @param [in] res 0 -> success, 255 -> error.
+ * @param [in] arfcn the arfcn we are synced to.
+ *
+ * No calculation needed for virtual pyh -> uses default values for a good link quality.
+ */
+void l1ctl_tx_fbsb_conf(uint8_t res, uint16_t arfcn)
+{
+	struct msgb *msg;
+	struct l1ctl_fbsb_conf *resp;
+	uint32_t fn = 0; // 0 should be okay here
+	uint16_t snr = 40; // signal noise ratio > 40db is best signal.
+	int16_t initial_freq_err = 0; // 0 means no error.
+	uint8_t bsic = 0;
+
+	msg = l1ctl_create_l2_msg(L1CTL_FBSB_CONF, fn,
+			snr,
+			arfcn);
+
+	resp = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*resp));
+	resp->initial_freq_err = htons(initial_freq_err);
+	resp->result = res;
+	resp->bsic = bsic;
+
+	DEBUGP(DL1C, "Sending to l23 - %s (res: %u)\n",
+	                getL1ctlPrimName(L1CTL_FBSB_CONF), res);
+
+	l1ctl_sap_tx_to_l23(msg);
+}
+
+/**
+ * @brief Transmit L1CTL_CCCH_MODE_CONF to layer 23.
+ *
+ * -- common control channel mode confirm --
+ *
+ * @param [in] ccch_mode the new configured ccch mode. Combined or non-combined, see l1ctl_proto.
+ *
+ * Called by layer 1 to inform layer 2 that the ccch mode was successfully changed.
+ */
+void l1ctl_tx_ccch_mode_conf(uint8_t ccch_mode)
+{
+	struct msgb *msg = l1ctl_msgb_alloc(L1CTL_CCCH_MODE_CONF);
+	struct l1ctl_ccch_mode_conf *mode_conf;
+	mode_conf = (struct l1ctl_ccch_mode_conf *)msgb_put(msg,
+	                sizeof(*mode_conf));
+	mode_conf->ccch_mode = ccch_mode;
+
+	DEBUGP(DL1C, "Sending to l23 - L1CTL_CCCH_MODE_CONF (mode: %u)\n",
+	                ccch_mode);
+	l1ctl_sap_tx_to_l23(msg);
+}
+
+/**
+ * @brief Transmit L1CTL_TCH_MODE_CONF to layer 23.
+ *
+ * -- traffic channel mode confirm --
+ *
+ * @param [in] tch_mode the new configured traffic channel mode, see gsm48_chan_mode in gsm_04_08.h.
+ * @param [in] audio_mode the new configured audio mode(s), see l1ctl_tch_mode_req in l1ctl_proto.h.
+ *
+ * Called by layer 1 to inform layer 23 that the traffic channel mode was successfully changed.
+ */
+void l1ctl_tx_tch_mode_conf(uint8_t tch_mode, uint8_t audio_mode)
+{
+	struct msgb *msg = l1ctl_msgb_alloc(L1CTL_TCH_MODE_CONF);
+	struct l1ctl_tch_mode_conf *mode_conf;
+	mode_conf = (struct l1ctl_tch_mode_conf *)msgb_put(msg,
+	                sizeof(*mode_conf));
+	mode_conf->tch_mode = tch_mode;
+	mode_conf->audio_mode = audio_mode;
+
+	DEBUGP(DL1C,
+	                "Sending to l23 - L1CTL_TCH_MODE_CONF (tch_mode: %u, audio_mode: %u)\n", tch_mode,
+	                audio_mode);
+	l1ctl_sap_tx_to_l23(msg);
+}
+
+
diff --git a/src/host/virt_phy/src/l1ctl_sap.h b/src/host/virt_phy/src/l1ctl_sap.h
new file mode 100644
index 0000000..2d67128
--- /dev/null
+++ b/src/host/virt_phy/src/l1ctl_sap.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/msgb.h>
+#include <l1ctl_proto.h>
+
+#include "l1ctl_sock.h"
+#include "virtual_um.h"
+#include "virt_l1_model.h"
+
+/* following sizes are used for message allocation */
+/* size of layer 3 header */
+#define L3_MSG_HEAD 4
+/* size of layer 3 payload */
+#define L3_MSG_DATA 200
+#define L3_MSG_SIZE (sizeof(struct l1ctl_hdr) + L3_MSG_HEAD + L3_MSG_DATA)
+
+void l1ctl_sap_init(struct l1_model_ms *model);
+void l1ctl_sap_tx_to_l23_inst(struct l1ctl_sock_inst *lsi, struct msgb *msg);
+void l1ctl_sap_tx_to_l23(struct msgb *msg);
+void l1ctl_sap_rx_from_l23_inst_cb(struct l1ctl_sock_inst *lsi, struct msgb *msg);
+void l1ctl_sap_rx_from_l23(struct msgb *msg);
+void l1ctl_sap_handler(struct msgb *msg);
+
+/* utility methods */
+struct msgb *l1ctl_msgb_alloc(uint8_t msg_type);
+struct msgb *l1ctl_create_l2_msg(int msg_type, uint32_t fn, uint16_t snr,
+                                 uint16_t arfcn);
+
+/* receive routines */
+void l1ctl_rx_fbsb_req(struct msgb *msg);
+void l1ctl_rx_dm_est_req(struct msgb *msg);
+void l1ctl_rx_dm_rel_req(struct msgb *msg);
+void l1ctl_rx_param_req(struct msgb *msg);
+void l1ctl_rx_dm_freq_req(struct msgb *msg);
+void l1ctl_rx_crypto_req(struct msgb *msg);
+void l1ctl_rx_rach_req(struct msgb *msg);
+void l1ctl_rx_data_req(struct msgb *msg);
+void l1ctl_rx_pm_req(struct msgb *msg);
+void l1ctl_rx_reset_req(struct msgb *msg);
+void l1ctl_rx_ccch_mode_req(struct msgb *msg);
+void l1ctl_rx_tch_mode_req(struct msgb *msg);
+void l1ctl_rx_neigh_pm_req(struct msgb *msg);
+void l1ctl_rx_traffic_req(struct msgb *msg);
+void l1ctl_rx_sim_req(struct msgb *msg);
+
+/* transmit routines */
+void l1ctl_tx_reset(uint8_t msg_type, uint8_t reset_type);
+void l1ctl_tx_pm_conf(struct l1ctl_pm_req *pm_req);
+void l1ctl_tx_fbsb_conf(uint8_t res, uint16_t arfcn);
+void l1ctl_tx_ccch_mode_conf(uint8_t ccch_mode);
+void l1ctl_tx_tch_mode_conf(uint8_t tch_mode, uint8_t audio_mode);
+void l1ctl_tx_msg(uint8_t msg_type);
diff --git a/src/host/virt_phy/src/l1ctl_sock.c b/src/host/virt_phy/src/l1ctl_sock.c
new file mode 100644
index 0000000..e52b731
--- /dev/null
+++ b/src/host/virt_phy/src/l1ctl_sock.c
@@ -0,0 +1,197 @@
+/* Socket based Layer1 <-> Layer23 communication over L1CTL primitives. */
+
+/* (C) 2016 Sebastian Stumpf
+ *
+ * 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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/serial.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+
+#include <arpa/inet.h>
+
+#include "l1ctl_sock.h"
+#include "virtual_um.h"
+#include "logging.h"
+
+#define L1CTL_SOCK_MSGB_SIZE	256
+
+/**
+ * @brief L1CTL socket file descriptor callback function.
+ *
+ * @param ofd The osmocom file descriptor.
+ * @param what Indicates if the fd has a read, write or exception request. See select.h.
+ *
+ * Will be called by osmo_select_main() if data on fd is pending.
+ */
+static int l1ctl_sock_data_cb(struct osmo_fd *ofd, unsigned int what)
+{
+	struct l1ctl_sock_inst *lsi = ofd->data;
+	int cnt = 0;
+	// Check if request is really read request
+	if (what & BSC_FD_READ) {
+		struct msgb *msg = msgb_alloc(L1CTL_SOCK_MSGB_SIZE,
+		                "L1CTL sock rx");
+		int rc;
+		uint16_t len;
+
+		// read length of the message first and convert to host byte order
+		rc = read(ofd->fd, &len, sizeof(len));
+		if (rc < sizeof(len)) {
+			goto ERR;
+		}
+		// convert to host byte order
+		len = ntohs(len);
+		if (len <= 0 || len > L1CTL_SOCK_MSGB_SIZE) {
+			goto ERR;
+		}
+		rc = read(ofd->fd, msgb_data(msg), len);
+
+		if (rc == len) {
+			msgb_put(msg, rc);
+			msg->l1h = msgb_data(msg);
+			lsi->recv_cb(lsi, msg);
+			return 0;
+		}
+ERR:
+		perror("Failed to receive msg from l2. Connection will be closed.\n");
+		l1ctl_sock_disconnect(lsi);
+	}
+	return 0;
+
+}
+
+static int l1ctl_sock_accept_cb(struct osmo_fd *ofd, unsigned int what)
+{
+
+	struct l1ctl_sock_inst *lsi = ofd->data;
+	struct sockaddr_un local_addr;
+	socklen_t addr_len = sizeof(struct sockaddr_in);
+	int fd;
+
+	fd = accept(ofd->fd, (struct sockaddr *)&local_addr, &addr_len);
+	if (fd < 0) {
+		fprintf(stderr, "Failed to accept connection to l2.\n");
+		return -1;
+	}
+
+	lsi->connection.fd = fd;
+	lsi->connection.when = BSC_FD_READ;
+	lsi->connection.cb = l1ctl_sock_data_cb;
+	lsi->connection.data = lsi;
+
+	if (osmo_fd_register(&lsi->connection) != 0) {
+		fprintf(stderr, "Failed to register the l2 connection fd.\n");
+		return -1;
+	}
+	return 0;
+}
+
+struct l1ctl_sock_inst *l1ctl_sock_init(
+                void *ctx,
+                void (*recv_cb)(struct l1ctl_sock_inst *lsi, struct msgb *msg),
+                char *path)
+{
+	struct l1ctl_sock_inst *lsi;
+	struct sockaddr_un local_addr;
+	int fd, rc;
+
+	if (!path)
+		path = L1CTL_SOCK_PATH;
+
+	if ((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
+		fprintf(stderr, "Failed to create Unix Domain Socket.\n");
+		return NULL;
+	}
+
+	local_addr.sun_family = AF_LOCAL;
+	strcpy(local_addr.sun_path, path);
+	unlink(local_addr.sun_path);
+
+	if ((rc = bind(fd, (struct sockaddr *)&local_addr, sizeof(local_addr)))
+	                != 0) {
+		fprintf(stderr, "Failed to bind the unix domain socket. '%s'\n",
+		                local_addr.sun_path);
+		return NULL;
+	}
+
+	if (listen(fd, 0) != 0) {
+		fprintf(stderr, "Failed to listen.\n");
+		return NULL;
+	}
+
+	lsi = talloc_zero(ctx, struct l1ctl_sock_inst);
+	lsi->priv = NULL;
+	lsi->recv_cb = recv_cb;
+	lsi->ofd.data = lsi;
+	lsi->ofd.fd = fd;
+	lsi->ofd.when = BSC_FD_READ;
+	lsi->ofd.cb = l1ctl_sock_accept_cb;
+	// no connection -> invalid filedescriptor and not 0 (==std_in)
+	lsi->connection.fd = -1;
+
+	osmo_fd_register(&lsi->ofd);
+
+	return lsi;
+}
+
+void l1ctl_sock_destroy(struct l1ctl_sock_inst *lsi)
+{
+	struct osmo_fd *ofd = &lsi->ofd;
+
+	osmo_fd_unregister(ofd);
+	close(ofd->fd);
+	ofd->fd = -1;
+	ofd->when = 0;
+
+	talloc_free(lsi);
+}
+
+void l1ctl_sock_disconnect(struct l1ctl_sock_inst *lsi)
+{
+	struct osmo_fd *ofd = &lsi->connection;
+	osmo_fd_unregister(ofd);
+	close(ofd->fd);
+	ofd->fd = -1;
+	ofd->when = 0;
+}
+
+int l1ctl_sock_write_msg(struct l1ctl_sock_inst *lsi, struct msgb *msg)
+{
+	int rc;
+	rc = write(lsi->connection.fd, msgb_data(msg), msgb_length(msg));
+	msgb_free(msg);
+	return rc;
+}
diff --git a/src/host/virt_phy/src/l1ctl_sock.h b/src/host/virt_phy/src/l1ctl_sock.h
new file mode 100644
index 0000000..ef9799c
--- /dev/null
+++ b/src/host/virt_phy/src/l1ctl_sock.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+
+#define L1CTL_SOCK_PATH	"/tmp/osmocom_l2"
+
+/* L1CTL socket instance contains socket data. */
+struct l1ctl_sock_inst {
+	void *priv; /* Will be appended after osmo-fd's data pointer. */
+	struct osmo_fd connection; /* L1CTL connection to l2 app */
+	struct osmo_fd ofd; /* Osmocom file descriptor to accept L1CTL connections. */
+	void (*recv_cb)(struct l1ctl_sock_inst *vui, struct msgb *msg); /* Callback function called for incoming data from l2 app. */
+};
+
+/**
+ * @brief Initialise the l1ctl socket for communication with l2 apps.
+ */
+struct l1ctl_sock_inst *l1ctl_sock_init(
+                void *ctx,
+                void (*recv_cb)(struct l1ctl_sock_inst *lsi, struct msgb *msg),
+                char *path);
+
+/**
+ * @brief Transmit message to l2.
+ */
+int l1ctl_sock_write_msg(struct l1ctl_sock_inst *lsi, struct msgb *msg);
+
+/**
+ * @brief Destroy instance.
+ */
+void l1ctl_sock_destroy();
+
+/**
+ * @brief Disconnect current connection.
+ */
+void l1ctl_sock_disconnect(struct l1ctl_sock_inst *lsi);
diff --git a/src/host/virt_phy/src/logging.c b/src/host/virt_phy/src/logging.c
new file mode 100644
index 0000000..a017a52
--- /dev/null
+++ b/src/host/virt_phy/src/logging.c
@@ -0,0 +1,113 @@
+/* Logging/Debug support of the virtual physical layer */
+
+/* (C) 2010 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 General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/application.h>
+
+#include "logging.h"
+
+const char* l1ctlPrimNames[] = {
+        "_L1CTL_NONE",
+        "L1CTL_FBSB_REQ",
+        "L1CTL_FBSB_CONF",
+        "L1CTL_DATA_IND",
+        "L1CTL_RACH_REQ",
+        "L1CTL_DM_EST_REQ",
+        "L1CTL_DATA_REQ",
+        "L1CTL_RESET_IND",
+        "L1CTL_PM_REQ",
+        "L1CTL_PM_CONF",
+        "L1CTL_ECHO_REQ",
+        "L1CTL_ECHO_CONF",
+        "L1CTL_RACH_CONF",
+        "L1CTL_RESET_REQ",
+        "L1CTL_RESET_CONF",
+        "L1CTL_DATA_CONF",
+        "L1CTL_CCCH_MODE_REQ",
+        "L1CTL_CCCH_MODE_CONF",
+        "L1CTL_DM_REL_REQ",
+        "L1CTL_PARAM_REQ",
+        "L1CTL_DM_FREQ_REQ",
+        "L1CTL_CRYPTO_REQ",
+        "L1CTL_SIM_REQ",
+        "L1CTL_SIM_CONF",
+        "L1CTL_TCH_MODE_REQ",
+        "L1CTL_TCH_MODE_CONF",
+        "L1CTL_NEIGH_PM_REQ",
+        "L1CTL_NEIGH_PM_IND",
+        "L1CTL_TRAFFIC_REQ",
+        "L1CTL_TRAFFIC_CONF",
+        "L1CTL_TRAFFIC_IND"
+};
+
+static const struct log_info_cat default_categories[] = {
+	[DL1C] = {
+		.name = "DL1C",
+		.description = "Layer 1 Control",
+		.color = "\033[1;31m",
+		.enabled = 1,
+		.loglevel = LOGL_DEBUG,
+	},
+	[DVIRPHY] = {
+		.name = "DVIRPHY",
+		.description = "Virtual Layer 1 Interface",
+		.color = "\033[1;31m",
+		.enabled = 1,
+		.loglevel = LOGL_DEBUG,
+	}
+};
+
+const struct log_info ms_log_info = {
+	.filter_fn = NULL,
+	.cat = default_categories,
+	.num_cat = ARRAY_SIZE(default_categories),
+};
+
+/**
+ * Initialize the logging system for the virtual physical layer.
+ */
+int ms_log_init(char *cat_mask) {
+	struct log_target *stderr_target;
+
+	log_init(&ms_log_info, NULL);
+	stderr_target = log_target_create_stderr();
+	if(!stderr) {
+		return -1;
+	}
+	log_add_target(stderr_target);
+	log_set_all_filter(stderr_target, 1);
+	//log_set_log_level(stderr_target, 1);
+	log_set_print_filename(stderr_target, 0);
+	log_set_use_color(stderr_target, 0);
+	log_set_print_timestamp(stderr_target, 1);
+	log_set_print_category(stderr_target, 1);
+	if(cat_mask) {
+		log_parse_category_mask(stderr_target, cat_mask);
+	}
+	return 0;
+}
+
+char *getL1ctlPrimName(uint8_t type)
+{
+	return l1ctlPrimNames[type];
+}
diff --git a/src/host/virt_phy/src/logging.h b/src/host/virt_phy/src/logging.h
new file mode 100644
index 0000000..87f12f7
--- /dev/null
+++ b/src/host/virt_phy/src/logging.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <osmocom/core/logging.h>
+
+#define DL1C 0
+#define DVIRPHY 1
+
+extern const struct log_info ms_log_info;
+
+int ms_log_init(char *cat_mask);
+char *getL1ctlPrimName(uint8_t type);
diff --git a/src/host/virt_phy/src/osmo_mcast_sock.c b/src/host/virt_phy/src/osmo_mcast_sock.c
new file mode 100644
index 0000000..c177734
--- /dev/null
+++ b/src/host/virt_phy/src/osmo_mcast_sock.c
@@ -0,0 +1,192 @@
+#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"
+
+struct mcast_server_sock *mcast_server_sock_setup(void *ctx,
+                                                  char* tx_mcast_group,
+                                                  int tx_mcast_port,
+                                                  int loopback)
+{
+	struct mcast_server_sock *serv_sock = talloc_zero(ctx,
+	                struct mcast_server_sock);
+
+	serv_sock->osmo_fd = talloc_zero(ctx, struct osmo_fd);
+	serv_sock->sock_conf = talloc_zero(ctx, struct sockaddr_in);
+
+	// setup mcast server socket
+	serv_sock->osmo_fd->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (serv_sock->osmo_fd->fd == -1) {
+		perror("Failed to create Multicast Server Socket");
+		return NULL;
+	}
+
+	serv_sock->sock_conf->sin_family = AF_INET;
+	serv_sock->sock_conf->sin_addr.s_addr = inet_addr(tx_mcast_group);
+	serv_sock->sock_conf->sin_port = htons(tx_mcast_port);
+
+	// determines whether sent mcast packets should be looped back to the local sockets.
+	// loopback must be enabled if the mcast client is on the same machine
+	if (setsockopt(serv_sock->osmo_fd->fd, IPPROTO_IP,
+	IP_MULTICAST_LOOP, &loopback, sizeof(loopback)) < 0) {
+		perror("Failed to disable loopback.\n");
+		return NULL;
+	}
+
+	return serv_sock;
+}
+
+struct mcast_client_sock *mcast_client_sock_setup(
+                void *ctx, char* mcast_group, int mcast_port,
+                int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+                void *osmo_fd_data)
+{
+	struct mcast_client_sock *client_sock = talloc_zero(ctx,
+	                struct mcast_client_sock);
+	struct sockaddr_in *rx_sock_conf = talloc_zero(NULL,
+	                struct sockaddr_in);
+	int rc, reuseaddr = 1, loopback = 1;
+
+	client_sock->osmo_fd = talloc_zero(ctx, struct osmo_fd);
+	client_sock->mcast_group = talloc_zero(ctx, struct ip_mreq);
+
+	// Create mcast client socket
+	client_sock->osmo_fd->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (client_sock->osmo_fd->fd == -1) {
+		perror("Could not create mcast client socket");
+		return NULL;
+	}
+
+	// Enable SO_REUSEADDR to allow multiple instances of this application to receive copies of the multicast datagrams.
+	rc = setsockopt(client_sock->osmo_fd->fd,
+	SOL_SOCKET,
+	SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
+	if (rc < 0) {
+		perror("Failed to configure REUSEADDR option");
+		return NULL;
+	}
+
+	// Bind to the proper port number with the IP address specified as INADDR_ANY.
+	rx_sock_conf->sin_family = AF_INET;
+	rx_sock_conf->sin_addr.s_addr = htonl(INADDR_ANY);
+	rx_sock_conf->sin_port = htons(mcast_port);
+	rc = bind(client_sock->osmo_fd->fd, (struct sockaddr *)rx_sock_conf,
+	                sizeof(*rx_sock_conf));
+	talloc_free(rx_sock_conf);
+	if (rc < 0) {
+		perror("Could not bind mcast client socket");
+		return NULL;
+	}
+
+	// Enable loopback of msgs to the host.
+	// Loopback must be enabled for the client, so multiple processes are able to recevie a mcast package.
+	rc = setsockopt(client_sock->osmo_fd->fd,
+	IPPROTO_IP,
+	IP_MULTICAST_LOOP, &loopback, sizeof(loopback));
+	if (rc < 0) {
+		perror("Failed to enable IP_MULTICAST_LOOP");
+		return NULL;
+	}
+
+	// Configure and join the multicast group
+	client_sock->mcast_group->imr_multiaddr.s_addr = inet_addr(mcast_group);
+	client_sock->mcast_group->imr_interface.s_addr = htonl(INADDR_ANY);
+	rc = setsockopt(client_sock->osmo_fd->fd,
+	IPPROTO_IP,
+	IP_ADD_MEMBERSHIP, client_sock->mcast_group,
+	                sizeof(*client_sock->mcast_group));
+	if (rc < 0) {
+		perror("Failed to join to mcast goup");
+		return NULL;
+	}
+
+	// configure and register the osmocom filedescriptor
+	client_sock->osmo_fd->cb = fd_rx_cb;
+	client_sock->osmo_fd->when = BSC_FD_READ;
+	client_sock->osmo_fd->data = osmo_fd_data;
+
+	osmo_fd_register(client_sock->osmo_fd);
+
+	return client_sock;
+}
+
+struct mcast_bidir_sock *mcast_bidir_sock_setup(
+                void *ctx, char* tx_mcast_group, int tx_mcast_port,
+                char* rx_mcast_group, int rx_mcast_port, int 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);
+	bidir_sock->rx_sock = mcast_client_sock_setup(ctx, rx_mcast_group,
+	                rx_mcast_port, fd_rx_cb, osmo_fd_data);
+	bidir_sock->tx_sock = mcast_server_sock_setup(ctx, tx_mcast_group,
+	                tx_mcast_port, loopback);
+	if (!bidir_sock->rx_sock || !bidir_sock->tx_sock) {
+		return NULL;
+	}
+	return bidir_sock;
+
+}
+
+int mcast_client_sock_rx(struct mcast_client_sock *client_sock, void* buf,
+                         int buf_len)
+{
+	return recv(client_sock->osmo_fd->fd, buf, buf_len, 0);
+}
+
+int mcast_server_sock_tx(struct mcast_server_sock *serv_sock, void* data,
+                         int data_len)
+{
+	return sendto(serv_sock->osmo_fd->fd, data, data_len, 0,
+	                (struct sockaddr *)serv_sock->sock_conf,
+	                sizeof(*serv_sock->sock_conf));
+}
+
+int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, void* data,
+                        int data_len)
+{
+	return mcast_server_sock_tx(bidir_sock->tx_sock, data, data_len);
+}
+int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, void* buf,
+                        int buf_len)
+{
+	return mcast_client_sock_rx(bidir_sock->rx_sock, buf, buf_len);
+}
+
+void mcast_client_sock_close(struct mcast_client_sock *client_sock)
+{
+	setsockopt(client_sock->osmo_fd->fd,
+	IPPROTO_IP,
+	IP_DROP_MEMBERSHIP, client_sock->mcast_group,
+	                sizeof(*client_sock->mcast_group));
+	osmo_fd_unregister(client_sock->osmo_fd);
+	client_sock->osmo_fd->fd = -1;
+	client_sock->osmo_fd->when = 0;
+	close(client_sock->osmo_fd->fd);
+	talloc_free(client_sock->mcast_group);
+	talloc_free(client_sock->osmo_fd);
+	talloc_free(client_sock);
+
+}
+void mcast_server_sock_close(struct mcast_server_sock *serv_sock)
+{
+	close(serv_sock->osmo_fd->fd);
+	talloc_free(serv_sock->sock_conf);
+	talloc_free(serv_sock);
+}
+
+void mcast_bidir_sock_close(struct mcast_bidir_sock *bidir_sock)
+{
+	mcast_client_sock_close(bidir_sock->rx_sock);
+	mcast_server_sock_close(bidir_sock->tx_sock);
+	talloc_free(bidir_sock);
+}
diff --git a/src/host/virt_phy/src/osmo_mcast_sock.h b/src/host/virt_phy/src/osmo_mcast_sock.h
new file mode 100644
index 0000000..f318ffe
--- /dev/null
+++ b/src/host/virt_phy/src/osmo_mcast_sock.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <netinet/in.h>
+#include <osmocom/core/select.h>
+
+struct mcast_server_sock {
+	struct osmo_fd *osmo_fd;
+	struct sockaddr_in *sock_conf;
+};
+
+struct mcast_client_sock {
+	struct osmo_fd *osmo_fd;
+	struct ip_mreq *mcast_group;
+};
+
+struct mcast_bidir_sock {
+	struct mcast_server_sock *tx_sock;
+	struct mcast_client_sock *rx_sock;
+};
+
+struct mcast_bidir_sock *mcast_bidir_sock_setup(
+                void *ctx, char* tx_mcast_group, int tx_mcast_port,
+                char* rx_mcast_group, int rx_mcast_port, int loopback,
+                int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+                void *osmo_fd_data);
+
+struct mcast_server_sock *mcast_server_sock_setup(void *ctx,
+                                                  char* tx_mcast_group,
+                                                  int tx_mcast_port,
+                                                  int loopback);
+struct mcast_client_sock *mcast_client_sock_setup(
+                void *ctx, char* mcast_group, int mcast_port,
+                int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what),
+                void *osmo_fd_data);
+int mcast_client_sock_rx(struct mcast_client_sock *client_sock, void* buf,
+                         int buf_len);
+int mcast_server_sock_tx(struct mcast_server_sock *serv_sock, void* data,
+                         int data_len);
+int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, void* data,
+                        int data_len);
+int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, void* buf,
+                        int buf_len);
+void mcast_client_sock_close(struct mcast_client_sock* client_sock);
+void mcast_server_sock_close(struct mcast_server_sock* server_sock);
+void mcast_bidir_sock_close(struct mcast_bidir_sock* bidir_sock);
+
diff --git a/src/host/virt_phy/src/virt_l1_model.c b/src/host/virt_phy/src/virt_l1_model.c
new file mode 100644
index 0000000..f2e1690
--- /dev/null
+++ b/src/host/virt_phy/src/virt_l1_model.c
@@ -0,0 +1,16 @@
+#include "virt_l1_model.h"
+
+struct l1_model_ms* l1_model_ms_init(void *ctx) {
+
+	struct l1_model_ms *model = talloc_zero(ctx, struct l1_model_ms);
+	model->state = talloc_zero(ctx, struct l1_state_ms);
+
+	return model;
+}
+
+void l1_model_ms_destroy(struct l1_model_ms *model) {
+	virt_um_destroy(model->vui);
+	l1ctl_sock_destroy(model->lsi);
+	talloc_free(model->state);
+	talloc_free(model);
+}
diff --git a/src/host/virt_phy/src/virt_l1_model.h b/src/host/virt_phy/src/virt_l1_model.h
new file mode 100644
index 0000000..55a1e3a
--- /dev/null
+++ b/src/host/virt_phy/src/virt_l1_model.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <layer1/sync.h>
+#include "l1ctl_sock.h"
+#include "virtual_um.h"
+
+struct l1_model_ms {
+	struct l1ctl_sock_inst *lsi;
+	struct virt_um_inst *vui;
+	struct l1_state_ms *state;
+};
+
+//TODO: must contain logical channel information (fram number, ciphering mode, ...)
+struct l1_state_ms {
+
+	/* the cell on which we are camping right now */
+	struct l1_cell_info serving_cell;
+
+	/* neighbor cell sync info */
+	struct l1_cell_info neigh_cell[L1S_NUM_NEIGH_CELL];
+
+	/* TCH */
+	uint8_t tch_mode;
+	uint8_t tch_sync;
+	uint8_t audio_mode;
+};
+
+struct l1_model_ms *l1_model_ms_init(void *ctx);
+
+void l1_model_ms_destroy(struct l1_model_ms *model);
+
diff --git a/src/host/virt_phy/src/virtphy.c b/src/host/virt_phy/src/virtphy.c
new file mode 100644
index 0000000..94f6fa5
--- /dev/null
+++ b/src/host/virt_phy/src/virtphy.c
@@ -0,0 +1,50 @@
+/* osmocom includes */
+
+#include "logging.h"
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmo-bts/scheduler.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <virt_l1_model.h>
+
+#include "virtual_um.h"
+#include "l1ctl_sock.h"
+#include "virt_l1_model.h"
+#include "gsmtapl1_if.h"
+#include "l1ctl_sap.h"
+
+int main(void)
+{
+
+	// init loginfo
+	static struct l1_model_ms *model;
+	ms_log_init("DL1C,1:DVIRPHY,1");
+	//ms_log_init("DL1C,8:DVIRPHY,8");
+
+	LOGP(DVIRPHY, LOGL_INFO, "Virtual physical layer starting up...\n");
+
+	model = l1_model_ms_init(NULL);
+
+	// TODO: make this configurable
+	model->vui = virt_um_init(NULL, DEFAULT_BTS_MCAST_GROUP, DEFAULT_BTS_MCAST_PORT, DEFAULT_MS_MCAST_GROUP, DEFAULT_MS_MCAST_PORT, gsmtapl1_rx_from_virt_um_inst_cb);
+	model->lsi = l1ctl_sock_init(NULL, l1ctl_sap_rx_from_l23_inst_cb, NULL);
+
+	gsmtapl1_init(model);
+	l1ctl_sap_init(model);
+
+	LOGP(DVIRPHY, LOGL_INFO, "Virtual physical layer ready...\n");
+
+	while (1) {
+		// handle osmocom fd READ events (l1ctl-unix-socket, virtual-um-mcast-socket)
+		osmo_select_main(0);
+		// handle outgoing l1ctl primitives to l2
+		// TODO implement scheduler for uplink messages
+	}
+
+	l1_model_ms_destroy(model);
+
+	// not reached
+	return EXIT_FAILURE;
+}
diff --git a/src/host/virt_phy/src/virtual_um.c b/src/host/virt_phy/src/virtual_um.c
new file mode 100644
index 0000000..2b15509
--- /dev/null
+++ b/src/host/virt_phy/src/virtual_um.c
@@ -0,0 +1,100 @@
+/* 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 <unistd.h>
+#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 "virtual_um.h"
+#include "osmo_mcast_sock.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;
+
+	// check if the read flag is set
+	if (what & BSC_FD_READ) {
+		// allocate message buffer of specified size
+		struct msgb *msg = msgb_alloc(VIRT_UM_MSGB_SIZE,
+		                "Virtual UM Rx");
+		int rc;
+
+		// read message from fd in message buffer
+		rc = mcast_bidir_sock_rx(vui->mcast_sock, msgb_data(msg), msgb_tailroom(msg));
+		// rc is number of bytes actually read
+		if (rc > 0) {
+			msgb_put(msg, rc);
+			// call the l1 callback function for a received msg
+			vui->recv_cb(vui, msg);
+		} else {
+			// TODO: this kind of error handling might be a bit harsh
+			vui->recv_cb(vui, NULL);
+			// Unregister fd from select loop
+			osmo_fd_unregister(ofd);
+			close(ofd->fd);
+			ofd->fd = -1;
+			ofd->when = 0;
+		}
+	}
+
+	return 0;
+}
+
+struct virt_um_inst *virt_um_init(
+                void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port,
+                const 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/host/virt_phy/src/virtual_um.h b/src/host/virt_phy/src/virtual_um.h
new file mode 100644
index 0000000..eafb994
--- /dev/null
+++ b/src/host/virt_phy/src/virtual_um.h
@@ -0,0 +1,24 @@
+#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 6666
+#define DEFAULT_BTS_MCAST_GROUP "225.0.0.1"
+#define DEFAULT_BTS_MCAST_PORT 6667
+
+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, const char *tx_mcast_group, uint16_t tx_mcast_port, const 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);

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

Gerrit-MessageType: newpatchset
Gerrit-Change-Id: I203c8ec58326e52a09603a37232fce7ae3641415
Gerrit-PatchSet: 2
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Owner: Harald Welte <laforge at gnumonks.org>
Gerrit-Reviewer: BastusIII <sebastian.stumpf87 at googlemail.com>
Gerrit-Reviewer: Jenkins Builder



More information about the gerrit-log mailing list