diff --git a/.gitignore b/.gitignore index 835134937..eda0126c5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ src/osmo-bts-sysmo/sysmobts src/osmo-bts-sysmo/sysmobts-remote src/osmo-bts-sysmo/sysmobts-mgr +src/osmo-bts-trx/osmobts-trx tests/atconfig tests/package.m4 diff --git a/configure.ac b/configure.ac index 641a6c9a2..752e85b30 100644 --- a/configure.ac +++ b/configure.ac @@ -37,6 +37,14 @@ AC_ARG_ENABLE(sysmocom-bts, AC_MSG_RESULT([$enable_sysmocom_bts]) AM_CONDITIONAL(ENABLE_SYSMOBTS, test "x$enable_sysmocom_bts" = "xyes") +AC_MSG_CHECKING([whether to enable trx hardware support]) +AC_ARG_ENABLE(trx, + AC_HELP_STRING([--enable-trx], + [enable code for trx hardware [default=no]]), + [enable_trx="yes"],[enable_trx="no"]) +AC_MSG_RESULT([$enable_trx]) +AM_CONDITIONAL(ENABLE_TRX, test "x$enable_trx" = "xyes") + # We share gsm_data.h with OpenBSC and need to be pointed to the source # directory of OpenBSC for now. AC_ARG_WITH([openbsc], @@ -70,6 +78,7 @@ AC_OUTPUT( src/Makefile src/common/Makefile src/osmo-bts-sysmo/Makefile + src/osmo-bts-trx/Makefile include/Makefile include/osmo-bts/Makefile tests/Makefile diff --git a/include/osmo-bts/gsm_data.h b/include/osmo-bts/gsm_data.h index bfa5285bf..06115b334 100644 --- a/include/osmo-bts/gsm_data.h +++ b/include/osmo-bts/gsm_data.h @@ -115,6 +115,13 @@ static inline struct femtol1_hdl *trx_femtol1_hdl(struct gsm_bts_trx *trx) return trx->role_bts.l1h; } +struct trx_l1h; + +static inline struct trx_l1h *trx_l1h_hdl(struct gsm_bts_trx *trx) +{ + return trx->role_bts.l1h; +} + void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state); diff --git a/include/osmo-bts/logging.h b/include/osmo-bts/logging.h index 226f77b75..b7c8a270a 100644 --- a/include/osmo-bts/logging.h +++ b/include/osmo-bts/logging.h @@ -16,6 +16,7 @@ enum { DDSP, DPCU, DHO, + DTRX, DABIS, DRTP, DSUM, diff --git a/include/osmo-bts/rsl.h b/include/osmo-bts/rsl.h index 42ea6ef71..6cb40dd29 100644 --- a/include/osmo-bts/rsl.h +++ b/include/osmo-bts/rsl.h @@ -11,6 +11,8 @@ enum { LCHAN_REL_ACT_OML, }; +int msgb_queue_flush(struct llist_head *list); + int down_rsl(struct gsm_bts_trx *trx, struct msgb *msg); int rsl_tx_rf_res(struct gsm_bts_trx *trx); int rsl_tx_chan_rqd(struct gsm_bts_trx *trx, struct gsm_time *gtime, diff --git a/src/Makefile.am b/src/Makefile.am index e2eab0daf..b8fe655c2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,3 +3,6 @@ SUBDIRS = common if ENABLE_SYSMOBTS SUBDIRS += osmo-bts-sysmo endif +if ENABLE_TRX +SUBDIRS += osmo-bts-trx +endif diff --git a/src/common/logging.c b/src/common/logging.c index 1e071dbb9..b117ee041 100644 --- a/src/common/logging.c +++ b/src/common/logging.c @@ -107,6 +107,12 @@ static struct log_info_cat bts_log_info_cat[] = { .color = "\033[0;37m", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DTRX] = { + .name = "DTRX", + .description = "TRX interface", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, #if 0 [DNS] = { .name = "DNS", diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am new file mode 100644 index 000000000..94a050cea --- /dev/null +++ b/src/osmo-bts-trx/Makefile.am @@ -0,0 +1,11 @@ +INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) +AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) -lortp + +EXTRA_DIST = trx_if.h l1_if.h scheduler.h xcch.h rach.h sch.h pxxch.h tch_fr.h + +bin_PROGRAMS = osmobts-trx + +osmobts_trx_SOURCES = main.c trx_if.c l1_if.c scheduler.c trx_vty.c xcch.c rach.c sch.c pxxch.c tch_fr.c +osmobts_trx_LDADD = $(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 000000000..66922b54c --- /dev/null +++ b/src/osmo-bts-trx/l1_if.c @@ -0,0 +1,571 @@ +/* + * layer 1 primitive handling and interface + * + * Copyright (C) 2013 Andreas Eversberg + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "trx_if.h" +#include "scheduler.h" + + +static const uint8_t tranceiver_chan_types[_GSM_PCHAN_MAX] = { + [GSM_PCHAN_NONE] = 8, + [GSM_PCHAN_CCCH] = 6, + [GSM_PCHAN_CCCH_SDCCH4] = 5, + [GSM_PCHAN_TCH_F] = 1, + [GSM_PCHAN_TCH_H] = 2, + [GSM_PCHAN_SDCCH8_SACCH8C] = 7, + [GSM_PCHAN_PDCH] = 13, + //[GSM_PCHAN_TCH_F_PDCH] = FIXME, + [GSM_PCHAN_UNKNOWN] = 0, +}; + + +/* + * create destroy trx l1 instance + */ + +struct trx_l1h *l1if_open(struct gsm_bts_trx *trx) +{ + struct trx_l1h *l1h; + int rc; + + l1h = talloc_zero(tall_bts_ctx, struct trx_l1h); + if (!l1h) + return NULL; + l1h->trx = trx; + trx->role_bts.l1h = l1h; + + trx_sched_init(l1h); + + rc = trx_if_open(l1h); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Cannot initialize scheduler\n"); + goto err; + } + + return l1h; + +err: + l1if_close(l1h); + return NULL; +} + +void l1if_close(struct trx_l1h *l1h) +{ + trx_if_close(l1h); + trx_sched_exit(l1h); + talloc_free(l1h); +} + +void l1if_reset(struct trx_l1h *l1h) +{ +} + +void check_tranceiver_availability(struct trx_l1h *l1h) +{ + struct gsm_bts_trx *trx = l1h->trx; + uint8_t tn; + + /* HACK, we should change state when we receive first clock from + * tranceiver */ + if (1) { + /* signal availability */ + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + + for (tn = 0; tn < 8; 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); + } +} + + +/* + * tranceiver provisioning + */ +int l1if_provision_tranceiver_trx(struct trx_l1h *l1h) +{ + uint8_t tn; + + if (l1h->config.poweron + && l1h->config.tsc_valid + && l1h->config.bsic_valid + && l1h->config.arfcn_valid) { + /* before power on */ + if (l1h->config.arfcn_valid && !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_valid && !l1h->config.tsc_sent) { + trx_if_cmd_settsc(l1h, l1h->config.tsc); + l1h->config.tsc_sent = 1; + } + if (l1h->config.bsic_valid && !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; + } + for (tn = 0; tn < 8; 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; + for (tn = 0; tn < 8; tn++) + l1h->config.slottype_sent[tn] = 0; + } + + return 0; +} + +int l1if_provision_tranceiver(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct trx_l1h *l1h; + + llist_for_each_entry(trx, &bts->trx_list, list) { + l1h = trx_l1h_hdl(trx); + 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; + l1if_provision_tranceiver_trx(l1h); + } + return 0; +} + +/* + * activation/configuration/deactivation of tranceiver's TRX + */ + +/* initialize the layer1 */ +static int trx_init(struct gsm_bts_trx *trx) +{ + struct trx_l1h *l1h = trx_l1h_hdl(trx); + + /* power on tranceiver, if not already */ + if (!l1h->config.poweron) { + l1h->config.poweron = 1; + l1h->config.poweron_sent = 0; + l1if_provision_tranceiver_trx(l1h); + } + + if (trx == trx->bts->c0) + lchan_init_lapdm(&trx->ts[0].lchan[4]); + + /* 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 tranceiver */ +static int trx_close(struct gsm_bts_trx *trx) +{ + struct trx_l1h *l1h = trx_l1h_hdl(trx); + + if (l1h->config.poweron) { + l1h->config.poweron = 0; + l1h->config.poweron_sent = 0; + l1if_provision_tranceiver_trx(l1h); + } + + return 0; +} + +/* set bts attributes */ +static uint8_t trx_set_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct trx_l1h *l1h; + uint8_t bsic = bts->bsic; + + llist_for_each_entry(trx, &bts->trx_list, list) { + l1h = trx_l1h_hdl(trx); + if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) { + l1h->config.bsic = bsic; + l1h->config.bsic_valid = 1; + l1h->config.bsic_sent = 0; + } + check_tranceiver_availability(l1h); + } + + + return 0; +} + +/* set trx attributes */ +static uint8_t trx_set_trx(struct gsm_bts_trx *trx) +{ + struct trx_l1h *l1h = trx_l1h_hdl(trx); + 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; + } + + return 0; +} + +/* set ts attributes */ +static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts) +{ + struct trx_l1h *l1h = trx_l1h_hdl(ts->trx); + uint8_t tn = ts->nr; + uint16_t tsc = ts->tsc; + enum gsm_phys_chan_config pchan = ts->pchan; + uint8_t slottype; + int rc; + + /* all TSC of all timeslots must be equal, because tranceiver 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; + } + + /* set physical channel */ + rc = trx_sched_set_pchan(l1h, tn, pchan); + if (rc) + return NM_NACK_RES_NOTAVAIL; + + slottype = tranceiver_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_tranceiver_trx(l1h); + } + + return 0; +} + + +/* + * primitive handling + */ + +/* enable ciphering */ +static int l1if_set_ciphering(struct trx_l1h *l1h, struct gsm_lchan *lchan, + int downlink) +{ + // FIXME + return 0; +} + +/* channel mode, encryption and/or multirate have changed */ +static int l1if_rsl_mode_modify(struct trx_l1h *l1h, struct gsm_lchan *lchan, + int downlink) +{ + + // FIXME + 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 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(l1h->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); +} + + +/* primitive from common part */ +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct trx_l1h *l1h = trx_l1h_hdl(trx); + struct msgb *msg = l1sap->oph.msg; + uint8_t chan_nr; + uint8_t tn, ss; + int rc = 0; + struct gsm_lchan *lchan; + + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + if (!msg) + break; + /* put data into scheduler's queue */ + return trx_sched_ph_data_req(l1h, l1sap); + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + if (!msg) + break; + /* put data into scheduler's queue */ + return trx_sched_tch_req(l1h, l1sap); + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + switch (l1sap->u.info.type) { + case PRIM_INFO_ACT_CIPH: + chan_nr = l1sap->u.info.u.ciph_req.chan_nr; + tn = L1SAP_CHAN2TS(chan_nr); + ss = l1sap_chan2ss(chan_nr); + lchan = &trx->ts[tn].lchan[ss]; + if (l1sap->u.info.u.ciph_req.downlink) { + l1if_set_ciphering(l1h, lchan, 1); + lchan->ciph_state = LCHAN_CIPH_RX_REQ; + } + if (l1sap->u.info.u.ciph_req.uplink) { + l1if_set_ciphering(l1h, lchan, 0); + lchan->ciph_state = LCHAN_CIPH_TXRX_REQ; + } + break; + case PRIM_INFO_ACTIVATE: + case PRIM_INFO_DEACTIVATE: + case PRIM_INFO_MODIFY: + chan_nr = l1sap->u.info.u.act_req.chan_nr; + tn = L1SAP_CHAN2TS(chan_nr); + ss = l1sap_chan2ss(chan_nr); + lchan = &trx->ts[tn].lchan[ss]; + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) { + if ((chan_nr & 0x80)) { + LOGP(DL1C, LOGL_ERROR, "Cannot activate" + " chan_nr 0x%02x\n", chan_nr); + break; + } + /* activate dedicated channel */ + trx_sched_set_lchan(l1h, chan_nr, 0x00, 0, 1); + trx_sched_set_lchan(l1h, chan_nr, 0x00, 1, 1); + trx_sched_set_lchan(l1h, chan_nr, 0x40, 0, 1); + trx_sched_set_lchan(l1h, chan_nr, 0x40, 1, 1); + /* init lapdm */ + lchan_init_lapdm(lchan); + /* confirm */ + mph_info_chan_confirm(l1h, chan_nr, + PRIM_INFO_ACTIVATE, 0); + break; + } + if (l1sap->u.info.type == PRIM_INFO_MODIFY) { + l1if_rsl_mode_modify(l1h, lchan, 0); + l1if_rsl_mode_modify(l1h, lchan, 1); + break; + } + if ((chan_nr & 0x80)) { + LOGP(DL1C, LOGL_ERROR, "Cannot deactivate " + "chan_nr 0x%02x\n", chan_nr); + break; + } + /* deactivate dedicated channel */ + if (!l1sap->u.info.u.act_req.sacch_only) { + trx_sched_set_lchan(l1h, chan_nr, 0x00, 0, 0); + trx_sched_set_lchan(l1h, chan_nr, 0x00, 1, 0); + } + trx_sched_set_lchan(l1h, chan_nr, 0x40, 0, 0); + trx_sched_set_lchan(l1h, chan_nr, 0x40, 1, 0); + /* confirm */ + 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); + 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; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + /* activate tranceiver */ + rc = trx_init(obj); + break; + case NM_OC_CHANNEL: + /* configure timeslot */ + rc = 0; //ts_connect(obj); + + /* Set to Operational State: Enabled */ + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + + /* Send OPSTART ack */ + rc = oml_mo_opstart_ack(mo); + + break; + case NM_OC_BTS: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); + rc = oml_mo_opstart_ack(mo); + 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 trx_close(trx); +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + diff --git a/src/osmo-bts-trx/l1_if.h b/src/osmo-bts-trx/l1_if.h new file mode 100644 index 000000000..7e0f36887 --- /dev/null +++ b/src/osmo-bts-trx/l1_if.h @@ -0,0 +1,121 @@ +#ifndef L1_IF_H_TRX +#define L1_IF_H_TRX + +/* These types define the different channels on a multiframe. + * Each channel has queues and can be activated individually. + */ +enum trx_chan_type { + TRXC_IDLE = 0, + TRXC_FCCH, + TRXC_SCH, + TRXC_BCCH, + TRXC_RACH, + TRXC_CCCH, + TRXC_TCHF, + TRXC_TCHH_0, + TRXC_TCHH_1, + TRXC_SDCCH4_0, + TRXC_SDCCH4_1, + TRXC_SDCCH4_2, + TRXC_SDCCH4_3, + TRXC_SDCCH8_0, + TRXC_SDCCH8_1, + TRXC_SDCCH8_2, + TRXC_SDCCH8_3, + TRXC_SDCCH8_4, + TRXC_SDCCH8_5, + TRXC_SDCCH8_6, + TRXC_SDCCH8_7, + TRXC_SACCHTF, + TRXC_SACCHTH_0, + TRXC_SACCHTH_1, + TRXC_SACCH4_0, + TRXC_SACCH4_1, + TRXC_SACCH4_2, + TRXC_SACCH4_3, + TRXC_SACCH8_0, + TRXC_SACCH8_1, + TRXC_SACCH8_2, + TRXC_SACCH8_3, + TRXC_SACCH8_4, + TRXC_SACCH8_5, + TRXC_SACCH8_6, + TRXC_SACCH8_7, + TRXC_PDTCH, + TRXC_PTCCH, + _TRX_CHAN_MAX +}; + +/* States each channel on a multiframe */ +struct trx_chan_state { + uint8_t dl_active; /* Channel is active for TX */ + uint8_t ul_active; /* Channel is active for RX */ + ubit_t *dl_bursts; /* burst buffer for TX */ + sbit_t *ul_bursts; /* burst buffer for RX */ + uint32_t ul_first_fn; /* fn of first burst */ + uint8_t ul_mask; /* mask of received bursts */ + uint8_t sacch_lost; /* SACCH loss detection */ +}; + +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; + int rxgain; + int rxgain_sent; + + int power_valid; + int power; + int power_sent; + + int maxdly_valid; + int maxdly; + int maxdly_sent; + + uint8_t slotmask; + + int slottype_valid[8]; + uint8_t slottype[8]; + int slottype_sent[8]; +}; + +struct trx_l1h { + struct llist_head trx_ctrl_list; + + struct gsm_bts_trx *trx; + + struct osmo_fd trx_ofd_ctrl; + struct osmo_timer_list trx_ctrl_timer; + struct osmo_fd trx_ofd_data; + + /* tranceiver config */ + struct trx_config config; + + uint8_t mf_index[8]; /* selected multiframe index */ + + /* Channel states for all channels on all timeslots */ + struct trx_chan_state chan_states[8][_TRX_CHAN_MAX]; + struct llist_head dl_prims[8]; /* Queue primitves for TX */ +}; + +struct trx_l1h *l1if_open(struct gsm_bts_trx *trx); +void l1if_close(struct trx_l1h *l1h); +void l1if_reset(struct trx_l1h *l1h); +int l1if_provision_tranceiver_trx(struct trx_l1h *l1h); +int l1if_provision_tranceiver(struct gsm_bts *bts); +int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn); + +#endif /* L1_IF_H_TRX */ diff --git a/src/osmo-bts-trx/main.c b/src/osmo-bts-trx/main.c new file mode 100644 index 000000000..1b3359aad --- /dev/null +++ b/src/osmo-bts-trx/main.c @@ -0,0 +1,402 @@ +/* Main program for OsmoBTS-TRX */ + +/* (C) 2011 by Harald Welte + * (C) 2013 by Andreas Eversberg + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "trx_if.h" +#include "scheduler.h" + +const int pcu_direct = 0; + +int quit = 0; +static const char *config_file = "osmo-bts.cfg"; +static int daemonize = 0; +static char *gsmtap_ip = 0; +static int high_prio = 0; +static int trx_num = 1; +char *software_version = "0.0"; +uint8_t abis_mac[6] = { 0, 1, 2, 3, 4, 5 }; +char *bsc_host = "localhost"; +char *bts_id = "1801/0"; + +// FIXME this is a hack +static void get_mac(void) +{ + struct if_nameindex *ifn = if_nameindex(); + struct ifreq ifr; + int sock; + int ret; + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) + return; + + memset(&ifr, 0, sizeof(ifr)); + if (!ifn) + return; + while (ifn->if_name) { + strncpy(ifr.ifr_name, ifn->if_name, sizeof(ifr.ifr_name)-1); + ret = ioctl(sock, SIOCGIFHWADDR, &ifr); + if (ret == 0 && !!memcmp(ifr.ifr_hwaddr.sa_data, + "\0\0\0\0\0\0", 6)) { + memcpy(abis_mac, ifr.ifr_hwaddr.sa_data, 6); + printf("Using MAC address of %s: " + "'%02x:%02x:%02x:%02x:%02x:%02x'\n", + ifn->if_name, + abis_mac[0], abis_mac[1], abis_mac[2], + abis_mac[3], abis_mac[4], abis_mac[5]); + break; + } + ifn++; + } +// if_freenameindex(ifn); +} + +int bts_model_init(struct gsm_bts *bts) +{ + void *l1h; + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + l1h = l1if_open(trx); + if (!l1h) { + LOGP(DL1C, LOGL_FATAL, "Cannot open L1 Interface\n"); + goto error; + } + + trx->role_bts.l1h = l1h; + trx->nominal_power = 23; + + l1if_reset(l1h); + } + + bts_model_vty_init(bts); + + return 0; + +error: + llist_for_each_entry(trx, &bts->trx_list, list) { + l1h = trx->role_bts.l1h; + if (l1h) + l1if_close(l1h); + } + + return -EIO; +} + +/* dummy, since no direct dsp support */ +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + return 0; +} + +static void print_help() +{ + printf( "Some useful options:\n" + " -h --help this text\n" + " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n" + " -D --daemonize For the process into a background daemon\n" + " -c --config-file Specify the filename of the config file\n" + " -s --disable-color Don't use colors in stderr log output\n" + " -T --timestamp Prefix every log line with a timestamp\n" + " -V --version Print version information and exit\n" + " -e --log-level Set a global log-level\n" + " -t --trx-num Set number of TRX (default=%d)\n" + " -i --gsmtap-ip The destination IP used for GSMTAP.\n" + " -H --high-prio Set realtime scheduler with maximum prio\n" + " -I --tranceiver-ip Set IP of tranceiver (default=%s)\n" + ,trx_num, tranceiver_ip); +} + +/* FIXME: finally get some option parsing code into libosmocore */ +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + /* FIXME: all those are generic Osmocom app options */ + { "help", 0, 0, 'h' }, + { "debug", 1, 0, 'd' }, + { "daemonize", 0, 0, 'D' }, + { "config-file", 1, 0, 'c' }, + { "disable-color", 0, 0, 's' }, + { "timestamp", 0, 0, 'T' }, + { "version", 0, 0, 'V' }, + { "log-level", 1, 0, 'e' }, + { "trx-num", 1, 0, 't' }, + { "gsmtap-ip", 1, 0, 'i' }, + { "high-prio", 0, 0, 'H' }, + { "tranceiver-ip", 1, 0, 'I' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "hc:d:Dc:sTVe:t:i:HI:", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + break; + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'd': + log_parse_category_mask(osmo_stderr_target, optarg); + break; + case 'D': + daemonize = 1; + break; + case 'c': + config_file = strdup(optarg); + break; + case 'T': + log_set_print_timestamp(osmo_stderr_target, 1); + break; + case 'V': + print_version(1); + exit(0); + break; + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 't': + trx_num = atoi(optarg); + if (trx_num < 1) + trx_num = 1; + break; + case 'i': + gsmtap_ip = optarg; + break; + case 'H': + high_prio = 1; + break; + case 'I': + tranceiver_ip = strdup(optarg); + break; + default: + break; + } + } +} + +static struct gsm_bts *bts; + +static void signal_handler(int signal) +{ + fprintf(stderr, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + //osmo_signal_dispatch(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); + if (!quit) + bts_shutdown(bts, "SIGINT"); + quit++; + break; + case SIGABRT: + case SIGUSR1: + case SIGUSR2: + talloc_report_full(tall_bts_ctx, stderr); + break; + default: + break; + } +} + +static int write_pid_file(char *procname) +{ + FILE *outf; + char tmp[PATH_MAX+1]; + + snprintf(tmp, sizeof(tmp)-1, "/var/run/%s.pid", procname); + tmp[PATH_MAX-1] = '\0'; + + outf = fopen(tmp, "w"); + if (!outf) + return -1; + + fprintf(outf, "%d\n", getpid()); + + fclose(outf); + + return 0; +} + +int main(int argc, char **argv) +{ + struct gsm_bts_role_bts *btsb; + struct gsm_bts_trx *trx; + struct e1inp_line *line; + void *tall_msgb_ctx; + int rc, i; + + printf("((*))\n |\n / \\ OsmoBTS\n"); + + get_mac(); + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb"); + msgb_set_talloc_ctx(tall_msgb_ctx); + + bts_log_init(NULL); + + handle_options(argc, argv); + + bts = gsm_bts_alloc(tall_bts_ctx); + if (!bts) { + fprintf(stderr, "Failed to create BTS structure\n"); + exit(1); + } + for (i = 1; i < trx_num; i++) { + trx = gsm_bts_trx_alloc(bts); + if (!trx) { + fprintf(stderr, "Failed to TRX structure\n"); + exit(1); + } + } + + vty_init(&bts_vty_info); + e1inp_vty_init(); + bts_vty_init(bts, &bts_log_info); + + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to to open bts\n"); + exit(1); + } + btsb = bts_role_bts(bts); + btsb->support.ciphers = 0; // CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + if (gsmtap_ip) { + gsmtap = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); + if (!gsmtap) { + fprintf(stderr, "Failed during gsmtap_init()\n"); + exit(1); + } + gsmtap_source_add_sink(gsmtap); + } + + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", + config_file); + exit(1); + } + + write_pid_file("osmo-bts"); + + rc = telnet_init(tall_bts_ctx, NULL, 4241); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + if (pcu_sock_init()) { + fprintf(stderr, "PCU L1 socket failed\n"); + exit(-1); + } + + signal(SIGINT, &signal_handler); + //signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + if (!btsb->bsc_oml_host) { + fprintf(stderr, "Cannot start BTS without knowing BSC OML IP\n"); + exit(1); + } + + line = abis_open(bts, btsb->bsc_oml_host, "sysmoBTS"); + if (!line) { + fprintf(stderr, "unable to connect to BSC\n"); + exit(1); + } + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + if (high_prio) { + struct sched_param schedp; + + /* high priority scheduling required for handling bursts */ + rc = sched_get_priority_max(SCHED_RR); + memset(&schedp, 0, sizeof(schedp)); + schedp.sched_priority = rc; + rc = sched_setscheduler(0, SCHED_RR, &schedp); + if (rc) { + fprintf(stderr, "Error setting scheduler\n"); + } + } + + while (quit < 2) { + log_reset_context(); + osmo_select_main(0); + } + +#if 0 + telnet_exit(); + + talloc_report_full(tall_bts_ctx, stderr); +#endif + + return 0; +} + diff --git a/src/osmo-bts-trx/pxxch.c b/src/osmo-bts-trx/pxxch.c new file mode 100644 index 000000000..17a7c9474 --- /dev/null +++ b/src/osmo-bts-trx/pxxch.c @@ -0,0 +1,473 @@ +/* + * pxxch.c + * + * Copyright (c) 2013 Andreas Eversberg + */ + +#include +#include +#include + +#include +#include +#include + +#include "pxxch.h" + + +/* + * GSM PDTCH parity (FIRE code) + * + * g(x) = (x^23 + 1)(x^17 + x^3 + 1) + * = x^40 + x^26 + x^23 + x^17 + x^3 + 1 + */ + +const struct osmo_crc64gen_code pxxch_crc40 = { + .bits = 40, + .poly = 0x0004820009ULL, + .init = 0x0000000000ULL, + .remainder = 0xffffffffffULL, +}; + + +/* + * GSM PDTCH CS-2, CS-3 parity + * + * g(x) = x^16 + x^12 + x^5 + 1 + */ + +const struct osmo_crc16gen_code pdtch_crc16 = { + .bits = 16, + .poly = 0x1021, + .init = 0x0000, + .remainder = 0xffff, +}; + + +/* + * GSM PDTCH convolutional coding + * + * G_0 = 1 + x^3 + x^4 + * G_1 = 1 + x + x^3 + x^4 + */ + +static const uint8_t conv_cs1_next_output[][2] = { + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, +}; + +static const uint8_t conv_cs1_next_state[][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + +static const struct osmo_conv_code conv_cs1 = { + .N = 2, + .K = 5, + .len = 224, + .next_output = conv_cs1_next_output, + .next_state = conv_cs1_next_state, +}; + +static const uint8_t conv_cs2_next_output[][2] = { + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, +}; + +static const uint8_t conv_cs2_next_state[][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + +static const struct osmo_conv_code conv_cs2 = { + .N = 2, + .K = 5, + .len = 290, + .next_output = conv_cs2_next_output, + .next_state = conv_cs2_next_state, +}; + +static const uint8_t conv_cs3_next_output[][2] = { + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, +}; + +static const uint8_t conv_cs3_next_state[][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + +static const struct osmo_conv_code conv_cs3 = { + .N = 2, + .K = 5, + .len = 334, + .next_output = conv_cs3_next_output, + .next_state = conv_cs3_next_state, +}; + + +/* + * GSM PxxCH interleaving and burst mapping + * + * Interleaving: + * + * Given 456 coded input bits, form 4 blocks of 114 bits: + * + * i(B, j) = c(n, k) k = 0, ..., 455 + * n = 0, ..., N, N + 1, ... + * B = B_0 + 4n + (k mod 4) + * j = 2(49k mod 57) + ((k mod 8) div 4) + * + * Mapping on Burst: + * + * e(B, j) = i(B, j) + * e(B, 59 + j) = i(B, 57 + j) j = 0, ..., 56 + * e(B, 57) = h_l(B) + * e(B, 58) = h_n(B) + * + * Where hl(B) and hn(B) are bits in burst B indicating flags. + */ + +static void +pxxch_deinterleave(sbit_t *cB, sbit_t *iB) +{ + int j, k, B; + + for (k=0; k<456; k++) { + B = k & 3; + j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); + cB[k] = iB[B * 114 + j]; + } +} + +static void +pxxch_interleave(ubit_t *cB, ubit_t *iB) +{ + int j, k, B; + + for (k=0; k<456; k++) { + B = k & 3; + j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); + iB[B * 114 + j] = cB[k]; + } +} + +static void +pxxch_burst_unmap(sbit_t *iB, sbit_t *eB, sbit_t *hl, sbit_t *hn) +{ + memcpy(iB, eB, 57); + memcpy(iB+57, eB+59, 57); + + if (hl) + *hl = eB[57]; + + if (hn) + *hn = eB[58]; +} + +static void +pxxch_burst_map(ubit_t *iB, ubit_t *eB, ubit_t *hl, ubit_t *hn) +{ + memcpy(eB, iB, 57); + memcpy(eB+59, iB+57, 57); + + if (hl) + eB[57] = *hl; + if (hn) + eB[58] = *hn; +} + +static ubit_t pdtch_hl_hn[4][8] = { + { 1,1, 1,1, 1,1, 1,1 }, + { 1,1, 0,0, 1,0, 0,0 }, + { 0,0, 1,0, 0,0, 0,1 }, + { 0,0, 0,1, 0,1, 1,0 }, +}; + +static ubit_t usf2six[8][6] = { + { 0,0,0, 0,0,0 }, + { 0,0,1, 0,1,1 }, + { 0,1,0, 1,1,0 }, + { 0,1,1, 1,0,1 }, + { 1,0,0, 1,0,1 }, + { 1,0,1, 1,1,0 }, + { 1,1,0, 0,1,1 }, + { 1,1,1, 0,0,0 }, +}; + +static ubit_t usf2twelve[8][12] = { + { 0,0,0, 0,0,0, 0,0,0, 0,0,0 }, + { 0,0,0, 0,1,1, 0,1,1, 1,0,1 }, + { 0,0,1, 1,0,1, 1,1,0, 1,1,0 }, + { 0,0,1, 1,1,0, 1,0,1, 0,1,1 }, + { 1,1,0, 1,0,0, 0,0,1, 0,1,1 }, + { 1,1,0, 1,1,1, 0,1,0, 1,1,0 }, + { 1,1,1, 0,0,1, 1,1,1, 1,0,1 }, + { 1,1,1, 0,1,0, 1,0,0, 0,0,0 }, +}; + +static uint8_t puncture_cs2[588] = { + 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1, 0,0,0,1, + 0,0,0,1, 0,0,0,1, 0,0,0,1 +}; + +static uint8_t puncture_cs3[676] = { + 0,0,0,0,0,0, 0,0,0,0,0,0, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,1,0,1, + 0,0,0,1,0,1, 0,0,0,1,0,1, 0,0,0,0 +}; + +int +pdch_decode(uint8_t *l2_data, sbit_t *bursts, uint8_t *usf_p) +{ + sbit_t iB[456], cB[676], hl_hn[8]; + ubit_t conv[456]; + int i, j, k, rv, best, cs, usf; + + for (i=0; i<4; i++) + pxxch_burst_unmap(&iB[i * 114], &bursts[i * 116], hl_hn + i*2, + hl_hn + i*2 + 1); + + for (i=0, best=0, cs=1; i<4; j++) { + for (j=0, k=0; j<4; j++) { + if (pdtch_hl_hn[i][j] == hl_hn[j]) + k++; + } + if (k > best) + cs = i+1; + } + + pxxch_deinterleave(cB, iB); + + switch (cs) { + case 1: + osmo_conv_decode(&conv_cs1, cB, conv); + + rv = osmo_crc64gen_check_bits(&pxxch_crc40, conv, 184, + conv+184); + if (rv) + return -1; + + osmo_ubit2pbit_ext(l2_data, 0, conv, 0, 184, 1); + + return 23; + case 2: + for (i=587, j=455; i>=0; i++) + if (!puncture_cs2[i]) + cB[i] = cB[j--]; + else + cB[i] = 0; + + osmo_conv_decode(&conv_cs2, cB, conv); + + for (i=0, best=0, usf=0; i<8; j++) { + for (j=0, k=0; j<6; j++) { + if (usf2six[i][j] == conv[j]) + k++; + } + if (k > best) + usf = i; + } + + conv[3] = (usf >> 2) & 1; + conv[4] = (usf >> 1) & 1; + conv[5] = usf & 1; + if (usf_p) + *usf_p = usf; + + rv = osmo_crc64gen_check_bits(&pxxch_crc40, conv+3, 271, + conv+3+271); + if (rv) + return -1; + + osmo_ubit2pbit_ext(l2_data, 0, conv, 0, 271, 1); + + return 34; + case 3: + for (i=675, j=455; i>=0; i++) + if (!puncture_cs3[i]) + cB[i] = cB[j--]; + else + cB[i] = 0; + + osmo_conv_decode(&conv_cs3, cB, conv); + + for (i=0, best=0, usf=0; i<8; j++) { + for (j=0, k=0; j<6; j++) { + if (usf2six[i][j] == conv[j]) + k++; + } + if (k > best) + usf = i; + } + + conv[3] = (usf >> 2) & 1; + conv[4] = (usf >> 1) & 1; + conv[5] = usf & 1; + if (usf_p) + *usf_p = usf; + + rv = osmo_crc64gen_check_bits(&pxxch_crc40, conv+3, 315, + conv+3+315); + if (rv) + return -1; + + osmo_ubit2pbit_ext(l2_data, 0, conv, 0, 315, 1); + + return 40; + case 4: + for (i=0; i<456;i++) + conv[i] = (cB[i] < 0) ? 1:0; + + for (i=0, best=0, usf=0; i<8; j++) { + for (j=0, k=0; j<12; j++) { + if (usf2twelve[i][j] == conv[j]) + k++; + } + if (k > best) + usf = i; + } + + conv[9] = (usf >> 2) & 1; + conv[10] = (usf >> 1) & 1; + conv[11] = usf & 1; + if (usf_p) + *usf_p = usf; + + rv = osmo_crc64gen_check_bits(&pxxch_crc40, conv+9, 431, + conv+9+431); + if (rv) + return -1; + + osmo_ubit2pbit_ext(l2_data, 0, conv, 0, 431, 1); + + return 54; + } + + return -1; +} + +int +pdtch_encode(ubit_t *bursts, uint8_t *l2_data, uint8_t l2_len) +{ + ubit_t iB[456], cB[676], *hl_hn; + ubit_t conv[334]; + int i, j, usf; + + switch (l2_len) { + case 23: + osmo_pbit2ubit_ext(conv, 0, l2_data, 0, 184, 1); + + osmo_crc64gen_set_bits(&pxxch_crc40, conv, 184, conv+184); + + osmo_conv_encode(&conv_cs1, conv, cB); + + hl_hn = pdtch_hl_hn[0]; + + break; + case 34: + osmo_pbit2ubit_ext(conv, 3, l2_data, 0, 271, 1); + usf = (conv[3] << 2) | (conv[4] << 1) | conv[5]; + + osmo_crc16gen_set_bits(&pdtch_crc16, conv+3, 271, conv+3+271); + + memcpy(conv, usf2six[usf], 6); + + osmo_conv_encode(&conv_cs2, conv, cB); + + for (i=0, j=0; i<588; i++) + if (!puncture_cs2[i]) + cB[j++] = cB[i]; + + hl_hn = pdtch_hl_hn[1]; + + break; + case 40: + osmo_pbit2ubit_ext(conv, 3, l2_data, 0, 315, 1); + usf = (conv[3] << 2) | (conv[4] << 1) | conv[5]; + + osmo_crc16gen_set_bits(&pdtch_crc16, conv+3, 315, conv+3+315); + + memcpy(conv, usf2six[usf], 6); + + osmo_conv_encode(&conv_cs3, conv, cB); + + for (i=0, j=0; i<676; i++) + if (!puncture_cs3[i]) + cB[j++] = cB[i]; + + hl_hn = pdtch_hl_hn[2]; + + break; + case 54: + osmo_pbit2ubit_ext(cB, 9, l2_data, 0, 431, 1); + usf = (cB[9] << 2) | (cB[10] << 1) | conv[11]; + + osmo_crc16gen_set_bits(&pdtch_crc16, cB+9, 431, cB+9+431); + + memcpy(cB, usf2twelve[usf], 12); + + hl_hn = pdtch_hl_hn[3]; + + break; + default: + return -1; + } + + pxxch_interleave(cB, iB); + + for (i=0; i<4; i++) + pxxch_burst_map(&iB[i * 114], &bursts[i * 116], hl_hn + i*2, + hl_hn + i*2 + 1); + + return 0; +} diff --git a/src/osmo-bts-trx/pxxch.h b/src/osmo-bts-trx/pxxch.h new file mode 100644 index 000000000..313fbf59a --- /dev/null +++ b/src/osmo-bts-trx/pxxch.h @@ -0,0 +1,7 @@ +#ifndef _PXXCH_H +#define _PXXCH_H + +int pdch_decode(uint8_t *l2_data, sbit_t *bursts, uint8_t *usf_p); +int pdtch_encode(ubit_t *bursts, uint8_t *l2_data, uint8_t l2_len); + +#endif /* _PXXCH_H */ diff --git a/src/osmo-bts-trx/rach.c b/src/osmo-bts-trx/rach.c new file mode 100644 index 000000000..670a4d1da --- /dev/null +++ b/src/osmo-bts-trx/rach.c @@ -0,0 +1,118 @@ +/* + * conv_rach.c + * + * Convolutional code tables for RACH channels + * + * Copyright (C) 2011 Sylvain Munaut + */ + + +#include +#include +#include + +#include +#include +#include + +#include "rach.h" + + +/* + * GSM RACH parity + * + * g(x) = x^6 + x^5 + x^3 + x^2 + x^1 + 1 + */ + +static const struct osmo_crc8gen_code rach_crc6 = { + .bits = 6, + .poly = 0x2f, + .init = 0x00, + .remainder = 0x3f, +}; + + +/* + * GSM RACH convolutional coding + * + * G_0 = 1 + x^3 + x^4 + * G_1 = 1 + x + x^3 + x^4 + */ + +static const uint8_t conv_rach_next_output[][2] = { + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, +}; + +static const uint8_t conv_rach_next_state[][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + +const struct osmo_conv_code conv_rach = { + .N = 2, + .K = 5, + .len = 14, + .next_output = conv_rach_next_output, + .next_state = conv_rach_next_state, +}; + + +/* + * GSM RACH apply BSIC to parity + * + * p(j) = p(j) xor b(j) j = 0, ..., 5 + * b(0) = MSB of PLMN colour code + * b(5) = LSB of BS colour code + */ + +static int +rach_apply_bsic(ubit_t *d, uint8_t bsic) +{ + int i; + + /* Apply it */ + for (i=0; i<6; i++) + d[8+i] ^= ((bsic >> (5-i)) & 1); + + return 0; +} + +int +rach_decode(uint8_t *ra, sbit_t *burst, uint8_t bsic) +{ + ubit_t conv[14]; + int rv; + + osmo_conv_decode(&conv_rach, burst, conv); + + rach_apply_bsic(conv, bsic); + + rv = osmo_crc8gen_check_bits(&rach_crc6, conv, 8, conv+8); + if (rv) + return -1; + + osmo_ubit2pbit_ext(ra, 0, conv, 0, 8, 1); + + return 0; +} + +int +rach_encode(ubit_t *burst, uint8_t *ra, uint8_t bsic) +{ + ubit_t conv[14]; + + osmo_pbit2ubit_ext(conv, 0, ra, 0, 8, 1); + + osmo_crc8gen_set_bits(&rach_crc6, conv, 8, conv+8); + + rach_apply_bsic(conv, bsic); + + osmo_conv_encode(&conv_rach, conv, burst); + + return 0; +} diff --git a/src/osmo-bts-trx/rach.h b/src/osmo-bts-trx/rach.h new file mode 100644 index 000000000..155555939 --- /dev/null +++ b/src/osmo-bts-trx/rach.h @@ -0,0 +1,7 @@ +#ifndef _RACH_H +#define _RACH_H + +int rach_decode(uint8_t *ra, sbit_t *burst, uint8_t bsic); +int rach_encode(ubit_t *burst, uint8_t *ra, uint8_t bsic); + +#endif /* _RACH_H */ diff --git a/src/osmo-bts-trx/sch.c b/src/osmo-bts-trx/sch.c new file mode 100644 index 000000000..e0d6e8dbf --- /dev/null +++ b/src/osmo-bts-trx/sch.c @@ -0,0 +1,89 @@ +/* + */ + + +#include +#include +#include + +#include +#include +#include + +#include "sch.h" + + +/* + * GSM SCH parity + * + * g(x) = x^10 + x^8 + x^6 + x^5 + x^4 + x^2 + 1 + */ + +const struct osmo_crc16gen_code sch_crc10 = { + .bits = 10, + .poly = 0x175, + .init = 0x000, + .remainder = 0x3ff, +}; + + +/* + * GSM SCH convolutional coding + * + * G_0 = 1 + x^3 + x^4 + * G_1 = 1 + x + x^3 + x^4 + */ + +static const uint8_t conv_rach_next_output[][2] = { + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, +}; + +static const uint8_t conv_rach_next_state[][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + +static const struct osmo_conv_code conv_sch = { + .N = 2, + .K = 5, + .len = 35, + .next_output = conv_rach_next_output, + .next_state = conv_rach_next_state, +}; + + +int +sch_decode(uint8_t *sb_info, sbit_t *burst) +{ + ubit_t conv[35]; + int rv; + + osmo_conv_decode(&conv_sch, burst, conv); + + rv = osmo_crc16gen_check_bits(&sch_crc10, conv, 25, conv+25); + if (rv) + return -1; + + osmo_ubit2pbit_ext(sb_info, 0, conv, 0, 25, 1); + + return 0; +} + +int +sch_encode(ubit_t *burst, uint8_t *sb_info) +{ + ubit_t conv[35]; + + osmo_pbit2ubit_ext(conv, 0, sb_info, 0, 25, 1); + + osmo_crc16gen_set_bits(&sch_crc10, conv, 25, conv+25); + + osmo_conv_encode(&conv_sch, conv, burst); + + return 0; +} diff --git a/src/osmo-bts-trx/sch.h b/src/osmo-bts-trx/sch.h new file mode 100644 index 000000000..8e60191b7 --- /dev/null +++ b/src/osmo-bts-trx/sch.h @@ -0,0 +1,7 @@ +#ifndef _SCH_H +#define _SCH_H + +int sch_decode(uint8_t *sb_info, sbit_t *burst); +int sch_encode(ubit_t *burst, uint8_t *sb_info); + +#endif /* _SCH_H */ diff --git a/src/osmo-bts-trx/scheduler.c b/src/osmo-bts-trx/scheduler.c new file mode 100644 index 000000000..a0caeecaf --- /dev/null +++ b/src/osmo-bts-trx/scheduler.c @@ -0,0 +1,1987 @@ +/* Scheduler for OsmoBTS-TRX */ + +/* (C) 2013 by Andreas Eversberg + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "l1_if.h" +#include "scheduler.h" +#include "xcch.h" +#include "tch_fr.h" +#include "rach.h" +#include "sch.h" +#include "pxxch.h" +#include "trx_if.h" + +void *tall_bts_ctx; + +static struct gsm_bts *bts; + +/* clock states */ +static uint32_t tranceiver_lost; +uint32_t tranceiver_last_fn; +static struct timeval tranceiver_clock_tv; +static struct osmo_timer_list tranceiver_clock_timer; + +/* clock advance for the tranceiver */ +uint32_t trx_clock_advance = 10; + +/* advance RTS to give some time for data processing. (especially PCU) */ +uint32_t trx_rts_advance = 5; /* about 20ms */ + +typedef int trx_sched_rts_func(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan); +typedef const ubit_t *trx_sched_dl_func(struct trx_l1h *l1h, uint8_t tn, + uint32_t fn, enum trx_chan_type chan, uint8_t bid); +typedef int trx_sched_ul_func(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa); + +static int rts_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan); +static int rts_tch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan); +static const ubit_t *tx_idle_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +static const ubit_t *tx_fcch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +static const ubit_t *tx_sch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +static const ubit_t *tx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +static const ubit_t *tx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +static const ubit_t *tx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +static const ubit_t *tx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid); +static int rx_rach_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa); +static int rx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa); +static int rx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa); +static int rx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa); +static int rx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa); + +static const ubit_t dummy_burst[148] = { + 0,0,0, + 1,1,1,1,1,0,1,1,0,1,1,1,0,1,1,0,0,0,0,0,1,0,1,0,0,1,0,0,1,1,1,0, + 0,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,1,1,0,0, + 0,1,0,1,1,1,0,0,0,1,0,1,1,1,0,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0, + 0,0,1,1,0,0,1,1,0,0,1,1,1,0,0,1,1,1,1,0,1,0,0,1,1,1,1,1,0,0,0,1, + 0,0,1,0,1,1,1,1,1,0,1,0,1,0, + 0,0,0, +}; + +static const ubit_t fcch_burst[148] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + +static const ubit_t tsc[8][26] = { + { 0,0,1,0,0,1,0,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,1,1, }, + { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,0,0,1,0,1,1,0,1,1,1, }, + { 0,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,0,1,1,1,0, }, + { 0,1,0,0,0,1,1,1,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,0, }, + { 0,0,0,1,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1, }, + { 0,1,0,0,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,1,1,1,0,1,0, }, + { 1,0,1,0,0,1,1,1,1,1,0,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1, }, + { 1,1,1,0,1,1,1,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,1,0,0, }, +}; + +static const ubit_t sch_train[64] = { + 1,0,1,1,1,0,0,1,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,1,1, + 0,0,1,0,1,1,0,1,0,1,0,0,0,1,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,0,1,1, +}; + +/* + * subchannel description structure + */ + +struct trx_chan_desc { + enum trx_chan_type chan; + uint8_t chan_nr; + uint8_t link_id; + const char *name; + trx_sched_rts_func *rts_fn; + trx_sched_dl_func *dl_fn; + trx_sched_ul_func *ul_fn; + int auto_active; +}; +struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { + { TRXC_IDLE, 0, 0, "IDLE", NULL, tx_idle_fn, NULL, 1 }, + { TRXC_FCCH, 0, 0, "FCCH", NULL, tx_fcch_fn, NULL, 1 }, + { TRXC_SCH, 0, 0, "SCH", NULL, tx_sch_fn, NULL, 1 }, + { TRXC_BCCH, 0x80, 0x00, "BCCH", rts_data_fn, tx_data_fn, NULL, 1 }, + { TRXC_RACH, 0x88, 0x00, "RACH", NULL, NULL, rx_rach_fn, 1 }, + { TRXC_CCCH, 0x90, 0x00, "CCCH", rts_data_fn, tx_data_fn, NULL, 1 }, + { TRXC_TCHF, 0x08, 0x00, "TCH/F", rts_tch_fn, tx_tchf_fn, rx_tchf_fn, 0 }, + { TRXC_TCHH_0, 0x10, 0x00, "TCH/H(0)", rts_tch_fn, tx_tchh_fn, rx_tchh_fn, 0 }, + { TRXC_TCHH_1, 0x18, 0x00, "TCH/H(1)", rts_tch_fn, tx_tchh_fn, rx_tchh_fn, 0 }, + { TRXC_SDCCH4_0, 0x20, 0x00, "SDCCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH4_1, 0x28, 0x00, "SDCCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH4_2, 0x30, 0x00, "SDCCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH4_3, 0x38, 0x00, "SDCCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH8_0, 0x40, 0x00, "SDCCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH8_1, 0x48, 0x00, "SDCCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH8_2, 0x50, 0x00, "SDCCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH8_3, 0x58, 0x00, "SDCCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH8_4, 0x60, 0x00, "SDCCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH8_5, 0x68, 0x00, "SDCCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH8_6, 0x70, 0x00, "SDCCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SDCCH8_7, 0x78, 0x00, "SDCCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCHTF, 0x08, 0x40, "SACCH/TF", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCHTH_0, 0x10, 0x40, "SACCH/TH(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCHTH_1, 0x18, 0x40, "SACCH/TH(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH4_0, 0x20, 0x40, "SACCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH4_1, 0x28, 0x40, "SACCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH4_2, 0x30, 0x40, "SACCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH4_3, 0x38, 0x40, "SACCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH8_0, 0x40, 0x40, "SACCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH8_1, 0x48, 0x40, "SACCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH8_2, 0x50, 0x40, "SACCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH8_3, 0x58, 0x40, "SACCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH8_4, 0x60, 0x40, "SACCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH8_5, 0x68, 0x40, "SACCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH8_6, 0x70, 0x40, "SACCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_SACCH8_7, 0x68, 0x40, "SACCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { TRXC_PDTCH, 0x08, 0x00, "PDTCH", rts_data_fn, tx_pdtch_fn, rx_pdtch_fn, 0 }, + { TRXC_PTCCH, 0x08, 0x00, "PTCCH", rts_data_fn, tx_pdtch_fn, rx_pdtch_fn, 0 }, +}; + + +/* + * init / exit + */ + +int trx_sched_init(struct trx_l1h *l1h) +{ + uint8_t tn; + int i; + struct trx_chan_state *chan_state; + + LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1h->trx->nr); + + /* hack to get bts */ + bts = l1h->trx->bts; + + for (tn = 0; tn < 8; tn++) { + l1h->mf_index[tn] = 0; + for (i = 0; i < _TRX_CHAN_MAX; i++) { + INIT_LLIST_HEAD(&l1h->dl_prims[tn]); + chan_state = &l1h->chan_states[tn][i]; + chan_state->ul_mask = 0x0; + } + } + + return 0; +} + +void trx_sched_exit(struct trx_l1h *l1h) +{ + uint8_t tn; + int i; + struct trx_chan_state *chan_state; + + LOGP(DL1C, LOGL_NOTICE, "Exit scheduler for trx=%u\n", l1h->trx->nr); + + for (tn = 0; tn < 8; tn++) { + for (i = 0; i < _TRX_CHAN_MAX; i++) { + msgb_queue_flush(&l1h->dl_prims[tn]); + chan_state = &l1h->chan_states[tn][i]; + if (chan_state->dl_bursts) { + talloc_free(chan_state->dl_bursts); + chan_state->dl_bursts = NULL; + } + if (chan_state->ul_bursts) { + talloc_free(chan_state->ul_bursts); + chan_state->ul_bursts = NULL; + } + } + } +} + + +/* + * data request (from upper layer) + */ + +int trx_sched_ph_data_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap) +{ + uint8_t tn = l1sap->u.data.chan_nr & 7; + + LOGP(DL1C, LOGL_INFO, "PH-DATA.req: chan_nr=0x%02x link_id=0x%02x " + "fn=%u ts=%u trx=%u\n", l1sap->u.data.chan_nr, + l1sap->u.data.link_id, l1sap->u.data.fn, tn, l1h->trx->nr); + + if (!l1sap->oph.msg) + abort(); + + msgb_enqueue(&l1h->dl_prims[tn], l1sap->oph.msg); + + return 0; +} + +int trx_sched_tch_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap) +{ + uint8_t tn = l1sap->u.tch.chan_nr & 7; + + LOGP(DL1C, LOGL_INFO, "TCH.req: chan_nr=0x%02x " + "fn=%u ts=%u trx=%u\n", l1sap->u.tch.chan_nr, + l1sap->u.tch.fn, tn, l1h->trx->nr); + + if (!l1sap->oph.msg) + abort(); + + msgb_enqueue(&l1h->dl_prims[tn], l1sap->oph.msg); + + return 0; +} + + +/* + * ready-to-send indication (to upper layer) + */ + +/* RTS for data frame */ +static int rts_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan) +{ + uint8_t chan_nr, link_id; + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + + /* get data for RTS indication */ + chan_nr = trx_chan_desc[chan].chan_nr | tn; + link_id = trx_chan_desc[chan].link_id; + + if (!chan_nr) { + LOGP(DL1C, LOGL_FATAL, "RTS func for %s with non-existing " + "chan_nr %d\n", trx_chan_desc[chan].name, chan_nr); + return -ENODEV; + } + + LOGP(DL1C, LOGL_INFO, "PH-RTS.ind: chan=%s chan_nr=0x%02x " + "link_id=0x%02x fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, + chan_nr, link_id, fn, tn, l1h->trx->nr); + + /* generate prim */ + msg = l1sap_msgb_alloc(200); + if (!msg) + return -ENOMEM; + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, msg); + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.link_id = link_id; + l1sap->u.data.fn = fn; + + return l1sap_up(l1h->trx, l1sap); +} + +/* RTS for traffic frame */ +static int rts_tch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan) +{ + uint8_t chan_nr, link_id; + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + + /* get data for RTS indication */ + chan_nr = trx_chan_desc[chan].chan_nr | tn; + link_id = trx_chan_desc[chan].link_id; + + if (!chan_nr) { + LOGP(DL1C, LOGL_FATAL, "RTS func for %s with non-existing " + "chan_nr %d\n", trx_chan_desc[chan].name, chan_nr); + return -ENODEV; + } + + LOGP(DL1C, LOGL_INFO, "TCH.ind: chan=%s chan_nr=0x%02x " + "fn=%u ts=%u trx=%u\n", trx_chan_desc[chan].name, + chan_nr, fn, tn, l1h->trx->nr); + + /* generate prim */ + msg = l1sap_msgb_alloc(200); + if (!msg) + return -ENOMEM; + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, + PRIM_OP_INDICATION, msg); + l1sap->u.tch.chan_nr = chan_nr | tn; + l1sap->u.tch.fn = fn; + + l1sap_up(l1h->trx, l1sap); + + /* generate prim */ + msg = l1sap_msgb_alloc(200); + if (!msg) + return -ENOMEM; + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, msg); + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.chan_nr = link_id; + l1sap->u.data.fn = fn; + + return l1sap_up(l1h->trx, l1sap); +} + + +/* + * TX on donlink + */ + +/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ +static const ubit_t *tx_idle_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr); + + return NULL; +} + +static const ubit_t *tx_fcch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr); + + return fcch_burst; +} + +static const ubit_t *tx_sch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + static ubit_t bits[148], burst[78]; + uint8_t sb_info[4]; + struct gsm_time t; + uint8_t t3p, bsic; + + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr); + + /* create SB info from GSM time and BSIC */ + gsm_fn2gsmtime(&t, fn); + t3p = t.t3 / 10; + bsic = l1h->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[2] = + (t3p & 0x1); + + /* encode bursts */ + sch_encode(burst, sb_info); + + /* compose burst */ + memset(bits, 0, 3); + memcpy(bits + 3, burst, 39); + memcpy(bits + 42, sch_train, 64); + memcpy(bits + 106, burst + 39, 39); + memset(bits + 145, 0, 3); + + return bits; +} + +static struct msgb *dequeue_prim(struct trx_l1h *l1h, int8_t tn,uint32_t fn, + enum trx_chan_type chan) +{ + struct msgb *found = NULL, *msg, *msg2; /* make GCC happy */ + struct osmo_phsap_prim *l1sap = NULL; /* make GCC happy */ + uint32_t check_fn; + + /* get burst from queue */ + llist_for_each_entry_safe(msg, msg2, &l1h->dl_prims[tn], list) { + l1sap = msgb_l1sap_prim(msg); + check_fn = ((l1sap->u.data.fn + 2715648 - fn) % 2715648); + if (check_fn > 20) { + LOGP(DL1C, LOGL_ERROR, "Prim for trx=%u ts=%u at fn=%u " + "is out of range. (current fn=%u)\n", + l1h->trx->nr, tn, l1sap->u.data.fn, fn); + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + continue; + } + if (check_fn > 0) + continue; + + /* found second message, check if we have TCH+FACCH */ + if (found) { + /* we found a message earlier */ + l1sap = msgb_l1sap_prim(found); + /* if we have TCH+something */ + if (l1sap->oph.primitive == PRIM_TCH) { + /* unlink and free message */ + llist_del(&found->list); + msgb_free(found); + found = msg; + } else { + l1sap = msgb_l1sap_prim(msg); + /* if we have TCH+something */ + if (l1sap->oph.primitive == PRIM_TCH) { + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + } + } + break; + } + found = msg; + } + + if (!found) { + LOGP(DL1C, LOGL_NOTICE, "%s has not been served !! No prim for " + "trx=%u ts=%u at fn=%u to transmit.\n", + trx_chan_desc[chan].name, l1h->trx->nr, tn, fn); + return NULL; + } + + l1sap = msgb_l1sap_prim(found); + + if ((l1sap->oph.primitive != PRIM_PH_DATA + && l1sap->oph.primitive != PRIM_TCH) + || l1sap->oph.operation != PRIM_OP_REQUEST) { + LOGP(DL1C, LOGL_ERROR, "Prim for ts=%u at fn=%u has wrong " + "type.\n", tn, fn); +free_msg: + /* unlink and free message */ + llist_del(&found->list); + msgb_free(found); + return NULL; + } + if ((l1sap->u.data.chan_nr ^ (trx_chan_desc[chan].chan_nr | tn)) + || ((l1sap->u.data.link_id ^ trx_chan_desc[chan].link_id) & 0x40)) { + LOGP(DL1C, LOGL_ERROR, "Prim for ts=%u at fn=%u has wrong " + "chan_nr=%02x link_id=%02x, expecting chan_nr=%02x " + "link_id=%02x.\n", tn, fn, l1sap->u.data.chan_nr, + l1sap->u.data.link_id, trx_chan_desc[chan].chan_nr | tn, + trx_chan_desc[chan].link_id); + goto free_msg; + } + + return found; +} + +static int compose_ph_data_ind(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *l2, uint8_t l2_len); + +static const ubit_t *tx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + struct msgb *msg = NULL; /* make GCC happy */ + ubit_t *burst, **bursts_p = &l1h->chan_states[tn][chan].dl_bursts; + static ubit_t bits[148]; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + /* get burst from queue */ + msg = dequeue_prim(l1h, tn, fn, chan); + if (msg) + goto got_msg; + +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) != 23) { + LOGP(DL1C, LOGL_FATAL, "Prim not 23 bytes, please FIX! " + "(len=%d)\n", msgb_l2len(msg)); + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + goto no_msg; + } + + /* handle loss detection of sacch */ + if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { + /* count and send BFI */ + if (++(l1h->chan_states[tn][chan].sacch_lost) > 1) + compose_ph_data_ind(l1h, tn, 0, chan, NULL, 0); + } + + /* alloc burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return NULL; + } + + /* encode bursts */ + xcch_encode(*bursts_p, msg->l2h); + + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + +send_burst: + /* compose burst */ + burst = *bursts_p + bid * 116; + memset(bits, 0, 3); + memcpy(bits + 3, burst, 58); + memcpy(bits + 61, tsc[l1h->config.tsc], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); + + return bits; +} + +static const ubit_t *tx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + struct msgb *msg = NULL; /* make GCC happy */ + ubit_t *burst, **bursts_p = &l1h->chan_states[tn][chan].dl_bursts; + static ubit_t bits[148]; + int rc; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + /* get burst from queue */ + msg = dequeue_prim(l1h, tn, fn, chan); + if (msg) + goto got_msg; + +no_msg: + /* free burst memory */ + if (*bursts_p) { + talloc_free(*bursts_p); + *bursts_p = NULL; + } + return NULL; + +got_msg: + /* alloc burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return NULL; + } + + /* encode bursts */ + rc = pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); + + /* check validity of message */ + if (rc) { + LOGP(DL1C, LOGL_FATAL, "Prim invalid length, please FIX! " + "(len=%d)\n", rc); + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + goto no_msg; + } + + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + +send_burst: + /* compose burst */ + burst = *bursts_p + bid * 116; + memset(bits, 0, 3); + memcpy(bits + 3, burst, 58); + memcpy(bits + 61, tsc[l1h->config.tsc], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); + + return bits; +} + +static const ubit_t *tx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + struct msgb *msg = NULL; /* make GCC happy */ + ubit_t *burst, **bursts_p = &l1h->chan_states[tn][chan].dl_bursts; + static ubit_t bits[148]; + struct osmo_phsap_prim *l1sap; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + /* get burst from queue */ + msg = dequeue_prim(l1h, tn, fn, chan); + if (msg) + goto got_msg; + +no_msg: + /* free burst memory */ + if (*bursts_p) { + talloc_free(*bursts_p); + *bursts_p = NULL; + } + return NULL; + +got_msg: + l1sap = msgb_l1sap_prim(msg); + if (l1sap->oph.primitive == PRIM_TCH) { + /* check validity of message */ + if (msgb_l2len(msg) != 33) { + LOGP(DL1C, LOGL_FATAL, "Prim not 33 bytes, please FIX! " + "(len=%d)\n", msgb_l2len(msg)); + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + goto no_msg; + } + } else { + /* check validity of message */ + if (msgb_l2len(msg) != 23) { + LOGP(DL1C, LOGL_FATAL, "Prim not 23 bytes, please FIX! " + "(len=%d)\n", msgb_l2len(msg)); + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + goto no_msg; + } + } + + /* alloc 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); + + /* encode bursts */ + tch_fr_encode(*bursts_p, msg->l2h, msgb_l2len(msg)); + + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + +send_burst: + /* compose burst */ + burst = *bursts_p + bid * 116; + memset(bits, 0, 3); + memcpy(bits + 3, burst, 58); + memcpy(bits + 61, tsc[l1h->config.tsc], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + LOGP(DL1C, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u trx=%u burst=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); + + return bits; +} + +static const ubit_t *tx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid) +{ + // FIXME + return NULL; +} + + + +/* + * RX on uplink (indication to upper layer) + */ + +static int rx_rach_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa) +{ + struct osmo_phsap_prim l1sap; + uint8_t ra; + int rc; + + LOGP(DL1C, LOGL_DEBUG, "Received %s fn=%u\n", + trx_chan_desc[chan].name, fn); + + /* decode */ + rc = rach_decode(&ra, bits + 8 + 41, l1h->trx->bts->bsic); + if (rc) { + LOGP(DL1C, LOGL_NOTICE, "Received bad rach frame at fn=%u " + "ra=%u\n", fn, ra); + 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.ra = ra; + l1sap.u.rach_ind.acc_delay = 0; //FIXME: TOA + l1sap.u.rach_ind.fn = fn; + + /* forward primitive */ + l1sap_up(l1h->trx, &l1sap); + + return 0; +} + +static int compose_ph_data_ind(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *l2, uint8_t l2_len) +{ + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + + /* compose primitive */ + msg = l1sap_msgb_alloc(l2_len); + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, msg); + l1sap->u.data.chan_nr = trx_chan_desc[chan].chan_nr | tn; + l1sap->u.data.link_id = trx_chan_desc[chan].link_id; + l1sap->u.data.fn = fn; + msg->l2h = msgb_put(msg, l2_len); + if (l2_len) + memcpy(msg->l2h, l2, l2_len); + + if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) + l1h->chan_states[tn][chan].sacch_lost = 0; + + /* forward primitive */ + l1sap_up(l1h->trx, l1sap); + + return 0; +} + +static int rx_data_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa) +{ + struct trx_chan_state *chan_state = &l1h->chan_states[tn][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 l2[23], l2_len; + int rc; + + LOGP(DL1C, LOGL_DEBUG, "Data received %s fn=%u ts=%u trx=%u bid=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); + + /* alloc burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return -ENOMEM; + } + + /* update mask */ + *mask |= (1 << bid); + + /* store frame number of first burst */ + if (bid == 0) + *first_fn = fn; + + /* copy burst to buffer of 4 bursts */ + burst = *bursts_p + bid * 116; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + + // FIXME: decrypt burst + + /* wait until complete set of bursts */ + if ((*mask & 0xf) != 0xf) + return 0; + *mask = 0x0; + + /* decode */ + rc = xcch_decode(l2, *bursts_p); + if (rc) { + LOGP(DL1C, LOGL_NOTICE, "Received bad data frame at fn=%u for " + "%s\n", *first_fn, trx_chan_desc[chan].name); + l2_len = 0; + } else + l2_len = 23; + + return compose_ph_data_ind(l1h, tn, *first_fn, chan, l2, l2_len); +} + +static int rx_pdtch_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa) +{ + struct trx_chan_state *chan_state = &l1h->chan_states[tn][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 l2[54+1]; + int rc; + + LOGP(DL1C, LOGL_DEBUG, "PDTCH received %s fn=%u ts=%u trx=%u bid=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); + + /* alloc burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return -ENOMEM; + } + + /* update mask */ + *mask |= (1 << bid); + + /* store frame number of first burst */ + if (bid == 0) + *first_fn = fn; + + /* copy burst to buffer of 4 bursts */ + burst = *bursts_p + bid * 116; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + + // FIXME: decrypt burst + + /* wait until complete set of bursts */ + if ((*mask & 0xf) != 0xf) + return 0; + *mask = 0x0; + + /* decode */ + rc = pdch_decode(l2 + 1, *bursts_p, NULL); + if (rc <= 0) { + LOGP(DL1C, LOGL_NOTICE, "Received bad PDTCH frame at fn=%u for " + "%s\n", *first_fn, trx_chan_desc[chan].name); + l2[0] = 0; /* bad frame */ + rc = 0; + } else + l2[0] = 7; /* valid frame */ + + return compose_ph_data_ind(l1h, tn, *first_fn, chan, l2, rc + 1); +} + +static int compose_tch_ind(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len) +{ + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + + /* compose primitive */ + msg = l1sap_msgb_alloc(tch_len); + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, + PRIM_OP_INDICATION, msg); + l1sap->u.tch.chan_nr = trx_chan_desc[chan].chan_nr | tn; + l1sap->u.tch.fn = fn; + msg->l2h = msgb_put(msg, tch_len); + if (tch_len) + memcpy(msg->l2h, tch, tch_len); + + /* forward primitive */ + l1sap_up(l1h->trx, l1sap); + + return 0; +} + +static int rx_tchf_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa) +{ + struct trx_chan_state *chan_state = &l1h->chan_states[tn][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 tch_data[33]; + int rc; + + LOGP(DL1C, LOGL_DEBUG, "TCH/F received %s fn=%u ts=%u trx=%u bid=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); + + /* alloc burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 928); + if (!*bursts_p) + return -ENOMEM; + } + + /* update mask */ + *mask |= (1 << bid); + + /* store frame number of first burst */ + if (bid == 0) + *first_fn = fn; + + /* 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); + + // FIXME: decrypt burst + + /* wait until complete set of bursts */ + if (*mask != 0xff) + return 0; + *mask = 0xf0; + + /* decode + * also shift buffer by 4 bursts for interleaving */ + rc = tch_fr_decode(tch_data, *bursts_p); + memcpy(*bursts_p, *bursts_p + 464, 464); + if (rc < 0) { + LOGP(DL1C, LOGL_NOTICE, "Received bad tch frame at fn=%u " + "for %s\n", *first_fn, trx_chan_desc[chan].name); + rc = 0; + } + + /* FACCH */ + if (rc == 23) + return compose_ph_data_ind(l1h, tn, *first_fn, chan, tch_data, + 23); + + /* TCH or BFI */ + return compose_tch_ind(l1h, tn, *first_fn, chan, tch_data, rc); +} + +static int rx_tchh_fn(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, int16_t toa) +{ + LOGP(DL1C, LOGL_DEBUG, "TCH/H Received %s fn=%u ts=%u trx=%u bid=%u\n", + trx_chan_desc[chan].name, fn, tn, l1h->trx->nr, bid); + + // FIXME + return 0; +} + + +/* + * multiframe structure + */ + +/* frame structures */ +struct trx_sched_frame { + enum trx_chan_type dl_chan; + uint8_t dl_bid; + enum trx_chan_type ul_chan; + uint8_t ul_bid; +}; + +static struct trx_sched_frame frame_bcch[51] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_BCCH, 0, TRXC_RACH, 0 }, + { TRXC_BCCH, 1, TRXC_RACH, 0 }, + { TRXC_BCCH, 2, TRXC_RACH, 0 }, + { TRXC_BCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_IDLE, 0, TRXC_RACH, 0 }, +}; + +static struct trx_sched_frame frame_bcch_sdcch4[102] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, + { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, + { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, + { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, + { TRXC_BCCH, 2, TRXC_RACH, 0 }, + { TRXC_BCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_SACCH4_2, 0 }, + { TRXC_CCCH, 1, TRXC_SACCH4_2, 1 }, + { TRXC_CCCH, 2, TRXC_SACCH4_2, 2 }, + { TRXC_CCCH, 3, TRXC_SACCH4_2, 3 }, + { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 }, + { TRXC_SCH, 0, TRXC_SACCH4_3, 1 }, + { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 }, + { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, + { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, + { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, + { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, + { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, + { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, + { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 }, + { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 }, + { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 }, + { TRXC_SACCH4_0, 3, TRXC_RACH, 0 }, + { TRXC_SACCH4_1, 0, TRXC_RACH, 0 }, + { TRXC_SACCH4_1, 1, TRXC_SDCCH4_2, 0 }, + { TRXC_SACCH4_1, 2, TRXC_SDCCH4_2, 1 }, + { TRXC_SACCH4_1, 3, TRXC_SDCCH4_2, 2 }, + { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, + { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, + { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, + { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, + { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, + { TRXC_BCCH, 2, TRXC_RACH, 0 }, + { TRXC_BCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 }, + { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 }, + { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 }, + { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 }, + { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 }, + { TRXC_SCH, 0, TRXC_SACCH4_1, 1 }, + { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 }, + { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, + { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, + { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, + { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, + { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, + { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, + { TRXC_SACCH4_2, 0, TRXC_SDCCH4_1, 1 }, + { TRXC_SACCH4_2, 1, TRXC_SDCCH4_1, 2 }, + { TRXC_SACCH4_2, 2, TRXC_SDCCH4_1, 3 }, + { TRXC_SACCH4_2, 3, TRXC_RACH, 0 }, + { TRXC_SACCH4_3, 0, TRXC_RACH, 0 }, + { TRXC_SACCH4_3, 1, TRXC_SDCCH4_2, 0 }, + { TRXC_SACCH4_3, 2, TRXC_SDCCH4_2, 1 }, + { TRXC_SACCH4_3, 3, TRXC_SDCCH4_2, 2 }, + { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, +}; + +static struct trx_sched_frame frame_sdcch8[102] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 }, + { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 }, + { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 }, + { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 }, + { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 }, + { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 }, + { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 }, + { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 }, + { TRXC_SDCCH8_2, 0, TRXC_SACCH8_7, 0 }, + { TRXC_SDCCH8_2, 1, TRXC_SACCH8_7, 1 }, + { TRXC_SDCCH8_2, 2, TRXC_SACCH8_7, 2 }, + { TRXC_SDCCH8_2, 3, TRXC_SACCH8_7, 3 }, + { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, + { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, + { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, + { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, + { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, + { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, + { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, + { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, + { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, + { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, + { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, + { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, + { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, + { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, + { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, + { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, + { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, + { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 }, + { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 }, + { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 }, + { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 }, + { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 }, + { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 }, + { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 }, + { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 }, + { TRXC_SACCH8_2, 0, TRXC_SDCCH8_6, 1 }, + { TRXC_SACCH8_2, 1, TRXC_SDCCH8_6, 2 }, + { TRXC_SACCH8_2, 2, TRXC_SDCCH8_6, 3 }, + { TRXC_SACCH8_2, 3, TRXC_SDCCH8_7, 0 }, + { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 }, + { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 }, + { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 }, + { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 }, + { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 }, + { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 }, + { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 }, + { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 }, + { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 }, + { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 }, + { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 }, + { TRXC_SDCCH8_1, 0, TRXC_SACCH8_2, 0 }, + { TRXC_SDCCH8_1, 1, TRXC_SACCH8_2, 1 }, + { TRXC_SDCCH8_1, 2, TRXC_SACCH8_2, 2 }, + { TRXC_SDCCH8_1, 3, TRXC_SACCH8_2, 3 }, + { TRXC_SDCCH8_2, 0, TRXC_SACCH8_3, 0 }, + { TRXC_SDCCH8_2, 1, TRXC_SACCH8_3, 1 }, + { TRXC_SDCCH8_2, 2, TRXC_SACCH8_3, 2 }, + { TRXC_SDCCH8_2, 3, TRXC_SACCH8_3, 3 }, + { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, + { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, + { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, + { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, + { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, + { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, + { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, + { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, + { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, + { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, + { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, + { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, + { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, + { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, + { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, + { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, + { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, + { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 }, + { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 }, + { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 }, + { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 }, + { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 }, + { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 }, + { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 }, + { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 }, + { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 }, + { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 }, + { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 }, + { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 }, + { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 }, + { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 }, + { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 }, + { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 }, + { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 }, + { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 }, + { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 }, +}; + +static struct trx_sched_frame frame_tchf[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, + { TRXC_TCHF, 1, TRXC_TCHF, 1 }, + { TRXC_TCHF, 2, TRXC_TCHF, 2 }, + { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, +}; + +static struct trx_sched_frame frame_tchh[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, + { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, + { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, + { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, +}; + +static struct trx_sched_frame frame_pdch[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PTCCH, 0, TRXC_PTCCH, 0 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PTCCH, 1, TRXC_PTCCH, 1 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PTCCH, 2, TRXC_PTCCH, 2 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PTCCH, 3, TRXC_PTCCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, + { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, + { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, + { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, +}; + +/* multiframe structure */ +struct trx_sched_multiframe { + enum gsm_phys_chan_config pchan; + uint8_t period; + struct trx_sched_frame *frames; + const char *name; +}; + +static struct trx_sched_multiframe trx_sched_multiframes[] = { + { GSM_PCHAN_NONE, 0, NULL, "NONE"}, + { GSM_PCHAN_CCCH, 51, frame_bcch, "BCCH+CCCH" }, + { GSM_PCHAN_CCCH_SDCCH4, 102, frame_bcch_sdcch4, "BCCH+CCCH+SDCCH/4+SACCH/4" }, + { GSM_PCHAN_SDCCH8_SACCH8C, 102, frame_sdcch8, "SDCCH/8+SACCH/8" }, + { GSM_PCHAN_TCH_F, 104, frame_tchf, "TCH/F+SACCH" }, + { GSM_PCHAN_TCH_H, 104, frame_tchh, "TCH/H+SACCH" }, + { GSM_PCHAN_PDCH, 104, frame_pdch, "PDCH" }, +}; + + +/* + * scheduler functions + */ + +/* set multiframe scheduler to given pchan */ +int trx_sched_set_pchan(struct trx_l1h *l1h, uint8_t tn, + enum gsm_phys_chan_config pchan) +{ + int i; + + /* ignore disabled slots */ + if (!(l1h->config.slotmask & (1 << tn))) + return -ENOTSUP; + + for (i = 0; ARRAY_SIZE(trx_sched_multiframes); i++) { + if (trx_sched_multiframes[i].pchan == pchan) { + l1h->mf_index[tn] = i; + LOGP(DL1C, LOGL_NOTICE, "Configuring multiframe with " + "%s trx=%d ts=%d\n", + trx_sched_multiframes[i].name, + l1h->trx->nr, tn); + return 0; + } + } + + return -EINVAL; +} + +/* setting all logical channels given attributes to active/inactive */ +int trx_sched_set_lchan(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t link_id, + int downlink, int active) +{ + uint8_t tn = L1SAP_CHAN2TS(chan_nr); + int i; + int rc = -EINVAL; + + /* look for all matching chan_nr/link_id */ + for (i = 0; i < _TRX_CHAN_MAX; i++) { + if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) + && trx_chan_desc[i].link_id == link_id) { + LOGP(DL1C, LOGL_NOTICE, "%s %s %s on trx=%d ts=%d\n", + (active) ? "Activating" : "Deactivating", + (downlink) ? "downlink" : "uplink", + trx_chan_desc[i].name, l1h->trx->nr, tn); + if (downlink) { + l1h->chan_states[tn][i].dl_active = active; + l1h->chan_states[tn][i].dl_active = active; + } else { + l1h->chan_states[tn][i].ul_active = active; + l1h->chan_states[tn][i].ul_active = active; + } + l1h->chan_states[tn][i].sacch_lost = 0; + rc = 0; + } + } + + return rc; +} + +/* process ready-to-send */ +static int trx_sched_rts(struct trx_l1h *l1h, uint8_t tn, uint32_t fn) +{ + struct trx_sched_frame *frame; + uint8_t offset, period, bid; + trx_sched_rts_func *func; + enum trx_chan_type chan; + + /* no multiframe set */ + if (!l1h->mf_index[tn]) + return 0; + + /* get frame from multiframe */ + period = trx_sched_multiframes[l1h->mf_index[tn]].period; + offset = fn % period; + frame = trx_sched_multiframes[l1h->mf_index[tn]].frames + offset; + + chan = frame->dl_chan; + bid = frame->dl_bid; + func = trx_chan_desc[frame->dl_chan].rts_fn; + + /* only on bid == 0 */ + if (bid != 0) + return 0; + + /* no RTS function */ + if (!func) + return 0; + + /* check if channel is active */ + if (!trx_chan_desc[chan].auto_active + && !l1h->chan_states[tn][chan].dl_active) + return -EINVAL; + + return func(l1h, tn, fn, frame->dl_chan); +} + +/* process downlink burst */ +static const ubit_t *trx_sched_dl_burst(struct trx_l1h *l1h, uint8_t tn, + uint32_t fn) +{ + struct trx_sched_frame *frame; + uint8_t offset, period, bid; + trx_sched_dl_func *func; + enum trx_chan_type chan; + const ubit_t *bits = NULL; + + if (!l1h->mf_index[tn]) + goto no_data; + + /* get frame from multiframe */ + period = trx_sched_multiframes[l1h->mf_index[tn]].period; + offset = fn % period; + frame = trx_sched_multiframes[l1h->mf_index[tn]].frames + offset; + + chan = frame->dl_chan; + bid = frame->dl_bid; + func = trx_chan_desc[chan].dl_fn; + + /* check if channel is active */ + if (!trx_chan_desc[chan].auto_active + && !l1h->chan_states[tn][chan].dl_active) + goto no_data; + + /* get burst from function */ + bits = func(l1h, tn, fn, chan, bid); + +no_data: + /* in case of C0, we need a dummy burst to maintain RF power */ + if (bits == NULL && l1h->trx == l1h->trx->bts->c0) { +if (0) if (chan != TRXC_IDLE) // hack + LOGP(DL1C, LOGL_DEBUG, "No burst data for %s fn=%u ts=%u " + "burst=%d on C0, so filling with dummy burst\n", + trx_chan_desc[chan].name, fn, tn, bid); + bits = dummy_burst; + } + + return bits; +} + +/* process uplink burst */ +int trx_sched_ul_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + sbit_t *bits, int8_t rssi, int16_t toa) +{ + struct trx_sched_frame *frame; + uint8_t offset, period, bid; + trx_sched_ul_func *func; + enum trx_chan_type chan; + int rc; + + if (!l1h->mf_index) + return -EINVAL; + + /* get frame from multiframe */ + period = trx_sched_multiframes[l1h->mf_index[tn]].period; + offset = fn % period; + frame = trx_sched_multiframes[l1h->mf_index[tn]].frames + offset; + + chan = frame->ul_chan; + bid = frame->ul_bid; + func = trx_chan_desc[chan].ul_fn; + + /* check if channel is active */ + if (!trx_chan_desc[chan].auto_active + && !l1h->chan_states[tn][chan].ul_active) + return -EINVAL; + + /* put burst to function */ + rc = func(l1h, tn, fn, chan, bid, bits, toa); + + return rc; +} + +/* schedule all frames of all TRX for given FN */ +static int trx_sched_fn(uint32_t fn) +{ + struct gsm_bts_trx *trx; + struct trx_l1h *l1h; + uint8_t tn; + const ubit_t *bits; + uint8_t gain; + + /* send time indication */ + l1if_mph_time_ind(bts, fn); + + /* advance frame number, so the tranceiver has more time until + * it must be transmitted. */ + fn = (fn + trx_clock_advance) % 2715648; + + /* process every TRX */ + llist_for_each_entry(trx, &bts->trx_list, list) { + l1h = trx_l1h_hdl(trx); + + /* we don't schedule, if power is off */ + if (!l1h->config.poweron) + continue; + + /* process every TS of TRX */ + for (tn = 0; tn < 8; tn++) { + /* ignore disabled slots */ + if (!(l1h->config.slotmask & (1 << tn))) + continue; + /* ready-to-send */ + trx_sched_rts(l1h, tn, + (fn + trx_rts_advance) % 2715648); + /* get burst for FN */ + bits = trx_sched_dl_burst(l1h, tn, fn); + if (!bits) { + /* if no bits, send dummy burst with no gain */ + bits = dummy_burst; + gain = 128; + } else + gain = 0; + trx_if_data(l1h, tn, fn, gain, bits); + } + } + + return 0; +} + + +/* + * frame clock + */ + +#define FRAME_DURATION_uS 4615 +#define MAX_FN_SKEW 50 +#define TRX_LOSS_FRAMES 400 + +extern int quit; +/* this timer fires for every FN to be processed */ +static void trx_ctrl_timer_cb(void *data) +{ + struct timeval tv_now, *tv_clock = &tranceiver_clock_tv; + int32_t elapsed; + + /* check if tranceiver is still alive */ + if (tranceiver_lost++ == TRX_LOSS_FRAMES) { + struct gsm_bts_trx *trx; + + LOGP(DL1C, LOGL_NOTICE, "No more clock from traneiver\n"); + + tranceiver_available = 0; + + /* flush pending messages of transceiver */ + llist_for_each_entry(trx, &bts->trx_list, list) + trx_if_flush(trx_l1h_hdl(trx)); + + /* start over provisioning tranceiver */ + l1if_provision_tranceiver(bts); + + return; + } + + gettimeofday(&tv_now, NULL); + + elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 + + (tv_now.tv_usec - tv_clock->tv_usec); + + /* if someone played with clock, or if the process stalled */ + if (elapsed > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed < 0) { + LOGP(DL1C, LOGL_NOTICE, "PC clock skew: elapsed uS %d\n", + elapsed); + tranceiver_available = 0; + return; + } + + while (elapsed > FRAME_DURATION_uS / 2) { + tv_clock->tv_usec += FRAME_DURATION_uS; + if (tv_clock->tv_usec >= 1000000) { + tv_clock->tv_sec++; + tv_clock->tv_usec -= 1000000; + } + tranceiver_last_fn = (tranceiver_last_fn + 1) % 2715648; + trx_sched_fn(tranceiver_last_fn); + elapsed -= FRAME_DURATION_uS; + } + osmo_timer_schedule(&tranceiver_clock_timer, 0, FRAME_DURATION_uS - elapsed); +} + + +/* receive clock from tranceiver */ +int trx_sched_clock(uint32_t fn) +{ + struct timeval tv_now, *tv_clock = &tranceiver_clock_tv; + int32_t elapsed; + int32_t elapsed_fn; + + /* reset lost counter */ + tranceiver_lost = 0; + + gettimeofday(&tv_now, NULL); + + /* clock becomes valid */ + if (!tranceiver_available) { + LOGP(DL1C, LOGL_NOTICE, "initial GSM clock received: fn=%u\n", + fn); +new_clock: + tranceiver_last_fn = fn; + trx_sched_fn(tranceiver_last_fn); + + /* schedule first FN to be transmitted */ + memcpy(tv_clock, &tv_now, sizeof(struct timeval)); + tranceiver_available = 1; + tranceiver_clock_timer.cb = trx_ctrl_timer_cb; + tranceiver_clock_timer.data = bts; + osmo_timer_schedule(&tranceiver_clock_timer, 0, + FRAME_DURATION_uS); + + return 0; + } + + osmo_timer_del(&tranceiver_clock_timer); + + /* calculate elapsed time since last_fn */ + elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 + + (tv_now.tv_usec - tv_clock->tv_usec); + + /* how much frames have been elapsed since last fn processed */ + elapsed_fn = (fn + 2715648 - tranceiver_last_fn) % 2715648; + if (elapsed_fn >= 135774) + elapsed_fn -= 2715648; + + /* 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", tranceiver_last_fn, fn); + goto new_clock; + } + + LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %d\n", elapsed_fn * FRAME_DURATION_uS - elapsed); + + /* too many frames have been processed already */ + if (elapsed_fn < 0) { + /* set clock to the time or last FN should have been + * transmitted. */ + tv_clock->tv_sec = tv_now.tv_sec; + tv_clock->tv_usec = tv_now.tv_usec + + (0 - elapsed_fn) * FRAME_DURATION_uS; + if (tv_clock->tv_usec >= 1000000) { + tv_clock->tv_sec++; + tv_clock->tv_usec -= 1000000; + } + /* set time to the time our next FN hast to be transmitted */ + osmo_timer_schedule(&tranceiver_clock_timer, 0, + FRAME_DURATION_uS * (1 - elapsed_fn)); + + return 0; + } + + /* transmit what we still need to transmit */ + while (fn != tranceiver_last_fn) { + tranceiver_last_fn = (tranceiver_last_fn + 1) % 2715648; + trx_sched_fn(tranceiver_last_fn); + } + + /* schedule next FN to be transmitted */ + memcpy(tv_clock, &tv_now, sizeof(struct timeval)); + osmo_timer_schedule(&tranceiver_clock_timer, 0, FRAME_DURATION_uS); + + return 0; +} + diff --git a/src/osmo-bts-trx/scheduler.h b/src/osmo-bts-trx/scheduler.h new file mode 100644 index 000000000..eb82327a2 --- /dev/null +++ b/src/osmo-bts-trx/scheduler.h @@ -0,0 +1,29 @@ +#ifndef TRX_SCHEDULER_H +#define TRX_SCHEDULER_H + +extern uint32_t trx_clock_advance; +extern uint32_t tranceiver_last_fn; + + +int trx_sched_init(struct trx_l1h *l1h); + +void trx_sched_exit(struct trx_l1h *l1h); + +int trx_sched_ph_data_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap); + +int trx_sched_tch_req(struct trx_l1h *l1h, struct osmo_phsap_prim *l1sap); + +int trx_sched_clock(uint32_t fn); + +int trx_sched_ul_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, + sbit_t *bits, int8_t rssi, int16_t toa); + +/* set multiframe scheduler to given pchan */ +int trx_sched_set_pchan(struct trx_l1h *l1h, uint8_t tn, + enum gsm_phys_chan_config pchan); + +/* setting all logical channels given attributes to active/inactive */ +int trx_sched_set_lchan(struct trx_l1h *l1h, uint8_t chan_nr, uint8_t link_id, + int downlink, int active); + +#endif /* TRX_SCHEDULER_H */ diff --git a/src/osmo-bts-trx/tch_fr.c b/src/osmo-bts-trx/tch_fr.c new file mode 100644 index 000000000..75f547527 --- /dev/null +++ b/src/osmo-bts-trx/tch_fr.c @@ -0,0 +1,310 @@ +/* + * tch_fr.c + * + * Copyright (c) 2013 Andreas Eversberg + */ + +#include +#include +#include + +#include +#include +#include + +#include "xcch.h" +#include "tch_fr.h" + + +/* + * GSM TCH FR/EFR parity + * + * g(x) = x^3 + x + 1 + */ + +const struct osmo_crc8gen_code tch_fr_crc3 = { + .bits = 3, + .poly = 0x2, + .init = 0x0, + .remainder = 0x7, +}; + + +/* + * GSM TCH FR/EFR convolutional coding + * + * G_0 = 1 + x^3 + x^4 + * G_1 = 1 + x + x^3 + x^4 + */ + +static const uint8_t conv_tch_fr_next_output[][2] = { + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, +}; + +static const uint8_t conv_tch_fr_next_state[][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + +static const struct osmo_conv_code conv_tch_fr = { + .N = 2, + .K = 5, + .len = 185, + .next_output = conv_tch_fr_next_output, + .next_state = conv_tch_fr_next_state, +}; + + +/* + * GSM TCH FR/EFR interleaving and burst mapping + * + * Interleaving: + * + * Given 456 coded input bits, form 8 blocks of 114 bits, + * where event bits of the first 4 block and off bits of the last 4 block + * are used: + * + * i(B, j) = c(n, k) k = 0, ..., 455 + * n = 0, ..., N, N + 1, ... + * B = B_0 + 4n + (k mod 8) + * j = 2(49k mod 57) + ((k mod 8) div 4) + * + * Mapping on Burst: + * + * e(B, j) = i(B, j) + * e(B, 59 + j) = i(B, 57 + j) j = 0, ..., 56 + * e(B, 57) = h_l(B) + * e(B, 58) = h_n(B) + * + * Where hl(B) and hn(B) are bits in burst B indicating flags. + */ + +static void +tch_fr_deinterleave(sbit_t *cB, sbit_t *iB) +{ + int j, k, B; + + for (k=0; k<456; k++) { + B = k & 7; + j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); + cB[k] = iB[B * 114 + j]; + } +} + +static void +tch_fr_interleave(ubit_t *cB, ubit_t *iB) +{ + int j, k, B; + + for (k=0; k<456; k++) { + B = k & 7; + j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); + iB[B * 114 + j] = cB[k]; + } +} + +static void +tch_fr_burst_unmap(sbit_t *iB, sbit_t *eB, sbit_t *h, int odd) +{ + int i; + + /* brainfuck: only copy even or odd bits */ + for (i=odd; i<57; i+=2) + iB[i] = eB[i]; + for (i=58-odd; i<114; i+=2) + iB[i] = eB[i+2]; + + if (h && !odd) + *h = eB[57]; + + if (h && odd) + *h = eB[58]; +} + +static void +tch_fr_burst_map(ubit_t *iB, ubit_t *eB, ubit_t *h, int odd) +{ + int i; + + /* brainfuck: only copy even or odd bits */ + for (i=odd; i<57; i+=2) + eB[i] = iB[i]; + for (i=58-odd; i<114; i+=2) + eB[i+2] = iB[i]; + + if (h && !odd) + eB[57] = *h; + if (h && odd) + eB[58] = *h; +} + +/* this corresponds to the bit-lengths of the individual codec + * parameters as indicated in Table 1.1 of TS 06.10 */ +static const uint8_t gsm_fr_map[] = { + 6, 6, 5, 5, 4, 4, 3, 3, + 7, 2, 2, 6, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 7, 2, 2, 6, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 7, 2, 2, 6, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 7, 2, 2, 6, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3 +}; + +static void +tch_fr_reassemble(uint8_t *tch_data, ubit_t *d_bits) +{ + int i, j, k, l, o; + + tch_data[0] = 0xd << 4; + /* reassemble d-bits */ + i = 0; /* counts bits */ + j = 4; /* counts output bits */ + k = gsm_fr_map[0]-1; /* current number bit in element */ + l = 0; /* counts element bits */ + o = 0; /* offset input bits */ + while (i < 260) { + tch_data[j>>3] |= (d_bits[k+o] << (7-(j&7))); + if (--k < 0) { + o += gsm_fr_map[l]; + k = gsm_fr_map[++l]-1; + } + i++; + j++; + } +} + +static void +tch_fr_disassemble(ubit_t *d_bits, uint8_t *tch_data) +{ + int i, j, k, l, o; + + i = 0; /* counts bits */ + j = 4; /* counts input bits */ + k = gsm_fr_map[0]-1; /* current number bit in element */ + l = 0; /* counts element bits */ + o = 0; /* offset output bits */ + while (i < 260) { + d_bits[k+o] = (tch_data[j>>3] >> (7-(j&7))) & 1; + if (--k < 0) { + o += gsm_fr_map[l]; + k = gsm_fr_map[++l]-1; + } + i++; + j++; + } +} + +static void +tch_fr_unreorder(ubit_t *d, ubit_t *p, ubit_t *u) +{ + int i; + + for (i=0; i<91; i++) { + d[i<<1] = u[i]; + d[(i<<1)+1] = u[184-i]; + } + for (i=0; i<3; i++) + p[i] = u[91+i]; +} + +static void +tch_fr_reorder(ubit_t *u, ubit_t *d, ubit_t *p) +{ + int i; + + for (i=0; i<91; i++) { + u[i] = d[i<<1]; + u[184-i] = d[(i<<1)+1]; + } + for (i=0; i<3; i++) + u[91+i] = p[i]; +} + +int +tch_fr_decode(uint8_t *tch_data, sbit_t *bursts) +{ + sbit_t iB[912], cB[456], h; + ubit_t conv[185], d[260], p[3]; + int i, rv, len, steal = 0; + + for (i=0; i<8; i++) { + tch_fr_burst_unmap(&iB[i * 114], &bursts[i * 116], &h, i>>2); + if (h < 0) + steal++; + } + + tch_fr_deinterleave(cB, iB); + + if (steal < 4) { + osmo_conv_decode(&conv_tch_fr, cB, conv); + + tch_fr_unreorder(d, p, conv); + + for (i=0; i<78; i++) + d[i+182] = (cB[i+378] < 0) ? 1:0; + + rv = osmo_crc8gen_check_bits(&tch_fr_crc3, d, 50, p); + if (rv) + return -1; + + tch_fr_reassemble(tch_data, d); + + len = 33; + } else { + rv = xcch_decode_cB(tch_data, cB); + if (rv) + return -1; + + len = 23; + } + + return len; +} + +int +tch_fr_encode(ubit_t *bursts, uint8_t *tch_data, int len) +{ + ubit_t iB[912], cB[456], h; + ubit_t conv[185], d[260], p[3]; + int i; + + switch (len) { + case 33: /* TCH FR */ + tch_fr_disassemble(d, tch_data); + + osmo_crc8gen_set_bits(&tch_fr_crc3, d, 50, p); + + tch_fr_reorder(conv, d, p); + + memcpy(cB+378, d+182, 78); + + osmo_conv_encode(&conv_tch_fr, conv, cB); + + h = 0; + + break; + case 23: /* FACCH */ + xcch_encode_cB(cB, tch_data); + + h = 1; + + break; + default: + return -1; + } + + tch_fr_interleave(cB, iB); + + for (i=0; i<8; i++) + tch_fr_burst_map(&iB[i * 114], &bursts[i * 116], &h, i>>2); + + return 0; +} diff --git a/src/osmo-bts-trx/tch_fr.h b/src/osmo-bts-trx/tch_fr.h new file mode 100644 index 000000000..68a931850 --- /dev/null +++ b/src/osmo-bts-trx/tch_fr.h @@ -0,0 +1,7 @@ +#ifndef _TCH_FR_H +#define _TCH_FR_H + +int tch_fr_decode(uint8_t *tch_data, sbit_t *bursts); +int tch_fr_encode(ubit_t *bursts, uint8_t *tch_data, int len); + +#endif /* _TCH_FR_H */ diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c new file mode 100644 index 000000000..abf992e07 --- /dev/null +++ b/src/osmo-bts-trx/trx_if.c @@ -0,0 +1,495 @@ +/* + * OpenBTS TRX interface handling + * + * Copyright (C) 2013 Andreas Eversberg + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "l1_if.h" +#include "trx_if.h" +#include "scheduler.h" + +int tranceiver_available = 0; +const char *tranceiver_ip = "127.0.0.1"; + +/* + * socket + */ + +static uint16_t base_port_local = 5800; + +/* open socket */ +static int trx_udp_open(void *priv, struct osmo_fd *ofd, uint16_t port, + int (*cb)(struct osmo_fd *fd, unsigned int what)) +{ + struct sockaddr_storage sas; + struct sockaddr *sa = (struct sockaddr *)&sas; + socklen_t sa_len; + + int rc; + + /* Init */ + ofd->fd = -1; + ofd->cb = cb; + ofd->data = priv; + + /* Listen / Binds */ + rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, tranceiver_ip, + port, OSMO_SOCK_F_BIND); + if (rc < 0) + return rc; + + /* Connect */ + sa_len = sizeof(sas); + rc = getsockname(ofd->fd, sa, &sa_len); + if (rc) + return rc; + + if (sa->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + sin->sin_port = htons(ntohs(sin->sin_port) - 100); + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + sin6->sin6_port = htons(ntohs(sin6->sin6_port) - 100); + } else { + return -EINVAL; + } + + rc = connect(ofd->fd, sa, sa_len); + if (rc) + return rc; + + + return 0; +} + +/* close socket */ +static void trx_udp_close(struct osmo_fd *ofd) +{ + if (ofd->fd > 0) { + osmo_fd_unregister(ofd); + close(ofd->fd); + ofd->fd = -1; + } +} + + +/* + * clock + */ + +static struct osmo_fd trx_ofd_clk; + + +/* get clock from clock socket */ +static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + char buf[1500]; + int len; + + len = recv(ofd->fd, buf, sizeof(buf) - 1, 0); + if (len <= 0) + return len; + buf[len] = '\0'; + + if (!strncmp(buf, "IND CLOCK ", 10)) { + uint32_t fn; + + sscanf(buf, "IND CLOCK %u", &fn); + LOGP(DTRX, LOGL_INFO, "Clock indication: fn=%u\n", fn); + trx_sched_clock(fn); + } else + LOGP(DTRX, LOGL_NOTICE, "Unknown message on clock port: %s\n", + buf); + + return 0; +} + + +/* + * ctrl + */ + +static void trx_ctrl_timer_cb(void *data); + +/* send first ctrl message and start timer */ +static void trx_ctrl_send(struct trx_l1h *l1h) +{ + struct trx_ctrl_msg *tcm; + + /* get first command */ + if (llist_empty(&l1h->trx_ctrl_list)) + return; + tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list); + + LOGP(DTRX, LOGL_DEBUG, "Sending control '%s' to trx=%u\n", tcm->cmd, + l1h->trx->nr); + /* send command */ + send(l1h->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd)+1, 0); + + /* start timer */ + l1h->trx_ctrl_timer.cb = trx_ctrl_timer_cb; + l1h->trx_ctrl_timer.data = l1h; + 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; + + LOGP(DTRX, LOGL_NOTICE, "No response from tranceiver\n"); + + trx_ctrl_send(l1h); +} + +/* add a new ctrl command */ +static int trx_ctrl_cmd(struct trx_l1h *l1h, const char *cmd, const char *fmt, + ...) +{ + struct trx_ctrl_msg *tcm; + va_list ap; + int l; + + /* create message */ + tcm = talloc_zero(tall_bts_ctx, struct trx_ctrl_msg); + if (!tcm) + return -ENOMEM; + if (fmt && fmt[0]) { + l = snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "CMD %s ", cmd); + va_start(ap, fmt); + vsnprintf(tcm->cmd + l, sizeof(tcm->cmd) - l - 1, fmt, ap); + va_end(ap); + } else + snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "CMD %s", cmd); + tcm->cmd_len = strlen(cmd); + llist_add_tail(&tcm->list, &l1h->trx_ctrl_list); + LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd); + + /* send message, if no pending message */ + if (!osmo_timer_pending(&l1h->trx_ctrl_timer)) + trx_ctrl_send(l1h); + + return 0; +} + +int trx_if_cmd_poweroff(struct trx_l1h *l1h) +{ + return trx_ctrl_cmd(l1h, "POWEROFF", ""); +} + +int trx_if_cmd_poweron(struct trx_l1h *l1h) +{ + return trx_ctrl_cmd(l1h, "POWERON", ""); +} + +int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc) +{ + return trx_ctrl_cmd(l1h, "SETTSC", "%d", tsc); +} + +int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic) +{ + return trx_ctrl_cmd(l1h, "SETBSIC", "%d", bsic); +} + +int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db) +{ + return trx_ctrl_cmd(l1h, "SETRXGAIN", "%d", db); +} + +int trx_if_cmd_setpower(struct trx_l1h *l1h, int db) +{ + return trx_ctrl_cmd(l1h, "SETPOWER", "%d", db); +} + +int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly) +{ + return trx_ctrl_cmd(l1h, "SETMAXDLY", "%d", dly); +} + +int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type) +{ + return trx_ctrl_cmd(l1h, "SETSLOT", "%d %d", tn, type); +} + +int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn) +{ + uint16_t freq10; + + freq10 = gsm_arfcn2freq10(arfcn, 1); /* RX = uplink */ + if (freq10 == 0xffff) { + LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", arfcn); + return -ENOTSUP; + } + + return trx_ctrl_cmd(l1h, "RXTUNE", "%d", freq10 * 100); +} + +int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn) +{ + uint16_t freq10; + + freq10 = gsm_arfcn2freq10(arfcn, 0); /* TX = downlink */ + if (freq10 == 0xffff) { + LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", arfcn); + return -ENOTSUP; + } + + return trx_ctrl_cmd(l1h, "TXTUNE", "%d", freq10 * 100); +} + +/* get response from ctrl socket */ +static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct trx_l1h *l1h = ofd->data; + char buf[1500]; + int len, resp; + + len = recv(ofd->fd, buf, sizeof(buf) - 1, 0); + if (len <= 0) + return len; + buf[len] = '\0'; + + if (!strncmp(buf, "RSP ", 4)) { + struct trx_ctrl_msg *tcm; + char *p; + int rsp_len = 0; + + /* calculate the length of response item */ + p = strchr(buf + 4, ' '); + if (p) + rsp_len = p - buf - 4; + else + rsp_len = strlen(buf) - 4; + + LOGP(DTRX, LOGL_DEBUG, "Response message: '%s'\n", buf); + + /* get command for response message */ + if (llist_empty(&l1h->trx_ctrl_list)) { + 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 respose matches command */ + if (rsp_len != tcm->cmd_len) { + notmatch: + LOGP(DTRX, LOGL_NOTICE, "Response message '%s' does " + "not match command message '%s'\n", buf, + tcm->cmd); + return -EINVAL; + } + if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) + goto notmatch; + + /* check for response code */ + if (*p) { + sscanf(p + 1, "%d", &resp); + if (resp) { + LOGP(DTRX, LOGL_ERROR, "Tranceiver rejected " + "TRX command with response: '%s'\n", + buf); + } + } + + /* remove command from list */ + llist_del(&tcm->list); + talloc_free(tcm); + + /* abort timer and send next message, if any */ + if (osmo_timer_pending(&l1h->trx_ctrl_timer)) + osmo_timer_del(&l1h->trx_ctrl_timer); + trx_ctrl_send(l1h); + } else + LOGP(DTRX, LOGL_NOTICE, "Unknown message on ctrl port: %s\n", + buf); + + return 0; +} + + +/* + * data + */ + +static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct trx_l1h *l1h = ofd->data; + uint8_t buf[256]; + int len; + uint8_t tn; + int8_t rssi; + int16_t toa; + uint32_t fn; + sbit_t bits[148]; + int i; + + len = recv(ofd->fd, buf, sizeof(buf), 0); + if (len <= 0) + return len; + if (len != 158) { + 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]; + toa = (int16_t)(buf[6] << 8) | buf[7]; + + /* copy and convert bits {254..0} to sbits {-127..127} */ + for (i = 0; i < 148; 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 >= 2715648) { + LOGP(DTRX, LOGL_ERROR, "Illegal FN %u\n", fn); + return -EINVAL; + } + + LOGP(DTRX, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d ", + tn, fn, rssi, toa); + + trx_sched_ul_burst(l1h, tn, fn, bits, rssi, toa); + + return 0; +} + +int trx_if_data(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, + const ubit_t *bits) +{ + uint8_t buf[256]; + + 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, 148); + + /* we must be sure that we have clock, and we have sent all control + * data */ + if (tranceiver_available && llist_empty(&l1h->trx_ctrl_list)) { + send(l1h->trx_ofd_data.fd, buf, 154, 0); + } else + LOGP(DTRX, LOGL_DEBUG, "Ignoring TX data, tranceiver " + "offline.\n"); + + return 0; +} + + +/* + * open/close + */ + +int trx_if_open(struct trx_l1h *l1h) +{ + int rc; + + LOGP(DTRX, LOGL_NOTICE, "Open tranceiver for trx=%u\n", l1h->trx->nr); + + /* open sockets */ + if (l1h->trx->nr == 0) { + rc = trx_udp_open(NULL, &trx_ofd_clk, base_port_local, + trx_clk_read_cb); + if (rc < 0) + return rc; + LOGP(DTRX, LOGL_NOTICE, "Waiting for tranceiver send clock\n"); + } + rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl, + base_port_local + (l1h->trx->nr << 1) + 1, trx_ctrl_read_cb); + if (rc < 0) + goto err; + rc = trx_udp_open(l1h, &l1h->trx_ofd_data, + base_port_local + (l1h->trx->nr << 1) + 2, trx_data_read_cb); + if (rc < 0) + goto err; + + /* initialize ctrl queue */ + INIT_LLIST_HEAD(&l1h->trx_ctrl_list); + + /* enable all slots */ + l1h->config.slotmask = 0xff; + + return 0; + +err: + trx_if_close(l1h); + return rc; +} + +/* flush 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); + } +} + +void trx_if_close(struct trx_l1h *l1h) +{ + LOGP(DTRX, LOGL_NOTICE, "Close tranceiver for trx=%u\n", l1h->trx->nr); + + trx_if_flush(l1h); + + /* close sockets */ + if (l1h->trx->nr == 0) + trx_udp_close(&trx_ofd_clk); + trx_udp_close(&l1h->trx_ofd_ctrl); + trx_udp_close(&l1h->trx_ofd_data); +} + diff --git a/src/osmo-bts-trx/trx_if.h b/src/osmo-bts-trx/trx_if.h new file mode 100644 index 000000000..656bd6a7a --- /dev/null +++ b/src/osmo-bts-trx/trx_if.h @@ -0,0 +1,29 @@ +#ifndef TRX_IF_H +#define TRX_IF_H + +extern int tranceiver_available; +extern const char *tranceiver_ip; + +struct trx_ctrl_msg { + struct llist_head list; + char cmd[128]; + int cmd_len; +}; + +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_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_data(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, + const ubit_t *bits); +int trx_if_open(struct trx_l1h *l1h); +void trx_if_flush(struct trx_l1h *l1h); +void trx_if_close(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 000000000..720f3538e --- /dev/null +++ b/src/osmo-bts-trx/trx_vty.c @@ -0,0 +1,281 @@ +/* VTY interface for sysmoBTS */ + +/* (C) 2013 by Andreas Eversberg + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "l1_if.h" +#include "scheduler.h" +#include "trx_if.h" + +static struct gsm_bts *vty_bts; + +DEFUN(show_tranceiver, show_tranceiver_cmd, "show tranceiver", + SHOW_STR "Display information about tranceivers\n") +{ + struct gsm_bts *bts = vty_bts; + struct gsm_bts_trx *trx; + struct trx_l1h *l1h; + uint8_t tn; + + if (!tranceiver_available) { + vty_out(vty, "Tranceiver is not connected%s", VTY_NEWLINE); + } else { + vty_out(vty, "Tranceiver is connected, current fn=%u%s", + tranceiver_last_fn, VTY_NEWLINE); + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + l1h = trx_l1h_hdl(trx); + vty_out(vty, "TRX %d%s", trx->nr, VTY_NEWLINE); + 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); + if (l1h->config.rxgain_valid) + vty_out(vty, " rxgain : %d%s", l1h->config.rxgain, + VTY_NEWLINE); + else + vty_out(vty, " rxgain : undefined%s", VTY_NEWLINE); + if (l1h->config.power_valid) + vty_out(vty, " power : %d%s", l1h->config.power, + VTY_NEWLINE); + else + vty_out(vty, " power : 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); + for (tn = 0; tn < 8; 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); + } + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_fn_advance, cfg_bts_fn_advance_cmd, + "fn-advance <0-30>", + "Set the number of frames to be transmitted in advance of current FN\n" + "Advance in frames\n") +{ + trx_clock_advance = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_rxgain, cfg_trx_rxgain_cmd, + "rxgain <0-50>", + "Set the receiver gain in dB\n" + "Gain in dB\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct trx_l1h *l1h = trx_l1h_hdl(trx); + + l1h->config.rxgain = atoi(argv[0]); + l1h->config.rxgain_valid = 1; + l1h->config.rxgain_sent = 0; + l1if_provision_tranceiver_trx(l1h); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_power, cfg_trx_power_cmd, + "power <0-50>", + "Set the transmitter power dampening\n" + "Power dampening in dB\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct trx_l1h *l1h = trx_l1h_hdl(trx); + + l1h->config.power = atoi(argv[0]); + l1h->config.power_valid = 1; + l1h->config.power_sent = 0; + l1if_provision_tranceiver_trx(l1h); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_maxdly, cfg_trx_maxdly_cmd, + "maxdly <0-31>", + "Set the maximum delay of GSM symbols\n" + "GSM symbols (approx. 1.1km per symbol)\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct trx_l1h *l1h = trx_l1h_hdl(trx); + + l1h->config.maxdly = atoi(argv[0]); + l1h->config.maxdly_valid = 1; + l1h->config.maxdly_sent = 0; + l1if_provision_tranceiver_trx(l1h); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_slotmask, cfg_trx_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 gsm_bts_trx *trx = vty->index; + struct trx_l1h *l1h = trx_l1h_hdl(trx); + uint8_t tn; + + l1h->config.slotmask = 0; + for (tn = 0; tn < 8; tn++) + if (argv[tn][0] == '1') + l1h->config.slotmask |= (1 << tn); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_no_rxgain, cfg_trx_no_rxgain_cmd, + "no rxgain <0-50>", + NO_STR "Unset the receiver gain in dB\n" + "Gain in dB\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct trx_l1h *l1h = trx_l1h_hdl(trx); + + l1h->config.rxgain_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_no_power, cfg_trx_no_power_cmd, + "no power <0-50>", + NO_STR "Unset the transmitter power dampening\n" + "Power dampening in dB\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct trx_l1h *l1h = trx_l1h_hdl(trx); + + l1h->config.power_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_no_maxdly, cfg_trx_no_maxdly_cmd, + "no maxdly <0-31>", + NO_STR "Unset the maximum delay of GSM symbols\n" + "GSM symbols (approx. 1.1km per symbol)\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct trx_l1h *l1h = trx_l1h_hdl(trx); + + l1h->config.maxdly_valid = 0; + + return CMD_SUCCESS; +} + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + vty_out(vty, " fn-advance %d%s", trx_clock_advance, VTY_NEWLINE); +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ + struct trx_l1h *l1h = trx_l1h_hdl(trx); + + if (l1h->config.rxgain_valid) + vty_out(vty, " rxgain %d%s", l1h->config.rxgain, VTY_NEWLINE); + if (l1h->config.power_valid) + vty_out(vty, " power %d%s", l1h->config.power, VTY_NEWLINE); + if (l1h->config.maxdly_valid) + vty_out(vty, " maxdly %d%s", l1h->config.maxdly, 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); +} + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + install_element_ve(&show_tranceiver_cmd); + + install_element(BTS_NODE, &cfg_bts_fn_advance_cmd); + + install_element(TRX_NODE, &cfg_trx_rxgain_cmd); + install_element(TRX_NODE, &cfg_trx_power_cmd); + install_element(TRX_NODE, &cfg_trx_maxdly_cmd); + install_element(TRX_NODE, &cfg_trx_slotmask_cmd); + install_element(TRX_NODE, &cfg_trx_no_rxgain_cmd); + install_element(TRX_NODE, &cfg_trx_no_power_cmd); + install_element(TRX_NODE, &cfg_trx_no_maxdly_cmd); + + return 0; +} diff --git a/src/osmo-bts-trx/xcch.c b/src/osmo-bts-trx/xcch.c new file mode 100644 index 000000000..024bf0713 --- /dev/null +++ b/src/osmo-bts-trx/xcch.c @@ -0,0 +1,193 @@ +/* + * xcch.c + * + * Copyright (c) 2011 Sylvain Munaut + */ + +#include +#include +#include + +#include +#include +#include + +#include "xcch.h" + + +/* + * GSM xCCH parity (FIRE code) + * + * g(x) = (x^23 + 1)(x^17 + x^3 + 1) + * = x^40 + x^26 + x^23 + x^17 + x^3 + 1 + */ + +const struct osmo_crc64gen_code xcch_crc40 = { + .bits = 40, + .poly = 0x0004820009ULL, + .init = 0x0000000000ULL, + .remainder = 0xffffffffffULL, +}; + + +/* + * GSM xCCH convolutional coding + * + * G_0 = 1 + x^3 + x^4 + * G_1 = 1 + x + x^3 + x^4 + */ + +static const uint8_t conv_xcch_next_output[][2] = { + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 3, 0 }, { 2, 1 }, { 3, 0 }, { 2, 1 }, + { 0, 3 }, { 1, 2 }, { 0, 3 }, { 1, 2 }, +}; + +static const uint8_t conv_xcch_next_state[][2] = { + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, + { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, + { 8, 9 }, { 10, 11 }, { 12, 13 }, { 14, 15 }, +}; + +static const struct osmo_conv_code conv_xcch = { + .N = 2, + .K = 5, + .len = 224, + .next_output = conv_xcch_next_output, + .next_state = conv_xcch_next_state, +}; + + +/* + * GSM xCCH interleaving and burst mapping + * + * Interleaving: + * + * Given 456 coded input bits, form 4 blocks of 114 bits: + * + * i(B, j) = c(n, k) k = 0, ..., 455 + * n = 0, ..., N, N + 1, ... + * B = B_0 + 4n + (k mod 4) + * j = 2(49k mod 57) + ((k mod 8) div 4) + * + * Mapping on Burst: + * + * e(B, j) = i(B, j) + * e(B, 59 + j) = i(B, 57 + j) j = 0, ..., 56 + * e(B, 57) = h_l(B) + * e(B, 58) = h_n(B) + * + * Where hl(B) and hn(B) are bits in burst B indicating flags. + */ + +static void +xcch_deinterleave(sbit_t *cB, sbit_t *iB) +{ + int j, k, B; + + for (k=0; k<456; k++) { + B = k & 3; + j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); + cB[k] = iB[B * 114 + j]; + } +} + +static void +xcch_interleave(ubit_t *cB, ubit_t *iB) +{ + int j, k, B; + + for (k=0; k<456; k++) { + B = k & 3; + j = 2 * ((49 * k) % 57) + ((k & 7) >> 2); + iB[B * 114 + j] = cB[k]; + } +} + +static void +xcch_burst_unmap(sbit_t *iB, sbit_t *eB, sbit_t *hl, sbit_t *hn) +{ + memcpy(iB, eB, 57); + memcpy(iB+57, eB+59, 57); + + if (hl) + *hl = eB[57]; + + if (hn) + *hn = eB[58]; +} + +static void +xcch_burst_map(ubit_t *iB, ubit_t *eB, ubit_t *hl, ubit_t *hn) +{ + memcpy(eB, iB, 57); + memcpy(eB+59, iB+57, 57); + + if (hl) + eB[57] = *hl; + if (hn) + eB[58] = *hn; +} + +int +xcch_decode_cB(uint8_t *l2_data, sbit_t *cB) +{ + ubit_t conv[224]; + int rv; + + osmo_conv_decode(&conv_xcch, cB, conv); + + rv = osmo_crc64gen_check_bits(&xcch_crc40, conv, 184, conv+184); + if (rv) + return -1; + + osmo_ubit2pbit_ext(l2_data, 0, conv, 0, 184, 1); + + return 0; +} + +int +xcch_decode(uint8_t *l2_data, sbit_t *bursts) +{ + sbit_t iB[456], cB[456]; + int i; + + for (i=0; i<4; i++) + xcch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, NULL); + + xcch_deinterleave(cB, iB); + + return xcch_decode_cB(l2_data, cB); +} + +int +xcch_encode_cB(ubit_t *cB, uint8_t *l2_data) +{ + ubit_t conv[224]; + + osmo_pbit2ubit_ext(conv, 0, l2_data, 0, 184, 1); + + osmo_crc64gen_set_bits(&xcch_crc40, conv, 184, conv+184); + + osmo_conv_encode(&conv_xcch, conv, cB); + + return 0; +} + +int +xcch_encode(ubit_t *bursts, uint8_t *l2_data) +{ + ubit_t iB[456], cB[456], hl = 1, hn = 1; + int i; + + xcch_encode_cB(cB, l2_data); + + xcch_interleave(cB, iB); + + for (i=0; i<4; i++) + xcch_burst_map(&iB[i * 114], &bursts[i * 116], &hl, &hn); + + return 0; +} diff --git a/src/osmo-bts-trx/xcch.h b/src/osmo-bts-trx/xcch.h new file mode 100644 index 000000000..8e7d39296 --- /dev/null +++ b/src/osmo-bts-trx/xcch.h @@ -0,0 +1,10 @@ +#ifndef _XCCH_H +#define _XCCH_H + +int xcch_decode_cB(uint8_t *l2_data, sbit_t *cB); +int xcch_decode(uint8_t *l2_data, sbit_t *bursts); +int xcch_encode_cB(ubit_t *cB, uint8_t *l2_data); +int xcch_encode(ubit_t *bursts, uint8_t *l2_data); + +#endif /* _XCCH_H */ +