aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bts-sysmo
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bts-sysmo')
-rw-r--r--src/osmo-bts-sysmo/Makefile.am43
-rw-r--r--src/osmo-bts-sysmo/calib_file.c475
-rw-r--r--src/osmo-bts-sysmo/calib_fixup.c101
-rw-r--r--src/osmo-bts-sysmo/eeprom.c1804
-rw-r--r--src/osmo-bts-sysmo/eeprom.h304
-rw-r--r--src/osmo-bts-sysmo/femtobts.c370
-rw-r--r--src/osmo-bts-sysmo/femtobts.h110
-rw-r--r--src/osmo-bts-sysmo/hw_misc.c113
-rw-r--r--src/osmo-bts-sysmo/hw_misc.h12
-rw-r--r--src/osmo-bts-sysmo/l1_fwd.h5
-rw-r--r--src/osmo-bts-sysmo/l1_fwd_main.c236
-rw-r--r--src/osmo-bts-sysmo/l1_if.c1899
-rw-r--r--src/osmo-bts-sysmo/l1_if.h171
-rw-r--r--src/osmo-bts-sysmo/l1_transp.h14
-rw-r--r--src/osmo-bts-sysmo/l1_transp_fwd.c152
-rw-r--r--src/osmo-bts-sysmo/l1_transp_hw.c329
-rw-r--r--src/osmo-bts-sysmo/main.c199
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts-calib.c537
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts-layer1.c800
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts-layer1.h45
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_eeprom.h44
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr.c336
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr.h122
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c384
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c547
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c186
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c321
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c531
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_misc.c275
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_misc.h65
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_nl.c120
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_nl.h24
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_par.c382
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_par.h38
-rw-r--r--src/osmo-bts-sysmo/misc/sysmobts_util.c256
-rw-r--r--src/osmo-bts-sysmo/oml.c1963
-rw-r--r--src/osmo-bts-sysmo/oml_router.c129
-rw-r--r--src/osmo-bts-sysmo/oml_router.h13
-rw-r--r--src/osmo-bts-sysmo/sysmobts_ctrl.c274
-rw-r--r--src/osmo-bts-sysmo/sysmobts_vty.c542
-rw-r--r--src/osmo-bts-sysmo/tch.c684
-rw-r--r--src/osmo-bts-sysmo/utils.c116
-rw-r--r--src/osmo-bts-sysmo/utils.h12
43 files changed, 15083 insertions, 0 deletions
diff --git a/src/osmo-bts-sysmo/Makefile.am b/src/osmo-bts-sysmo/Makefile.am
new file mode 100644
index 00000000..4901ea3c
--- /dev/null
+++ b/src/osmo-bts-sysmo/Makefile.am
@@ -0,0 +1,43 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(SYSMOBTS_INCDIR)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS)
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS)
+
+EXTRA_DIST = misc/sysmobts_mgr.h misc/sysmobts_misc.h misc/sysmobts_par.h \
+ misc/sysmobts_eeprom.h misc/sysmobts_nl.h femtobts.h hw_misc.h \
+ misc/sysmobts-layer1.h \
+ l1_fwd.h l1_if.h l1_transp.h eeprom.h utils.h oml_router.h
+
+bin_PROGRAMS = osmo-bts-sysmo osmo-bts-sysmo-remote l1fwd-proxy sysmobts-mgr sysmobts-util
+
+COMMON_SOURCES = main.c femtobts.c l1_if.c oml.c sysmobts_vty.c tch.c hw_misc.c calib_file.c \
+ eeprom.c calib_fixup.c utils.c misc/sysmobts_par.c oml_router.c sysmobts_ctrl.c
+
+osmo_bts_sysmo_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
+osmo_bts_sysmo_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+osmo_bts_sysmo_remote_SOURCES = $(COMMON_SOURCES) l1_transp_fwd.c
+osmo_bts_sysmo_remote_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+l1fwd_proxy_SOURCES = l1_fwd_main.c l1_transp_hw.c
+l1fwd_proxy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+if ENABLE_SYSMOBTS_CALIB
+bin_PROGRAMS = sysmobts-calib
+
+sysmobts_calib_SOURCES = misc/sysmobts-calib.c misc/sysmobts-layer1.c
+sysmobts_calib_LDADD = -lrt $(COMMON_LDADD)
+endif
+
+sysmobts_mgr_SOURCES = \
+ misc/sysmobts_mgr.c misc/sysmobts_misc.c \
+ misc/sysmobts_par.c misc/sysmobts_nl.c \
+ misc/sysmobts_mgr_2050.c \
+ misc/sysmobts_mgr_vty.c \
+ misc/sysmobts_mgr_nl.c \
+ misc/sysmobts_mgr_temp.c \
+ misc/sysmobts_mgr_calib.c \
+ eeprom.c
+sysmobts_mgr_LDADD = $(LIBGPS_LIBS) $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+sysmobts_util_SOURCES = misc/sysmobts_util.c misc/sysmobts_par.c eeprom.c
+sysmobts_util_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/src/osmo-bts-sysmo/calib_file.c b/src/osmo-bts-sysmo/calib_file.c
new file mode 100644
index 00000000..2f723dd0
--- /dev/null
+++ b/src/osmo-bts-sysmo/calib_file.c
@@ -0,0 +1,475 @@
+/* sysmocom femtobts L1 calibration file routines*/
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1const.h>
+
+#include "l1_if.h"
+#include "femtobts.h"
+#include "eeprom.h"
+#include "utils.h"
+
+struct calib_file_desc {
+ const char *fname;
+ GsmL1_FreqBand_t band;
+ int uplink;
+ int rx;
+};
+
+static const struct calib_file_desc calib_files[] = {
+ {
+ .fname = "calib_rxu_850.cfg",
+ .band = GsmL1_FreqBand_850,
+ .uplink = 1,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxu_900.cfg",
+ .band = GsmL1_FreqBand_900,
+ .uplink = 1,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxu_1800.cfg",
+ .band = GsmL1_FreqBand_1800,
+ .uplink = 1,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxu_1900.cfg",
+ .band = GsmL1_FreqBand_1900,
+ .uplink = 1,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxd_850.cfg",
+ .band = GsmL1_FreqBand_850,
+ .uplink = 0,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxd_900.cfg",
+ .band = GsmL1_FreqBand_900,
+ .uplink = 0,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxd_1800.cfg",
+ .band = GsmL1_FreqBand_1800,
+ .uplink = 0,
+ .rx = 1,
+ }, {
+ .fname = "calib_rxd_1900.cfg",
+ .band = GsmL1_FreqBand_1900,
+ .uplink = 0,
+ .rx = 1,
+ }, {
+ .fname = "calib_tx_850.cfg",
+ .band = GsmL1_FreqBand_850,
+ .uplink = 0,
+ .rx = 0,
+ }, {
+ .fname = "calib_tx_900.cfg",
+ .band = GsmL1_FreqBand_900,
+ .uplink = 0,
+ .rx = 0,
+ }, {
+ .fname = "calib_tx_1800.cfg",
+ .band = GsmL1_FreqBand_1800,
+ .uplink = 0,
+ .rx = 0,
+ }, {
+ .fname = "calib_tx_1900.cfg",
+ .band = GsmL1_FreqBand_1900,
+ .uplink = 0,
+ .rx = 0,
+
+ },
+};
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+static const unsigned int arrsize_by_band[] = {
+ [GsmL1_FreqBand_850] = 124,
+ [GsmL1_FreqBand_900] = 194,
+ [GsmL1_FreqBand_1800] = 374,
+ [GsmL1_FreqBand_1900] = 299
+};
+
+static float read_float(FILE *in)
+{
+ int rc;
+ float f = 0.0f;
+
+ rc = fscanf(in, "%f\n", &f);
+ if (rc != 1)
+ LOGP(DL1C, LOGL_ERROR,
+ "Reading a float from calib data failed.\n");
+ return f;
+}
+
+static int read_int(FILE *in)
+{
+ int rc;
+ int i = 0;
+
+ rc = fscanf(in, "%d\n", &i);
+ if (rc != 1)
+ LOGP(DL1C, LOGL_ERROR,
+ "Reading an int from calib data failed.\n");
+ return i;
+}
+
+/* some particular units have calibration data that is incompatible with
+ * firmware >= 3.3, so we need to alter it as follows: */
+static const float delta_by_band[Num_GsmL1_FreqBand] = {
+ [GsmL1_FreqBand_850] = -2.5f,
+ [GsmL1_FreqBand_900] = -2.0f,
+ [GsmL1_FreqBand_1800] = -8.0f,
+ [GsmL1_FreqBand_1900] = -12.0f,
+};
+
+extern const uint8_t fixup_macs[95][6];
+
+static void determine_fixup(struct femtol1_hdl *fl1h)
+{
+ uint8_t macaddr[6];
+ int rc, i;
+
+ rc = eeprom_ReadEthAddr(macaddr);
+ if (rc != EEPROM_SUCCESS) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Unable to read Ethenet MAC from EEPROM\n");
+ return;
+ }
+
+ /* assume no fixup is needed */
+ fl1h->fixup_needed = FIXUP_NOT_NEEDED;
+
+ if (fl1h->hw_info.dsp_version[0] < 3 ||
+ (fl1h->hw_info.dsp_version[0] == 3 &&
+ fl1h->hw_info.dsp_version[1] < 3)) {
+ LOGP(DL1C, LOGL_NOTICE, "No calibration table fix-up needed, "
+ "firmware < 3.3\n");
+ return;
+ }
+
+ for (i = 0; i < sizeof(fixup_macs)/6; i++) {
+ if (!memcmp(fixup_macs[i], macaddr, 6)) {
+ fl1h->fixup_needed = FIXUP_NEEDED;
+ break;
+ }
+ }
+
+ LOGP(DL1C, LOGL_NOTICE, "MAC Address is %02x:%02x:%02x:%02x:%02x:%02x -> %s\n",
+ macaddr[0], macaddr[1], macaddr[2], macaddr[3],
+ macaddr[4], macaddr[5],
+ fl1h->fixup_needed == FIXUP_NEEDED ? "FIXUP" : "NO FIXUP");
+}
+
+static int fixup_needed(struct femtol1_hdl *fl1h)
+{
+ if (fl1h->fixup_needed == FIXUP_UNITILIAZED)
+ determine_fixup(fl1h);
+
+ return fl1h->fixup_needed == FIXUP_NEEDED;
+}
+#endif /* API 2.4.0 */
+
+static void calib_fixup_rx(struct femtol1_hdl *fl1h, SuperFemto_Prim_t *prim)
+{
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+ SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq;
+
+ if (fixup_needed(fl1h))
+ rx->fExtRxGain += delta_by_band[rx->freqBand];
+#endif
+}
+
+static int calib_file_read(const char *path, const struct calib_file_desc *desc,
+ SuperFemto_Prim_t *prim)
+{
+ FILE *in;
+ char fname[PATH_MAX];
+
+ fname[0] = '\0';
+ snprintf(fname, sizeof(fname)-1, "%s/%s", path, desc->fname);
+ fname[sizeof(fname)-1] = '\0';
+
+ in = fopen(fname, "r");
+ if (!in) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to open '%s' for calibration data.\n", fname);
+ return -1;
+ }
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+ int i;
+ if (desc->rx) {
+ SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq;
+ memset(rx, 0, sizeof(*rx));
+
+ prim->id = SuperFemto_PrimId_SetRxCalibTblReq;
+
+ rx->freqBand = desc->band;
+ rx->bUplink = desc->uplink;
+
+ rx->fExtRxGain = read_float(in);
+ rx->fRxMixGainCorr = read_float(in);
+
+ for (i = 0; i < ARRAY_SIZE(rx->fRxLnaGainCorr); i++)
+ rx->fRxLnaGainCorr[i] = read_float(in);
+
+ for (i = 0; i < arrsize_by_band[desc->band]; i++)
+ rx->fRxRollOffCorr[i] = read_float(in);
+
+ if (desc->uplink) {
+ rx->u8IqImbalMode = read_int(in);
+ printf("%s: u8IqImbalMode=%d\n", desc->fname, rx->u8IqImbalMode);
+
+ for (i = 0; i < ARRAY_SIZE(rx->u16IqImbalCorr); i++)
+ rx->u16IqImbalCorr[i] = read_int(in);
+ }
+ } else {
+ SuperFemto_SetTxCalibTblReq_t *tx = &prim->u.setTxCalibTblReq;
+ memset(tx, 0, sizeof(*tx));
+
+ prim->id = SuperFemto_PrimId_SetTxCalibTblReq;
+
+ tx->freqBand = desc->band;
+
+ for (i = 0; i < ARRAY_SIZE(tx->fTxGainGmsk); i++)
+ tx->fTxGainGmsk[i] = read_float(in);
+
+ tx->fTx8PskCorr = read_float(in);
+
+ for (i = 0; i < ARRAY_SIZE(tx->fTxExtAttCorr); i++)
+ tx->fTxExtAttCorr[i] = read_float(in);
+
+ for (i = 0; i < arrsize_by_band[desc->band]; i++)
+ tx->fTxRollOffCorr[i] = read_float(in);
+ }
+#else
+#warning Format of calibration tables before API version 2.4.0 not supported
+#endif
+ fclose(in);
+
+ return 0;
+}
+
+static int calib_eeprom_read(const struct calib_file_desc *desc, SuperFemto_Prim_t *prim)
+{
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+ eeprom_Error_t eerr;
+ int i;
+ if (desc->rx) {
+ SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq;
+ eeprom_RxCal_t rx_cal;
+
+ memset(rx, 0, sizeof(*rx));
+
+ prim->id = SuperFemto_PrimId_SetRxCalibTblReq;
+
+ rx->freqBand = desc->band;
+ rx->bUplink = desc->uplink;
+
+ eerr = eeprom_ReadRxCal(desc->band, desc->uplink, &rx_cal);
+ if (eerr != EEPROM_SUCCESS) {
+ LOGP(DL1C, LOGL_ERROR, "Error reading RxCalibration "
+ "from EEPROM, band=%d, ul=%d, err=%d\n",
+ desc->band, desc->uplink, eerr);
+ return -EIO;
+ }
+
+ rx->fExtRxGain = rx_cal.fExtRxGain;
+ rx->fRxMixGainCorr = rx_cal.fRxMixGainCorr;
+
+ for (i = 0; i < ARRAY_SIZE(rx->fRxLnaGainCorr); i++)
+ rx->fRxLnaGainCorr[i] = rx_cal.fRxLnaGainCorr[i];
+
+ for (i = 0; i < arrsize_by_band[desc->band]; i++)
+ rx->fRxRollOffCorr[i] = rx_cal.fRxRollOffCorr[i];
+
+ if (desc->uplink) {
+ rx->u8IqImbalMode = rx_cal.u8IqImbalMode;
+
+ for (i = 0; i < ARRAY_SIZE(rx->u16IqImbalCorr); i++)
+ rx->u16IqImbalCorr[i] = rx_cal.u16IqImbalCorr[i];
+ }
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0)
+ rx->u8DspMajVer = rx_cal.u8DspMajVer;
+ rx->u8DspMinVer = rx_cal.u8DspMinVer;
+ rx->u8FpgaMajVer = rx_cal.u8FpgaMajVer;
+ rx->u8FpgaMinVer = rx_cal.u8FpgaMinVer;
+#endif
+ } else {
+ SuperFemto_SetTxCalibTblReq_t *tx = &prim->u.setTxCalibTblReq;
+ eeprom_TxCal_t tx_cal;
+
+ memset(tx, 0, sizeof(*tx));
+
+ prim->id = SuperFemto_PrimId_SetTxCalibTblReq;
+ tx->freqBand = desc->band;
+
+ eerr = eeprom_ReadTxCal(desc->band, &tx_cal);
+ if (eerr != EEPROM_SUCCESS) {
+ LOGP(DL1C, LOGL_ERROR, "Error reading TxCalibration "
+ "from EEPROM, band=%d, err=%d\n",
+ desc->band, eerr);
+ return -EIO;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tx->fTxGainGmsk); i++)
+ tx->fTxGainGmsk[i] = tx_cal.fTxGainGmsk[i];
+
+ tx->fTx8PskCorr = tx_cal.fTx8PskCorr;
+
+ for (i = 0; i < ARRAY_SIZE(tx->fTxExtAttCorr); i++)
+ tx->fTxExtAttCorr[i] = tx_cal.fTxExtAttCorr[i];
+
+ for (i = 0; i < arrsize_by_band[desc->band]; i++)
+ tx->fTxRollOffCorr[i] = tx_cal.fTxRollOffCorr[i];
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0)
+ tx->u8DspMajVer = tx_cal.u8DspMajVer;
+ tx->u8DspMinVer = tx_cal.u8DspMinVer;
+ tx->u8FpgaMajVer = tx_cal.u8FpgaMajVer;
+ tx->u8FpgaMinVer = tx_cal.u8FpgaMinVer;
+#endif
+ }
+#endif
+
+ return 0;
+}
+
+/* determine next calibration file index based on supported bands */
+static int next_calib_file_idx(uint32_t band_mask, int last_idx)
+{
+ int i;
+
+ for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) {
+ int band = band_femto2osmo(calib_files[i].band);
+ if (band < 0)
+ continue;
+ if (band_mask & band)
+ return i;
+ }
+ return -1;
+}
+
+/* iteratively download the calibration data into the L1 */
+
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data);
+
+/* send the calibration table for a single specified file */
+static int calib_file_send(struct femtol1_hdl *fl1h,
+ const struct calib_file_desc *desc)
+{
+ struct calib_send_state *st = &fl1h->st;
+ struct msgb *msg;
+ char *calib_path = fl1h->phy_inst->u.sysmobts.calib_path;
+ int rc;
+
+ msg = sysp_msgb_alloc();
+
+ if (calib_path)
+ rc = calib_file_read(calib_path, desc, msgb_sysprim(msg));
+ else
+ rc = calib_eeprom_read(desc, msgb_sysprim(msg));
+ if (rc < 0) {
+ msgb_free(msg);
+
+ /* still, we'd like to continue trying to load
+ * calibration for all other bands */
+ st->last_file_idx = next_calib_file_idx(fl1h->hw_info.band_support,
+ st->last_file_idx);
+ if (st->last_file_idx >= 0)
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ else
+ return rc;
+ }
+ calib_fixup_rx(fl1h, msgb_sysprim(msg));
+
+ return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL);
+}
+
+/* completion callback after every SetCalibTbl is confirmed */
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct calib_send_state *st = &fl1h->st;
+ char *calib_path = fl1h->phy_inst->u.sysmobts.calib_path;
+
+ LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded (src: %s)\n",
+ calib_files[st->last_file_idx].fname,
+ calib_path ? "file" : "eeprom");
+
+ msgb_free(l1_msg);
+
+ st->last_file_idx = next_calib_file_idx(fl1h->hw_info.band_support,
+ st->last_file_idx);
+ if (st->last_file_idx >= 0)
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ eeprom_free_resources();
+
+ return 0;
+}
+
+
+int calib_load(struct femtol1_hdl *fl1h)
+{
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0)
+ LOGP(DL1C, LOGL_ERROR, "L1 calibration is not supported on pre 2.4.0 firmware.\n");
+ return -1;
+#else
+ int idx = next_calib_file_idx(fl1h->hw_info.band_support, -1);
+ if (idx < 0) {
+ LOGP(DL1C, LOGL_ERROR, "No band_support?!?\n");
+ return -1;
+ }
+ return calib_file_send(fl1h, &calib_files[idx]);
+#endif
+}
+
+
+#if 0
+int main(int argc, char **argv)
+{
+ SuperFemto_Prim_t p;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(calib_files); i++) {
+ memset(&p, 0, sizeof(p));
+ calib_read_file(argv[1], &calib_files[i], &p);
+ }
+ exit(0);
+}
+#endif
diff --git a/src/osmo-bts-sysmo/calib_fixup.c b/src/osmo-bts-sysmo/calib_fixup.c
new file mode 100644
index 00000000..29dd34dd
--- /dev/null
+++ b/src/osmo-bts-sysmo/calib_fixup.c
@@ -0,0 +1,101 @@
+/* AUTOGENERATED, DO NOT EDIT */
+
+#include <stdint.h>
+
+const uint8_t fixup_macs[95][6] = {
+ { 0x00, 0x0D, 0xCC, 0x08, 0x02, 0x3B },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x31 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x32 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x33 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x34 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x35 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x36 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x37 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x38 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x39 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3A },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3C },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3D },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3E },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x40 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x41 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x42 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x43 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x44 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x45 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x46 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x47 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x48 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x49 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4A },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4B },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4C },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4D },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4E },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4F },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x50 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x51 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x52 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x53 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x55 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x56 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x57 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x58 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x59 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5A },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5B },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5C },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5D },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5E },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5F },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x60 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x97 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x98 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x99 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9A },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9B },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9C },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9D },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9E },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9F },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA0 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA1 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA3 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA4 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA5 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA6 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA7 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA8 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA9 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAA },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAB },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAC },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAD },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAE },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAF },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB0 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB1 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB2 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB3 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB4 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB5 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB6 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB7 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB8 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB9 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBA },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBB },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBC },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBE },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBF },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC0 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC1 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC3 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC6 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC7 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC8 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC9 },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCA },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCB },
+ { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCD },
+};
diff --git a/src/osmo-bts-sysmo/eeprom.c b/src/osmo-bts-sysmo/eeprom.c
new file mode 100644
index 00000000..472b78e2
--- /dev/null
+++ b/src/osmo-bts-sysmo/eeprom.c
@@ -0,0 +1,1804 @@
+// $Id: $
+/****************************************************************************
+ *
+ * **** I
+ * ****** ***
+ * ******* ****
+ * ******** **** **** **** ********* ******* **** ***********
+ * ********* **** **** **** ********* ************** *************
+ * **** ***** **** **** **** **** ***** ****** ***** ****
+ * **** ***** **** **** **** **** ***** **** **** ****
+ * **** ********* **** **** **** **** **** **** ****
+ * **** ******** **** ****I **** ***** ***** **** ****
+ * **** ****** ***** ****** ***** ****** ******* ****** *******
+ * **** **** ************ ****** ************* *************
+ * **** *** **** **** **** ***** **** ***** ****
+ * ****
+ * I N N O V A T I O N T O D A Y F O R T O M M O R O W ****
+ * ***
+ *
+ ************************************************************************//**
+ *
+ * @file eeprom.c
+ * @brief SuperFemto EEPROM interface.
+ *
+ * Author : Yves Godin
+ * Date : 2012
+ * $Revision: $
+ *
+ * Copyright (c) Nutaq. 2012
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ ***************************************************************************
+ *
+ * "$Revision: $"
+ * "$Name: $"
+ * "$Date: $"
+ *
+ ***************************************************************************/
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "eeprom.h"
+
+//#define DISP_ERROR 1
+
+#ifdef DISP_ERROR
+#define PERROR(x, args ...) fprintf(stderr, x, ## args)
+#else
+#define PERROR(x, args ...) do { } while (0)
+#endif
+
+/****************************************************************************
+ * Private constants *
+ ****************************************************************************/
+
+/**
+ * EEPROM device file
+ */
+#define EEPROM_DEV "/sys/bus/i2c/devices/i2c-1/1-0050/eeprom"
+
+/**
+ * EEPROM configuration start address
+ */
+#define EEPROM_CFG_START_ADDR 0x0100
+
+/**
+ * EEPROM configuration max size
+ */
+#define EEPROM_CFG_MAX_SIZE (0x2000 - EEPROM_CFG_START_ADDR)
+
+/**
+ * EEPROM config magic ID
+ */
+#define EEPROM_CFG_MAGIC_ID 0x53464548
+
+/**
+ * EEPROM header version
+ */
+#define EEPROM_HDR_V1 1
+#define EEPROM_HDR_V2 2
+
+/**
+ * EEPROM section ID
+ */
+typedef enum
+{
+ EEPROM_SID_SYSINFO = 0x1000, ///< System information
+ EEPROM_SID_RFCLOCK_CAL = 0x2000, ///< RF Clock Calibration
+ EEPROM_SID_GSM850_TXCAL = 0x3000, ///< GSM-850 TX Calibration Table
+ EEPROM_SID_GSM850_RXUCAL = 0x3010, ///< GSM-850 RX Uplink Calibration Table
+ EEPROM_SID_GSM850_RXDCAL = 0x3020, ///< GSM-850 RX Downlink Calibration Table
+ EEPROM_SID_GSM900_TXCAL = 0x3100, ///< GSM-900 TX Calibration Table
+ EEPROM_SID_GSM900_RXUCAL = 0x3110, ///< GSM-900 RX Uplink Calibration Table
+ EEPROM_SID_GSM900_RXDCAL = 0x3120, ///< GSM-900 RX Downlink Calibration Table
+ EEPROM_SID_DCS1800_TXCAL = 0x3200, ///< DCS-1800 TX Calibration Table
+ EEPROM_SID_DCS1800_RXUCAL = 0x3210, ///< DCS-1800 RX Uplink Calibration Table
+ EEPROM_SID_DCS1800_RXDCAL = 0x3220, ///< DCS-1800 RX Downlink Calibration Table
+ EEPROM_SID_PCS1900_TXCAL = 0x3300, ///< PCS-1900 TX Calibration Table
+ EEPROM_SID_PCS1900_RXUCAL = 0x3310, ///< PCS-1900 RX Uplink Calibration Table
+ EEPROM_SID_PCS1900_RXDCAL = 0x3320, ///< PCS-1900 RX Downlink Calibration Table
+ EEPROM_SID_ASSY = 0x3400 ///< Assembly information
+} eeprom_SID_t;
+
+/****************************************************************************
+ * Private types *
+ ****************************************************************************/
+
+/**
+ * TX calibration table (common part) V1
+ */
+typedef struct
+{
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ int16_t sfixTxGainGmsk[80]; ///< [Q10.5] Gain setting for GMSK output level from +50dBm to -29 dBm
+ int16_t sfixTx8PskCorr; ///< [Q6.9] Gain adjustment for 8 PSK (default to +3.25 dB)
+ int16_t sfixTxExtAttCorr[31]; ///< [Q6.9] Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB)
+ int16_t sfixTxRollOffCorr[0]; ///< [Q6.9] Gain correction for each ARFCN
+} __attribute__((packed)) eeprom_CfgTxCal_t;
+
+/**
+ * RX calibration table (common part) V1
+ */
+typedef struct
+{
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ uint16_t u16IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto)
+ uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation
+
+ int16_t sfixExtRxGain; ///< [Q6.9] External RX gain
+ int16_t sfixRxMixGainCorr; ///< [Q6.9] Mixer gain error compensation
+ int16_t sfixRxLnaGainCorr[3]; ///< [Q6.9] LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ int16_t sfixRxRollOffCorr[0]; ///< [Q6.9] Frequency roll-off compensation
+} __attribute__((packed)) eeprom_CfgRxCal_t;
+
+/**
+ * TX calibration table (common part) V2
+ */
+typedef struct
+{
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+ uint8_t u8DspMajVer; ///< DSP firmware major version
+ uint8_t u8DspMinVer; ///< DSP firmware minor version
+ uint8_t u8FpgaMajVer; ///< FPGA firmware major version
+ uint8_t u8FpgaMinVer; ///< FPGA firmware minor version
+ int16_t sfixTxGainGmsk[80]; ///< [Q10.5] Gain setting for GMSK output level from +50dBm to -29 dBm
+ int16_t sfixTx8PskCorr; ///< [Q6.9] Gain adjustment for 8 PSK (default to +3.25 dB)
+ int16_t sfixTxExtAttCorr[31]; ///< [Q6.9] Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB)
+ int16_t sfixTxRollOffCorr[0]; ///< [Q6.9] Gain correction for each ARFCN
+} __attribute__((packed)) eeprom_CfgTxCalV2_t;
+
+/**
+ * RX calibration table (common part) V2
+ */
+typedef struct
+{
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+ uint8_t u8DspMajVer; ///< DSP firmware major version
+ uint8_t u8DspMinVer ; ///< DSP firmware minor version
+ uint8_t u8FpgaMajVer; ///< FPGA firmware major version
+ uint8_t u8FpgaMinVer; ///< FPGA firmware minor version
+ uint16_t u16IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto)
+ uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation
+ int16_t sfixExtRxGain; ///< [Q6.9] External RX gain
+ int16_t sfixRxMixGainCorr; ///< [Q6.9] Mixer gain error compensation
+ int16_t sfixRxLnaGainCorr[3]; ///< [Q6.9] LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ int16_t sfixRxRollOffCorr[0]; ///< [Q6.9] Frequency roll-off compensation
+} __attribute__((packed)) eeprom_CfgRxCalV2_t;
+
+
+/**
+ * EEPROM configuration area format
+ */
+typedef struct
+{
+ struct
+ {
+ uint32_t u32MagicId; ///< Magic ID (0x53464548)
+ uint32_t u16Version : 16; ///< Header format version (v1)
+ uint32_t : 16; ///< unused
+ } hdr;
+
+ union
+ {
+ /** EEPROM Format V1 */
+ struct
+ {
+ /** System information */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ char szSn[16]; ///< Serial number
+ uint32_t u8Rev : 8; ///< Board revision
+ uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknows)
+ uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknows)
+ uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t : 12; ///< unused
+ } __attribute__((packed)) sysInfo;
+
+ /** RF Clock configuration */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ int i24ClkCor :24; ///< Clock correction value in PPB.
+ uint32_t u8ClkSrc : 8; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge)
+ } __attribute__((packed)) rfClk;
+
+ /** GSM-850 TX Calibration Table */
+ eeprom_CfgTxCal_t gsm850TxCal;
+ uint16_t __gsm850TxCalMem[124];
+
+ /** GSM-850 RX Uplink Calibration Table */
+ eeprom_CfgRxCal_t gsm850RxuCal;
+ uint16_t __gsm850RxuCalMem[124];
+
+ /** GSM-850 RX Downlink Calibration Table */
+ eeprom_CfgRxCal_t gsm850RxdCal;
+ uint16_t __gsm850RxdCalMem[124];
+
+ /** GSM-900 TX Calibration Table */
+ eeprom_CfgTxCal_t gsm900TxCal;
+ uint16_t __gsm900TxCalMem[194];
+
+ /** GSM-900 RX Uplink Calibration Table */
+ eeprom_CfgRxCal_t gsm900RxuCal;
+ uint16_t __gsm900RxuCalMem[194];
+
+ /** GSM-900 RX Downlink Calibration Table */
+ eeprom_CfgRxCal_t gsm900RxdCal;
+ uint16_t __gsm900RxdCalMem[194];
+
+ /** DCS-1800 TX Calibration Table */
+ eeprom_CfgTxCal_t dcs1800TxCal;
+ uint16_t __dcs1800TxCalMem[374];
+
+ /** DCS-1800 RX Uplink Calibration Table */
+ eeprom_CfgRxCal_t dcs1800RxuCal;
+ uint16_t __dcs1800RxuCalMem[374];
+
+ /** DCS-1800 RX Downlink Calibration Table */
+ eeprom_CfgRxCal_t dcs1800RxdCal;
+ uint16_t __dcs1800RxdCalMem[374];
+
+ /** PCS-1900 TX Calibration Table */
+ eeprom_CfgTxCal_t pcs1900TxCal;
+ uint16_t __pcs1900TxCalMem[299];
+
+ /** PCS-1900 RX Uplink Calibration Table */
+ eeprom_CfgRxCal_t pcs1900RxuCal;
+ uint16_t __pcs1900RxuCalMem[299];
+
+ /** PCS-1900 RX Downlink Calibration Table */
+ eeprom_CfgRxCal_t pcs1900RxdCal;
+ uint16_t __pcs1900RxdCalMem[299];
+
+ } __attribute__((packed)) v1;
+
+ /** EEPROM Format V2 */
+ struct
+ {
+ /** System information */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+ char szSn[16]; ///< Serial number
+ uint32_t u8Rev : 8; ///< Board revision
+ uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknows)
+ uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknows)
+ uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknows)
+ uint32_t : 12; ///< unused
+ } __attribute__((packed)) sysInfo;
+
+ /** RF Clock configuration */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+
+ int i24ClkCor :24; ///< Clock correction value in PPB.
+ uint32_t u8ClkSrc : 8; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge)
+ } __attribute__((packed)) rfClk;
+
+ /** GSM-850 TX Calibration Table */
+ eeprom_CfgTxCalV2_t gsm850TxCalV2;
+ uint16_t __gsm850TxCalMemV2[124];
+
+ /** GSM-850 RX Uplink Calibration Table */
+ eeprom_CfgRxCalV2_t gsm850RxuCalV2;
+ uint16_t __gsm850RxuCalMemV2[124];
+
+ /** GSM-850 RX Downlink Calibration Table */
+ eeprom_CfgRxCalV2_t gsm850RxdCalV2;
+ uint16_t __gsm850RxdCalMemV2[124];
+
+ /** GSM-900 TX Calibration Table */
+ eeprom_CfgTxCalV2_t gsm900TxCalV2;
+ uint16_t __gsm900TxCalMemV2[194];
+
+ /** GSM-900 RX Uplink Calibration Table */
+ eeprom_CfgRxCalV2_t gsm900RxuCalV2;
+ uint16_t __gsm900RxuCalMemV2[194];
+
+ /** GSM-900 RX Downlink Calibration Table */
+ eeprom_CfgRxCalV2_t gsm900RxdCalV2;
+ uint16_t __gsm900RxdCalMemV2[194];
+
+ /** DCS-1800 TX Calibration Table */
+ eeprom_CfgTxCalV2_t dcs1800TxCalV2;
+ uint16_t __dcs1800TxCalMemV2[374];
+
+ /** DCS-1800 RX Uplink Calibration Table */
+ eeprom_CfgRxCalV2_t dcs1800RxuCalV2;
+ uint16_t __dcs1800RxuCalMemV2[374];
+
+ /** DCS-1800 RX Downlink Calibration Table */
+ eeprom_CfgRxCalV2_t dcs1800RxdCalV2;
+ uint16_t __dcs1800RxdCalMemV2[374];
+
+ /** PCS-1900 TX Calibration Table */
+ eeprom_CfgTxCalV2_t pcs1900TxCalV2;
+ uint16_t __pcs1900TxCalMemV2[299];
+
+ /** PCS-1900 RX Uplink Calibration Table */
+ eeprom_CfgRxCalV2_t pcs1900RxuCalV2;
+ uint16_t __pcs1900RxuCalMemV2[299];
+
+ /** PCS-1900 RX Downlink Calibration Table */
+ eeprom_CfgRxCalV2_t pcs1900RxdCalV2;
+ uint16_t __pcs1900RxdCalMemV2[299];
+
+ /** Assembly information */
+ struct
+ {
+ uint16_t u16SectionID; ///< Section ID
+ uint16_t u16Crc; ///< Parity
+ uint32_t u32Time; ///< Epoch time
+ char szSn[16]; ///< System serial number
+ char szPartNum[20]; ///< System part number
+ uint8_t u8TsID ; ///< Test station ID
+ uint8_t u8TstVer ; ///< Test version
+ uint8_t u8PaType; ///< PA type (0: None, 1-254 supported, 255 ; Unknown)
+ uint8_t u8PaBand; ///< PA GSM band (0: Unknown, 1: 850 MHz, 2: 900 MHz, 4: 1800 MHz, 8: 1900 MHz)
+ uint8_t u8PaMajVer; ///< PA major version
+ uint8_t u8PaMinVer; ///< PA minor version
+ } __attribute__((packed)) assyInfo;
+ } __attribute__((packed)) v2;
+ } __attribute__((packed)) cfg;
+} __attribute__((packed)) eeprom_Cfg_t;
+
+
+
+/****************************************************************************
+ * Private routine prototypes *
+ ****************************************************************************/
+
+static int eeprom_read( int addr, int size, char *pBuff );
+static int eeprom_write( int addr, int size, const char *pBuff );
+static uint16_t eeprom_crc( uint8_t *pu8Data, int len );
+static eeprom_Cfg_t *eeprom_cached_config(void);
+
+
+/****************************************************************************
+ * Public functions *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Function : eeprom_ResetCfg
+ ************************************************************************//**
+ *
+ * This function reset the content of the EEPROM config area.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ResetCfg( void )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Clear the structure
+ memset( &ee, 0xFF, sizeof(eeprom_Cfg_t) );
+
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) )
+ {
+ return EEPROM_ERR_DEVICE;
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+eeprom_Error_t eeprom_ReadEthAddr( uint8_t *ethaddr )
+{
+ int err;
+
+ err = eeprom_read(0, 6, (char *) ethaddr);
+ if ( err != 6 )
+ {
+ return EEPROM_ERR_DEVICE;
+ }
+ return EEPROM_SUCCESS;
+}
+
+/****************************************************************************
+ * Function : eeprom_ReadSysInfo
+ ************************************************************************//**
+ *
+ * This function reads the system information from the EEPROM.
+ *
+ * @param [inout] pTime
+ * Pointer to a system info structure.
+ *
+ * @param [inout] pSysInfo
+ * Pointer to a system info structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadSysInfo( eeprom_SysInfo_t *pSysInfo )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ PERROR( "Invalid EEPROM format\n" );
+ return EEPROM_ERR_INVALID;
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V1:
+ case EEPROM_HDR_V2:
+ {
+ // Get a copy of the EEPROM section
+ err = eeprom_read( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.sysInfo), sizeof(ee.cfg.v1.sysInfo), (char *)&ee.cfg.v1.sysInfo );
+ if ( err != sizeof(ee.cfg.v1.sysInfo) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the ID
+ if ( ee.cfg.v1.sysInfo.u16SectionID != EEPROM_SID_SYSINFO )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&ee.cfg.v1.sysInfo.u32Time, sizeof(ee.cfg.v1.sysInfo) - 2 * sizeof(uint16_t) ) != ee.cfg.v1.sysInfo.u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the content of the section
+ memcpy( (void *)pSysInfo->szSn, ee.cfg.v1.sysInfo.szSn, sizeof(pSysInfo->szSn) );
+ pSysInfo->u8Rev = ee.cfg.v1.sysInfo.u8Rev;
+ pSysInfo->u8Tcxo = ee.cfg.v1.sysInfo.u2Tcxo;
+ pSysInfo->u8Ocxo = ee.cfg.v1.sysInfo.u2Ocxo;
+ pSysInfo->u8GSM850 = ee.cfg.v1.sysInfo.u2GSM850;
+ pSysInfo->u8GSM900 = ee.cfg.v1.sysInfo.u2GSM900;
+ pSysInfo->u8DCS1800 = ee.cfg.v1.sysInfo.u2DCS1800;
+ pSysInfo->u8PCS1900 = ee.cfg.v1.sysInfo.u2PCS1900;
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_WriteSysInfo
+ ************************************************************************//**
+ *
+ * This function writes the system information to the EEPROM.
+ *
+ * @param [in] pSysInfo
+ * Pointer to the system info structure to be written.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteSysInfo( const eeprom_SysInfo_t *pSysInfo )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V2:
+ {
+ ee.cfg.v1.sysInfo.u16SectionID = EEPROM_SID_SYSINFO;
+ ee.cfg.v1.sysInfo.u16Crc = 0;
+ ee.cfg.v1.sysInfo.u32Time = time(NULL);
+
+ // Compress the info
+ memcpy( ee.cfg.v1.sysInfo.szSn, pSysInfo->szSn, sizeof(ee.cfg.v1.sysInfo.szSn) );
+ ee.cfg.v1.sysInfo.u8Rev = pSysInfo->u8Rev;
+ ee.cfg.v1.sysInfo.u2Tcxo = pSysInfo->u8Tcxo;
+ ee.cfg.v1.sysInfo.u2Ocxo = pSysInfo->u8Ocxo;
+ ee.cfg.v1.sysInfo.u2GSM850 = pSysInfo->u8GSM850;
+ ee.cfg.v1.sysInfo.u2GSM900 = pSysInfo->u8GSM900;
+ ee.cfg.v1.sysInfo.u2DCS1800 = pSysInfo->u8DCS1800;
+ ee.cfg.v1.sysInfo.u2PCS1900 = pSysInfo->u8PCS1900;
+
+ // Add the CRC
+ ee.cfg.v1.sysInfo.u16Crc = eeprom_crc( (uint8_t *)&ee.cfg.v1.sysInfo.u32Time, sizeof(ee.cfg.v1.sysInfo) - 2 * sizeof(uint16_t) );
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.sysInfo), sizeof(ee.cfg.v1.sysInfo), (const char *) &ee.cfg.v1.sysInfo );
+ if ( err != sizeof(ee.cfg.v1.sysInfo) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_ReadRfClockCal
+ ************************************************************************//**
+ *
+ * This function reads the RF clock calibration data from the EEPROM.
+ *
+ * @param [inout] pRfClockCal
+ * Pointer to a RF clock calibration structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadRfClockCal( eeprom_RfClockCal_t *pRfClockCal )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee), (char *) &ee );
+ if ( err != sizeof(ee) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ PERROR( "Invalid EEPROM format\n" );
+ return EEPROM_ERR_INVALID;
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V1:
+ case EEPROM_HDR_V2:
+ {
+ // Get a copy of the EEPROM section
+ err = eeprom_read( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.rfClk), sizeof(ee.cfg.v1.rfClk), (char *)&ee.cfg.v1.rfClk );
+ if ( err != sizeof(ee.cfg.v1.rfClk) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the ID
+ if ( ee.cfg.v1.rfClk.u16SectionID != EEPROM_SID_RFCLOCK_CAL )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&ee.cfg.v1.rfClk.u32Time, sizeof(ee.cfg.v1.rfClk) - 2 * sizeof(uint16_t) ) != ee.cfg.v1.rfClk.u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the content of the section
+ pRfClockCal->iClkCor = ee.cfg.v1.rfClk.i24ClkCor;
+ pRfClockCal->u8ClkSrc = ee.cfg.v1.rfClk.u8ClkSrc;
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_WriteRfClockCal
+ ************************************************************************//**
+ *
+ * This function writes the RF clock calibration data to the EEPROM.
+ *
+ * @param [in] pSysInfo
+ * Pointer to the system info structure to be written.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteRfClockCal( const eeprom_RfClockCal_t *pRfClockCal )
+{
+ int err;
+ eeprom_Cfg_t ee;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V2:
+ {
+ ee.cfg.v1.rfClk.u16SectionID = EEPROM_SID_RFCLOCK_CAL;
+ ee.cfg.v1.rfClk.u16Crc = 0;
+ ee.cfg.v1.rfClk.u32Time = time(NULL);
+
+ // Compress the info
+ ee.cfg.v1.rfClk.i24ClkCor = pRfClockCal->iClkCor;
+ ee.cfg.v1.rfClk.u8ClkSrc = pRfClockCal->u8ClkSrc;
+
+ // Add the CRC
+ ee.cfg.v1.rfClk.u16Crc = eeprom_crc( (uint8_t *)&ee.cfg.v1.rfClk.u32Time, sizeof(ee.cfg.v1.rfClk) - 2 * sizeof(uint16_t) );
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.rfClk), sizeof(ee.cfg.v1.rfClk), (const char *) &ee.cfg.v1.rfClk );
+ if ( err != sizeof(ee.cfg.v1.rfClk) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_ReadTxCal
+ ************************************************************************//**
+ *
+ * This function reads the TX calibration tables for the specified band from
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [inout] pTxCal
+ * Pointer to a TX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadTxCal( int iBand, eeprom_TxCal_t *pTxCal )
+{
+ int i;
+ int size;
+ int nArfcn;
+ eeprom_Cfg_t *ee = eeprom_cached_config();
+ eeprom_SID_t sId;
+ eeprom_CfgTxCal_t *pCfgTxCal = NULL;
+ eeprom_CfgTxCalV2_t *pCfgTxCalV2 = NULL;
+
+ // Get a copy of the EEPROM header
+ if (!ee)
+ {
+ PERROR( "Reading cached content failed.\n" );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ switch ( ee->hdr.u16Version )
+ {
+ case EEPROM_HDR_V1:
+ {
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ sId = EEPROM_SID_GSM850_TXCAL;
+ pCfgTxCal = &ee->cfg.v1.gsm850TxCal;
+ size = sizeof(ee->cfg.v1.gsm850TxCal) + sizeof(ee->cfg.v1.__gsm850TxCalMem);
+ break;
+ case 1:
+ nArfcn = 194;
+ sId = EEPROM_SID_GSM900_TXCAL;
+ pCfgTxCal = &ee->cfg.v1.gsm900TxCal;
+ size = sizeof(ee->cfg.v1.gsm900TxCal) + sizeof(ee->cfg.v1.__gsm900TxCalMem);
+ break;
+ case 2:
+ nArfcn = 374;
+ sId = EEPROM_SID_DCS1800_TXCAL;
+ pCfgTxCal = &ee->cfg.v1.dcs1800TxCal;
+ size = sizeof(ee->cfg.v1.dcs1800TxCal) + sizeof(ee->cfg.v1.__dcs1800TxCalMem);
+ break;
+ case 3:
+ nArfcn = 299;
+ sId = EEPROM_SID_PCS1900_TXCAL;
+ pCfgTxCal = &ee->cfg.v1.pcs1900TxCal;
+ size = sizeof(ee->cfg.v1.pcs1900TxCal) + sizeof(ee->cfg.v1.__pcs1900TxCalMem);
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ // Validate the ID
+ if ( pCfgTxCal->u16SectionID != sId )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&pCfgTxCal->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgTxCal->u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the content of the section
+ for ( i = 0; i < 80; i++ )
+ {
+ pTxCal->fTxGainGmsk[i] = (float)pCfgTxCal->sfixTxGainGmsk[i] * 0.03125f;
+ }
+ pTxCal->fTx8PskCorr = (float)pCfgTxCal->sfixTx8PskCorr * 0.001953125f;
+ for ( i = 0; i < 31; i++ )
+ {
+ pTxCal->fTxExtAttCorr[i] = (float)pCfgTxCal->sfixTxExtAttCorr[i] * 0.001953125f;
+ }
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ pTxCal->fTxRollOffCorr[i] = (float)pCfgTxCal->sfixTxRollOffCorr[i] * 0.001953125f;
+ }
+
+ //DSP firmware version
+ pTxCal->u8DspMajVer = 0;
+ pTxCal->u8DspMinVer = 0;
+
+ //FPGA firmware version
+ pTxCal->u8FpgaMajVer = 0;
+ pTxCal->u8FpgaMinVer = 0;
+
+ break;
+ }
+
+ case EEPROM_HDR_V2:
+ {
+
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ sId = EEPROM_SID_GSM850_TXCAL;
+ pCfgTxCalV2 = &ee->cfg.v2.gsm850TxCalV2;
+ size = sizeof(ee->cfg.v2.gsm850TxCalV2) + sizeof(ee->cfg.v2.__gsm850TxCalMemV2);
+ break;
+ case 1:
+ nArfcn = 194;
+ sId = EEPROM_SID_GSM900_TXCAL;
+ pCfgTxCalV2 = &ee->cfg.v2.gsm900TxCalV2;
+ size = sizeof(ee->cfg.v2.gsm900TxCalV2) + sizeof(ee->cfg.v2.__gsm900TxCalMemV2);
+ break;
+ case 2:
+ nArfcn = 374;
+ sId = EEPROM_SID_DCS1800_TXCAL;
+ pCfgTxCalV2 = &ee->cfg.v2.dcs1800TxCalV2;
+ size = sizeof(ee->cfg.v2.dcs1800TxCalV2) + sizeof(ee->cfg.v2.__dcs1800TxCalMemV2);
+ break;
+ case 3:
+ nArfcn = 299;
+ sId = EEPROM_SID_PCS1900_TXCAL;
+ pCfgTxCalV2 = &ee->cfg.v2.pcs1900TxCalV2;
+ size = sizeof(ee->cfg.v2.pcs1900TxCalV2) + sizeof(ee->cfg.v2.__pcs1900TxCalMemV2);
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+
+ // Validate the ID
+ if ( pCfgTxCalV2->u16SectionID != sId )
+ {
+ PERROR( "Uninitialised data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&pCfgTxCalV2->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgTxCalV2->u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the content of the section
+ for ( i = 0; i < 80; i++ )
+ {
+ pTxCal->fTxGainGmsk[i] = (float)pCfgTxCalV2->sfixTxGainGmsk[i] * 0.03125f;
+ }
+ pTxCal->fTx8PskCorr = (float)pCfgTxCalV2->sfixTx8PskCorr * 0.001953125f;
+ for ( i = 0; i < 31; i++ )
+ {
+ pTxCal->fTxExtAttCorr[i] = (float)pCfgTxCalV2->sfixTxExtAttCorr[i] * 0.001953125f;
+ }
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ pTxCal->fTxRollOffCorr[i] = (float)pCfgTxCalV2->sfixTxRollOffCorr[i] * 0.001953125f;
+ }
+
+ //DSP firmware version
+ pTxCal->u8DspMajVer = pCfgTxCalV2->u8DspMajVer;
+ pTxCal->u8DspMinVer = pCfgTxCalV2->u8DspMinVer;
+
+ //FPGA firmware version
+ pTxCal->u8FpgaMajVer = pCfgTxCalV2->u8FpgaMajVer;
+ pTxCal->u8FpgaMinVer = pCfgTxCalV2->u8FpgaMinVer;
+
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_WriteTxCal
+ ************************************************************************//**
+ *
+ * This function writes the TX calibration tables for the specified band to
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] pTxCal
+ * Pointer to a TX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteTxCal( int iBand, const eeprom_TxCal_t *pTxCal )
+{
+ int i;
+ int err;
+ int size;
+ int nArfcn;
+ eeprom_Cfg_t ee;
+ eeprom_SID_t sId;
+ eeprom_CfgTxCalV2_t *pCfgTxCal = NULL;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V2:
+ {
+ int32_t fixVal;
+
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ sId = EEPROM_SID_GSM850_TXCAL;
+ pCfgTxCal = &ee.cfg.v2.gsm850TxCalV2;
+ size = sizeof(ee.cfg.v2.gsm850TxCalV2) + sizeof(ee.cfg.v2.__gsm850TxCalMemV2);
+ break;
+ case 1:
+ nArfcn = 194;
+ sId = EEPROM_SID_GSM900_TXCAL;
+ pCfgTxCal = &ee.cfg.v2.gsm900TxCalV2;
+ size = sizeof(ee.cfg.v2.gsm900TxCalV2) + sizeof(ee.cfg.v2.__gsm900TxCalMemV2);
+ break;
+ case 2:
+ nArfcn = 374;
+ sId = EEPROM_SID_DCS1800_TXCAL;
+ pCfgTxCal = &ee.cfg.v2.dcs1800TxCalV2;
+ size = sizeof(ee.cfg.v2.dcs1800TxCalV2) + sizeof(ee.cfg.v2.__dcs1800TxCalMemV2);
+ break;
+ case 3:
+ nArfcn = 299;
+ sId = EEPROM_SID_PCS1900_TXCAL;
+ pCfgTxCal = &ee.cfg.v2.pcs1900TxCalV2;
+ size = sizeof(ee.cfg.v2.pcs1900TxCalV2) + sizeof(ee.cfg.v2.__pcs1900TxCalMemV2);
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ pCfgTxCal->u16SectionID = sId;
+ pCfgTxCal->u16Crc = 0;
+ pCfgTxCal->u32Time = time(NULL);
+
+ //DSP firmware version
+ pCfgTxCal->u8DspMajVer = pTxCal->u8DspMajVer;
+ pCfgTxCal->u8DspMinVer = pTxCal->u8DspMinVer;
+
+ //FPGA firmware version
+ pCfgTxCal->u8FpgaMajVer = pTxCal->u8FpgaMajVer;
+ pCfgTxCal->u8FpgaMinVer = pTxCal->u8FpgaMinVer;
+
+ // Compress the calibration tables
+ for ( i = 0; i < 80; i++ )
+ {
+ fixVal = (int32_t)(pTxCal->fTxGainGmsk[i] * 32.f + (pTxCal->fTxGainGmsk[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgTxCal->sfixTxGainGmsk[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgTxCal->sfixTxGainGmsk[i] = -32768;
+ else pCfgTxCal->sfixTxGainGmsk[i] = (int16_t)fixVal;
+ }
+ fixVal = (int32_t)(pTxCal->fTx8PskCorr * 512.f + (pTxCal->fTx8PskCorr>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgTxCal->sfixTx8PskCorr = 32767;
+ else if ( fixVal < -32768 ) pCfgTxCal->sfixTx8PskCorr = -32768;
+ else pCfgTxCal->sfixTx8PskCorr = (int16_t)fixVal;
+ for ( i = 0; i < 31; i++ )
+ {
+ fixVal = (int32_t)(pTxCal->fTxExtAttCorr[i] * 512.f + (pTxCal->fTxExtAttCorr[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgTxCal->sfixTxExtAttCorr[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgTxCal->sfixTxExtAttCorr[i] = -32768;
+ else pCfgTxCal->sfixTxExtAttCorr[i] = (int16_t)fixVal;
+ }
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ fixVal = (int32_t)(pTxCal->fTxRollOffCorr[i] * 512.f + (pTxCal->fTxRollOffCorr[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgTxCal->sfixTxRollOffCorr[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgTxCal->sfixTxRollOffCorr[i] = -32768;
+ else pCfgTxCal->sfixTxRollOffCorr[i] = (int16_t)fixVal;
+ }
+
+ // Add the CRC
+ pCfgTxCal->u16Crc = eeprom_crc( (uint8_t *)&pCfgTxCal->u32Time, size - 2 * sizeof(uint16_t) );
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint8_t*)pCfgTxCal - (uint8_t*)&ee), size, (const char *)pCfgTxCal );
+ if ( err != size )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_ReadRxCal
+ ************************************************************************//**
+ *
+ * This function reads the RX calibration tables for the specified band from
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] iUplink
+ * Uplink flag (0:downlink, X:downlink).
+ *
+ * @param [inout] pRxCal
+ * Pointer to a RX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadRxCal( int iBand, int iUplink, eeprom_RxCal_t *pRxCal )
+{
+ int i;
+ int size;
+ int nArfcn;
+ eeprom_Cfg_t *ee = eeprom_cached_config();
+ eeprom_SID_t sId;
+ eeprom_CfgRxCal_t *pCfgRxCal = NULL;
+ eeprom_CfgRxCalV2_t *pCfgRxCalV2 = NULL;
+
+
+ if (!ee)
+ {
+ PERROR( "Reading cached content failed.\n" );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ switch ( ee->hdr.u16Version )
+ {
+ case EEPROM_HDR_V1:
+ {
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM850_RXUCAL;
+ pCfgRxCal = &ee->cfg.v1.gsm850RxuCal;
+ size = sizeof(ee->cfg.v1.gsm850RxuCal) + sizeof(ee->cfg.v1.__gsm850RxuCalMem);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM850_RXDCAL;
+ pCfgRxCal = &ee->cfg.v1.gsm850RxdCal;
+ size = sizeof(ee->cfg.v1.gsm850RxdCal) + sizeof(ee->cfg.v1.__gsm850RxdCalMem);
+ }
+ break;
+ case 1:
+ nArfcn = 194;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM900_RXUCAL;
+ pCfgRxCal = &ee->cfg.v1.gsm900RxuCal;
+ size = sizeof(ee->cfg.v1.gsm900RxuCal) + sizeof(ee->cfg.v1.__gsm900RxuCalMem);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM900_RXDCAL;
+ pCfgRxCal = &ee->cfg.v1.gsm900RxdCal;
+ size = sizeof(ee->cfg.v1.gsm900RxdCal) + sizeof(ee->cfg.v1.__gsm900RxdCalMem);
+ }
+ break;
+ case 2:
+ nArfcn = 374;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_DCS1800_RXUCAL;
+ pCfgRxCal = &ee->cfg.v1.dcs1800RxuCal;
+ size = sizeof(ee->cfg.v1.dcs1800RxuCal) + sizeof(ee->cfg.v1.__dcs1800RxuCalMem);
+ }
+ else
+ {
+ sId = EEPROM_SID_DCS1800_RXDCAL;
+ pCfgRxCal = &ee->cfg.v1.dcs1800RxdCal;
+ size = sizeof(ee->cfg.v1.dcs1800RxdCal) + sizeof(ee->cfg.v1.__dcs1800RxdCalMem);
+ }
+ break;
+ case 3:
+ nArfcn = 299;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_PCS1900_RXUCAL;
+ pCfgRxCal = &ee->cfg.v1.pcs1900RxuCal;
+ size = sizeof(ee->cfg.v1.pcs1900RxuCal) + sizeof(ee->cfg.v1.__pcs1900RxuCalMem);
+ }
+ else
+ {
+ sId = EEPROM_SID_PCS1900_RXDCAL;
+ pCfgRxCal = &ee->cfg.v1.pcs1900RxdCal;
+ size = sizeof(ee->cfg.v1.pcs1900RxdCal) + sizeof(ee->cfg.v1.__pcs1900RxdCalMem);
+ }
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ // Validate the ID
+ if ( pCfgRxCal->u16SectionID != sId )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&pCfgRxCal->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgRxCal->u16Crc )
+ {
+ PERROR( "Parity error\n" );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the IQ imbalance mode (0:off, 1:on, 2:auto)
+ pRxCal->u8IqImbalMode = pCfgRxCal->u16IqImbalMode;
+
+ // Expand the IQ imbalance compensation
+ for ( i = 0; i < 4; i++ )
+ {
+ pRxCal->u16IqImbalCorr[i] = pCfgRxCal->u16IqImbalCorr[i];
+ }
+
+ // Expand the External RX gain
+ pRxCal->fExtRxGain = (float)pCfgRxCal->sfixExtRxGain * 0.001953125f;
+
+ // Expand the Mixer gain error compensation
+ pRxCal->fRxMixGainCorr = (float)pCfgRxCal->sfixRxMixGainCorr * 0.001953125f;
+
+ // Expand the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ for ( i = 0; i < 3; i++ )
+ {
+ pRxCal->fRxLnaGainCorr[i] = (float)pCfgRxCal->sfixRxLnaGainCorr[i] * 0.001953125f;
+ }
+
+ // Expand the Frequency roll-off compensation
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ pRxCal->fRxRollOffCorr[i] = (float)pCfgRxCal->sfixRxRollOffCorr[i] * 0.001953125f;
+ }
+
+ //DSP firmware version
+ pRxCal->u8DspMajVer = 0;
+ pRxCal->u8DspMinVer = 0;
+
+ //FPGA firmware version
+ pRxCal->u8FpgaMajVer = 0;
+ pRxCal->u8FpgaMinVer = 0;
+
+ break;
+ }
+
+ case EEPROM_HDR_V2:
+ {
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM850_RXUCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.gsm850RxuCalV2;
+ size = sizeof(ee->cfg.v2.gsm850RxuCalV2) + sizeof(ee->cfg.v2.__gsm850RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM850_RXDCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.gsm850RxdCalV2;
+ size = sizeof(ee->cfg.v2.gsm850RxdCalV2) + sizeof(ee->cfg.v2.__gsm850RxdCalMemV2);
+ }
+ break;
+ case 1:
+ nArfcn = 194;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM900_RXUCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.gsm900RxuCalV2;
+ size = sizeof(ee->cfg.v2.gsm900RxuCalV2) + sizeof(ee->cfg.v2.__gsm900RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM900_RXDCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.gsm900RxdCalV2;
+ size = sizeof(ee->cfg.v2.gsm900RxdCalV2) + sizeof(ee->cfg.v2.__gsm900RxdCalMemV2);
+ }
+ break;
+ case 2:
+ nArfcn = 374;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_DCS1800_RXUCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.dcs1800RxuCalV2;
+ size = sizeof(ee->cfg.v2.dcs1800RxuCalV2) + sizeof(ee->cfg.v2.__dcs1800RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_DCS1800_RXDCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.dcs1800RxdCalV2;
+ size = sizeof(ee->cfg.v2.dcs1800RxdCalV2) + sizeof(ee->cfg.v2.__dcs1800RxdCalMemV2);
+ }
+ break;
+ case 3:
+ nArfcn = 299;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_PCS1900_RXUCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.pcs1900RxuCalV2;
+ size = sizeof(ee->cfg.v2.pcs1900RxuCalV2) + sizeof(ee->cfg.v2.__pcs1900RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_PCS1900_RXDCAL;
+ pCfgRxCalV2 = &ee->cfg.v2.pcs1900RxdCalV2;
+ size = sizeof(ee->cfg.v2.pcs1900RxdCalV2) + sizeof(ee->cfg.v2.__pcs1900RxdCalMemV2);
+ }
+ break;
+
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ // Validate the ID
+ if ( pCfgRxCalV2->u16SectionID != sId )
+ {
+ PERROR( "Uninitialized data section\n" );
+ return EEPROM_ERR_UNAVAILABLE;
+ }
+
+ // Validate the CRC
+ if ( eeprom_crc( (uint8_t *)&pCfgRxCalV2->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgRxCalV2->u16Crc )
+ {
+ PERROR( "Parity error - Band %d\n", iBand );
+ return EEPROM_ERR_PARITY;
+ }
+
+ // Expand the IQ imbalance mode (0:off, 1:on, 2:auto)
+ pRxCal->u8IqImbalMode = pCfgRxCalV2->u16IqImbalMode;
+
+ // Expand the IQ imbalance compensation
+ for ( i = 0; i < 4; i++ )
+ {
+ pRxCal->u16IqImbalCorr[i] = pCfgRxCalV2->u16IqImbalCorr[i];
+ }
+
+ // Expand the External RX gain
+ pRxCal->fExtRxGain = (float)pCfgRxCalV2->sfixExtRxGain * 0.001953125;
+
+ // Expand the Mixer gain error compensation
+ pRxCal->fRxMixGainCorr = (float)pCfgRxCalV2->sfixRxMixGainCorr * 0.001953125;
+
+ // Expand the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ for ( i = 0; i < 3; i++ )
+ {
+ pRxCal->fRxLnaGainCorr[i] = (float)pCfgRxCalV2->sfixRxLnaGainCorr[i] * 0.001953125;
+ }
+
+ // Expand the Frequency roll-off compensation
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ pRxCal->fRxRollOffCorr[i] = (float)pCfgRxCalV2->sfixRxRollOffCorr[i] * 0.001953125;
+ }
+
+ //DSP firmware version
+ pRxCal->u8DspMajVer = pCfgRxCalV2->u8DspMajVer;
+ pRxCal->u8DspMinVer = pCfgRxCalV2->u8DspMinVer;
+
+ //FPGA firmware version
+ pRxCal->u8FpgaMajVer = pCfgRxCalV2->u8FpgaMajVer;
+ pRxCal->u8FpgaMinVer = pCfgRxCalV2->u8FpgaMinVer;
+
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Function : eeprom_WriteRxCal
+ ************************************************************************//**
+ *
+ * This function writes the RX calibration tables for the specified band to
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] iUplink
+ * Uplink flag (0:downlink, X:downlink).
+ *
+ * @param [in] pRxCal
+ * Pointer to a RX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteRxCal( int iBand, int iUplink, const eeprom_RxCal_t *pRxCal )
+{
+ int i;
+ int err;
+ int size;
+ int nArfcn;
+ eeprom_Cfg_t ee;
+ eeprom_SID_t sId;
+ eeprom_CfgRxCalV2_t *pCfgRxCal = NULL;
+
+ // Get a copy of the EEPROM header
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr );
+ if ( err != sizeof(ee.hdr) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+
+ // Validate the header magic ID
+ if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ // Init the header
+ ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID;
+ ee.hdr.u16Version = EEPROM_HDR_V2;
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee );
+ if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ }
+
+ switch ( ee.hdr.u16Version )
+ {
+ case EEPROM_HDR_V2:
+ {
+ int32_t fixVal;
+
+ switch ( iBand )
+ {
+ case 0:
+ nArfcn = 124;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM850_RXUCAL;
+ pCfgRxCal = &ee.cfg.v2.gsm850RxuCalV2;
+ size = sizeof(ee.cfg.v2.gsm850RxuCalV2) + sizeof(ee.cfg.v2.__gsm850RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM850_RXDCAL;
+ pCfgRxCal = &ee.cfg.v2.gsm850RxdCalV2;
+ size = sizeof(ee.cfg.v2.gsm850RxdCalV2) + sizeof(ee.cfg.v2.__gsm850RxdCalMemV2);
+ }
+ break;
+ case 1:
+ nArfcn = 194;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_GSM900_RXUCAL;
+ pCfgRxCal = &ee.cfg.v2.gsm900RxuCalV2;
+ size = sizeof(ee.cfg.v2.gsm900RxuCalV2) + sizeof(ee.cfg.v2.__gsm900RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_GSM900_RXDCAL;
+ pCfgRxCal = &ee.cfg.v2.gsm900RxdCalV2;
+ size = sizeof(ee.cfg.v2.gsm900RxdCalV2) + sizeof(ee.cfg.v2.__gsm900RxdCalMemV2);
+ }
+ break;
+ case 2:
+ nArfcn = 374;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_DCS1800_RXUCAL;
+ pCfgRxCal = &ee.cfg.v2.dcs1800RxuCalV2;
+ size = sizeof(ee.cfg.v2.dcs1800RxuCalV2) + sizeof(ee.cfg.v2.__dcs1800RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_DCS1800_RXDCAL;
+ pCfgRxCal = &ee.cfg.v2.dcs1800RxdCalV2;
+ size = sizeof(ee.cfg.v2.dcs1800RxdCalV2) + sizeof(ee.cfg.v2.__dcs1800RxdCalMemV2);
+ }
+ break;
+ case 3:
+ nArfcn = 299;
+ if ( iUplink )
+ {
+ sId = EEPROM_SID_PCS1900_RXUCAL;
+ pCfgRxCal = &ee.cfg.v2.pcs1900RxuCalV2;
+ size = sizeof(ee.cfg.v2.pcs1900RxuCalV2) + sizeof(ee.cfg.v2.__pcs1900RxuCalMemV2);
+ }
+ else
+ {
+ sId = EEPROM_SID_PCS1900_RXDCAL;
+ pCfgRxCal = &ee.cfg.v2.pcs1900RxdCalV2;
+ size = sizeof(ee.cfg.v2.pcs1900RxdCalV2) + sizeof(ee.cfg.v2.__pcs1900RxdCalMemV2);
+ }
+ break;
+ default:
+ PERROR( "Invalid GSM band specified (%d)\n", iBand );
+ return EEPROM_ERR_INVALID;
+ }
+
+ pCfgRxCal->u16SectionID = sId;
+ pCfgRxCal->u16Crc = 0;
+ pCfgRxCal->u32Time = time(NULL);
+
+ //DSP firmware version
+ pCfgRxCal->u8DspMajVer = pRxCal->u8DspMajVer;
+ pCfgRxCal->u8DspMinVer = pRxCal->u8DspMinVer;
+
+ //FPGA firmware version
+ pCfgRxCal->u8FpgaMajVer = pRxCal->u8FpgaMajVer;
+ pCfgRxCal->u8FpgaMinVer = pRxCal->u8FpgaMinVer;
+
+ // Compress the IQ imbalance mode (0:off, 1:on, 2:auto)
+ pCfgRxCal->u16IqImbalMode = pRxCal->u8IqImbalMode;
+
+ // Compress the IQ imbalance compensation
+ for ( i = 0; i < 4; i++ )
+ {
+ pCfgRxCal->u16IqImbalCorr[i] = pRxCal->u16IqImbalCorr[i];
+ }
+
+ // Compress the External RX gain
+ fixVal = (int32_t)(pRxCal->fExtRxGain * 512.f + (pRxCal->fExtRxGain>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgRxCal->sfixExtRxGain = 32767;
+ else if ( fixVal < -32768 ) pCfgRxCal->sfixExtRxGain = -32768;
+ else pCfgRxCal->sfixExtRxGain = (int16_t)fixVal;
+
+ // Compress the Mixer gain error compensation
+ fixVal = (int32_t)(pRxCal->fRxMixGainCorr * 512.f + (pRxCal->fRxMixGainCorr>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgRxCal->sfixRxMixGainCorr = 32767;
+ else if ( fixVal < -32768 ) pCfgRxCal->sfixRxMixGainCorr = -32768;
+ else pCfgRxCal->sfixRxMixGainCorr = (int16_t)fixVal;
+
+ // Compress the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ for ( i = 0; i < 3; i++ )
+ {
+ fixVal = (int32_t)(pRxCal->fRxLnaGainCorr[i] * 512.f + (pRxCal->fRxLnaGainCorr[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgRxCal->sfixRxLnaGainCorr[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgRxCal->sfixRxLnaGainCorr[i] = -32768;
+ else pCfgRxCal->sfixRxLnaGainCorr[i] = (int16_t)fixVal;
+ }
+
+ // Compress the Frequency roll-off compensation
+ for ( i = 0; i < nArfcn; i++ )
+ {
+ fixVal = (int32_t)(pRxCal->fRxRollOffCorr[i] * 512.f + (pRxCal->fRxRollOffCorr[i]>0 ? 0.5f:-0.5f));
+ if ( fixVal > 32767 ) pCfgRxCal->sfixRxRollOffCorr[i] = 32767;
+ else if ( fixVal < -32768 ) pCfgRxCal->sfixRxRollOffCorr[i] = -32768;
+ else pCfgRxCal->sfixRxRollOffCorr[i] = (int16_t)fixVal;
+ }
+
+ // Add the CRC
+ pCfgRxCal->u16Crc = eeprom_crc( (uint8_t *)&pCfgRxCal->u32Time, size - 2 * sizeof(uint16_t) );
+
+ // Write it to the EEPROM
+ err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint8_t*)pCfgRxCal - (uint8_t*)&ee), size, (const char *)pCfgRxCal );
+ if ( err != size )
+ {
+ PERROR( "Error while writing to the EEPROM (%d)\n", err );
+ return EEPROM_ERR_DEVICE;
+ }
+ break;
+ }
+
+ default:
+ {
+ PERROR( "Unsupported header version\n" );
+ return EEPROM_ERR_UNSUPPORTED;
+ }
+ }
+ return EEPROM_SUCCESS;
+}
+
+
+/****************************************************************************
+ * Private functions *
+ ****************************************************************************/
+
+/**
+ * Dump the content of the EEPROM to the standard output
+ */
+int eeprom_dump( int addr, int size, int hex )
+{
+ FILE *f;
+ char ch;
+ int i;
+
+ f = fopen( EEPROM_DEV, "r+" );
+ if ( f == NULL )
+ {
+ perror( "eeprom fopen" );
+ return -1;
+ }
+ if (fseek( f, addr, SEEK_SET ) != 0)
+ {
+ perror( "eeprom fseek" );
+ fclose( f );
+ return -1;
+ }
+
+ for ( i = 0; i < size; ++i, ++addr )
+ {
+ if ( fread( &ch, 1, 1, f ) != 1 )
+ {
+ perror( "eeprom fread" );
+ fclose( f );
+ return -1;
+ }
+ if ( hex )
+ {
+ if ( (i % 16) == 0 )
+ {
+ printf( "\n %.4x| ", addr );
+ }
+ else if ( (i % 8) == 0 )
+ {
+ printf( " " );
+ }
+ printf( "%.2x ", ch );
+ }
+ else
+ putchar( ch );
+ }
+ if ( hex )
+ {
+ printf( "\n\n" );
+ }
+ fflush( stdout );
+
+ fclose( f );
+ return 0;
+}
+
+static FILE *g_file;
+static eeprom_Cfg_t *g_cached_cfg;
+
+void eeprom_free_resources(void)
+{
+ if (g_file)
+ fclose(g_file);
+ g_file = NULL;
+
+ /* release the header */
+ free(g_cached_cfg);
+ g_cached_cfg = NULL;
+}
+
+/**
+ * Read up to 'size' bytes of data from the EEPROM starting at offset 'addr'.
+ */
+static int eeprom_read( int addr, int size, char *pBuff )
+{
+ FILE *f = g_file;
+ int n;
+
+ if (!f) {
+ f = fopen( EEPROM_DEV, "r+" );
+ if ( f == NULL )
+ {
+ perror( "eeprom fopen" );
+ return -1;
+ }
+ g_file = f;
+ }
+ if (fseek( f, addr, SEEK_SET ) != 0)
+ {
+ perror( "eeprom fseek" );
+ return -1;
+ }
+
+ n = fread( pBuff, 1, size, f );
+ return n;
+}
+
+static void eeprom_cache_cfg(void)
+{
+ int err;
+
+ free(g_cached_cfg);
+ g_cached_cfg = malloc(sizeof(*g_cached_cfg));
+
+ if (!g_cached_cfg)
+ return;
+
+ err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(*g_cached_cfg), (char *) g_cached_cfg );
+ if ( err != sizeof(*g_cached_cfg) )
+ {
+ PERROR( "Error while reading the EEPROM content (%d)\n", err );
+ goto error;
+ }
+
+ if ( g_cached_cfg->hdr.u32MagicId != EEPROM_CFG_MAGIC_ID )
+ {
+ PERROR( "Invalid EEPROM format\n" );
+ goto error;
+ }
+
+ return;
+
+error:
+ free(g_cached_cfg);
+ g_cached_cfg = NULL;
+}
+
+static eeprom_Cfg_t *eeprom_cached_config(void)
+{
+ if (!g_cached_cfg)
+ eeprom_cache_cfg();
+ return g_cached_cfg;
+}
+
+/**
+ * Write up to 'size' bytes of data to the EEPROM starting at offset 'addr'.
+ */
+static int eeprom_write( int addr, int size, const char *pBuff )
+{
+ FILE *f = g_file;
+ int n;
+
+ if (!f) {
+ f = fopen( EEPROM_DEV, "r+" );
+ if ( f == NULL )
+ {
+ perror( "eeprom fopen" );
+ return -1;
+ }
+ g_file = f;
+ }
+ if (fseek( f, addr, SEEK_SET ) != 0)
+ {
+ perror( "eeprom fseek" );
+ n = -1;
+ goto error;
+ }
+
+ n = fwrite( pBuff, 1, size, f );
+
+error:
+ fclose( f );
+ g_file = NULL;
+ return n;
+}
+
+
+/**
+ * EEPROM CRC.
+ */
+static uint16_t eeprom_crc( uint8_t *pu8Data, int len )
+{
+ int i;
+ uint16_t crc = 0xFFFF;
+
+ while (len--) {
+ crc ^= (uint16_t)*pu8Data++;
+
+ for (i=0; i<8; i++) {
+ if (crc & 1) crc = (crc >> 1) ^ 0x8408;
+ else crc = (crc >> 1);
+ }
+ }
+
+ crc = ~crc;
+ return crc;
+}
diff --git a/src/osmo-bts-sysmo/eeprom.h b/src/osmo-bts-sysmo/eeprom.h
new file mode 100644
index 00000000..f75e54f9
--- /dev/null
+++ b/src/osmo-bts-sysmo/eeprom.h
@@ -0,0 +1,304 @@
+/***************************************************************************
+ *
+ * **** I
+ * ****** ***
+ * ******* ****
+ * ******** **** **** **** ********* ******* **** ***********
+ * ********* **** **** **** ********* ************** *************
+ * **** ***** **** **** **** **** ***** ****** ***** ****
+ * **** ***** **** **** **** **** ***** **** **** ****
+ * **** ********* **** **** **** **** **** **** ****
+ * **** ******** **** ****I **** ***** ***** **** ****
+ * **** ****** ***** ****** ***** ****** ******* ****** *******
+ * **** **** ************ ****** ************* *************
+ * **** *** **** **** **** ***** **** ***** ****
+ * ****
+ * I N N O V A T I O N T O D A Y F O R T O M M O R O W ****
+ * ***
+ *
+ ***************************************************************************
+ *
+ * Project : SuperFemto
+ * File : eeprom.h
+ * Description : EEPROM interface.
+ *
+ * Copyright (c) Nutaq. 2012
+ *
+ ***************************************************************************
+ *
+ * "$Revision: 1.1 $"
+ * "$Name: $"
+ * "$Date: 2012/06/20 02:18:30 $"
+ * "$Author: Yves.Godin $"
+ *
+ ***************************************************************************/
+#ifndef EEPROM_H__
+#define EEPROM_H__
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Public constants *
+ ****************************************************************************/
+
+/**
+ * EEPROM error code
+ */
+typedef enum
+{
+ EEPROM_SUCCESS = 0, ///< Success
+ EEPROM_ERR_DEVICE = -1, ///< Device access error
+ EEPROM_ERR_PARITY = -2, ///< Parity error
+ EEPROM_ERR_UNAVAILABLE = -3, ///< Information unavailable
+ EEPROM_ERR_INVALID = -4, ///< Invalid format
+ EEPROM_ERR_UNSUPPORTED = -5, ///< Unsupported format
+} eeprom_Error_t;
+
+
+/****************************************************************************
+ * Struct : eeprom_SysInfo_t
+ ************************************************************************//**
+ *
+ * SuperFemto system information.
+ *
+ ***************************************************************************/
+typedef struct eeprom_SysInfo
+{
+ char szSn[16]; ///< Serial number
+ uint8_t u8Rev; ///< Board revision
+ uint8_t u8Tcxo; ///< TCXO present (0:absent, 1:present, X:unknown)
+ uint8_t u8Ocxo; ///< OCXO present (0:absent, 1:present, X:unknown)
+ uint8_t u8GSM850; ///< GSM-850 supported (0:unsupported, 1:supported, X:unknown)
+ uint8_t u8GSM900; ///< GSM-900 supported (0:unsupported, 1:supported, X:unknown)
+ uint8_t u8DCS1800; ///< GSM-1800 supported (0:unsupported, 1:supported, X:unknown)
+ uint8_t u8PCS1900; ///< GSM-1900 supported (0:unsupported, 1:supported, X:unknown)
+} eeprom_SysInfo_t;
+
+/****************************************************************************
+ * Struct : eeprom_RfClockCal_t
+ ************************************************************************//**
+ *
+ * SuperFemto RF clock calibration.
+ *
+ ***************************************************************************/
+typedef struct eeprom_RfClockCal
+{
+ int iClkCor; ///< Clock correction value in PPB.
+ uint8_t u8ClkSrc; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge)
+} eeprom_RfClockCal_t;
+
+/****************************************************************************
+ * Struct : eeprom_TxCal_t
+ ************************************************************************//**
+ *
+ * SuperFemto transmit calibration table.
+ *
+ ***************************************************************************/
+typedef struct eeprom_TxCal
+{
+ uint8_t u8DspMajVer; ///< DSP firmware major version
+ uint8_t u8DspMinVer; ///< DSP firmware minor version
+ uint8_t u8FpgaMajVer; ///< FPGA firmware major version
+ uint8_t u8FpgaMinVer; ///< FPGA firmware minor version
+ float fTxGainGmsk[80]; ///< Gain setting for GMSK output level from +50dBm to -29 dBm
+ float fTx8PskCorr; ///< Gain adjustment for 8 PSK (default to +3.25 dB)
+ float fTxExtAttCorr[31]; ///< Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB)
+ float fTxRollOffCorr[374]; /**< Gain correction for each ARFCN
+ for GSM-850 : 0=128, 1:129, ..., 123:251, [124-373]:unused
+ for GSM-900 : 0=955, 1:956, ..., 70:1, ..., 317:956, [318-373]:unused
+ for DCS-1800: 0=512, 1:513, ..., 373:885
+ for PCS-1900: 0=512, 1:513, ..., 298:810, [299-373]:unused */
+} eeprom_TxCal_t;
+
+/****************************************************************************
+ * Struct : eeprom_RxCal_t
+ ************************************************************************//**
+ *
+ * SuperFemto receive calibration table.
+ *
+ ***************************************************************************/
+typedef struct eeprom_RxCal
+{
+ uint8_t u8DspMajVer; ///< DSP firmware major version
+ uint8_t u8DspMinVer; ///< DSP firmware minor version
+ uint8_t u8FpgaMajVer; ///< FPGA firmware major version
+ uint8_t u8FpgaMinVer; ///< FPGA firmware minor version
+ float fExtRxGain; ///< External RX gain
+ float fRxMixGainCorr; ///< Mixer gain error compensation
+ float fRxLnaGainCorr[3]; ///< LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB)
+ float fRxRollOffCorr[374]; /***< Frequency roll-off compensation
+ for GSM-850 : 0=128, 1:129, ..., 123:251, [124-373]:unused
+ for GSM-900 : 0=955, 1:956, ..., 70:1, ..., 317:956, [318-373]:unused
+ for DCS-1800: 0=512, 1:513, ..., 373:885
+ for PCS-1900: 0=512, 1:513, ..., 298:810, [299-373]:unused */
+ uint8_t u8IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto)
+ uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation
+} eeprom_RxCal_t;
+
+
+/****************************************************************************
+ * Public functions *
+ ****************************************************************************/
+
+eeprom_Error_t eeprom_ReadEthAddr( uint8_t *ethaddr );
+
+/****************************************************************************
+ * Function : eeprom_ResetCfg
+ ************************************************************************//**
+ *
+ * This function reset the content of the EEPROM config area.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ResetCfg( void );
+
+/****************************************************************************
+ * Function : eeprom_ReadSysInfo
+ ************************************************************************//**
+ *
+ * This function reads the system information from the EEPROM.
+ *
+ * @param [inout] pTime
+ * Pointer to a system info structure.
+ *
+ * @param [inout] pSysInfo
+ * Pointer to a system info structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadSysInfo( eeprom_SysInfo_t *pSysInfo );
+
+/****************************************************************************
+ * Function : eeprom_WriteSysInfo
+ ************************************************************************//**
+ *
+ * This function writes the system information to the EEPROM.
+ *
+ * @param [in] pSysInfo
+ * Pointer to the system info structure to be written.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteSysInfo( const eeprom_SysInfo_t *pSysInfo );
+
+/****************************************************************************
+ * Function : eeprom_ReadRfClockCal
+ ************************************************************************//**
+ *
+ * This function reads the RF clock calibration data from the EEPROM.
+ *
+ * @param [inout] pRfClockCal
+ * Pointer to a RF clock calibration structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadRfClockCal( eeprom_RfClockCal_t *pRfClockCal );
+
+/****************************************************************************
+ * Function : eeprom_WriteRfClockCal
+ ************************************************************************//**
+ *
+ * This function writes the RF clock calibration data to the EEPROM.
+ *
+ * @param [in] pSysInfo
+ * Pointer to the system info structure to be written.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteRfClockCal( const eeprom_RfClockCal_t *pRfClockCal );
+
+/****************************************************************************
+ * Function : eeprom_ReadTxCal
+ ************************************************************************//**
+ *
+ * This function reads the TX calibration tables for the specified band from
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [inout] pTxCal
+ * Pointer to a TX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadTxCal( int iBand, eeprom_TxCal_t *pTxCal );
+
+/****************************************************************************
+ * Function : eeprom_WriteTxCal
+ ************************************************************************//**
+ *
+ * This function writes the TX calibration tables for the specified band to
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] pTxCal
+ * Pointer to a TX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteTxCal( int iBand, const eeprom_TxCal_t *pTxCal );
+
+/****************************************************************************
+ * Function : eeprom_ReadRxCal
+ ************************************************************************//**
+ *
+ * This function reads the RX calibration tables for the specified band from
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] iUplink
+ * Uplink flag (0:downlink, X:downlink).
+ *
+ * @param [inout] pRxCal
+ * Pointer to a RX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_ReadRxCal( int iBand, int iUplink, eeprom_RxCal_t *pRxCal );
+
+/****************************************************************************
+ * Function : eeprom_WriteRxCal
+ ************************************************************************//**
+ *
+ * This function writes the RX calibration tables for the specified band to
+ * the EEPROM.
+ *
+ * @param [in] iBand
+ * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900).
+ *
+ * @param [in] iUplink
+ * Uplink flag (0:downlink, X:downlink).
+ *
+ * @param [in] pRxCal
+ * Pointer to a RX calibration table structure.
+ *
+ * @return
+ * 0 if or an error core.
+ *
+ ****************************************************************************/
+eeprom_Error_t eeprom_WriteRxCal( int iBand, int iUplink, const eeprom_RxCal_t *pRxCal );
+
+void eeprom_free_resources(void);
+
+#endif // EEPROM_H__
diff --git a/src/osmo-bts-sysmo/femtobts.c b/src/osmo-bts-sysmo/femtobts.c
new file mode 100644
index 00000000..480fe06b
--- /dev/null
+++ b/src/osmo-bts-sysmo/femtobts.c
@@ -0,0 +1,370 @@
+/* sysmocom femtobts L1 API related definitions */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1dbg.h>
+
+#include "femtobts.h"
+
+const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM] = {
+ [GsmL1_PrimId_MphInitReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphCloseReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphConnectReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphDisconnectReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphActivateReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphDeactivateReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphConfigReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphMeasureReq] = L1P_T_REQ,
+ [GsmL1_PrimId_MphInitCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphCloseCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphConnectCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphDisconnectCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphActivateCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphDeactivateCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphConfigCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphMeasureCnf] = L1P_T_CONF,
+ [GsmL1_PrimId_MphTimeInd] = L1P_T_IND,
+ [GsmL1_PrimId_MphSyncInd] = L1P_T_IND,
+ [GsmL1_PrimId_PhEmptyFrameReq] = L1P_T_REQ,
+ [GsmL1_PrimId_PhDataReq] = L1P_T_REQ,
+ [GsmL1_PrimId_PhConnectInd] = L1P_T_IND,
+ [GsmL1_PrimId_PhReadyToSendInd] = L1P_T_IND,
+ [GsmL1_PrimId_PhDataInd] = L1P_T_IND,
+ [GsmL1_PrimId_PhRaInd] = L1P_T_IND,
+};
+
+const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1] = {
+ { GsmL1_PrimId_MphInitReq, "MPH-INIT.req" },
+ { GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" },
+ { GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" },
+ { GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" },
+ { GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" },
+ { GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" },
+ { GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" },
+ { GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" },
+ { GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" },
+ { GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" },
+ { GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" },
+ { GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" },
+ { GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" },
+ { GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" },
+ { GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" },
+ { GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" },
+ { GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" },
+ { GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" },
+ { GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" },
+ { GsmL1_PrimId_PhDataReq, "PH-DATA.req" },
+ { GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" },
+ { GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" },
+ { GsmL1_PrimId_PhDataInd, "PH-DATA.ind" },
+ { GsmL1_PrimId_PhRaInd, "PH-RA.ind" },
+ { 0, NULL }
+};
+
+const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM] = {
+ [GsmL1_PrimId_MphInitReq] = GsmL1_PrimId_MphInitCnf,
+ [GsmL1_PrimId_MphCloseReq] = GsmL1_PrimId_MphCloseCnf,
+ [GsmL1_PrimId_MphConnectReq] = GsmL1_PrimId_MphConnectCnf,
+ [GsmL1_PrimId_MphDisconnectReq] = GsmL1_PrimId_MphDisconnectCnf,
+ [GsmL1_PrimId_MphActivateReq] = GsmL1_PrimId_MphActivateCnf,
+ [GsmL1_PrimId_MphDeactivateReq] = GsmL1_PrimId_MphDeactivateCnf,
+ [GsmL1_PrimId_MphConfigReq] = GsmL1_PrimId_MphConfigCnf,
+ [GsmL1_PrimId_MphMeasureReq] = GsmL1_PrimId_MphMeasureCnf,
+};
+
+const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM] = {
+ [SuperFemto_PrimId_SystemInfoReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_SystemInfoCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_SystemFailureInd] = L1P_T_IND,
+ [SuperFemto_PrimId_ActivateRfReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_ActivateRfCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_DeactivateRfReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_DeactivateRfCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_SetTraceFlagsReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_RfClockInfoReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_RfClockInfoCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_RfClockSetupReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_RfClockSetupCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_Layer1ResetReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_Layer1ResetCnf] = L1P_T_CONF,
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ [SuperFemto_PrimId_GetTxCalibTblReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_GetTxCalibTblCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_SetTxCalibTblReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_SetTxCalibTblCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_GetRxCalibTblReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_GetRxCalibTblCnf] = L1P_T_CONF,
+ [SuperFemto_PrimId_SetRxCalibTblReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_SetRxCalibTblCnf] = L1P_T_CONF,
+#endif
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+ [SuperFemto_PrimId_MuteRfReq] = L1P_T_REQ,
+ [SuperFemto_PrimId_MuteRfCnf] = L1P_T_CONF,
+#endif
+};
+
+const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1] = {
+ { SuperFemto_PrimId_SystemInfoReq, "SYSTEM-INFO.req" },
+ { SuperFemto_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" },
+ { SuperFemto_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" },
+ { SuperFemto_PrimId_ActivateRfReq, "ACTIVATE-RF.req" },
+ { SuperFemto_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" },
+ { SuperFemto_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" },
+ { SuperFemto_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" },
+ { SuperFemto_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" },
+ { SuperFemto_PrimId_RfClockInfoReq, "RF-CLOCK-INFO.req" },
+ { SuperFemto_PrimId_RfClockInfoCnf, "RF-CLOCK-INFO.conf" },
+ { SuperFemto_PrimId_RfClockSetupReq, "RF-CLOCK-SETUP.req" },
+ { SuperFemto_PrimId_RfClockSetupCnf, "RF-CLOCK-SETUP.conf" },
+ { SuperFemto_PrimId_Layer1ResetReq, "LAYER1-RESET.req" },
+ { SuperFemto_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" },
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ { SuperFemto_PrimId_GetTxCalibTblReq, "GET-TX-CALIB.req" },
+ { SuperFemto_PrimId_GetTxCalibTblCnf, "GET-TX-CALIB.cnf" },
+ { SuperFemto_PrimId_SetTxCalibTblReq, "SET-TX-CALIB.req" },
+ { SuperFemto_PrimId_SetTxCalibTblCnf, "SET-TX-CALIB.cnf" },
+ { SuperFemto_PrimId_GetRxCalibTblReq, "GET-RX-CALIB.req" },
+ { SuperFemto_PrimId_GetRxCalibTblCnf, "GET-RX-CALIB.cnf" },
+ { SuperFemto_PrimId_SetRxCalibTblReq, "SET-RX-CALIB.req" },
+ { SuperFemto_PrimId_SetRxCalibTblCnf, "SET-RX-CALIB.cnf" },
+#endif
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+ { SuperFemto_PrimId_MuteRfReq, "MUTE-RF.req" },
+ { SuperFemto_PrimId_MuteRfCnf, "MUTE-RF.cnf" },
+#endif
+ { 0, NULL }
+};
+
+const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM] = {
+ [SuperFemto_PrimId_SystemInfoReq] = SuperFemto_PrimId_SystemInfoCnf,
+ [SuperFemto_PrimId_ActivateRfReq] = SuperFemto_PrimId_ActivateRfCnf,
+ [SuperFemto_PrimId_DeactivateRfReq] = SuperFemto_PrimId_DeactivateRfCnf,
+ [SuperFemto_PrimId_RfClockInfoReq] = SuperFemto_PrimId_RfClockInfoCnf,
+ [SuperFemto_PrimId_RfClockSetupReq] = SuperFemto_PrimId_RfClockSetupCnf,
+ [SuperFemto_PrimId_Layer1ResetReq] = SuperFemto_PrimId_Layer1ResetCnf,
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ [SuperFemto_PrimId_GetTxCalibTblReq] = SuperFemto_PrimId_GetTxCalibTblCnf,
+ [SuperFemto_PrimId_SetTxCalibTblReq] = SuperFemto_PrimId_SetTxCalibTblCnf,
+ [SuperFemto_PrimId_GetRxCalibTblReq] = SuperFemto_PrimId_GetRxCalibTblCnf,
+ [SuperFemto_PrimId_SetRxCalibTblReq] = SuperFemto_PrimId_SetRxCalibTblCnf,
+#endif
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+ [SuperFemto_PrimId_MuteRfReq] = SuperFemto_PrimId_MuteRfCnf,
+#endif
+};
+
+const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1] = {
+ { GsmL1_Sapi_Idle, "IDLE" },
+ { GsmL1_Sapi_Fcch, "FCCH" },
+ { GsmL1_Sapi_Sch, "SCH" },
+ { GsmL1_Sapi_Sacch, "SACCH" },
+ { GsmL1_Sapi_Sdcch, "SDCCH" },
+ { GsmL1_Sapi_Bcch, "BCCH" },
+ { GsmL1_Sapi_Pch, "PCH" },
+ { GsmL1_Sapi_Agch, "AGCH" },
+ { GsmL1_Sapi_Cbch, "CBCH" },
+ { GsmL1_Sapi_Rach, "RACH" },
+ { GsmL1_Sapi_TchF, "TCH/F" },
+ { GsmL1_Sapi_FacchF, "FACCH/F" },
+ { GsmL1_Sapi_TchH, "TCH/H" },
+ { GsmL1_Sapi_FacchH, "FACCH/H" },
+ { GsmL1_Sapi_Nch, "NCH" },
+ { GsmL1_Sapi_Pdtch, "PDTCH" },
+ { GsmL1_Sapi_Pacch, "PACCH" },
+ { GsmL1_Sapi_Pbcch, "PBCCH" },
+ { GsmL1_Sapi_Pagch, "PAGCH" },
+ { GsmL1_Sapi_Ppch, "PPCH" },
+ { GsmL1_Sapi_Pnch, "PNCH" },
+ { GsmL1_Sapi_Ptcch, "PTCCH" },
+ { GsmL1_Sapi_Prach, "PRACH" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1] = {
+ { GsmL1_Status_Success, "Success" },
+ { GsmL1_Status_Generic, "Generic error" },
+ { GsmL1_Status_NoMemory, "Not enough memory" },
+ { GsmL1_Status_Timeout, "Timeout" },
+ { GsmL1_Status_InvalidParam, "Invalid parameter" },
+ { GsmL1_Status_Busy, "Resource busy" },
+ { GsmL1_Status_NoRessource, "No more resources" },
+ { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" },
+ { GsmL1_Status_NullInterface, "Trying to call a NULL interface" },
+ { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" },
+ { GsmL1_Status_BadCrc, "Bad CRC" },
+ { GsmL1_Status_BadUsf, "Bad USF" },
+ { GsmL1_Status_InvalidCPS, "Invalid CPS field" },
+ { GsmL1_Status_UnexpectedBurst, "Unexpected burst" },
+ { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" },
+ { GsmL1_Status_CriticalError, "Critical error" },
+ { GsmL1_Status_OverheatError, "Overheat error" },
+ { GsmL1_Status_DeviceError, "Device error" },
+ { GsmL1_Status_FacchError, "FACCH / TCH order error" },
+ { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" },
+ { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" },
+ { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" },
+ { GsmL1_Status_NotSynchronized, "Not synchronized" },
+ { GsmL1_Status_Unsupported, "Unsupported feature" },
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0)
+ { GsmL1_Status_ClockError, "Clock error" },
+#endif
+ { 0, NULL }
+};
+
+const struct value_string femtobts_tracef_names[29] = {
+ { DBG_DEBUG, "DEBUG" },
+ { DBG_L1WARNING, "L1_WARNING" },
+ { DBG_ERROR, "ERROR" },
+ { DBG_L1RXMSG, "L1_RX_MSG" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" },
+ { DBG_L1TXMSG, "L1_TX_MSG" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" },
+ { DBG_MPHCNF, "MPH_CNF" },
+ { DBG_MPHIND, "MPH_IND" },
+ { DBG_MPHREQ, "MPH_REQ" },
+ { DBG_PHIND, "PH_IND" },
+ { DBG_PHREQ, "PH_REQ" },
+ { DBG_PHYRF, "PHY_RF" },
+ { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" },
+ { DBG_MODE, "MODE" },
+ { DBG_TDMAINFO, "TDMA_INFO" },
+ { DBG_BADCRC, "BAD_CRC" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "DEVICE_MSG" },
+ { DBG_RACHINFO, "RACH_INFO" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "MEMORY" },
+ { DBG_PROFILING, "PROFILING" },
+ { DBG_TESTCOMMENT, "TEST_COMMENT" },
+ { DBG_TEST, "TEST" },
+ { DBG_STATUS, "STATUS" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_tracef_docs[29] = {
+ { DBG_DEBUG, "Debug Region" },
+ { DBG_L1WARNING, "L1 Warning Region" },
+ { DBG_ERROR, "Error Region" },
+ { DBG_L1RXMSG, "L1_RX_MSG Region" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" },
+ { DBG_L1TXMSG, "L1_TX_MSG Region" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" },
+ { DBG_MPHCNF, "MphConfirmation Region" },
+ { DBG_MPHIND, "MphIndication Region" },
+ { DBG_MPHREQ, "MphRequest Region" },
+ { DBG_PHIND, "PhIndication Region" },
+ { DBG_PHREQ, "PhRequest Region" },
+ { DBG_PHYRF, "PhyRF Region" },
+ { DBG_PHYRFMSGBYTE, "PhyRF Message Region" },
+ { DBG_MODE, "Mode Region" },
+ { DBG_TDMAINFO, "TDMA Info Region" },
+ { DBG_BADCRC, "Bad CRC Region" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "Device Message Region" },
+ { DBG_RACHINFO, "RACH Info" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "Memory Region" },
+ { DBG_PROFILING, "Profiling Region" },
+ { DBG_TESTCOMMENT, "Test Comments" },
+ { DBG_TEST, "Test Region" },
+ { DBG_STATUS, "Status Region" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_tch_pl_names[] = {
+ { GsmL1_TchPlType_NA, "N/A" },
+ { GsmL1_TchPlType_Fr, "FR" },
+ { GsmL1_TchPlType_Hr, "HR" },
+ { GsmL1_TchPlType_Efr, "EFR" },
+ { GsmL1_TchPlType_Amr, "AMR(IF2)" },
+ { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" },
+ { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" },
+ { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" },
+ { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" },
+ { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" },
+ { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" },
+ { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" },
+ { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" },
+ { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_clksrc_names[] = {
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ { SuperFemto_ClkSrcId_None, "None" },
+ { SuperFemto_ClkSrcId_Ocxo, "ocxo" },
+ { SuperFemto_ClkSrcId_Tcxo, "tcxo" },
+ { SuperFemto_ClkSrcId_External, "ext" },
+ { SuperFemto_ClkSrcId_GpsPps, "gps" },
+ { SuperFemto_ClkSrcId_Trx, "trx" },
+ { SuperFemto_ClkSrcId_Rx, "rx" },
+ { SuperFemto_ClkSrcId_Edge, "edge" },
+ { SuperFemto_ClkSrcId_NetList, "nwl" },
+#else
+ { SF_CLKSRC_NONE, "None" },
+ { SF_CLKSRC_OCXO, "ocxo" },
+ { SF_CLKSRC_TCXO, "tcxo" },
+ { SF_CLKSRC_EXT, "ext" },
+ { SF_CLKSRC_GPS, "gps" },
+ { SF_CLKSRC_TRX, "trx" },
+ { SF_CLKSRC_RX, "rx" },
+#endif
+ { 0, NULL }
+};
+
+const struct value_string femtobts_dir_names[] = {
+ { GsmL1_Dir_TxDownlink, "TxDL" },
+ { GsmL1_Dir_TxUplink, "TxUL" },
+ { GsmL1_Dir_RxUplink, "RxUL" },
+ { GsmL1_Dir_RxDownlink, "RxDL" },
+ { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" },
+ { 0, NULL }
+};
+
+const struct value_string femtobts_chcomb_names[] = {
+ { GsmL1_LogChComb_0, "dummy" },
+ { GsmL1_LogChComb_I, "tch_f" },
+ { GsmL1_LogChComb_II, "tch_h" },
+ { GsmL1_LogChComb_IV, "ccch" },
+ { GsmL1_LogChComb_V, "ccch_sdcch4" },
+ { GsmL1_LogChComb_VII, "sdcch8" },
+ { GsmL1_LogChComb_XIII, "pdtch" },
+ { 0, NULL }
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS] = {
+ [PDCH_CS_1] = 23,
+ [PDCH_CS_2] = 34,
+ [PDCH_CS_3] = 40,
+ [PDCH_CS_4] = 54,
+ [PDCH_MCS_1] = 27,
+ [PDCH_MCS_2] = 33,
+ [PDCH_MCS_3] = 42,
+ [PDCH_MCS_4] = 49,
+ [PDCH_MCS_5] = 60,
+ [PDCH_MCS_6] = 78,
+ [PDCH_MCS_7] = 118,
+ [PDCH_MCS_8] = 142,
+ [PDCH_MCS_9] = 154
+};
diff --git a/src/osmo-bts-sysmo/femtobts.h b/src/osmo-bts-sysmo/femtobts.h
new file mode 100644
index 00000000..9163ebbf
--- /dev/null
+++ b/src/osmo-bts-sysmo/femtobts.h
@@ -0,0 +1,110 @@
+#ifndef FEMTOBTS_H
+#define FEMTOBTS_H
+
+#include <stdlib.h>
+#include <osmocom/core/utils.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1const.h>
+
+#ifdef FEMTOBTS_API_VERSION
+#define SuperFemto_PrimId_t FemtoBts_PrimId_t
+#define SuperFemto_Prim_t FemtoBts_Prim_t
+#define SuperFemto_PrimId_SystemInfoReq FemtoBts_PrimId_SystemInfoReq
+#define SuperFemto_PrimId_SystemInfoCnf FemtoBts_PrimId_SystemInfoCnf
+#define SuperFemto_SystemInfoCnf_t FemtoBts_SystemInfoCnf_t
+#define SuperFemto_PrimId_SystemFailureInd FemtoBts_PrimId_SystemFailureInd
+#define SuperFemto_PrimId_ActivateRfReq FemtoBts_PrimId_ActivateRfReq
+#define SuperFemto_PrimId_ActivateRfCnf FemtoBts_PrimId_ActivateRfCnf
+#define SuperFemto_PrimId_DeactivateRfReq FemtoBts_PrimId_DeactivateRfReq
+#define SuperFemto_PrimId_DeactivateRfCnf FemtoBts_PrimId_DeactivateRfCnf
+#define SuperFemto_PrimId_SetTraceFlagsReq FemtoBts_PrimId_SetTraceFlagsReq
+#define SuperFemto_PrimId_RfClockInfoReq FemtoBts_PrimId_RfClockInfoReq
+#define SuperFemto_PrimId_RfClockInfoCnf FemtoBts_PrimId_RfClockInfoCnf
+#define SuperFemto_PrimId_RfClockSetupReq FemtoBts_PrimId_RfClockSetupReq
+#define SuperFemto_PrimId_RfClockSetupCnf FemtoBts_PrimId_RfClockSetupCnf
+#define SuperFemto_PrimId_Layer1ResetReq FemtoBts_PrimId_Layer1ResetReq
+#define SuperFemto_PrimId_Layer1ResetCnf FemtoBts_PrimId_Layer1ResetCnf
+#define SuperFemto_PrimId_NUM FemtoBts_PrimId_NUM
+#define HW_SYSMOBTS_V1 1
+#define SUPERFEMTO_API(x,y,z) FEMTOBTS_API(x,y,z)
+#endif
+
+#ifdef L1_HAS_RTP_MODE
+/*
+ * The bit ordering has been fixed on >= 3.10 but I am verifying
+ * this on 3.11.
+ */
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3, 11, 0)
+#define USE_L1_RTP_MODE /* Tell L1 to use RTP mode */
+#endif
+#endif
+
+/*
+ * Depending on the firmware version either GsmL1_Prim_t or SuperFemto_Prim_t
+ * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
+ * bigger struct.
+ */
+#define SYSMOBTS_PRIM_SIZE \
+ (OSMO_MAX(sizeof(SuperFemto_Prim_t), sizeof(GsmL1_Prim_t)) + 128)
+
+enum l1prim_type {
+ L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */
+ L1P_T_REQ,
+ L1P_T_CONF,
+ L1P_T_IND,
+};
+
+#if !defined(SUPERFEMTO_API_VERSION) || SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0)
+enum uperfemto_clk_src {
+ SF_CLKSRC_NONE = 0,
+ SF_CLKSRC_OCXO = 1,
+ SF_CLKSRC_TCXO = 2,
+ SF_CLKSRC_EXT = 3,
+ SF_CLKSRC_GPS = 4,
+ SF_CLKSRC_TRX = 5,
+ SF_CLKSRC_RX = 6,
+ SF_CLKSRC_NL = 7,
+};
+#endif
+
+const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM];
+const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1];
+const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM];
+
+const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM];
+const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1];
+const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM];
+
+const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1];
+const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1];
+
+const struct value_string femtobts_tracef_names[29];
+const struct value_string femtobts_tracef_docs[29];
+
+const struct value_string femtobts_tch_pl_names[15];
+const struct value_string femtobts_chcomb_names[8];
+const struct value_string femtobts_clksrc_names[10];
+
+const struct value_string femtobts_dir_names[6];
+
+enum pdch_cs {
+ PDCH_CS_1,
+ PDCH_CS_2,
+ PDCH_CS_3,
+ PDCH_CS_4,
+ PDCH_MCS_1,
+ PDCH_MCS_2,
+ PDCH_MCS_3,
+ PDCH_MCS_4,
+ PDCH_MCS_5,
+ PDCH_MCS_6,
+ PDCH_MCS_7,
+ PDCH_MCS_8,
+ PDCH_MCS_9,
+ _NUM_PDCH_CS
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS];
+
+#endif /* FEMTOBTS_H */
diff --git a/src/osmo-bts-sysmo/hw_misc.c b/src/osmo-bts-sysmo/hw_misc.c
new file mode 100644
index 00000000..6aa3b83f
--- /dev/null
+++ b/src/osmo-bts-sysmo/hw_misc.c
@@ -0,0 +1,113 @@
+/* Misc HW routines for Sysmocom BTS */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/utils.h>
+
+#include "hw_misc.h"
+
+static const struct value_string sysmobts_led_names[] = {
+ { LED_RF_ACTIVE, "activity_led" },
+ { LED_ONLINE, "online_led" },
+ { 0, NULL }
+};
+
+int sysmobts_led_set(enum sysmobts_led nr, int on)
+{
+ char tmp[PATH_MAX+1];
+ const char *filename;
+ int fd;
+ uint8_t byte;
+
+ if (on)
+ byte = '1';
+ else
+ byte = '0';
+
+ filename = get_value_string(sysmobts_led_names, nr);
+ if (!filename)
+ return -EINVAL;
+
+ snprintf(tmp, sizeof(tmp)-1, "/sys/class/leds/%s/brightness", filename);
+ tmp[sizeof(tmp)-1] = '\0';
+
+ fd = open(tmp, O_WRONLY);
+ if (fd < 0)
+ return -ENODEV;
+
+ write(fd, &byte, 1);
+
+ close(fd);
+
+ return 0;
+}
+
+#if 0
+#define HWMON_PREFIX "/sys/class/hwmon/hwmon0/device"
+
+static FILE *temperature_f[NUM_TEMP];
+
+int sysmobts_temp_init()
+{
+ char tmp[PATH_MAX+1];
+ FILE *in;
+ int rc = 0;
+
+ for (i = 0; i < NUM_TEMP; i++) {
+ snprintf(tmp, sizeof(tmp)-1, HWMON_PREFIX "/temp%u_input", i+1),
+ tmp[sizeof(tmp)-1] = '\0';
+
+ temperature_f[i] = fopen(tmp, "r");
+ if (!temperature_f[i])
+ rc = -ENODEV;
+ }
+
+ return 0;
+}
+
+int sysmobts_temp_get(uint8_t num)
+{
+ if (num >= NUM_TEMP)
+ return -EINVAL;
+
+ if (!temperature_f[num])
+ return -ENODEV;
+
+
+ in = fopen(tmp, "r");
+ if (!in)
+ return -ENODEV;
+
+ fclose(tmp);
+
+ return 0;
+}
+#endif
diff --git a/src/osmo-bts-sysmo/hw_misc.h b/src/osmo-bts-sysmo/hw_misc.h
new file mode 100644
index 00000000..c4838dbf
--- /dev/null
+++ b/src/osmo-bts-sysmo/hw_misc.h
@@ -0,0 +1,12 @@
+#ifndef _SYSMOBTS_HW_MISC_H
+#define _SYSMOBTS_HW_MISC_H
+
+enum sysmobts_led {
+ LED_NONE,
+ LED_RF_ACTIVE,
+ LED_ONLINE,
+};
+
+int sysmobts_led_set(enum sysmobts_led nr, int on);
+
+#endif
diff --git a/src/osmo-bts-sysmo/l1_fwd.h b/src/osmo-bts-sysmo/l1_fwd.h
new file mode 100644
index 00000000..55397920
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_fwd.h
@@ -0,0 +1,5 @@
+#define L1FWD_L1_PORT 9999
+#define L1FWD_SYS_PORT 9998
+#define L1FWD_TCH_PORT 9997
+#define L1FWD_PDTCH_PORT 9996
+
diff --git a/src/osmo-bts-sysmo/l1_fwd_main.c b/src/osmo-bts-sysmo/l1_fwd_main.c
new file mode 100644
index 00000000..bc9fc21c
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_fwd_main.c
@@ -0,0 +1,236 @@
+/* Sysmocom femtobts L1 proxy */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/application.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "l1_fwd.h"
+
+static const uint16_t fwd_udp_ports[_NUM_MQ_WRITE] = {
+ [MQ_SYS_READ] = L1FWD_SYS_PORT,
+ [MQ_L1_READ] = L1FWD_L1_PORT,
+#ifndef HW_SYSMOBTS_V1
+ [MQ_TCH_READ] = L1FWD_TCH_PORT,
+ [MQ_PDTCH_READ] = L1FWD_PDTCH_PORT,
+#endif
+};
+
+struct l1fwd_hdl {
+ struct sockaddr_storage remote_sa[_NUM_MQ_WRITE];
+ socklen_t remote_sa_len[_NUM_MQ_WRITE];
+
+ struct osmo_wqueue udp_wq[_NUM_MQ_WRITE];
+
+ struct femtol1_hdl *fl1h;
+};
+
+
+/* callback when there's a new L1 primitive coming in from the HW */
+int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg)
+{
+ struct l1fwd_hdl *l1fh = fl1h->priv;
+
+ /* Enqueue message to UDP socket */
+ if (osmo_wqueue_enqueue(&l1fh->udp_wq[wq], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Write queue %d full. dropping msg\n", wq);
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/* callback when there's a new SYS primitive coming in from the HW */
+int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg)
+{
+ struct l1fwd_hdl *l1fh = fl1h->priv;
+
+ /* Enqueue message to UDP socket */
+ if (osmo_wqueue_enqueue(&l1fh->udp_wq[MQ_SYS_WRITE], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE ful. dropping msg\n");
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+
+/* data has arrived on the udp socket */
+static int udp_read_cb(struct osmo_fd *ofd)
+{
+ struct msgb *msg = msgb_alloc_headroom(SYSMOBTS_PRIM_SIZE, 128, "udp_rx");
+ struct l1fwd_hdl *l1fh = ofd->data;
+ struct femtol1_hdl *fl1h = l1fh->fl1h;
+ int rc;
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->l1h = msg->data;
+
+ l1fh->remote_sa_len[ofd->priv_nr] = sizeof(l1fh->remote_sa[ofd->priv_nr]);
+ rc = recvfrom(ofd->fd, msg->l1h, msgb_tailroom(msg), 0,
+ (struct sockaddr *) &l1fh->remote_sa[ofd->priv_nr], &l1fh->remote_sa_len[ofd->priv_nr]);
+ if (rc < 0) {
+ perror("read from udp");
+ msgb_free(msg);
+ return rc;
+ } else if (rc == 0) {
+ perror("len=0 read from udp");
+ msgb_free(msg);
+ return rc;
+ }
+ msgb_put(msg, rc);
+
+ DEBUGP(DL1C, "UDP: Received %u bytes for queue %d\n", rc,
+ ofd->priv_nr);
+
+ /* put the message into the right queue */
+ if (osmo_wqueue_enqueue(&fl1h->write_q[ofd->priv_nr], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Write queue %d full. dropping msg\n",
+ ofd->priv_nr);
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/* callback when we can write to the UDP socket */
+static int udp_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ int rc;
+ struct l1fwd_hdl *l1fh = ofd->data;
+
+ DEBUGP(DL1C, "UDP: Writing %u bytes for queue %d\n", msgb_l1len(msg),
+ ofd->priv_nr);
+
+ rc = sendto(ofd->fd, msg->l1h, msgb_l1len(msg), 0,
+ (const struct sockaddr *)&l1fh->remote_sa[ofd->priv_nr], l1fh->remote_sa_len[ofd->priv_nr]);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
+ strerror(errno));
+ return rc;
+ } else if (rc < msgb_l1len(msg)) {
+ LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: "
+ "%u < %u\n", rc, msgb_l1len(msg));
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct l1fwd_hdl *l1fh;
+ struct femtol1_hdl *fl1h;
+ int rc, i;
+ void *ctx = talloc_named_const(NULL, 0, "l1_fwd");
+
+ printf("sizeof(GsmL1_Prim_t) = %zu\n", sizeof(GsmL1_Prim_t));
+ printf("sizeof(SuperFemto_Prim_t) = %zu\n", sizeof(SuperFemto_Prim_t));
+
+ osmo_init_logging2(ctx, &bts_log_info);
+
+ /*
+ * hack and prevent that two l1fwd-proxy/sysmobts run at the same
+ * time. This is done by binding to the same VTY port.
+ */
+ rc = osmo_sock_init(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
+ "127.0.0.1", 4241, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to bind to the BTS VTY port.\n");
+ return EXIT_FAILURE;
+ }
+
+ /* allocate new femtol1_handle */
+ fl1h = talloc_zero(ctx, struct femtol1_hdl);
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+
+ /* open the actual hardware transport */
+ for (i = 0; i < ARRAY_SIZE(fl1h->write_q); i++) {
+ rc = l1if_transport_open(i, fl1h);
+ if (rc < 0)
+ exit(1);
+ }
+
+ /* create our fwd handle */
+ l1fh = talloc_zero(ctx, struct l1fwd_hdl);
+
+ l1fh->fl1h = fl1h;
+ fl1h->priv = l1fh;
+
+ /* Open UDP */
+ for (i = 0; i < ARRAY_SIZE(l1fh->udp_wq); i++) {
+ struct osmo_wqueue *wq = &l1fh->udp_wq[i];
+
+ osmo_wqueue_init(wq, 10);
+ wq->write_cb = udp_write_cb;
+ wq->read_cb = udp_read_cb;
+
+ wq->bfd.when |= BSC_FD_READ;
+ wq->bfd.data = l1fh;
+ wq->bfd.priv_nr = i;
+ rc = osmo_sock_init_ofd(&wq->bfd, AF_UNSPEC, SOCK_DGRAM,
+ IPPROTO_UDP, NULL, fwd_udp_ports[i],
+ OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ perror("sock_init");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ rc = osmo_select_main(0);
+ if (rc < 0) {
+ perror("select");
+ exit(1);
+ }
+ }
+ exit(0);
+}
diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c
new file mode 100644
index 00000000..87cf25a0
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_if.c
@@ -0,0 +1,1899 @@
+/* Interface handler for Sysmocom L1 */
+
+/* (C) 2011-2016 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/lapdm.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/tx_power.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "hw_misc.h"
+#include "misc/sysmobts_par.h"
+#include "eeprom.h"
+#include "utils.h"
+
+struct wait_l1_conf {
+ struct llist_head list; /* internal linked list */
+ struct osmo_timer_list timer; /* timer for L1 timeout */
+ unsigned int conf_prim_id; /* primitive we expect in response */
+ HANDLE conf_hLayer3; /* layer 3 handle we expect in response */
+ unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */
+ l1if_compl_cb *cb;
+ void *cb_data;
+};
+
+static void release_wlc(struct wait_l1_conf *wlc)
+{
+ osmo_timer_del(&wlc->timer);
+ talloc_free(wlc);
+}
+
+static void l1if_req_timeout(void *data)
+{
+ struct wait_l1_conf *wlc = data;
+
+ if (wlc->is_sys_prim)
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n",
+ get_value_string(femtobts_sysprim_names, wlc->conf_prim_id));
+ else
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n",
+ get_value_string(femtobts_l1prim_names, wlc->conf_prim_id));
+ exit(23);
+}
+
+static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim)
+{
+ switch (prim->id) {
+ case GsmL1_PrimId_MphInitReq:
+ return prim->u.mphInitReq.hLayer3;
+ case GsmL1_PrimId_MphCloseReq:
+ return prim->u.mphCloseReq.hLayer3;
+ case GsmL1_PrimId_MphConnectReq:
+ return prim->u.mphConnectReq.hLayer3;
+ case GsmL1_PrimId_MphDisconnectReq:
+ return prim->u.mphDisconnectReq.hLayer3;
+ case GsmL1_PrimId_MphActivateReq:
+ return prim->u.mphActivateReq.hLayer3;
+ case GsmL1_PrimId_MphDeactivateReq:
+ return prim->u.mphDeactivateReq.hLayer3;
+ case GsmL1_PrimId_MphConfigReq:
+ return prim->u.mphConfigReq.hLayer3;
+ case GsmL1_PrimId_MphMeasureReq:
+ return prim->u.mphMeasureReq.hLayer3;
+ case GsmL1_PrimId_MphInitCnf:
+ return prim->u.mphInitCnf.hLayer3;
+ case GsmL1_PrimId_MphCloseCnf:
+ return prim->u.mphCloseCnf.hLayer3;
+ case GsmL1_PrimId_MphConnectCnf:
+ return prim->u.mphConnectCnf.hLayer3;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ return prim->u.mphDisconnectCnf.hLayer3;
+ case GsmL1_PrimId_MphActivateCnf:
+ return prim->u.mphActivateCnf.hLayer3;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ return prim->u.mphDeactivateCnf.hLayer3;
+ case GsmL1_PrimId_MphConfigCnf:
+ return prim->u.mphConfigCnf.hLayer3;
+ case GsmL1_PrimId_MphMeasureCnf:
+ return prim->u.mphMeasureCnf.hLayer3;
+ case GsmL1_PrimId_MphTimeInd:
+ case GsmL1_PrimId_MphSyncInd:
+ case GsmL1_PrimId_PhEmptyFrameReq:
+ case GsmL1_PrimId_PhDataReq:
+ case GsmL1_PrimId_PhConnectInd:
+ case GsmL1_PrimId_PhReadyToSendInd:
+ case GsmL1_PrimId_PhDataInd:
+ case GsmL1_PrimId_PhRaInd:
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id);
+ break;
+ }
+ return 0;
+}
+
+
+static int _l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ int is_system_prim, l1if_compl_cb *cb, void *data)
+{
+ struct wait_l1_conf *wlc;
+ struct osmo_wqueue *wqueue;
+ unsigned int timeout_secs;
+
+ /* allocate new wsc and store reference to mutex and conf_id */
+ wlc = talloc_zero(fl1h, struct wait_l1_conf);
+ wlc->cb = cb;
+ wlc->cb_data = data;
+
+ /* Make sure we actually have received a REQUEST type primitive */
+ if (is_system_prim == 0) {
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+
+ LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n",
+ get_value_string(femtobts_l1prim_names, l1p->id));
+
+ if (femtobts_l1prim_type[l1p->id] != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n",
+ get_value_string(femtobts_l1prim_names, l1p->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 0;
+ wlc->conf_prim_id = femtobts_l1prim_req2conf[l1p->id];
+ wlc->conf_hLayer3 = l1p_get_hLayer3(l1p);
+ wqueue = &fl1h->write_q[MQ_L1_WRITE];
+ timeout_secs = 30;
+ } else {
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n",
+ get_value_string(femtobts_sysprim_names, sysp->id));
+
+ if (femtobts_sysprim_type[sysp->id] != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n",
+ get_value_string(femtobts_sysprim_names, sysp->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 1;
+ wlc->conf_prim_id = femtobts_sysprim_req2conf[sysp->id];
+ wqueue = &fl1h->write_q[MQ_SYS_WRITE];
+ timeout_secs = 30;
+ }
+
+ /* enqueue the message in the queue and add wsc to list */
+ if (osmo_wqueue_enqueue(wqueue, msg) != 0) {
+ /* So we will get a timeout but the log message might help */
+ LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n",
+ is_system_prim ? "system primitive" : "gsm");
+ msgb_free(msg);
+ }
+ llist_add(&wlc->list, &fl1h->wlc_list);
+
+ /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */
+ wlc->timer.data = wlc;
+ wlc->timer.cb = l1if_req_timeout;
+ osmo_timer_schedule(&wlc->timer, timeout_secs, 0);
+
+ return 0;
+}
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 1, cb, data);
+}
+
+int l1if_gsm_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 0, cb, data);
+}
+
+/* allocate a msgb containing a GsmL1_Prim_t */
+struct msgb *l1p_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t));
+
+ return msg;
+}
+
+/* allocate a msgb containing a SuperFemto_Prim_t */
+struct msgb *sysp_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(SuperFemto_Prim_t), "sys_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(SuperFemto_Prim_t));
+
+ return msg;
+}
+
+static GsmL1_PhDataReq_t *
+data_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = rts_ind->hLayer1;
+ data_req->u8Tn = rts_ind->u8Tn;
+ data_req->u32Fn = rts_ind->u32Fn;
+ data_req->sapi = rts_ind->sapi;
+ data_req->subCh = rts_ind->subCh;
+ data_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return data_req;
+}
+
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = rts_ind->hLayer1;
+ empty_req->u8Tn = rts_ind->u8Tn;
+ empty_req->u32Fn = rts_ind->u32Fn;
+ empty_req->sapi = rts_ind->sapi;
+ empty_req->subCh = rts_ind->subCh;
+ empty_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return empty_req;
+}
+
+/* fill PH-DATA.req from l1sap primitive */
+static GsmL1_PhDataReq_t *
+data_req_from_l1sap(GsmL1_Prim_t *l1p, struct femtol1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch,
+ uint8_t block_nr, uint8_t len)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = fl1->hLayer1;
+ data_req->u8Tn = tn;
+ data_req->u32Fn = fn;
+ data_req->sapi = sapi;
+ data_req->subCh = sub_ch;
+ data_req->u8BlockNbr = block_nr;
+
+ data_req->msgUnitParam.u8Size = len;
+
+ return data_req;
+}
+
+/* fill PH-EMPTY_FRAME.req from l1sap primitive */
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct femtol1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi,
+ uint8_t subch, uint8_t block_nr)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = fl1->hLayer1;
+ empty_req->u8Tn = tn;
+ empty_req->u32Fn = fn;
+ empty_req->sapi = sapi;
+ empty_req->subCh = subch;
+ empty_req->u8BlockNbr = block_nr;
+
+ return empty_req;
+}
+
+static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap, bool use_cache)
+{
+ struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx);
+ struct msgb *l1msg = l1p_msgb_alloc();
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0;
+ uint8_t chan_nr, link_id;
+ int len;
+
+ if (!msg) {
+ LOGP(DL1C, LOGL_FATAL, "PH-DATA.req without msg. "
+ "Please fix!\n");
+ abort();
+ }
+
+ len = msgb_l2len(msg);
+
+ chan_nr = l1sap->u.data.chan_nr;
+ link_id = l1sap->u.data.link_id;
+ u32Fn = l1sap->u.data.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ subCh = 0x1f;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ sapi = GsmL1_Sapi_Sacch;
+ if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr))
+ subCh = l1sap_chan2ss(chan_nr);
+ } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) {
+ if (ts_is_pdch(&trx->ts[u8Tn])) {
+ if (L1SAP_IS_PTCCH(u32Fn)) {
+ sapi = GsmL1_Sapi_Ptcch;
+ u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn);
+ } else {
+ sapi = GsmL1_Sapi_Pdtch;
+ u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn);
+ }
+ } else {
+ sapi = GsmL1_Sapi_FacchF;
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ }
+ } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_FacchH;
+ u8BlockNbr = (u32Fn % 26) >> 3;
+ } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Bcch;
+ } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Cbch;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ /* The sapi depends on DSP configuration, not
+ * on the actual SYSTEM INFORMATION 3. */
+ u8BlockNbr = l1sap_fn2ccch_block(u32Fn);
+ if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ"))
+ sapi = GsmL1_Sapi_Pch;
+ else
+ sapi = GsmL1_Sapi_Agch;
+ } else {
+ LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d "
+ "chan_nr %d link_id %d\n", l1sap->oph.primitive,
+ l1sap->oph.operation, chan_nr, link_id);
+ msgb_free(l1msg);
+ return -EINVAL;
+ }
+
+ /* convert l1sap message to GsmL1 primitive, keep payload */
+ if (len) {
+ /* data request */
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len);
+ if (use_cache)
+ memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ lchan->tch.dtx.facch, msgb_l2len(msg));
+ else if (dtx_dl_amr_enabled(lchan) &&
+ ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) ||
+ (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) ||
+ (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) {
+ if (sapi == GsmL1_Sapi_FacchF) {
+ sapi = GsmL1_Sapi_TchF;
+ }
+ if (sapi == GsmL1_Sapi_FacchH) {
+ sapi = GsmL1_Sapi_TchH;
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ }
+ if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) {
+ /* FACCH interruption of DTX silence */
+ /* cache FACCH data */
+ memcpy(lchan->tch.dtx.facch, msg->l2h,
+ msgb_l2len(msg));
+ /* prepare ONSET or INH message */
+ if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_Onset;
+ else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_SidUpdateInH;
+ else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F)
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
+ GsmL1_TchPlType_Amr_SidFirstInH;
+ /* ignored CMR/CMI pair */
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0;
+ l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0;
+ /* update length */
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi,
+ subCh, u8BlockNbr, 3);
+ /* update FN so it can be checked by TCH silence
+ resume handler */
+ lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
+ }
+ } else if (dtx_dl_amr_enabled(lchan) &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) {
+ /* update FN so it can be checked by TCH silence
+ resume handler */
+ lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
+ }
+ else {
+ OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer));
+ memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h,
+ msgb_l2len(msg));
+ }
+ LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n",
+ osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ l1p->u.phDataReq.msgUnitParam.u8Size));
+ } else {
+ /* empty frame */
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ }
+
+ /* send message to DSP's queue */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) {
+ LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(l1msg);
+ } else
+ dtx_int_signal(lchan);
+
+ if (dtx_recursion(lchan))
+ ph_data_req(trx, msg, l1sap, true);
+ return 0;
+}
+
+static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap, bool use_cache, bool marker)
+{
+ struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx);
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi;
+ uint8_t chan_nr;
+ GsmL1_Prim_t *l1p;
+ struct msgb *nmsg = NULL;
+ int rc = -1;
+
+ chan_nr = l1sap->u.tch.chan_nr;
+ u32Fn = l1sap->u.tch.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_TchH;
+ } else {
+ subCh = 0x1f;
+ sapi = GsmL1_Sapi_TchF;
+ }
+
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ /* create new message and fill data */
+ if (msg) {
+ msgb_pull(msg, sizeof(*l1sap));
+ /* create new message */
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ l1p = msgb_l1prim(nmsg);
+ rc = l1if_tch_encode(lchan,
+ l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ &l1p->u.phDataReq.msgUnitParam.u8Size,
+ msg->data, msg->len, u32Fn, use_cache,
+ l1sap->u.tch.marker);
+ if (rc < 0) {
+ /* no data encoded for L1: smth will be generated below */
+ msgb_free(nmsg);
+ nmsg = NULL;
+ }
+ }
+
+ /* no message/data, we might generate an empty traffic msg or re-send
+ cached SID in case of DTX */
+ if (!nmsg)
+ nmsg = gen_empty_tch_msg(lchan, u32Fn);
+
+ /* no traffic message, we generate an empty msg */
+ if (!nmsg) {
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+ }
+
+ l1p = msgb_l1prim(nmsg);
+
+ /* if we provide data, or if data is already in nmsg */
+ if (l1p->u.phDataReq.msgUnitParam.u8Size) {
+ /* data request */
+ data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh,
+ u8BlockNbr,
+ l1p->u.phDataReq.msgUnitParam.u8Size);
+ } else {
+ /* empty frame */
+ if (trx->bts->dtxd && trx != trx->bts->c0)
+ lchan->tch.dtx.dl_active = true;
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ }
+ /* send message to DSP's queue */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg) != 0) {
+ LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(nmsg);
+ return -ENOBUFS;
+ }
+ if (dtx_is_first_p1(lchan))
+ dtx_dispatch(lchan, E_FIRST);
+ else
+ dtx_int_signal(lchan);
+
+ if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */
+ return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false);
+
+ return 0;
+}
+
+static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx);
+ uint8_t chan_nr;
+ struct gsm_lchan *lchan;
+ int rc = 0;
+
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACT_CIPH:
+ chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.u.ciph_req.uplink) {
+ l1if_set_ciphering(fl1, lchan, 0);
+ lchan->ciph_state = LCHAN_CIPH_RX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink) {
+ l1if_set_ciphering(fl1, lchan, 1);
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ }
+ if (l1sap->u.info.u.ciph_req.downlink
+ && l1sap->u.info.u.ciph_req.uplink)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ break;
+ case PRIM_INFO_ACTIVATE:
+ case PRIM_INFO_DEACTIVATE:
+ case PRIM_INFO_MODIFY:
+ chan_nr = l1sap->u.info.u.act_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.type == PRIM_INFO_ACTIVATE)
+ l1if_rsl_chan_act(lchan);
+ else if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+ if (lchan->ho.active == HANDOVER_WAIT_FRAME)
+ l1if_rsl_chan_mod(lchan);
+ else
+ l1if_rsl_mode_modify(lchan);
+ } else if (l1sap->u.info.u.act_req.sacch_only)
+ l1if_rsl_deact_sacch(lchan);
+ else
+ l1if_rsl_chan_rel(lchan);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
+ l1sap->u.info.type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/* primitive from common part */
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ int rc = 0;
+
+ /* called functions MUST NOT take ownership of msgb, as it is
+ * free()d below */
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ rc = ph_data_req(trx, msg, l1sap, false);
+ break;
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+ rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker);
+ break;
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ rc = mph_info_req(trx, msg, l1sap);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ rc = -EINVAL;
+ }
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int handle_mph_time_ind(struct femtol1_hdl *fl1,
+ GsmL1_MphTimeInd_t *time_ind,
+ struct msgb *msg)
+{
+ struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct osmo_phsap_prim l1sap;
+ uint32_t fn;
+
+ /* increment the primitive count for the alive timer */
+ fl1->alive_prim_cnt++;
+
+ /* ignore every time indication, except for c0 */
+ if (trx != bts->c0) {
+ msgb_free(msg);
+ return 0;
+ }
+
+ fn = time_ind->u32Fn;
+
+ 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;
+
+ msgb_free(msg);
+
+ return l1sap_up(trx, &l1sap);
+}
+
+static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return GSM_PCHAN_PDCH;
+ return GSM_PCHAN_TCH_F;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is;
+ default:
+ return ts->pchan;
+ }
+}
+
+static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
+ GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh,
+ uint8_t u8Tn, uint32_t u32Fn)
+{
+ uint8_t cbits = 0;
+ enum gsm_phys_chan_config pchan = pick_pchan(ts);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH);
+
+ switch (sapi) {
+ case GsmL1_Sapi_Bcch:
+ cbits = 0x10;
+ break;
+ case GsmL1_Sapi_Cbch:
+ cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */
+ break;
+ case GsmL1_Sapi_Sacch:
+ switch(pchan) {
+ case GSM_PCHAN_TCH_F:
+ cbits = 0x01;
+ break;
+ case GSM_PCHAN_TCH_H:
+ cbits = 0x02 + subCh;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Sdcch:
+ switch(pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Agch:
+ case GsmL1_Sapi_Pch:
+ cbits = 0x12;
+ break;
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_TchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_TchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_FacchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_FacchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_Ptcch:
+ if (!L1SAP_IS_PTCCH(u32Fn)) {
+ LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame "
+ "number other than 12, got it at %u (%u). "
+ "Please fix!\n", u32Fn % 52, u32Fn);
+ abort();
+ }
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ /* not reached due to default case above */
+ return (cbits << 3) | u8Tn;
+}
+
+static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1,
+ GsmL1_PhReadyToSendInd_t *rts_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct msgb *resp_msg;
+ GsmL1_PhDataReq_t *data_req;
+ GsmL1_MsgUnitParam_t *msu_param;
+ struct gsm_time g_time;
+ uint32_t t3p;
+ int rc;
+ struct osmo_phsap_prim *l1sap;
+ uint8_t chan_nr, link_id;
+ uint32_t fn;
+
+ /* check if primitive should be handled by common part */
+ chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi,
+ rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn);
+ if (chan_nr) {
+ fn = rts_ind->u32Fn;
+ if (rts_ind->sapi == GsmL1_Sapi_Sacch)
+ link_id = LID_SACCH;
+ else
+ link_id = LID_DEDIC;
+ /* recycle the msgb and use it for the L1 primitive,
+ * which means that we (or our caller) must not free it */
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ if (rts_ind->sapi == GsmL1_Sapi_TchF
+ || rts_ind->sapi == GsmL1_Sapi_TchH) {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.tch.chan_nr = chan_nr;
+ l1sap->u.tch.fn = fn;
+ } else {
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ }
+
+ return l1sap_up(trx, l1sap);
+ }
+
+ gsm_fn2gsmtime(&g_time, rts_ind->u32Fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n",
+ get_value_string(femtobts_l1sapi_names, rts_ind->sapi));
+
+ /* in all other cases, we need to allocate a new PH-DATA.ind
+ * primitive msgb and start to fill it */
+ resp_msg = l1p_msgb_alloc();
+ data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
+ msu_param = &data_req->msgUnitParam;
+
+ /* set default size */
+ msu_param->u8Size = GSM_MACBLOCK_LEN;
+
+ switch (rts_ind->sapi) {
+ case GsmL1_Sapi_Sch:
+ /* compute T3prime */
+ t3p = (g_time.t3 - 1) / 10;
+ /* fill SCH burst with data */
+ msu_param->u8Size = 4;
+ msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9);
+ msu_param->u8Buffer[1] = (g_time.t1 >> 1);
+ msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1);
+ msu_param->u8Buffer[3] = (t3p & 1);
+ break;
+ case GsmL1_Sapi_Prach:
+ goto empty_frame;
+ break;
+ default:
+ memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN);
+ break;
+ }
+tx:
+
+ /* transmit */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) {
+ LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(resp_msg);
+ }
+
+ /* free the msgb, as we have not handed it to l1sap and thus
+ * need to release its memory */
+ msgb_free(l1p_msg);
+ return 0;
+
+empty_frame:
+ /* in case we decide to send an empty frame... */
+ empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
+
+ goto tx;
+}
+
+static void dump_meas_res(int ll, GsmL1_MeasParam_t *m)
+{
+ LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, "
+ "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality,
+ m->fBer, m->i16BurstTiming);
+}
+
+static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ uint32_t fn, GsmL1_MeasParam_t *m)
+{
+ 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_MEAS;
+ l1sap.u.info.u.meas_ind.chan_nr = chan_nr;
+ l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming * 64;
+ l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000);
+ l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1);
+ l1sap.u.info.u.meas_ind.fn = fn;
+
+ /* l1sap wants to take msgb ownership. However, as there is no
+ * msg, it will msgb_free(l1sap.oph.msg == NULL) */
+ return l1sap_up(trx, &l1sap);
+}
+
+static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1);
+ uint8_t chan_nr, link_id;
+ struct msgb *sap_msg;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t fn;
+ struct gsm_time g_time;
+ int rc = 0;
+
+ chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi,
+ data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn);
+ if (!chan_nr) {
+ LOGPFN(DL1C, LOGL_ERROR, data_ind->u32Fn, "PH-DATA-INDICATION for unknown sapi %s (%d)\n",
+ get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->sapi);
+ msgb_free(l1p_msg);
+ return ENOTSUP;
+ }
+ fn = data_ind->u32Fn;
+ link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC;
+
+ process_meas_res(trx, chan_nr, fn, &data_ind->measParam);
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n",
+ get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->hLayer2,
+ osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size));
+ dump_meas_res(LOGL_DEBUG, &data_ind->measParam);
+
+ /* check for TCH */
+ if (data_ind->sapi == GsmL1_Sapi_TchF
+ || data_ind->sapi == GsmL1_Sapi_TchH) {
+ /* TCH speech frame handling */
+ rc = l1if_tch_rx(trx, chan_nr, l1p_msg);
+ msgb_free(l1p_msg);
+ return rc;
+ }
+
+ /* fill L1SAP header */
+ sap_msg = l1sap_msgb_alloc(data_ind->msgUnitParam.u8Size);
+ l1sap = msgb_l1sap_prim(sap_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, sap_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ l1sap->u.data.rssi = (int8_t) (data_ind->measParam.fRssi);
+ if (!pcu_direct) { /* FIXME: if pcu_direct=1, then this is not set, what to do in pcu_tx_data_ind() in this case ?*/
+ l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000;
+ l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming * 64;
+ l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10;
+ }
+ /* copy data from L1 primitive to L1SAP primitive */
+ sap_msg->l2h = msgb_put(sap_msg, data_ind->msgUnitParam.u8Size);
+ memcpy(sap_msg->l2h, data_ind->msgUnitParam.u8Buffer,
+ data_ind->msgUnitParam.u8Size);
+
+
+ msgb_free(l1p_msg);
+
+ return l1sap_up(trx, l1sap);
+}
+
+static int handle_ph_ra_ind(struct femtol1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1);
+ struct gsm_bts *bts = trx->bts;
+ struct gsm_lchan *lchan;
+ struct osmo_phsap_prim *l1sap;
+ int rc;
+ struct ph_rach_ind_param rach_ind_param;
+
+ /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */
+ if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) {
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ dump_meas_res(LOGL_DEBUG, &ra_ind->measParam);
+
+ if ((ra_ind->msgUnitParam.u8Size != 1) &&
+ (ra_ind->msgUnitParam.u8Size != 2)) {
+ LOGPFN(DL1C, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n",
+ ra_ind->sapi);
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */
+ rach_ind_param = (struct ph_rach_ind_param) {
+ /* .chan_nr set below */
+ /* .ra set below */
+ .acc_delay = 0,
+ .fn = ra_ind->u32Fn,
+ /* .is_11bit set below */
+ /* .burst_type set below */
+ .rssi = (int8_t) ra_ind->measParam.fRssi,
+ .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0),
+ .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64,
+ };
+
+ lchan = l1if_hLayer_to_lchan(trx, ra_ind->hLayer2);
+ if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH ||
+ lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 ||
+ lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ rach_ind_param.chan_nr = 0x88;
+ else
+ rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan);
+
+ if (ra_ind->msgUnitParam.u8Size == 2) {
+ uint16_t temp;
+ uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0];
+ ra = ra << 3;
+ temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7);
+ ra = ra | temp;
+ rach_ind_param.is_11bit = 1;
+ rach_ind_param.ra = ra;
+ } else {
+ rach_ind_param.is_11bit = 0;
+ rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0];
+ }
+
+ /* the old legacy full-bits acc_delay cannot express negative values */
+ if (ra_ind->measParam.i16BurstTiming > 0)
+ rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2;
+
+ /* mapping of the burst type, the values are specific to osmo-bts-sysmo */
+ switch (ra_ind->burstType) {
+ case GsmL1_BurstType_Access_0:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_0;
+ break;
+ case GsmL1_BurstType_Access_1:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_1;
+ break;
+ case GsmL1_BurstType_Access_2:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_ACCESS_2;
+ break;
+ default:
+ rach_ind_param.burst_type =
+ GSM_L1_BURST_TYPE_NONE;
+ break;
+ }
+
+ /* msgb_trim() invalidates ra_ind, make that abundantly clear: */
+ ra_ind = NULL;
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive data\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION,
+ l1p_msg);
+ l1sap->u.rach_ind = rach_ind_param;
+
+ return l1sap_up(trx, l1sap);
+}
+
+/* handle any random indication from the L1 */
+static int l1if_handle_ind(struct femtol1_hdl *fl1, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ int rc = 0;
+
+ /* all the below called functions must take ownership of the msgb */
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg);
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ msgb_free(msg);
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ msgb_free(msg);
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd,
+ msg);
+ break;
+ case GsmL1_PrimId_PhDataInd:
+ rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg);
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg);
+ break;
+ default:
+ msgb_free(msg);
+ }
+
+ return rc;
+}
+
+static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc)
+{
+ if (wlc->is_sys_prim != 0)
+ return 0;
+ if (l1p->id != wlc->conf_prim_id)
+ return 0;
+ if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3)
+ return 0;
+ return 1;
+}
+
+int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ /* silent, don't clog the log file */
+ break;
+ default:
+ LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n",
+ get_value_string(femtobts_l1prim_names, l1p->id), wq);
+ }
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ if (is_prim_compat(l1p, wlc)) {
+ llist_del(&wlc->list);
+ if (wlc->cb) {
+ /* call-back function must take
+ * ownership of msgb */
+ rc = wlc->cb(femtol1_hdl_trx(fl1h), msg,
+ wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ return rc;
+ }
+ }
+
+ /* if we reach here, it is not a Conf for a pending Req */
+ return l1if_handle_ind(fl1h, msg);
+}
+
+int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n",
+ get_value_string(femtobts_sysprim_names, sysp->id));
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ /* the limitation here is that we cannot have multiple callers
+ * sending the same primitive */
+ if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) {
+ llist_del(&wlc->list);
+ if (wlc->cb) {
+ /* call-back function must take
+ * ownership of msgb */
+ rc = wlc->cb(femtol1_hdl_trx(fl1h), msg,
+ wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ return rc;
+ }
+ }
+ /* if we reach here, it is not a Conf for a pending Req */
+ return l1if_handle_ind(fl1h, msg);
+}
+
+static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+ int on = 0;
+ unsigned int i;
+
+ if (sysp->id == SuperFemto_PrimId_ActivateRfCnf)
+ on = 1;
+
+ if (on)
+ status = sysp->u.activateRfCnf.status;
+ else
+ status = sysp->u.deactivateRfCnf.status;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE",
+ get_value_string(femtobts_l1status_names, status));
+
+
+ if (on) {
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n",
+ get_value_string(femtobts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-ACT failure");
+ } else
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 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 (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+ oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY);
+ } else {
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 0);
+ 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);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+int get_clk_cal(struct femtol1_hdl *hdl)
+{
+#ifdef FEMTOBTS_API_VERSION
+ return hdl->clk_cal;
+#else
+ switch (hdl->clk_src) {
+ case SuperFemto_ClkSrcId_Ocxo:
+ case SuperFemto_ClkSrcId_Tcxo:
+ /* only for those on-board clocks it makes sense to use
+ * the calibration value */
+ return hdl->clk_cal;
+ default:
+ /* external clocks like GPS are taken 1:1 without any
+ * modification by a local calibration value */
+ LOGP(DL1C, LOGL_INFO, "Ignoring Clock Calibration for "
+ "selected %s clock\n",
+ get_value_string(femtobts_clksrc_names, hdl->clk_src));
+ return 0;
+ }
+#endif
+}
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,2,0)
+/*
+ * RevC was the last HW revision without an external
+ * attenuator. Check for that.
+ */
+static int has_external_atten(struct femtol1_hdl *hdl)
+{
+ /* older version doesn't have an attenuator */
+ return hdl->hw_info.ver_major > 2;
+}
+#endif /* 2.2.0 */
+
+/* activate or de-activate the entire RF-Frontend */
+int l1if_activate_rf(struct femtol1_hdl *hdl, int on)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ if (on) {
+ sysp->id = SuperFemto_PrimId_ActivateRfReq;
+#ifdef HW_SYSMOBTS_V1
+ sysp->u.activateRfReq.u12ClkVc = get_clk_cal(hdl);
+#else
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(0,2,0)
+ sysp->u.activateRfReq.timing.u8TimSrc = 1; /* Master */
+#endif /* 0.2.0 */
+ sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0;
+ sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct;
+ /* Use clock from OCXO or whatever source is configured */
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0)
+ sysp->u.activateRfReq.rfTrx.u8ClkSrc = hdl->clk_src;
+#else
+ sysp->u.activateRfReq.rfTrx.clkSrc = hdl->clk_src;
+#endif /* 2.1.0 */
+ sysp->u.activateRfReq.rfTrx.iClkCor = get_clk_cal(hdl);
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0)
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0)
+ sysp->u.activateRfReq.rfRx.u8ClkSrc = hdl->clk_src;
+#else
+ sysp->u.activateRfReq.rfRx.clkSrc = hdl->clk_src;
+#endif /* 2.1.0 */
+ sysp->u.activateRfReq.rfRx.iClkCor = get_clk_cal(hdl);
+#endif /* API 2.4.0 */
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,2,0)
+ struct gsm_bts_trx *trx = hdl->phy_inst->trx;
+ if (has_external_atten(hdl)) {
+ LOGP(DL1C, LOGL_INFO, "Using external attenuator.\n");
+ sysp->u.activateRfReq.rfTrx.u8UseExtAtten = 1;
+ sysp->u.activateRfReq.rfTrx.fMaxTxPower =
+ (float) get_p_trxout_target_mdBm(trx, 0) / 1000;
+ }
+#endif /* 2.2.0 */
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,8,1)
+ /* maximum cell size in quarter-bits, 90 == 12.456 km */
+ sysp->u.activateRfReq.u8MaxCellSize = 90;
+#endif
+#endif /* !HW_SYSMOBTS_V1 */
+ } else {
+ sysp->id = SuperFemto_PrimId_DeactivateRfReq;
+ }
+
+ return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL);
+}
+
+static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) {
+ struct gsm_lchan *lchan = &ts->lchan[i];
+
+ if (!is_muted)
+ continue;
+
+ if (lchan->state != LCHAN_S_ACTIVE)
+ continue;
+
+ /* skip channels that might be active for another reason */
+ if (lchan->type == GSM_LCHAN_CCCH)
+ continue;
+ if (lchan->type == GSM_LCHAN_PDTCH)
+ continue;
+
+ if (lchan->s <= 0)
+ continue;
+
+ lchan->s = 0;
+ rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL);
+ }
+}
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n",
+ get_value_string(femtobts_l1status_names, status));
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0);
+ } else {
+ int i;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n",
+ get_value_string(femtobts_l1status_names, status));
+ bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]);
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1);
+
+ osmo_static_assert(
+ ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute),
+ ts_array_size);
+
+ for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i)
+ mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+#endif
+
+/* mute/unmute RF time slots */
+int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb)
+{
+
+ LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n",
+ mute[0], mute[1], mute[2], mute[3],
+ mute[4], mute[5], mute[6], mute[7]
+ );
+
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(3,6,0)
+ const uint8_t unmuted[8] = { 0,0,0,0,0,0,0,0 };
+ struct gsm_bts_trx *trx = hdl->phy_inst->trx;
+ int i;
+ LOGP(DL1C, LOGL_ERROR, "RF-MUTE.req not supported by SuperFemto\n");
+ /* always acknowledge an un-MUTE (which is a no-op if MUTE is not supported */
+ if (!memcmp(mute, unmuted, ARRAY_SIZE(unmuted))) {
+ bts_update_status(BTS_STATUS_RF_MUTE, mute[0]);
+ oml_mo_rf_lock_chg(&trx->mo, mute, 1);
+ for (i = 0; i < ARRAY_SIZE(unmuted); ++i)
+ mute_handle_ts(&trx->ts[i], mute[i]);
+ return 0;
+ }
+ return -ENOTSUP;
+#else
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ sysp->id = SuperFemto_PrimId_MuteRfReq;
+ memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute));
+ /* save for later use */
+ memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute));
+
+ return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL);
+#endif /* < 3.6.0 */
+}
+
+/* call-back on arrival of DSP+FPGA version + band capability */
+static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ SuperFemto_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ fl1h->hw_info.dsp_version[0] = sic->dspVersion.major;
+ fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor;
+ fl1h->hw_info.dsp_version[2] = sic->dspVersion.build;
+
+ fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major;
+ fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor;
+ fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build;
+
+#ifndef HW_SYSMOBTS_V1
+ fl1h->hw_info.ver_major = sic->boardVersion.rev;
+ fl1h->hw_info.ver_minor = sic->boardVersion.option;
+#endif
+
+ LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\nn",
+ sic->dspVersion.major, sic->dspVersion.minor,
+ sic->dspVersion.build, sic->fpgaVersion.major,
+ sic->fpgaVersion.minor, sic->fpgaVersion.build);
+
+#ifdef HW_SYSMOBTS_V1
+ if (sic->rfBand.gsm850)
+ fl1h->hw_info.band_support |= GSM_BAND_850;
+ if (sic->rfBand.gsm900)
+ fl1h->hw_info.band_support |= GSM_BAND_900;
+ if (sic->rfBand.dcs1800)
+ fl1h->hw_info.band_support |= GSM_BAND_1800;
+ if (sic->rfBand.pcs1900)
+ fl1h->hw_info.band_support |= GSM_BAND_1900;
+#endif
+
+ if (!(fl1h->hw_info.band_support & trx->bts->band))
+ LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n",
+ gsm_band_name(trx->bts->band));
+
+ if (l1if_dsp_ver(fl1h) < L1_VER_SHIFT(5,3,3))
+ fl1h->rtp_hr_jumble_needed = true;
+
+ /* Request the activation */
+ l1if_activate_rf(fl1h, 1);
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0)
+ /* load calibration tables (if we know their path) */
+ int rc = calib_load(fl1h);
+ if (rc < 0)
+ LOGP(DL1C, LOGL_ERROR, "Operating without calibration; "
+ "unable to load tables!\n");
+#else
+ LOGP(DL1C, LOGL_NOTICE, "Operating without calibration "
+ "as software was compiled against old header files\n");
+#endif
+
+ msgb_free(resp);
+
+ /* FIXME: clock related */
+ return 0;
+}
+
+/* request DSP+FPGA code versions + band capability */
+static int l1if_get_info(struct femtol1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ sysp->id = SuperFemto_PrimId_SystemInfoReq;
+
+ return l1if_req_compl(hdl, msg, info_compl_cb, NULL);
+}
+
+static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status = sysp->u.layer1ResetCnf.status;
+
+ LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n",
+ get_value_string(femtobts_l1status_names, status));
+
+ msgb_free(resp);
+
+ /* If we're coming out of reset .. */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n",
+ get_value_string(femtobts_l1status_names, status));
+ bts_shutdown(trx->bts, "L1-RESET failure");
+ }
+
+ /* as we cannot get the current DSP trace flags, we simply
+ * set them to zero (or whatever dsp_trace_f has been initialized to */
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f);
+
+ /* obtain version information on DSP/FPGA and band capabilities */
+ l1if_get_info(fl1h);
+
+ return 0;
+}
+
+int l1if_reset(struct femtol1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ sysp->id = SuperFemto_PrimId_Layer1ResetReq;
+
+ return l1if_req_compl(hdl, msg, reset_compl_cb, NULL);
+}
+
+/* set the trace flags within the DSP */
+int l1if_set_trace_flags(struct femtol1_hdl *hdl, uint32_t flags)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n",
+ flags);
+
+ sysp->id = SuperFemto_PrimId_SetTraceFlagsReq;
+ sysp->u.setTraceFlagsReq.u32Tf = flags;
+
+ hdl->dsp_trace_f = flags;
+
+ /* There is no confirmation we could wait for */
+ if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n");
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/* get those femtol1_hdl.hw_info elements that sre in EEPROM */
+static int get_hwinfo_eeprom(struct femtol1_hdl *fl1h)
+{
+ eeprom_SysInfo_t sysinfo;
+ int val, rc;
+
+ rc = sysmobts_get_type(&val);
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.model_nr = val;
+
+ rc = sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_FLAGS, &val);
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.model_flags = val;
+
+ rc = sysmobts_get_trx(&val);
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.trx_nr = val;
+
+ rc = eeprom_ReadSysInfo(&sysinfo);
+ if (rc != EEPROM_SUCCESS) {
+ /* some early units don't yet have the EEPROM
+ * information structure */
+ LOGP(DL1C, LOGL_ERROR, "Unable to read band support "
+ "from EEPROM, assuming all bands\n");
+ fl1h->hw_info.band_support = GSM_BAND_850 | GSM_BAND_900 | GSM_BAND_1800 | GSM_BAND_1900;
+ return 0;
+ }
+
+ if (sysinfo.u8GSM850)
+ fl1h->hw_info.band_support |= GSM_BAND_850;
+ if (sysinfo.u8GSM900)
+ fl1h->hw_info.band_support |= GSM_BAND_900;
+ if (sysinfo.u8DCS1800)
+ fl1h->hw_info.band_support |= GSM_BAND_1800;
+ if (sysinfo.u8PCS1900)
+ fl1h->hw_info.band_support |= GSM_BAND_1900;
+
+ return 0;
+}
+
+/* Set the clock calibration to the value read from the eeprom. */
+static void clk_cal_use_eeprom(struct femtol1_hdl *hdl)
+{
+ struct phy_instance *pinst = hdl->phy_inst;
+ eeprom_RfClockCal_t rf_clk;
+ int rc;
+
+ if (!pinst->u.sysmobts.clk_use_eeprom)
+ return;
+
+ rc = eeprom_ReadRfClockCal(&rf_clk);
+ if (rc != EEPROM_SUCCESS) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to read from EEPROM.\n");
+ return;
+ }
+
+ hdl->clk_cal = rf_clk.iClkCor;
+ LOGP(DL1C, LOGL_NOTICE,
+ "Read clock calibration(%d) from EEPROM.\n", hdl->clk_cal);
+}
+
+struct femtol1_hdl *l1if_open(struct phy_instance *pinst)
+{
+ struct femtol1_hdl *fl1h;
+ int rc;
+
+#ifndef HW_SYSMOBTS_V1
+ LOGP(DL1C, LOGL_INFO, "sysmoBTSv2 L1IF compiled against API headers "
+ "v%u.%u.%u\n", SUPERFEMTO_API_VERSION >> 16,
+ (SUPERFEMTO_API_VERSION >> 8) & 0xff,
+ SUPERFEMTO_API_VERSION & 0xff);
+#else
+ LOGP(DL1C, LOGL_INFO, "sysmoBTSv1 L1IF compiled against API headers "
+ "v%u.%u.%u\n", FEMTOBTS_API_VERSION >> 16,
+ (FEMTOBTS_API_VERSION >> 8) & 0xff,
+ FEMTOBTS_API_VERSION & 0xff);
+#endif
+
+ fl1h = talloc_zero(pinst, struct femtol1_hdl);
+ if (!fl1h)
+ return NULL;
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+
+ fl1h->phy_inst = pinst;
+ fl1h->dsp_trace_f = pinst->u.sysmobts.dsp_trace_f;
+ fl1h->clk_src = pinst->u.sysmobts.clk_src;
+ fl1h->clk_cal = pinst->u.sysmobts.clk_cal;
+ clk_cal_use_eeprom(fl1h);
+ get_hwinfo_eeprom(fl1h);
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0)
+ if (fl1h->clk_src == SuperFemto_ClkSrcId_None) {
+ if (fl1h->hw_info.model_nr == 2050) {
+ /* On the sysmoBTS 2050, we don't have an OCXO but
+ * start with the TCXO and will sync it with the PPS
+ * of the GPS in case there is a fix. */
+ fl1h->clk_src = SuperFemto_ClkSrcId_Tcxo;
+ LOGP(DL1C, LOGL_INFO, "Clock source defaulting to GPS 1PPS "
+ "on sysmoBTS 2050\n");
+ } else {
+ /* default clock source: OCXO */
+ fl1h->clk_src = SuperFemto_ClkSrcId_Ocxo;
+ }
+ }
+#else
+ if (fl1h->clk_src == SF_CLKSRC_NONE)
+ fl1h->clk_src = SF_CLKSRC_OCXO;
+#endif
+
+ rc = l1if_transport_open(MQ_SYS_WRITE, fl1h);
+ if (rc < 0) {
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ rc = l1if_transport_open(MQ_L1_WRITE, fl1h);
+ if (rc < 0) {
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ l1if_reset(fl1h);
+
+ return fl1h;
+}
+
+int l1if_close(struct femtol1_hdl *fl1h)
+{
+ l1if_transport_close(MQ_L1_WRITE, fl1h);
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ return 0;
+}
+
+#ifdef HW_SYSMOBTS_V1
+int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h)
+{
+ LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n");
+ return -ENOTSUP;
+}
+
+int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h)
+{
+ LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n");
+ return -ENOTSUP;
+}
+
+#else
+static int clock_reset_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ msgb_free(resp);
+ return 0;
+}
+
+static int clock_setup_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+
+ if (sysp->u.rfClockSetupCnf.status != GsmL1_Status_Success)
+ LOGP(DL1C, LOGL_ERROR, "Rx RfClockSetupConf failed with: %d\n",
+ sysp->u.rfClockSetupCnf.status);
+ msgb_free(resp);
+ return 0;
+}
+
+static int clock_correct_info_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+
+ LOGP(DL1C, LOGL_NOTICE,
+ "RfClockInfo iClkCor=%d/clkSrc=%s Err=%d/ErrRes=%d/clkSrc=%s\n",
+ sysp->u.rfClockInfoCnf.rfTrx.iClkCor,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrx.clkSrc),
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc));
+
+ if (sysp->u.rfClockInfoCnf.rfTrx.clkSrc == SuperFemto_ClkSrcId_GpsPps) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Calibrating GPS against GPS doesn not make sense.\n");
+ msgb_free(resp);
+ return -1;
+ }
+
+ if (sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc == SuperFemto_ClkSrcId_None) {
+ LOGP(DL1C, LOGL_ERROR,
+ "No reference clock set. Please reset first.\n");
+ msgb_free(resp);
+ return -1;
+ }
+
+ if (sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes == 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Couldn't determine the clock difference.\n");
+ msgb_free(resp);
+ return -1;
+ }
+
+ fl1h->clk_cal = sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr;
+ fl1h->phy_inst->u.sysmobts.clk_use_eeprom = 0;
+ msgb_free(resp);
+
+ /*
+ * Let's reset the counter and this will lead to applying the
+ * new calibration.
+ */
+ l1if_rf_clock_info_reset(fl1h);
+
+ return 0;
+}
+
+int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ /* Set GPS/PPS as reference */
+ sysp->id = SuperFemto_PrimId_RfClockSetupReq;
+ sysp->u.rfClockSetupReq.rfTrx.iClkCor = get_clk_cal(fl1h);
+ sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src;
+ sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps;
+ l1if_req_compl(fl1h, msg, clock_setup_cb, NULL);
+
+ /* Reset the error counters */
+ msg = sysp_msgb_alloc();
+ sysp = msgb_sysprim(msg);
+
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 1;
+
+ return l1if_req_compl(fl1h, msg, clock_reset_cb, NULL);
+}
+
+int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 0;
+
+ return l1if_req_compl(fl1h, msg, clock_correct_info_cb, NULL);
+}
+
+#endif
+
+static void set_power_param(struct trx_power_params *out,
+ int trx_p_max_out_dBm,
+ int int_pa_nominal_gain_dB)
+{
+ out->trx_p_max_out_mdBm = to_mdB(trx_p_max_out_dBm);
+ out->pa.nominal_gain_mdB = to_mdB(int_pa_nominal_gain_dB);
+}
+
+static void fill_trx_power_params(struct gsm_bts_trx *trx,
+ struct phy_instance *pinst)
+{
+ struct femtol1_hdl *fl1h = pinst->u.sysmobts.hdl;
+
+ switch (fl1h->hw_info.model_nr) {
+ case 1020:
+ set_power_param(&trx->power_params, 23, 10);
+ break;
+ case 1100:
+ set_power_param(&trx->power_params, 23, 17);
+ break;
+ case 2050:
+ set_power_param(&trx->power_params, 37, 0);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "Unknown/Unsupported "
+ "sysmoBTS Model Number %u\n",
+ fl1h->hw_info.model_nr);
+ /* fall-through */
+ case 0xffff:
+ /* sysmoBTS 1002 without any setting in EEPROM */
+ LOGP(DL1C, LOGL_NOTICE, "Assuming 1002 for sysmoBTS "
+ "Model number %u\n", fl1h->hw_info.model_nr);
+ case 1002:
+ set_power_param(&trx->power_params, 23, 0);
+ }
+}
+
+
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ struct phy_instance *pinst = phy_instance_by_num(plink, 0);
+ struct femtol1_hdl *hdl;
+ struct gsm_bts *bts;
+
+ OSMO_ASSERT(pinst);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+ pinst->u.sysmobts.hdl = l1if_open(pinst);
+ if (!pinst->u.sysmobts.hdl) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n");
+ return -EIO;
+ }
+
+ fill_trx_power_params(pinst->trx, pinst);
+
+ bts = pinst->trx->bts;
+ if (pinst->trx == bts->c0) {
+ int rc;
+ rc = get_p_max_out_mdBm(bts->c0);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Cannot determine nominal "
+ "transmit power. Assuming 23dBm.\n");
+ }
+ bts->c0->nominal_power = rc;
+ }
+
+ hdl = pinst->u.sysmobts.hdl;
+ osmo_strlcpy(bts->sub_model, sysmobts_model(hdl->hw_info.model_nr, hdl->hw_info.trx_nr), sizeof(bts->sub_model));
+ snprintf(pinst->version, sizeof(pinst->version), "%u.%u dsp %u.%u.%u fpga %u.%u.%u",
+ hdl->hw_info.ver_major, hdl->hw_info.ver_minor,
+ hdl->hw_info.dsp_version[0], hdl->hw_info.dsp_version[1], hdl->hw_info.dsp_version[2],
+ hdl->hw_info.fpga_version[0], hdl->hw_info.fpga_version[1], hdl->hw_info.fpga_version[2]);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTED);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/l1_if.h b/src/osmo-bts-sysmo/l1_if.h
new file mode 100644
index 00000000..1b214be7
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_if.h
@@ -0,0 +1,171 @@
+#ifndef _FEMTO_L1_H
+#define _FEMTO_L1_H
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/phy_link.h>
+
+#include <sysmocom/femtobts/gsml1prim.h>
+
+#include <stdbool.h>
+
+enum {
+ MQ_SYS_READ,
+ MQ_L1_READ,
+#ifndef HW_SYSMOBTS_V1
+ MQ_TCH_READ,
+ MQ_PDTCH_READ,
+#endif
+ _NUM_MQ_READ
+};
+
+enum {
+ MQ_SYS_WRITE,
+ MQ_L1_WRITE,
+#ifndef HW_SYSMOBTS_V1
+ MQ_TCH_WRITE,
+ MQ_PDTCH_WRITE,
+#endif
+ _NUM_MQ_WRITE
+};
+
+struct calib_send_state {
+ const char *path;
+ int last_file_idx;
+};
+
+enum {
+ FIXUP_UNITILIAZED,
+ FIXUP_NEEDED,
+ FIXUP_NOT_NEEDED,
+};
+
+struct femtol1_hdl {
+ struct gsm_time gsm_time;
+ uint32_t hLayer1; /* handle to the L1 instance in the DSP */
+ uint32_t dsp_trace_f; /* currently operational DSP trace flags */
+ int clk_cal;
+ uint8_t clk_src;
+ struct llist_head wlc_list;
+
+ struct phy_instance *phy_inst; /* Reference to PHY instance */
+
+ struct osmo_timer_list alive_timer;
+ unsigned int alive_prim_cnt;
+
+ struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */
+ struct osmo_wqueue write_q[_NUM_MQ_WRITE];
+
+ struct {
+ /* from DSP/FPGA after L1 Init */
+ uint8_t dsp_version[3];
+ uint8_t fpga_version[3];
+ uint32_t band_support; /* bitmask of GSM_BAND_* */
+ uint8_t ver_major;
+ uint8_t ver_minor;
+ /* from EEPROM */
+ uint16_t model_nr;
+ uint16_t model_flags;
+ uint8_t trx_nr;
+ } hw_info;
+
+ int fixup_needed;
+ bool rtp_hr_jumble_needed;
+
+ struct calib_send_state st;
+
+ uint8_t last_rf_mute[8];
+
+ /* for l1_fwd */
+ void *priv;
+};
+
+
+#define L1_VER_SHIFT(x,y,z) ((x << 16) | (y << 8) | (z))
+
+static inline uint32_t l1if_dsp_ver(struct femtol1_hdl *fl1h)
+{
+ const uint8_t *v = fl1h->hw_info.dsp_version;
+ return L1_VER_SHIFT(v[0], v[1], v[2]);
+}
+
+static inline uint32_t l1if_fpga_ver(struct femtol1_hdl *fl1h)
+{
+ const uint8_t *v = fl1h->hw_info.fpga_version;
+ return L1_VER_SHIFT(v[0], v[1], v[2]);
+}
+
+#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
+#define msgb_sysprim(msg) ((SuperFemto_Prim_t *)(msg)->l1h)
+
+typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data);
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+int l1if_gsm_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+
+struct femtol1_hdl *l1if_open(struct phy_instance *pinst);
+int l1if_close(struct femtol1_hdl *hdl);
+int l1if_reset(struct femtol1_hdl *hdl);
+int l1if_activate_rf(struct femtol1_hdl *hdl, int on);
+int l1if_set_trace_flags(struct femtol1_hdl *hdl, uint32_t flags);
+int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power);
+int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb);
+
+struct msgb *l1p_msgb_alloc(void);
+struct msgb *sysp_msgb_alloc(void);
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan);
+struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer);
+
+/* tch.c */
+int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+ bool use_cache, bool marker);
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg);
+int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn);
+
+/* ciphering */
+int l1if_set_ciphering(struct femtol1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink);
+
+/* channel control */
+int l1if_rsl_chan_act(struct gsm_lchan *lchan);
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan);
+int l1if_rsl_chan_mod(struct gsm_lchan *lchan);
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan);
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan);
+
+/* calibration loading */
+int calib_load(struct femtol1_hdl *fl1h);
+int get_clk_cal(struct femtol1_hdl *hdl);
+
+/* on-line re-calibration */
+int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h);
+int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h);
+
+/* public helpers for test */
+int bts_check_for_ciph_cmd(struct femtol1_hdl *fl1h,
+ struct msgb *msg, struct gsm_lchan *lchan);
+
+static inline struct femtol1_hdl *trx_femtol1_hdl(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ OSMO_ASSERT(pinst);
+ return pinst->u.sysmobts.hdl;
+}
+
+static inline struct gsm_bts_trx *femtol1_hdl_trx(struct femtol1_hdl *fl1h)
+{
+ OSMO_ASSERT(fl1h->phy_inst);
+ return fl1h->phy_inst->trx;
+}
+#endif /* _FEMTO_L1_H */
diff --git a/src/osmo-bts-sysmo/l1_transp.h b/src/osmo-bts-sysmo/l1_transp.h
new file mode 100644
index 00000000..b2967f93
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_transp.h
@@ -0,0 +1,14 @@
+#ifndef _FEMTOL1_TRANSPH_
+#define _FEMTOL1_TRANSPH_
+
+#include <osmocom/core/msgb.h>
+
+/* functions a transport calls on arrival of primitive from BTS */
+int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg);
+int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg);
+
+/* functions exported by a transport */
+int l1if_transport_open(int q, struct femtol1_hdl *fl1h);
+int l1if_transport_close(int q, struct femtol1_hdl *fl1h);
+
+#endif /* _FEMTOL1_TRANSP_H */
diff --git a/src/osmo-bts-sysmo/l1_transp_fwd.c b/src/osmo-bts-sysmo/l1_transp_fwd.c
new file mode 100644
index 00000000..87c230bb
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_transp_fwd.c
@@ -0,0 +1,152 @@
+/* Interface handler for Sysmocom L1 (forwarding) */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "l1_fwd.h"
+
+static const uint16_t fwd_udp_ports[] = {
+ [MQ_SYS_WRITE] = L1FWD_SYS_PORT,
+ [MQ_L1_WRITE] = L1FWD_L1_PORT,
+#ifndef HW_SYSMOBTS_V1
+ [MQ_TCH_WRITE] = L1FWD_TCH_PORT,
+ [MQ_PDTCH_WRITE]= L1FWD_PDTCH_PORT,
+#endif
+};
+
+static int fwd_read_cb(struct osmo_fd *ofd)
+{
+ struct msgb *msg = msgb_alloc_headroom(SYSMOBTS_PRIM_SIZE, 128, "udp_rx");
+ struct femtol1_hdl *fl1h = ofd->data;
+ int rc;
+
+ if (!msg)
+ return -ENOMEM;
+
+ msg->l1h = msg->data;
+ rc = read(ofd->fd, msg->l1h, msgb_tailroom(msg));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Short read from UDP\n");
+ msgb_free(msg);
+ return rc;
+ } else if (rc == 0) {
+ LOGP(DL1C, LOGL_ERROR, "Len=0 from UDP\n");
+ msgb_free(msg);
+ return rc;
+ }
+ msgb_put(msg, rc);
+
+ if (ofd->priv_nr == MQ_SYS_WRITE)
+ rc = l1if_handle_sysprim(fl1h, msg);
+ else
+ rc = l1if_handle_l1prim(ofd->priv_nr, fl1h, msg);
+
+ return rc;
+}
+
+static int prim_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ /* write to the fd */
+ return write(ofd->fd, msg->l1h, msgb_l1len(msg));
+}
+
+int l1if_transport_open(int q, struct femtol1_hdl *fl1h)
+{
+ int rc;
+ char *bts_host = getenv("L1FWD_BTS_HOST");
+
+ switch (q) {
+ case MQ_L1_WRITE:
+ LOGP(DL1C, LOGL_INFO, "sizeof(GsmL1_Prim_t) = %zu\n",
+ sizeof(GsmL1_Prim_t));
+ break;
+ case MQ_SYS_WRITE:
+ LOGP(DL1C, LOGL_INFO, "sizeof(SuperFemto_Prim_t) = %zu\n",
+ sizeof(SuperFemto_Prim_t));
+ break;
+ }
+
+ if (!bts_host) {
+ fprintf(stderr, "You have to set the L1FWD_BTS_HOST environment variable\n");
+ exit(2);
+ }
+
+ struct osmo_wqueue *wq = &fl1h->write_q[q];
+ struct osmo_fd *ofd = &wq->bfd;
+
+ osmo_wqueue_init(wq, 10);
+ wq->write_cb = prim_write_cb;
+ wq->read_cb = fwd_read_cb;
+
+ ofd->data = fl1h;
+ ofd->priv_nr = q;
+ ofd->when |= BSC_FD_READ;
+
+ rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP,
+ bts_host, fwd_udp_ports[q],
+ OSMO_SOCK_F_CONNECT);
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+int l1if_transport_close(int q, struct femtol1_hdl *fl1h)
+{
+ struct osmo_wqueue *wq = &fl1h->write_q[q];
+ struct osmo_fd *ofd = &wq->bfd;
+
+ osmo_wqueue_clear(wq);
+ osmo_fd_unregister(ofd);
+ close(ofd->fd);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/l1_transp_hw.c b/src/osmo-bts-sysmo/l1_transp_hw.c
new file mode 100644
index 00000000..01bc2005
--- /dev/null
+++ b/src/osmo-bts-sysmo/l1_transp_hw.c
@@ -0,0 +1,329 @@
+/* Interface handler for Sysmocom L1 (real hardware) */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+
+
+#ifdef HW_SYSMOBTS_V1
+#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/femtobts_dsp2arm"
+#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/femtobts_arm2dsp"
+#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_dsp2arm"
+#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_arm2dsp"
+#else
+#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/superfemto_dsp2arm"
+#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/superfemto_arm2dsp"
+#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm"
+#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp"
+
+#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm"
+#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp"
+#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm"
+#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp"
+#endif
+
+static const char *rd_devnames[] = {
+ [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME,
+ [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME,
+#ifndef HW_SYSMOBTS_V1
+ [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME,
+ [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME,
+#endif
+};
+
+static const char *wr_devnames[] = {
+ [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME,
+ [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME,
+#ifndef HW_SYSMOBTS_V1
+ [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME,
+ [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME,
+#endif
+};
+
+/*
+ * Make sure that all structs we read fit into the SYSMOBTS_PRIM_SIZE
+ */
+osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= SYSMOBTS_PRIM_SIZE, l1_prim)
+osmo_static_assert(sizeof(SuperFemto_Prim_t) + 128 <= SYSMOBTS_PRIM_SIZE, super_prim)
+
+static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
+{
+ struct osmo_wqueue *queue;
+
+ queue = container_of(fd, struct osmo_wqueue, bfd);
+
+ if (what & BSC_FD_READ)
+ queue->read_cb(fd);
+
+ if (what & BSC_FD_EXCEPT)
+ queue->except_cb(fd);
+
+ if (what & BSC_FD_WRITE) {
+ struct iovec iov[5];
+ struct msgb *msg, *tmp;
+ int written, count = 0;
+
+ fd->when &= ~BSC_FD_WRITE;
+
+ llist_for_each_entry(msg, &queue->msg_queue, list) {
+ /* more writes than we have */
+ if (count >= ARRAY_SIZE(iov))
+ break;
+
+ iov[count].iov_base = msg->l1h;
+ iov[count].iov_len = msgb_l1len(msg);
+ count += 1;
+ }
+
+ /* TODO: check if all lengths are the same. */
+
+
+ /* Nothing scheduled? This should not happen. */
+ if (count == 0) {
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ written = writev(fd->fd, iov, count);
+ if (written < 0) {
+ /* nothing written?! */
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ /* now delete the written entries */
+ written = written / iov[0].iov_len;
+ count = 0;
+ llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) {
+ queue->current_length -= 1;
+
+ llist_del(&msg->list);
+ msgb_free(msg);
+
+ count += 1;
+ if (count >= written)
+ break;
+ }
+
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ }
+
+ return 0;
+}
+
+static int prim_size_for_queue(int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return sizeof(SuperFemto_Prim_t);
+ case MQ_L1_WRITE:
+#ifndef HW_SYSMOBTS_V1
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+#endif
+ return sizeof(GsmL1_Prim_t);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+}
+
+/* callback when there's something to read from the l1 msg_queue */
+static int read_dispatch_one(struct femtol1_hdl *fl1h, struct msgb *msg, int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return l1if_handle_sysprim(fl1h, msg);
+ case MQ_L1_WRITE:
+#ifndef HW_SYSMOBTS_V1
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+#endif
+ return l1if_handle_l1prim(queue, fl1h, msg);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+};
+
+static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int i, rc;
+
+ const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr);
+ uint32_t count;
+
+ struct iovec iov[3];
+ struct msgb *msg[ARRAY_SIZE(iov)];
+
+ for (i = 0; i < ARRAY_SIZE(iov); ++i) {
+ msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd");
+ msg[i]->l1h = msg[i]->data;
+
+ iov[i].iov_base = msg[i]->l1h;
+ iov[i].iov_len = msgb_tailroom(msg[i]);
+ }
+
+ rc = readv(ofd->fd, iov, ARRAY_SIZE(iov));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno));
+ /* N. B: we do not abort to let the cycle below cleanup allocated memory properly,
+ the return value is ignored by the caller anyway.
+ TODO: use libexplain's explain_readv() to provide detailed error description */
+ count = 0;
+ } else
+ count = rc / prim_size;
+
+ for (i = 0; i < count; ++i) {
+ msgb_put(msg[i], prim_size);
+ read_dispatch_one(ofd->data, msg[i], ofd->priv_nr);
+ }
+
+ for (i = count; i < ARRAY_SIZE(iov); ++i)
+ msgb_free(msg[i]);
+
+ return 1;
+}
+
+/* callback when we can write to one of the l1 msg_queue devices */
+static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(ofd->fd, msg->l1h, msgb_l1len(msg));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
+ strerror(errno));
+ return rc;
+ } else if (rc < msg->len) {
+ LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: "
+ "%u < %u\n", rc, msg->len);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int l1if_transport_open(int q, struct femtol1_hdl *hdl)
+{
+ int rc;
+
+ /* Step 1: Open all msg_queue file descriptors */
+ struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+ struct osmo_wqueue *wq = &hdl->write_q[q];
+ struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+ rc = open(rd_devnames[q], O_RDONLY);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "[%d] unable to open %s for reading: %s\n",
+ q, rd_devnames[q], strerror(errno));
+ return rc;
+ }
+ read_ofd->fd = rc;
+ read_ofd->priv_nr = q;
+ read_ofd->data = hdl;
+ read_ofd->cb = l1if_fd_cb;
+ read_ofd->when = BSC_FD_READ;
+ rc = osmo_fd_register(read_ofd);
+ if (rc < 0) {
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+ return rc;
+ }
+
+ rc = open(wr_devnames[q], O_WRONLY);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "[%d] unable to open %s for writing: %s\n",
+ q, wr_devnames[q], strerror(errno));
+ goto out_read;
+ }
+ osmo_wqueue_init(wq, 10);
+ wq->write_cb = l1fd_write_cb;
+ write_ofd->cb = wqueue_vector_cb;
+ write_ofd->fd = rc;
+ write_ofd->priv_nr = q;
+ write_ofd->data = hdl;
+ write_ofd->when = BSC_FD_WRITE;
+ rc = osmo_fd_register(write_ofd);
+ if (rc < 0) {
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+ goto out_read;
+ }
+
+ return 0;
+
+out_read:
+ close(hdl->read_ofd[q].fd);
+ osmo_fd_unregister(&hdl->read_ofd[q]);
+
+ return rc;
+}
+
+int l1if_transport_close(int q, struct femtol1_hdl *hdl)
+{
+ struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+ struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+ osmo_fd_unregister(read_ofd);
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+
+ osmo_fd_unregister(write_ofd);
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/main.c b/src/osmo-bts-sysmo/main.c
new file mode 100644
index 00000000..221e8e8a
--- /dev/null
+++ b/src/osmo-bts-sysmo/main.c
@@ -0,0 +1,199 @@
+/* Main program for Sysmocom BTS */
+
+/* (C) 2011-2015 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/l1sap.h>
+
+#define SYSMOBTS_RF_LOCK_PATH "/var/lock/bts_rf_lock"
+
+#include "utils.h"
+#include "eeprom.h"
+#include "l1_if.h"
+#include "hw_misc.h"
+#include "oml_router.h"
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ struct stat st;
+ static struct osmo_fd accept_fd, read_fd;
+ int rc;
+
+ bts->variant = BTS_OSMO_SYSMO;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+ rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd);
+ if (rc < 0) {
+ fprintf(stderr, "Error creating the OML router: %s rc=%d\n",
+ OML_ROUTER_PATH, rc);
+ exit(1);
+ }
+
+ if (stat(SYSMOBTS_RF_LOCK_PATH, &st) == 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n");
+ exit(23);
+ }
+
+ gsm_bts_set_feature(bts, BTS_FEAT_GPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_EGPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
+ gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ return 0;
+}
+
+void bts_update_status(enum bts_global_status which, int on)
+{
+ static uint64_t states = 0;
+ uint64_t old_states = states;
+ int led_rf_active_on;
+
+ if (on)
+ states |= (1ULL << which);
+ else
+ states &= ~(1ULL << which);
+
+ led_rf_active_on =
+ (states & (1ULL << BTS_STATUS_RF_ACTIVE)) &&
+ !(states & (1ULL << BTS_STATUS_RF_MUTE));
+
+ LOGP(DL1C, LOGL_INFO,
+ "Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n",
+ which, on,
+ (long long)old_states, (long long)states,
+ led_rf_active_on);
+
+ sysmobts_led_set(LED_RF_ACTIVE, led_rf_active_on);
+}
+
+void bts_model_print_help()
+{
+ printf(
+ " -w --hw-version Print the targeted HW Version\n"
+ " -M --pcu-direct Force PCU to access message queue for "
+ "PDCH dchannel directly\n"
+ );
+};
+
+static void print_hwversion()
+{
+#ifdef HW_SYSMOBTS_V1
+ printf("sysmobts was compiled for hw version 1.\n");
+#else
+ printf("sysmobts was compiled for hw version 2.\n");
+#endif
+}
+
+int bts_model_handle_options(int argc, char **argv)
+{
+ int num_errors = 0;
+
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ /* specific to this hardware */
+ { "hw-version", 0, 0, 'w' },
+ { "pcu-direct", 0, 0, 'M' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "wM",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'M':
+ pcu_direct = 1;
+ break;
+ case 'w':
+ print_hwversion();
+ exit(0);
+ break;
+ default:
+ num_errors++;
+ break;
+ }
+ }
+
+ return num_errors;
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+ pinst->u.sysmobts.clk_use_eeprom = 1;
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ /* for now, we simply terminate the program and re-spawn */
+ bts_shutdown(bts, "Abis close");
+}
+
+int main(int argc, char **argv)
+{
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts-calib.c b/src/osmo-bts-sysmo/misc/sysmobts-calib.c
new file mode 100644
index 00000000..a111d1d5
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts-calib.c
@@ -0,0 +1,537 @@
+/* OCXO/TCXO based calibration utility */
+
+/*
+ * (C) 2012-2013 Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <math.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmocom/core/utils.h>
+
+#include "sysmobts-layer1.h"
+
+enum actions {
+ ACTION_SCAN,
+ ACTION_CALIB,
+ ACTION_BCCH,
+ ACTION_BCCH_CCCH,
+};
+
+static const char *modes[] = {
+ [ACTION_SCAN] = "scan",
+ [ACTION_CALIB] = "calibrate",
+ [ACTION_BCCH] = "bcch",
+ [ACTION_BCCH_CCCH] = "bcch_ccch",
+};
+
+static const char *bands[] = {
+ [GsmL1_FreqBand_850] = "850",
+ [GsmL1_FreqBand_900] = "900",
+ [GsmL1_FreqBand_1800] = "1800",
+ [GsmL1_FreqBand_1900] = "1900",
+};
+
+struct channel_pair {
+ int min;
+ int max;
+};
+
+static const struct channel_pair arfcns[] = {
+ [GsmL1_FreqBand_850] = { .min = 128, .max = 251 },
+ [GsmL1_FreqBand_900] = { .min = 1, .max = 124 },
+ [GsmL1_FreqBand_1800] = { .min = 512, .max = 885 },
+ [GsmL1_FreqBand_1900] = { .min = 512, .max = 810 },
+
+};
+
+static const char *clk_source[] = {
+ [SuperFemto_ClkSrcId_Ocxo] = "ocxo",
+ [SuperFemto_ClkSrcId_Tcxo] = "tcxo",
+ [SuperFemto_ClkSrcId_External] = "external",
+ [SuperFemto_ClkSrcId_GpsPps] = "gps",
+ [SuperFemto_ClkSrcId_Trx] = "trx",
+ [SuperFemto_ClkSrcId_Rx] = "rx",
+ [SuperFemto_ClkSrcId_Edge] = "edge",
+ [SuperFemto_ClkSrcId_NetList] = "netlisten",
+};
+
+static const struct value_string sapi_names[GsmL1_Sapi_NUM+1] = {
+ { GsmL1_Sapi_Fcch, "FCCH" },
+ { GsmL1_Sapi_Sch, "SCH" },
+ { GsmL1_Sapi_Sacch, "SACCH" },
+ { GsmL1_Sapi_Sdcch, "SDCCH" },
+ { GsmL1_Sapi_Bcch, "BCCH" },
+ { GsmL1_Sapi_Pch, "PCH" },
+ { GsmL1_Sapi_Agch, "AGCH" },
+ { GsmL1_Sapi_Cbch, "CBCH" },
+ { GsmL1_Sapi_Rach, "RACH" },
+ { GsmL1_Sapi_TchF, "TCH/F" },
+ { GsmL1_Sapi_FacchF, "FACCH/F" },
+ { GsmL1_Sapi_TchH, "TCH/H" },
+ { GsmL1_Sapi_FacchH, "FACCH/H" },
+ { GsmL1_Sapi_Nch, "NCH" },
+ { GsmL1_Sapi_Pdtch, "PDTCH" },
+ { GsmL1_Sapi_Pacch, "PACCH" },
+ { GsmL1_Sapi_Pbcch, "PBCCH" },
+ { GsmL1_Sapi_Pagch, "PAGCH" },
+ { GsmL1_Sapi_Ppch, "PPCH" },
+ { GsmL1_Sapi_Pnch, "PNCH" },
+ { GsmL1_Sapi_Ptcch, "PTCCH" },
+ { GsmL1_Sapi_Prach, "PRACH" },
+ { 0, NULL }
+};
+
+static int action = ACTION_SCAN;
+static int band = GsmL1_FreqBand_900;
+static int calib = SuperFemto_ClkSrcId_Ocxo;
+static int source = SuperFemto_ClkSrcId_NetList;
+static int dsp_flags = 0x0;
+static int cal_arfcn = 0;
+static int initial_cor = 0;
+static int steps = -1;
+
+static void print_usage(void)
+{
+ printf("Usage: sysmobts-calib ARGS\n");
+}
+
+static void print_help(void)
+{
+ printf(" -h --help this text\n");
+ printf(" -c --clock "
+ "ocxo|tcxo|external|gps|trx|rx|edge\n");
+ printf(" -s --calibration-source "
+ "ocxo|tcxo|external|gps|trx|rx|edge|netlisten\n");
+ printf(" -b --band 850|900|1800|1900\n");
+ printf(" -m --mode scan|calibrate|bcch|bcch_ccch\n");
+ printf(" -a --arfcn NR arfcn for calibration\n");
+ printf(" -d --dsp-flags NR dsp mask for debug log\n");
+ printf(" -t --threshold level\n");
+ printf(" -i --initial-clock-correction COR.\n");
+ printf(" -t --steps STEPS\n");
+}
+
+static int find_value(const char **array, int size, char *value)
+{
+ int i = 0;
+ for (i = 0; i < size; ++i) {
+ if (array[i] == NULL)
+ continue;
+ if (strcmp(value, array[i]) == 0)
+ return i;
+ }
+
+ printf("Failed to find: '%s'\n", value);
+ exit(-2);
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"calibration-source", 1, 0, 's'},
+ {"clock", 1, 0, 'c'},
+ {"mode", 1, 0, 'm'},
+ {"band", 1, 0, 'b'},
+ {"dsp-flags", 1, 0, 'd'},
+ {"arfcn", 1, 0, 'a'},
+ {"initial-clock-correction", 1, 0, 'i'},
+ {"steps", 1, 0, 't'},
+ {0, 0, 0, 0},
+ };
+
+ c = getopt_long(argc, argv, "hs:c:m:b:d:a:i:t:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'h':
+ print_usage();
+ print_help();
+ exit(0);
+ case 's':
+ source = find_value(clk_source,
+ ARRAY_SIZE(clk_source), optarg);
+ break;
+ case 'c':
+ calib = find_value(clk_source,
+ ARRAY_SIZE(clk_source), optarg);
+ break;
+ case 'm':
+ action = find_value(modes,
+ ARRAY_SIZE(modes), optarg);
+ break;
+ case 'b':
+ band = find_value(bands,
+ ARRAY_SIZE(bands), optarg);
+ break;
+ case 'd':
+ dsp_flags = strtol(optarg, NULL, 16);
+ break;
+ case 'a':
+ cal_arfcn = atoi(optarg);
+ break;
+ case 'i':
+ initial_cor = atoi(optarg);
+ break;
+ case 't':
+ steps = atoi(optarg);
+ break;
+ default:
+ printf("Unhandled option, terminating.\n");
+ exit(-1);
+ }
+ }
+
+ if (source == calib) {
+ printf("Clock source and reference clock may not be the same.\n");
+ exit(-3);
+ }
+
+ if (calib == SuperFemto_ClkSrcId_NetList) {
+ printf("Clock may not be network listen.\n");
+ exit(-4);
+ }
+
+ if (action == ACTION_CALIB && source == SuperFemto_ClkSrcId_NetList) {
+ if (cal_arfcn == 0) {
+ printf("Please specify the reference ARFCN.\n");
+ exit(-5);
+ }
+
+ if (cal_arfcn < arfcns[band].min || cal_arfcn > arfcns[band].max) {
+ printf("ARFCN(%d) is not in the given band.\n", cal_arfcn);
+ exit(-6);
+ }
+ }
+}
+
+#define CHECK_RC(rc) \
+ if (rc != 0) \
+ return EXIT_FAILURE;
+
+#define CHECK_RC_MSG(rc, msg) \
+ if (rc != 0) { \
+ printf("%s: %d\n", msg, rc); \
+ return EXIT_FAILURE; \
+ }
+#define CHECK_COND_MSG(cond, rc, msg) \
+ if (cond) { \
+ printf("%s: %d\n", msg, rc); \
+ return EXIT_FAILURE; \
+ }
+
+struct scan_result
+{
+ uint16_t arfcn;
+ float rssi;
+};
+
+static int scan_cmp(const void *arg1, const void *arg2)
+{
+ struct scan_result *elem1 = (struct scan_result *) arg1;
+ struct scan_result *elem2 = (struct scan_result * )arg2;
+
+ float diff = elem1->rssi - elem2->rssi;
+ if (diff > 0.0)
+ return 1;
+ else if (diff < 0.0)
+ return -1;
+ else
+ return 0;
+}
+
+static int scan_band()
+{
+ int arfcn, rc, i;
+
+ /* Scan results.. at most 400 items */
+ struct scan_result results[400];
+ memset(&results, 0, sizeof(results));
+ int num_scan_results = 0;
+
+ printf("Going to scan bands.\n");
+
+ for (arfcn = arfcns[band].min; arfcn <= arfcns[band].max; ++arfcn) {
+ float mean_rssi;
+
+ printf(".");
+ fflush(stdout);
+ rc = power_scan(band, arfcn, 10, &mean_rssi);
+ CHECK_RC_MSG(rc, "Power Measurement failed");
+
+ results[num_scan_results].arfcn = arfcn;
+ results[num_scan_results].rssi = mean_rssi;
+ num_scan_results++;
+ }
+
+ qsort(results, num_scan_results, sizeof(struct scan_result), scan_cmp);
+ printf("\nSorted scan results (weakest first):\n");
+ for (i = 0; i < num_scan_results; ++i)
+ printf("ARFCN %3d: %.4f\n", results[i].arfcn, results[i].rssi);
+
+ return 0;
+}
+
+static int calib_get_clock_error(void)
+{
+ int rc, clkErr, clkErrRes;
+
+ printf("Going to determine the clock offset.\n");
+
+ rc = rf_clock_info(&clkErr, &clkErrRes);
+ CHECK_RC_MSG(rc, "Clock info failed.\n");
+
+ if (clkErr == 0 && clkErrRes == 0) {
+ printf("Failed to get the clock info. Are both clocks present?\n");
+ return -1;
+ }
+
+ /*
+ * Empiric gps error determination. With revE and firmware v3.3
+ * the clock error for TCXO to GPS appears to have a different
+ * sign. The device in question doesn't have a networklisten mode
+ * so it is impossible to verify that this only applies to GPS.
+ */
+ if (source == SuperFemto_ClkSrcId_GpsPps)
+ clkErr *= -1;
+
+
+ /* this is an absolute clock error */
+ printf("The calibration value is: %d\n", clkErr);
+ return 0;
+}
+
+static int calib_clock_after_sync(void)
+{
+ int rc, clkErr, clkErrRes, iteration, cor;
+
+ iteration = 0;
+ cor = initial_cor;
+
+ printf("Trying to calibrate now and reducing clock error.\n");
+
+ for (iteration = 0; iteration < steps || steps <= 0; ++iteration) {
+ if (steps > 0)
+ printf("Iteration %d/%d with correction: %d\n", iteration, steps, cor);
+ else
+ printf("Iteration %d with correction: %d\n", iteration, cor);
+
+ rc = rf_clock_info(&clkErr, &clkErrRes);
+ CHECK_RC_MSG(rc, "Clock info failed.\n");
+
+ /*
+ * TODO: use the clock error resolution here, implement it as a
+ * a PID controller..
+ */
+
+ /* Picocell class requires 0.1ppm.. but that is 'too easy' */
+ if (fabs(clkErr / 1000.0f) <= 0.05f) {
+ printf("The calibration value is: %d\n", cor);
+ return 1;
+ }
+
+ cor -= clkErr / 2;
+ rc = set_clock_cor(cor, calib, source);
+ CHECK_RC_MSG(rc, "Clock correction failed.\n");
+ }
+
+ return -1;
+}
+
+static int find_initial_clock(HANDLE layer1, int *clock)
+{
+ int i;
+
+ printf("Trying to find an initial clock value.\n");
+
+ for (i = 0; i < 1000; ++i) {
+ int rc;
+ int cor = i * 150;
+ rc = wait_for_sync(layer1, cor, calib, source);
+ if (rc == 1) {
+ printf("Found initial clock offset: %d\n", cor);
+ *clock = cor;
+ break;
+ } else {
+ CHECK_RC_MSG(rc, "Failed to set new clock value.\n");
+ }
+
+ cor = i * -150;
+ rc = wait_for_sync(layer1, cor, calib, source);
+ if (rc == 1) {
+ printf("Found initial clock offset: %d\n", cor);
+ *clock = cor;
+ break;
+ } else {
+ CHECK_RC_MSG(rc, "Failed to set new clock value.\n");
+ }
+ }
+
+ return 0;
+}
+
+static int calib_clock_netlisten(void)
+{
+ int rc, cor = initial_cor;
+ float mean_rssi;
+ HANDLE layer1;
+
+ rc = power_scan(band, cal_arfcn, 10, &mean_rssi);
+ CHECK_RC_MSG(rc, "ARFCN measurement scan failed");
+ if (mean_rssi < -118.0f)
+ printf("ARFCN has weak signal for calibration: %f\n", mean_rssi);
+
+ /* initial lock */
+ rc = follow_sch(band, cal_arfcn, calib, source, &layer1);
+ if (rc == -23)
+ rc = find_initial_clock(layer1, &cor);
+ CHECK_RC_MSG(rc, "Following SCH failed");
+
+ /* now try to calibrate it */
+ rc = set_clock_cor(cor, calib, source);
+ CHECK_RC_MSG(rc, "Clock setup failed.");
+
+ calib_clock_after_sync();
+
+ rc = mph_close(layer1);
+ CHECK_RC_MSG(rc, "MPH-Close");
+
+ return EXIT_SUCCESS;
+}
+
+static int calib_clock(void)
+{
+ int rc;
+
+ /* now try to calibrate it */
+ rc = set_clock_cor(initial_cor, calib, source);
+ CHECK_RC_MSG(rc, "Clock setup failed.");
+
+ calib_get_clock_error();
+
+ return EXIT_SUCCESS;
+}
+
+static int bcch_follow(void)
+{
+ int rc, cor = initial_cor;
+ float mean_rssi;
+ HANDLE layer1;
+
+ rc = power_scan(band, cal_arfcn, 10, &mean_rssi);
+ CHECK_RC_MSG(rc, "ARFCN measurement scan failed");
+ if (mean_rssi < -118.0f)
+ printf("ARFCN has weak signal for calibration: %f\n", mean_rssi);
+
+ /* initial lock */
+ rc = follow_sch(band, cal_arfcn, calib, source, &layer1);
+ if (rc == -23)
+ rc = find_initial_clock(layer1, &cor);
+ CHECK_RC_MSG(rc, "Following SCH failed");
+
+ /* identify the BSIC and set it as TSC */
+ rc = find_bsic();
+ CHECK_COND_MSG(rc < 0, rc, "Identifying the BSIC failed");
+ rc = set_tsc_from_bsic(layer1, rc);
+ CHECK_RC_MSG(rc, "Setting the TSC failed");
+
+
+ /* follow the bcch */
+ rc = follow_bcch(layer1);
+ CHECK_RC_MSG(rc, "Follow BCCH");
+
+ /* follow the pch */
+ if (action == ACTION_BCCH_CCCH) {
+ rc = follow_pch(layer1);
+ CHECK_RC_MSG(rc, "Follow BCCH/CCCH");
+ }
+
+ /* now wait for the PhDataInd */
+ for (;;) {
+ uint32_t fn;
+ uint8_t block;
+ uint8_t data[23];
+ size_t size;
+ struct gsm_time gsmtime;
+ GsmL1_Sapi_t sapi;
+
+ rc = wait_for_data(data, &size, &fn, &block, &sapi);
+ if (rc == 1)
+ continue;
+ CHECK_RC_MSG(rc, "No Data Indication");
+
+ gsm_fn2gsmtime(&gsmtime, fn);
+ printf("%02u/%02u/%02u %6s %s\n",
+ gsmtime.t1, gsmtime.t2, gsmtime.t3,
+ get_value_string(sapi_names, sapi),
+ osmo_hexdump(data, size));
+ }
+
+ rc = mph_close(layer1);
+ CHECK_RC_MSG(rc, "MPH-Close");
+
+ return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ handle_options(argc, argv);
+ printf("Initializing the Layer1\n");
+ rc = initialize_layer1(dsp_flags);
+ CHECK_RC(rc);
+
+ printf("Fetching system info.\n");
+ rc = print_system_info();
+ CHECK_RC(rc);
+
+ printf("Opening RF frontend with clock(%d) and correction(%d)\n",
+ calib, initial_cor);
+ rc = activate_rf_frontend(calib, initial_cor);
+ CHECK_RC(rc);
+
+ if (action == ACTION_SCAN)
+ return scan_band();
+ else if (action == ACTION_BCCH || action == ACTION_BCCH_CCCH)
+ return bcch_follow();
+ else {
+ if (source == SuperFemto_ClkSrcId_NetList)
+ return calib_clock_netlisten();
+ return calib_clock();
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.c b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c
new file mode 100644
index 00000000..4b34f50e
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c
@@ -0,0 +1,800 @@
+/* Layer1 handling for the DSP/FPGA */
+/*
+ * (C) 2012-2013 Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+
+#include "sysmobts-layer1.h"
+
+#define ARRAY_SIZE(ar) (sizeof(ar)/sizeof((ar)[0]))
+
+#define BTS_DSP2ARM "/dev/msgq/superfemto_dsp2arm"
+#define BTS_ARM2DSP "/dev/msgq/superfemto_arm2dsp"
+#define L1_SIG_ARM2DSP "/dev/msgq/gsml1_sig_arm2dsp"
+#define L1_SIG_DSP2ARM "/dev/msgq/gsml1_sig_dsp2arm"
+
+int set_clock_cor(int clock_cor, int calib, int source);
+static int wait_read_ignore(int seconds);
+
+static int sys_dsp2arm = -1,
+ sys_arm2dsp = -1,
+ sig_dsp2arm = -1,
+ sig_arm2dsp = -1;
+
+static int sync_indicated = 0;
+static int time_indicated = 0;
+
+static int open_devices()
+{
+ sys_dsp2arm = open(BTS_DSP2ARM, O_RDONLY);
+ if (sys_dsp2arm == -1) {
+ perror("Failed to open dsp2arm system queue");
+ return -1;
+ }
+
+ sys_arm2dsp = open(BTS_ARM2DSP, O_WRONLY);
+ if (sys_arm2dsp == -1) {
+ perror("Failed to open arm2dsp system queue");
+ return -2;
+ }
+
+ sig_dsp2arm = open(L1_SIG_DSP2ARM, O_RDONLY);
+ if (sig_dsp2arm == -1) {
+ perror("Failed to open dsp2arm sig queue");
+ return -3;
+ }
+
+ sig_arm2dsp = open(L1_SIG_ARM2DSP, O_WRONLY);
+ if (sig_arm2dsp == -1) {
+ perror("Failed to open arm2dsp sig queue");
+ return -4;
+ }
+
+ return 0;
+}
+
+/**
+ * Send a primitive to the system queue
+ */
+static int send_primitive(int primitive, SuperFemto_Prim_t *prim)
+{
+ prim->id = primitive;
+ return write(sys_arm2dsp, prim, sizeof(*prim)) != sizeof(*prim);
+}
+
+/**
+ * Wait for a confirmation
+ */
+static int wait_primitive(int wait_for, SuperFemto_Prim_t *prim)
+{
+ memset(prim, 0, sizeof(*prim));
+ int rc = read(sys_dsp2arm, prim, sizeof(*prim));
+ if (rc != sizeof(*prim)) {
+ printf("Short read in %s: %d\n", __func__, rc);
+ return -1;
+ }
+
+ if (prim->id != wait_for) {
+ printf("Got primitive %d but waited for %d\n",
+ prim->id, wait_for);
+ return -2;
+ }
+
+ return 0;
+}
+
+/* The Cnf for the Req, assume it is a +1 */
+static int answer_for(int primitive)
+{
+ return primitive + 1;
+}
+
+static int send_recv_primitive(int p, SuperFemto_Prim_t *prim)
+{
+ int rc;
+ rc = send_primitive(p, prim);
+ if (rc != 0)
+ return -1;
+
+ rc = wait_primitive(answer_for(p), prim);
+ if (rc != 0)
+ return -2;
+ return 0;
+}
+
+static int answer_for_sig(int prim)
+{
+ static const GsmL1_PrimId_t cnf[] = {
+ [GsmL1_PrimId_MphInitReq] = GsmL1_PrimId_MphInitCnf,
+ [GsmL1_PrimId_MphCloseReq] = GsmL1_PrimId_MphCloseCnf,
+ [GsmL1_PrimId_MphConnectReq] = GsmL1_PrimId_MphConnectCnf,
+ [GsmL1_PrimId_MphActivateReq] = GsmL1_PrimId_MphActivateCnf,
+ [GsmL1_PrimId_MphConfigReq] = GsmL1_PrimId_MphConfigCnf,
+ [GsmL1_PrimId_MphMeasureReq] = GsmL1_PrimId_MphMeasureCnf,
+ };
+
+ if (prim < 0 || prim >= ARRAY_SIZE(cnf)) {
+ printf("Unknown primitive: %d\n", prim);
+ exit(-3);
+ }
+
+ return cnf[prim];
+}
+
+static int is_indication(int prim)
+{
+ return
+ prim == GsmL1_PrimId_MphTimeInd ||
+ prim == GsmL1_PrimId_MphSyncInd ||
+ prim == GsmL1_PrimId_PhConnectInd ||
+ prim == GsmL1_PrimId_PhReadyToSendInd ||
+ prim == GsmL1_PrimId_PhDataInd ||
+ prim == GsmL1_PrimId_PhRaInd;
+}
+
+
+static int send_recv_sig_prim(int p, GsmL1_Prim_t *prim)
+{
+ int rc;
+ prim->id = p;
+ rc = write(sig_arm2dsp, prim, sizeof(*prim));
+ if (rc != sizeof(*prim)) {
+ printf("Failed to write: %d\n", rc);
+ return -1;
+ }
+
+ do {
+ rc = read(sig_dsp2arm, prim, sizeof(*prim));
+ if (rc != sizeof(*prim)) {
+ printf("Failed to read: %d\n", rc);
+ return -2;
+ }
+ } while (is_indication(prim->id));
+
+ if (prim->id != answer_for_sig(p)) {
+ printf("Wrong L1 result got %d wanted %d for prim: %d\n",
+ prim->id, answer_for_sig(p), p);
+ return -3;
+ }
+
+ return 0;
+}
+
+static int wait_for_indication(int p, GsmL1_Prim_t *prim)
+{
+ int rc;
+ memset(prim, 0, sizeof(*prim));
+
+ struct timespec start_time, now_time;
+ clock_gettime(CLOCK_MONOTONIC, &start_time);
+
+ /*
+ * TODO: select.... with timeout. The below will work 99% as we will
+ * get time indications very soonish after the connect
+ */
+ for (;;) {
+ clock_gettime(CLOCK_MONOTONIC, &now_time);
+ if (now_time.tv_sec - start_time.tv_sec > 10) {
+ printf("Timeout waiting for indication.\n");
+ return -4;
+ }
+
+ rc = read(sig_dsp2arm, prim, sizeof(*prim));
+ if (rc != sizeof(*prim)) {
+ printf("Failed to read.\n");
+ return -1;
+ }
+
+ if (!is_indication(prim->id)) {
+ printf("No indication: %d\n", prim->id);
+ return -2;
+ }
+
+ if (p != prim->id && prim->id == GsmL1_PrimId_MphSyncInd) {
+ printf("Got sync.\n");
+ sync_indicated = 1;
+ continue;
+ }
+ if (p != prim->id && prim->id == GsmL1_PrimId_MphTimeInd) {
+ time_indicated = 1;
+ continue;
+ }
+
+ if (p != prim->id) {
+ printf("Wrong indication got %d wanted %d\n",
+ prim->id, p);
+ return -3;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+static int set_trace_flags(uint32_t dsp)
+{
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.setTraceFlagsReq.u32Tf = dsp;
+ return send_primitive(SuperFemto_PrimId_SetTraceFlagsReq, &prim);
+}
+
+static int reset_and_wait()
+{
+ int rc;
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ rc = send_recv_primitive(SuperFemto_PrimId_Layer1ResetReq, &prim);
+ if (rc != 0)
+ return -1;
+ if (prim.u.layer1ResetCnf.status != GsmL1_Status_Success)
+ return -2;
+ return 0;
+}
+
+/**
+ * Open the message queues and (re-)initialize the DSP and FPGA
+ */
+int initialize_layer1(uint32_t dsp_flags)
+{
+ if (open_devices() != 0) {
+ printf("Failed to open devices.\n");
+ return -1;
+ }
+
+ if (set_trace_flags(dsp_flags) != 0) {
+ printf("Failed to set dsp flags.\n");
+ return -2;
+ }
+ if (reset_and_wait() != 0) {
+ printf("Failed to reset the firmware.\n");
+ return -3;
+ }
+ return 0;
+}
+
+/**
+ * Print systems infos
+ */
+int print_system_info()
+{
+ int rc;
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ rc = send_recv_primitive(SuperFemto_PrimId_SystemInfoReq, &prim);
+ if (rc != 0) {
+ printf("Failed to send SystemInfoRequest.\n");
+ return -1;
+ }
+
+ if (prim.u.systemInfoCnf.status != GsmL1_Status_Success) {
+ printf("Failed to request SystemInfoRequest.\n");
+ return -2;
+ }
+
+#define INFO_DSP(x) x.u.systemInfoCnf.dspVersion
+#define INFO_FPGA(x) x.u.systemInfoCnf.fpgaVersion
+#ifdef FEMTOBTS_NO_BOARD_VERSION
+#define BOARD_REV(x) -1
+#define BOARD_OPT(x) -1
+#define COMPILED_MAJOR (FEMTOBTS_API_VERSION >> 16)
+#define COMPILED_MINOR ((FEMTOBTS_API_VERSION >> 8) & 0xff)
+#define COMPILED_BUILD (FEMTOBTS_API_VERSION & 0xff)
+#else
+#define BOARD_REV(x) x.u.systemInfoCnf.boardVersion.rev
+#define BOARD_OPT(x) x.u.systemInfoCnf.boardVersion.option
+#define COMPILED_MAJOR (SUPERFEMTO_API_VERSION >> 16)
+#define COMPILED_MINOR ((SUPERFEMTO_API_VERSION >> 8) & 0xff)
+#define COMPILED_BUILD (SUPERFEMTO_API_VERSION & 0xff)
+#endif
+
+ printf("Compiled against: v%u.%u.%u\n",
+ COMPILED_MAJOR, COMPILED_MINOR, COMPILED_BUILD);
+ printf("Running DSP v%d.%d.%d FPGA v%d.%d.%d Rev: %d Option: %d\n",
+ INFO_DSP(prim).major, INFO_DSP(prim).minor, INFO_DSP(prim).build,
+ INFO_FPGA(prim).major, INFO_FPGA(prim).minor, INFO_FPGA(prim).build,
+ BOARD_REV(prim), BOARD_OPT(prim));
+
+ if (COMPILED_MAJOR != INFO_DSP(prim).major || COMPILED_MINOR != INFO_DSP(prim).minor) {
+ printf("WARNING! WARNING! WARNING! WARNING! WARNING\n");
+ printf("You might run this against an incompatible firmware.\n");
+ printf("Continuing anyway but the result might be broken\n");
+ }
+#undef INFO_DSP
+#undef INFO_FPGA
+#undef BOARD_REV
+#undef BOARD_OPT
+#undef COMPILED_MAJOR
+#undef COMPILED_MINOR
+#undef COMPILED_BUILD
+ return 0;
+}
+
+int activate_rf_frontend(int clock_source, int initial_cor)
+{
+ int rc;
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.activateRfReq.timing.u8TimSrc = 1;
+ prim.u.activateRfReq.msgq.u8UseTchMsgq = 0;
+ prim.u.activateRfReq.msgq.u8UsePdtchMsgq = 0;
+
+ prim.u.activateRfReq.rfTrx.iClkCor = initial_cor;
+ prim.u.activateRfReq.rfTrx.clkSrc = clock_source;
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0)
+ prim.u.activateRfReq.rfRx.iClkCor = initial_cor;
+ prim.u.activateRfReq.rfRx.clkSrc = clock_source;
+#endif
+
+ rc = send_recv_primitive(SuperFemto_PrimId_ActivateRfReq, &prim);
+ return rc;
+}
+
+static int mph_init(int band, int arfcn, HANDLE *layer1)
+{
+ int rc;
+ GsmL1_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.mphInitReq.deviceParam.devType = GsmL1_DevType_Rxd;
+ prim.u.mphInitReq.deviceParam.freqBand = band;
+ prim.u.mphInitReq.deviceParam.u16Arfcn = arfcn;
+ prim.u.mphInitReq.deviceParam.u16BcchArfcn = arfcn;
+ prim.u.mphInitReq.deviceParam.fRxPowerLevel = -75.f;
+ prim.u.mphInitReq.deviceParam.u8AutoTA = 1;
+
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphInitReq, &prim);
+ if (rc != 0) {
+ printf("Failed to initialize the physical channel.\n");
+ return -1;
+ }
+
+ if (prim.u.mphInitCnf.status != GsmL1_Status_Success) {
+ printf("MPH Init failed.\n");
+ return -2;
+ }
+
+#if 0
+ if (prim.u.mphInitCnf.freqBand != band) {
+ printf("Layer1 ignored the band: %d\n",
+ prim.u.mphInitCnf.freqBand);
+ return -3;
+ }
+#endif
+
+ *layer1 = prim.u.mphInitCnf.hLayer1;
+ return 0;
+}
+
+int mph_close(HANDLE layer1)
+{
+ int rc;
+ GsmL1_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.mphCloseReq.hLayer1 = layer1;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphCloseReq, &prim);
+ if (rc != 0) {
+ printf("Failed to close the MPH\n");
+ return -6;
+ }
+ if (prim.u.mphCloseCnf.status != GsmL1_Status_Success) {
+ printf("MPH Close failed.\n");
+ return -7;
+ }
+
+ return 0;
+}
+
+int follow_sch(int band, int arfcn, int clock, int ref, HANDLE *layer1)
+{
+ int rc;
+ GsmL1_Prim_t prim;
+
+ time_indicated = 0;
+ sync_indicated = 0;
+
+ rc = mph_init(band, arfcn, layer1);
+ if (rc != 0)
+ return rc;
+
+ /* 1.) Connect */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphConnectReq.hLayer1 = *layer1;
+ prim.u.mphConnectReq.u8Tn = 0;
+ prim.u.mphConnectReq.logChComb = GsmL1_LogChComb_IV;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphConnectReq, &prim);
+ if (rc != 0) {
+ printf("Failed to connect.\n");
+ return -1;
+ }
+ if (prim.u.mphConnectCnf.status != GsmL1_Status_Success) {
+ printf("Connect failed.\n");
+ return -2;
+ }
+ if (prim.u.mphConnectCnf.u8Tn != 0) {
+ printf("Wrong timeslot.\n");
+ return -3;
+ }
+
+ /* 2.) Activate */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphActivateReq.hLayer1 = *layer1;
+ prim.u.mphActivateReq.u8Tn = 0;
+ prim.u.mphActivateReq.sapi = GsmL1_Sapi_Sch;
+ prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim);
+ if (rc != 0) {
+ printf("Activation failed.\n");
+ return -4;
+ }
+ if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) {
+ printf("Activation not successful.\n");
+ return -5;
+ }
+
+ /* 3.) Wait for indication... TODO: check... */
+ printf("Waiting for connect indication.\n");
+ rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim);
+ if (rc != 0) {
+ printf("Didn't get a connect indication.\n");
+ return rc;
+ }
+
+ /* 4.) Indication Syndication TODO: check... */
+ if (!sync_indicated) {
+ printf("Waiting for sync indication.\n");
+ rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim);
+ if (rc < 0) {
+ printf("Didn't get a sync indication.\n");
+ return -23;
+ } else if (rc == 0) {
+ if (!prim.u.mphSyncInd.u8Synced) {
+ printf("Failed to get sync.\n");
+ return -23;
+ } else {
+ printf("Synced.\n");
+ }
+ }
+ } else {
+ printf("Already synced.\n");
+ }
+
+ return 0;
+}
+
+static int follow_sapi(HANDLE layer1, const GsmL1_Sapi_t sapi)
+{
+ int rc;
+ GsmL1_Prim_t prim;
+
+ /* 1.) Activate BCCH or such... */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphActivateReq.hLayer1 = layer1;
+ prim.u.mphActivateReq.u8Tn = 0;
+ prim.u.mphActivateReq.sapi = sapi;
+ prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink;
+
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim);
+ if (rc != 0) {
+ printf("Activation failed.\n");
+ return -4;
+ }
+ if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) {
+ printf("Activation not successful.\n");
+ return -5;
+ }
+
+ /* 2.) Wait for indication... */
+ printf("Waiting for connect indication.\n");
+ rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim);
+ if (rc != 0) {
+ printf("Didn't get a connect indication.\n");
+ return rc;
+ }
+
+ if (prim.u.phConnectInd.sapi != sapi) {
+ printf("Got a connect indication for the wrong type: %d\n",
+ prim.u.phConnectInd.sapi);
+ return -6;
+ }
+
+ /* 3.) Wait for PhDataInd... */
+ printf("Waiting for data.\n");
+ rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim);
+ if (rc != 0) {
+ printf("Didn't get data.\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+int follow_bcch(HANDLE layer1)
+{
+ return follow_sapi(layer1, GsmL1_Sapi_Bcch);
+}
+
+int follow_pch(HANDLE layer1)
+{
+ return follow_sapi(layer1, GsmL1_Sapi_Pch);
+}
+
+int find_bsic(void)
+{
+ int rc, i;
+ GsmL1_Prim_t prim;
+
+ printf("Waiting for SCH data.\n");
+ for (i = 0; i < 10; ++i) {
+ uint8_t bsic;
+ rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim);
+ if (rc < 0) {
+ printf("Didn't get SCH data.\n");
+ return rc;
+ }
+ if (prim.u.phDataInd.sapi != GsmL1_Sapi_Sch)
+ continue;
+
+ bsic = (prim.u.phDataInd.msgUnitParam.u8Buffer[0] >> 2) & 0xFF;
+ return bsic;
+ }
+
+ printf("Giving up finding the SCH\n");
+ return -1;
+}
+
+int set_tsc_from_bsic(HANDLE layer1, int bsic)
+{
+ int rc;
+ int tsc = bsic & 0x7;
+ GsmL1_Prim_t prim;
+
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphConfigReq.hLayer3 = 0x23;
+ prim.u.mphConfigReq.hLayer1 = layer1;
+ prim.u.mphConfigReq.cfgParamId = GsmL1_ConfigParamId_SetNbTsc;
+ prim.u.mphConfigReq.cfgParams.setNbTsc.u8NbTsc = tsc;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphConfigReq, &prim);
+ if (rc != 0) {
+ printf("Failed to send configure.\n");
+ }
+
+ if (prim.u.mphConfigCnf.status != GsmL1_Status_Success) {
+ printf("Failed to set the config cnf.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int set_clock_cor(int clock_cor, int calib, int source)
+{
+ int rc;
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ prim.u.rfClockSetupReq.rfTrx.iClkCor = clock_cor;
+ prim.u.rfClockSetupReq.rfTrx.clkSrc = calib;
+#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0)
+ prim.u.rfClockSetupReq.rfRx.iClkCor = clock_cor;
+ prim.u.rfClockSetupReq.rfRx.clkSrc = calib;
+#endif
+ prim.u.rfClockSetupReq.rfTrxClkCal.clkSrc = source;
+
+ rc = send_recv_primitive(SuperFemto_PrimId_RfClockSetupReq, &prim);
+ if (rc != 0) {
+ printf("Failed to set the clock setup.\n");
+ return -1;
+ }
+ if (prim.u.rfClockSetupCnf.status != GsmL1_Status_Success) {
+ printf("Clock setup was not successfull.\n");
+ return -2;
+ }
+
+ return 0;
+}
+
+int rf_clock_info(int *clkErr, int *clkErrRes)
+{
+ SuperFemto_Prim_t prim;
+ memset(&prim, 0, sizeof(prim));
+
+ int rc;
+
+ /* reset the counter */
+ prim.u.rfClockInfoReq.u8RstClkCal = 1;
+ rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim);
+ if (rc != 0) {
+ printf("Failed to reset the clock info.\n");
+ return -1;
+ }
+
+ /* wait for a value */
+ wait_read_ignore(15);
+
+ /* ask for the current counter/error */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.rfClockInfoReq.u8RstClkCal = 0;
+ rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim);
+ if (rc != 0) {
+ printf("Failed to get the clock info.\n");
+ return -2;
+ }
+
+ printf("Error: %d Res: %d\n",
+ prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr,
+ prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes);
+ *clkErr = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr;
+ *clkErrRes = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes;
+ return 0;
+}
+
+int power_scan(int band, int arfcn, int duration, float *mean_rssi)
+{
+ int rc;
+ HANDLE layer1;
+ GsmL1_Prim_t prim;
+
+ /* init */
+ rc = mph_init(band, arfcn, &layer1);
+ if (rc != 0)
+ return rc;
+
+ /* mph measure request */
+ memset(&prim, 0, sizeof(prim));
+ prim.u.mphMeasureReq.hLayer1 = layer1;
+ prim.u.mphMeasureReq.u32Duration = duration;
+ rc = send_recv_sig_prim(GsmL1_PrimId_MphMeasureReq, &prim);
+ if (rc != 0) {
+ printf("Failed to send measurement request.\n");
+ return -4;
+ }
+
+ if (prim.u.mphMeasureCnf.status != GsmL1_Status_Success) {
+ printf("MphMeasureReq was not confirmed.\n");
+ return -5;
+ }
+
+ *mean_rssi = prim.u.mphMeasureCnf.fMeanRssi;
+
+ /* close */
+ rc = mph_close(layer1);
+ return rc;
+}
+
+/**
+ * Wait for indication...
+ */
+int wait_for_sync(HANDLE layer1, int cor, int calib, int source)
+{
+ GsmL1_Prim_t prim;
+ int rc;
+
+ rc = set_clock_cor(cor, calib, source);
+ if (rc != 0) {
+ printf("Failed to set the clock correction.\n");
+ return -1;
+ }
+
+ sync_indicated = 0;
+ rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim);
+ if (rc < 0 && rc != -4) {
+ return rc;
+ } else if (rc == 0) {
+ if (!prim.u.mphSyncInd.u8Synced) {
+ printf("Failed to get sync.\n");
+ return 0;
+ }
+ printf("Synced.\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sap)
+{
+ GsmL1_Prim_t prim;
+ int rc;
+
+ rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim);
+ if (rc < 0)
+ return rc;
+ if (prim.u.phDataInd.sapi == GsmL1_Sapi_Sch)
+ return 1;
+
+ *size = prim.u.phDataInd.msgUnitParam.u8Size;
+ *fn = prim.u.phDataInd.u32Fn;
+ *block = prim.u.phDataInd.u8BlockNbr;
+ *sap = prim.u.phDataInd.sapi;
+ memcpy(data, prim.u.phDataInd.msgUnitParam.u8Buffer, *size);
+ return 0;
+}
+
+/**
+ * Make sure the pipe is not running full.
+ *
+ */
+static int wait_read_ignore(int seconds)
+{
+ int max, rc;
+ fd_set fds;
+ struct timeval timeout;
+
+ max = sys_dsp2arm > sig_dsp2arm ? sys_dsp2arm : sig_dsp2arm;
+
+ timeout.tv_sec = seconds;
+ timeout.tv_usec = 0;
+
+ while (1) {
+ FD_ZERO(&fds);
+ FD_SET(sys_dsp2arm, &fds);
+ FD_SET(sig_dsp2arm, &fds);
+
+
+ rc = select(max + 1, &fds, NULL, NULL, &timeout);
+ if (rc == -1) {
+ printf("Failed to select.\n");
+ return -1;
+ } else if (rc) {
+ if (FD_ISSET(sys_dsp2arm, &fds)) {
+ SuperFemto_Prim_t prim;
+ rc = read(sys_dsp2arm, &prim, sizeof(prim));
+ if (rc != sizeof(prim)) {
+ perror("Failed to read system primitive");
+ return -2;
+ }
+ }
+ if (FD_ISSET(sig_dsp2arm, &fds)) {
+ GsmL1_Prim_t prim;
+ rc = read(sig_dsp2arm, &prim, sizeof(prim));
+ if (rc != sizeof(prim)) {
+ perror("Failed to read signal primitiven");
+ return -3;
+ }
+ }
+ } else if (timeout.tv_sec <= 0 && timeout.tv_usec <= 0) {
+ break;
+ }
+
+#ifndef __linux__
+#error "Non portable code"
+#endif
+ }
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.h b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h
new file mode 100644
index 00000000..e7d59c94
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h
@@ -0,0 +1,45 @@
+#ifndef SYSMOBTS_LAYER_H
+#define SYSMOBTS_LAYER_H
+
+#include <sysmocom/femtobts/superfemto.h>
+
+#ifdef FEMTOBTS_API_VERSION
+#define SuperFemto_PrimId_t FemtoBts_PrimId_t
+#define SuperFemto_Prim_t FemtoBts_Prim_t
+#define SuperFemto_PrimId_SystemInfoReq FemtoBts_PrimId_SystemInfoReq
+#define SuperFemto_PrimId_SystemInfoCnf FemtoBts_PrimId_SystemInfoCnf
+#define SuperFemto_SystemInfoCnf_t FemtoBts_SystemInfoCnf_t
+#define SuperFemto_PrimId_SystemFailureInd FemtoBts_PrimId_SystemFailureInd
+#define SuperFemto_PrimId_ActivateRfReq FemtoBts_PrimId_ActivateRfReq
+#define SuperFemto_PrimId_ActivateRfCnf FemtoBts_PrimId_ActivateRfCnf
+#define SuperFemto_PrimId_DeactivateRfReq FemtoBts_PrimId_DeactivateRfReq
+#define SuperFemto_PrimId_DeactivateRfCnf FemtoBts_PrimId_DeactivateRfCnf
+#define SuperFemto_PrimId_SetTraceFlagsReq FemtoBts_PrimId_SetTraceFlagsReq
+#define SuperFemto_PrimId_RfClockInfoReq FemtoBts_PrimId_RfClockInfoReq
+#define SuperFemto_PrimId_RfClockInfoCnf FemtoBts_PrimId_RfClockInfoCnf
+#define SuperFemto_PrimId_RfClockSetupReq FemtoBts_PrimId_RfClockSetupReq
+#define SuperFemto_PrimId_RfClockSetupCnf FemtoBts_PrimId_RfClockSetupCnf
+#define SuperFemto_PrimId_Layer1ResetReq FemtoBts_PrimId_Layer1ResetReq
+#define SuperFemto_PrimId_Layer1ResetCnf FemtoBts_PrimId_Layer1ResetCnf
+#define SuperFemto_PrimId_NUM FemtoBts_PrimId_NUM
+#define HW_SYSMOBTS_V1 1
+#define SUPERFEMTO_API(x,y,z) FEMTOBTS_API(x,y,z)
+#endif
+
+extern int initialize_layer1(uint32_t dsp_flags);
+extern int print_system_info();
+extern int activate_rf_frontend(int clock_source, int clock_cor);
+extern int power_scan(int band, int arfcn, int duration, float *mean_rssi);
+extern int follow_sch(int band, int arfcn, int calib, int reference, HANDLE *layer1);
+extern int follow_bch(HANDLE layer1);
+extern int find_bsic(void);
+extern int set_tsc_from_bsic(HANDLE layer1, int bsic);
+extern int set_clock_cor(int clock_corr, int calib, int source);
+extern int rf_clock_info(int *clkErr, int *clkErrRes);
+extern int mph_close(HANDLE layer1);
+extern int wait_for_sync(HANDLE layer1, int cor, int calib, int source);
+extern int follow_bcch(HANDLE layer1);
+extern int follow_pch(HANDLE layer1);
+extern int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sapi);
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h
new file mode 100644
index 00000000..b7a27fb7
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h
@@ -0,0 +1,44 @@
+#ifndef _SYSMOBTS_EEPROM_H
+#define _SYSMOBTS_EEPROM_H
+
+#include <stdint.h>
+
+struct sysmobts_net_cfg {
+ uint8_t mode; /* 0 */
+ uint32_t ip; /* 1 - 4 */
+ uint32_t mask; /* 5 - 8 */
+ uint32_t gw; /* 9 - 12 */
+ uint32_t dns; /* 13 - 16 */
+} __attribute__((packed));
+
+struct sysmobts_eeprom { /* offset */
+ uint8_t eth_mac[6]; /* 0-5 */
+ uint8_t _pad0[10]; /* 6-15 */
+ uint16_t unused1; /* 16-17 */
+ uint8_t temp1_max; /* 18 */
+ uint8_t temp2_max; /* 19 */
+ uint32_t serial_nr; /* 20-23 */
+ uint32_t operational_hours; /* 24-27 */
+ uint32_t boot_count; /* 28-31 */
+ uint16_t model_nr; /* 32-33 */
+ uint16_t model_flags; /* 34-35 */
+ uint8_t trx_nr; /* 36 */
+ uint8_t boot_state[48]; /* 37-84 */
+ uint8_t _pad1[18]; /* 85-102 */
+ struct sysmobts_net_cfg net_cfg;/* 103-119 */
+ uint8_t crc; /* 120 */
+ uint8_t gpg_key[128]; /* 121-249 */
+} __attribute__((packed));
+
+enum sysmobts_model_number {
+ MODEL_SYSMOBTS_1002 = 1002,
+ MODEL_SYSMOBTS_1020 = 1020,
+ MODEL_SYSMOBTS_2050 = 2050,
+};
+
+enum sysmobts_net_mode {
+ NET_MODE_DHCP,
+ NET_MODE_STATIC,
+};
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c
new file mode 100644
index 00000000..a0080738
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c
@@ -0,0 +1,336 @@
+/* Main program for SysmoBTS management daemon */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+
+#include "misc/sysmobts_misc.h"
+#include "misc/sysmobts_mgr.h"
+#include "misc/sysmobts_par.h"
+
+static int bts_type;
+static int trx_number;
+
+static int no_eeprom_write = 0;
+static int daemonize = 0;
+void *tall_mgr_ctx;
+
+/* every 6 hours means 365*4 = 1460 EEprom writes per year (max) */
+#define TEMP_TIMER_SECS (6 * 3600)
+
+/* every 1 hours means 365*24 = 8760 EEprom writes per year (max) */
+#define HOURS_TIMER_SECS (1 * 3600)
+
+/* the initial state */
+static struct sysmobts_mgr_instance manager = {
+ .config_file = "sysmobts-mgr.cfg",
+ .rf_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .digital_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .board_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .pa_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 100,
+ },
+ .action_warn = 0,
+ .action_crit = TEMP_ACT_PA_OFF,
+ .state = STATE_NORMAL,
+};
+
+
+static int classify_bts(void)
+{
+ int rc;
+
+ rc = sysmobts_get_type(&bts_type);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to get model number.\n");
+ return -1;
+ }
+
+ rc = sysmobts_get_trx(&trx_number);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to get the trx number.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int sysmobts_bts_type(void)
+{
+ return bts_type;
+}
+
+int sysmobts_trx_number(void)
+{
+ return trx_number;
+}
+
+int is_sbts2050(void)
+{
+ return bts_type == 2050;
+}
+
+int is_sbts2050_trx(int trx)
+{
+ return trx_number == trx;
+}
+
+int is_sbts2050_master(void)
+{
+ if (!is_sbts2050())
+ return 0;
+ if (!is_sbts2050_trx(0))
+ return 0;
+ return 1;
+}
+
+static struct osmo_timer_list temp_timer;
+static void check_temp_timer_cb(void *unused)
+{
+ sysmobts_check_temp(no_eeprom_write);
+
+ osmo_timer_schedule(&temp_timer, TEMP_TIMER_SECS, 0);
+}
+
+static struct osmo_timer_list hours_timer;
+static void hours_timer_cb(void *unused)
+{
+ sysmobts_update_hours(no_eeprom_write);
+
+ osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0);
+}
+
+static void print_help(void)
+{
+ printf("sysmobts-mgr [-nsD] [-d cat]\n");
+ printf(" -n Do not write to EEPROM\n");
+ printf(" -s Disable color\n");
+ printf(" -d CAT enable debugging\n");
+ printf(" -D daemonize\n");
+ printf(" -c Specify the filename of the config file\n");
+}
+
+static int parse_options(int argc, char **argv)
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) {
+ switch (opt) {
+ case 'n':
+ no_eeprom_write = 1;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ 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':
+ manager.config_file = optarg;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stderr, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ case SIGTERM:
+ sysmobts_check_temp(no_eeprom_write);
+ sysmobts_update_hours(no_eeprom_write);
+ exit(0);
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_mgr_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static struct log_info_cat mgr_log_info_cat[] = {
+ [DTEMP] = {
+ .name = "DTEMP",
+ .description = "Temperature monitoring",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFW] = {
+ .name = "DFW",
+ .description = "DSP/FPGA firmware management",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFIND] = {
+ .name = "DFIND",
+ .description = "ipaccess-find handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DCALIB] = {
+ .name = "DCALIB",
+ .description = "Calibration handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+};
+
+static const struct log_info mgr_log_info = {
+ .cat = mgr_log_info_cat,
+ .num_cat = ARRAY_SIZE(mgr_log_info_cat),
+};
+
+int main(int argc, char **argv)
+{
+ int rc;
+ struct ctrl_connection *ccon;
+
+ tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager");
+ msgb_talloc_ctx_init(tall_mgr_ctx, 0);
+
+ srand(time(NULL));
+
+ osmo_init_logging2(tall_mgr_ctx, &mgr_log_info);
+ if (classify_bts() != 0)
+ exit(2);
+
+ osmo_init_ignore_signals();
+ signal(SIGINT, &signal_handler);
+ signal(SIGTERM, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ sysmobts_mgr_vty_init();
+ logging_vty_add_cmds(&mgr_log_info);
+ rc = sysmobts_mgr_parse_config(&manager);
+ if (rc < 0) {
+ LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n");
+ exit(1);
+ }
+
+ rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ /* start temperature check timer */
+ temp_timer.cb = check_temp_timer_cb;
+ check_temp_timer_cb(NULL);
+
+ /* start operational hours timer */
+ hours_timer.cb = hours_timer_cb;
+ hours_timer_cb(NULL);
+
+ /* start uc temperature check timer */
+ sbts2050_uc_initialize();
+
+ /* handle broadcast messages for ipaccess-find */
+ if (sysmobts_mgr_nl_init() != 0)
+ exit(3);
+
+ /* Initialize the temperature control */
+ ccon = osmo_ctrl_conn_alloc(tall_mgr_ctx, NULL);
+ rc = -1;
+ if (ccon) {
+ ccon->write_queue.bfd.data = ccon;
+ rc = osmo_sock_init_ofd(&ccon->write_queue.bfd, AF_INET,
+ SOCK_STREAM, IPPROTO_TCP,
+ "localhost", OSMO_CTRL_PORT_BTS,
+ OSMO_SOCK_F_CONNECT);
+ }
+ if (rc < 0)
+ LOGP(DLCTRL, LOGL_ERROR, "Can't connect to CTRL @ localhost:%u\n",
+ OSMO_CTRL_PORT_BTS);
+ else
+ LOGP(DLCTRL, LOGL_NOTICE, "CTRL connected to locahost:%u\n",
+ OSMO_CTRL_PORT_BTS);
+
+ sysmobts_mgr_temp_init(&manager, ccon);
+
+ if (sysmobts_mgr_calib_init(&manager) != 0)
+ exit(3);
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+
+ while (1) {
+ log_reset_context();
+ osmo_select_main(0);
+ }
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.h b/src/osmo-bts-sysmo/misc/sysmobts_mgr.h
new file mode 100644
index 00000000..88f4e245
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.h
@@ -0,0 +1,122 @@
+#ifndef _SYSMOBTS_MGR_H
+#define _SYSMOBTS_MGR_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+
+#include <gps.h>
+
+#include <stdint.h>
+
+enum {
+ DTEMP,
+ DFW,
+ DFIND,
+ DCALIB,
+};
+
+
+enum {
+#if 0
+ TEMP_ACT_PWR_CONTRL = 0x1,
+#endif
+ TEMP_ACT_SLAVE_OFF = 0x4,
+ TEMP_ACT_PA_OFF = 0x8,
+ TEMP_ACT_BTS_SRV_OFF = 0x10,
+};
+
+/* actions only for normal state */
+enum {
+#if 0
+ TEMP_ACT_NORM_PW_CONTRL = 0x1,
+#endif
+ TEMP_ACT_NORM_SLAVE_ON = 0x4,
+ TEMP_ACT_NORM_PA_ON = 0x8,
+ TEMP_ACT_NORM_BTS_SRV_ON= 0x10,
+};
+
+enum sysmobts_temp_state {
+ STATE_NORMAL, /* Everything is fine */
+ STATE_WARNING_HYST, /* Go back to normal next? */
+ STATE_WARNING, /* We are above the warning threshold */
+ STATE_CRITICAL, /* We have an issue. Wait for below warning */
+};
+
+/**
+ * Temperature Limits. We separate from a threshold
+ * that will generate a warning and one that is so
+ * severe that an action will be taken.
+ */
+struct sysmobts_temp_limit {
+ int thresh_warn;
+ int thresh_crit;
+};
+
+enum mgr_vty_node {
+ MGR_NODE = _LAST_OSMOVTY_NODE + 1,
+
+ ACT_NORM_NODE,
+ ACT_WARN_NODE,
+ ACT_CRIT_NODE,
+ LIMIT_RF_NODE,
+ LIMIT_DIGITAL_NODE,
+ LIMIT_BOARD_NODE,
+ LIMIT_PA_NODE,
+};
+
+struct sysmobts_mgr_instance {
+ const char *config_file;
+
+ struct sysmobts_temp_limit rf_limit;
+ struct sysmobts_temp_limit digital_limit;
+
+ /* Only available on sysmobts 2050 */
+ struct sysmobts_temp_limit board_limit;
+ struct sysmobts_temp_limit pa_limit;
+
+ int action_norm;
+ int action_warn;
+ int action_crit;
+
+ enum sysmobts_temp_state state;
+
+ struct {
+ int initial_calib_started;
+ int is_up;
+ struct osmo_timer_list recon_timer;
+ struct ipa_client_conn *bts_conn;
+
+ int state;
+ struct osmo_timer_list timer;
+ uint32_t last_seqno;
+
+ /* gps structure to see if there is a fix */
+ int gps_open;
+ struct osmo_fd gpsfd;
+ struct gps_data_t gpsdata;
+ struct osmo_timer_list fix_timeout;
+
+ /* Loop/Re-try control */
+ int calib_from_loop;
+ struct osmo_timer_list calib_timeout;
+ } calib;
+};
+
+int sysmobts_mgr_vty_init(void);
+int sysmobts_mgr_parse_config(struct sysmobts_mgr_instance *mgr);
+int sysmobts_mgr_nl_init(void);
+int sysmobts_mgr_temp_init(struct sysmobts_mgr_instance *mgr,
+ struct ctrl_connection *ctrl);
+const char *sysmobts_mgr_temp_get_state(enum sysmobts_temp_state state);
+
+
+int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr);
+int sysmobts_mgr_calib_run(struct sysmobts_mgr_instance *mgr);
+
+
+extern void *tall_mgr_ctx;
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c
new file mode 100644
index 00000000..12961e3f
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c
@@ -0,0 +1,384 @@
+/* (C) 2014 by s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "sysmobts_misc.h"
+#include "sysmobts_par.h"
+#include "sysmobts_mgr.h"
+#include "btsconfig.h"
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/serial.h>
+
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#ifdef BUILD_SBTS2050
+#include <sysmocom/femtobts/sbts2050_header.h>
+
+#define SERIAL_ALLOC_SIZE 300
+#define SIZE_HEADER_RSP 5
+#define SIZE_HEADER_CMD 4
+
+struct uc {
+ int id;
+ int fd;
+ const char *path;
+};
+
+struct ucinfo {
+ uint16_t id;
+ int master;
+ int slave;
+ int pa;
+};
+
+static struct uc ucontrol0 = {
+ .id = 0,
+ .path = "/dev/ttyS0",
+ .fd = -1,
+};
+
+/**********************************************************************
+ * Functions read/write from serial interface
+ *********************************************************************/
+static int hand_serial_read(int fd, struct msgb *msg, int numbytes)
+{
+ int rc, bread = 0;
+
+ if (numbytes > msgb_tailroom(msg))
+ return -ENOSPC;
+
+ while (bread < numbytes) {
+ rc = read(fd, msg->tail, numbytes - bread);
+ if (rc < 0)
+ return -1;
+ if (rc == 0)
+ break;
+
+ bread += rc;
+ msgb_put(msg, rc);
+ }
+
+ return bread;
+}
+
+static int hand_serial_write(int fd, struct msgb *msg)
+{
+ int rc, bwritten = 0;
+
+ while (msg->len > 0) {
+ rc = write(fd, msg->data, msg->len);
+ if (rc <= 0)
+ return -1;
+
+ msgb_pull(msg, rc);
+ bwritten += rc;
+ }
+
+ return bwritten;
+}
+
+/**********************************************************************
+ * Functions request information to Microcontroller
+ *********************************************************************/
+static void add_parity(cmdpkt_t *command)
+{
+ int n;
+ uint8_t parity = 0x00;
+ for (n = 0; n < SIZE_HEADER_CMD+command->u8Len; n++)
+ parity ^= ((uint8_t *)command)[n];
+
+ command->cmd.raw[command->u8Len] = parity;
+}
+
+static struct msgb *sbts2050_ucinfo_sndrcv(struct uc *ucontrol, const struct ucinfo *info)
+{
+ int num, rc;
+ cmdpkt_t *command;
+ rsppkt_t *response;
+ struct msgb *msg;
+ fd_set fdread;
+ struct timeval tout = {
+ .tv_sec = 10,
+ };
+
+ switch (info->id) {
+ case SBTS2050_TEMP_RQT:
+ num = sizeof(command->cmd.tempGet);
+ break;
+ case SBTS2050_PWR_RQT:
+ num = sizeof(command->cmd.pwrSetState);
+ break;
+ case SBTS2050_PWR_STATUS:
+ num = sizeof(command->cmd.pwrGetStatus);
+ break;
+ default:
+ return NULL;
+ }
+ num = num + SIZE_HEADER_CMD+1;
+
+ msg = msgb_alloc(SERIAL_ALLOC_SIZE, "Message Microcontroller");
+ if (msg == NULL) {
+ LOGP(DTEMP, LOGL_ERROR, "Error creating msg\n");
+ return NULL;
+ }
+ command = (cmdpkt_t *) msgb_put(msg, num);
+
+ command->u16Magic = 0xCAFE;
+ switch (info->id) {
+ case SBTS2050_TEMP_RQT:
+ command->u8Id = info->id;
+ command->u8Len = sizeof(command->cmd.tempGet);
+ break;
+ case SBTS2050_PWR_RQT:
+ command->u8Id = info->id;
+ command->u8Len = sizeof(command->cmd.pwrSetState);
+ command->cmd.pwrSetState.u1MasterEn = !!info->master;
+ command->cmd.pwrSetState.u1SlaveEn = !!info->slave;
+ command->cmd.pwrSetState.u1PwrAmpEn = !!info->pa;
+ break;
+ case SBTS2050_PWR_STATUS:
+ command->u8Id = info->id;
+ command->u8Len = sizeof(command->cmd.pwrGetStatus);
+ break;
+ default:
+ goto err;
+ }
+
+ add_parity(command);
+
+ if (hand_serial_write(ucontrol->fd, msg) < 0)
+ goto err;
+
+ msgb_reset(msg);
+
+ FD_ZERO(&fdread);
+ FD_SET(ucontrol->fd, &fdread);
+
+ num = SIZE_HEADER_RSP;
+ while (1) {
+ rc = select(ucontrol->fd+1, &fdread, NULL, NULL, &tout);
+ if (rc > 0) {
+ if (hand_serial_read(ucontrol->fd, msg, num) < 0)
+ goto err;
+
+ response = (rsppkt_t *)msg->data;
+
+ if (response->u8Id != info->id || msg->len <= 0 ||
+ response->i8Error != RQT_SUCCESS)
+ goto err;
+
+ if (msg->len == SIZE_HEADER_RSP + response->u8Len + 1)
+ break;
+
+ num = response->u8Len + 1;
+ } else
+ goto err;
+ }
+
+ return msg;
+
+err:
+ msgb_free(msg);
+ return NULL;
+}
+
+/**********************************************************************
+ * Get power status function
+ *********************************************************************/
+int sbts2050_uc_get_status(struct sbts2050_power_status *status)
+{
+ struct msgb *msg;
+ const struct ucinfo info = {
+ .id = SBTS2050_PWR_STATUS,
+ };
+ rsppkt_t *response;
+
+ memset(status, 0, sizeof(*status));
+ msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info);
+
+ if (msg == NULL) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Error requesting power status.\n");
+ return -1;
+ }
+
+ response = (rsppkt_t *)msg->data;
+
+ status->main_supply_current = response->rsp.pwrGetStatus.u8MainSupplyA / 64.f;
+
+ status->master_enabled = response->rsp.pwrGetStatus.u1MasterEn;
+ status->master_voltage = response->rsp.pwrGetStatus.u8MasterV / 32.f;
+ status->master_current = response->rsp.pwrGetStatus.u8MasterA / 64.f;;
+
+ status->slave_enabled = response->rsp.pwrGetStatus.u1SlaveEn;
+ status->slave_voltage = response->rsp.pwrGetStatus.u8SlaveV / 32.f;
+ status->slave_current = response->rsp.pwrGetStatus.u8SlaveA / 64.f;
+
+ status->pa_enabled = response->rsp.pwrGetStatus.u1PwrAmpEn;
+ status->pa_voltage = response->rsp.pwrGetStatus.u8PwrAmpV / 4.f;
+ status->pa_current = response->rsp.pwrGetStatus.u8PwrAmpA / 64.f;
+
+ status->pa_bias_voltage = response->rsp.pwrGetStatus.u8PwrAmpBiasV / 16.f;
+
+ msgb_free(msg);
+ return 0;
+}
+
+/**********************************************************************
+ * Uc Power Switching handling
+ *********************************************************************/
+int sbts2050_uc_set_power(int pmaster, int pslave, int ppa)
+{
+ struct msgb *msg;
+ const struct ucinfo info = {
+ .id = SBTS2050_PWR_RQT,
+ .master = pmaster,
+ .slave = pslave,
+ .pa = ppa
+ };
+
+ msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info);
+
+ if (msg == NULL) {
+ LOGP(DTEMP, LOGL_ERROR, "Error switching off some unit.\n");
+ return -1;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Switch off/on success:\n"
+ "MASTER %s\n"
+ "SLAVE %s\n"
+ "PA %s\n",
+ pmaster ? "ON" : "OFF",
+ pslave ? "ON" : "OFF",
+ ppa ? "ON" : "OFF");
+
+ msgb_free(msg);
+ return 0;
+}
+
+/**********************************************************************
+ * Uc temperature handling
+ *********************************************************************/
+int sbts2050_uc_check_temp(int *temp_pa, int *temp_board)
+{
+ rsppkt_t *response;
+ struct msgb *msg;
+ const struct ucinfo info = {
+ .id = SBTS2050_TEMP_RQT,
+ };
+
+ msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info);
+
+ if (msg == NULL) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n");
+ return -1;
+ }
+
+ response = (rsppkt_t *)msg->data;
+
+ *temp_board = response->rsp.tempGet.i8BrdTemp;
+ *temp_pa = response->rsp.tempGet.i8PaTemp;
+
+ LOGP(DTEMP, LOGL_DEBUG, "Temperature Board: %+3d C, "
+ "Tempeture PA: %+3d C\n",
+ response->rsp.tempGet.i8BrdTemp,
+ response->rsp.tempGet.i8PaTemp);
+ msgb_free(msg);
+ return 0;
+}
+
+void sbts2050_uc_initialize(void)
+{
+ if (!is_sbts2050())
+ return;
+
+ ucontrol0.fd = osmo_serial_init(ucontrol0.path, 115200);
+ if (ucontrol0.fd < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to open the serial interface\n");
+ return;
+ }
+
+ if (is_sbts2050_master()) {
+ LOGP(DTEMP, LOGL_NOTICE, "Going to enable the PA.\n");
+ sbts2050_uc_set_pa_power(1);
+ }
+}
+
+int sbts2050_uc_set_pa_power(int on_off)
+{
+ struct sbts2050_power_status status;
+ if (sbts2050_uc_get_status(&status) != 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Failed to read current power status.\n");
+ return -1;
+ }
+
+ return sbts2050_uc_set_power(status.master_enabled, status.slave_enabled, on_off);
+}
+
+int sbts2050_uc_set_slave_power(int on_off)
+{
+ struct sbts2050_power_status status;
+ if (sbts2050_uc_get_status(&status) != 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Failed to read current power status.\n");
+ return -1;
+ }
+
+ return sbts2050_uc_set_power(
+ status.master_enabled,
+ on_off,
+ status.pa_enabled);
+}
+#else
+void sbts2050_uc_initialize(void)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "sysmoBTS2050 was not enabled at compile time.\n");
+}
+
+int sbts2050_uc_check_temp(int *temp_pa, int *temp_board)
+{
+ LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without temp support.\n");
+ *temp_pa = *temp_board = 99999;
+ return -1;
+}
+
+int sbts2050_uc_get_status(struct sbts2050_power_status *status)
+{
+ memset(status, 0, sizeof(*status));
+ LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without status support.\n");
+ return -1;
+}
+
+int sbts2050_uc_set_pa_power(int on_off)
+{
+ LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without PA support.\n");
+ return -1;
+}
+
+int sbts2050_uc_set_slave_power(int on_off)
+{
+ LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without UC support.\n");
+ return -1;
+}
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c
new file mode 100644
index 00000000..b0b5edd8
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c
@@ -0,0 +1,547 @@
+/* OCXO/TCXO calibration control for SysmoBTS management daemon */
+
+/*
+ * (C) 2014,2015 by Holger Hans Peter Freyther
+ * (C) 2014 by Harald Welte for the IPA code from the oml router
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/sysmobts_mgr.h"
+#include "misc/sysmobts_misc.h"
+#include "osmo-bts/msg_utils.h"
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/ipa.h>
+
+static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop);
+static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int reason);
+static void request_clock_reset(struct sysmobts_mgr_instance *mgr);
+static void bts_updown_cb(struct ipa_client_conn *link, int up);
+
+enum calib_state {
+ CALIB_INITIAL,
+ CALIB_GPS_WAIT_FOR_FIX,
+ CALIB_CTR_RESET,
+ CALIB_CTR_WAIT,
+ CALIB_COR_SET,
+};
+
+enum calib_result {
+ CALIB_FAIL_START,
+ CALIB_FAIL_GPS,
+ CALIB_FAIL_CTRL,
+ CALIB_SUCESS,
+};
+
+static inline int compat_gps_read(struct gps_data_t *data)
+{
+/* API break in gpsd 6bba8b329fc7687b15863d30471d5af402467802 */
+#if GPSD_API_MAJOR_VERSION >= 7 && GPSD_API_MINOR_VERSION >= 0
+ return gps_read(data, NULL, 0);
+#else
+ return gps_read(data);
+#endif
+}
+
+static void calib_loop_run(void *_data)
+{
+ int rc;
+ struct sysmobts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n");
+ rc = calib_run(mgr, 1);
+ if (rc != 0)
+ calib_state_reset(mgr, CALIB_FAIL_START);
+}
+
+static void mgr_gps_close(struct sysmobts_mgr_instance *mgr)
+{
+ if (!mgr->calib.gps_open)
+ return;
+
+ osmo_timer_del(&mgr->calib.fix_timeout);
+
+ osmo_fd_unregister(&mgr->calib.gpsfd);
+ gps_close(&mgr->calib.gpsdata);
+ memset(&mgr->calib.gpsdata, 0, sizeof(mgr->calib.gpsdata));
+ mgr->calib.gps_open = 0;
+}
+
+static void mgr_gps_checkfix(struct sysmobts_mgr_instance *mgr)
+{
+ struct gps_data_t *data = &mgr->calib.gpsdata;
+
+ /* No 2D fix yet */
+ if (data->fix.mode < MODE_2D) {
+ LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n",
+ data->fix.mode);
+ return;
+ }
+
+ /* The trimble driver is broken...add some sanity checking */
+ if (data->satellites_used < 1) {
+ LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n",
+ data->satellites_used);
+ return;
+ }
+
+ LOGP(DCALIB, LOGL_NOTICE, "Got a GPS fix continuing.\n");
+ osmo_timer_del(&mgr->calib.fix_timeout);
+ mgr_gps_close(mgr);
+ request_clock_reset(mgr);
+}
+
+static int mgr_gps_read(struct osmo_fd *fd, unsigned int what)
+{
+ int rc;
+ struct sysmobts_mgr_instance *mgr = fd->data;
+ rc = compat_gps_read(&mgr->calib.gpsdata);
+ if (rc == -1) {
+ LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPS);
+ return -1;
+ }
+
+ if (rc > 0)
+ mgr_gps_checkfix(mgr);
+ return 0;
+}
+
+static void mgr_gps_fix_timeout(void *_data)
+{
+ struct sysmobts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPRS fix.\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPS);
+}
+
+static void mgr_gps_open(struct sysmobts_mgr_instance *mgr)
+{
+ int rc;
+
+ rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->calib.gpsdata);
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_GPS);
+ return;
+ }
+
+ mgr->calib.gps_open = 1;
+ gps_stream(&mgr->calib.gpsdata, WATCH_ENABLE, NULL);
+
+ mgr->calib.gpsfd.data = mgr;
+ mgr->calib.gpsfd.cb = mgr_gps_read;
+ mgr->calib.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT;
+ mgr->calib.gpsfd.fd = mgr->calib.gpsdata.gps_fd;
+ if (osmo_fd_register(&mgr->calib.gpsfd) < 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPS);
+ }
+
+ mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX;
+ mgr->calib.fix_timeout.data = mgr;
+ mgr->calib.fix_timeout.cb = mgr_gps_fix_timeout;
+ osmo_timer_schedule(&mgr->calib.fix_timeout, 60, 0);
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Opened the GPSD connection waiting for fix: %d\n",
+ mgr->calib.gpsfd.fd);
+}
+
+static void send_ctrl_cmd(struct sysmobts_mgr_instance *mgr,
+ struct msgb *msg)
+{
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+ ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+ ipa_client_conn_send(mgr->calib.bts_conn, msg);
+}
+
+static void send_set_ctrl_cmd_int(struct sysmobts_mgr_instance *mgr,
+ const char *key, const int val)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
+ ret = snprintf((char *) msg->data, 4096, "SET %u %s %d",
+ mgr->calib.last_seqno++, key, val);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void send_set_ctrl_cmd(struct sysmobts_mgr_instance *mgr,
+ const char *key, const char *val)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
+ ret = snprintf((char *) msg->data, 4096, "SET %u %s %s",
+ mgr->calib.last_seqno++, key, val);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void send_get_ctrl_cmd(struct sysmobts_mgr_instance *mgr,
+ const char *key)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL GET");
+ ret = snprintf((char *) msg->data, 4096, "GET %u %s",
+ mgr->calib.last_seqno++, key);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop)
+{
+ if (!mgr->calib.is_up) {
+ LOGP(DCALIB, LOGL_ERROR, "Control interface not connected.\n");
+ return -1;
+ }
+
+ if (mgr->calib.state != CALIB_INITIAL) {
+ LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
+ return -2;
+ }
+
+ mgr->calib.calib_from_loop = from_loop;
+
+ /* From now on everything will be handled from the failure */
+ mgr->calib.initial_calib_started = 1;
+ mgr_gps_open(mgr);
+ return 0;
+}
+
+int sysmobts_mgr_calib_run(struct sysmobts_mgr_instance *mgr)
+{
+ return calib_run(mgr, 0);
+}
+
+static void request_clock_reset(struct sysmobts_mgr_instance *mgr)
+{
+ send_set_ctrl_cmd(mgr, "trx.0.clock-info", "1");
+ mgr->calib.state = CALIB_CTR_RESET;
+}
+
+static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int outcome)
+{
+ if (mgr->calib.calib_from_loop) {
+ /*
+ * In case of success calibrate in two hours again
+ * and in case of a failure in some minutes.
+ */
+ int timeout = 2 * 60 * 60;
+ if (outcome != CALIB_SUCESS)
+ timeout = 5 * 60;
+
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0);
+ }
+
+ mgr->calib.state = CALIB_INITIAL;
+ osmo_timer_del(&mgr->calib.timer);
+
+ mgr_gps_close(mgr);
+}
+
+static void calib_get_clock_err_cb(void *_data)
+{
+ struct sysmobts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_DEBUG,
+ "Requesting current clock-info.\n");
+ send_get_ctrl_cmd(mgr, "trx.0.clock-info");
+}
+
+static void handle_ctrl_reset_resp(
+ struct sysmobts_mgr_instance *mgr,
+ struct ctrl_cmd *cmd)
+{
+ if (strcmp(cmd->variable, "trx.0.clock-info") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected variable: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ if (strcmp(cmd->reply, "success") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected reply: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ mgr->calib.state = CALIB_CTR_WAIT;
+ mgr->calib.timer.cb = calib_get_clock_err_cb;
+ mgr->calib.timer.data = mgr;
+ osmo_timer_schedule(&mgr->calib.timer, 60, 0);
+ LOGP(DCALIB, LOGL_DEBUG,
+ "Reset the calibration counter. Waiting 60 seconds.\n");
+}
+
+static void handle_ctrl_get_resp(
+ struct sysmobts_mgr_instance *mgr,
+ struct ctrl_cmd *cmd)
+{
+ char *saveptr = NULL;
+ char *clk_cur;
+ char *clk_src;
+ char *cal_err;
+ char *cal_res;
+ char *cal_src;
+ int cal_err_int;
+
+ if (strcmp(cmd->variable, "trx.0.clock-info") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected variable: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ clk_cur = strtok_r(cmd->reply, ",", &saveptr);
+ clk_src = strtok_r(NULL, ",", &saveptr);
+ cal_err = strtok_r(NULL, ",", &saveptr);
+ cal_res = strtok_r(NULL, ",", &saveptr);
+ cal_src = strtok_r(NULL, ",", &saveptr);
+
+ if (!clk_cur || !clk_src || !cal_err || !cal_res || !cal_src) {
+ LOGP(DCALIB, LOGL_ERROR, "Parse error on clock-info reply\n");
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+
+ }
+ cal_err_int = atoi(cal_err);
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Calibration CUR(%s) SRC(%s) ERR(%s/%d) RES(%s) SRC(%s)\n",
+ clk_cur, clk_src, cal_err, cal_err_int, cal_res, cal_src);
+
+ if (strcmp(cal_res, "0") == 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Invalid clock resolution. Giving up\n");
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ /* Now we can finally set the new value */
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Going to apply %d as new clock correction.\n",
+ -cal_err_int);
+ send_set_ctrl_cmd_int(mgr, "trx.0.clock-correction", -cal_err_int);
+ mgr->calib.state = CALIB_COR_SET;
+}
+
+static void handle_ctrl_set_cor(
+ struct sysmobts_mgr_instance *mgr,
+ struct ctrl_cmd *cmd)
+{
+ if (strcmp(cmd->variable, "trx.0.clock-correction") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected variable: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ if (strcmp(cmd->reply, "success") != 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unexpected reply: %s\n", cmd->variable);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ return;
+ }
+
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Calibration process completed\n");
+ calib_state_reset(mgr, CALIB_SUCESS);
+}
+
+static void handle_ctrl(struct sysmobts_mgr_instance *mgr, struct msgb *msg)
+{
+ struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg);
+ if (!cmd) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n");
+ return;
+ }
+
+ switch (cmd->type) {
+ case CTRL_TYPE_GET_REPLY:
+ switch (mgr->calib.state) {
+ case CALIB_CTR_WAIT:
+ handle_ctrl_get_resp(mgr, cmd);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unhandled response in state: %d %s/%s\n",
+ mgr->calib.state, cmd->variable, cmd->reply);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ break;
+ };
+ break;
+ case CTRL_TYPE_SET_REPLY:
+ switch (mgr->calib.state) {
+ case CALIB_CTR_RESET:
+ handle_ctrl_reset_resp(mgr, cmd);
+ break;
+ case CALIB_COR_SET:
+ handle_ctrl_set_cor(mgr, cmd);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unhandled response in state: %d %s/%s\n",
+ mgr->calib.state, cmd->variable, cmd->reply);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ break;
+ };
+ break;
+ case CTRL_TYPE_TRAP:
+ /* ignore any form of trap */
+ break;
+ default:
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unhandled CTRL response: %d. Resetting state\n",
+ cmd->type);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ break;
+ }
+
+ talloc_free(cmd);
+}
+
+/* Schedule a connect towards the BTS */
+static void schedule_bts_connect(struct sysmobts_mgr_instance *mgr)
+{
+ DEBUGP(DLCTRL, "Scheduling BTS connect\n");
+ osmo_timer_schedule(&mgr->calib.recon_timer, 1, 0);
+}
+
+/* BTS re-connect timer call-back */
+static void bts_recon_timer_cb(void *data)
+{
+ int rc;
+ struct sysmobts_mgr_instance *mgr = data;
+
+ /* The connection failures are to be expected during boot */
+ mgr->calib.bts_conn->ofd->when |= BSC_FD_WRITE;
+ rc = ipa_client_conn_open(mgr->calib.bts_conn);
+ if (rc < 0) {
+ LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n");
+ schedule_bts_connect(mgr);
+ }
+}
+
+static int bts_read_cb(struct ipa_client_conn *link, struct msgb *msg)
+{
+ int rc;
+ struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg);
+ struct ipaccess_head_ext *hh_ext;
+
+ DEBUGP(DCALIB, "Received data from BTS: %s\n",
+ osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+
+ /* regular message handling */
+ rc = msg_verify_ipa_structure(msg);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Invalid IPA message from BTS (rc=%d)\n", rc);
+ goto err;
+ }
+
+ switch (hh->proto) {
+ case IPAC_PROTO_IPACCESS:
+ /* handle the core IPA CCM messages in libosmoabis */
+ ipa_ccm_rcvmsg_bts_base(msg, link->ofd);
+ msgb_free(msg);
+ break;
+ case IPAC_PROTO_OSMO:
+ hh_ext = (struct ipaccess_head_ext *) hh->data;
+ switch (hh_ext->proto) {
+ case IPAC_PROTO_EXT_CTRL:
+ handle_ctrl(link->data, msg);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Unhandled osmo ID %u from BTS\n", hh_ext->proto);
+ };
+ msgb_free(msg);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Unhandled stream ID %u from BTS\n", hh->proto);
+ msgb_free(msg);
+ break;
+ }
+
+ return 0;
+err:
+ msgb_free(msg);
+ return -1;
+}
+
+/* link to BSC has gone up or down */
+static void bts_updown_cb(struct ipa_client_conn *link, int up)
+{
+ struct sysmobts_mgr_instance *mgr = link->data;
+
+ LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down");
+
+ if (up) {
+ mgr->calib.is_up = 1;
+ mgr->calib.last_seqno = 0;
+
+ if (!mgr->calib.initial_calib_started)
+ calib_run(mgr, 1);
+ } else {
+ mgr->calib.is_up = 0;
+ schedule_bts_connect(mgr);
+ calib_state_reset(mgr, CALIB_FAIL_CTRL);
+ }
+}
+
+int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr)
+{
+ if (!is_sbts2050_master()) {
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Calib is only possible on the sysmoBTS2050 master\n");
+ return 0;
+ }
+
+ mgr->calib.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0,
+ "localhost", 4238,
+ bts_updown_cb, bts_read_cb,
+ NULL, mgr);
+ if (!mgr->calib.bts_conn) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to create IPA connection\n");
+ return -1;
+ }
+
+ mgr->calib.recon_timer.cb = bts_recon_timer_cb;
+ mgr->calib.recon_timer.data = mgr;
+ schedule_bts_connect(mgr);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c
new file mode 100644
index 00000000..48a03124
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c
@@ -0,0 +1,186 @@
+/* NetworkListen for SysmoBTS management daemon */
+
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/sysmobts_mgr.h"
+#include "misc/sysmobts_misc.h"
+#include "misc/sysmobts_nl.h"
+#include "misc/sysmobts_par.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <arpa/inet.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <string.h>
+
+static struct osmo_fd nl_fd;
+
+/*
+ * The TLV structure in IPA messages in UDP packages is a bit
+ * weird. First the header appears to have an extra NULL byte
+ * and second the L16 of the L16TV needs to include +1 for the
+ * tag. The default msgb/tlv and libosmo-abis routines do not
+ * provide this.
+ */
+
+static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto)
+{
+ struct ipaccess_head *hh;
+
+ /* prepend the ip.access header */
+ hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1);
+ hh->len = htons(msg->len - sizeof(*hh) - 1);
+ hh->proto = proto;
+}
+
+static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag,
+ const uint8_t *val)
+{
+ uint8_t *buf = msgb_put(msg, len + 2 + 1);
+
+ *buf++ = (len + 1) >> 8;
+ *buf++ = (len + 1) & 0xff;
+ *buf++ = tag;
+ memcpy(buf, val, len);
+}
+
+/*
+ * We don't look at the content of the request yet and lie
+ * about most of the responses.
+ */
+static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd,
+ uint8_t *data, size_t len)
+{
+ static int fetched_info = 0;
+ static char mac_str[20] = {0, };
+ static char *model_name;
+ static char ser_str[20] = {0, };
+
+ struct sockaddr_in loc_addr;
+ int rc;
+ char loc_ip[INET_ADDRSTRLEN];
+ struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response");
+ if (!msg) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n");
+ return;
+ }
+
+ if (!fetched_info) {
+ uint8_t mac[6];
+ int serno;
+
+ /* fetch the MAC */
+ sysmobts_par_get_buf(SYSMOBTS_PAR_MAC, mac, sizeof(mac));
+ snprintf(mac_str, sizeof(mac_str), "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
+ mac[0], mac[1], mac[2],
+ mac[3], mac[4], mac[5]);
+
+ /* fetch the serial number */
+ sysmobts_par_get_int(SYSMOBTS_PAR_SERNR, &serno);
+ snprintf(ser_str, sizeof(ser_str), "%d", serno);
+
+ /* fetch the model and trx number */
+ model_name = sysmobts_model(sysmobts_bts_type(), sysmobts_trx_number());
+ fetched_info = 1;
+ }
+
+ if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n");
+ return;
+ }
+
+ msgb_put_u8(msg, IPAC_MSGT_ID_RESP);
+
+ /* append MAC addr */
+ quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str);
+
+ /* append ip address */
+ inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip));
+ quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip);
+
+ /* append the serial number */
+ quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str);
+
+ /* abuse some flags */
+ quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name);
+
+ /* ip.access nanoBTS would reply to port==3006 */
+ ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS);
+ rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src));
+ if (rc != msg->len)
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to send with rc(%d) errno(%d)\n", rc, errno);
+}
+
+static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what)
+{
+ uint8_t data[2048];
+ char src[INET_ADDRSTRLEN];
+ struct sockaddr_in addr = {};
+ socklen_t len = sizeof(addr);
+ int rc;
+
+ rc = recvfrom(fd->fd, data, sizeof(data), 0,
+ (struct sockaddr *) &addr, &len);
+ if (rc <= 0) {
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to read from socket errno(%d)\n", errno);
+ return -1;
+ }
+
+ LOGP(DFIND, LOGL_DEBUG,
+ "Received request from: %s size %d\n",
+ inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc);
+
+ if (rc < 6)
+ return 0;
+
+ if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET)
+ return 0;
+
+ respond_to(&addr, fd, data + 6, rc - 6);
+ return 0;
+}
+
+int sysmobts_mgr_nl_init(void)
+{
+ int rc;
+
+ nl_fd.cb = ipaccess_bcast;
+ rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ "0.0.0.0", 3006, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ perror("Socket creation");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c
new file mode 100644
index 00000000..1be56ac2
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c
@@ -0,0 +1,321 @@
+/* Temperature control for SysmoBTS management daemon */
+
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/sysmobts_mgr.h"
+#include "misc/sysmobts_misc.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include <stdlib.h>
+
+static struct sysmobts_mgr_instance *s_mgr;
+static struct osmo_timer_list temp_ctrl_timer;
+
+static const struct value_string state_names[] = {
+ { STATE_NORMAL, "NORMAL" },
+ { STATE_WARNING_HYST, "WARNING (HYST)" },
+ { STATE_WARNING, "WARNING" },
+ { STATE_CRITICAL, "CRITICAL" },
+ { 0, NULL }
+};
+
+const char *sysmobts_mgr_temp_get_state(enum sysmobts_temp_state state)
+{
+ return get_value_string(state_names, state);
+}
+
+static int next_state(enum sysmobts_temp_state current_state, int critical, int warning)
+{
+ int next_state = -1;
+ switch (current_state) {
+ case STATE_NORMAL:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ break;
+ case STATE_WARNING_HYST:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ else
+ next_state = STATE_NORMAL;
+ break;
+ case STATE_WARNING:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (!warning)
+ next_state = STATE_WARNING_HYST;
+ break;
+ case STATE_CRITICAL:
+ if (!critical && !warning)
+ next_state = STATE_WARNING;
+ break;
+ };
+
+ return next_state;
+}
+
+static void handle_normal_actions(int actions)
+{
+ /* switch off the PA */
+ if (actions & TEMP_ACT_NORM_PA_ON) {
+ if (!is_sbts2050()) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "PA can only be switched-on on the master\n");
+ } else if (sbts2050_uc_set_pa_power(1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the PA\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched on the PA as normal action.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_NORM_SLAVE_ON) {
+ if (!is_sbts2050()) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Slave on only possible on the sysmoBTS2050\n");
+ } else if (sbts2050_uc_set_slave_power(1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the slave BTS\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched on the slave as normal action.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_NORM_BTS_SRV_ON) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch on the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl start osmo-bts-sysmo");
+ }
+}
+
+static void handle_actions(int actions)
+{
+ /* switch off the PA */
+ if (actions & TEMP_ACT_PA_OFF) {
+ if (!is_sbts2050()) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "PA can only be switched-off on the master\n");
+ } else if (sbts2050_uc_set_pa_power(0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the PA. Stop BTS?\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the PA due temperature.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_SLAVE_OFF) {
+ if (!is_sbts2050()) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Slave off only possible on the sysmoBTS2050\n");
+ } else if (sbts2050_uc_set_slave_power(0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the slave BTS\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the slave due temperature\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_BTS_SRV_OFF) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch off the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl stop osmo-bts-sysmo");
+ }
+}
+
+/**
+ * Go back to normal! Depending on the configuration execute the normal
+ * actions that could (start to) undo everything we did in the other
+ * states. What is still missing is the power increase/decrease depending
+ * on the state. E.g. starting from WARNING_HYST we might want to slowly
+ * ramp up the output power again.
+ */
+static void execute_normal_act(struct sysmobts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System is back to normal temperature.\n");
+ handle_normal_actions(manager->action_norm);
+}
+
+static void execute_warning_act(struct sysmobts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached temperature warning.\n");
+ handle_actions(manager->action_warn);
+}
+
+static void execute_critical_act(struct sysmobts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n");
+ handle_actions(manager->action_crit);
+}
+
+static void sysmobts_mgr_temp_handle(struct sysmobts_mgr_instance *manager,
+ struct ctrl_connection *ctrl, int critical,
+ int warning)
+{
+ int new_state = next_state(manager->state, critical, warning);
+ struct ctrl_cmd *rep;
+ char *oml_alert = NULL;
+
+ /* Nothing changed */
+ if (new_state < 0)
+ return;
+
+ LOGP(DTEMP, LOGL_NOTICE, "Moving from state %s to %s.\n",
+ get_value_string(state_names, manager->state),
+ get_value_string(state_names, new_state));
+ manager->state = new_state;
+ switch (manager->state) {
+ case STATE_NORMAL:
+ execute_normal_act(manager);
+ break;
+ case STATE_WARNING_HYST:
+ /* do nothing? Maybe start to increase transmit power? */
+ break;
+ case STATE_WARNING:
+ execute_warning_act(manager);
+ oml_alert = "Temperature Warning";
+ break;
+ case STATE_CRITICAL:
+ execute_critical_act(manager);
+ oml_alert = "Temperature Critical";
+ break;
+ };
+
+ if (!oml_alert)
+ return;
+
+ rep = ctrl_cmd_create(tall_mgr_ctx, CTRL_TYPE_SET);
+ if (!rep) {
+ LOGP(DTEMP, LOGL_ERROR, "OML alert creation failed for %s.\n",
+ oml_alert);
+ return;
+ }
+
+ rep->id = talloc_asprintf(rep, "%d", rand());
+ rep->variable = "oml-alert";
+ rep->value = oml_alert;
+ LOGP(DTEMP, LOGL_ERROR, "OML alert sent: %d\n",
+ ctrl_cmd_send(&ctrl->write_queue, rep));
+ talloc_free(rep);
+}
+
+static void temp_ctrl_check(struct ctrl_connection *ctrl)
+{
+ int rc;
+ int warn_thresh_passed = 0;
+ int crit_thresh_passed = 0;
+
+ LOGP(DTEMP, LOGL_DEBUG, "Going to check the temperature.\n");
+
+ /* Read the current digital temperature */
+ rc = sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL, SYSMOBTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the digital temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->digital_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->digital_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "Digital temperature is: %d\n", temp);
+ }
+
+ /* Read the current RF temperature */
+ rc = sysmobts_temp_get(SYSMOBTS_TEMP_RF, SYSMOBTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the RF temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->rf_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->rf_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "RF temperature is: %d\n", temp);
+ }
+
+ if (is_sbts2050()) {
+ int temp_pa, temp_board;
+
+ rc = sbts2050_uc_check_temp(&temp_pa, &temp_board);
+ if (rc != 0) {
+ /* XXX what do here? */
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the temperature! Reboot?!\n");
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ } else {
+ LOGP(DTEMP, LOGL_DEBUG, "SBTS2050 board(%d) PA(%d)\n",
+ temp_board, temp_pa);
+ if (temp_pa > s_mgr->pa_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp_pa > s_mgr->pa_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ if (temp_board > s_mgr->board_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp_board > s_mgr->board_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ }
+ }
+
+ sysmobts_mgr_temp_handle(s_mgr, ctrl, crit_thresh_passed,
+ warn_thresh_passed);
+}
+
+static void temp_ctrl_check_cb(void *ctrl)
+{
+ temp_ctrl_check(ctrl);
+ /* Check every two minutes? XXX make it configurable! */
+ osmo_timer_schedule(&temp_ctrl_timer, 2 * 60, 0);
+}
+
+int sysmobts_mgr_temp_init(struct sysmobts_mgr_instance *mgr,
+ struct ctrl_connection *ctrl)
+{
+ s_mgr = mgr;
+ temp_ctrl_timer.cb = temp_ctrl_check_cb;
+ temp_ctrl_timer.data = ctrl;
+ temp_ctrl_check_cb(ctrl);
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c
new file mode 100644
index 00000000..444ee7c3
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c
@@ -0,0 +1,531 @@
+/* (C) 2014 by sysmocom - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * Author: Alvaro Neira Ayuso <anayuso@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/logging.h>
+
+#include "sysmobts_misc.h"
+#include "sysmobts_mgr.h"
+#include "btsconfig.h"
+
+static struct sysmobts_mgr_instance *s_mgr;
+
+static const char copyright[] =
+ "(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n"
+ "(C) 2014 by Holger Hans Peter Freyther\r\n"
+ "License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static int go_to_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case MGR_NODE:
+ vty->node = CONFIG_NODE;
+ break;
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_RF_NODE:
+ case LIMIT_DIGITAL_NODE:
+ case LIMIT_BOARD_NODE:
+ case LIMIT_PA_NODE:
+ vty->node = MGR_NODE;
+ break;
+ default:
+ vty->node = CONFIG_NODE;
+ }
+ return vty->node;
+}
+
+static int is_config_node(struct vty *vty, int node)
+{
+ switch (node) {
+ case MGR_NODE:
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_RF_NODE:
+ case LIMIT_DIGITAL_NODE:
+ case LIMIT_BOARD_NODE:
+ case LIMIT_PA_NODE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static struct vty_app_info vty_info = {
+ .name = "sysmobts-mgr",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = go_to_parent,
+ .is_config_node = is_config_node,
+ .copyright = copyright,
+};
+
+
+#define MGR_STR "Configure sysmobts-mgr\n"
+
+static struct cmd_node mgr_node = {
+ MGR_NODE,
+ "%s(sysmobts-mgr)# ",
+ 1,
+};
+
+static struct cmd_node act_norm_node = {
+ ACT_NORM_NODE,
+ "%s(action-normal)# ",
+ 1,
+};
+
+static struct cmd_node act_warn_node = {
+ ACT_WARN_NODE,
+ "%s(action-warn)# ",
+ 1,
+};
+
+static struct cmd_node act_crit_node = {
+ ACT_CRIT_NODE,
+ "%s(action-critical)# ",
+ 1,
+};
+
+static struct cmd_node limit_rf_node = {
+ LIMIT_RF_NODE,
+ "%s(limit-rf)# ",
+ 1,
+};
+
+static struct cmd_node limit_digital_node = {
+ LIMIT_DIGITAL_NODE,
+ "%s(limit-digital)# ",
+ 1,
+};
+
+static struct cmd_node limit_board_node = {
+ LIMIT_BOARD_NODE,
+ "%s(limit-board)# ",
+ 1,
+};
+
+static struct cmd_node limit_pa_node = {
+ LIMIT_PA_NODE,
+ "%s(limit-pa)# ",
+ 1,
+};
+
+DEFUN(cfg_mgr, cfg_mgr_cmd,
+ "sysmobts-mgr",
+ MGR_STR)
+{
+ vty->node = MGR_NODE;
+ return CMD_SUCCESS;
+}
+
+static void write_temp_limit(struct vty *vty, const char *name,
+ struct sysmobts_temp_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning %d%s",
+ limit->thresh_warn, VTY_NEWLINE);
+ vty_out(vty, " threshold critical %d%s",
+ limit->thresh_crit, VTY_NEWLINE);
+}
+
+static void write_norm_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " %spa-on%s",
+ (actions & TEMP_ACT_NORM_PA_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-on%s",
+ (actions & TEMP_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sslave-on%s",
+ (actions & TEMP_ACT_NORM_SLAVE_ON) ? "" : "no ", VTY_NEWLINE);
+}
+
+static void write_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+#if 0
+ vty_out(vty, " %spower-control%s",
+ (actions & TEMP_ACT_PWR_CONTRL) ? "" : "no ", VTY_NEWLINE);
+
+ /* only on the sysmobts 2050 */
+ vty_out(vty, " %smaster-off%s",
+ (actions & TEMP_ACT_MASTER_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sslave-off%s",
+ (actions & TEMP_ACT_MASTER_OFF) ? "" : "no ", VTY_NEWLINE);
+#endif
+ vty_out(vty, " %spa-off%s",
+ (actions & TEMP_ACT_PA_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-off%s",
+ (actions & TEMP_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sslave-off%s",
+ (actions & TEMP_ACT_SLAVE_OFF) ? "" : "no ", VTY_NEWLINE);
+}
+
+static int config_write_mgr(struct vty *vty)
+{
+ vty_out(vty, "sysmobts-mgr%s", VTY_NEWLINE);
+
+ write_temp_limit(vty, "limits rf", &s_mgr->rf_limit);
+ write_temp_limit(vty, "limits digital", &s_mgr->digital_limit);
+ write_temp_limit(vty, "limits board", &s_mgr->board_limit);
+ write_temp_limit(vty, "limits pa", &s_mgr->pa_limit);
+
+ write_norm_action(vty, "actions normal", s_mgr->action_norm);
+ write_action(vty, "actions warn", s_mgr->action_warn);
+ write_action(vty, "actions critical", s_mgr->action_crit);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_dummy(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+#define CFG_LIMIT(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT(rf, "RF\n", LIMIT_RF_NODE, rf_limit)
+CFG_LIMIT(digital, "Digital\n", LIMIT_DIGITAL_NODE, digital_limit)
+CFG_LIMIT(board, "Board\n", LIMIT_BOARD_NODE, board_limit)
+CFG_LIMIT(pa, "Power Amplifier\n", LIMIT_PA_NODE, pa_limit)
+#undef CFG_LIMIT
+
+DEFUN(cfg_limit_warning, cfg_thresh_warning_cmd,
+ "threshold warning <0-200>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct sysmobts_temp_limit *limit = vty->index;
+ limit->thresh_warn = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_crit, cfg_thresh_crit_cmd,
+ "threshold critical <0-200>",
+ "Threshold to reach\n" "Severe level\n" "Range\n")
+{
+ struct sysmobts_temp_limit *limit = vty->index;
+ limit->thresh_crit = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+#define CFG_ACTION(name, expl, switch_to, variable) \
+DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \
+ "actions " #name, \
+ "Configure Actions\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->variable; \
+ return CMD_SUCCESS; \
+}
+CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm)
+CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn)
+CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit)
+#undef CFG_ACTION
+
+DEFUN(cfg_action_pa_on, cfg_action_pa_on_cmd,
+ "pa-on",
+ "Switch the Power Amplifier on\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_NORM_PA_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa_on, cfg_no_action_pa_on_cmd,
+ "no pa-on",
+ NO_STR "Switch the Power Amplifier on\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_NORM_PA_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd,
+ "bts-service-on",
+ "Start the systemd osmo-bts-sysmo.service\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd,
+ "no bts-service-on",
+ NO_STR "Start the systemd osmo-bts-sysmo.service\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_slave_on, cfg_action_slave_on_cmd,
+ "slave-on",
+ "Power-on secondary device on sysmoBTS2050\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_NORM_SLAVE_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_slave_on, cfg_no_action_slave_on_cmd,
+ "no slave-on",
+ NO_STR "Power-on secondary device on sysmoBTS2050\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_NORM_SLAVE_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa_off, cfg_action_pa_off_cmd,
+ "pa-off",
+ "Switch the Power Amplifier off\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_PA_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa_off, cfg_no_action_pa_off_cmd,
+ "no pa-off",
+ NO_STR "Do not switch off the Power Amplifier\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_PA_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd,
+ "bts-service-off",
+ "Stop the systemd osmo-bts-sysmo.service\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd,
+ "no bts-service-off",
+ NO_STR "Stop the systemd osmo-bts-sysmo.service\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_slave_off, cfg_action_slave_off_cmd,
+ "slave-off",
+ "Power-off secondary device on sysmoBTS2050\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_SLAVE_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_slave_off, cfg_no_action_slave_off_cmd,
+ "no slave-off",
+ NO_STR "Power-off secondary device on sysmoBTS2050\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_SLAVE_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_mgr, show_mgr_cmd, "show manager",
+ SHOW_STR "Display information about the manager")
+{
+ vty_out(vty, "BTS Control Interface: %s%s",
+ s_mgr->calib.is_up ? "connected" : "disconnected", VTY_NEWLINE);
+ vty_out(vty, "Temperature control state: %s%s",
+ sysmobts_mgr_temp_get_state(s_mgr->state), VTY_NEWLINE);
+ vty_out(vty, "Current Temperatures%s", VTY_NEWLINE);
+ vty_out(vty, " Digital: %f Celcius%s",
+ sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL,
+ SYSMOBTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " RF: %f Celcius%s",
+ sysmobts_temp_get(SYSMOBTS_TEMP_RF,
+ SYSMOBTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ if (is_sbts2050()) {
+ int temp_pa, temp_board;
+ struct sbts2050_power_status status;
+
+ vty_out(vty, " sysmoBTS 2050 is %s%s",
+ is_sbts2050_master() ? "master" : "slave", VTY_NEWLINE);
+
+ sbts2050_uc_check_temp(&temp_pa, &temp_board);
+ vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_pa, VTY_NEWLINE);
+ vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_board, VTY_NEWLINE);
+
+ sbts2050_uc_get_status(&status);
+ vty_out(vty, "Power Status%s", VTY_NEWLINE);
+ vty_out(vty, " Main Supply :(ON) [(24.00)Vdc, %4.2f A]%s",
+ status.main_supply_current, VTY_NEWLINE);
+ vty_out(vty, " Master SF : %s [%6.2f Vdc, %4.2f A]%s",
+ status.master_enabled ? "ON " : "OFF",
+ status.master_voltage, status.master_current,
+ VTY_NEWLINE);
+ vty_out(vty, " Slave SF : %s [%6.2f Vdc, %4.2f A]%s",
+ status.slave_enabled ? "ON" : "OFF",
+ status.slave_voltage, status.slave_current,
+ VTY_NEWLINE);
+ vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A]%s",
+ status.pa_enabled ? "ON" : "OFF",
+ status.pa_voltage, status.pa_current,
+ VTY_NEWLINE);
+ vty_out(vty, " PA Bias : %s [%6.2f Vdc, ---- A]%s",
+ status.pa_enabled ? "ON" : "OFF",
+ status.pa_bias_voltage,
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(calibrate_trx, calibrate_trx_cmd,
+ "trx 0 calibrate-clock",
+ "Transceiver commands\n" "Transceiver 0\n"
+ "Calibrate clock against GPS PPS\n")
+{
+ if (sysmobts_mgr_calib_run(s_mgr) < 0) {
+ vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+static void register_limit(int limit)
+{
+ install_element(limit, &cfg_thresh_warning_cmd);
+ install_element(limit, &cfg_thresh_crit_cmd);
+}
+
+static void register_normal_action(int act)
+{
+ install_element(act, &cfg_action_pa_on_cmd);
+ install_element(act, &cfg_no_action_pa_on_cmd);
+ install_element(act, &cfg_action_bts_srv_on_cmd);
+ install_element(act, &cfg_no_action_bts_srv_on_cmd);
+
+ /* these only work on the sysmobts 2050 */
+ install_element(act, &cfg_action_slave_on_cmd);
+ install_element(act, &cfg_no_action_slave_on_cmd);
+}
+
+static void register_action(int act)
+{
+#if 0
+ install_element(act, &cfg_action_pwr_contrl_cmd);
+ install_element(act, &cfg_no_action_pwr_contrl_cmd);
+#endif
+ install_element(act, &cfg_action_pa_off_cmd);
+ install_element(act, &cfg_no_action_pa_off_cmd);
+ install_element(act, &cfg_action_bts_srv_off_cmd);
+ install_element(act, &cfg_no_action_bts_srv_off_cmd);
+
+ /* these only work on the sysmobts 2050 */
+ install_element(act, &cfg_action_slave_off_cmd);
+ install_element(act, &cfg_no_action_slave_off_cmd);
+}
+
+int sysmobts_mgr_vty_init(void)
+{
+ vty_init(&vty_info);
+
+ install_element_ve(&show_mgr_cmd);
+
+ install_element(ENABLE_NODE, &calibrate_trx_cmd);
+
+ install_node(&mgr_node, config_write_mgr);
+ install_element(CONFIG_NODE, &cfg_mgr_cmd);
+
+ /* install the limit nodes */
+ install_node(&limit_rf_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_rf_cmd);
+ register_limit(LIMIT_RF_NODE);
+
+ install_node(&limit_digital_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_digital_cmd);
+ register_limit(LIMIT_DIGITAL_NODE);
+
+ install_node(&limit_board_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_board_cmd);
+ register_limit(LIMIT_BOARD_NODE);
+
+ install_node(&limit_pa_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa_cmd);
+ register_limit(LIMIT_PA_NODE);
+
+ /* install the normal node */
+ install_node(&act_norm_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_normal_cmd);
+ register_normal_action(ACT_NORM_NODE);
+
+ /* install the warning and critical node */
+ install_node(&act_warn_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_warn_cmd);
+ register_action(ACT_WARN_NODE);
+
+ install_node(&act_crit_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_critical_cmd);
+ register_action(ACT_CRIT_NODE);
+
+ return 0;
+}
+
+int sysmobts_mgr_parse_config(struct sysmobts_mgr_instance *manager)
+{
+ int rc;
+
+ s_mgr = manager;
+ rc = vty_read_config_file(s_mgr->config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ s_mgr->config_file);
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.c b/src/osmo-bts-sysmo/misc/sysmobts_misc.c
new file mode 100644
index 00000000..d996d644
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.c
@@ -0,0 +1,275 @@
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+
+#include "btsconfig.h"
+#include "sysmobts_misc.h"
+#include "sysmobts_par.h"
+#include "sysmobts_mgr.h"
+
+/*********************************************************************
+ * Temperature handling
+ *********************************************************************/
+
+#define TEMP_PATH "/sys/class/hwmon/hwmon0/device/temp%u_%s"
+
+static const char *temp_type_str[_NUM_TEMP_TYPES] = {
+ [SYSMOBTS_TEMP_INPUT] = "input",
+ [SYSMOBTS_TEMP_LOWEST] = "lowest",
+ [SYSMOBTS_TEMP_HIGHEST] = "highest",
+};
+
+int sysmobts_temp_get(enum sysmobts_temp_sensor sensor,
+ enum sysmobts_temp_type type)
+{
+ char buf[PATH_MAX];
+ char tempstr[8];
+ int fd, rc;
+
+ if (sensor < SYSMOBTS_TEMP_DIGITAL ||
+ sensor > SYSMOBTS_TEMP_RF)
+ return -EINVAL;
+
+ if (type >= ARRAY_SIZE(temp_type_str))
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, TEMP_PATH, sensor, temp_type_str[type]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, tempstr, sizeof(tempstr));
+ tempstr[sizeof(tempstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+
+ close(fd);
+
+ return atoi(tempstr);
+}
+
+static const struct {
+ const char *name;
+ enum sysmobts_temp_sensor sensor;
+ enum sysmobts_par ee_par;
+} temp_data[] = {
+ {
+ .name = "digital",
+ .sensor = SYSMOBTS_TEMP_DIGITAL,
+ .ee_par = SYSMOBTS_PAR_TEMP_DIG_MAX,
+ }, {
+ .name = "rf",
+ .sensor = SYSMOBTS_TEMP_RF,
+ .ee_par = SYSMOBTS_PAR_TEMP_RF_MAX,
+ }
+};
+
+void sysmobts_check_temp(int no_eeprom_write)
+{
+ int temp_old[ARRAY_SIZE(temp_data)];
+ int temp_hi[ARRAY_SIZE(temp_data)];
+ int temp_cur[ARRAY_SIZE(temp_data)];
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(temp_data); i++) {
+ int ret;
+ rc = sysmobts_par_get_int(temp_data[i].ee_par, &ret);
+ temp_old[i] = ret * 1000;
+ temp_hi[i] = sysmobts_temp_get(temp_data[i].sensor,
+ SYSMOBTS_TEMP_HIGHEST);
+ temp_cur[i] = sysmobts_temp_get(temp_data[i].sensor,
+ SYSMOBTS_TEMP_INPUT);
+
+ if ((temp_cur[i] < 0 && temp_cur[i] > -1000) ||
+ (temp_hi[i] < 0 && temp_hi[i] > -1000)) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n");
+ return;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n",
+ temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000);
+
+ if (temp_hi[i] > temp_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "temperature: %d.%d C\n", temp_data[i].name,
+ temp_hi[i]/1000, temp_hi[i]%1000);
+
+ if (!no_eeprom_write) {
+ rc = sysmobts_par_set_int(SYSMOBTS_PAR_TEMP_DIG_MAX,
+ temp_hi[0]/1000);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max temp %d (%s)\n", temp_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+/*********************************************************************
+ * Hours handling
+ *********************************************************************/
+static time_t last_update;
+
+int sysmobts_update_hours(int no_eeprom_write)
+{
+ time_t now = time(NULL);
+ int rc, op_hrs;
+
+ /* first time after start of manager program */
+ if (last_update == 0) {
+ last_update = now;
+
+ rc = sysmobts_par_get_int(SYSMOBTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ return 0;
+ }
+
+ if (now >= last_update + 3600) {
+ rc = sysmobts_par_get_int(SYSMOBTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ /* number of hours to increase */
+ op_hrs += (now-last_update)/3600;
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ if (!no_eeprom_write) {
+ rc = sysmobts_par_set_int(SYSMOBTS_PAR_HOURS, op_hrs);
+ if (rc < 0)
+ return rc;
+ }
+
+ last_update = now;
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ * Firmware reloading
+ *********************************************************************/
+
+#define SYSMOBTS_FW_PATH "/lib/firmware"
+
+static const char *fw_names[_NUM_FW] = {
+ [SYSMOBTS_FW_FPGA] = "sysmobts-v2.bit",
+ [SYSMOBTS_FW_DSP] = "sysmobts-v2.out",
+};
+static const char *fw_devs[_NUM_FW] = {
+ [SYSMOBTS_FW_FPGA] = "/dev/fpgadl_par0",
+ [SYSMOBTS_FW_DSP] = "/dev/dspdl_dm644x_0",
+};
+
+int sysmobts_firmware_reload(enum sysmobts_firmware_type type)
+{
+ char name[PATH_MAX];
+ uint8_t buf[1024];
+ int fd_in, fd_out, rc;
+
+ if (type >= _NUM_FW)
+ return -EINVAL;
+
+ snprintf(name, sizeof(name)-1, "%s/%s",
+ SYSMOBTS_FW_PATH, fw_names[type]);
+ name[sizeof(name)-1] = '\0';
+
+ fd_in = open(name, O_RDONLY);
+ if (fd_in < 0) {
+ LOGP(DFW, LOGL_ERROR, "unable ot open firmware file %s: %s\n",
+ name, strerror(errno));
+ return fd_in;
+ }
+
+ fd_out = open(fw_devs[type], O_WRONLY);
+ if (fd_out < 0) {
+ LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n",
+ fw_devs[type], strerror(errno));
+ close(fd_in);
+ return fd_out;
+ }
+
+ while ((rc = read(fd_in, buf, sizeof(buf)))) {
+ int written;
+
+ if (rc < 0) {
+ LOGP(DFW, LOGL_ERROR, "error %d during read "
+ "from %s: %s\n", rc, name, strerror(errno));
+ close(fd_in);
+ close(fd_out);
+ return -EIO;
+ }
+
+ written = write(fd_out, buf, rc);
+ if (written < rc) {
+ LOGP(DFW, LOGL_ERROR, "short write during "
+ "fw write to %s\n", fw_devs[type]);
+ close(fd_in);
+ close(fd_out);
+ return -EIO;
+ }
+ }
+
+ close(fd_in);
+ close(fd_out);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.h b/src/osmo-bts-sysmo/misc/sysmobts_misc.h
new file mode 100644
index 00000000..06166cf6
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.h
@@ -0,0 +1,65 @@
+#ifndef _SYSMOBTS_MISC_H
+#define _SYSMOBTS_MISC_H
+
+#include <stdint.h>
+
+enum sysmobts_temp_sensor {
+ SYSMOBTS_TEMP_DIGITAL = 1,
+ SYSMOBTS_TEMP_RF = 2,
+};
+
+enum sysmobts_temp_type {
+ SYSMOBTS_TEMP_INPUT,
+ SYSMOBTS_TEMP_LOWEST,
+ SYSMOBTS_TEMP_HIGHEST,
+ _NUM_TEMP_TYPES
+};
+
+int sysmobts_temp_get(enum sysmobts_temp_sensor sensor,
+ enum sysmobts_temp_type type);
+
+void sysmobts_check_temp(int no_eeprom_write);
+
+int sysmobts_update_hours(int no_epprom_write);
+
+enum sysmobts_firmware_type {
+ SYSMOBTS_FW_FPGA,
+ SYSMOBTS_FW_DSP,
+ _NUM_FW
+};
+
+int sysmobts_firmware_reload(enum sysmobts_firmware_type type);
+
+
+int sysmobts_bts_type();
+int sysmobts_trx_number();
+int is_sbts2050(void);
+int is_sbts2050_trx(int);
+int is_sbts2050_master(void);
+
+struct sbts2050_power_status {
+ float main_supply_current;
+
+ int master_enabled;
+ float master_voltage;
+ float master_current;
+
+ int slave_enabled;
+ float slave_voltage;
+ float slave_current;
+
+ int pa_enabled;
+ float pa_voltage;
+ float pa_current;
+
+ float pa_bias_voltage;
+};
+
+int sbts2050_uc_check_temp(int *temp_pa, int *temp_board);
+int sbts2050_uc_set_power(int pmaster, int pslave, int ppa);
+int sbts2050_uc_get_status(struct sbts2050_power_status *status);
+int sbts2050_uc_set_pa_power(int on_off);
+int sbts2050_uc_set_slave_power(int on_off);
+void sbts2050_uc_initialize();
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_nl.c
new file mode 100644
index 00000000..67aa6636
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.c
@@ -0,0 +1,120 @@
+/* Helper for netlink */
+
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+
+#include <sys/socket.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define NLMSG_TAIL(nmsg) \
+ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
+
+/**
+ * In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source
+ * address will be used when sending a message this function can be used.
+ * It will ask the routing code of the kernel for the PREFSRC
+ */
+int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source)
+{
+ int fd, rc;
+ struct rtmsg *r;
+ struct rtattr *rta;
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[1024];
+ } req;
+
+ memset(&req, 0, sizeof(req));
+
+ fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE);
+ if (fd < 0) {
+ perror("nl socket");
+ return -1;
+ }
+
+ /* Send a rtmsg and ask for a response */
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ req.n.nlmsg_type = RTM_GETROUTE;
+ req.n.nlmsg_seq = 1;
+
+ /* Prepare the routing request */
+ req.r.rtm_family = AF_INET;
+
+ /* set the dest */
+ rta = NLMSG_TAIL(&req.n);
+ rta->rta_type = RTA_DST;
+ rta->rta_len = RTA_LENGTH(sizeof(*dest));
+ memcpy(RTA_DATA(rta), dest, sizeof(*dest));
+
+ /* update sizes for dest */
+ req.r.rtm_dst_len = sizeof(*dest) * 8;
+ req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len);
+
+ rc = send(fd, &req, req.n.nlmsg_len, 0);
+ if (rc != req.n.nlmsg_len) {
+ perror("short write");
+ close(fd);
+ return -2;
+ }
+
+
+ /* now receive a response and parse it */
+ rc = recv(fd, &req, sizeof(req), 0);
+ if (rc <= 0) {
+ perror("short read");
+ close(fd);
+ return -3;
+ }
+
+ if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) {
+ close(fd);
+ return -4;
+ }
+
+ r = NLMSG_DATA(&req.n);
+ rc -= NLMSG_LENGTH(sizeof(*r));
+ rta = RTM_RTA(r);
+ while (RTA_OK(rta, rc)) {
+ if (rta->rta_type != RTA_PREFSRC) {
+ rta = RTA_NEXT(rta, rc);
+ continue;
+ }
+
+ /* we are done */
+ memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta));
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+ return -5;
+}
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.h b/src/osmo-bts-sysmo/misc/sysmobts_nl.h
new file mode 100644
index 00000000..84f4d9cc
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.h
@@ -0,0 +1,24 @@
+/*
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+struct in_addr;
+
+int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source);
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_par.c b/src/osmo-bts-sysmo/misc/sysmobts_par.c
new file mode 100644
index 00000000..de81fff5
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_par.c
@@ -0,0 +1,382 @@
+/* sysmobts - access to hardware related parameters */
+
+/* (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/crc8gen.h>
+#include <osmocom/core/utils.h>
+
+#include "sysmobts_eeprom.h"
+#include "sysmobts_par.h"
+#include "eeprom.h"
+
+#define EEPROM_PATH "/sys/devices/platform/i2c_davinci.1/i2c-1/1-0050/eeprom"
+
+static const struct osmo_crc8gen_code crc8_ccit = {
+ .bits = 8,
+ .poly = 0x83,
+ .init = 0xFF,
+ .remainder = 0x00,
+};
+
+const struct value_string sysmobts_par_names[_NUM_SYSMOBTS_PAR+1] = {
+ { SYSMOBTS_PAR_MAC, "ethaddr" },
+ { SYSMOBTS_PAR_CLK_FACTORY, "clk-factory" },
+ { SYSMOBTS_PAR_TEMP_DIG_MAX, "temp-dig-max" },
+ { SYSMOBTS_PAR_TEMP_RF_MAX, "temp-rf-max" },
+ { SYSMOBTS_PAR_SERNR, "serial-nr" },
+ { SYSMOBTS_PAR_HOURS, "hours-running" },
+ { SYSMOBTS_PAR_BOOTS, "boot-count" },
+ { SYSMOBTS_PAR_KEY, "key" },
+ { SYSMOBTS_PAR_MODEL_NR, "model-nr" },
+ { SYSMOBTS_PAR_MODEL_FLAGS, "model-flags" },
+ { SYSMOBTS_PAR_TRX_NR, "trx-nr" },
+ { 0, NULL }
+};
+
+static struct {
+ int read;
+ struct sysmobts_eeprom ee;
+} g_ee;
+
+static struct sysmobts_eeprom *get_eeprom(int update_rqd)
+{
+ if (update_rqd || g_ee.read == 0) {
+ int fd, rc;
+
+ fd = open(EEPROM_PATH, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ rc = read(fd, &g_ee.ee, sizeof(g_ee.ee));
+
+ close(fd);
+
+ if (rc < sizeof(g_ee.ee))
+ return NULL;
+
+ g_ee.read = 1;
+ }
+
+ return &g_ee.ee;
+}
+
+static int set_eeprom(struct sysmobts_eeprom *ee)
+{
+ int fd, rc;
+
+ memcpy(&g_ee.ee, ee, sizeof(*ee));
+
+ fd = open(EEPROM_PATH, O_WRONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = write(fd, ee, sizeof(*ee));
+ if (rc < sizeof(*ee)) {
+ close(fd);
+ return -EIO;
+ }
+
+ close(fd);
+
+ return 0;
+}
+
+int sysmobts_par_is_int(enum sysmobts_par par)
+{
+ switch (par) {
+ case SYSMOBTS_PAR_CLK_FACTORY:
+ case SYSMOBTS_PAR_TEMP_DIG_MAX:
+ case SYSMOBTS_PAR_TEMP_RF_MAX:
+ case SYSMOBTS_PAR_SERNR:
+ case SYSMOBTS_PAR_HOURS:
+ case SYSMOBTS_PAR_BOOTS:
+ case SYSMOBTS_PAR_MODEL_NR:
+ case SYSMOBTS_PAR_MODEL_FLAGS:
+ case SYSMOBTS_PAR_TRX_NR:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+int sysmobts_par_get_int(enum sysmobts_par par, int *ret)
+{
+ eeprom_RfClockCal_t rf_clk;
+ eeprom_Error_t err;
+ struct sysmobts_eeprom *ee = get_eeprom(0);
+
+ if (!ee)
+ return -EIO;
+
+ if (par >= _NUM_SYSMOBTS_PAR)
+ return -ENODEV;
+
+ switch (par) {
+ case SYSMOBTS_PAR_CLK_FACTORY:
+ err = eeprom_ReadRfClockCal(&rf_clk);
+ if (err != EEPROM_SUCCESS)
+ return -EIO;
+ *ret = rf_clk.iClkCor;
+ break;
+ case SYSMOBTS_PAR_TEMP_DIG_MAX:
+ *ret = ee->temp1_max;
+ break;
+ case SYSMOBTS_PAR_TEMP_RF_MAX:
+ *ret = ee->temp2_max;
+ break;
+ case SYSMOBTS_PAR_SERNR:
+ *ret = ee->serial_nr;
+ break;
+ case SYSMOBTS_PAR_HOURS:
+ *ret = ee->operational_hours;
+ break;
+ case SYSMOBTS_PAR_BOOTS:
+ *ret = ee->boot_count;
+ break;
+ case SYSMOBTS_PAR_MODEL_NR:
+ *ret = ee->model_nr;
+ break;
+ case SYSMOBTS_PAR_MODEL_FLAGS:
+ *ret = ee->model_flags;
+ break;
+ case SYSMOBTS_PAR_TRX_NR:
+ *ret = ee->trx_nr;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int sysmobts_par_set_int(enum sysmobts_par par, int val)
+{
+ eeprom_RfClockCal_t rf_clk;
+ eeprom_Error_t err;
+ struct sysmobts_eeprom *ee = get_eeprom(1);
+
+ if (!ee)
+ return -EIO;
+
+ if (par >= _NUM_SYSMOBTS_PAR)
+ return -ENODEV;
+
+ switch (par) {
+ case SYSMOBTS_PAR_CLK_FACTORY:
+ err = eeprom_ReadRfClockCal(&rf_clk);
+ if (err != EEPROM_SUCCESS)
+ return -EIO;
+ rf_clk.iClkCor = val;
+ err = eeprom_WriteRfClockCal(&rf_clk);
+ if (err != EEPROM_SUCCESS)
+ return -EIO;
+ break;
+ case SYSMOBTS_PAR_TEMP_DIG_MAX:
+ ee->temp1_max = val;
+ break;
+ case SYSMOBTS_PAR_TEMP_RF_MAX:
+ ee->temp2_max = val;
+ break;
+ case SYSMOBTS_PAR_SERNR:
+ ee->serial_nr = val;
+ break;
+ case SYSMOBTS_PAR_HOURS:
+ ee->operational_hours = val;
+ break;
+ case SYSMOBTS_PAR_BOOTS:
+ ee->boot_count = val;
+ break;
+ case SYSMOBTS_PAR_MODEL_NR:
+ ee->model_nr = val;
+ break;
+ case SYSMOBTS_PAR_MODEL_FLAGS:
+ ee->model_flags = val;
+ break;
+ case SYSMOBTS_PAR_TRX_NR:
+ ee->trx_nr = val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ set_eeprom(ee);
+
+ return 0;
+}
+
+int sysmobts_par_get_buf(enum sysmobts_par par, uint8_t *buf,
+ unsigned int size)
+{
+ uint8_t *ptr;
+ unsigned int len;
+ struct sysmobts_eeprom *ee = get_eeprom(0);
+
+ if (!ee)
+ return -EIO;
+
+ if (par >= _NUM_SYSMOBTS_PAR)
+ return -ENODEV;
+
+ switch (par) {
+ case SYSMOBTS_PAR_MAC:
+ ptr = ee->eth_mac;
+ len = sizeof(ee->eth_mac);
+ break;
+ case SYSMOBTS_PAR_KEY:
+ ptr = ee->gpg_key;
+ len = sizeof(ee->gpg_key);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (size < len)
+ len = size;
+ memcpy(buf, ptr, len);
+
+ return len;
+}
+
+int sysmobts_par_set_buf(enum sysmobts_par par, const uint8_t *buf,
+ unsigned int size)
+{
+ uint8_t *ptr;
+ unsigned int len;
+ struct sysmobts_eeprom *ee = get_eeprom(0);
+
+ if (!ee)
+ return -EIO;
+
+ if (par >= _NUM_SYSMOBTS_PAR)
+ return -ENODEV;
+
+ switch (par) {
+ case SYSMOBTS_PAR_MAC:
+ ptr = ee->eth_mac;
+ len = sizeof(ee->eth_mac);
+ break;
+ case SYSMOBTS_PAR_KEY:
+ ptr = ee->gpg_key;
+ len = sizeof(ee->gpg_key);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (len < size)
+ size = len;
+
+ memcpy(ptr, buf, size);
+
+ return len;
+}
+
+int sysmobts_par_get_net(struct sysmobts_net_cfg *cfg)
+{
+ struct sysmobts_eeprom *ee = get_eeprom(0);
+ ubit_t bits[sizeof(*cfg) * 8];
+ uint8_t crc;
+ int rc;
+
+ if (!ee)
+ return -EIO;
+
+ /* convert the net_cfg to unpacked bits */
+ rc = osmo_pbit2ubit(bits, (uint8_t *) &ee->net_cfg, sizeof(bits));
+ if (rc != sizeof(bits))
+ return -EFAULT;
+ /* compute the crc and compare */
+ crc = osmo_crc8gen_compute_bits(&crc8_ccit, bits, sizeof(bits));
+ if (crc != ee->crc) {
+ fprintf(stderr, "Computed CRC(%d) wanted CRC(%d)\n", crc, ee->crc);
+ return -EBADMSG;
+ }
+ /* return the actual data */
+ *cfg = ee->net_cfg;
+ return 0;
+}
+
+int sysmobts_get_type(int *bts_type)
+{
+ return sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_NR, bts_type);
+}
+
+int sysmobts_get_trx(int *trx_number)
+{
+ return sysmobts_par_get_int(SYSMOBTS_PAR_TRX_NR, trx_number);
+}
+
+char *sysmobts_model(int bts_type, int trx_num)
+{
+ switch(bts_type) {
+ case 0:
+ case 0xffff:
+ case 1002:
+ return "sysmoBTS 1002";
+ case 2050:
+ switch(trx_num) {
+ case 0:
+ return "sysmoBTS 2050 (master)";
+ case 1:
+ return "sysmoBTS 2050 (slave)";
+ default:
+ return "sysmoBTS 2050 (unknown)";
+ }
+ default:
+ return "Unknown";
+ }
+}
+
+int sysmobts_par_set_net(struct sysmobts_net_cfg *cfg)
+{
+ struct sysmobts_eeprom *ee = get_eeprom(1);
+ ubit_t bits[sizeof(*cfg) * 8];
+ int rc;
+
+ if (!ee)
+ return -EIO;
+
+ /* convert the net_cfg to unpacked bits */
+ rc = osmo_pbit2ubit(bits, (uint8_t *) cfg, sizeof(bits));
+ if (rc != sizeof(bits))
+ return -EFAULT;
+ /* compute and store the result */
+ ee->net_cfg = *cfg;
+ ee->crc = osmo_crc8gen_compute_bits(&crc8_ccit, bits, sizeof(bits));
+ return set_eeprom(ee);
+}
+
+osmo_static_assert(offsetof(struct sysmobts_eeprom, trx_nr) == 36, offset_36);
+osmo_static_assert(offsetof(struct sysmobts_eeprom, boot_state) == 37, offset_37);
+osmo_static_assert(offsetof(struct sysmobts_eeprom, _pad1) == 85, offset_85);
+osmo_static_assert(offsetof(struct sysmobts_eeprom, net_cfg.mode) == 103, offset_103);
+osmo_static_assert((offsetof(struct sysmobts_eeprom, net_cfg.ip) & 0x3) == 0, ip_32bit_aligned);
+osmo_static_assert(offsetof(struct sysmobts_eeprom, gpg_key) == 121, offset_121);
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_par.h b/src/osmo-bts-sysmo/misc/sysmobts_par.h
new file mode 100644
index 00000000..52bf67df
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_par.h
@@ -0,0 +1,38 @@
+#ifndef _SYSMOBTS_PAR_H
+#define _SYSMOBTS_PAR_H
+
+#include <osmocom/core/utils.h>
+
+struct sysmobts_net_cfg;
+
+enum sysmobts_par {
+ SYSMOBTS_PAR_MAC,
+ SYSMOBTS_PAR_CLK_FACTORY,
+ SYSMOBTS_PAR_TEMP_DIG_MAX,
+ SYSMOBTS_PAR_TEMP_RF_MAX,
+ SYSMOBTS_PAR_SERNR,
+ SYSMOBTS_PAR_HOURS,
+ SYSMOBTS_PAR_BOOTS,
+ SYSMOBTS_PAR_KEY,
+ SYSMOBTS_PAR_MODEL_NR,
+ SYSMOBTS_PAR_MODEL_FLAGS,
+ SYSMOBTS_PAR_TRX_NR,
+ _NUM_SYSMOBTS_PAR
+};
+
+extern const struct value_string sysmobts_par_names[_NUM_SYSMOBTS_PAR+1];
+
+int sysmobts_par_get_int(enum sysmobts_par par, int *ret);
+int sysmobts_par_set_int(enum sysmobts_par par, int val);
+int sysmobts_par_get_buf(enum sysmobts_par par, uint8_t *buf,
+ unsigned int size);
+int sysmobts_par_set_buf(enum sysmobts_par par, const uint8_t *buf,
+ unsigned int size);
+int sysmobts_par_get_net(struct sysmobts_net_cfg *cfg);
+int sysmobts_par_set_net(struct sysmobts_net_cfg *cfg);
+int sysmobts_get_type(int *bts_type);
+int sysmobts_get_trx(int *trx_number);
+char *sysmobts_model(int bts_type, int trx_num);
+int sysmobts_par_is_int(enum sysmobts_par par);
+
+#endif
diff --git a/src/osmo-bts-sysmo/misc/sysmobts_util.c b/src/osmo-bts-sysmo/misc/sysmobts_util.c
new file mode 100644
index 00000000..c9930d8f
--- /dev/null
+++ b/src/osmo-bts-sysmo/misc/sysmobts_util.c
@@ -0,0 +1,256 @@
+/* sysmobts-util - access to hardware related parameters */
+
+/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "sysmobts_par.h"
+#include "sysmobts_eeprom.h"
+
+enum act {
+ ACT_GET,
+ ACT_SET,
+ ACT_NET_GET,
+ ACT_NET_SET,
+};
+
+static enum act action;
+static char *write_arg;
+static int void_warranty;
+
+
+static struct in_addr net_ip = { 0, }, net_dns = { 0, }, net_gw = { 0, }, net_mask = { 0, };
+static uint8_t net_mode = 0;
+
+static void print_help()
+{
+ const struct value_string *par = sysmobts_par_names;
+
+ printf("sysmobts-util [--void-warranty -r | -w value] param_name\n");
+ printf("sysmobts-util --net-read\n");
+ printf("sysmobts-util --net-write --mode INT --ip IP_STR --gw IP_STR --dns IP_STR --net-mask IP_STR\n");
+ printf("Possible param names:\n");
+
+ for (; par->str != NULL; par += 1) {
+ if (!sysmobts_par_is_int(par->value))
+ continue;
+ printf(" %s\n", par->str);
+ }
+}
+
+static int parse_options(int argc, char **argv)
+{
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "read", 0, 0, 'r' },
+ { "void-warranty", 0, 0, 1000},
+ { "write", 1, 0, 'w' },
+ { "ip", 1, 0, 241 },
+ { "gw", 1, 0, 242 },
+ { "dns", 1, 0, 243 },
+ { "net-mask", 1, 0, 244 },
+ { "mode", 1, 0, 245 },
+ { "net-read", 0, 0, 246 },
+ { "net-write", 0, 0, 247 },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "rw:h",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'r':
+ action = ACT_GET;
+ break;
+ case 'w':
+ action = ACT_SET;
+ write_arg = optarg;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ break;
+ case 1000:
+ printf("Will void warranty on write.\n");
+ void_warranty = 1;
+ break;
+ case 246:
+ action = ACT_NET_GET;
+ break;
+ case 247:
+ action = ACT_NET_SET;
+ break;
+ case 245:
+ net_mode = atoi(optarg);
+ break;
+ case 244:
+ inet_aton(optarg, &net_mask);
+ break;
+ case 243:
+ inet_aton(optarg, &net_dns);
+ break;
+ case 242:
+ inet_aton(optarg, &net_gw);
+ break;
+ case 241:
+ inet_aton(optarg, &net_ip);
+ break;
+ default:
+ printf("Unknown option %d/%c\n", c, c);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static const char *make_addr(uint32_t saddr)
+{
+ struct in_addr addr;
+ addr.s_addr = ntohl(saddr);
+ return inet_ntoa(addr);
+}
+
+static void dump_net_cfg(struct sysmobts_net_cfg *net_cfg)
+{
+ if (net_cfg->mode == NET_MODE_DHCP) {
+ printf("IP=dhcp\n");
+ printf("DNS=\n");
+ printf("GATEWAY=\n");
+ printf("NETMASK=\n");
+ } else {
+ printf("IP=%s\n", make_addr(net_cfg->ip));
+ printf("GATEWAY=%s\n", make_addr(net_cfg->gw));
+ printf("DNS=%s\n", make_addr(net_cfg->dns));
+ printf("NETMASK=%s\n", make_addr(net_cfg->mask));
+ }
+}
+
+static int handle_net(void)
+{
+ struct sysmobts_net_cfg net_cfg;
+ int rc;
+
+ switch (action) {
+ case ACT_NET_GET:
+ rc = sysmobts_par_get_net(&net_cfg);
+ if (rc != 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ exit(rc);
+ }
+ dump_net_cfg(&net_cfg);
+ break;
+ case ACT_NET_SET:
+ memset(&net_cfg, 0, sizeof(net_cfg));
+ net_cfg.mode = net_mode;
+ net_cfg.ip = htonl(net_ip.s_addr);
+ net_cfg.mask = htonl(net_mask.s_addr);
+ net_cfg.gw = htonl(net_gw.s_addr);
+ net_cfg.dns = htonl(net_dns.s_addr);
+ printf("Going to write\n");
+ dump_net_cfg(&net_cfg);
+
+ rc = sysmobts_par_set_net(&net_cfg);
+ if (rc != 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ exit(rc);
+ }
+ break;
+ default:
+ printf("Unhandled action %d\n", action);
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ const char *parname;
+ enum sysmobts_par par;
+ int rc, val;
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ if (action > ACT_SET)
+ return handle_net();
+
+ if (optind >= argc && action <+ ACT_NET_GET) {
+ fprintf(stderr, "You must specify the parameter name\n");
+ exit(2);
+ }
+ parname = argv[optind];
+
+ rc = get_string_value(sysmobts_par_names, parname);
+ if (rc < 0) {
+ fprintf(stderr, "`%s' is not a valid parameter\n", parname);
+ exit(2);
+ } else
+ par = rc;
+
+ switch (action) {
+ case ACT_GET:
+ rc = sysmobts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("%d\n", val);
+ break;
+ case ACT_SET:
+ rc = sysmobts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) {
+ fprintf(stderr, "Parameter is already set!\r\n");
+ goto err;
+ }
+ rc = sysmobts_par_set_int(par, atoi(write_arg));
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("Success setting %s=%d\n", parname,
+ atoi(write_arg));
+ break;
+ default:
+ fprintf(stderr, "Unsupported action\n");
+ goto err;
+ }
+
+ exit(0);
+
+err:
+ exit(1);
+}
+
diff --git a/src/osmo-bts-sysmo/oml.c b/src/osmo-bts-sysmo/oml.c
new file mode 100644
index 00000000..ea7527dd
--- /dev/null
+++ b/src/osmo-bts-sysmo/oml.c
@@ -0,0 +1,1963 @@
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013-2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+#include <sysmocom/femtobts/superfemto.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+
+#include "l1_if.h"
+#include "femtobts.h"
+#include "utils.h"
+
+static int mph_info_chan_confirm(struct gsm_lchan *lchan,
+ 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 = gsm_lchan2chan_nr(lchan);
+ l1sap.u.info.u.act_cnf.cause = cause;
+
+ return l1sap_up(lchan->ts->trx, &l1sap);
+}
+
+enum sapi_cmd_type {
+ SAPI_CMD_ACTIVATE,
+ SAPI_CMD_CONFIG_CIPHERING,
+ SAPI_CMD_CONFIG_LOGCH_PARAM,
+ SAPI_CMD_SACCH_REL_MARKER,
+ SAPI_CMD_REL_MARKER,
+ SAPI_CMD_DEACTIVATE,
+};
+
+struct sapi_cmd {
+ struct llist_head entry;
+ GsmL1_Sapi_t sapi;
+ GsmL1_Dir_t dir;
+ enum sapi_cmd_type type;
+ int (*callback)(struct gsm_lchan *lchan, int status);
+};
+
+static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = {
+ [GSM_PCHAN_NONE] = GsmL1_LogChComb_0,
+ [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV,
+ [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I,
+ [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII,
+ [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0,
+ /*
+ * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be
+ * part of this, only "real" pchan values will be looked up here.
+ * See the callers of ts_connect_as().
+ */
+};
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb);
+
+static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct femtol1_hdl *gl1,
+ HANDLE hLayer3)
+{
+ prim->id = id;
+
+ /* for some reason the hLayer1 and hlayer3 fields are not always at the
+ * same position in the GsmL1_Prim_t, so we have to have this ugly case
+ * statement here... */
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq:
+ //prim->u.mphInitReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphInitReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphCloseReq:
+ prim->u.mphCloseReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphCloseReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConnectReq:
+ prim->u.mphConnectReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphConnectReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDisconnectReq:
+ prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphDisconnectReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphActivateReq:
+ prim->u.mphActivateReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphActivateReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDeactivateReq:
+ prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphDeactivateReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConfigReq:
+ prim->u.mphConfigReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphConfigReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphMeasureReq:
+ prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1;
+ prim->u.mphMeasureReq.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphInitCnf:
+ prim->u.mphInitCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphCloseCnf:
+ prim->u.mphCloseCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConnectCnf:
+ prim->u.mphConnectCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ prim->u.mphDisconnectCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphActivateCnf:
+ prim->u.mphActivateCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ prim->u.mphDeactivateCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphConfigCnf:
+ prim->u.mphConfigCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphMeasureCnf:
+ prim->u.mphMeasureCnf.hLayer3 = hLayer3;
+ break;
+ case GsmL1_PrimId_MphTimeInd:
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ break;
+ case GsmL1_PrimId_PhEmptyFrameReq:
+ prim->u.phEmptyFrameReq.hLayer1 = gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhDataReq:
+ prim->u.phDataReq.hLayer1 = gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ break;
+ case GsmL1_PrimId_PhDataInd:
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id);
+ break;
+ }
+ return &prim->u;
+}
+
+static HANDLE l1p_handle_for_trx(struct gsm_bts_trx *trx)
+{
+ struct gsm_bts *bts = trx->bts;
+
+ osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit);
+ osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit);
+ osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit);
+
+ return bts->nr << 24
+ | trx->nr << 16;
+}
+
+static HANDLE l1p_handle_for_ts(struct gsm_bts_trx_ts *ts)
+{
+ osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit);
+
+ return l1p_handle_for_trx(ts->trx)
+ | ts->nr << 8;
+}
+
+
+static HANDLE l1p_handle_for_lchan(struct gsm_lchan *lchan)
+{
+ osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit);
+
+ return l1p_handle_for_ts(lchan->ts)
+ | lchan->nr;
+}
+
+GsmL1_Status_t prim_status(GsmL1_Prim_t *prim)
+{
+ /* for some reason the Status field is not always at the same position
+ * in the GsmL1_Prim_t, so we have to have this ugly case statement here... */
+ switch (prim->id) {
+ case GsmL1_PrimId_MphInitCnf:
+ return prim->u.mphInitCnf.status;
+ case GsmL1_PrimId_MphCloseCnf:
+ return prim->u.mphCloseCnf.status;
+ case GsmL1_PrimId_MphConnectCnf:
+ return prim->u.mphConnectCnf.status;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ return prim->u.mphDisconnectCnf.status;
+ case GsmL1_PrimId_MphActivateCnf:
+ return prim->u.mphActivateCnf.status;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ return prim->u.mphDeactivateCnf.status;
+ case GsmL1_PrimId_MphConfigCnf:
+ return prim->u.mphConfigCnf.status;
+ case GsmL1_PrimId_MphMeasureCnf:
+ return prim->u.mphMeasureCnf.status;
+ default:
+ break;
+ }
+ return GsmL1_Status_Success;
+}
+
+#if 0
+static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data)
+{
+ struct msgb *resp_msg = data;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+
+ if (prim_status(l1p) != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(femtobts_l1prim_names, l1p->id),
+ get_value_string(femtobts_l1status_names, cc->status));
+ return 0;
+ }
+
+ msgb_free(l1_msg);
+
+ return abis_nm_sendmsg(msg);
+}
+#endif
+
+int lchan_activate(struct gsm_lchan *lchan);
+
+static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_Status_t status = prim_status(l1p);
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(femtobts_l1prim_names, l1p->id),
+ get_value_string(femtobts_l1status_names, status));
+ msgb_free(l1_msg);
+ return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM);
+ }
+
+ msgb_free(l1_msg);
+
+ /* Set to Operational State: Enabled */
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+
+ /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 &&
+ mo->obj_inst.ts_nr == 0) {
+ struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts);
+ DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n");
+ mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
+ LCHAN_REL_ACT_OML;
+ lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]);
+ if (cbch) {
+ cbch->rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_activate(cbch);
+ }
+ }
+
+ /* Send OPSTART ack */
+ return oml_mo_opstart_ack(mo);
+}
+
+static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_abis_mo *mo;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
+
+ mo = &trx->ts[cnf->u8Tn].mo;
+ return opstart_compl(mo, l1_msg);
+}
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n",
+ get_value_string(femtobts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-MUTE failure");
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+#endif
+
+static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf;
+
+ LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n",
+ get_value_string(femtobts_l1status_names, ic->status));
+
+ /* store layer1 handle */
+ if (ic->status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n",
+ get_value_string(femtobts_l1status_names, ic->status));
+ bts_shutdown(trx->bts, "MPH-INIT failure");
+ }
+
+ fl1h->hLayer1 = ic->hLayer1;
+
+#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0)
+ /* If the TRX was already locked the MphInit would have undone it */
+ if (trx->mo.nm_state.administrative == NM_STATE_LOCKED)
+ trx_rf_lock(trx, 1, trx_mute_on_init_cb);
+#endif
+
+ /* Begin to ramp up the power */
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+
+ return opstart_compl(&trx->mo, l1_msg);
+}
+
+int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids,
+ unsigned int num_attr_ids)
+{
+ unsigned int i;
+
+ if (!mo->nm_attr)
+ return 0;
+
+ for (i = 0; i < num_attr_ids; i++) {
+ if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i]))
+ return 0;
+ }
+ return 1;
+}
+
+static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R };
+
+/* initialize the layer1 */
+static int trx_init(struct gsm_bts_trx *trx)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg;
+ GsmL1_MphInitReq_t *mi_req;
+ GsmL1_DeviceParam_t *dev_par;
+ int femto_band;
+ int initial_mdBm = power_ramp_initial_power_mdBm(trx);
+
+ if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr,
+ ARRAY_SIZE(trx_rqd_attr))) {
+ /* HACK: spec says we need to decline, but openbsc
+ * doesn't deal with this very well */
+ return oml_mo_opstart_ack(&trx->mo);
+ //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM);
+ }
+
+ femto_band = sysmobts_select_femto_band(trx, trx->arfcn);
+ if (femto_band < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n",
+ gsm_band_name(trx->bts->band));
+ }
+
+ msg = l1p_msgb_alloc();
+ mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h,
+ l1p_handle_for_trx(trx));
+ dev_par = &mi_req->deviceParam;
+ dev_par->devType = GsmL1_DevType_TxdRxu;
+ dev_par->freqBand = femto_band;
+ dev_par->u16Arfcn = trx->arfcn;
+ dev_par->u16BcchArfcn = trx->bts->c0->arfcn;
+ dev_par->u8NbTsc = trx->bts->bsic & 7;
+ dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx)
+ ? 0.0 : trx->bts->ul_power_target;
+
+ dev_par->fTxPowerLevel = ((float) initial_mdBm) / 1000;
+ LOGP(DL1C, LOGL_NOTICE, "Init TRX (ARFCN %u, TSC %u, RxPower % 2f dBm, "
+ "TxPower % 2.2f dBm\n", dev_par->u16Arfcn, dev_par->u8NbTsc,
+ dev_par->fRxPowerLevel, dev_par->fTxPowerLevel);
+ trx->power_params.p_total_cur_mdBm = trx->power_params.ramp.max_initial_pout_mdBm;
+
+ /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */
+ return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL);
+}
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ return fl1h->hLayer1;
+}
+
+static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ msgb_free(l1_msg);
+ return 0;
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg;
+
+ msg = l1p_msgb_alloc();
+ prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h,
+ l1p_handle_for_trx(trx));
+ LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr);
+
+ return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL);
+}
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ uint8_t mute[8];
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mute); ++i)
+ mute[i] = locked ? 1 : 0;
+
+ return l1if_mute_rf(fl1h, mute, cb);
+}
+
+int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8],
+ int success)
+{
+ if (success) {
+ int i;
+ int is_locked = 1;
+
+ for (i = 0; i < 8; ++i)
+ if (!mute_state[i])
+ is_locked = 0;
+
+ mo->nm_state.administrative =
+ is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_ack(mo);
+ } else {
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
+ }
+}
+
+static int ts_connect_as(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan,
+ l1if_compl_cb *cb, void *data)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx);
+ GsmL1_MphConnectReq_t *cr;
+
+ if (pchan == GSM_PCHAN_TCH_F_PDCH
+ || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Requested TS connect as %s,"
+ " expected a specific pchan instead\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan));
+ return -EINVAL;
+ }
+
+ cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h,
+ l1p_handle_for_ts(ts));
+ cr->u8Tn = ts->nr;
+ cr->logChComb = pchan_to_logChComb[pchan];
+
+ DEBUGP(DL1C, "%s pchan=%s ts_connect_as(%s) logChComb=%s\n",
+ gsm_lchan_name(ts->lchan), gsm_pchan_name(ts->pchan),
+ gsm_pchan_name(pchan), get_value_string(femtobts_chcomb_names,
+ cr->logChComb));
+
+ return l1if_gsm_req_compl(fl1h, msg, cb, NULL);
+}
+
+static int ts_opstart(struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config pchan = ts->pchan;
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ /* First connect as NONE, until first RSL CHAN ACT. */
+ pchan = GSM_PCHAN_NONE;
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ /* First connect as TCH/F, expecting PDCH ACT. */
+ pchan = GSM_PCHAN_TCH_F;
+ break;
+ default:
+ /* simply use ts->pchan */
+ break;
+ }
+ return ts_connect_as(ts, pchan, opstart_compl_cb, NULL);
+}
+
+GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan)
+{
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ return GsmL1_Sapi_TchF;
+ case GSM_LCHAN_TCH_H:
+ return GsmL1_Sapi_TchH;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n",
+ gsm_lchan_name(lchan));
+ break;
+ }
+ return GsmL1_Sapi_Idle;
+}
+
+GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = lchan->ts->pchan;
+
+ if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ pchan = lchan->ts->dyn.pchan_want;
+
+ switch (pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ if (lchan->type == GSM_LCHAN_CCCH)
+ return GsmL1_SubCh_NA;
+ /* fall-through */
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ return lchan->nr;
+ case GSM_PCHAN_NONE:
+ case GSM_PCHAN_CCCH:
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_PDCH:
+ case GSM_PCHAN_TCH_F_PDCH:
+ case GSM_PCHAN_UNKNOWN:
+ default:
+ /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */
+ return GsmL1_SubCh_NA;
+ }
+
+ return GsmL1_SubCh_NA;
+}
+
+struct sapi_dir {
+ GsmL1_Sapi_t sapi;
+ GsmL1_Dir_t dir;
+};
+
+static const struct sapi_dir ccch_sapis[] = {
+ { GsmL1_Sapi_Fcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Bcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Agch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Pch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir tchf_sapis[] = {
+ { GsmL1_Sapi_TchF, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_TchF, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_FacchF, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_FacchF, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir tchh_sapis[] = {
+ { GsmL1_Sapi_TchH, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_TchH, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_FacchH, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_FacchH, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir sdcch_sapis[] = {
+ { GsmL1_Sapi_Sdcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sdcch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink },
+};
+
+static const struct sapi_dir cbch_sapis[] = {
+ { GsmL1_Sapi_Cbch, GsmL1_Dir_TxDownlink },
+ /* Does the CBCH really have a SACCH in Downlink? */
+ { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink },
+};
+
+static const struct sapi_dir pdtch_sapis[] = {
+ { GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink },
+ { GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink },
+#if 0
+ { GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink },
+ { GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink },
+#endif
+};
+
+static const struct sapi_dir ho_sapis[] = {
+ { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink },
+};
+
+struct lchan_sapis {
+ const struct sapi_dir *sapis;
+ unsigned int num_sapis;
+};
+
+static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
+ [GSM_LCHAN_SDCCH] = {
+ .sapis = sdcch_sapis,
+ .num_sapis = ARRAY_SIZE(sdcch_sapis),
+ },
+ [GSM_LCHAN_TCH_F] = {
+ .sapis = tchf_sapis,
+ .num_sapis = ARRAY_SIZE(tchf_sapis),
+ },
+ [GSM_LCHAN_TCH_H] = {
+ .sapis = tchh_sapis,
+ .num_sapis = ARRAY_SIZE(tchh_sapis),
+ },
+ [GSM_LCHAN_CCCH] = {
+ .sapis = ccch_sapis,
+ .num_sapis = ARRAY_SIZE(ccch_sapis),
+ },
+ [GSM_LCHAN_PDTCH] = {
+ .sapis = pdtch_sapis,
+ .num_sapis = ARRAY_SIZE(pdtch_sapis),
+ },
+ [GSM_LCHAN_CBCH] = {
+ .sapis = cbch_sapis,
+ .num_sapis = ARRAY_SIZE(cbch_sapis),
+ },
+};
+
+static const struct lchan_sapis sapis_for_ho = {
+ .sapis = ho_sapis,
+ .num_sapis = ARRAY_SIZE(ho_sapis),
+};
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
+
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir);
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan);
+
+/**
+ * Execute the first SAPI command of the queue. In case of the markers
+ * this method is re-entrant so we need to make sure to remove a command
+ * from the list before calling a function that will queue a command.
+ *
+ * \return 0 in case no Gsm Request was sent, 1 otherwise
+ */
+static int sapi_queue_exeute(struct gsm_lchan *lchan)
+{
+ int res;
+ struct sapi_cmd *cmd;
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+
+ switch (cmd->type) {
+ case SAPI_CMD_ACTIVATE:
+ mph_send_activate_req(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_CIPHERING:
+ mph_send_config_ciphering(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_CONFIG_LOGCH_PARAM:
+ mph_send_config_logchpar(lchan, cmd);
+ res = 1;
+ break;
+ case SAPI_CMD_SACCH_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = check_sapi_release(lchan, GsmL1_Sapi_Sacch,
+ GsmL1_Dir_TxDownlink);
+ res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch,
+ GsmL1_Dir_RxUplink);
+ break;
+ case SAPI_CMD_REL_MARKER:
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = lchan_deactivate_sapis(lchan);
+ break;
+ case SAPI_CMD_DEACTIVATE:
+ mph_send_deactivate_req(lchan, cmd);
+ res = 1;
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE,
+ "Unimplemented command type %d\n", cmd->type);
+ llist_del(&cmd->entry);
+ talloc_free(cmd);
+ res = 0;
+ abort();
+ break;
+ }
+
+ return res;
+}
+
+static void sapi_queue_send(struct gsm_lchan *lchan)
+{
+ int res;
+
+ do {
+ res = sapi_queue_exeute(lchan);
+ } while (res == 0 && !llist_empty(&lchan->sapi_cmds));
+}
+
+static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status)
+{
+ int end;
+ struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next,
+ struct sapi_cmd, entry);
+ llist_del(&cmd->entry);
+ end = llist_empty(&lchan->sapi_cmds);
+
+ if (cmd->callback)
+ cmd->callback(lchan, status);
+ talloc_free(cmd);
+
+ if (end || llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_DEBUG,
+ "%s End of SAPI cmd queue encountered.%s\n",
+ gsm_lchan_name(lchan),
+ llist_empty(&lchan->sapi_cmds)
+ ? " Queue is now empty."
+ : " More pending.");
+ return;
+ }
+
+ sapi_queue_send(lchan);
+}
+
+/**
+ * Queue and possible execute a SAPI command. Return 1 in case the command was
+ * already executed and 0 in case if it was only put into the queue
+ */
+static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ int start = llist_empty(&lchan->sapi_cmds);
+ llist_add_tail(&cmd->entry, &lchan->sapi_cmds);
+
+ if (!start)
+ return 0;
+
+ sapi_queue_send(lchan);
+ return 1;
+}
+
+static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(femtobts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n",
+ get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_ASSIGNED;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(femtobts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_ACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan)
+{
+ return 0xBB
+ | (lchan->nr << 8)
+ | (lchan->ts->nr << 16)
+ | (lchan->ts->trx->nr << 24);
+}
+
+/* obtain a ptr to the lapdm_channel for a given hLayer */
+struct gsm_lchan *
+l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2)
+{
+ uint8_t magic = hLayer2 & 0xff;
+ uint8_t ts_nr = (hLayer2 >> 16) & 0xff;
+ uint8_t lchan_nr = (hLayer2 >> 8)& 0xff;
+ struct gsm_bts_trx_ts *ts;
+
+ if (magic != 0xBB)
+ return NULL;
+
+ /* FIXME: if we actually run on the BTS, the 32bit field is large
+ * enough to simply put a pointer inside. */
+ if (ts_nr >= ARRAY_SIZE(trx->ts))
+ return NULL;
+
+ ts = &trx->ts[ts_nr];
+
+ if (lchan_nr >= ARRAY_SIZE(ts->lchan))
+ return NULL;
+
+ return &ts->lchan[lchan_nr];
+}
+
+/* we regularly check if the DSP L1 is still sending us primitives.
+ * if not, we simply stop the BTS program (and be re-spawned) */
+static void alive_timer_cb(void *data)
+{
+ struct femtol1_hdl *fl1h = data;
+
+ if (fl1h->alive_prim_cnt == 0) {
+ LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n");
+ exit(23);
+ }
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+}
+
+static void clear_amr_params(GsmL1_LogChParam_t *lch_par)
+{
+ int j;
+ /* common for the SIGN, V1 and EFR: */
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA;
+ lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset;
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+}
+
+static void set_payload_format(GsmL1_LogChParam_t *lch_par)
+{
+#ifdef L1_HAS_RTP_MODE
+#ifdef USE_L1_RTP_MODE
+ lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp;
+#else
+ lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_If2;
+#endif /* USE_L1_RTP_MODE */
+#endif /* L1_HAS_RTP_MODE */
+}
+
+static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
+{
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie;
+ int j;
+
+ LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n",
+ gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode);
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ /* we have to set some TCH payload type even if we don't
+ * know yet what codec we will use later on */
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Efr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Amr;
+ set_payload_format(lch_par);
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */
+ lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan);
+
+ /* initialize to clean state */
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+
+ j = 0;
+ if (mr_conf->m4_75)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_15)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_90)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m6_70)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_40)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_95)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m10_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+ if (mr_conf->m12_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2;
+ break;
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n",
+ gsm_lchan_name(lchan));
+ break;
+ }
+}
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ int sapi = cmd->sapi;
+ int dir = cmd->dir;
+ GsmL1_MphActivateReq_t *act_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq,
+ fl1h, l1p_handle_for_lchan(lchan));
+ lch_par = &act_req->logChPrm;
+ act_req->u8Tn = lchan->ts->nr;
+ act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ act_req->dir = dir;
+ act_req->sapi = sapi;
+ act_req->hLayer2 = l1if_lchan_to_hLayer(lchan);
+ act_req->hLayer3 = act_req->hLayer2;
+
+ switch (act_req->sapi) {
+ case GsmL1_Sapi_Rach:
+ lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Agch:
+ lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name);
+ break;
+ case GsmL1_Sapi_TchH:
+ case GsmL1_Sapi_TchF:
+ lchan2lch_par(lch_par, lchan);
+ /*
+ * Be sure that every packet is received, even if it
+ * fails. In this case the length might be lower or 0.
+ */
+ act_req->fBFILevel = -200.0f;
+ break;
+ case GsmL1_Sapi_Ptcch:
+ lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Prach:
+ lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Sacch:
+ /*
+ * For the SACCH we need to set the u8MsPowerLevel when
+ * doing manual MS power control.
+ */
+ if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+ /* fall through */
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ /*
+ * Be sure that every packet is received, even if it
+ * fails. In this case the length might be lower or 0.
+ */
+ act_req->fBFILevel = -200.0f;
+ break;
+ default:
+ break;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ",
+ gsm_lchan_name(lchan), act_req->hLayer2,
+ get_value_string(femtobts_l1sapi_names, act_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(femtobts_dir_names, act_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL);
+}
+
+static void sapi_clear_queue(struct llist_head *queue)
+{
+ struct sapi_cmd *next, *tmp;
+
+ llist_for_each_entry_safe(next, tmp, queue, entry) {
+ llist_del(&next->entry);
+ talloc_free(next);
+ }
+}
+
+static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+
+ /* FIXME: Error handling */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s act failed mark broken due status: %d\n",
+ gsm_lchan_name(lchan), status);
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ if (lchan->state != LCHAN_S_ACT_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0);
+
+ /* set the initial ciphering parameters for both directions */
+ l1if_set_ciphering(fl1h, lchan, 1);
+ l1if_set_ciphering(fl1h, lchan, 0);
+ if (lchan->encr.alg_id)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ else
+ lchan->ciph_state = LCHAN_CIPH_NONE;
+
+ return 0;
+}
+
+static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_ACTIVATE;
+ cmd->callback = sapi_activate_cb;
+ queue_sapi_command(lchan, cmd);
+}
+
+int lchan_activate(struct gsm_lchan *lchan)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Trying to activate lchan, but commands in queue\n",
+ gsm_lchan_name(lchan));
+
+ /* override the regular SAPIs if this is the first hand-over
+ * related activation of the LCHAN */
+ if (lchan->ho.active == HANDOVER_ENABLED)
+ s4l = &sapis_for_ho;
+
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+
+ if (sapi == GsmL1_Sapi_Sch) {
+ /* once we activate the SCH, we should get MPH-TIME.ind */
+ fl1h->alive_timer.cb = alive_timer_cb;
+ fl1h->alive_timer.data = fl1h;
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+ }
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+#warning "FIXME: Should this be in sapi_activate_cb?"
+ lchan_init_lapdm(lchan);
+
+ return 0;
+}
+
+const struct value_string femtobts_l1cfgt_names[] = {
+ { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" },
+ { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" },
+ { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" },
+ { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" },
+ { 0, NULL }
+};
+
+static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi)
+{
+ int i;
+
+ switch (sapi) {
+ case GsmL1_Sapi_Rach:
+ LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic);
+ break;
+ case GsmL1_Sapi_Agch:
+ LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ",
+ lch_par->agch.u8NbrOfAgch);
+ break;
+ case GsmL1_Sapi_Sacch:
+ LOGPC(DL1C, logl, "MS Power Level 0x%02x",
+ lch_par->sacch.u8MsPowerLevel);
+ break;
+ case GsmL1_Sapi_TchF:
+ case GsmL1_Sapi_TchH:
+ LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (",
+ lch_par->tch.amrCmiPhase,
+ lch_par->tch.amrInitCodecMode);
+ for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) {
+ LOGPC(DL1C, logl, "%x ",
+ lch_par->tch.amrActiveCodecSet[i]);
+ }
+ break;
+ case GsmL1_Sapi_Ptcch:
+ LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->ptcch.u8Bsic);
+ break;
+ case GsmL1_Sapi_Prach:
+ LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->prach.u8Bsic);
+ break;
+ default:
+ break;
+ }
+ LOGPC(DL1C, logl, ")\n");
+}
+
+static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_trx_name(trx),
+ get_value_string(femtobts_l1cfgt_names, cc->cfgParamId));
+
+ LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n",
+ cc->cfgParams.setTxPowerLevel.fTxPowerLevel);
+
+ power_trx_change_compl(trx,
+ (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000));
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, cc->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", cc->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1cfgt_names, cc->cfgParamId));
+
+ switch (cc->cfgParamId) {
+ case GsmL1_ConfigParamId_SetLogChParams:
+ dump_lch_par(LOGL_INFO,
+ &cc->cfgParams.setLogChParams.logChParams,
+ cc->cfgParams.setLogChParams.sapi);
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetCipheringParams:
+ switch (lchan->ciph_state) {
+ case LCHAN_CIPH_RX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF;
+ break;
+ case LCHAN_CIPH_RX_CONF_TX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ break;
+ case LCHAN_CIPH_RXTX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ break;
+ case LCHAN_CIPH_NONE:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ default:
+ LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state);
+ break;
+ }
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got ciphering conf with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetNbTsc:
+ default:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ }
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
+ l1p_handle_for_lchan(lchan));
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams;
+ conf_req->cfgParams.setLogChParams.sapi = cmd->sapi;
+ conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr;
+ conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ conf_req->cfgParams.setLogChParams.dir = cmd->dir;
+ conf_req->hLayer3 = l1if_lchan_to_hLayer(lchan);
+
+ lch_par = &conf_req->cfgParams.setLogChParams.logChParams;
+ lchan2lch_par(lch_par, lchan);
+
+ /* Update the MS Power Level */
+ if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+
+ /* FIXME: update encryption */
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1sapi_names,
+ conf_req->cfgParams.setLogChParams.sapi));
+ LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ",
+ conf_req->cfgParams.setLogChParams.u8Tn,
+ conf_req->cfgParams.setLogChParams.subCh,
+ conf_req->cfgParams.setLogChParams.dir);
+ dump_lch_par(LOGL_INFO,
+ &conf_req->cfgParams.setLogChParams.logChParams,
+ conf_req->cfgParams.setLogChParams.sapi);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->sapi = sapi;
+ cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM;
+ queue_sapi_command(lchan, cmd);
+}
+
+static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction)
+{
+ enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan));
+ return 0;
+}
+
+int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0);
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel;
+ conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power;
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL);
+}
+
+const enum GsmL1_CipherId_t rsl2l1_ciph[] = {
+ [0] = GsmL1_CipherId_A50,
+ [1] = GsmL1_CipherId_A50,
+ [2] = GsmL1_CipherId_A51,
+ [3] = GsmL1_CipherId_A52,
+ [4] = GsmL1_CipherId_A53,
+};
+
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ struct GsmL1_MphConfigReq_t *cfgr;
+
+ cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
+ l1p_handle_for_lchan(lchan));
+
+ cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams;
+ cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr;
+ cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ cfgr->cfgParams.setCipheringParams.dir = cmd->dir;
+ cfgr->hLayer3 = l1if_lchan_to_hLayer(lchan);
+
+ if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph))
+ return -EINVAL;
+ cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id];
+
+ LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n",
+ gsm_lchan_name(lchan),
+ cfgr->cfgParams.setCipheringParams.cipherId,
+ get_value_string(femtobts_dir_names,
+ cfgr->cfgParams.setCipheringParams.dir));
+
+ memcpy(cfgr->cfgParams.setCipheringParams.u8Kc,
+ lchan->encr.key, lchan->encr.key_len);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_CONFIG_CIPHERING;
+ queue_sapi_command(lchan, cmd);
+}
+
+int l1if_set_ciphering(struct femtol1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink)
+{
+ int dir;
+
+ /* ignore the request when the channel is not active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ if (dir_downlink)
+ dir = GsmL1_Dir_TxDownlink;
+ else
+ dir = GsmL1_Dir_RxUplink;
+
+ enqueue_sapi_ciphering_cmd(lchan, dir);
+
+ return 0;
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch);
+ return 0;
+}
+
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink);
+ tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink);
+
+ /* FIXME: update encryption */
+
+ return 0;
+}
+
+static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf;
+
+ lchan = l1if_hLayer_to_lchan(trx, ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(femtobts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n",
+ get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_NONE;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(femtobts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got de-activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_DEACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+ return 0;
+}
+
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphDeactivateReq_t *deact_req;
+
+ deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq,
+ fl1h, l1p_handle_for_lchan(lchan));
+ deact_req->u8Tn = lchan->ts->nr;
+ deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ deact_req->dir = cmd->dir;
+ deact_req->sapi = cmd->sapi;
+ deact_req->hLayer3 = l1if_lchan_to_hLayer(lchan);
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_l1sapi_names, deact_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(femtobts_dir_names, deact_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL);
+}
+
+static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status)
+{
+ /* FIXME: Error handling. There is no NACK... */
+ if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ /* Don't send an REL ACK on SACCH deactivate */
+ if (lchan->state != LCHAN_S_REL_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_NONE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+
+ /* Reactivate CCCH due to SI3 update in RSL */
+ if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) {
+ lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
+ lchan_activate(lchan);
+ }
+ return 0;
+}
+
+static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_DEACTIVATE;
+ cmd->callback = sapi_deactivate_cb;
+ return queue_sapi_command(lchan, cmd);
+}
+
+/*
+ * Release the SAPI if it was allocated. E.g. the SACCH might be already
+ * deactivated or during a hand-over the TCH was not allocated yet.
+ */
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ /* check if we should schedule a release */
+ if (dir & GsmL1_Dir_TxDownlink) {
+ if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL;
+ } else if (dir & GsmL1_Dir_RxUplink) {
+ if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL;
+ }
+
+ /* now schedule the command and maybe dispatch it */
+ return enqueue_sapi_deact_cmd(lchan, sapi, dir);
+}
+
+static int release_sapis_for_ho(struct gsm_lchan *lchan)
+{
+ int res = 0;
+ int i;
+
+ const struct lchan_sapis *s4l = &sapis_for_ho;
+
+ for (i = s4l->num_sapis-1; i >= 0; i--)
+ res |= check_sapi_release(lchan,
+ s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ return res;
+}
+
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ int i, res;
+
+ res = 0;
+
+ /* The order matters.. the Facch needs to be released first */
+ for (i = s4l->num_sapis-1; i >= 0; i--) {
+ /* Stop the alive timer once we deactivate the SCH */
+ if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch)
+ osmo_timer_del(&fl1h->alive_timer);
+
+ /* Release if it was allocated */
+ res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ }
+
+ /* always attempt to disable the RACH burst */
+ res |= release_sapis_for_ho(lchan);
+
+ /* nothing was queued */
+ if (res == 0) {
+ LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ }
+
+ return res;
+}
+
+static void enqueue_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to release all active SAPIs */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
+{
+ lchan_set_state(lchan, LCHAN_S_REL_REQ);
+ enqueue_rel_marker(lchan);
+ return 0;
+}
+
+static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to check if the SACCH is allocated */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_SACCH_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
+{
+ enqueue_sacch_rel_marker(lchan);
+ return 0;
+}
+
+/* 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: more checks if the attributes are valid */
+
+ switch (msg_type) {
+ case NM_MT_SET_CHAN_ATTR:
+ /* our L1 only supports one global TSC for all channels
+ * one one TRX, so we need to make sure not to activate
+ * channels with a different TSC!! */
+ if (TLVP_PRES_LEN(new_attr, NM_ATT_TSC, 1) &&
+ *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) {
+ LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n",
+ *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7);
+ return -NM_NACK_PARAM_RANGE;
+ }
+ break;
+ }
+ 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)
+{
+ if (kind == NM_OC_RADIO_CARRIER) {
+ struct gsm_bts_trx *trx = obj;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ /* Did we go through MphInit yet? If yes fire and forget */
+ if (fl1h->hLayer1)
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+ }
+
+ /* FIXME: we actaully need to send a ACK or NACK for the OML message */
+ return oml_fom_ack_nack(msg, 0);
+}
+
+/* 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:
+ rc = trx_init(obj);
+ break;
+ case NM_OC_CHANNEL:
+ rc = ts_opstart(obj);
+ break;
+ case NM_OC_BTS:
+ case NM_OC_SITE_MANAGER:
+ case NM_OC_BASEB_TRANSC:
+ case NM_OC_GPRS_NSE:
+ case NM_OC_GPRS_CELL:
+ case NM_OC_GPRS_NSVC:
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1);
+ rc = oml_mo_opstart_ack(mo);
+ if (mo->obj_class == NM_OC_BTS) {
+ oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK);
+ oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK);
+ }
+ break;
+ default:
+ rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
+ }
+ return rc;
+}
+
+int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, uint8_t adm_state)
+{
+ int rc = -EINVAL;
+ int granted = 0;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+
+ if (mo->procedure_pending) {
+ LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: "
+ "pending procedure on RC %d\n",
+ ((struct gsm_bts_trx *)obj)->nr);
+ return 0;
+ }
+ mo->procedure_pending = 1;
+ switch (adm_state) {
+ case NM_STATE_LOCKED:
+ rc = trx_rf_lock(obj, 1, NULL);
+ break;
+ case NM_STATE_UNLOCKED:
+ rc = trx_rf_lock(obj, 0, NULL);
+ break;
+ default:
+ granted = 1;
+ break;
+ }
+
+ if (!granted && rc == 0)
+ /* in progress, will send ack/nack after completion */
+ return 0;
+
+ mo->procedure_pending = 0;
+
+ break;
+ default:
+ /* blindly accept all state changes */
+ granted = 1;
+ break;
+ }
+
+ if (granted) {
+ mo->nm_state.administrative = adm_state;
+ return oml_mo_statechg_ack(mo);
+ } else
+ return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
+
+}
+
+int l1if_rsl_chan_act(struct gsm_lchan *lchan)
+{
+ //uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE);
+ //uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE);
+ lchan_activate(lchan);
+ return 0;
+}
+
+/**
+ * Modify the given lchan in the handover scenario. This is a lot like
+ * second channel activation but with some additional activation.
+ */
+int l1if_rsl_chan_mod(struct gsm_lchan *lchan)
+{
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ if (lchan->ho.active == HANDOVER_NONE)
+ return -1;
+
+ LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n",
+ gsm_lchan_name(lchan));
+
+ /* Give up listening to RACH bursts */
+ release_sapis_for_ho(lchan);
+
+ /* Activate the normal SAPIs */
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+ return 0;
+}
+
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan)
+{
+ /* A duplicate RF Release Request, ignore it */
+ if (lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n",
+ gsm_lchan_name(lchan));
+ return 0;
+ }
+
+ lchan_deactivate(lchan);
+ return 0;
+}
+
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan)
+{
+ /* Only de-activate the SACCH if the lchan is active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return 0;
+ return bts_model_lchan_deactivate_sacch(lchan);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx);
+
+ return l1if_activate_rf(fl1, 0);
+}
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+ return l1if_set_txpower(trx_femtol1_hdl(trx), ((float) p_trxout_mdBm)/1000.0);
+}
+
+static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf;
+ struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
+ OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);
+
+ LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n",
+ gsm_lchan_name(ts->lchan));
+
+ cb_ts_disconnected(ts);
+
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx);
+ GsmL1_MphDisconnectReq_t *cr;
+
+ DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan));
+ cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h,
+ l1p_handle_for_ts(ts));
+ cr->u8Tn = ts->nr;
+
+ return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL);
+}
+
+static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
+ struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
+ OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);
+
+ DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n",
+ gsm_lchan_name(ts->lchan),
+ gsm_pchan_name(ts->pchan),
+ ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "",
+ ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "",
+ ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : "");
+
+ cb_ts_connected(ts, 0);
+
+ return 0;
+}
+
+void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config as_pchan)
+{
+ int rc;
+
+ rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL);
+ if (rc)
+ cb_ts_connected(ts, rc);
+}
diff --git a/src/osmo-bts-sysmo/oml_router.c b/src/osmo-bts-sysmo/oml_router.c
new file mode 100644
index 00000000..f3d08373
--- /dev/null
+++ b/src/osmo-bts-sysmo/oml_router.c
@@ -0,0 +1,129 @@
+/* Beginnings of an OML router */
+
+/* (C) 2014 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "oml_router.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/msg_utils.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what)
+{
+ struct msgb *msg;
+ int rc;
+
+ msg = oml_msgb_alloc();
+ if (!msg) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n");
+ return -1;
+ }
+
+ rc = recv(fd->fd, msg->tail, msg->data_len, 0);
+ if (rc <= 0) {
+ close(fd->fd);
+ osmo_fd_unregister(fd);
+ fd->fd = -1;
+ goto err;
+ }
+
+ msg->l1h = msgb_put(msg, rc);
+ rc = msg_verify_ipa_structure(msg);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "OML Router: Invalid IPA message rc(%d)\n", rc);
+ goto err;
+ }
+
+ rc = msg_verify_oml_structure(msg);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR,
+ "OML Router: Invalid OML message rc(%d)\n", rc);
+ goto err;
+ }
+
+ /* todo dispatch message */
+
+err:
+ msgb_free(msg);
+ return -1;
+}
+
+static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what)
+{
+ int fd;
+ struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data;
+
+ /* Accept only one connection at a time. De-register it */
+ if (read_fd->fd > -1) {
+ LOGP(DL1C, LOGL_NOTICE,
+ "New OML router connection. Closing old one.\n");
+ close(read_fd->fd);
+ osmo_fd_unregister(read_fd);
+ read_fd->fd = -1;
+ }
+
+ fd = accept(accept_fd->fd, NULL, NULL);
+ if (fd < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n",
+ strerror(errno));
+ return -1;
+ }
+
+ read_fd->fd = fd;
+ if (osmo_fd_register(read_fd) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n");
+ close(fd);
+ read_fd->fd = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+int oml_router_init(struct gsm_bts *bts, const char *path,
+ struct osmo_fd *accept_fd, struct osmo_fd *read_fd)
+{
+ int rc;
+
+ memset(accept_fd, 0, sizeof(*accept_fd));
+ memset(read_fd, 0, sizeof(*read_fd));
+
+ accept_fd->cb = oml_router_accept_cb;
+ accept_fd->data = read_fd;
+
+ read_fd->cb = oml_router_read_cb;
+ read_fd->data = bts;
+ read_fd->when = BSC_FD_READ;
+ read_fd->fd = -1;
+
+ rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0,
+ path,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK);
+ return rc;
+}
diff --git a/src/osmo-bts-sysmo/oml_router.h b/src/osmo-bts-sysmo/oml_router.h
new file mode 100644
index 00000000..55f0681d
--- /dev/null
+++ b/src/osmo-bts-sysmo/oml_router.h
@@ -0,0 +1,13 @@
+#pragma once
+
+struct gsm_bts;
+struct osmo_fd;
+
+/**
+ * The default path sysmobts will listen for incoming
+ * registrations for OML routing and sending.
+ */
+#define OML_ROUTER_PATH "/var/run/sysmobts_oml_router"
+
+
+int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read);
diff --git a/src/osmo-bts-sysmo/sysmobts_ctrl.c b/src/osmo-bts-sysmo/sysmobts_ctrl.c
new file mode 100644
index 00000000..21df88e5
--- /dev/null
+++ b/src/osmo-bts-sysmo/sysmobts_ctrl.c
@@ -0,0 +1,274 @@
+/* Control Interface for sysmoBTS */
+
+/* (C) 2014 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+
+
+/* for control interface */
+
+#ifndef HW_SYSMOBTS_V1
+CTRL_CMD_DEFINE(clock_info, "clock-info");
+static int ctrl_clkinfo_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ struct ctrl_cmd_def *cd = data;
+ struct ctrl_cmd *cmd = cd->cmd;
+
+ LOGP(DL1C, LOGL_NOTICE,
+ "RfClockInfo iClkCor=%d/clkSrc=%s Err=%d/ErrRes=%d/clkSrc=%s\n",
+ sysp->u.rfClockInfoCnf.rfTrx.iClkCor,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrx.clkSrc),
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc));
+
+ if (ctrl_cmd_def_is_zombie(cd)) {
+ msgb_free(resp);
+ return 0;
+ }
+
+ cmd->reply = talloc_asprintf(cmd, "%d,%s,%d,%d,%s",
+ sysp->u.rfClockInfoCnf.rfTrx.iClkCor,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrx.clkSrc),
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes,
+ get_value_string(femtobts_clksrc_names,
+ sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc));
+
+ ctrl_cmd_def_send(cd);
+
+ msgb_free(resp);
+
+ return 0;
+}
+static int get_clock_info(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct ctrl_cmd_def *cd;
+
+ /* geneate a deferred control command */
+ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
+
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 0;
+
+ return l1if_req_compl(fl1h, msg, ctrl_clkinfo_cb, cd);
+}
+static int ctrl_set_clkinfo_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct ctrl_cmd_def *cd = data;
+ struct ctrl_cmd *cmd = cd->cmd;
+
+ if (ctrl_cmd_def_is_zombie(cd)) {
+ msgb_free(resp);
+ return 0;
+ }
+
+ cmd->reply = "success";
+
+ ctrl_cmd_def_send(cd);
+
+ msgb_free(resp);
+
+ return 0;
+}
+static int clock_setup_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ msgb_free(resp);
+ return 0;
+}
+
+static int set_clock_info(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct ctrl_cmd_def *cd;
+
+ /* geneate a deferred control command */
+ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
+
+ /* Set GPS/PPS as reference */
+ sysp->id = SuperFemto_PrimId_RfClockSetupReq;
+ sysp->u.rfClockSetupReq.rfTrx.iClkCor = get_clk_cal(fl1h);
+ sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src;
+ sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps;
+ l1if_req_compl(fl1h, msg, clock_setup_cb, NULL);
+
+ /* Reset the error counters */
+ msg = sysp_msgb_alloc();
+ sysp = msgb_sysprim(msg);
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 1;
+
+ l1if_req_compl(fl1h, msg, ctrl_set_clkinfo_cb, cd);
+
+ return CTRL_CMD_HANDLED;
+}
+
+static int verify_clock_info(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ return 0;
+}
+
+
+CTRL_CMD_DEFINE(clock_corr, "clock-correction");
+static int ctrl_get_clkcorr_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ struct ctrl_cmd_def *cd = data;
+ struct ctrl_cmd *cmd = cd->cmd;
+
+ if (ctrl_cmd_def_is_zombie(cd)) {
+ msgb_free(resp);
+ return 0;
+ }
+
+ cmd->reply = talloc_asprintf(cmd, "%d",
+ sysp->u.rfClockInfoCnf.rfTrx.iClkCor);
+
+ ctrl_cmd_def_send(cd);
+
+ msgb_free(resp);
+
+ return 0;
+}
+static int get_clock_corr(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct ctrl_cmd_def *cd;
+
+ /* we could theoretically simply respond with a cached value, but I
+ * prefer to to ask the actual L1 about the currently used value to
+ * avoid any mistakes */
+
+ /* geneate a deferred control command */
+ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
+
+ sysp->id = SuperFemto_PrimId_RfClockInfoReq;
+ sysp->u.rfClockInfoReq.u8RstClkCal = 0;
+
+ l1if_req_compl(fl1h, msg, ctrl_get_clkcorr_cb, cd);
+
+ return CTRL_CMD_HANDLED;
+}
+static int ctrl_set_clkcorr_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ SuperFemto_Prim_t *sysp = msgb_sysprim(resp);
+ struct ctrl_cmd_def *cd = data;
+ struct ctrl_cmd *cmd = cd->cmd;
+
+ if (ctrl_cmd_def_is_zombie(cd)) {
+ msgb_free(resp);
+ return 0;
+ }
+
+ if (sysp->u.rfClockSetupCnf.status != GsmL1_Status_Success) {
+ cmd->type = CTRL_CMD_ERROR;
+ cmd->reply = "Error setting new correction value.";
+ } else
+ cmd->reply = "success";
+
+ ctrl_cmd_def_send(cd);
+
+ msgb_free(resp);
+
+ return 0;
+}
+static int set_clock_corr(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+ struct msgb *msg = sysp_msgb_alloc();
+ SuperFemto_Prim_t *sysp = msgb_sysprim(msg);
+ struct ctrl_cmd_def *cd;
+
+ fl1h->clk_cal = atoi(cmd->value);
+
+ /* geneate a deferred control command */
+ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10);
+
+ sysp->id = SuperFemto_PrimId_RfClockSetupReq;
+ sysp->u.rfClockSetupReq.rfTrx.iClkCor = fl1h->clk_cal;
+ sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src;
+ sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps;
+
+ l1if_req_compl(fl1h, msg, ctrl_set_clkcorr_cb, cd);
+
+ return CTRL_CMD_HANDLED;
+}
+
+static int verify_clock_corr(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ /* FIXME: check the range */
+ return 0;
+}
+#endif /* HW_SYSMOBTS_V1 */
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ int rc = 0;
+
+#ifndef HW_SYSMOBTS_V1
+ rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_clock_info);
+ rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_clock_corr);
+#endif /* HW_SYSMOBTS_V1 */
+
+ return rc;
+}
diff --git a/src/osmo-bts-sysmo/sysmobts_vty.c b/src/osmo-bts-sysmo/sysmobts_vty.c
new file mode 100644
index 00000000..b105bf4d
--- /dev/null
+++ b/src/osmo-bts-sysmo/sysmobts_vty.c
@@ -0,0 +1,542 @@
+/* VTY interface for sysmoBTS */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2012,2013 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/rsl.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+#include "utils.h"
+
+extern int lchan_activate(struct gsm_lchan *lchan);
+
+#define TRX_STR "Transceiver related commands\n" "TRX number\n"
+
+#define SHOW_TRX_STR \
+ SHOW_STR \
+ TRX_STR
+#define DSP_TRACE_F_STR "DSP Trace Flag\n"
+
+static struct gsm_bts *vty_bts;
+
+/* configuration */
+
+DEFUN(cfg_phy_clkcal_eeprom, cfg_phy_clkcal_eeprom_cmd,
+ "clock-calibration eeprom",
+ "Use the eeprom clock calibration value\n")
+{
+ struct phy_instance *pinst = vty->index;
+
+ pinst->u.sysmobts.clk_use_eeprom = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_clkcal_def, cfg_phy_clkcal_def_cmd,
+ "clock-calibration default",
+ "Set the clock calibration value\n" "Default Clock DAC value\n")
+{
+ struct phy_instance *pinst = vty->index;
+
+ pinst->u.sysmobts.clk_use_eeprom = 0;
+ pinst->u.sysmobts.clk_cal = 0xffff;
+
+ return CMD_SUCCESS;
+}
+
+#ifdef HW_SYSMOBTS_V1
+DEFUN(cfg_phy_clkcal, cfg_phy_clkcal_cmd,
+ "clock-calibration <0-4095>",
+ "Set the clock calibration value\n" "Clock DAC value\n")
+{
+ unsigned int clkcal = atoi(argv[0]);
+ struct phy_instance *pinst = vty->index;
+
+ pinst->u.sysmobts.clk_use_eeprom = 0;
+ pinst->u.sysmobts.clk_cal = clkcal & 0xfff;
+
+ return CMD_SUCCESS;
+}
+#else
+DEFUN(cfg_phy_clkcal, cfg_phy_clkcal_cmd,
+ "clock-calibration <-4095-4095>",
+ "Set the clock calibration value\n" "Offset in PPB\n")
+{
+ int clkcal = atoi(argv[0]);
+ struct phy_instance *pinst = vty->index;
+
+ pinst->u.sysmobts.clk_use_eeprom = 0;
+ pinst->u.sysmobts.clk_cal = clkcal;
+
+ return CMD_SUCCESS;
+}
+#endif
+
+DEFUN(cfg_phy_clksrc, cfg_phy_clksrc_cmd,
+ "clock-source (tcxo|ocxo|ext|gps)",
+ "Set the clock source value\n"
+ "Use the TCXO\n"
+ "Use the OCXO\n"
+ "Use an external clock\n"
+ "Use the GPS pps\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int rc;
+
+ rc = get_string_value(femtobts_clksrc_names, argv[0]);
+ if (rc < 0)
+ return CMD_WARNING;
+
+ pinst->u.sysmobts.clk_src = rc;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd,
+ "trx-calibration-path PATH",
+ "Set the path name to TRX calibration data\n" "Path name\n")
+{
+ struct phy_instance *pinst = vty->index;
+
+ if (pinst->u.sysmobts.calib_path)
+ talloc_free(pinst->u.sysmobts.calib_path);
+
+ pinst->u.sysmobts.calib_path = talloc_strdup(pinst, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_trx_ul_power_target, cfg_trx_ul_power_target_cmd,
+ "uplink-power-target <-110-0>",
+ "Obsolete alias for bts uplink-power-target\n"
+ "Target uplink Rx level in dBm\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->bts->ul_power_target = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd,
+ "nominal-tx-power <0-100>",
+ "Set the nominal transmit output power in dBm\n"
+ "Nominal transmit output power level in dBm\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->nominal_power = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd,
+ "HIDDEN", TRX_STR)
+{
+ struct phy_instance *pinst = vty->index;
+ unsigned int flag;
+
+ flag = get_string_value(femtobts_tracef_names, argv[1]);
+ pinst->u.sysmobts.dsp_trace_f |= ~flag;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
+ "HIDDEN", NO_STR TRX_STR)
+{
+ struct phy_instance *pinst = vty->index;
+ unsigned int flag;
+
+ flag = get_string_value(femtobts_tracef_names, argv[1]);
+ pinst->u.sysmobts.dsp_trace_f &= ~flag;
+
+ return CMD_SUCCESS;
+}
+
+/* runtime */
+
+DEFUN(show_phy_clksrc, show_trx_clksrc_cmd,
+ "show phy <0-255> clock-source",
+ SHOW_TRX_STR "Display the clock source for this TRX")
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst = vty_get_phy_instance(vty, phy_nr, 0);
+
+ if (!pinst)
+ return CMD_WARNING;
+
+ vty_out(vty, "PHY Clock Source: %s%s",
+ get_value_string(femtobts_clksrc_names,
+ pinst->u.sysmobts.clk_src), VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
+ "show trx <0-0> dsp-trace-flags",
+ SHOW_TRX_STR "Display the current setting of the DSP trace flags")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct femtol1_hdl *fl1h;
+ int i;
+
+ if (!trx)
+ return CMD_WARNING;
+
+ fl1h = trx_femtol1_hdl(trx);
+
+ vty_out(vty, "Femto L1 DSP trace flags:%s", VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(femtobts_tracef_names); i++) {
+ const char *endis;
+
+ if (femtobts_tracef_names[i].value == 0 &&
+ femtobts_tracef_names[i].str == NULL)
+ break;
+
+ if (fl1h->dsp_trace_f & femtobts_tracef_names[i].value)
+ endis = "enabled";
+ else
+ endis = "disabled";
+
+ vty_out(vty, "DSP Trace %-15s %s%s",
+ femtobts_tracef_names[i].str, endis,
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+
+}
+
+DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR)
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst;
+ struct femtol1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.sysmobts.hdl;
+ flag = get_string_value(femtobts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR)
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_instance *pinst;
+ struct femtol1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.sysmobts.hdl;
+ flag = get_string_value(femtobts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_sys_info, show_sys_info_cmd,
+ "show phy <0-255> instance <0-255> system-information",
+ SHOW_TRX_STR "Display information about system\n")
+{
+ int phy_nr = atoi(argv[0]);
+ int inst_nr = atoi(argv[1]);
+ struct phy_link *plink = phy_link_by_num(phy_nr);
+ struct phy_instance *pinst;
+ struct femtol1_hdl *fl1h;
+ int i;
+
+ if (!plink) {
+ vty_out(vty, "Cannot find PHY link %u%s",
+ phy_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ pinst = phy_instance_by_num(plink, inst_nr);
+ if (!pinst) {
+ vty_out(vty, "Cannot find PHY instance %u%s",
+ phy_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ fl1h = pinst->u.sysmobts.hdl;
+
+ vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s",
+ fl1h->hw_info.dsp_version[0],
+ fl1h->hw_info.dsp_version[1],
+ fl1h->hw_info.dsp_version[2],
+ fl1h->hw_info.fpga_version[0],
+ fl1h->hw_info.fpga_version[1],
+ fl1h->hw_info.fpga_version[2], VTY_NEWLINE);
+
+ vty_out(vty, "GSM Band Support: ");
+ for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) {
+ if (fl1h->hw_info.band_support & (1 << i))
+ vty_out(vty, "%s ", gsm_band_name(1 << i));
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(activate_lchan, activate_lchan_cmd,
+ "trx <0-0> <0-7> (activate|deactivate) <0-7>",
+ TRX_STR
+ "Timeslot number\n"
+ "Activate Logical Channel\n"
+ "Deactivate Logical Channel\n"
+ "Logical Channel Number\n" )
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[3]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ if (!strcmp(argv[2], "activate"))
+ lchan_activate(lchan);
+ else
+ lchan_deactivate(lchan);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_tx_power, set_tx_power_cmd,
+ "trx <0-0> tx-power <-110-100>",
+ TRX_STR
+ "Set transmit power (override BSC)\n"
+ "Transmit power in dBm\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int power = atoi(argv[1]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+
+ power_ramp_start(trx, to_mdB(power), 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(reset_rf_clock_ctr, reset_rf_clock_ctr_cmd,
+ "trx <0-0> rf-clock-info reset",
+ TRX_STR
+ "RF Clock Information\n" "Reset the counter\n")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ l1if_rf_clock_info_reset(fl1h);
+ return CMD_SUCCESS;
+}
+
+DEFUN(correct_rf_clock_ctr, correct_rf_clock_ctr_cmd,
+ "trx <0-0> rf-clock-info correct",
+ TRX_STR
+ "RF Clock Information\n" "Apply\n")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ l1if_rf_clock_info_correct(fl1h);
+ return CMD_SUCCESS;
+}
+
+DEFUN(loopback, loopback_cmd,
+ "trx <0-0> <0-7> loopback <0-1>",
+ TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_loopback, no_loopback_cmd,
+ "no trx <0-0> <0-7> loopback <0-1>",
+ NO_STR TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 0;
+
+ return CMD_SUCCESS;
+}
+
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ if (trx->nominal_power != get_p_max_out_mdBm(trx))
+ vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,
+ VTY_NEWLINE);
+}
+
+void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
+{
+}
+
+void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
+{
+ int i;
+
+ for (i = 0; i < 32; i++) {
+ if (pinst->u.sysmobts.dsp_trace_f & (1 << i)) {
+ const char *name;
+ name = get_value_string(femtobts_tracef_names, (1 << i));
+ vty_out(vty, " dsp-trace-flag %s%s", name,
+ VTY_NEWLINE);
+ }
+ }
+
+ if (pinst->u.sysmobts.clk_use_eeprom)
+ vty_out(vty, " clock-calibration eeprom%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " clock-calibration %d%s",
+ pinst->u.sysmobts.clk_cal, VTY_NEWLINE);
+ if (pinst->u.sysmobts.calib_path)
+ vty_out(vty, " trx-calibration-path %s%s",
+ pinst->u.sysmobts.calib_path, VTY_NEWLINE);
+ if (pinst->u.sysmobts.clk_src)
+ vty_out(vty, " clock-source %s%s",
+ get_value_string(femtobts_clksrc_names,
+ pinst->u.sysmobts.clk_src), VTY_NEWLINE);
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ /* runtime-patch the command strings with debug levels */
+ dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names,
+ "trx <0-0> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs,
+ TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names,
+ "no trx <0-0> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs,
+ NO_STR TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ cfg_phy_dsp_trace_f_cmd.string =
+ vty_cmd_string_from_valstr(bts, femtobts_tracef_names,
+ "dsp-trace-flag (", "|", ")",
+ VTY_DO_LOWER);
+ cfg_phy_dsp_trace_f_cmd.doc =
+ vty_cmd_string_from_valstr(bts, femtobts_tracef_docs,
+ DSP_TRACE_F_STR, "\n", "", 0);
+
+ cfg_phy_no_dsp_trace_f_cmd.string =
+ vty_cmd_string_from_valstr(bts, femtobts_tracef_names,
+ "no dsp-trace-flag (", "|", ")",
+ VTY_DO_LOWER);
+ cfg_phy_no_dsp_trace_f_cmd.doc =
+ vty_cmd_string_from_valstr(bts, femtobts_tracef_docs,
+ NO_STR DSP_TRACE_F_STR, "\n",
+ "", 0);
+
+ install_element_ve(&show_dsp_trace_f_cmd);
+ install_element_ve(&show_sys_info_cmd);
+ install_element_ve(&show_trx_clksrc_cmd);
+ install_element_ve(&dsp_trace_f_cmd);
+ install_element_ve(&no_dsp_trace_f_cmd);
+
+ install_element(ENABLE_NODE, &activate_lchan_cmd);
+ install_element(ENABLE_NODE, &set_tx_power_cmd);
+ install_element(ENABLE_NODE, &reset_rf_clock_ctr_cmd);
+ install_element(ENABLE_NODE, &correct_rf_clock_ctr_cmd);
+
+ install_element(ENABLE_NODE, &loopback_cmd);
+ install_element(ENABLE_NODE, &no_loopback_cmd);
+
+ install_element(BTS_NODE, &cfg_bts_auto_band_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd);
+
+ install_element(TRX_NODE, &cfg_trx_ul_power_target_cmd);
+ install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+
+ install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_clkcal_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_clkcal_eeprom_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_clkcal_def_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_clksrc_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd);
+
+ return 0;
+}
diff --git a/src/osmo-bts-sysmo/tch.c b/src/osmo-bts-sysmo/tch.c
new file mode 100644
index 00000000..54e73136
--- /dev/null
+++ b/src/osmo-bts-sysmo/tch.c
@@ -0,0 +1,684 @@
+/* Traffic channel support for Sysmocom BTS L1 */
+
+/* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#include <sysmocom/femtobts/superfemto.h>
+#include <sysmocom/femtobts/gsml1prim.h>
+#include <sysmocom/femtobts/gsml1const.h>
+#include <sysmocom/femtobts/gsml1types.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+
+static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+#ifdef USE_L1_RTP_MODE
+ /* new L1 can deliver bits like we need them */
+ cur = msgb_put(msg, GSM_FR_BYTES);
+ memcpy(cur, l1_payload, GSM_FR_BYTES);
+#else
+ /* step1: reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, payload_len);
+
+ cur = msgb_put(msg, GSM_FR_BYTES);
+
+ /* step2: we need to shift the entire L1 payload by 4 bits right */
+ osmo_nibble_shift_right(cur, l1_payload, GSM_FR_BITS/4);
+
+ cur[0] |= 0xD0;
+#endif /* USE_L1_RTP_MODE */
+
+ lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan);
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+#ifdef USE_L1_RTP_MODE
+ /* new L1 can deliver bits like we need them */
+ memcpy(l1_payload, rtp_payload, GSM_FR_BYTES);
+#else
+ /* step2: we need to shift the RTP payload left by one nibble*/
+ osmo_nibble_shift_left_unal(l1_payload, rtp_payload, GSM_FR_BITS/4);
+
+ /* step1: reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, payload_len);
+#endif /* USE_L1_RTP_MODE */
+ return GSM_FR_BYTES;
+}
+
+#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE)
+static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload,
+ uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+#ifdef USE_L1_RTP_MODE
+ /* new L1 can deliver bits like we need them */
+ cur = msgb_put(msg, GSM_EFR_BYTES);
+ memcpy(cur, l1_payload, GSM_EFR_BYTES);
+#else
+ /* step1: reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, payload_len);
+
+ cur = msgb_put(msg, GSM_EFR_BYTES);
+
+ /* step 2: we need to shift the entire L1 payload by 4 bits right */
+ osmo_nibble_shift_right(cur, l1_payload, GSM_EFR_BITS/4);
+
+ cur[0] |= 0xC0;
+#endif /* USE_L1_RTP_MODE */
+ enum osmo_amr_type ft;
+ enum osmo_amr_quality bfi;
+ uint8_t cmr;
+ int8_t sti, cmi;
+ osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti);
+ lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan);
+
+ return msg;
+}
+
+static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len)
+{
+#ifndef USE_L1_RTP_MODE
+#error We don't support EFR with L1 that doesn't support RTP mode!
+#else
+ memcpy(l1_payload, rtp_payload, payload_len);
+
+ return payload_len;
+#endif
+}
+#else
+#warning No EFR support in L1
+#endif /* L1_HAS_EFR */
+
+#ifdef USE_L1_RTP_MODE
+/* change the bit-order of each unaligned field inside the HR codec
+ * payload from little-endian bit-ordering to bit-endian and vice-versa.
+ * This is required on all sysmoBTS DSP versions < 5.3.3 in order to
+ * be compliant with ETSI TS 101 318 Chapter 5.2 */
+static void hr_jumble(uint8_t *dst, const uint8_t *src)
+{
+ /* Table 2 / Section 5.2.1 of ETSI TS 101 381 */
+ const int p_unvoiced[] =
+ { 5, 11, 9, 8, 1, 2, 7, 7, 5, 7, 7, 5, 7, 7, 5, 7, 7, 5 };
+ /* Table 3 / Section 5.2.1 of ETSI TS 101 381 */
+ const int p_voiced[] =
+ { 5, 11, 9, 8, 1, 2, 8, 9, 5, 4, 9, 5, 4, 9, 5, 4, 9, 5 };
+
+ int base, i, j, l, si, di;
+ const int *p;
+
+ memset(dst, 0x00, GSM_HR_BYTES);
+
+ p = (src[4] & 0x30) ? p_voiced : p_unvoiced;
+
+ base = 0;
+ for (i = 0; i < 18; i++) {
+ l = p[i];
+ for (j = 0; j < l; j++) {
+ si = base + j;
+ di = base + l - j - 1;
+
+ if (src[si >> 3] & (1 << (7 - (si & 7))))
+ dst[di >> 3] |= (1 << (7 - (di & 7)));
+ }
+
+ base += l;
+ }
+}
+#endif /* USE_L1_RTP_MODE */
+
+static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return NULL;
+ }
+
+ cur = msgb_put(msg, GSM_HR_BYTES);
+#ifdef USE_L1_RTP_MODE
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ if (fl1h->rtp_hr_jumble_needed)
+ hr_jumble(cur, l1_payload);
+ else
+ memcpy(cur, l1_payload, GSM_HR_BYTES);
+#else /* USE_L1_RTP_MODE */
+ memcpy(cur, l1_payload, GSM_HR_BYTES);
+ /* reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(cur, GSM_HR_BYTES);
+#endif /* USE_L1_RTP_MODE */
+
+ lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan);
+
+ return msg;
+}
+
+/*! \brief convert GSM-FR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ unsigned int payload_len, struct gsm_lchan *lchan)
+{
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n",
+ payload_len, GSM_HR_BYTES);
+ return 0;
+ }
+
+#ifdef USE_L1_RTP_MODE
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx);
+ if (fl1h->rtp_hr_jumble_needed)
+ hr_jumble(l1_payload, rtp_payload);
+ else
+ memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
+#else /* USE_L1_RTP_MODE */
+ memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
+ /* reverse the bit-order of each payload byte */
+ osmo_revbytebits_buf(l1_payload, GSM_HR_BYTES);
+#endif /* USE_L1_RTP_MODE */
+
+ return GSM_HR_BYTES;
+}
+
+static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len,
+ struct gsm_lchan *lchan)
+{
+#ifndef USE_L1_RTP_MODE
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+#endif
+ struct msgb *msg;
+ uint8_t amr_if2_len = payload_len - 2;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
+ if (!msg)
+ return NULL;
+
+#ifdef USE_L1_RTP_MODE
+ cur = msgb_put(msg, amr_if2_len);
+ memcpy(cur, l1_payload+2, amr_if2_len);
+
+ /*
+ * Audiocode's MGW doesn't like receiving CMRs that are not
+ * the same as the previous one. This means we need to patch
+ * the content here.
+ */
+ if ((cur[0] & 0xF0) == 0xF0)
+ cur[0]= lchan->tch.last_cmr << 4;
+ else
+ lchan->tch.last_cmr = cur[0] >> 4;
+#else
+ u_int8_t cmr;
+ uint8_t ft = l1_payload[2] & 0xF;
+ uint8_t cmr_idx = l1_payload[1];
+ /* CMR == Unset means CMR was not transmitted at this TDMA */
+ if (cmr_idx == GsmL1_AmrCodecMode_Unset)
+ cmr = lchan->tch.last_cmr;
+ else if (cmr_idx >= amr_mrc->num_modes ||
+ cmr_idx > GsmL1_AmrCodecMode_Unset) {
+ /* Make sure the CMR of the phone is in the active codec set */
+ LOGP(DL1P, LOGL_NOTICE, "L1->RTP: overriding CMR IDX %u\n", cmr_idx);
+ cmr = AMR_CMR_NONE;
+ } else {
+ cmr = amr_mrc->bts_mode[cmr_idx].mode;
+ lchan->tch.last_cmr = cmr;
+ }
+
+ /* RFC 3267 4.4.1 Payload Header */
+ msgb_put_u8(msg, (cmr << 4));
+
+ /* RFC 3267 AMR TOC */
+ msgb_put_u8(msg, AMR_TOC_QBIT | (ft << 3));
+
+ cur = msgb_put(msg, amr_if2_len-1);
+
+ /* step1: reverse the bit-order within every byte */
+ osmo_revbytebits_buf(l1_payload+2, amr_if2_len);
+
+ /* step2: shift everything left by one nibble */
+ osmo_nibble_shift_left_unal(cur, l1_payload+2, amr_if2_len*2 -1);
+
+#endif /* USE_L1_RTP_MODE */
+
+ return msg;
+}
+
+/*! \brief convert AMR from RTP payload to L1 format
+ * \param[out] l1_payload payload part of L1 buffer
+ * \param[in] rtp_payload pointer to RTP payload data
+ * \param[in] payload_len length of \a rtp_payload
+ * \returns number of \a l1_payload bytes filled
+ */
+static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload,
+ uint8_t payload_len, uint8_t ft)
+{
+#ifdef USE_L1_RTP_MODE
+ memcpy(l1_payload, rtp_payload, payload_len);
+#else
+ uint8_t amr_if2_core_len = payload_len - 2;
+
+ /* step1: shift everything right one nibble; make space for FT */
+ osmo_nibble_shift_right(l1_payload+2, rtp_payload+2, amr_if2_core_len*2);
+ /* step2: reverse the bit-order within every byte of the IF2
+ * core frame contained in the RTP payload */
+ osmo_revbytebits_buf(l1_payload+2, amr_if2_core_len+1);
+
+ /* lower 4 bit of first FR2 byte contains FT */
+ l1_payload[2] |= ft;
+#endif /* USE_L1_RTP_MODE */
+ return payload_len;
+}
+
+#define RTP_MSGB_ALLOC_SIZE 512
+
+/*! \brief function for incoming RTP via TCH.req
+ * \param[in] rtp_pl buffer containing RTP payload
+ * \param[in] rtp_pl_len length of \a rtp_pl
+ * \param[in] use_cache Use cached payload instead of parsing RTP
+ * \param[in] marker RTP header Marker bit (indicates speech onset)
+ * \returns 0 if encoding result can be sent further to L1 without extra actions
+ * positive value if data is ready AND extra actions are required
+ * negative value otherwise (no data for L1 encoded)
+ *
+ * This function prepares a msgb with a L1 PH-DATA.req primitive and
+ * queues it into lchan->dl_tch_queue.
+ *
+ * Note that the actual L1 primitive header is not fully initialized
+ * yet, as things like the frame number, etc. are unknown at the time we
+ * pre-fill the primtive.
+ */
+int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+ bool use_cache, bool marker)
+{
+ uint8_t *payload_type;
+ uint8_t *l1_payload, ft;
+ int rc = 0;
+ bool is_sid = false;
+
+ DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(rtp_pl, rtp_pl_len));
+
+ payload_type = &data[0];
+ l1_payload = &data[1];
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F) {
+ *payload_type = GsmL1_TchPlType_Fr;
+ rc = rtppayload_to_l1_fr(l1_payload,
+ rtp_pl, rtp_pl_len);
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len);
+ } else{
+ *payload_type = GsmL1_TchPlType_Hr;
+ rc = rtppayload_to_l1_hr(l1_payload,
+ rtp_pl, rtp_pl_len, lchan);
+ if (rc && lchan->ts->trx->bts->dtxd)
+ is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len);
+ }
+ if (is_sid)
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1);
+ break;
+#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE)
+ case GSM48_CMODE_SPEECH_EFR:
+ *payload_type = GsmL1_TchPlType_Efr;
+ rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
+ rtp_pl_len);
+ /* FIXME: detect and save EFR SID */
+ break;
+#endif
+ case GSM48_CMODE_SPEECH_AMR:
+ if (use_cache) {
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache,
+ lchan->tch.dtx.len, ft);
+ *len = lchan->tch.dtx.len + 1;
+ return 0;
+ }
+
+ rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn,
+ l1_payload, marker, len, &ft);
+ if (rc < 0)
+ return rc;
+ if (!dtx_dl_amr_enabled(lchan)) {
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ }
+
+ /* DTX DL-specific logic below: */
+ switch (lchan->tch.dtx.dl_amr_fsm->state) {
+ case ST_ONSET_V:
+ *payload_type = GsmL1_TchPlType_Amr_Onset;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ *len = 3;
+ return 1;
+ case ST_VOICE:
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_SID_F1:
+ if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstP1;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl,
+ rtp_pl_len, ft);
+ return 0;
+ }
+ /* AMR FR */
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_SID_F2:
+ *payload_type = GsmL1_TchPlType_Amr;
+ rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+ ft);
+ return 0;
+ case ST_F1_INH_V:
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstInH;
+ *len = 3;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ return 1;
+ case ST_U_INH_V:
+ *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH;
+ *len = 3;
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+ return 1;
+ case ST_SID_U:
+ case ST_U_NOINH:
+ return -EAGAIN;
+ case ST_FACCH:
+ return -EBADMSG;
+ default:
+ LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state "
+ "%d\n", lchan->tch.dtx.dl_amr_fsm->state);
+ return -EINVAL;
+ }
+ break;
+ default:
+ /* we don't support CSD modes */
+ rc = -1;
+ break;
+ }
+
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n",
+ gsm_lchan_name(lchan));
+ return -EBADMSG;
+ }
+
+ *len = rc + 1;
+
+ DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(data, *len));
+ return 0;
+}
+
+static int is_recv_only(uint8_t speech_mode)
+{
+ return (speech_mode & 0xF0) == (1 << 4);
+}
+
+/*! \brief receive a traffic L1 primitive for a given lchan */
+int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg);
+ GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd;
+ uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 };
+ struct msgb *rmsg = NULL;
+ struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
+
+ if (is_recv_only(lchan->abis_ip.speech_mode))
+ return -EAGAIN;
+
+ if (data_ind->msgUnitParam.u8Size < 1) {
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr);
+ /* Push empty payload to upper layers */
+ rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP");
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
+ data_ind->measParam.fBer * 10000,
+ data_ind->measParam.fLinkQuality * 10);
+ }
+
+ payload_type = data_ind->msgUnitParam.u8Buffer[0];
+ payload = data_ind->msgUnitParam.u8Buffer + 1;
+ payload_len = data_ind->msgUnitParam.u8Size - 1;
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE)
+ case GsmL1_TchPlType_Efr:
+#endif
+ if (lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Hr:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Amr:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Amr_Onset:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ /* according to 3GPP TS 26.093 ONSET frames precede the first
+ speech frame of a speech burst - set the marker for next RTP
+ frame */
+ lchan->rtp_tx_marker = true;
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP1:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP2:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstInH:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ lchan->rtp_tx_marker = true;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ case GsmL1_TchPlType_Amr_SidUpdateInH:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ lchan->rtp_tx_marker = true;
+ LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 "
+ "(%d bytes)\n", payload_len);
+ break;
+ default:
+ LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_tch_pl_names, payload_type));
+ break;
+ }
+
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+ rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Hr:
+ rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan);
+ break;
+#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE)
+ case GsmL1_TchPlType_Efr:
+ rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan);
+ break;
+#endif
+ case GsmL1_TchPlType_Amr:
+ rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan);
+ break;
+ case GsmL1_TchPlType_Amr_SidFirstP1:
+ memcpy(sid_first, payload, payload_len);
+ int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD);
+ if (len < 0)
+ return 0;
+ rmsg = l1_to_rtppayload_amr(sid_first, len, lchan);
+ break;
+ /* FIXME: what about GsmL1_TchPlType_Amr_SidBad? not well documented. */
+ }
+
+ if (rmsg)
+ return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
+ data_ind->measParam.fBer * 10000,
+ data_ind->measParam.fLinkQuality * 10);
+
+ return 0;
+
+err_payload_match:
+ LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n",
+ gsm_lchan_name(lchan),
+ get_value_string(femtobts_tch_pl_names, payload_type));
+ return -EINVAL;
+}
+
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn)
+{
+ struct msgb *msg;
+ GsmL1_Prim_t *l1p;
+ GsmL1_PhDataReq_t *data_req;
+ GsmL1_MsgUnitParam_t *msu_param;
+ uint8_t *payload_type;
+ uint8_t *l1_payload;
+ int rc;
+
+ msg = l1p_msgb_alloc();
+ if (!msg)
+ return NULL;
+
+ l1p = msgb_l1prim(msg);
+ data_req = &l1p->u.phDataReq;
+ msu_param = &data_req->msgUnitParam;
+ payload_type = &msu_param->u8Buffer[0];
+ l1_payload = &msu_param->u8Buffer[1];
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SPEECH_AMR:
+ if (lchan->type == GSM_LCHAN_TCH_H &&
+ dtx_dl_amr_enabled(lchan)) {
+ /* we have to explicitly handle sending SID FIRST P2 for
+ AMR HR in here */
+ *payload_type = GsmL1_TchPlType_Amr_SidFirstP2;
+ rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload,
+ false, &(msu_param->u8Size),
+ NULL);
+ if (rc == 0)
+ return msg;
+ }
+ *payload_type = GsmL1_TchPlType_Amr;
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ *payload_type = GsmL1_TchPlType_Fr;
+ else
+ *payload_type = GsmL1_TchPlType_Hr;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ *payload_type = GsmL1_TchPlType_Efr;
+ break;
+ default:
+ msgb_free(msg);
+ return NULL;
+ }
+
+ rc = repeat_last_sid(lchan, l1_payload, fn);
+ if (!rc) {
+ msgb_free(msg);
+ return NULL;
+ }
+ msu_param->u8Size = rc;
+
+ return msg;
+}
diff --git a/src/osmo-bts-sysmo/utils.c b/src/osmo-bts-sysmo/utils.c
new file mode 100644
index 00000000..0e3ef273
--- /dev/null
+++ b/src/osmo-bts-sysmo/utils.c
@@ -0,0 +1,116 @@
+/*
+ * Helper utilities that are used in OML
+ *
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2013 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "utils.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include "femtobts.h"
+#include "l1_if.h"
+
+int band_femto2osmo(GsmL1_FreqBand_t band)
+{
+ switch (band) {
+ case GsmL1_FreqBand_850:
+ return GSM_BAND_850;
+ case GsmL1_FreqBand_900:
+ return GSM_BAND_900;
+ case GsmL1_FreqBand_1800:
+ return GSM_BAND_1800;
+ case GsmL1_FreqBand_1900:
+ return GSM_BAND_1900;
+ default:
+ return -1;
+ }
+}
+
+static int band_osmo2femto(struct gsm_bts_trx *trx, enum gsm_band osmo_band)
+{
+ struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
+
+ /* check if the TRX hardware actually supports the given band */
+ if (!(fl1h->hw_info.band_support & osmo_band))
+ return -1;
+
+ /* if yes, convert from osmcoom style band definition to L1 band */
+ switch (osmo_band) {
+ case GSM_BAND_850:
+ return GsmL1_FreqBand_850;
+ case GSM_BAND_900:
+ return GsmL1_FreqBand_900;
+ case GSM_BAND_1800:
+ return GsmL1_FreqBand_1800;
+ case GSM_BAND_1900:
+ return GsmL1_FreqBand_1900;
+ default:
+ return -1;
+ }
+}
+
+/**
+ * Select the band that matches the ARFCN. In general the ARFCNs
+ * for GSM1800 and GSM1900 overlap and one needs to specify the
+ * rightband. When moving between GSM900/GSM1800 and GSM850/1900
+ * modifying the BTS configuration is a bit annoying. The auto-band
+ * configuration allows to ease with this transition.
+ */
+int sysmobts_select_femto_band(struct gsm_bts_trx *trx, uint16_t arfcn)
+{
+ enum gsm_band band;
+ struct gsm_bts *bts = trx->bts;
+ int rc;
+
+ if (!bts->auto_band)
+ return band_osmo2femto(trx, bts->band);
+
+ /*
+ * We need to check what will happen now.
+ */
+ rc = gsm_arfcn2band_rc(arfcn, &band);
+ if (rc) /* wrong ARFCN, give up */
+ return -1;
+
+ /* if we are already on the right band return */
+ if (band == bts->band)
+ return band_osmo2femto(trx, bts->band);
+
+ /* Check if it is GSM1800/GSM1900 */
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900)
+ return band_osmo2femto(trx, bts->band);
+
+ /*
+ * Now to the actual autobauding. We just want DCS/DCS and
+ * PCS/PCS for PCS we check for 850/1800 though
+ */
+ if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800)
+ || (band == GSM_BAND_1800 && bts->band == GSM_BAND_900)
+ || (band == GSM_BAND_850 && bts->band == GSM_BAND_1900))
+ return band_osmo2femto(trx, band);
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850)
+ return band_osmo2femto(trx, GSM_BAND_1900);
+
+ /* give up */
+ return -1;
+}
diff --git a/src/osmo-bts-sysmo/utils.h b/src/osmo-bts-sysmo/utils.h
new file mode 100644
index 00000000..45908d50
--- /dev/null
+++ b/src/osmo-bts-sysmo/utils.h
@@ -0,0 +1,12 @@
+#ifndef SYSMOBTS_UTILS_H
+#define SYSMOBTS_UTILS_H
+
+#include <stdint.h>
+#include "femtobts.h"
+
+struct gsm_bts_trx;
+
+int band_femto2osmo(GsmL1_FreqBand_t band);
+
+int sysmobts_select_femto_band(struct gsm_bts_trx *trx, uint16_t arfcn);
+#endif