diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | include/osmo-bts/gsm_data.h | 6 | ||||
-rw-r--r-- | include/osmo-bts/phy_link.h | 13 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/osmo-bts-virtual/Makefile.am | 10 | ||||
-rw-r--r-- | src/osmo-bts-virtual/bts_model.c | 169 | ||||
-rw-r--r-- | src/osmo-bts-virtual/l1_if.c | 465 | ||||
-rw-r--r-- | src/osmo-bts-virtual/l1_if.h | 20 | ||||
-rw-r--r-- | src/osmo-bts-virtual/main.c | 126 | ||||
-rw-r--r-- | src/osmo-bts-virtual/osmo_mcast_sock.c | 112 | ||||
-rw-r--r-- | src/osmo-bts-virtual/osmo_mcast_sock.h | 29 | ||||
-rw-r--r-- | src/osmo-bts-virtual/scheduler_virtbts.c | 633 | ||||
-rw-r--r-- | src/osmo-bts-virtual/virtual_um.c | 92 | ||||
-rw-r--r-- | src/osmo-bts-virtual/virtual_um.h | 26 | ||||
-rw-r--r-- | src/osmo-bts-virtual/virtualbts_vty.c | 185 |
16 files changed, 1890 insertions, 1 deletions
@@ -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, ×lot); + /* 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; +} |