diff options
Diffstat (limited to 'src/osmo-bts-trx')
-rw-r--r-- | src/osmo-bts-trx/Makefile.am | 10 | ||||
-rw-r--r-- | src/osmo-bts-trx/l1_if.c | 782 | ||||
-rw-r--r-- | src/osmo-bts-trx/l1_if.h | 82 | ||||
-rw-r--r-- | src/osmo-bts-trx/loops.c | 340 | ||||
-rw-r--r-- | src/osmo-bts-trx/loops.h | 27 | ||||
-rw-r--r-- | src/osmo-bts-trx/main.c | 151 | ||||
-rw-r--r-- | src/osmo-bts-trx/scheduler_trx.c | 1635 | ||||
-rw-r--r-- | src/osmo-bts-trx/trx_if.c | 838 | ||||
-rw-r--r-- | src/osmo-bts-trx/trx_if.h | 35 | ||||
-rw-r--r-- | src/osmo-bts-trx/trx_vty.c | 606 |
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; +} |