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