summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile14
-rw-r--r--src/host/virt_phy/.gitignore3
-rw-r--r--src/host/virt_phy/Makefile.am2
-rw-r--r--src/host/virt_phy/README2
-rw-r--r--src/host/virt_phy/configure.ac27
-rw-r--r--src/host/virt_phy/example_configs/osmocom-bb-mobile.cfg67
-rw-r--r--src/host/virt_phy/include/layer1/mframe_sched.h68
-rw-r--r--src/host/virt_phy/include/layer1/sync.h204
-rw-r--r--src/host/virt_phy/include/layer1/tdma_sched.h73
-rw-r--r--src/host/virt_phy/src/Makefile.am19
-rw-r--r--src/host/virt_phy/src/gsmtapl1_if.c282
-rw-r--r--src/host/virt_phy/src/gsmtapl1_if.h18
-rw-r--r--src/host/virt_phy/src/l1ctl_sap.c833
-rw-r--r--src/host/virt_phy/src/l1ctl_sap.h53
-rw-r--r--src/host/virt_phy/src/l1ctl_sock.c197
-rw-r--r--src/host/virt_phy/src/l1ctl_sock.h37
-rw-r--r--src/host/virt_phy/src/logging.c113
-rw-r--r--src/host/virt_phy/src/logging.h11
-rw-r--r--src/host/virt_phy/src/osmo_mcast_sock.c192
-rw-r--r--src/host/virt_phy/src/osmo_mcast_sock.h46
-rw-r--r--src/host/virt_phy/src/virt_l1_model.c16
-rw-r--r--src/host/virt_phy/src/virt_l1_model.h31
-rw-r--r--src/host/virt_phy/src/virtphy.c50
-rw-r--r--src/host/virt_phy/src/virtual_um.c100
-rw-r--r--src/host/virt_phy/src/virtual_um.h24
25 files changed, 2481 insertions, 1 deletions
diff --git a/src/Makefile b/src/Makefile
index 00231a64..e83175c4 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -12,7 +12,8 @@ CROSS_TOOL_PREFIX=$(CROSS_HOST)-
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/Makefile: host/osmocon/configure
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 @@ clean:
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 @@ distclean:
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 00000000..1e19c7fb
--- /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 00000000..515d51b5
--- /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 00000000..a7806647
--- /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 00000000..bcdbf91d
--- /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 00000000..382a09dc
--- /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 00000000..ecdb1ec8
--- /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 00000000..dae85a1a
--- /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 00000000..f58d59bb
--- /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 00000000..a110435a
--- /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 00000000..11214cf8
--- /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 00000000..8c7491c0
--- /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 00000000..46121ed1
--- /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 00000000..2d671287
--- /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 00000000..e52b731a
--- /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 00000000..ef9799c2
--- /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 00000000..a017a521
--- /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@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 00000000..87f12f77
--- /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 00000000..c1777347
--- /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 00000000..f318ffea
--- /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 00000000..f2e16905
--- /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 00000000..55a1e3ae
--- /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 00000000..94f6fa50
--- /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 00000000..2b155095
--- /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@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 00000000..eafb9940
--- /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);