aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2016-01-09 13:13:37 +0100
committerSebastian Stumpf <sebastian.stumpf87@googlemail.com>2017-01-05 12:47:51 +0100
commit17eb1a45b446d631449143a52b503978e6661637 (patch)
treea2f18f02bdb4caa6c6bbbc6aafa6f686a4e7f8e3
parentc2ecca6b0496127709dcd3afa9d366085d8bec97 (diff)
WIP: Initial check-in of a new virtual BTS
-rw-r--r--configure.ac1
-rw-r--r--include/osmo-bts/gsm_data.h6
-rw-r--r--include/osmo-bts/phy_link.h11
-rw-r--r--src/Makefile.am2
-rw-r--r--src/osmo-bts-virtual/Makefile.am10
-rw-r--r--src/osmo-bts-virtual/bts_model.c149
-rw-r--r--src/osmo-bts-virtual/l1_if.c318
-rw-r--r--src/osmo-bts-virtual/l1_if.h18
-rw-r--r--src/osmo-bts-virtual/main.c107
-rw-r--r--src/osmo-bts-virtual/scheduler_virtbts.c594
-rw-r--r--src/osmo-bts-virtual/virtual_um.c211
-rw-r--r--src/osmo-bts-virtual/virtual_um.h18
-rw-r--r--src/osmo-bts-virtual/virtualbts_vty.c142
13 files changed, 1586 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index 3fd6b8a1..84aa35e0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -130,6 +130,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 772a7050..26c52032 100644
--- a/include/osmo-bts/gsm_data.h
+++ b/include/osmo-bts/gsm_data.h
@@ -112,6 +112,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 6b2f21ea..38e7ffa8 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 {
@@ -55,6 +57,12 @@ struct phy_link {
int power_sent;
} osmotrx;
struct {
+ char *mcast_group;
+ char *mcast_dev;
+ uint16_t mcast_port;
+ struct virt_um_inst *virt_um;
+ } virt;
+ struct {
/* MAC address of the PHY */
struct sockaddr_ll phy_addr;
/* Network device name */
@@ -102,6 +110,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;
} octphy;
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..07c6d9d3
--- /dev/null
+++ b/src/osmo-bts-virtual/Makefile.am
@@ -0,0 +1,10 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR)
+AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS)
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS)
+
+EXTRA_DIST = 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_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..1eea26b1
--- /dev/null
+++ b/src/osmo-bts-virtual/bts_model.c
@@ -0,0 +1,149 @@
+/* (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 <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>
+
+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)
+{
+ 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);
+}
+
+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:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+ rc = oml_mo_opstart_ack(mo);
+ break;
+ case NM_OC_BTS:
+ case NM_OC_SITE_MANAGER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_GPRS_NSE:
+ case NM_OC_GPRS_CELL:
+ case NM_OC_GPRS_NSVC:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1);
+ rc = oml_mo_opstart_ack(mo);
+ if (mo->obj_class == NM_OC_BTS) {
+ oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK);
+ }
+ break;
+ 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..0e407bba
--- /dev/null
+++ b/src/osmo-bts-virtual/l1_if.c
@@ -0,0 +1,318 @@
+/* Virtual BTS layer 1 primitive handling and interface
+ *
+ * Copyright (C) 2015 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <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 <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"
+
+static void virt_um_rcv_cb(struct virt_um_inst *vui, struct msgb *msg)
+{
+ /* FIXME: Handle msg from MS */
+}
+
+
+/* called by common part once OML link is established */
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+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);
+
+ plink->u.virt.virt_um = virt_um_init(plink, plink->u.virt.mcast_group,
+ plink->u.virt.mcast_port,
+ plink->u.virt.mcast_dev, plink,
+ virt_um_rcv_cb);
+ 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);
+ if (pinst->trx == pinst->trx->bts->c0)
+ vbts_sched_start(pinst->trx->bts);
+ }
+
+ /* 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];
+ 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, 0x00, 1);
+ /* activate associated channel */
+ trx_sched_set_lchan(sched, chan_nr, 0x40, 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..8f6d3a86
--- /dev/null
+++ b/src/osmo-bts-virtual/l1_if.h
@@ -0,0 +1,18 @@
+#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 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..a27ee453
--- /dev/null
+++ b/src/osmo-bts-virtual/main.c
@@ -0,0 +1,107 @@
+/* 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>
+
+/* 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;
+ int rc;
+
+ 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");
+}
+
+int main(int argc, char **argv)
+{
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-virtual/scheduler_virtbts.c b/src/osmo-bts-virtual/scheduler_virtbts.c
new file mode 100644
index 00000000..c23632c5
--- /dev/null
+++ b/src/osmo-bts-virtual/scheduler_virtbts.c
@@ -0,0 +1,594 @@
+/* Scheduler worker functiosn for Virtua 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 <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/netif/rtp.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"
+
+extern void *tall_bts_ctx;
+
+
+
+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];
+ uint8_t ss = 0; //FIXME(chdesc);
+ uint8_t gsmtap_chan;
+ struct msgb *outmsg;
+
+ gsmtap_chan = chantype_rsl2gsmtap(chdesc->chan_nr, chdesc->link_id);
+ outmsg = gsmtap_makemsg(l1t->trx->arfcn, tn, gsmtap_chan, ss, fn,
+ 0, 0, msgb_l2(msg), msgb_l2len(msg));
+ if (outmsg) {
+ struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+ struct virt_um_inst *virt_um = pinst->phy_link->u.virt.virt_um;
+ virt_um_write_msg(virt_um, outmsg);
+ }
+
+ /* free 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)
+{
+ 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)
+{
+ 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)
+{
+ 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)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+ struct msgb *msg;
+
+ if (bid > 0)
+ return NULL;
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (msg)
+ goto got_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);
+
+no_msg:
+ return NULL;
+
+got_msg:
+ /* 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);
+ goto no_msg;
+ }
+
+ 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)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+ struct msgb *msg = NULL; /* make GCC happy */
+ int rc;
+
+ if (bid > 0)
+ return NULL;
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (msg)
+ goto got_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);
+
+no_msg:
+ return NULL;
+
+got_msg:
+ 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;
+ uint8_t bfi, cmr_codec, ft_codec;
+ int cmr, ft, i;
+
+ 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;
+ }
+#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)
+{
+ struct msgb *msg_tch = NULL, *msg_facch = NULL;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ uint8_t tch_mode = chan_state->tch_mode;
+
+ 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)
+{
+ struct msgb *msg_tch = NULL, *msg_facch = NULL;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct gsm_bts_trx_ts *ts = &l1t->trx->ts[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, 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, 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, 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, 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, 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 */
+ 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;
+
+ for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
+ /* Generate RTS.ind to higher layers */
+ _sched_rts(l1t, tn, (fn + RTS_ADVANCE) % GSM_HYPERFRAME);
+ /* schedule transmit backend functions */
+ _sched_dl_burst(l1t, tn, fn);
+ }
+ }
+
+ 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);
+
+ elapsed_us = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 +
+ (tv_now.tv_usec - tv_clock->tv_usec);
+
+ if (elapsed_us > 2*FRAME_DURATION_uS)
+ LOGP(DL1P, LOGL_NOTICE, "vbts_fn_timer_cb after %d us\n", elapsed_us);
+
+ 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);
+ 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 */
+ 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);
+ 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..2126b092
--- /dev/null
+++ b/src/osmo-bts-virtual/virtual_um.c
@@ -0,0 +1,211 @@
+/* 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 <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <net/if.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/gsmtap.h>
+
+#include "virtual_um.h"
+
+#define VIRT_UM_MSGB_SIZE 256
+
+static int mcast_join_group(int fd, const char *group, const char *netdev)
+{
+ int ifindex = 0;
+ int rc, af;
+ socklen_t af_len = sizeof(af);
+
+ if (netdev) {
+ ifindex = if_nametoindex(netdev);
+ if (!ifindex)
+ return -1;
+ }
+
+ rc = getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &af, &af_len);
+ if (rc < 0)
+ return rc;
+
+ switch (af) {
+ case AF_INET:
+ {
+ struct ip_mreqn mr;
+ memset(&mr, 0, sizeof(mr));
+ inet_pton(AF_INET, group, &mr.imr_multiaddr);
+ if (ifindex)
+ mr.imr_ifindex = ifindex;
+ rc = setsockopt(fd, SOL_SOCKET, IP_ADD_MEMBERSHIP,
+ &mr, sizeof(mr));
+ }
+ break;
+ case AF_INET6:
+ {
+ struct ipv6_mreq mr;
+ memset(&mr, 0, sizeof(mr));
+ inet_pton(AF_INET6, group, &mr.ipv6mr_multiaddr);
+ if (ifindex)
+ mr.ipv6mr_interface = ifindex;
+ rc = setsockopt(fd, SOL_SOCKET, IPV6_ADD_MEMBERSHIP,
+ &mr, sizeof(mr));
+
+ }
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+static int mcast_connect(int fd, const char *group, uint16_t port)
+{
+ int rc, af;
+ socklen_t af_len = sizeof(af);
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+
+ rc = getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &af, &af_len);
+ if (rc < 0)
+ return rc;
+
+ switch (af) {
+ case AF_INET:
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(port);
+ inet_pton(AF_INET, group, &sin.sin_addr);
+ rc = connect(fd, (struct sockaddr *) &sin, sizeof(sin));
+ break;
+ case AF_INET6:
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = htons(port);
+ inet_pton(AF_INET6, group, &sin6.sin6_addr);
+ rc = connect(fd, (struct sockaddr *) &sin6, sizeof(sin6));
+ break;
+ default:
+ return -1;
+ }
+
+ return rc;
+}
+
+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;
+
+ rc = read(ofd->fd, msgb_data(msg), msgb_tailroom(msg));
+ if (rc > 0) {
+ msgb_put(msg, rc);
+ vui->recv_cb(vui, msg);
+ } else {
+ vui->recv_cb(vui, NULL);
+ 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 *group, uint16_t port,
+ const char *netdev, void *priv,
+ void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg))
+{
+ struct virt_um_inst *vui;
+ int fd, rc;
+
+ if (!port)
+ port = GSMTAP_UDP_PORT;
+ if (!group)
+ group = "239.0.47.29";
+
+ /* crate a socked and bind it to the multicast group. Do NOT
+ * specify a fixed port locally, to make stack choose a random
+ * free UDP port. */
+ fd = osmo_sock_init(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, group,
+ 0, OSMO_SOCK_F_BIND);
+ if (fd < 0)
+ return NULL;
+
+ /* join the multicast group */
+ rc = mcast_join_group(fd, group, netdev);
+ if (rc < 0) {
+ close(fd);
+ return NULL;
+ }
+
+ /* and finally also connect */
+ rc = mcast_connect(fd, group, port);
+ if (rc < 0) {
+ close(fd);
+ return NULL;
+ }
+
+ vui = talloc_zero(ctx, struct virt_um_inst);
+ vui->priv = priv;
+ vui->recv_cb = recv_cb;
+ vui->ofd.data = vui;
+ vui->ofd.fd = fd;
+ vui->ofd.when = BSC_FD_READ;
+ vui->ofd.cb = virt_um_fd_cb;
+
+ osmo_fd_register(&vui->ofd);
+
+ return vui;
+}
+
+void virt_um_destroy(struct virt_um_inst *vui)
+{
+ struct osmo_fd *ofd = &vui->ofd;
+
+ osmo_fd_unregister(ofd);
+ close(ofd->fd);
+ ofd->fd = -1;
+ ofd->when = 0;
+
+ talloc_free(vui);
+}
+
+int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(vui->ofd.fd, 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..65292211
--- /dev/null
+++ b/src/osmo-bts-virtual/virtual_um.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/msgb.h>
+
+struct virt_um_inst {
+ void *priv;
+ struct osmo_fd ofd;
+ void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg);
+};
+
+struct virt_um_inst *virt_um_init(void *ctx, const char *group, uint16_t port,
+ const char *netdev, void *priv,
+ 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..e8c97acc
--- /dev/null
+++ b/src/osmo-bts-virtual/virtualbts_vty.c
@@ -0,0 +1,142 @@
+/* VTY interface 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 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>
+
+#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(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 (plink->u.virt.mcast_group)
+ vty_out(vty, " virtual-um multicast-group %s%s",
+ plink->u.virt.mcast_group, VTY_NEWLINE);
+ if (plink->u.virt.mcast_port)
+ vty_out(vty, " virtual-um udp-port %u%s",
+ plink->u.virt.mcast_port, VTY_NEWLINE);
+}
+
+#define VUM_STR "Virtual Um layer\n"
+
+DEFUN(cfg_phy_mcast_group, cfg_phy_mcast_group_cmd,
+ "virtual-um multicast-group GROUP",
+ VUM_STR "Configure the 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;
+ }
+
+ if (plink->u.virt.mcast_group)
+ talloc_free(plink->u.virt.mcast_group);
+ plink->u.virt.mcast_group = talloc_strdup(plink, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_phy_mcast_port, cfg_phy_mcast_port_cmd,
+ "virtual-um udp-port <0-65535>",
+ VUM_STR "Configure the 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.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;
+ }
+
+ if (plink->u.virt.mcast_dev)
+ talloc_free(plink->u.virt.mcast_dev);
+ plink->u.virt.mcast_dev = talloc_strdup(plink, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ install_element(PHY_NODE, &cfg_phy_mcast_group_cmd);
+ install_element(PHY_NODE, &cfg_phy_mcast_port_cmd);
+ install_element(PHY_NODE, &cfg_phy_mcast_dev_cmd);
+
+ return 0;
+}