diff options
Diffstat (limited to 'src/osmo-bts-sysmo')
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 |