From bf286abc409d04dc1f317a2d74f050235958dac4 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sat, 9 Jan 2016 13:13:37 +0100 Subject: WIP: Initial check-in of a new virtual BTS --- configure.ac | 1 + include/osmo-bts/gsm_data.h | 6 + include/osmo-bts/phy_link.h | 11 + src/Makefile.am | 2 +- src/osmo-bts-virtual/Makefile.am | 10 + src/osmo-bts-virtual/bts_model.c | 149 ++++++++ src/osmo-bts-virtual/l1_if.c | 318 +++++++++++++++++ src/osmo-bts-virtual/l1_if.h | 18 + src/osmo-bts-virtual/main.c | 107 ++++++ src/osmo-bts-virtual/scheduler_virtbts.c | 594 +++++++++++++++++++++++++++++++ src/osmo-bts-virtual/virtual_um.c | 211 +++++++++++ src/osmo-bts-virtual/virtual_um.h | 18 + src/osmo-bts-virtual/virtualbts_vty.c | 142 ++++++++ 13 files changed, 1586 insertions(+), 1 deletion(-) create mode 100644 src/osmo-bts-virtual/Makefile.am create mode 100644 src/osmo-bts-virtual/bts_model.c create mode 100644 src/osmo-bts-virtual/l1_if.c create mode 100644 src/osmo-bts-virtual/l1_if.h create mode 100644 src/osmo-bts-virtual/main.c create mode 100644 src/osmo-bts-virtual/scheduler_virtbts.c create mode 100644 src/osmo-bts-virtual/virtual_um.c create mode 100644 src/osmo-bts-virtual/virtual_um.h create mode 100644 src/osmo-bts-virtual/virtualbts_vty.c 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 { @@ -54,6 +56,12 @@ struct phy_link { int power_oml; 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; @@ -101,6 +109,9 @@ struct phy_instance { struct trx_l1h *hdl; bool sw_act_reported; } osmotrx; + struct { + struct l1sched_trx sched; + } virt; struct { /* logical transceiver number within one PHY */ uint32_t trx_id; 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 + * + * 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 . + * + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + * + * 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +#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 + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* 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 + * + * 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 . + * + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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 +#include + +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 + * + * 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 . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#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; +} -- cgit v1.2.3