aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bts-trx
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bts-trx')
-rw-r--r--src/osmo-bts-trx/Makefile.am10
-rw-r--r--src/osmo-bts-trx/l1_if.c782
-rw-r--r--src/osmo-bts-trx/l1_if.h82
-rw-r--r--src/osmo-bts-trx/loops.c340
-rw-r--r--src/osmo-bts-trx/loops.h27
-rw-r--r--src/osmo-bts-trx/main.c151
-rw-r--r--src/osmo-bts-trx/scheduler_trx.c1635
-rw-r--r--src/osmo-bts-trx/trx_if.c838
-rw-r--r--src/osmo-bts-trx/trx_if.h35
-rw-r--r--src/osmo-bts-trx/trx_vty.c606
10 files changed, 4506 insertions, 0 deletions
diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am
new file mode 100644
index 00000000..19222405
--- /dev/null
+++ b/src/osmo-bts-trx/Makefile.am
@@ -0,0 +1,10 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOCODING_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOCODING_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl
+
+EXTRA_DIST = trx_if.h l1_if.h loops.h
+
+bin_PROGRAMS = osmo-bts-trx
+
+osmo_bts_trx_SOURCES = main.c trx_if.c l1_if.c scheduler_trx.c trx_vty.c loops.c
+osmo_bts_trx_LDADD = $(top_builddir)/src/common/libl1sched.a $(top_builddir)/src/common/libbts.a $(LDADD)
diff --git a/src/osmo-bts-trx/l1_if.c b/src/osmo-bts-trx/l1_if.c
new file mode 100644
index 00000000..da1b554f
--- /dev/null
+++ b/src/osmo-bts-trx/l1_if.c
@@ -0,0 +1,782 @@
+/*
+ * layer 1 primitive handling and interface
+ *
+ * Copyright (C) 2013 Andreas Eversberg <jolly@eversberg.eu>
+ * Copyright (C) 2015 Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ *
+ * 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 <stdbool.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/abis_nm.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/amr.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/scheduler.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+
+
+static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = {
+ [GSM_PCHAN_NONE] = 8,
+ [GSM_PCHAN_CCCH] = 4,
+ [GSM_PCHAN_CCCH_SDCCH4] = 5,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 5,
+ [GSM_PCHAN_TCH_F] = 1,
+ [GSM_PCHAN_TCH_H] = 3,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = 7,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 7,
+ [GSM_PCHAN_PDCH] = 13,
+ /* [GSM_PCHAN_TCH_F_PDCH] not needed here, see trx_set_ts_as_pchan() */
+ [GSM_PCHAN_UNKNOWN] = 0,
+};
+
+struct trx_l1h *trx_l1h_alloc(void *tall_ctx, struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h;
+ l1h = talloc_zero(tall_ctx, struct trx_l1h);
+ l1h->phy_inst = pinst;
+ trx_if_init(l1h);
+ return l1h;
+}
+
+static void check_transceiver_availability_trx(struct trx_l1h *l1h, int avail)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct gsm_bts_trx *trx = pinst->trx;
+ uint8_t tn;
+
+ /* HACK, we should change state when we receive first clock from
+ * transceiver */
+ if (avail) {
+ /* signal availability */
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+ if (!pinst->u.osmotrx.sw_act_reported) {
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+ pinst->u.osmotrx.sw_act_reported = true;
+ }
+
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED,
+ (l1h->config.slotmask & (1 << tn)) ?
+ NM_AVSTATE_DEPENDENCY :
+ NM_AVSTATE_NOT_INSTALLED);
+ } else {
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED,
+ NM_AVSTATE_OFF_LINE);
+ oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED,
+ NM_AVSTATE_OFF_LINE);
+
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED,
+ NM_AVSTATE_OFF_LINE);
+ }
+}
+
+int check_transceiver_availability(struct gsm_bts *bts, int avail)
+{
+ struct gsm_bts_trx *trx;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ check_transceiver_availability_trx(l1h, avail);
+ }
+ return 0;
+}
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) {
+ lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+ /* FIXME: perform whatever is needed (if any) to set proper PCH/AGCH allocation according to
+ 3GPP TS 44.018 Table 10.5.2.11.1 using num_agch(lchan->ts->trx, "TRX L1"); function */
+ return 0;
+ }
+ /* set lchan inactive */
+ lchan_set_state(lchan, LCHAN_S_NONE);
+
+ return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan),
+ LID_DEDIC, 0);
+}
+
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
+{
+ struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan),
+ LID_SACCH, 0);
+}
+
+/*
+ * transceiver provisioning
+ */
+int l1if_provision_transceiver_trx(struct trx_l1h *l1h)
+{
+ uint8_t tn;
+
+ if (!transceiver_available)
+ return -EIO;
+
+ if (l1h->config.poweron
+ && l1h->config.tsc_valid
+ && l1h->config.bsic_valid
+ && l1h->config.arfcn_valid) {
+ /* before power on */
+ if (!l1h->config.arfcn_sent) {
+ trx_if_cmd_rxtune(l1h, l1h->config.arfcn);
+ trx_if_cmd_txtune(l1h, l1h->config.arfcn);
+ l1h->config.arfcn_sent = 1;
+ }
+ if (!l1h->config.tsc_sent) {
+ trx_if_cmd_settsc(l1h, l1h->config.tsc);
+ l1h->config.tsc_sent = 1;
+ }
+ if (!l1h->config.bsic_sent) {
+ trx_if_cmd_setbsic(l1h, l1h->config.bsic);
+ l1h->config.bsic_sent = 1;
+ }
+
+ if (!l1h->config.poweron_sent) {
+ trx_if_cmd_poweron(l1h);
+ l1h->config.poweron_sent = 1;
+ }
+
+ /* after power on */
+ if (l1h->config.rxgain_valid && !l1h->config.rxgain_sent) {
+ trx_if_cmd_setrxgain(l1h, l1h->config.rxgain);
+ l1h->config.rxgain_sent = 1;
+ }
+ if (l1h->config.power_valid && !l1h->config.power_sent) {
+ trx_if_cmd_setpower(l1h, l1h->config.power);
+ l1h->config.power_sent = 1;
+ }
+ if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) {
+ trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly);
+ l1h->config.maxdly_sent = 1;
+ }
+ if (l1h->config.maxdlynb_valid && !l1h->config.maxdlynb_sent) {
+ trx_if_cmd_setmaxdlynb(l1h, l1h->config.maxdlynb);
+ l1h->config.maxdlynb_sent = 1;
+ }
+
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ if (l1h->config.slottype_valid[tn]
+ && !l1h->config.slottype_sent[tn]) {
+ trx_if_cmd_setslot(l1h, tn,
+ l1h->config.slottype[tn]);
+ l1h->config.slottype_sent[tn] = 1;
+ }
+ }
+ return 0;
+ }
+
+ if (!l1h->config.poweron && !l1h->config.poweron_sent) {
+ trx_if_cmd_poweroff(l1h);
+ l1h->config.poweron_sent = 1;
+ l1h->config.rxgain_sent = 0;
+ l1h->config.power_sent = 0;
+ l1h->config.maxdly_sent = 0;
+ l1h->config.maxdlynb_sent = 0;
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ l1h->config.slottype_sent[tn] = 0;
+ }
+
+ return 0;
+}
+
+int l1if_provision_transceiver(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t tn;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ l1h->config.arfcn_sent = 0;
+ l1h->config.tsc_sent = 0;
+ l1h->config.bsic_sent = 0;
+ l1h->config.poweron_sent = 0;
+ l1h->config.rxgain_sent = 0;
+ l1h->config.power_sent = 0;
+ l1h->config.maxdly_sent = 0;
+ l1h->config.maxdlynb_sent = 0;
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ l1h->config.slottype_sent[tn] = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+ return 0;
+}
+
+/*
+ * activation/configuration/deactivation of transceiver's TRX
+ */
+
+/* initialize the layer1 */
+static int trx_init(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ /* power on transceiver, if not already */
+ if (!l1h->config.poweron) {
+ l1h->config.poweron = 1;
+ l1h->config.poweron_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ if (trx == trx->bts->c0)
+ lchan_init_lapdm(&trx->ts[0].lchan[CCCH_LCHAN]);
+
+ /* Set to Operational State: Enabled */
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+
+ /* Send OPSTART ack */
+ return oml_mo_opstart_ack(&trx->mo);
+}
+
+/* deactivate transceiver */
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ enum gsm_phys_chan_config pchan = trx->ts[0].pchan;
+
+ /* close all logical channels and reset timeslots */
+ trx_sched_reset(&l1h->l1s);
+
+ /* deactivate lchan for CCCH */
+ if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4 ||
+ pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) {
+ lchan_set_state(&trx->ts[0].lchan[CCCH_LCHAN], LCHAN_S_INACTIVE);
+ }
+
+ /* power off transceiver, if not already */
+ if (l1h->config.poweron) {
+ l1h->config.poweron = 0;
+ l1h->config.poweron_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ /* Set to Operational State: Disabled */
+ check_transceiver_availability_trx(l1h, 0);
+
+ return 0;
+}
+
+/* on RSL failure, deactivate transceiver */
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ bts_shutdown(bts, "Abis close");
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ /* we always implement the power control loop in osmo-bts software, as
+ * there is no automatism in the underlying osmo-trx */
+ return 0;
+}
+
+/* set bts attributes */
+static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t bsic = bts->bsic;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) {
+ l1h->config.bsic = bsic;
+ l1h->config.bsic_valid = 1;
+ l1h->config.bsic_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+ }
+ check_transceiver_availability(bts, transceiver_available);
+
+
+ return 0;
+}
+
+/* set trx attributes */
+static uint8_t trx_set_trx(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ uint16_t arfcn = trx->arfcn;
+
+ if (l1h->config.arfcn != arfcn || !l1h->config.arfcn_valid) {
+ l1h->config.arfcn = arfcn;
+ l1h->config.arfcn_valid = 1;
+ l1h->config.arfcn_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ if (l1h->config.power_oml) {
+ l1h->config.power = trx->max_power_red;
+ l1h->config.power_valid = 1;
+ l1h->config.power_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ return 0;
+}
+
+/* set ts attributes */
+static uint8_t trx_set_ts_as_pchan(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan)
+{
+ struct phy_instance *pinst = trx_phy_instance(ts->trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ uint8_t tn = ts->nr;
+ uint16_t tsc = ts->tsc;
+ uint8_t slottype;
+ int rc;
+
+ /* all TSC of all timeslots must be equal, because transceiver only
+ * supports one TSC per TRX */
+
+ if (l1h->config.tsc != tsc || !l1h->config.tsc_valid) {
+ l1h->config.tsc = tsc;
+ l1h->config.tsc_valid = 1;
+ l1h->config.tsc_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ /* ignore disabled slots */
+ if (!(l1h->config.slotmask & (1 << tn)))
+ return NM_NACK_RES_NOTAVAIL;
+
+ /* set physical channel. For dynamic timeslots, the caller should have
+ * decided on a more specific PCHAN type already. */
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH);
+ rc = trx_sched_set_pchan(&l1h->l1s, tn, pchan);
+ if (rc)
+ return NM_NACK_RES_NOTAVAIL;
+
+ /* activate lchan for CCCH */
+ if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4 ||
+ pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) {
+ ts->lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_set_state(&ts->lchan[CCCH_LCHAN], LCHAN_S_ACTIVE);
+ }
+
+ slottype = transceiver_chan_types[pchan];
+
+ if (l1h->config.slottype[tn] != slottype
+ || !l1h->config.slottype_valid[tn]) {
+ l1h->config.slottype[tn] = slottype;
+ l1h->config.slottype_valid[tn] = 1;
+ l1h->config.slottype_sent[tn] = 0;
+ l1if_provision_transceiver_trx(l1h);
+ }
+
+ return 0;
+}
+
+static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config pchan;
+
+ /* For dynamic timeslots, pick the pchan type that should currently be
+ * active. This should only be called during init, PDCH transitions
+ * will call trx_set_ts_as_pchan() directly. */
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ OSMO_ASSERT((ts->flags & TS_F_PDCH_PENDING_MASK) == 0);
+ pchan = (ts->flags & TS_F_PDCH_ACTIVE)? GSM_PCHAN_PDCH
+ : GSM_PCHAN_TCH_F;
+ break;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ OSMO_ASSERT(ts->dyn.pchan_is == ts->dyn.pchan_want);
+ pchan = ts->dyn.pchan_is;
+ break;
+ default:
+ pchan = ts->pchan;
+ break;
+ }
+
+ return trx_set_ts_as_pchan(ts, pchan);
+}
+
+
+/*
+ * primitive handling
+ */
+
+/* enable ciphering */
+static int l1if_set_ciphering(struct trx_l1h *l1h, struct gsm_lchan *lchan,
+ uint8_t chan_nr, int downlink)
+{
+ /* ciphering already enabled in both directions */
+ if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF)
+ return -EINVAL;
+
+ if (!downlink) {
+ /* set uplink */
+ trx_sched_set_cipher(&l1h->l1s, 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(&l1h->l1s, chan_nr, 0,
+ lchan->encr.alg_id - 1, lchan->encr.key,
+ lchan->encr.key_len);
+ }
+ trx_sched_set_cipher(&l1h->l1s, 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 trx_l1h *l1h, uint8_t chan_nr,
+ enum osmo_mph_info_type type, uint8_t cause)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ 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(pinst->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, int16_t toa256,
+ float ber, float rssi, uint32_t fn)
+{
+ 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_256bits = toa256;
+ l1sap->u.info.u.meas_ind.ber10k = (unsigned int) (ber * 10000);
+ l1sap->u.info.u.meas_ind.inv_rssi = (uint8_t) (rssi * -1);
+ l1sap->u.info.u.meas_ind.fn = fn;
+}
+
+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, int16_t toa256)
+{
+ 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;
+
+ LOGPFN(DMEAS, LOGL_DEBUG, fn, "RX UL measurement for %s fn=%u chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS "
+ "ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa256=%d\n",
+ gsm_lchan_name(lchan), fn, chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power_ctrl.current),
+ rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa256);
+
+ l1if_fill_meas_res(&l1sap, chan_nr, toa256, ber, rssi, fn);
+
+ 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 trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ struct msgb *msg = l1sap->oph.msg;
+ uint8_t chan_nr;
+ 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(&l1h->l1s, l1sap);
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+ if (!msg)
+ break;
+ /* put data into scheduler's queue */
+ return trx_sched_tch_req(&l1h->l1s, 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;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.u.ciph_req.uplink)
+ l1if_set_ciphering(l1h, lchan, chan_nr, 0);
+ if (l1sap->u.info.u.ciph_req.downlink)
+ l1if_set_ciphering(l1h, 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;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) {
+ if ((chan_nr & 0xE0) == 0x80) {
+ LOGP(DL1C, LOGL_ERROR, "Cannot activate"
+ " chan_nr 0x%02x\n", chan_nr);
+ break;
+ }
+
+ /* trx_chan_desc[] in scheduler.c uses the RSL_CHAN_OSMO_PDCH cbits
+ * (0xc0) to indicate the need for PDTCH and PTCCH SAPI activation.
+ * However, 0xc0 is a cbits pattern exclusively used for Osmocom style
+ * dyn TS (a non-standard RSL Chan Activ mod); hence, for IPA style dyn
+ * TS, the chan_nr will never reflect 0xc0 and we would omit the
+ * PDTCH,PTTCH SAPIs. To properly de-/activate the PDTCH SAPIs in
+ * scheduler.c, make sure the 0xc0 cbits are set for de-/activating PDTCH
+ * lchans, i.e. both Osmocom and IPA style dyn TS. (For Osmocom style dyn
+ * TS, the chan_nr typically already reflects 0xc0, while it doesn't for
+ * IPA style.) */
+ if (lchan->type == GSM_LCHAN_PDTCH)
+ chan_nr = RSL_CHAN_OSMO_PDCH | (chan_nr & ~RSL_CHAN_NR_MASK);
+
+ /* activate dedicated channel */
+ trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_DEDIC, 1);
+ /* activate associated channel */
+ trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_SACCH, 1);
+ /* set mode */
+ trx_sched_set_mode(&l1h->l1s, 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(l1h, lchan, chan_nr, 0);
+ l1if_set_ciphering(l1h, 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(l1h, chan_nr,
+ PRIM_INFO_ACTIVATE, 0);
+ break;
+ }
+ if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+ /* change mode */
+ trx_sched_set_mode(&l1h->l1s, 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;
+ }
+ /* here, type == PRIM_INFO_DEACTIVATE */
+ if ((chan_nr & 0xE0) == 0x80) {
+ LOGP(DL1C, LOGL_ERROR, "Cannot deactivate "
+ "chan_nr 0x%02x\n", chan_nr);
+ break;
+ }
+ /* deactivate associated channel */
+ bts_model_lchan_deactivate_sacch(lchan);
+ if (!l1sap->u.info.u.act_req.sacch_only) {
+ /* deactivate dedicated channel */
+ lchan_deactivate(lchan);
+ /* confirm only on dedicated channel */
+ mph_info_chan_confirm(l1h, chan_nr,
+ PRIM_INFO_DEACTIVATE, 0);
+ }
+ 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;
+}
+
+
+/*
+ * oml handling
+ */
+
+/* callback from OML */
+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)
+{
+ /* FIXME: check if the attributes are valid */
+ return 0;
+}
+
+/* callback from OML */
+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 = trx_set_bts(obj, new_attr);
+ break;
+ case NM_MT_SET_RADIO_ATTR:
+ cause = trx_set_trx(obj);
+ break;
+ case NM_MT_SET_CHAN_ATTR:
+ cause = trx_set_ts(obj);
+ break;
+ }
+
+ return oml_fom_ack_nack(msg, cause);
+}
+
+/* callback from OML */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj)
+{
+ int rc;
+ LOGP(DOML, LOGL_DEBUG, "bts_model_opstart: %s received\n",
+ get_value_string(abis_nm_obj_class_names, mo->obj_class));
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ /* activate transceiver */
+ rc = trx_init(obj);
+ break;
+ case NM_OC_CHANNEL:
+ 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, NM_AVSTATE_OK);
+ rc = oml_mo_opstart_ack(mo);
+ 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)
+{
+ /* blindly accept all state changes */
+ 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_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+#warning "implement bts_model_change_power\n"
+ LOGP(DL1C, LOGL_NOTICE, "Setting TRX output power not supported!\n");
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ /* no action required, signal completion right away. */
+ cb_ts_disconnected(ts);
+ return 0;
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config as_pchan)
+{
+ int rc;
+ LOGP(DL1C, LOGL_DEBUG, "%s bts_model_ts_connect(as_pchan=%s)\n",
+ gsm_ts_name(ts), gsm_pchan_name(as_pchan));
+
+ rc = trx_set_ts_as_pchan(ts, as_pchan);
+ if (rc)
+ cb_ts_connected(ts, rc);
+
+ LOGP(DL1C, LOGL_NOTICE, "%s bts_model_ts_connect(as_pchan=%s) success,"
+ " calling cb_ts_connected()\n",
+ gsm_ts_name(ts), gsm_pchan_name(as_pchan));
+ cb_ts_connected(ts, 0);
+}
diff --git a/src/osmo-bts-trx/l1_if.h b/src/osmo-bts-trx/l1_if.h
new file mode 100644
index 00000000..165f9d81
--- /dev/null
+++ b/src/osmo-bts-trx/l1_if.h
@@ -0,0 +1,82 @@
+#ifndef L1_IF_H_TRX
+#define L1_IF_H_TRX
+
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/phy_link.h>
+#include "trx_if.h"
+
+struct trx_config {
+ uint8_t poweron; /* poweron(1) or poweroff(0) */
+ int poweron_sent;
+
+ int arfcn_valid;
+ uint16_t arfcn;
+ int arfcn_sent;
+
+ int tsc_valid;
+ uint8_t tsc;
+ int tsc_sent;
+
+ int bsic_valid;
+ uint8_t bsic;
+ int bsic_sent;
+
+ int rxgain_valid;
+ uint8_t rxgain;
+ int rxgain_sent;
+
+ int power_valid;
+ uint8_t power;
+ int power_oml;
+ int power_sent;
+
+ int maxdly_valid;
+ int maxdly;
+ int maxdly_sent;
+
+ int maxdlynb_valid;
+ int maxdlynb;
+ int maxdlynb_sent;
+
+ uint8_t slotmask;
+
+ int slottype_valid[TRX_NR_TS];
+ uint8_t slottype[TRX_NR_TS];
+ int slottype_sent[TRX_NR_TS];
+};
+
+struct trx_l1h {
+ struct llist_head trx_ctrl_list;
+ /* Latest RSPed cmd, used to catch duplicate RSPs from sent retransmissions */
+ struct trx_ctrl_msg *last_acked;
+
+ //struct gsm_bts_trx *trx;
+ struct phy_instance *phy_inst;
+
+ struct osmo_fd trx_ofd_ctrl;
+ struct osmo_timer_list trx_ctrl_timer;
+ struct osmo_fd trx_ofd_data;
+
+ /* transceiver config */
+ struct trx_config config;
+ uint8_t ho_rach_detect[TRX_NR_TS][TS_MAX_LCHAN];
+
+ struct l1sched_trx l1s;
+};
+
+struct trx_l1h *trx_l1h_alloc(void *tall_ctx, struct phy_instance *pinst);
+int check_transceiver_availability(struct gsm_bts *bts, int avail);
+int l1if_provision_transceiver_trx(struct trx_l1h *l1h);
+int l1if_provision_transceiver(struct gsm_bts *bts);
+int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn);
+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, int16_t toa256);
+
+static inline struct l1sched_trx *trx_l1sched_hdl(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx->role_bts.l1h;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ return &l1h->l1s;
+}
+
+#endif /* L1_IF_H_TRX */
diff --git a/src/osmo-bts-trx/loops.c b/src/osmo-bts-trx/loops.c
new file mode 100644
index 00000000..926b4c6f
--- /dev/null
+++ b/src/osmo-bts-trx/loops.c
@@ -0,0 +1,340 @@
+/* Loop control for OsmoBTS-TRX */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * 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 <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/l1sap.h>
+#include <osmocom/core/bits.h>
+
+#include "trx_if.h"
+#include "l1_if.h"
+#include "loops.h"
+
+/*
+ * MS Power loop
+ */
+
+static int ms_power_diff(struct gsm_lchan *lchan, uint8_t chan_nr, int8_t diff)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ enum gsm_band band = trx->bts->band;
+ uint16_t arfcn = trx->arfcn;
+ int8_t new_power;
+
+ new_power = lchan->ms_power_ctrl.current - (diff >> 1);
+
+ if (diff == 0)
+ return 0;
+
+ if (new_power < 0)
+ new_power = 0;
+
+ // FIXME: to go above 1W, we need to know classmark of MS
+ if (arfcn >= 512 && arfcn <= 885) {
+ if (new_power > 15)
+ new_power = 15;
+ } else {
+ if (new_power > 19)
+ new_power = 19;
+ }
+
+ /* a higher value means a lower level (and vice versa) */
+ if (new_power > lchan->ms_power_ctrl.current + MS_LOWER_MAX)
+ new_power = lchan->ms_power_ctrl.current + MS_LOWER_MAX;
+ else if (new_power < lchan->ms_power_ctrl.current - MS_RAISE_MAX)
+ new_power = lchan->ms_power_ctrl.current - MS_RAISE_MAX;
+
+ if (lchan->ms_power_ctrl.current == new_power) {
+ LOGP(DLOOP, LOGL_INFO, "Keeping MS new_power of trx=%u "
+ "chan_nr=0x%02x at control level %d (%d dBm)\n",
+ trx->nr, chan_nr, new_power,
+ ms_pwr_dbm(band, new_power));
+
+ return 0;
+ }
+
+ LOGP(DLOOP, LOGL_INFO, "%s MS new_power of trx=%u chan_nr=0x%02x from "
+ "control level %d (%d dBm) to %d (%d dBm)\n",
+ (diff > 0) ? "Raising" : "Lowering",
+ trx->nr, chan_nr, lchan->ms_power_ctrl.current,
+ ms_pwr_dbm(band, lchan->ms_power_ctrl.current), new_power,
+ ms_pwr_dbm(band, new_power));
+
+ lchan->ms_power_ctrl.current = new_power;
+
+ return 0;
+}
+
+static int ms_power_val(struct l1sched_chan_state *chan_state, int8_t rssi)
+{
+ /* ignore inserted dummy frames, treat as lost frames */
+ if (rssi < -127)
+ return 0;
+
+ LOGP(DLOOP, LOGL_DEBUG, "Got RSSI value of %d\n", rssi);
+
+ chan_state->meas.rssi_count++;
+
+ chan_state->meas.rssi_got_burst = 1;
+
+ /* store and process RSSI */
+ if (chan_state->meas.rssi_valid_count
+ == ARRAY_SIZE(chan_state->meas.rssi))
+ return 0;
+ chan_state->meas.rssi[chan_state->meas.rssi_valid_count++] = rssi;
+ chan_state->meas.rssi_valid_count++;
+
+ return 0;
+}
+
+static int ms_power_clock(struct gsm_lchan *lchan,
+ uint8_t chan_nr, struct l1sched_chan_state *chan_state)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ int rssi;
+ int i;
+
+ /* skip every second clock, to prevent oscillating due to roundtrip
+ * delay */
+ if (!(chan_state->meas.clock & 1))
+ return 0;
+
+ LOGP(DLOOP, LOGL_DEBUG, "Got SACCH master clock at RSSI count %d\n",
+ chan_state->meas.rssi_count);
+
+ /* wait for initial burst */
+ if (!chan_state->meas.rssi_got_burst)
+ return 0;
+
+ /* if no burst was received from MS at clock */
+ if (chan_state->meas.rssi_count == 0) {
+ LOGP(DLOOP, LOGL_NOTICE, "LOST SACCH frame of trx=%u "
+ "chan_nr=0x%02x, so we raise MS power\n",
+ trx->nr, chan_nr);
+ return ms_power_diff(lchan, chan_nr, MS_RAISE_MAX);
+ }
+
+ /* reset total counter */
+ chan_state->meas.rssi_count = 0;
+
+ /* check the minimum level received after MS acknowledged the ordered
+ * power level */
+ if (chan_state->meas.rssi_valid_count == 0)
+ return 0;
+ for (rssi = 999, i = 0; i < chan_state->meas.rssi_valid_count; i++) {
+ if (rssi > chan_state->meas.rssi[i])
+ rssi = chan_state->meas.rssi[i];
+ }
+
+ /* reset valid counter */
+ chan_state->meas.rssi_valid_count = 0;
+
+ /* change RSSI */
+ LOGP(DLOOP, LOGL_DEBUG, "Lowest RSSI: %d Target RSSI: %d Current "
+ "MS power: %d (%d dBm) of trx=%u chan_nr=0x%02x\n", rssi,
+ pinst->phy_link->u.osmotrx.trx_target_rssi, lchan->ms_power_ctrl.current,
+ ms_pwr_dbm(trx->bts->band, lchan->ms_power_ctrl.current),
+ trx->nr, chan_nr);
+ ms_power_diff(lchan, chan_nr, pinst->phy_link->u.osmotrx.trx_target_rssi - rssi);
+
+ return 0;
+}
+
+
+/* 90% of one bit duration in 1/256 symbols: 256*0.9 */
+#define TOA256_9OPERCENT 230
+
+/*
+ * Timing Advance loop
+ */
+
+int ta_val(struct gsm_lchan *lchan, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, int16_t toa256)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+
+ /* check if the current L1 header acks to the current ordered TA */
+ if (lchan->meas.l1_info[1] != lchan->rqd_ta)
+ return 0;
+
+ /* sum measurement */
+ chan_state->meas.toa256_sum += toa256;
+ if (++(chan_state->meas.toa_num) < 16)
+ return 0;
+
+ /* complete set */
+ toa256 = chan_state->meas.toa256_sum / chan_state->meas.toa_num;
+
+ /* check for change of TOA */
+ if (toa256 < -TOA256_9OPERCENT && lchan->rqd_ta > 0) {
+ LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too "
+ "early (%d), now lowering TA from %d to %d\n",
+ trx->nr, chan_nr, toa256, lchan->rqd_ta,
+ lchan->rqd_ta - 1);
+ lchan->rqd_ta--;
+ } else if (toa256 > TOA256_9OPERCENT && lchan->rqd_ta < 63) {
+ LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too "
+ "late (%d), now raising TA from %d to %d\n",
+ trx->nr, chan_nr, toa256, lchan->rqd_ta,
+ lchan->rqd_ta + 1);
+ lchan->rqd_ta++;
+ } else
+ LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is "
+ "correct (%d), keeping current TA of %d\n",
+ trx->nr, chan_nr, toa256, lchan->rqd_ta);
+
+ chan_state->meas.toa_num = 0;
+ chan_state->meas.toa256_sum = 0;
+
+ return 0;
+}
+
+int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa256)
+{
+ struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)]
+ .lchan[l1sap_chan2ss(chan_nr)];
+ struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+
+ if (pinst->phy_link->u.osmotrx.trx_ms_power_loop)
+ ms_power_val(chan_state, rssi);
+
+ if (pinst->phy_link->u.osmotrx.trx_ta_loop)
+ ta_val(lchan, chan_nr, chan_state, toa256);
+
+ return 0;
+}
+
+int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state)
+{
+ struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)]
+ .lchan[l1sap_chan2ss(chan_nr)];
+ struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+
+ if (pinst->phy_link->u.osmotrx.trx_ms_power_loop)
+ ms_power_clock(lchan, chan_nr, chan_state);
+
+ /* count the number of SACCH clocks */
+ chan_state->meas.clock++;
+
+ return 0;
+}
+
+int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, float ber)
+{
+ struct gsm_bts_trx *trx = l1t->trx;
+ struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)]
+ .lchan[l1sap_chan2ss(chan_nr)];
+
+ /* check if loop is enabled */
+ if (!chan_state->amr_loop)
+ return 0;
+
+ /* wait for MS to use the requested codec */
+ if (chan_state->ul_ft != chan_state->dl_cmr)
+ return 0;
+
+ /* count bit errors */
+ if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ chan_state->ber_num += 2;
+ chan_state->ber_sum += (ber + ber);
+ } else {
+ chan_state->ber_num++;
+ chan_state->ber_sum += ber;
+ }
+
+ /* count frames */
+ if (chan_state->ber_num < 48)
+ return 0;
+
+ /* calculate average (reuse ber variable) */
+ ber = chan_state->ber_sum / chan_state->ber_num;
+
+ /* reset bit errors */
+ chan_state->ber_num = 0;
+ chan_state->ber_sum = 0;
+
+ LOGP(DLOOP, LOGL_DEBUG, "Current bit error rate (BER) %.6f "
+ "codec id %d of trx=%u chan_nr=0x%02x\n", ber,
+ chan_state->ul_ft, trx->nr, chan_nr);
+
+ /* degrade */
+ if (chan_state->dl_cmr > 0) {
+ /* degrade, if ber is above threshold FIXME: C/I */
+ if (ber >
+ lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr-1].threshold) {
+ LOGP(DLOOP, LOGL_DEBUG, "Degrading due to BER %.6f "
+ "from codec id %d to %d of trx=%u "
+ "chan_nr=0x%02x\n", ber, chan_state->dl_cmr,
+ chan_state->dl_cmr - 1, trx->nr, chan_nr);
+ chan_state->dl_cmr--;
+ }
+
+ return 0;
+ }
+
+ /* upgrade */
+ if (chan_state->dl_cmr < chan_state->codecs - 1) {
+ /* degrade, if ber is above threshold FIXME: C/I*/
+ if (ber <
+ lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr].threshold
+ - lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr].hysteresis) {
+ LOGP(DLOOP, LOGL_DEBUG, "Upgrading due to BER %.6f "
+ "from codec id %d to %d of trx=%u "
+ "chan_nr=0x%02x\n", ber, chan_state->dl_cmr,
+ chan_state->dl_cmr + 1, trx->nr, chan_nr);
+ chan_state->dl_cmr++;
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+
+int trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop)
+{
+ if (chan_state->amr_loop && !loop) {
+ chan_state->amr_loop = 0;
+
+ return 0;
+ }
+
+ if (!chan_state->amr_loop && loop) {
+ chan_state->amr_loop = 1;
+
+ /* reset bit errors */
+ chan_state->ber_num = 0;
+ chan_state->ber_sum = 0;
+
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-trx/loops.h b/src/osmo-bts-trx/loops.h
new file mode 100644
index 00000000..f9e69c84
--- /dev/null
+++ b/src/osmo-bts-trx/loops.h
@@ -0,0 +1,27 @@
+#ifndef _TRX_LOOPS_H
+#define _TRX_LOOPS_H
+
+/*
+ * calibration of loops
+ */
+
+/* how much power levels do we raise/lower as maximum (1 level = 2 dB) */
+#define MS_RAISE_MAX 4
+#define MS_LOWER_MAX 2
+
+/*
+ * loops api
+ */
+
+int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa);
+
+int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state);
+
+int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr,
+ struct l1sched_chan_state *chan_state, float ber);
+
+int trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop);
+
+#endif /* _TRX_LOOPS_H */
diff --git a/src/osmo-bts-trx/main.c b/src/osmo-bts-trx/main.c
new file mode 100644
index 00000000..9529190e
--- /dev/null
+++ b/src/osmo-bts-trx/main.c
@@ -0,0 +1,151 @@
+/* Main program for OsmoBTS-TRX */
+
+/* (C) 2011-2015 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * 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 <sched.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.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 <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/bits.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/control_if.h>
+#include <osmo-bts/scheduler.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+
+/* dummy, since no direct dsp support */
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+ 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[] = {
+ { 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;
+}
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ bts->variant = BTS_OSMO_TRX;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+ /* FIXME: this needs to be overridden with the real hardrware
+ * value */
+ bts->c0->nominal_power = 23;
+
+ gsm_bts_set_feature(bts, BTS_FEAT_GPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_CBCH);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+ plink->u.osmotrx.local_ip = talloc_strdup(plink, "127.0.0.1");
+ plink->u.osmotrx.remote_ip = talloc_strdup(plink, "127.0.0.1");
+ plink->u.osmotrx.base_port_local = 5800;
+ plink->u.osmotrx.base_port_remote = 5700;
+ plink->u.osmotrx.clock_advance = 20;
+ plink->u.osmotrx.rts_advance = 5;
+ plink->u.osmotrx.trx_ta_loop = true;
+ plink->u.osmotrx.trx_ms_power_loop = false;
+ plink->u.osmotrx.trx_target_rssi = -10;
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h;
+ l1h = trx_l1h_alloc(tall_bts_ctx, pinst);
+ pinst->u.osmotrx.hdl = l1h;
+
+ l1h->config.power_oml = 1;
+}
+
+int main(int argc, char **argv)
+{
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c
new file mode 100644
index 00000000..fa3aed22
--- /dev/null
+++ b/src/osmo-bts-trx/scheduler_trx.c
@@ -0,0 +1,1635 @@
+/* Scheduler worker functions for OsmoBTS-TRX */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <sys/timerfd.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer_compat.h>
+#include <osmocom/codec/codec.h>
+#include <osmocom/codec/ecu.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/a5.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+#include "loops.h"
+
+extern void *tall_bts_ctx;
+
+/* Maximum size of a EGPRS message in bytes */
+#define EGPRS_0503_MAX_BYTES 155
+
+
+/* Compute the bit error rate in 1/10000 units */
+static inline uint16_t compute_ber10k(int n_bits_total, int n_errors)
+{
+ if (n_bits_total == 0)
+ return 10000;
+ else
+ return 10000 * n_errors / n_bits_total;
+}
+
+/*
+ * 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)
+{
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting IDLE\n");
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ return NULL;
+}
+
+/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */
+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)
+{
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting FCCH\n");
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ /* BURST BYPASS */
+
+ return (ubit_t *) _sched_fcch_burst;
+}
+
+/* obtain a to-be-transmitted SCH (synchronization channel) burst */
+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)
+{
+ static ubit_t bits[GSM_BURST_LEN], burst[78];
+ uint8_t sb_info[4];
+ struct gsm_time t;
+ uint8_t t3p, bsic;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting SCH\n");
+
+ /* BURST BYPASS */
+
+ /* create SB info from GSM time and BSIC */
+ gsm_fn2gsmtime(&t, fn);
+ t3p = t.t3 / 10;
+ bsic = l1t->trx->bts->bsic;
+ sb_info[0] =
+ ((bsic & 0x3f) << 2) |
+ ((t.t1 & 0x600) >> 9);
+ sb_info[1] =
+ ((t.t1 & 0x1fe) >> 1);
+ sb_info[2] =
+ ((t.t1 & 0x001) << 7) |
+ ((t.t2 & 0x1f) << 2) |
+ ((t3p & 0x6) >> 1);
+ sb_info[3] =
+ (t3p & 0x1);
+
+ /* encode bursts */
+ gsm0503_sch_encode(burst, sb_info);
+
+ /* compose burst */
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 39);
+ memcpy(bits + 42, _sched_sch_train, 64);
+ memcpy(bits + 106, burst + 39, 39);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ return bits;
+}
+
+/* obtain a to-be-transmitted data (SACCH/SDCCH) burst */
+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 l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+ uint8_t link_id = trx_chan_desc[chan].link_id;
+ uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn;
+ struct msgb *msg = NULL; /* make GCC happy */
+ ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
+ static ubit_t bits[GSM_BURST_LEN];
+
+ /* send burst, if we already got a frame */
+ if (bid > 0) {
+ if (!*bursts_p)
+ return NULL;
+ goto send_burst;
+ }
+
+ /* send clock information to loops process */
+ if (L1SAP_IS_LINK_SACCH(link_id))
+ trx_loop_sacch_clock(l1t, chan_nr, &l1ts->chan_state[chan]);
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (msg)
+ goto got_msg;
+
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
+
+no_msg:
+ /* free burst memory */
+ if (*bursts_p) {
+ talloc_free(*bursts_p);
+ *bursts_p = NULL;
+ }
+ return NULL;
+
+got_msg:
+ /* check validity of message */
+ if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! "
+ "(len=%d)\n", msgb_l2len(msg));
+ /* free message */
+ msgb_free(msg);
+ goto no_msg;
+ }
+
+ /* BURST BYPASS */
+
+ /* handle loss detection of SACCH */
+ if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) {
+ /* count and send BFI */
+ if (++(l1ts->chan_state[chan].lost_frames) > 1) {
+ /* TODO: Should we pass old TOA here? Otherwise we risk
+ * unnecessary decreasing TA */
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn,
+ 456, 456, -110, 0);
+ /* FIXME: use actual values for BER etc */
+ _sched_compose_ph_data_ind(l1t, tn, 0, chan, NULL, 0,
+ -110, 0, 0, 10000,
+ PRES_INFO_INVALID);
+ }
+ }
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 464);
+ if (!*bursts_p)
+ return NULL;
+ }
+
+ /* encode bursts */
+ gsm0503_xcch_encode(*bursts_p, msg->l2h);
+
+ /* free message */
+ msgb_free(msg);
+
+send_burst:
+ /* compose burst */
+ burst = *bursts_p + bid * 116;
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 58);
+ memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+ memcpy(bits + 87, burst + 58, 58);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+ return bits;
+}
+
+/* obtain a to-be-transmitted PDTCH (packet data) burst */
+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 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 */
+ ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
+ enum trx_burst_type *burst_type = &l1ts->chan_state[chan].dl_burst_type;
+ static ubit_t bits[EGPRS_BURST_LEN];
+ int rc = 0;
+
+ /* send burst, if we already got a frame */
+ if (bid > 0) {
+ if (!*bursts_p)
+ return NULL;
+ goto send_burst;
+ }
+
+ /* get mac block from queue */
+ msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+ if (msg)
+ goto got_msg;
+
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
+
+no_msg:
+ /* free burst memory */
+ if (*bursts_p) {
+ talloc_free(*bursts_p);
+ *bursts_p = NULL;
+ }
+ return NULL;
+
+got_msg:
+ /* BURST BYPASS */
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx,
+ GSM0503_EGPRS_BURSTS_NBITS);
+ if (!*bursts_p)
+ return NULL;
+ }
+
+ /* encode bursts */
+ rc = gsm0503_pdtch_egprs_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
+ if (rc < 0)
+ rc = gsm0503_pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
+
+ /* check validity of message */
+ if (rc < 0) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim invalid length, please FIX! "
+ "(len=%ld)\n", msg->tail - msg->l2h);
+ /* free message */
+ msgb_free(msg);
+ goto no_msg;
+ } else if (rc == GSM0503_EGPRS_BURSTS_NBITS) {
+ *burst_type = TRX_BURST_8PSK;
+ } else {
+ *burst_type = TRX_BURST_GMSK;
+ }
+
+ /* free message */
+ msgb_free(msg);
+
+send_burst:
+ /* compose burst */
+ if (*burst_type == TRX_BURST_8PSK) {
+ burst = *bursts_p + bid * 348;
+ memset(bits, 1, 9);
+ memcpy(bits + 9, burst, 174);
+ memcpy(bits + 183, _sched_egprs_tsc[gsm_ts_tsc(ts)], 78);
+ memcpy(bits + 261, burst + 174, 174);
+ memset(bits + 435, 1, 9);
+
+ if (nbits)
+ *nbits = EGPRS_BURST_LEN;
+ } else {
+ burst = *bursts_p + bid * 116;
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 58);
+ memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+ memcpy(bits + 87, burst + 58, 58);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+ }
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+ return bits;
+}
+
+/* determine if the FN is transmitting a CMR (1) or not (0) */
+static inline int fn_is_codec_mode_request(uint32_t fn)
+{
+ return (((fn + 4) % 26) >> 2) & 1;
+}
+
+/* common section for generation of TCH bursts (TCH/H and TCH/F) */
+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)
+{
+ 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;
+
+ /* handle loss detection of received TCH frames */
+ if (rsl_cmode == RSL_CMOD_SPD_SPEECH
+ && ++(chan_state->lost_frames) > 5) {
+ uint8_t tch_data[GSM_FR_BYTES];
+ int len;
+
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Missing TCH bursts detected, sending BFI\n");
+
+ /* 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 = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->dl_cmr],
+ chan_state->codec[chan_state->dl_ft], AMR_BAD);
+ if (len < 2)
+ break;
+ memset(tch_data + 2, 0, len - 2);
+ _sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len);
+ break;
+ default:
+inval_mode1:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
+ len = 0;
+ }
+ if (len)
+ _sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len);
+ }
+
+ /* 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) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+ "TCH twice, please FIX!\n");
+ msgb_free(msg2);
+ } else
+ msg_facch = msg2;
+ }
+ } else {
+ msg_facch = msg1;
+ if (msg2) {
+ l1sap = msgb_l1sap_prim(msg2);
+ if (l1sap->oph.primitive != PRIM_TCH) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+ "FACCH twice, please FIX!\n");
+ 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) {
+ LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "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 cmr_codec;
+ int cmr, ft, i;
+ enum osmo_amr_type ft_codec;
+ enum osmo_amr_quality bfi;
+ int8_t sti, cmi;
+
+ if (rsl_cmode != RSL_CMOD_SPD_SPEECH) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, "
+ "because we are not in speech mode\n");
+ goto free_bad_msg;
+ }
+
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+ if (chan != TRXC_TCHF) /* HR */
+ len = 15;
+ else
+ len = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ if (chan != TRXC_TCHF)
+ goto inval_mode2;
+ len = GSM_EFR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ len = osmo_amr_rtp_dec(msg_tch->l2h, msgb_l2len(msg_tch),
+ &cmr_codec, &cmi, &ft_codec,
+ &bfi, &sti);
+ 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) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+ "Codec (FT = %d) of RTP frame not in list\n", ft_codec);
+ goto free_bad_msg;
+ }
+ if (fn_is_codec_mode_request(fn) && chan_state->dl_ft != ft) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Codec (FT = %d) "
+ " of RTP cannot be changed now, but in next frame\n", ft_codec);
+ goto free_bad_msg;
+ }
+ chan_state->dl_ft = ft;
+ if (bfi == AMR_BAD) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Transmitting 'bad AMR frame'\n");
+ goto free_bad_msg;
+ }
+ break;
+ default:
+inval_mode2:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
+ goto free_bad_msg;
+ }
+ if (len < 0) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n");
+ goto free_bad_msg;
+ }
+ if (msgb_l2len(msg_tch) != len) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with "
+ "invalid length! (expecting %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;
+}
+
+/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */
+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;
+ 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;
+ ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
+ static ubit_t bits[GSM_BURST_LEN];
+
+ /* send burst, if we already got a frame */
+ if (bid > 0) {
+ if (!*bursts_p)
+ return NULL;
+ goto send_burst;
+ }
+
+ tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
+
+ /* BURST BYPASS */
+
+ /* allocate burst memory, if not already,
+ * otherwise shift buffer by 4 bursts for interleaving */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 928);
+ if (!*bursts_p)
+ return NULL;
+ } else {
+ memcpy(*bursts_p, *bursts_p + 464, 464);
+ memset(*bursts_p + 464, 0, 464);
+ }
+
+ /* no message at all */
+ if (!msg_tch && !msg_facch) {
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
+ goto send_burst;
+ }
+
+ /* encode bursts (prioritize FACCH) */
+ if (msg_facch)
+ gsm0503_tch_fr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch),
+ 1);
+ else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
+ /* the first FN 4,13,21 defines that CMI is included in frame,
+ * the first FN 0,8,17 defines that CMR is included in frame.
+ */
+ gsm0503_tch_afs_encode(*bursts_p, msg_tch->l2h + 2,
+ msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
+ chan_state->codec, chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ else
+ gsm0503_tch_fr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch), 1);
+
+ /* free message */
+ if (msg_tch)
+ msgb_free(msg_tch);
+ if (msg_facch)
+ msgb_free(msg_facch);
+
+send_burst:
+ /* compose burst */
+ burst = *bursts_p + bid * 116;
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 58);
+ memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+ memcpy(bits + 87, burst + 58, 58);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+ return bits;
+}
+
+/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */
+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 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;
+ ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
+ static ubit_t bits[GSM_BURST_LEN];
+
+ /* send burst, if we already got a frame */
+ if (bid > 0) {
+ if (!*bursts_p)
+ return NULL;
+ goto send_burst;
+ }
+
+ /* get TCH and/or FACCH */
+ tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
+
+ /* check for FACCH alignment */
+ if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) {
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on "
+ "even frames, please fix RTS!\n");
+ msgb_free(msg_facch);
+ msg_facch = NULL;
+ }
+
+ /* BURST BYPASS */
+
+ /* allocate burst memory, if not already,
+ * otherwise shift buffer by 2 bursts for interleaving */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 696);
+ if (!*bursts_p)
+ return NULL;
+ } else {
+ memcpy(*bursts_p, *bursts_p + 232, 232);
+ if (chan_state->dl_ongoing_facch) {
+ memcpy(*bursts_p + 232, *bursts_p + 464, 232);
+ memset(*bursts_p + 464, 0, 232);
+ } else {
+ memset(*bursts_p + 232, 0, 232);
+ }
+ }
+
+ /* no message at all */
+ if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) {
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
+ goto send_burst;
+ }
+
+ /* encode bursts (prioritize FACCH) */
+ if (msg_facch) {
+ gsm0503_tch_hr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch));
+ chan_state->dl_ongoing_facch = 1; /* first of two TCH frames */
+ } else if (chan_state->dl_ongoing_facch) /* second of two TCH frames */
+ chan_state->dl_ongoing_facch = 0; /* we are done with FACCH */
+ else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
+ /* the first FN 4,13,21 or 5,14,22 defines that CMI is included
+ * in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is
+ * included in frame. */
+ gsm0503_tch_ahs_encode(*bursts_p, msg_tch->l2h + 2,
+ msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
+ chan_state->codec, chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ else
+ gsm0503_tch_hr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch));
+
+ /* free message */
+ if (msg_tch)
+ msgb_free(msg_tch);
+ if (msg_facch)
+ msgb_free(msg_facch);
+
+send_burst:
+ /* compose burst */
+ burst = *bursts_p + bid * 116;
+ memset(bits, 0, 3);
+ memcpy(bits + 3, burst, 58);
+ memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+ memcpy(bits + 87, burst + 58, 58);
+ memset(bits + 145, 0, 3);
+
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+ return bits;
+}
+
+
+/*
+ * RX on uplink (indication to upper layer)
+ */
+
+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, int16_t toa256)
+{
+ uint8_t chan_nr;
+ struct osmo_phsap_prim l1sap;
+ int n_errors, n_bits_total;
+ uint8_t ra;
+ int rc;
+
+ chan_nr = trx_chan_desc[chan].chan_nr | tn;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received RACH toa=%d\n", toa256);
+
+ /* decode */
+ rc = gsm0503_rach_decode_ber(&ra, bits + 8 + 41, l1t->trx->bts->bsic, &n_errors, &n_bits_total);
+ if (rc) {
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received bad AB frame\n");
+ return 0;
+ }
+
+ /* compose primitive */
+ /* generate prim */
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION,
+ NULL);
+ l1sap.u.rach_ind.chan_nr = chan_nr;
+ l1sap.u.rach_ind.ra = ra;
+ l1sap.u.rach_ind.acc_delay = (toa256 >= 0) ? toa256/256 : 0;
+ l1sap.u.rach_ind.fn = fn;
+
+ /* 11bit RACH is not supported for osmo-trx */
+ l1sap.u.rach_ind.is_11bit = 0;
+ l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0;
+ l1sap.u.rach_ind.rssi = rssi;
+ l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors);
+ l1sap.u.rach_ind.acc_delay_256bits = toa256;
+
+ /* forward primitive */
+ l1sap_up(l1t->trx, &l1sap);
+
+ return 0;
+}
+
+/*! \brief a single (SDCCH/SACCH) 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, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+ uint32_t *first_fn = &chan_state->ul_first_fn;
+ uint8_t *mask = &chan_state->ul_mask;
+ float *rssi_sum = &chan_state->rssi_sum;
+ uint8_t *rssi_num = &chan_state->rssi_num;
+ int32_t *toa256_sum = &chan_state->toa256_sum;
+ uint8_t *toa_num = &chan_state->toa_num;
+ uint8_t l2[GSM_MACBLOCK_LEN], l2_len;
+ int n_errors, n_bits_total;
+ uint16_t ber10k;
+ int rc;
+
+ /* handle RACH, if handover RACH detection is turned on */
+ if (chan_state->ho_rach_detect == 1)
+ return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256);
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received Data, bid=%u\n", bid);
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 464);
+ if (!*bursts_p)
+ return -ENOMEM;
+ }
+
+ /* clear burst & store frame number of first burst */
+ if (bid == 0) {
+ memset(*bursts_p, 0, 464);
+ *mask = 0x0;
+ *first_fn = fn;
+ *rssi_sum = 0;
+ *rssi_num = 0;
+ *toa256_sum = 0;
+ *toa_num = 0;
+ }
+
+ /* update mask + RSSI */
+ *mask |= (1 << bid);
+ *rssi_sum += rssi;
+ (*rssi_num)++;
+ *toa256_sum += toa256;
+ (*toa_num)++;
+
+ /* copy burst to buffer of 4 bursts */
+ burst = *bursts_p + bid * 116;
+ memcpy(burst, bits + 3, 58);
+ memcpy(burst + 58, bits + 87, 58);
+
+ /* send burst information to loops process */
+ if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) {
+ trx_loop_sacch_input(l1t, trx_chan_desc[chan].chan_nr | tn,
+ chan_state, rssi, toa256);
+ }
+
+ /* wait until complete set of bursts */
+ if (bid != 3)
+ return 0;
+
+ /* check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete data (%u/%u)\n",
+ *first_fn, (*first_fn) % l1ts->mf_period);
+
+ /* we require first burst to have correct FN */
+ if (!(*mask & 0x1)) {
+ *mask = 0x0;
+ return 0;
+ }
+ }
+ *mask = 0x0;
+
+ /* decode */
+ rc = gsm0503_xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total);
+ if (rc) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n",
+ *first_fn, (*first_fn) % l1ts->mf_period);
+ l2_len = 0;
+ } else
+ l2_len = GSM_MACBLOCK_LEN;
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr | tn,
+ n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa256_sum / *toa_num);
+ ber10k = compute_ber10k(n_bits_total, n_errors);
+ return _sched_compose_ph_data_ind(l1t, tn, *first_fn, chan, l2, l2_len,
+ *rssi_sum / *rssi_num,
+ 4 * (*toa256_sum) / *toa_num, 0, ber10k,
+ PRES_INFO_UNKNOWN);
+}
+
+/*! \brief a single PDTCH burst was received by the PHY, process it */
+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, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+ uint32_t *first_fn = &chan_state->ul_first_fn;
+ uint8_t *mask = &chan_state->ul_mask;
+ float *rssi_sum = &chan_state->rssi_sum;
+ uint8_t *rssi_num = &chan_state->rssi_num;
+ int32_t *toa256_sum = &chan_state->toa256_sum;
+ uint8_t *toa_num = &chan_state->toa_num;
+ uint8_t l2[EGPRS_0503_MAX_BYTES];
+ int n_errors, n_bursts_bits, n_bits_total;
+ uint16_t ber10k;
+ int rc;
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received PDTCH bid=%u\n", bid);
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx,
+ GSM0503_EGPRS_BURSTS_NBITS);
+ if (!*bursts_p)
+ return -ENOMEM;
+ }
+
+ /* clear burst */
+ if (bid == 0) {
+ memset(*bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS);
+ *mask = 0x0;
+ *first_fn = fn;
+ *rssi_sum = 0;
+ *rssi_num = 0;
+ *toa256_sum = 0;
+ *toa_num = 0;
+ }
+
+ /* update mask + rssi */
+ *mask |= (1 << bid);
+ *rssi_sum += rssi;
+ (*rssi_num)++;
+ *toa256_sum += toa256;
+ (*toa_num)++;
+
+ /* copy burst to buffer of 4 bursts */
+ if (nbits == EGPRS_BURST_LEN) {
+ burst = *bursts_p + bid * 348;
+ memcpy(burst, bits + 9, 174);
+ memcpy(burst + 174, bits + 261, 174);
+ n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS;
+ } else {
+ burst = *bursts_p + bid * 116;
+ memcpy(burst, bits + 3, 58);
+ memcpy(burst + 58, bits + 87, 58);
+ n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS;
+ }
+
+ /* wait until complete set of bursts */
+ if (bid != 3)
+ return 0;
+
+ /* check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ }
+ *mask = 0x0;
+
+ /*
+ * Attempt to decode EGPRS bursts first. For 8-PSK EGPRS this is all we
+ * do. Attempt GPRS decoding on EGPRS failure. If the burst is GPRS,
+ * then we incur decoding overhead of 31 bits on the Type 3 EGPRS
+ * header, which is tolerable.
+ */
+ rc = gsm0503_pdtch_egprs_decode(l2, *bursts_p, n_bursts_bits,
+ NULL, &n_errors, &n_bits_total);
+
+ if ((nbits == GSM_BURST_LEN) && (rc < 0)) {
+ rc = gsm0503_pdtch_decode(l2, *bursts_p, NULL,
+ &n_errors, &n_bits_total);
+ }
+
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr | tn,
+ n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa256_sum / *toa_num);
+
+ if (rc <= 0) {
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received bad PDTCH (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ return 0;
+ }
+ ber10k = compute_ber10k(n_bits_total, n_errors);
+ return _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 3) % GSM_HYPERFRAME, chan,
+ l2, rc, *rssi_sum / *rssi_num, 4 * (*toa256_sum) / *toa_num, 0,
+ ber10k, PRES_INFO_BOTH);
+}
+
+/*! \brief a single TCH/F burst was received by the PHY, process it */
+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, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+ uint32_t *first_fn = &chan_state->ul_first_fn;
+ uint8_t *mask = &chan_state->ul_mask;
+ uint8_t rsl_cmode = chan_state->rsl_cmode;
+ uint8_t tch_mode = chan_state->tch_mode;
+ uint8_t tch_data[128]; /* just to be safe */
+ int rc, amr = 0;
+ int n_errors, n_bits_total;
+ bool bfi_flag = false;
+ struct gsm_lchan *lchan =
+ get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | tn);
+
+ /* handle rach, if handover rach detection is turned on */
+ if (chan_state->ho_rach_detect == 1)
+ return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256);
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received TCH/F, bid=%u\n", bid);
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 928);
+ if (!*bursts_p)
+ return -ENOMEM;
+ }
+
+ /* clear burst */
+ if (bid == 0) {
+ memset(*bursts_p + 464, 0, 464);
+ *mask = 0x0;
+ *first_fn = fn;
+ }
+
+ /* update mask */
+ *mask |= (1 << bid);
+
+ /* copy burst to end of buffer of 8 bursts */
+ burst = *bursts_p + bid * 116 + 464;
+ memcpy(burst, bits + 3, 58);
+ memcpy(burst + 58, bits + 87, 58);
+
+ /* wait until complete set of bursts */
+ if (bid != 3)
+ return 0;
+
+ /* check for complete set of bursts */
+ if ((*mask & 0xf) != 0xf) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ }
+ *mask = 0x0;
+
+ /* decode
+ * also shift buffer by 4 bursts for interleaving */
+ switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
+ : tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR */
+ rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total);
+ if (rc >= 0)
+ lchan_set_marker(osmo_fr_check_sid(tch_data, rc), lchan); /* DTXu */
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 1, &n_errors, &n_bits_total);
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ /* the first FN 0,8,17 defines that CMI is included in frame,
+ * the first FN 4,13,21 defines that CMR is included in frame.
+ * NOTE: A frame ends 7 FN after start.
+ */
+ rc = gsm0503_tch_afs_decode(tch_data + 2, *bursts_p,
+ (((fn + 26 - 7) % 26) >> 2) & 1, chan_state->codec,
+ chan_state->codecs, &chan_state->ul_ft,
+ &chan_state->ul_cmr, &n_errors, &n_bits_total);
+ if (rc)
+ trx_loop_amr_input(l1t,
+ trx_chan_desc[chan].chan_nr | tn, chan_state,
+ (float)n_errors/(float)n_bits_total);
+ amr = 2; /* we store tch_data + 2 header bytes */
+ /* only good speech frames get rtp header */
+ if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->ul_cmr],
+ chan_state->codec[chan_state->ul_ft], AMR_GOOD);
+ }
+ break;
+ default:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode %u invalid, please fix!\n",
+ tch_mode);
+ return -EINVAL;
+ }
+ memcpy(*bursts_p, *bursts_p + 464, 464);
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr|tn,
+ n_errors, n_bits_total, rssi, toa256);
+
+ /* Check if the frame is bad */
+ if (rc < 0) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ bfi_flag = true;
+ goto bfi;
+ }
+ if (rc < 4) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u) "
+ "with invalid codec mode %d\n", fn % l1ts->mf_period, l1ts->mf_period, rc);
+ bfi_flag = true;
+ goto bfi;
+ }
+
+ /* FACCH */
+ if (rc == GSM_MACBLOCK_LEN) {
+ uint16_t ber10k = compute_ber10k(n_bits_total, n_errors);
+ _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan,
+ tch_data + amr, GSM_MACBLOCK_LEN, rssi, 4 * toa256, 0,
+ ber10k, PRES_INFO_UNKNOWN);
+bfi:
+ if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
+ /* indicate bad frame */
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR */
+ if (lchan->tch.dtx.ul_sid) {
+ /* DTXu: pause in progress. Push empty payload to upper layers */
+ rc = 0;
+ goto compose_l1sap;
+ }
+
+ /* Perform error concealment if possible */
+ rc = osmo_ecu_fr_conceal(&lchan->ecu_state.fr, tch_data);
+ if (rc) {
+ memset(tch_data, 0, GSM_FR_BYTES);
+ tch_data[0] = 0xd0;
+ }
+
+ rc = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ memset(tch_data, 0, GSM_EFR_BYTES);
+ tch_data[0] = 0xc0;
+ rc = GSM_EFR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->dl_cmr],
+ chan_state->codec[chan_state->dl_ft],
+ AMR_BAD);
+ if (rc < 2)
+ break;
+ memset(tch_data + 2, 0, rc - 2);
+ break;
+ default:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+ "TCH mode %u invalid, please fix!\n", tch_mode);
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
+ return 0;
+
+ /* Reset ECU with a good frame */
+ if (!bfi_flag && tch_mode == GSM48_CMODE_SPEECH_V1)
+ osmo_ecu_fr_reset(&lchan->ecu_state.fr, tch_data);
+
+ /* TCH or BFI */
+compose_l1sap:
+ return _sched_compose_tch_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan,
+ tch_data, rc);
+}
+
+/*! \brief a single TCH/H burst was received by the PHY, process it */
+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, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+ sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+ uint32_t *first_fn = &chan_state->ul_first_fn;
+ uint8_t *mask = &chan_state->ul_mask;
+ uint8_t rsl_cmode = chan_state->rsl_cmode;
+ uint8_t tch_mode = chan_state->tch_mode;
+ uint8_t tch_data[128]; /* just to be safe */
+ int rc, amr = 0;
+ int n_errors, n_bits_total;
+ struct gsm_lchan *lchan =
+ get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | tn);
+ /* Note on FN-10: If we are at FN 10, we decoded an even aligned
+ * TCH/FACCH frame, because our burst buffer carries 6 bursts.
+ * Even FN ending at: 10,11,19,20,2,3
+ */
+ int fn_is_odd = (((fn + 26 - 10) % 26) >> 2) & 1;
+
+ /* handle RACH, if handover RACH detection is turned on */
+ if (chan_state->ho_rach_detect == 1)
+ return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256);
+
+ LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received TCH/H, bid=%u\n", bid);
+
+ /* allocate burst memory, if not already */
+ if (!*bursts_p) {
+ *bursts_p = talloc_zero_size(tall_bts_ctx, 696);
+ if (!*bursts_p)
+ return -ENOMEM;
+ }
+
+ /* clear burst */
+ if (bid == 0) {
+ memset(*bursts_p + 464, 0, 232);
+ *mask = 0x0;
+ *first_fn = fn;
+ }
+
+ /* update mask */
+ *mask |= (1 << bid);
+
+ /* copy burst to end of buffer of 6 bursts */
+ burst = *bursts_p + bid * 116 + 464;
+ memcpy(burst, bits + 3, 58);
+ memcpy(burst + 58, bits + 87, 58);
+
+ /* wait until complete set of bursts */
+ if (bid != 1)
+ return 0;
+
+ /* check for complete set of bursts */
+ if ((*mask & 0x3) != 0x3) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ }
+ *mask = 0x0;
+
+ /* skip second of two TCH frames of FACCH was received */
+ if (chan_state->ul_ongoing_facch) {
+ chan_state->ul_ongoing_facch = 0;
+ memcpy(*bursts_p, *bursts_p + 232, 232);
+ memcpy(*bursts_p + 232, *bursts_p + 464, 232);
+ goto bfi;
+ }
+
+ /* decode
+ * also shift buffer by 4 bursts for interleaving */
+ switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
+ : tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* HR or signalling */
+ /* Note on FN-10: If we are at FN 10, we decoded an even aligned
+ * TCH/FACCH frame, because our burst buffer carries 6 bursts.
+ * Even FN ending at: 10,11,19,20,2,3
+ */
+ rc = gsm0503_tch_hr_decode(tch_data, *bursts_p,
+ fn_is_odd, &n_errors, &n_bits_total);
+ if (rc) /* DTXu */
+ lchan_set_marker(osmo_hr_check_sid(tch_data, rc), lchan);
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ /* the first FN 0,8,17 or 1,9,18 defines that CMI is included
+ * in frame, the first FN 4,13,21 or 5,14,22 defines that CMR
+ * is included in frame.
+ */
+ rc = gsm0503_tch_ahs_decode(tch_data + 2, *bursts_p,
+ fn_is_odd, fn_is_odd, chan_state->codec,
+ chan_state->codecs, &chan_state->ul_ft,
+ &chan_state->ul_cmr, &n_errors, &n_bits_total);
+ if (rc)
+ trx_loop_amr_input(l1t,
+ trx_chan_desc[chan].chan_nr | tn, chan_state,
+ (float)n_errors/(float)n_bits_total);
+ amr = 2; /* we store tch_data + 2 two */
+ /* only good speech frames get rtp header */
+ if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->ul_cmr],
+ chan_state->codec[chan_state->ul_ft], AMR_GOOD);
+ }
+ break;
+ default:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode %u invalid, please fix!\n",
+ tch_mode);
+ return -EINVAL;
+ }
+ memcpy(*bursts_p, *bursts_p + 232, 232);
+ memcpy(*bursts_p + 232, *bursts_p + 464, 232);
+
+ /* Send uplink measurement information to L2 */
+ l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr|tn,
+ n_errors, n_bits_total, rssi, toa256);
+
+ /* Check if the frame is bad */
+ if (rc < 0) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n",
+ fn % l1ts->mf_period, l1ts->mf_period);
+ goto bfi;
+ }
+ if (rc < 4) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u) "
+ "with invalid codec mode %d\n", fn % l1ts->mf_period, l1ts->mf_period, rc);
+ goto bfi;
+ }
+
+ /* FACCH */
+ if (rc == GSM_MACBLOCK_LEN) {
+ chan_state->ul_ongoing_facch = 1;
+ uint16_t ber10k = compute_ber10k(n_bits_total, n_errors);
+ _sched_compose_ph_data_ind(l1t, tn,
+ (fn + GSM_HYPERFRAME - 10 - ((fn % 26) >= 19)) % GSM_HYPERFRAME, chan,
+ tch_data + amr, GSM_MACBLOCK_LEN, rssi, toa256/64, 0,
+ ber10k, PRES_INFO_UNKNOWN);
+bfi:
+ if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
+ /* indicate bad frame */
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* HR */
+ if (lchan->tch.dtx.ul_sid) {
+ /* DTXu: pause in progress. Push empty payload to upper layers */
+ rc = 0;
+ goto compose_l1sap;
+ }
+ tch_data[0] = 0x70; /* F = 0, FT = 111 */
+ memset(tch_data + 1, 0, 14);
+ rc = 15;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ rc = osmo_amr_rtp_enc(tch_data,
+ chan_state->codec[chan_state->dl_cmr],
+ chan_state->codec[chan_state->dl_ft],
+ AMR_BAD);
+ if (rc < 2)
+ break;
+ memset(tch_data + 2, 0, rc - 2);
+ break;
+ default:
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+ "TCH mode %u invalid, please fix!\n", tch_mode);
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
+ return 0;
+
+compose_l1sap:
+ /* TCH or BFI */
+ /* Note on FN 19 or 20: If we received the last burst of a frame,
+ * it actually starts at FN 8 or 9. A burst starting there, overlaps
+ * with the slot 12, so an extra FN must be subtracted to get correct
+ * start of frame.
+ */
+ return _sched_compose_tch_ind(l1t, tn,
+ (fn + GSM_HYPERFRAME - 10 - ((fn%26)==19) - ((fn%26)==20)) % GSM_HYPERFRAME,
+ chan, tch_data, rc);
+}
+
+/* schedule all frames of all TRX for given FN */
+static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn)
+{
+ struct gsm_bts_trx *trx;
+ uint8_t tn;
+ const ubit_t *bits;
+ uint8_t gain;
+ uint16_t nbits = 0;
+
+ /* send time indication */
+ l1if_mph_time_ind(bts, fn);
+
+ /* process every TRX */
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ struct phy_link *plink = pinst->phy_link;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ struct l1sched_trx *l1t = &l1h->l1s;
+
+ /* advance frame number, so the transceiver has more
+ * time until it must be transmitted. */
+ fn = (fn + plink->u.osmotrx.clock_advance) % GSM_HYPERFRAME;
+
+ /* we don't schedule, if power is off */
+ if (!trx_if_powered(l1h))
+ continue;
+
+ /* process every TS of TRX */
+ for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) {
+ /* ready-to-send */
+ _sched_rts(l1t, tn,
+ (fn + plink->u.osmotrx.rts_advance) % GSM_HYPERFRAME);
+ /* get burst for FN */
+ bits = _sched_dl_burst(l1t, tn, fn, &nbits);
+ if (!bits) {
+ /* if no bits, send no burst */
+ continue;
+ } else
+ gain = 0;
+ if (nbits)
+ trx_if_send_burst(l1h, tn, fn, gain, bits, nbits);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * TRX frame clock handling
+ *
+ * In a "normal" synchronous PHY layer, we would be polled every time
+ * the PHY needs data for a given frame number. However, the
+ * OpenBTS-inherited TRX protocol works differently: We (L1) must
+ * autonomously send burst data based on our own clock, and every so
+ * often (currently every ~ 216 frames), we get a clock indication from
+ * the TRX.
+ *
+ * We're using a MONOTONIC timerfd interval timer for the 4.615ms frame
+ * intervals, and then compute + send the 8 bursts for that frame.
+ *
+ * Upon receiving a clock indication from the TRX, we compensate
+ * accordingly: If we were transmitting too fast, we're delaying the
+ * next interval timer accordingly. If we were too slow, we immediately
+ * send burst data for the missing frame numbers.
+ */
+
+/*! clock state of a given TRX */
+struct osmo_trx_clock_state {
+ /*! number of FN periods without TRX clock indication */
+ uint32_t fn_without_clock_ind;
+ struct {
+ /*! last FN we processed based on FN period timer */
+ uint32_t fn;
+ /*! time at which we last processed FN */
+ struct timespec tv;
+ } last_fn_timer;
+ struct {
+ /*! last FN we received a clock indication for */
+ uint32_t fn;
+ /*! time at which we received the last clock indication */
+ struct timespec tv;
+ } last_clk_ind;
+ /*! Osmocom FD wrapper for timerfd */
+ struct osmo_fd fn_timer_ofd;
+};
+
+/* TODO: This must go and become part of the phy_link */
+static struct osmo_trx_clock_state g_clk_s = { .fn_timer_ofd.fd = -1 };
+
+/*! duration of a GSM frame in nano-seconds. (120ms/26) */
+#define FRAME_DURATION_nS 4615384
+/*! duration of a GSM frame in micro-seconds (120s/26) */
+#define FRAME_DURATION_uS (FRAME_DURATION_nS/1000)
+/*! maximum number of 'missed' frame periods we can tolerate of OS doesn't schedule us*/
+#define MAX_FN_SKEW 50
+/*! maximum number of frame periods we can tolerate without TRX Clock Indication*/
+#define TRX_LOSS_FRAMES 400
+
+/*! compute the number of micro-seconds difference elapsed between \a last and \a now */
+static inline int64_t compute_elapsed_us(const struct timespec *last, const struct timespec *now)
+{
+ struct timespec elapsed;
+
+ timespecsub(now, last, &elapsed);
+ return (int64_t)(elapsed.tv_sec * 1000000) + (elapsed.tv_nsec / 1000);
+}
+
+/*! compute the number of frame number intervals elapsed between \a last and \a now */
+static inline int compute_elapsed_fn(const uint32_t last, const uint32_t now)
+{
+ int elapsed_fn = (now + GSM_HYPERFRAME - last) % GSM_HYPERFRAME;
+ if (elapsed_fn >= 135774)
+ elapsed_fn -= GSM_HYPERFRAME;
+ return elapsed_fn;
+}
+
+/*! normalise given 'struct timespec', i.e. carry nanoseconds into seconds */
+static inline void normalize_timespec(struct timespec *ts)
+{
+ ts->tv_sec += ts->tv_nsec / 1000000000;
+ ts->tv_nsec = ts->tv_nsec % 1000000000;
+}
+
+/*! Increment a GSM frame number modulo GSM_HYPERFRAME */
+#define INCREMENT_FN(fn) (fn) = (((fn) + 1) % GSM_HYPERFRAME)
+
+extern int quit;
+
+/*! this is the timerfd-callback firing for every FN to be processed */
+static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct gsm_bts *bts = ofd->data;
+ struct osmo_trx_clock_state *tcs = &g_clk_s;
+ struct timespec tv_now;
+ uint64_t expire_count;
+ int64_t elapsed_us, error_us;
+ int rc, i;
+
+ if (!(what & BSC_FD_READ))
+ return 0;
+
+ /* read from timerfd: number of expirations of periodic timer */
+ rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count));
+ if (rc < 0 && errno == EAGAIN)
+ return 0;
+ OSMO_ASSERT(rc == sizeof(expire_count));
+
+ if (expire_count > 1) {
+ LOGP(DL1C, LOGL_NOTICE, "FN timer expire_count=%"PRIu64": We missed %"PRIu64" timers\n",
+ expire_count, expire_count-1);
+ }
+
+ /* check if transceiver is still alive */
+ if (tcs->fn_without_clock_ind++ == TRX_LOSS_FRAMES) {
+ LOGP(DL1C, LOGL_NOTICE, "No more clock from transceiver\n");
+ goto no_clock;
+ }
+
+ /* compute actual elapsed time and resulting OS scheduling error */
+ clock_gettime(CLOCK_MONOTONIC, &tv_now);
+ elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now);
+ error_us = elapsed_us - FRAME_DURATION_uS;
+#ifdef DEBUG_CLOCK
+ printf("%s(): %09ld, elapsed_us=%05" PRId64 ", error_us=%-d: fn=%d\n", __func__,
+ tv_now.tv_nsec, elapsed_us, error_us, tcs->last_fn_timer.fn+1);
+#endif
+ tcs->last_fn_timer.tv = tv_now;
+
+ /* if someone played with clock, or if the process stalled */
+ if (elapsed_us > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) {
+ LOGP(DL1C, LOGL_ERROR, "PC clock skew: elapsed_us=%" PRId64 ", error_us=%" PRId64 "\n",
+ elapsed_us, error_us);
+ goto no_clock;
+ }
+
+ /* call trx_sched_fn() for all expired FN */
+ for (i = 0; i < expire_count; i++) {
+ INCREMENT_FN(tcs->last_fn_timer.fn);
+ trx_sched_fn(bts, tcs->last_fn_timer.fn);
+ }
+
+ return 0;
+
+no_clock:
+ osmo_timerfd_disable(&tcs->fn_timer_ofd);
+ transceiver_available = 0;
+
+ bts_shutdown(bts, "No clock from osmo-trx");
+
+ return -1;
+}
+
+/*! reset clock with current fn and schedule it. Called when trx becomes
+ * available or when max clock skew is reached */
+static int trx_setup_clock(struct gsm_bts *bts, struct osmo_trx_clock_state *tcs,
+ struct timespec *tv_now, const struct timespec *interval, uint32_t fn)
+{
+ tcs->last_fn_timer.fn = fn;
+ /* call trx cheduler function for new 'last' FN */
+ trx_sched_fn(bts, tcs->last_fn_timer.fn);
+
+ /* schedule first FN clock timer */
+ osmo_timerfd_setup(&tcs->fn_timer_ofd, trx_fn_timer_cb, bts);
+ osmo_timerfd_schedule(&tcs->fn_timer_ofd, NULL, interval);
+
+ tcs->last_fn_timer.tv = *tv_now;
+ tcs->last_clk_ind.tv = *tv_now;
+ tcs->last_clk_ind.fn = fn;
+
+ return 0;
+}
+
+/*! called every time we receive a clock indication from TRX */
+int trx_sched_clock(struct gsm_bts *bts, uint32_t fn)
+{
+ struct osmo_trx_clock_state *tcs = &g_clk_s;
+ struct timespec tv_now;
+ int elapsed_fn;
+ int64_t elapsed_us, elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk;
+ unsigned int fn_caught_up = 0;
+ const struct timespec interval = { .tv_sec = 0, .tv_nsec = FRAME_DURATION_nS };
+
+ if (quit)
+ return 0;
+
+ /* reset lost counter */
+ tcs->fn_without_clock_ind = 0;
+
+ clock_gettime(CLOCK_MONOTONIC, &tv_now);
+
+ /* clock becomes valid */
+ if (!transceiver_available) {
+ LOGP(DL1C, LOGL_NOTICE, "initial GSM clock received: fn=%u\n", fn);
+
+ transceiver_available = 1;
+
+ /* start provisioning transceiver */
+ l1if_provision_transceiver(bts);
+
+ /* tell BSC */
+ check_transceiver_availability(bts, 1);
+
+ return trx_setup_clock(bts, tcs, &tv_now, &interval, fn);
+ }
+
+ /* calculate elapsed time +fn since last timer */
+ elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now);
+ elapsed_fn = compute_elapsed_fn(tcs->last_fn_timer.fn, fn);
+#ifdef DEBUG_CLOCK
+ printf("%s(): LAST_TIMER %9ld, elapsed_us=%7d, elapsed_fn=%+3d\n", __func__,
+ tv_now.tv_nsec, elapsed_us, elapsed_fn);
+#endif
+ /* negative elapsed_fn values mean that we've already processed
+ * more FN based on the local interval timer than what the TRX
+ * now reports in the clock indication. Positive elapsed_fn
+ * values mean we still have a backlog to process */
+
+ /* calculate elapsed time +fn since last clk ind */
+ elapsed_us_since_clk = compute_elapsed_us(&tcs->last_clk_ind.tv, &tv_now);
+ elapsed_fn_since_clk = compute_elapsed_fn(tcs->last_clk_ind.fn, fn);
+ /* error (delta) between local clock since last CLK and CLK based on FN clock at TRX */
+ error_us_since_clk = elapsed_us_since_clk - (FRAME_DURATION_uS * elapsed_fn_since_clk);
+ LOGP(DL1C, LOGL_INFO, "TRX Clock Ind: elapsed_us=%7"PRId64", "
+ "elapsed_fn=%3"PRId64", error_us=%+5"PRId64"\n",
+ elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk);
+
+ /* TODO: put this computed error_us_since_clk into some filter
+ * function and use that to adjust our regular timer interval to
+ * compensate for clock drift between the PC clock and the
+ * TRX/SDR clock */
+
+ tcs->last_clk_ind.tv = tv_now;
+ tcs->last_clk_ind.fn = fn;
+
+ /* check for max clock skew */
+ if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) {
+ LOGP(DL1C, LOGL_NOTICE, "GSM clock skew: old fn=%u, "
+ "new fn=%u\n", tcs->last_fn_timer.fn, fn);
+ return trx_setup_clock(bts, tcs, &tv_now, &interval, fn);
+ }
+
+ LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %" PRId64 "us (elapsed_fn=%d)\n",
+ elapsed_fn * FRAME_DURATION_uS - elapsed_us, elapsed_fn);
+
+ /* too many frames have been processed already */
+ if (elapsed_fn < 0) {
+ struct timespec first = interval;
+ /* set clock to the time or last FN should have been
+ * transmitted. */
+ first.tv_nsec += (0 - elapsed_fn) * FRAME_DURATION_nS;
+ normalize_timespec(&first);
+ LOGP(DL1C, LOGL_NOTICE, "We were %d FN faster than TRX, compensating\n", -elapsed_fn);
+ /* set time to the time our next FN has to be transmitted */
+ osmo_timerfd_schedule(&tcs->fn_timer_ofd, &first, &interval);
+ return 0;
+ }
+
+ /* transmit what we still need to transmit */
+ while (fn != tcs->last_fn_timer.fn) {
+ INCREMENT_FN(tcs->last_fn_timer.fn);
+ trx_sched_fn(bts, tcs->last_fn_timer.fn);
+ fn_caught_up++;
+ }
+
+ if (fn_caught_up) {
+ LOGP(DL1C, LOGL_NOTICE, "We were %d FN slower than TRX, compensated\n", elapsed_fn);
+ tcs->last_fn_timer.tv = tv_now;
+ }
+
+ return 0;
+}
+
+void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate)
+{
+ struct phy_instance *pinst = trx_phy_instance(l1t->trx);
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ if (activate)
+ trx_if_cmd_handover(l1h, tn, ss);
+ else
+ trx_if_cmd_nohandover(l1h, tn, ss);
+}
diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c
new file mode 100644
index 00000000..abe6846d
--- /dev/null
+++ b/src/osmo-bts-trx/trx_if.c
@@ -0,0 +1,838 @@
+/*
+ * OpenBTS-style TRX interface/protocol handling
+ *
+ * This file contains the BTS-side implementation of the OpenBTS-style
+ * UDP TRX protocol. It manages the clock, control + burst-data UDP
+ * sockets and their respective protocol encoding/parsing.
+ *
+ * Copyright (C) 2013 Andreas Eversberg <jolly@eversberg.eu>
+ * Copyright (C) 2016-2017 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/scheduler.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+
+/* enable to print RSSI level graph */
+//#define TOA_RSSI_DEBUG
+
+int transceiver_available = 0;
+
+#define TRX_MAX_BURST_LEN 512
+
+/*
+ * socket helper functions
+ */
+
+/*! convenience wrapper to open socket + fill in osmo_fd */
+static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
+ uint16_t port_local, const char *host_remote, uint16_t port_remote,
+ int (*cb)(struct osmo_fd *fd, unsigned int what))
+{
+ int rc;
+
+ /* Init */
+ ofd->fd = -1;
+ ofd->cb = cb;
+ ofd->data = priv;
+
+ /* Listen / Binds + Connect */
+ rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, host_local, port_local,
+ host_remote, port_remote, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+/* close socket + unregister osmo_fd */
+static void trx_udp_close(struct osmo_fd *ofd)
+{
+ if (ofd->fd >= 0) {
+ osmo_fd_unregister(ofd);
+ close(ofd->fd);
+ ofd->fd = -1;
+ }
+}
+
+
+/*
+ * TRX clock socket
+ */
+
+/* get clock from clock socket */
+static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct phy_link *plink = ofd->data;
+ struct phy_instance *pinst = phy_instance_by_num(plink, 0);
+ char buf[1500];
+ int len;
+ uint32_t fn;
+
+ OSMO_ASSERT(pinst);
+
+ len = recv(ofd->fd, buf, sizeof(buf) - 1, 0);
+ if (len <= 0)
+ return len;
+ buf[len] = '\0';
+
+ if (!!strncmp(buf, "IND CLOCK ", 10)) {
+ LOGP(DTRX, LOGL_NOTICE, "Unknown message on clock port: %s\n",
+ buf);
+ return 0;
+ }
+
+ if (sscanf(buf, "IND CLOCK %u", &fn) != 1) {
+ LOGP(DTRX, LOGL_ERROR, "Unable to parse '%s'\n", buf);
+ return 0;
+ }
+
+ LOGP(DTRX, LOGL_INFO, "Clock indication: fn=%u\n", fn);
+
+ if (fn >= GSM_HYPERFRAME) {
+ fn %= GSM_HYPERFRAME;
+ LOGP(DTRX, LOGL_ERROR, "Indicated clock's FN is not wrapping "
+ "correctly, correcting to fn=%u\n", fn);
+ }
+
+ /* inform core TRX clock handling code that a FN has been received */
+ trx_sched_clock(pinst->trx->bts, fn);
+
+ return 0;
+}
+
+
+/*
+ * TRX ctrl socket
+ */
+
+/* send first ctrl message and start timer */
+static void trx_ctrl_send(struct trx_l1h *l1h)
+{
+ struct trx_ctrl_msg *tcm;
+ char buf[1500];
+ int len;
+
+ /* get first command */
+ if (llist_empty(&l1h->trx_ctrl_list))
+ return;
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+
+ len = snprintf(buf, sizeof(buf), "CMD %s%s%s", tcm->cmd, tcm->params_len ? " ":"", tcm->params);
+ OSMO_ASSERT(len < sizeof(buf));
+
+ LOGP(DTRX, LOGL_DEBUG, "Sending control '%s' to %s\n", buf, phy_instance_name(l1h->phy_inst));
+ /* send command */
+ send(l1h->trx_ofd_ctrl.fd, buf, len+1, 0);
+
+ /* start timer */
+ osmo_timer_schedule(&l1h->trx_ctrl_timer, 2, 0);
+}
+
+/* send first ctrl message and start timer */
+static void trx_ctrl_timer_cb(void *data)
+{
+ struct trx_l1h *l1h = data;
+ struct trx_ctrl_msg *tcm = NULL;
+
+ /* get first command */
+ OSMO_ASSERT(!llist_empty(&l1h->trx_ctrl_list));
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+
+ LOGP(DTRX, LOGL_NOTICE, "No satisfactory response from transceiver for %s (CMD %s%s%s)\n",
+ phy_instance_name(l1h->phy_inst),
+ tcm->cmd, tcm->params_len ? " ":"", tcm->params);
+
+ trx_ctrl_send(l1h);
+}
+
+void trx_if_init(struct trx_l1h *l1h)
+{
+ l1h->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
+ l1h->trx_ctrl_timer.data = l1h;
+}
+
+/*! Send a new TRX control command.
+ * \param[inout] l1h TRX Layer1 handle to which to send command
+ * \param[in] criticial
+ * \param[in] cmd zero-terminated string containing command
+ * \param[in] fmt Format string (+ variable list of arguments)
+ * \returns 0 on success; negative on error
+ *
+ * The new ocommand will be added to the end of the control command
+ * queue.
+ */
+static int trx_ctrl_cmd(struct trx_l1h *l1h, int critical, const char *cmd,
+ const char *fmt, ...)
+{
+ struct trx_ctrl_msg *tcm;
+ struct trx_ctrl_msg *prev = NULL;
+ va_list ap;
+ int pending;
+
+ if (!transceiver_available &&
+ !(!strcmp(cmd, "POWEROFF") || !strcmp(cmd, "POWERON"))) {
+ LOGP(DTRX, LOGL_ERROR, "CTRL %s ignored: No clock from "
+ "transceiver, please fix!\n", cmd);
+ return -EIO;
+ }
+
+ pending = !llist_empty(&l1h->trx_ctrl_list);
+
+ /* create message */
+ tcm = talloc_zero(tall_bts_ctx, struct trx_ctrl_msg);
+ if (!tcm)
+ return -ENOMEM;
+ snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "%s", cmd);
+ tcm->cmd[sizeof(tcm->cmd)-1] = '\0';
+ tcm->cmd_len = strlen(tcm->cmd);
+ if (fmt && fmt[0]) {
+ va_start(ap, fmt);
+ vsnprintf(tcm->params, sizeof(tcm->params) - 1, fmt, ap);
+ va_end(ap);
+ tcm->params[sizeof(tcm->params)-1] = '\0';
+ tcm->params_len = strlen(tcm->params);
+ } else {
+ tcm->params[0] ='\0';
+ tcm->params_len = 0;
+ }
+ tcm->critical = critical;
+
+ /* Avoid adding consecutive duplicate messages, eg: two consecutive POWEROFF */
+ if(pending)
+ prev = llist_entry(l1h->trx_ctrl_list.prev, struct trx_ctrl_msg, list);
+
+ if (!pending ||
+ !(strcmp(tcm->cmd, prev->cmd) == 0 && strcmp(tcm->params, prev->params) == 0)) {
+ LOGP(DTRX, LOGL_INFO, "Enqueuing TRX control command 'CMD %s%s%s'\n",
+ tcm->cmd, tcm->params_len ? " ":"", tcm->params);
+ llist_add_tail(&tcm->list, &l1h->trx_ctrl_list);
+ }
+
+ /* send message, if we didn't already have pending messages */
+ if (!pending)
+ trx_ctrl_send(l1h);
+
+ return 0;
+}
+
+/*! Send "POWEROFF" command to TRX */
+int trx_if_cmd_poweroff(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ if (pinst->num == 0)
+ return trx_ctrl_cmd(l1h, 1, "POWEROFF", "");
+ else
+ return 0;
+}
+
+/*! Send "POWERON" command to TRX */
+int trx_if_cmd_poweron(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ if (pinst->num == 0)
+ return trx_ctrl_cmd(l1h, 1, "POWERON", "");
+ else
+ return 0;
+}
+
+/*! Send "SETTSC" command to TRX */
+int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ if (pinst->phy_link->u.osmotrx.use_legacy_setbsic)
+ return 0;
+
+ return trx_ctrl_cmd(l1h, 1, "SETTSC", "%d", tsc);
+}
+
+/*! Send "SETBSIC" command to TRX */
+int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic)
+ return 0;
+
+ return trx_ctrl_cmd(l1h, 1, "SETBSIC", "%d", bsic);
+}
+
+/*! Send "SETRXGAIN" command to TRX */
+int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETRXGAIN", "%d", db);
+}
+
+/*! Send "SETPOWER" command to TRX */
+int trx_if_cmd_setpower(struct trx_l1h *l1h, int db)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETPOWER", "%d", db);
+}
+
+/*! Send "SETMAXDLY" command to TRX, i.e. maximum delay for RACH bursts */
+int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETMAXDLY", "%d", dly);
+}
+
+/*! Send "SETMAXDLYNB" command to TRX, i.e. maximum delay for normal bursts */
+int trx_if_cmd_setmaxdlynb(struct trx_l1h *l1h, int dly)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETMAXDLYNB", "%d", dly);
+}
+
+/*! Send "SETSLOT" command to TRX: Configure Channel Combination for TS */
+int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type)
+{
+ return trx_ctrl_cmd(l1h, 1, "SETSLOT", "%d %d", tn, type);
+}
+
+/*! Send "RXTUNE" command to TRX: Tune Receiver to given ARFCN */
+int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ uint16_t freq10;
+
+ if (pinst->trx->bts->band == GSM_BAND_1900)
+ arfcn |= ARFCN_PCS;
+
+ freq10 = gsm_arfcn2freq10(arfcn, 1); /* RX = uplink */
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n",
+ arfcn & ~ARFCN_FLAG_MASK);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(l1h, 1, "RXTUNE", "%d", freq10 * 100);
+}
+
+/*! Send "TXTUNE" command to TRX: Tune Transmitter to given ARFCN */
+int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ uint16_t freq10;
+
+ if (pinst->trx->bts->band == GSM_BAND_1900)
+ arfcn |= ARFCN_PCS;
+
+ freq10 = gsm_arfcn2freq10(arfcn, 0); /* TX = downlink */
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n",
+ arfcn & ~ARFCN_FLAG_MASK);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(l1h, 1, "TXTUNE", "%d", freq10 * 100);
+}
+
+/*! Send "HANDOVER" command to TRX: Enable handover RACH Detection on timeslot/sub-slot */
+int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss)
+{
+ return trx_ctrl_cmd(l1h, 1, "HANDOVER", "%d %d", tn, ss);
+}
+
+/*! Send "NOHANDOVER" command to TRX: Disable handover RACH Detection on timeslot/sub-slot */
+int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss)
+{
+ return trx_ctrl_cmd(l1h, 1, "NOHANDOVER", "%d %d", tn, ss);
+}
+
+struct trx_ctrl_rsp {
+ char cmd[50];
+ char params[100];
+ int status;
+};
+
+static int parse_rsp(const char *buf_in, size_t len_in, struct trx_ctrl_rsp *rsp)
+{
+ char *p, *k;
+
+ if (strncmp(buf_in, "RSP ", 4))
+ goto parse_err;
+
+ /* Get the RSP cmd name */
+ if (!(p = strchr(buf_in + 4, ' ')))
+ goto parse_err;
+
+ if (p - buf_in >= sizeof(rsp->cmd)) {
+ LOGP(DTRX, LOGL_ERROR, "cmd buffer too small %lu >= %lu\n",
+ p - buf_in, sizeof(rsp->cmd));
+ goto parse_err;
+ }
+
+ rsp->cmd[0] = '\0';
+ strncat(rsp->cmd, buf_in + 4, p - buf_in - 4);
+
+ /* Now comes the status code of the response */
+ p++;
+ if (sscanf(p, "%d", &rsp->status) != 1)
+ goto parse_err;
+
+ /* Now copy back the parameters */
+ k = strchr(p, ' ');
+ if (k)
+ k++;
+ else
+ k = p + strlen(p);
+
+ if (strlen(k) >= sizeof(rsp->params)) {
+ LOGP(DTRX, LOGL_ERROR, "params buffer too small %lu >= %lu\n",
+ strlen(k), sizeof(rsp->params));
+ goto parse_err;
+ }
+ rsp->params[0] = '\0';
+ strcat(rsp->params, k);
+ return 0;
+
+parse_err:
+ LOGP(DTRX, LOGL_NOTICE, "Unknown message on ctrl port: %s\n",
+ buf_in);
+ return -1;
+}
+
+static bool cmd_matches_rsp(struct trx_ctrl_msg *tcm, struct trx_ctrl_rsp *rsp)
+{
+ if (strcmp(tcm->cmd, rsp->cmd))
+ return false;
+
+ /* For SETSLOT we also need to check if it's the response for the
+ specific timeslot. For other commands such as SETRXGAIN, it is
+ expected that they can return different values */
+ if (strcmp(tcm->cmd, "SETSLOT") == 0 && strcmp(tcm->params, rsp->params))
+ return false;
+
+ return true;
+}
+
+/* -EINVAL: unrecoverable error, exit BTS
+ * N > 0: try sending originating command again after N seconds
+ * 0: Done with response, get originating command out from send queue
+ */
+static int trx_ctrl_rx_rsp(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp, bool critical)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+
+ /* If TRX fails, try again after 1 sec */
+ if (strcmp(rsp->cmd, "POWERON") == 0) {
+ if (rsp->status == 0) {
+ if (pinst->phy_link->state != PHY_LINK_CONNECTED)
+ phy_link_state_set(pinst->phy_link, PHY_LINK_CONNECTED);
+ return 0;
+ } else {
+ LOGP(DTRX, LOGL_NOTICE,
+ "transceiver (%s) rejected POWERON command (%d), re-trying in a few seconds\n",
+ phy_instance_name(pinst), rsp->status);
+ if (pinst->phy_link->state != PHY_LINK_SHUTDOWN)
+ phy_link_state_set(pinst->phy_link, PHY_LINK_SHUTDOWN);
+ return 5;
+ }
+ }
+
+ if (rsp->status) {
+ LOGP(DTRX, critical ? LOGL_FATAL : LOGL_NOTICE,
+ "transceiver (%s) rejected TRX command with response: '%s%s%s %d'\n",
+ phy_instance_name(pinst), rsp->cmd, rsp->params[0] != '\0' ? " ":"",
+ rsp->params, rsp->status);
+ if (critical)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*! Get + parse response from TRX ctrl socket */
+static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_l1h *l1h = ofd->data;
+ struct phy_instance *pinst = l1h->phy_inst;
+ char buf[1500];
+ struct trx_ctrl_rsp rsp;
+ int len, rc;
+ struct trx_ctrl_msg *tcm;
+
+ len = recv(ofd->fd, buf, sizeof(buf) - 1, 0);
+ if (len <= 0)
+ return len;
+ buf[len] = '\0';
+
+ if (parse_rsp(buf, len, &rsp) < 0)
+ return 0;
+
+ LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
+
+ /* abort timer and send next message, if any */
+ if (osmo_timer_pending(&l1h->trx_ctrl_timer))
+ osmo_timer_del(&l1h->trx_ctrl_timer);
+
+ /* get command for response message */
+ if (llist_empty(&l1h->trx_ctrl_list)) {
+ /* RSP from a retransmission, skip it */
+ if (l1h->last_acked && cmd_matches_rsp(l1h->last_acked, &rsp)) {
+ LOGP(DTRX, LOGL_NOTICE, "Discarding duplicated RSP "
+ "from old CMD '%s'\n", buf);
+ return 0;
+ }
+ LOGP(DTRX, LOGL_NOTICE, "Response message without "
+ "command\n");
+ return -EINVAL;
+ }
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg,
+ list);
+
+ /* check if response matches command */
+ if (!cmd_matches_rsp(tcm, &rsp)) {
+ /* RSP from a retransmission, skip it */
+ if (l1h->last_acked && cmd_matches_rsp(l1h->last_acked, &rsp)) {
+ LOGP(DTRX, LOGL_NOTICE, "Discarding duplicated RSP "
+ "from old CMD '%s'\n", buf);
+ return 0;
+ }
+ LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE,
+ "Response message '%s' does not match command "
+ "message 'CMD %s%s%s'\n",
+ buf, tcm->cmd, tcm->params_len ? " ":"", tcm->params);
+ goto rsp_error;
+ }
+
+ /* check for response code */
+ rc = trx_ctrl_rx_rsp(l1h, &rsp, tcm->critical);
+ if (rc == -EINVAL)
+ goto rsp_error;
+
+ /* re-schedule last cmd in rc seconds time */
+ if (rc > 0) {
+ osmo_timer_schedule(&l1h->trx_ctrl_timer, rc, 0);
+ return 0;
+ }
+
+ /* remove command from list, save it to last_acked and removed previous last_acked */
+ llist_del(&tcm->list);
+ talloc_free(l1h->last_acked);
+ l1h->last_acked = tcm;
+
+ trx_ctrl_send(l1h);
+
+ return 0;
+
+rsp_error:
+ bts_shutdown(pinst->trx->bts, "TRX-CTRL-MSG: CRITICAL");
+ /* keep tcm list, so process is stopped */
+ return -EIO;
+}
+
+
+/*
+ * TRX burst data socket
+ */
+
+static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_l1h *l1h = ofd->data;
+ uint8_t buf[TRX_MAX_BURST_LEN];
+ int len;
+ uint8_t tn;
+ int8_t rssi;
+ int16_t toa256 = 0;
+ uint32_t fn;
+ sbit_t bits[EGPRS_BURST_LEN];
+ int i, burst_len = GSM_BURST_LEN;
+
+ len = recv(ofd->fd, buf, sizeof(buf), 0);
+ if (len <= 0) {
+ return len;
+ } else if (len == EGPRS_BURST_LEN + 10) {
+ burst_len = EGPRS_BURST_LEN;
+ /* Accept bursts ending with 2 bytes of padding (OpenBTS compatible trx) or without them: */
+ } else if (len != GSM_BURST_LEN + 10 && len != GSM_BURST_LEN + 8) {
+ LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid lenght "
+ "'%d'\n", len);
+ return -EINVAL;
+ }
+ tn = buf[0];
+ fn = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4];
+ rssi = -(int8_t)buf[5];
+ toa256 = ((int16_t)(buf[6] << 8) | buf[7]);
+
+ /* copy and convert bits {254..0} to sbits {-127..127} */
+ for (i = 0; i < burst_len; i++) {
+ if (buf[8 + i] == 255)
+ bits[i] = -127;
+ else
+ bits[i] = 127 - buf[8 + i];
+ }
+
+ if (tn >= 8) {
+ LOGP(DTRX, LOGL_ERROR, "Illegal TS %d\n", tn);
+ return -EINVAL;
+ }
+ if (fn >= GSM_HYPERFRAME) {
+ LOGP(DTRX, LOGL_ERROR, "Illegal FN %u\n", fn);
+ return -EINVAL;
+ }
+
+ LOGP(DTRX, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa256=%d\n",
+ tn, fn, rssi, toa256);
+
+#ifdef TOA_RSSI_DEBUG
+ char deb[128];
+
+ sprintf(deb, "| 0 "
+ " | rssi=%4d toa=%5d fn=%u", rssi, toa256, fn);
+ deb[1 + (128 + rssi) / 4] = '*';
+ fprintf(stderr, "%s\n", deb);
+#endif
+
+ /* feed received burst into scheduler code */
+ trx_sched_ul_burst(&l1h->l1s, tn, fn, bits, burst_len, rssi, toa256);
+
+ return 0;
+}
+
+/*! Send burst data for given FN/timeslot to TRX
+ * \param[inout] l1h TRX Layer1 handle referring to TX
+ * \param[in] tn Timeslot Number (0..7)
+ * \param[in] fn GSM Frame Number
+ * \param[in] pwr Transmit Power to use
+ * \param[in] bits Unpacked bits to be transmitted
+ * \param[in] nbits Number of \a bits
+ * \returns 0 on success; negative on error */
+int trx_if_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr,
+ const ubit_t *bits, uint16_t nbits)
+{
+ uint8_t buf[TRX_MAX_BURST_LEN];
+
+ if ((nbits != GSM_BURST_LEN) && (nbits != EGPRS_BURST_LEN)) {
+ LOGP(DTRX, LOGL_ERROR, "Tx burst length %u invalid\n", nbits);
+ return -1;
+ }
+
+ LOGP(DTRX, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
+
+ buf[0] = tn;
+ buf[1] = (fn >> 24) & 0xff;
+ buf[2] = (fn >> 16) & 0xff;
+ buf[3] = (fn >> 8) & 0xff;
+ buf[4] = (fn >> 0) & 0xff;
+ buf[5] = pwr;
+
+ /* copy ubits {0,1} */
+ memcpy(buf + 6, bits, nbits);
+
+ /* we must be sure that we have clock, and we have sent all control
+ * data */
+ if (transceiver_available && llist_empty(&l1h->trx_ctrl_list)) {
+ send(l1h->trx_ofd_data.fd, buf, nbits + 6, 0);
+ } else
+ LOGP(DTRX, LOGL_DEBUG, "Ignoring TX data, transceiver "
+ "offline.\n");
+
+ return 0;
+}
+
+
+/*
+ * open/close
+ */
+
+/*! flush (delete) all pending control messages */
+void trx_if_flush(struct trx_l1h *l1h)
+{
+ struct trx_ctrl_msg *tcm;
+
+ /* free ctrl message list */
+ while (!llist_empty(&l1h->trx_ctrl_list)) {
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg,
+ list);
+ llist_del(&tcm->list);
+ talloc_free(tcm);
+ }
+ talloc_free(l1h->last_acked);
+}
+
+/*! close the TRX for given handle (data + control socket) */
+void trx_if_close(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ LOGP(DTRX, LOGL_NOTICE, "Close transceiver for %s\n",
+ phy_instance_name(pinst));
+
+ trx_if_flush(l1h);
+
+ /* close sockets */
+ trx_udp_close(&l1h->trx_ofd_ctrl);
+ trx_udp_close(&l1h->trx_ofd_data);
+}
+
+/*! compute UDP port number used for TRX protocol */
+static uint16_t compute_port(struct phy_instance *pinst, int remote, int is_data)
+{
+ struct phy_link *plink = pinst->phy_link;
+ uint16_t inc = 1;
+
+ if (is_data)
+ inc = 2;
+
+ if (remote)
+ return plink->u.osmotrx.base_port_remote + (pinst->num << 1) + inc;
+ else
+ return plink->u.osmotrx.base_port_local + (pinst->num << 1) + inc;
+}
+
+/*! open a TRX interface. creates contro + data sockets */
+static int trx_if_open(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+ int rc;
+
+ LOGP(DTRX, LOGL_NOTICE, "Open transceiver for %s\n",
+ phy_instance_name(pinst));
+
+ /* initialize ctrl queue */
+ INIT_LLIST_HEAD(&l1h->trx_ctrl_list);
+
+ /* open sockets */
+ rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl,
+ plink->u.osmotrx.local_ip,
+ compute_port(pinst, 0, 0),
+ plink->u.osmotrx.remote_ip,
+ compute_port(pinst, 1, 0), trx_ctrl_read_cb);
+ if (rc < 0)
+ goto err;
+ rc = trx_udp_open(l1h, &l1h->trx_ofd_data,
+ plink->u.osmotrx.local_ip,
+ compute_port(pinst, 0, 1),
+ plink->u.osmotrx.remote_ip,
+ compute_port(pinst, 1, 1), trx_data_read_cb);
+ if (rc < 0)
+ goto err;
+
+ /* enable all slots */
+ l1h->config.slotmask = 0xff;
+
+ /* FIXME: why was this only for TRX0 ? */
+ //if (l1h->trx->nr == 0)
+ trx_if_cmd_poweroff(l1h);
+
+ return 0;
+
+err:
+ trx_if_close(l1h);
+ return rc;
+}
+
+/*! close the control + burst data sockets for one phy_instance */
+static void trx_phy_inst_close(struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ trx_if_close(l1h);
+ trx_sched_exit(&l1h->l1s);
+}
+
+/*! open the control + burst data sockets for one phy_instance */
+static int trx_phy_inst_open(struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h;
+ int rc;
+
+ l1h = pinst->u.osmotrx.hdl;
+ if (!l1h)
+ return -EINVAL;
+
+ rc = trx_sched_init(&l1h->l1s, pinst->trx);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot initialize scheduler for phy "
+ "instance %d\n", pinst->num);
+ return -EIO;
+ }
+
+ rc = trx_if_open(l1h);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot open TRX interface for phy "
+ "instance %d\n", pinst->num);
+ trx_phy_inst_close(pinst);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*! open the PHY link using TRX protocol */
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ struct phy_instance *pinst;
+ int rc;
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+ /* open the shared/common clock socket */
+ rc = trx_udp_open(plink, &plink->u.osmotrx.trx_ofd_clk,
+ plink->u.osmotrx.local_ip,
+ plink->u.osmotrx.base_port_local,
+ plink->u.osmotrx.remote_ip,
+ plink->u.osmotrx.base_port_remote,
+ trx_clk_read_cb);
+ if (rc < 0) {
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ return -1;
+ }
+
+ /* open the individual instances with their ctrl+data sockets */
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (trx_phy_inst_open(pinst) < 0)
+ goto cleanup;
+ }
+ /* FIXME: is there better way to check/report TRX availability? */
+ transceiver_available = 1;
+ return 0;
+
+cleanup:
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (pinst->u.osmotrx.hdl) {
+ trx_if_close(pinst->u.osmotrx.hdl);
+ pinst->u.osmotrx.hdl = NULL;
+ }
+ }
+ trx_udp_close(&plink->u.osmotrx.trx_ofd_clk);
+ return -1;
+}
+
+/*! determine if the TRX for given handle is powered up */
+int trx_if_powered(struct trx_l1h *l1h)
+{
+ return l1h->config.poweron;
+}
diff --git a/src/osmo-bts-trx/trx_if.h b/src/osmo-bts-trx/trx_if.h
new file mode 100644
index 00000000..206f5e54
--- /dev/null
+++ b/src/osmo-bts-trx/trx_if.h
@@ -0,0 +1,35 @@
+#ifndef TRX_IF_H
+#define TRX_IF_H
+
+extern int transceiver_available;
+
+struct trx_l1h;
+
+struct trx_ctrl_msg {
+ struct llist_head list;
+ char cmd[28];
+ char params[100];
+ int cmd_len;
+ int params_len;
+ int critical;
+};
+
+void trx_if_init(struct trx_l1h *l1h);
+int trx_if_cmd_poweroff(struct trx_l1h *l1h);
+int trx_if_cmd_poweron(struct trx_l1h *l1h);
+int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc);
+int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic);
+int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db);
+int trx_if_cmd_setpower(struct trx_l1h *l1h, int db);
+int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly);
+int trx_if_cmd_setmaxdlynb(struct trx_l1h *l1h, int dly);
+int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type);
+int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn);
+int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn);
+int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss);
+int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss);
+int trx_if_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr,
+ const ubit_t *bits, uint16_t nbits);
+int trx_if_powered(struct trx_l1h *l1h);
+
+#endif /* TRX_IF_H */
diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c
new file mode 100644
index 00000000..e9710acd
--- /dev/null
+++ b/src/osmo-bts-trx/trx_vty.c
@@ -0,0 +1,606 @@
+/* VTY interface for sysmoBTS */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * 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 <inttypes.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/socket.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/logging.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/scheduler.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+#include "loops.h"
+
+#define OSMOTRX_STR "OsmoTRX Transceiver configuration\n"
+
+static struct gsm_bts *vty_bts;
+
+DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver",
+ SHOW_STR "Display information about transceivers\n")
+{
+ struct gsm_bts *bts = vty_bts;
+ struct gsm_bts_trx *trx;
+ struct trx_l1h *l1h;
+
+ if (!transceiver_available) {
+ vty_out(vty, "transceiver is not connected%s", VTY_NEWLINE);
+ } else {
+ vty_out(vty, "transceiver is connected%s", VTY_NEWLINE);
+ }
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ char *sname = osmo_sock_get_name(NULL, pinst->phy_link->u.osmotrx.trx_ofd_clk.fd);
+ l1h = pinst->u.osmotrx.hdl;
+ vty_out(vty, "TRX %d %s%s", trx->nr, sname, VTY_NEWLINE);
+ talloc_free(sname);
+ vty_out(vty, " %s%s",
+ (l1h->config.poweron) ? "poweron":"poweroff",
+ VTY_NEWLINE);
+ if (l1h->config.arfcn_valid)
+ vty_out(vty, " arfcn : %d%s%s",
+ (l1h->config.arfcn & ~ARFCN_PCS),
+ (l1h->config.arfcn & ARFCN_PCS) ? " (PCS)" : "",
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " arfcn : undefined%s", VTY_NEWLINE);
+ if (l1h->config.tsc_valid)
+ vty_out(vty, " tsc : %d%s", l1h->config.tsc,
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " tsc : undefined%s", VTY_NEWLINE);
+ if (l1h->config.bsic_valid)
+ vty_out(vty, " bsic : %d%s", l1h->config.bsic,
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " bisc : undefined%s", VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+
+static void show_phy_inst_single(struct vty *vty, struct phy_instance *pinst)
+{
+ uint8_t tn;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ vty_out(vty, "PHY Instance %s%s",
+ phy_instance_name(pinst), VTY_NEWLINE);
+
+ if (l1h->config.rxgain_valid)
+ vty_out(vty, " rx-gain : %d dB%s",
+ l1h->config.rxgain, VTY_NEWLINE);
+ else
+ vty_out(vty, " rx-gain : undefined%s", VTY_NEWLINE);
+ if (l1h->config.power_valid)
+ vty_out(vty, " tx-attenuation : %d dB%s",
+ l1h->config.power, VTY_NEWLINE);
+ else
+ vty_out(vty, " tx-attenuation : undefined%s", VTY_NEWLINE);
+ if (l1h->config.maxdly_valid)
+ vty_out(vty, " maxdly : %d%s", l1h->config.maxdly,
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " maxdly : undefined%s", VTY_NEWLINE);
+ if (l1h->config.maxdlynb_valid)
+ vty_out(vty, " maxdlynb : %d%s", l1h->config.maxdlynb,
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " maxdlynb : undefined%s", VTY_NEWLINE);
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ if (!((1 << tn) & l1h->config.slotmask))
+ vty_out(vty, " slot #%d: unsupported%s", tn,
+ VTY_NEWLINE);
+ else if (l1h->config.slottype_valid[tn])
+ vty_out(vty, " slot #%d: type %d%s", tn,
+ l1h->config.slottype[tn],
+ VTY_NEWLINE);
+ else
+ vty_out(vty, " slot #%d: undefined%s", tn,
+ VTY_NEWLINE);
+ }
+}
+
+static void show_phy_single(struct vty *vty, struct phy_link *plink)
+{
+ struct phy_instance *pinst;
+
+ vty_out(vty, "PHY %u%s", plink->num, VTY_NEWLINE);
+
+ llist_for_each_entry(pinst, &plink->instances, list)
+ show_phy_inst_single(vty, pinst);
+}
+
+DEFUN(show_phy, show_phy_cmd, "show phy",
+ SHOW_STR "Display information about the available PHYs")
+{
+ int i;
+
+ for (i = 0; i < 255; i++) {
+ struct phy_link *plink = phy_link_by_num(i);
+ if (!plink)
+ break;
+ show_phy_single(vty, plink);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_ms_power_loop, cfg_phy_ms_power_loop_cmd,
+ "osmotrx ms-power-loop <-127-127>", OSMOTRX_STR
+ "Enable MS power control loop\nTarget RSSI value (transceiver specific, "
+ "should be 6dB or more above noise floor)\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.trx_target_rssi = atoi(argv[0]);
+ plink->u.osmotrx.trx_ms_power_loop = true;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_ms_power_loop, cfg_phy_no_ms_power_loop_cmd,
+ "no osmotrx ms-power-loop",
+ NO_STR OSMOTRX_STR "Disable MS power control loop\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.trx_ms_power_loop = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_timing_advance_loop, cfg_phy_timing_advance_loop_cmd,
+ "osmotrx timing-advance-loop", OSMOTRX_STR
+ "Enable timing advance control loop\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.trx_ta_loop = true;
+
+ return CMD_SUCCESS;
+}
+DEFUN(cfg_phy_no_timing_advance_loop, cfg_phy_no_timing_advance_loop_cmd,
+ "no osmotrx timing-advance-loop",
+ NO_STR OSMOTRX_STR "Disable timing advance control loop\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.trx_ta_loop = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_maxdly, cfg_phyinst_maxdly_cmd,
+ "osmotrx maxdly <0-31>",
+ OSMOTRX_STR
+ "Set the maximum acceptable delay of an Access Burst (in GSM symbols)."
+ " Access Burst is the first burst a mobile transmits in order to establish"
+ " a connection and it is used to estimate Timing Advance (TA) which is"
+ " then applied to Normal Bursts to compensate for signal delay due to"
+ " distance. So changing this setting effectively changes maximum range of"
+ " the cell, because if we receive an Access Burst with a delay higher than"
+ " this value, it will be ignored and connection is dropped.\n"
+ "GSM symbols (approx. 1.1km per symbol)\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.maxdly = atoi(argv[0]);
+ l1h->config.maxdly_valid = 1;
+ l1h->config.maxdly_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_phyinst_maxdlynb, cfg_phyinst_maxdlynb_cmd,
+ "osmotrx maxdlynb <0-31>",
+ OSMOTRX_STR
+ "Set the maximum acceptable delay of a Normal Burst (in GSM symbols)."
+ " USE FOR TESTING ONLY, DON'T CHANGE IN PRODUCTION USE!"
+ " During normal operation, Normal Bursts delay are controled by a Timing"
+ " Advance control loop and thus Normal Bursts arrive to a BTS with no more"
+ " than a couple GSM symbols, which is already taken into account in osmo-trx."
+ " So changing this setting will have no effect in production installations"
+ " except increasing osmo-trx CPU load. This setting is only useful when"
+ " testing with a transmitter which can't precisely synchronize to the BTS"
+ " downlink signal, like e.g. R&S CMD57.\n"
+ "GSM symbols (approx. 1.1km per symbol)\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.maxdlynb = atoi(argv[0]);
+ l1h->config.maxdlynb_valid = 1;
+ l1h->config.maxdlynb_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_slotmask, cfg_phyinst_slotmask_cmd,
+ "slotmask (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0)",
+ "Set the supported slots\n"
+ "TS0 supported\nTS0 unsupported\nTS1 supported\nTS1 unsupported\n"
+ "TS2 supported\nTS2 unsupported\nTS3 supported\nTS3 unsupported\n"
+ "TS4 supported\nTS4 unsupported\nTS5 supported\nTS5 unsupported\n"
+ "TS6 supported\nTS6 unsupported\nTS7 supported\nTS7 unsupported\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+ uint8_t tn;
+
+ l1h->config.slotmask = 0;
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ if (argv[tn][0] == '1')
+ l1h->config.slotmask |= (1 << tn);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd,
+ "osmotrx power (on|off)",
+ OSMOTRX_STR
+ "Change TRX state\n"
+ "Turn it ON or OFF\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ if (strcmp(argv[0], "on"))
+ vty_out(vty, "OFF: %d%s", trx_if_cmd_poweroff(l1h), VTY_NEWLINE);
+ else {
+ vty_out(vty, "ON: %d%s", trx_if_cmd_poweron(l1h), VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_fn_advance, cfg_phy_fn_advance_cmd,
+ "osmotrx fn-advance <0-30>",
+ OSMOTRX_STR
+ "Set the number of frames to be transmitted to transceiver in advance "
+ "of current FN\n"
+ "Advance in frames\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.clock_advance = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_rts_advance, cfg_phy_rts_advance_cmd,
+ "osmotrx rts-advance <0-30>",
+ OSMOTRX_STR
+ "Set the number of frames to be requested (PCU) in advance of current "
+ "FN. Do not change this, unless you have a good reason!\n"
+ "Advance in frames\n")
+{
+ struct phy_link *plink = vty->index;
+
+ plink->u.osmotrx.rts_advance = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_rxgain, cfg_phyinst_rxgain_cmd,
+ "osmotrx rx-gain <0-50>",
+ OSMOTRX_STR
+ "Set the receiver gain in dB\n"
+ "Gain in dB\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.rxgain = atoi(argv[0]);
+ l1h->config.rxgain_valid = 1;
+ l1h->config.rxgain_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_tx_atten, cfg_phyinst_tx_atten_cmd,
+ "osmotrx tx-attenuation <0-50>",
+ OSMOTRX_STR
+ "Set the transmitter attenuation\n"
+ "Fixed attenuation in dB, overriding OML\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.power = atoi(argv[0]);
+ l1h->config.power_oml = 0;
+ l1h->config.power_valid = 1;
+ l1h->config.power_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_tx_atten_oml, cfg_phyinst_tx_atten_oml_cmd,
+ "osmotrx tx-attenuation oml",
+ OSMOTRX_STR
+ "Set the transmitter attenuation\n"
+ "Use NM_ATT_RF_MAXPOWR_R (max power reduction) from BSC via OML\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.power_oml = 1;
+ l1h->config.power_valid = 1;
+ l1h->config.power_sent = 0;
+ l1if_provision_transceiver_trx(l1h);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_no_rxgain, cfg_phyinst_no_rxgain_cmd,
+ "no osmotrx rx-gain",
+ NO_STR OSMOTRX_STR "Unset the receiver gain in dB\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.rxgain_valid = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_no_tx_atten, cfg_phyinst_no_tx_atten_cmd,
+ "no osmotrx tx-attenuation",
+ NO_STR OSMOTRX_STR "Unset the transmitter attenuation\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.power_valid = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd,
+ "no osmotrx maxdly",
+ NO_STR OSMOTRX_STR
+ "Unset the maximum delay of GSM symbols\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.maxdly_valid = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phyinst_no_maxdlynb, cfg_phyinst_no_maxdlynb_cmd,
+ "no osmotrx maxdlynb",
+ NO_STR OSMOTRX_STR
+ "Unset the maximum delay of GSM symbols\n")
+{
+ struct phy_instance *pinst = vty->index;
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ l1h->config.maxdlynb_valid = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_transc_ip, cfg_phy_transc_ip_cmd,
+ "osmotrx ip HOST",
+ OSMOTRX_STR
+ "Set local and remote IP address\n"
+ "IP address (for both OsmoBtsTrx and OsmoTRX)\n")
+{
+ struct phy_link *plink = vty->index;
+
+ osmo_talloc_replace_string(plink, &plink->u.osmotrx.local_ip, argv[0]);
+ osmo_talloc_replace_string(plink, &plink->u.osmotrx.remote_ip, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_osmotrx_ip, cfg_phy_osmotrx_ip_cmd,
+ "osmotrx ip (local|remote) A.B.C.D",
+ OSMOTRX_STR
+ "Set IP address\n" "Local IP address (BTS)\n"
+ "Remote IP address (OsmoTRX)\n" "IP address\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (!strcmp(argv[0], "local"))
+ osmo_talloc_replace_string(plink, &plink->u.osmotrx.local_ip, argv[1]);
+ else if (!strcmp(argv[0], "remote"))
+ osmo_talloc_replace_string(plink, &plink->u.osmotrx.remote_ip, argv[1]);
+ else
+ return CMD_WARNING;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_base_port, cfg_phy_base_port_cmd,
+ "osmotrx base-port (local|remote) <0-65535>",
+ OSMOTRX_STR "Set base UDP port number\n" "Local UDP port\n"
+ "Remote UDP port\n" "UDP base port number\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (!strcmp(argv[0], "local"))
+ plink->u.osmotrx.base_port_local = atoi(argv[1]);
+ else
+ plink->u.osmotrx.base_port_remote = atoi(argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_setbsic, cfg_phy_setbsic_cmd,
+ "osmotrx legacy-setbsic", OSMOTRX_STR
+ "Use SETBSIC to configure transceiver (use ONLY with OpenBTS Transceiver!)\n")
+{
+ struct phy_link *plink = vty->index;
+ plink->u.osmotrx.use_legacy_setbsic = true;
+
+ vty_out(vty, "%% You have enabled SETBSIC, which is not supported by OsmoTRX "
+ "but only useful if you want to interface with legacy OpenBTS Transceivers%s",
+ VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_setbsic, cfg_phy_no_setbsic_cmd,
+ "no osmotrx legacy-setbsic",
+ NO_STR OSMOTRX_STR "Disable Legacy SETBSIC to configure transceiver\n")
+{
+ struct phy_link *plink = vty->index;
+ plink->u.osmotrx.use_legacy_setbsic = false;
+
+ return CMD_SUCCESS;
+}
+
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+ if (plink->u.osmotrx.local_ip)
+ vty_out(vty, " osmotrx ip local %s%s",
+ plink->u.osmotrx.local_ip, VTY_NEWLINE);
+ if (plink->u.osmotrx.remote_ip)
+ vty_out(vty, " osmotrx ip remote %s%s",
+ plink->u.osmotrx.remote_ip, VTY_NEWLINE);
+
+ if (plink->u.osmotrx.trx_ms_power_loop)
+ vty_out(vty, " osmotrx ms-power-loop %d%s", plink->u.osmotrx.trx_target_rssi, VTY_NEWLINE);
+ else
+ vty_out(vty, " no osmotrx ms-power-loop%s", VTY_NEWLINE);
+ vty_out(vty, " %sosmotrx timing-advance-loop%s", (plink->u.osmotrx.trx_ta_loop) ? "" : "no ", VTY_NEWLINE);
+
+ if (plink->u.osmotrx.base_port_local)
+ vty_out(vty, " osmotrx base-port local %"PRIu16"%s",
+ plink->u.osmotrx.base_port_local, VTY_NEWLINE);
+ if (plink->u.osmotrx.base_port_remote)
+ vty_out(vty, " osmotrx base-port remote %"PRIu16"%s",
+ plink->u.osmotrx.base_port_remote, VTY_NEWLINE);
+
+ vty_out(vty, " osmotrx fn-advance %d%s",
+ plink->u.osmotrx.clock_advance, VTY_NEWLINE);
+ vty_out(vty, " osmotrx rts-advance %d%s",
+ plink->u.osmotrx.rts_advance, VTY_NEWLINE);
+
+ if (plink->u.osmotrx.use_legacy_setbsic)
+ vty_out(vty, " osmotrx legacy-setbsic%s", VTY_NEWLINE);
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+ struct trx_l1h *l1h = pinst->u.osmotrx.hdl;
+
+ if (l1h->config.rxgain_valid)
+ vty_out(vty, " osmotrx rx-gain %d%s",
+ l1h->config.rxgain, VTY_NEWLINE);
+ if (l1h->config.power_valid) {
+ if (l1h->config.power_oml)
+ vty_out(vty, " osmotrx tx-attenuation oml%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " osmotrx tx-attenuation %d%s",
+ l1h->config.power, VTY_NEWLINE);
+ }
+ if (l1h->config.maxdly_valid)
+ vty_out(vty, " osmotrx maxdly %d%s", l1h->config.maxdly, VTY_NEWLINE);
+ if (l1h->config.maxdlynb_valid)
+ vty_out(vty, " osmotrx maxdlynb %d%s", l1h->config.maxdlynb, VTY_NEWLINE);
+ if (l1h->config.slotmask != 0xff)
+ vty_out(vty, " slotmask %d %d %d %d %d %d %d %d%s",
+ l1h->config.slotmask & 1,
+ (l1h->config.slotmask >> 1) & 1,
+ (l1h->config.slotmask >> 2) & 1,
+ (l1h->config.slotmask >> 3) & 1,
+ (l1h->config.slotmask >> 4) & 1,
+ (l1h->config.slotmask >> 5) & 1,
+ (l1h->config.slotmask >> 6) & 1,
+ l1h->config.slotmask >> 7,
+ VTY_NEWLINE);
+}
+
+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)
+{
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ install_element_ve(&show_transceiver_cmd);
+ install_element_ve(&show_phy_cmd);
+
+ install_element(PHY_NODE, &cfg_phy_ms_power_loop_cmd);
+ install_element(PHY_NODE, &cfg_phy_no_ms_power_loop_cmd);
+ install_element(PHY_NODE, &cfg_phy_timing_advance_loop_cmd);
+ install_element(PHY_NODE, &cfg_phy_no_timing_advance_loop_cmd);
+ install_element(PHY_NODE, &cfg_phy_base_port_cmd);
+ install_element(PHY_NODE, &cfg_phy_fn_advance_cmd);
+ install_element(PHY_NODE, &cfg_phy_rts_advance_cmd);
+ install_element(PHY_NODE, &cfg_phy_transc_ip_cmd);
+ install_element(PHY_NODE, &cfg_phy_osmotrx_ip_cmd);
+ install_element(PHY_NODE, &cfg_phy_setbsic_cmd);
+ install_element(PHY_NODE, &cfg_phy_no_setbsic_cmd);
+
+ install_element(PHY_INST_NODE, &cfg_phyinst_rxgain_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_oml_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_no_rxgain_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_no_tx_atten_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_slotmask_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_power_on_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_maxdly_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_no_maxdly_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_maxdlynb_cmd);
+ install_element(PHY_INST_NODE, &cfg_phyinst_no_maxdlynb_cmd);
+
+ return 0;
+}
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ return 0;
+}