From b92100ad36f40d3125ff945fbd38aece873d1718 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sun, 6 Sep 2015 16:04:32 +0200 Subject: [PATCH 1/9] Add support for Octasic OCTSDR-2G GSM PHY This adds support for a new PHY to OsmoBTS, the Octasic OCTSDR-2G PHY. This is a proprietary GSM PHY running on a familty of Octasic DSPs. --- .gitignore | 2 + configure.ac | 20 + doc/examples/octphy/osmo-bts.cfg | 28 + src/Makefile.am | 5 + src/osmo-bts-octphy/Makefile.am | 13 + src/osmo-bts-octphy/l1_if.c | 1365 ++++++++++++++++++++++++++ src/osmo-bts-octphy/l1_if.h | 89 ++ src/osmo-bts-octphy/l1_oml.c | 1399 +++++++++++++++++++++++++++ src/osmo-bts-octphy/l1_oml.h | 18 + src/osmo-bts-octphy/l1_tch.c | 402 ++++++++ src/osmo-bts-octphy/l1_utils.c | 130 +++ src/osmo-bts-octphy/l1_utils.h | 9 + src/osmo-bts-octphy/main.c | 305 ++++++ src/osmo-bts-octphy/octphy_hw_api.c | 364 +++++++ src/osmo-bts-octphy/octphy_hw_api.h | 14 + src/osmo-bts-octphy/octphy_vty.c | 193 ++++ src/osmo-bts-octphy/octpkt.c | 190 ++++ src/osmo-bts-octphy/octpkt.h | 22 + 18 files changed, 4568 insertions(+) create mode 100644 doc/examples/octphy/osmo-bts.cfg create mode 100644 src/osmo-bts-octphy/Makefile.am create mode 100644 src/osmo-bts-octphy/l1_if.c create mode 100644 src/osmo-bts-octphy/l1_if.h create mode 100644 src/osmo-bts-octphy/l1_oml.c create mode 100644 src/osmo-bts-octphy/l1_oml.h create mode 100644 src/osmo-bts-octphy/l1_tch.c create mode 100644 src/osmo-bts-octphy/l1_utils.c create mode 100644 src/osmo-bts-octphy/l1_utils.h create mode 100644 src/osmo-bts-octphy/main.c create mode 100644 src/osmo-bts-octphy/octphy_hw_api.c create mode 100644 src/osmo-bts-octphy/octphy_hw_api.h create mode 100644 src/osmo-bts-octphy/octphy_vty.c create mode 100644 src/osmo-bts-octphy/octpkt.c create mode 100644 src/osmo-bts-octphy/octpkt.h diff --git a/.gitignore b/.gitignore index 8f6babc57..af0a848e1 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ src/osmo-bts-sysmo/sysmobts-mgr src/osmo-bts-trx/osmobts-trx +src/osmo-bts-octphy/osmo-bts-octphy + tests/atconfig tests/package.m4 tests/agch/agch_test diff --git a/configure.ac b/configure.ac index 7b043a6ee..8239c122f 100644 --- a/configure.ac +++ b/configure.ac @@ -49,6 +49,25 @@ AC_ARG_ENABLE(trx, AC_MSG_RESULT([$enable_trx]) AM_CONDITIONAL(ENABLE_TRX, test "x$enable_trx" = "xyes") +AC_MSG_CHECKING([whether to enable support for Octasic OCTPHY-2G]) +AC_ARG_ENABLE(octphy, + AC_HELP_STRING([--enable-octphy], + [enable code for Octasic OCTPHY-2G [default=no]]), + [enable_octphy="yes"],[enable_octphy="no"]) +AC_ARG_WITH([octsdr-2g], [AS_HELP_STRING([--with-octsdr-2g], [Location of the OCTSDR-2G API header files])], + [octsdr2g_incdir="$withval"],[octsdr2g_incdir="`cd $srcdir; pwd`/src/osmo-bts-octphy/"]) +AC_SUBST([OCTSDR2G_INCDIR], $octsdr2g_incdir) +AC_MSG_RESULT([$enable_octphy]) +AM_CONDITIONAL(ENABLE_OCTPHY, test "x$enable_octphy" = "xyes") +if test "$enable_octphy" = "yes" ; then + oldCPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS -I$OCTSDR2G_INCDIR -I$srcdir/include $LIBOSMOCORE_CFLAGS" + AC_CHECK_HEADER([octphy/octvc1/gsm/octvc1_gsm_default.h],[], + [AC_MSG_ERROR([octphy/octvc1/gsm/octvc1_gsm_default.h can not be found in $octsdr2g_incdir])], + [#include ]) + CPPFLAGS=$oldCPPFLAGS +fi + # We share gsm_data.h with OpenBSC and need to be pointed to the source # directory of OpenBSC for now. AC_ARG_WITH([openbsc], @@ -83,6 +102,7 @@ AC_OUTPUT( src/common/Makefile src/osmo-bts-sysmo/Makefile src/osmo-bts-trx/Makefile + src/osmo-bts-octphy/Makefile include/Makefile include/osmo-bts/Makefile tests/Makefile diff --git a/doc/examples/octphy/osmo-bts.cfg b/doc/examples/octphy/osmo-bts.cfg new file mode 100644 index 000000000..07bcae010 --- /dev/null +++ b/doc/examples/octphy/osmo-bts.cfg @@ -0,0 +1,28 @@ +! +! OsmoBTS () configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level all everything + logging level rsl info + logging level oml info + logging level rll notice + logging level rr notice + logging level meas notice + logging level pag info + logging level l1c info + logging level l1p info + logging level dsp info + logging level abis notice +! +line vty + no login +! +bts 0 + band 1800 + ipa unit-id 1234 0 + oml remote-ip 127.0.0.1 + phy-hw-addr 00:0C:90:2e:80:1e + phy-netdev eth0.2342 diff --git a/src/Makefile.am b/src/Makefile.am index b8fe655c2..a29a9230b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,6 +3,11 @@ SUBDIRS = common if ENABLE_SYSMOBTS SUBDIRS += osmo-bts-sysmo endif + if ENABLE_TRX SUBDIRS += osmo-bts-trx endif + +if ENABLE_OCTPHY +SUBDIRS += osmo-bts-octphy +endif diff --git a/src/osmo-bts-octphy/Makefile.am b/src/osmo-bts-octphy/Makefile.am new file mode 100644 index 000000000..07fc6bce2 --- /dev/null +++ b/src/osmo-bts-octphy/Makefile.am @@ -0,0 +1,13 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) -I$(OCTSDR2G_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -lortp + +EXTRA_DIST = l1_if.h l1_oml.h l1_utils.h l1_octInit.h l1_octIoMsg.h + +bin_PROGRAMS = osmo-bts-octphy + +COMMON_SOURCES = main.c l1_if.c l1_oml.c l1_utils.c l1_tch.c octphy_hw_api.c octphy_vty.c octpkt.c + +osmo_bts_octphy_SOURCES = $(COMMON_SOURCES) +osmo_bts_octphy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c new file mode 100644 index 000000000..d104fa753 --- /dev/null +++ b/src/osmo-bts-octphy/l1_if.c @@ -0,0 +1,1365 @@ +/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte + * + * based on a copy of osmo-bts-sysmo/l1_if.c, which is + * Copyright (C) 2011-2014 by Harald Welte + * Copyright (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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "l1_oml.h" +#include "l1_utils.h" + +#include "octpkt.h" + +#include +#define OCTVC1_RC2STRING_DECLARE +#include +#include +#define OCTVC1_OPT_DECLARE_DEFAULTS +#include + +#define cPKTAPI_FIFO_ID_MSG 0xAAAA0001 + +/* allocate a msgb for a Layer1 primitive */ +struct msgb *l1p_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc_headroom(1500, 24, "l1_prim"); + if (!msg) + return msg; + + msg->l2h = msg->data; + return msg; +} + +void l1if_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, struct msgb *msg, + struct octphy_hdl *fl1h, uint32_t msg_type, uint32_t api_cmd) +{ + octvc1_fill_msg_hdr(mh, msgb_l2len(msg), fl1h->session_id, + fl1h->next_trans_id++, 0 /* user_info */, + msg_type, 0, api_cmd); +} + +/* Map OSMOCOM BAND type to Octasic type */ +tOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM +osmocom_to_octphy_band(enum gsm_band osmo_band, unsigned int arfcn) +{ + switch (osmo_band) { + case GSM_BAND_450: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_450; + case GSM_BAND_850: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_850; + case GSM_BAND_900: + if (arfcn >= 955 && arfcn <= 974) + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_R_900; + else if (arfcn >= 975 && arfcn <= 1023) + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_E_900; + else + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_P_900; + case GSM_BAND_1800: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_DCS_1800; + case GSM_BAND_1900: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_PCS_1900; + default: + return -EINVAL; + } +}; + +struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx, + tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id) +{ + unsigned int lchan_idx; + + OSMO_ASSERT(lch_id->byTimeslotNb < ARRAY_SIZE(trx->ts)); + if (lch_id->bySubChannelNb == cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL) { + switch (lch_id->bySAPI) { + case cOCTVC1_GSM_SAPI_ENUM_FCCH: + case cOCTVC1_GSM_SAPI_ENUM_SCH: + case cOCTVC1_GSM_SAPI_ENUM_BCCH: + case cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH: + case cOCTVC1_GSM_SAPI_ENUM_RACH: + lchan_idx = 4; + break; + case cOCTVC1_GSM_SAPI_ENUM_CBCH: + /* it is always index 2 (3rd element), whether in a + * combined CCCH+SDCCH4 or in a SDCCH8 */ + lchan_idx = 2; + break; + default: + lchan_idx = 0; + break; + } + } else + lchan_idx = lch_id->bySubChannelNb; + + OSMO_ASSERT(lchan_idx < ARRAY_SIZE(trx->ts[0].lchan)); + + return &trx->ts[lch_id->byTimeslotNb].lchan[lchan_idx]; +} + + +/* TODO: Unify with sysmobts? */ +struct wait_l1_conf { + struct llist_head list; + struct osmo_timer_list timer; + uint32_t prim_id; + uint32_t trans_id; + 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; + + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", + get_value_string(octphy_cid_vals, wlc->prim_id)); + exit(23); +} + +/* send a request(command) to L1, scheduling a call-back to be executed + * on receiving the response*/ +int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + struct wait_l1_conf *wlc; + + /* assume that there is a VC1 Message header and that it + * contains a command ID in network byte order */ + tOCTVC1_MSG_HEADER *msg_hdr = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t type_r_cmdid = ntohl(msg_hdr->ul_Type_R_CmdId); + uint32_t cmd_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) & cOCTVC1_MSG_ID_BIT_MASK; + + LOGP(DL1C, LOGL_DEBUG, "l1if_req_compl(msg_len=%u, cmd_id=%s) %s\n", + msgb_length(msg), get_value_string(octphy_cid_vals, cmd_id), + osmo_hexdump(msg->data, msgb_length(msg))); + + /* push the two common headers in front */ + octvocnet_push_ctl_hdr(msg, cOCTVC1_FIFO_ID_MGW_CONTROL, + cPKTAPI_FIFO_ID_MSG, fl1h->socket_id); + octpkt_push_common_hdr(msg, cOCTVOCNET_PKT_FORMAT_CTRL, 0, + cOCTPKT_HDR_CONTROL_PROTOCOL_TYPE_ENUM_OCTVOCNET); + + if (cb) { + wlc = talloc_zero(fl1h, struct wait_l1_conf); + wlc->cb = cb; + wlc->cb_data = data; + wlc->prim_id = cmd_id; + wlc->trans_id = ntohl(msg_hdr->ulTransactionId); + + /* schedule a timer for 10 seconds. If PHY fails to + * respond, we terminate */ + wlc->timer.data = wlc; + wlc->timer.cb = l1if_req_timeout; + osmo_timer_schedule(&wlc->timer, 10, 0); + + llist_add_tail(&wlc->list, &fl1h->wlc_list); + } + + /* queue for execution and response handling */ + if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "Tx Write queue full. dropping msg\n"); + msgb_free(msg); + } + + return 0; +} + +/* For OctPHY, this only about sending state changes to BSC */ +int l1if_activate_rf(struct octphy_hdl *fl1h, int on) +{ + int i; + struct gsm_bts_trx *trx = fl1h->priv; + if (on) { + /* 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 { + 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); + } + + return 0; +} + +static uint8_t chan_nr_by_sapi(enum gsm_phys_chan_config pchan, + tOCTVC1_GSM_SAPI_ENUM sapi, uint8_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + switch (sapi) { + case cOCTVC1_GSM_SAPI_ENUM_BCCH: + cbits = 0x10; + break; + case cOCTVC1_GSM_SAPI_ENUM_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: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", pchan); + return 0; + } + break; + case cOCTVC1_GSM_SAPI_ENUM_SDCCH: + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", pchan); + return 0; + } + break; + case cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH: + cbits = 0x12; + break; + case cOCTVC1_GSM_SAPI_ENUM_TCHF: + cbits = 0x01; + break; + case cOCTVC1_GSM_SAPI_ENUM_TCHH: + cbits = 0x02 + subCh; + break; + case cOCTVC1_GSM_SAPI_ENUM_FACCHF: + cbits = 0x01; + break; + case cOCTVC1_GSM_SAPI_ENUM_FACCHH: + cbits = 0x02 + subCh; + break; + case cOCTVC1_GSM_SAPI_ENUM_PDTCH: + case cOCTVC1_GSM_SAPI_ENUM_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 cOCTVC1_GSM_SAPI_ENUM_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; + } + return ((cbits << 3) | u8Tn); +} + +static void data_req_from_rts_ind(tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req, + const tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *rts_ind) +{ + data_req->TrxId = rts_ind->TrxId; + data_req->LchId = rts_ind->LchId; + data_req->Data.ulFrameNumber = rts_ind->ulFrameNumber; + data_req->Data.ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_NONE; +} + +#if 0 +static void empty_req_from_rts_ind(tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD * empty_req, + const tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *rts_ind) +{ + empty_req->TrxId = rts_ind->TrxId; + empty_req->LchId = rts_ind->LchId; + empty_req->ulFrameNumber = rts_ind->ulFrameNumber; +} +#endif + +/*********************************************************************** + * handle messages coming down from generic part + ***********************************************************************/ + + +static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + struct msgb *l1msg = l1p_msgb_alloc(); + uint32_t u32Fn; + uint8_t u8Tn, subCh, sapi = 0; + uint8_t chan_nr, link_id; + int len; + int rc; + + if (!msg) { + LOGP(DL1C, LOGL_FATAL, "L1SAP 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 = 0xf1; + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_SACCH; + if (!L1SAP_IS_CHAN_TCHF(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr)) { + if (trx->ts[u8Tn].pchan == GSM_PCHAN_PDCH) { + if (L1SAP_IS_PTCCH(u32Fn)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_PTCCH; + } else { + sapi = cOCTVC1_GSM_SAPI_ENUM_PDTCH; + } + } else { + sapi = cOCTVC1_GSM_SAPI_ENUM_FACCHF; + } + } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_FACCHH; + } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_SDCCH; + } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_SDCCH; + } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_BCCH; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { +#warning Set BS_AG_BLKS_RES + sapi = cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH; + } else { + LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d " + "chan_nr %d link_id %d\n", + l1sap->oph.primitive, l1sap->oph.operation, + chan_nr, link_id); + rc = -EINVAL; + msgb_free(msg); + goto done; + } + + if (len) { + /* create new PHY primitive in l1msg, copying payload */ + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) + msgb_put(l1msg, sizeof(*data_req)); + + l1if_fill_msg_hdr(&data_req->Header, l1msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID); + + data_req->TrxId.byTrxId = trx->nr; + data_req->LchId.byTimeslotNb = u8Tn; + data_req->LchId.bySAPI = sapi; + data_req->LchId.bySubChannelNb = subCh; + data_req->LchId.byDirection = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + data_req->Data.ulFrameNumber = u32Fn; + data_req->Data.ulDataLength = msgb_l2len(msg); + memcpy(data_req->Data.abyDataContent, msg->l2h, msgb_l2len(msg)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); + } else { + /* No data available, generate Empty frame Req in l1msg */ + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD *empty_frame_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD *) + msgb_put(l1msg, sizeof(*empty_frame_req)); + + l1if_fill_msg_hdr(&empty_frame_req->Header, l1msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CID); + + empty_frame_req->TrxId.byTrxId = trx->nr; + empty_frame_req->LchId.byTimeslotNb = u8Tn; + empty_frame_req->LchId.bySAPI = sapi; + empty_frame_req->LchId.bySubChannelNb = subCh; + empty_frame_req->LchId.byDirection = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + empty_frame_req->ulFrameNumber = u32Fn; + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD_SWAP(empty_frame_req); + } + + msgb_free(msg); + + rc = l1if_req_compl(fl1h, l1msg, NULL, NULL); +done: + return rc; +} + + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, sapi, ss; + uint8_t chan_nr; + struct msgb *nmsg = NULL; + + chan_nr = l1sap->u.tch.chan_nr; + u32Fn = l1sap->u.tch.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + ss = subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_TCHH; + } else { + subCh = 0xf1; + ss = 0; + sapi = cOCTVC1_GSM_SAPI_ENUM_TCHF; + } + + lchan = &trx->ts[u8Tn].lchan[ss]; + + /* create new message */ + nmsg = l1p_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + + /* create new message and fill data */ + if (msg) { + msgb_pull(msg, sizeof(*l1sap)); + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) + msgb_put(nmsg, sizeof(*data_req)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_DEF(data_req); + + l1if_fill_msg_hdr(&data_req->Header, nmsg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID); + + data_req->TrxId.byTrxId = trx->nr; + data_req->LchId.byTimeslotNb = u8Tn; + data_req->LchId.bySAPI = sapi; + data_req->LchId.bySubChannelNb = subCh; + data_req->LchId.byDirection = + cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + data_req->Data.ulFrameNumber = u32Fn; + + l1if_tch_encode(lchan, + &data_req->Data.ulPayloadType, + data_req->Data.abyDataContent, + &data_req->Data.ulDataLength, + msg->data, msg->len); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); + } else { + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD *empty_frame_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD *) + msgb_put(nmsg, sizeof(*empty_frame_req)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD_DEF(empty_frame_req); + + l1if_fill_msg_hdr(&empty_frame_req->Header, nmsg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CID); + + empty_frame_req->TrxId.byTrxId = trx->nr; + empty_frame_req->LchId.byTimeslotNb = u8Tn; + empty_frame_req->LchId.bySAPI = sapi; + empty_frame_req->LchId.bySubChannelNb = subCh; + empty_frame_req->LchId.byDirection = + cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + empty_frame_req->ulFrameNumber = u32Fn; + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD_SWAP(empty_frame_req); + } + + msgb_free(msg); + return l1if_req_compl(fl1h, nmsg, NULL, NULL); +} + +static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + uint8_t u8Tn, ss; + 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; + u8Tn = L1SAP_CHAN2TS(chan_nr); + ss = l1sap_chan2ss(chan_nr); + lchan = &trx->ts[u8Tn].lchan[ss]; + if (l1sap->u.info.u.ciph_req.uplink) { + l1if_set_ciphering(lchan, 0); + lchan->ciph_state = LCHAN_CIPH_RX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink) { + l1if_set_ciphering(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; + u8Tn = L1SAP_CHAN2TS(chan_nr); + ss = l1sap_chan2ss(chan_nr); + lchan = &trx->ts[u8Tn].lchan[ss]; + + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) + l1if_rsl_chan_act(lchan); + else if (l1sap->u.info.type == PRIM_INFO_MODIFY) { +#warning "Mode Modify is currently not not supported for Octasic PHY" + /* 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 L1SAP 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; + + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + rc = ph_data_req(trx, msg, l1sap); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + rc = ph_tch_req(trx, msg, l1sap); + break; + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + rc = mph_info_req(trx, msg, l1sap); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "L1SAP unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + } + + if (rc) + msgb_free(msg); + return rc; + +} + +int bts_model_init(struct gsm_bts *bts) +{ + struct octphy_hdl *fl1h; + + fl1h = talloc_zero(bts, struct octphy_hdl); + if (!fl1h) + return -ENOMEM; + + INIT_LLIST_HEAD(&fl1h->wlc_list); + fl1h->priv = bts->c0; + bts->c0->role_bts.l1h = fl1h; + /* FIXME: what is the nominal transmit power of the PHY/board? */ + bts->c0->nominal_power = 15; + + /* configure some reasonable defaults, to be overridden by VTY */ + fl1h->config.rf_port_index = 0; + fl1h->config.rx_gain_db = 70; + fl1h->config.tx_atten_db = 0; + + bts_model_vty_init(bts); + + return 0; +} + +/*********************************************************************** + * handling of messages coming up from PHY + ***********************************************************************/ + +static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, + tOCTVC1_GSM_MEASUREMENT_INFO * 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_qbits = m->sBurstTiming; + + l1sap.u.info.u.meas_ind.ber10k = (unsigned int)(m->usBERCnt * 100); + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->sRSSIDbm * -1); + + return l1sap_up(trx, &l1sap); +} + +static void dump_meas_res(int ll, tOCTVC1_GSM_MEASUREMENT_INFO * m) +{ + LOGPC(DL1C, ll, ", Meas: RSSI %d dBm, Burst Timing %d Quarter of bits, " + "BER Error Count %d in last decoded frame, BER Toatal Bit count %d in last decoded frame\n", + m->sRSSIDbm, m->sBurstTiming, m->usBERCnt, m->usBERTotalBitCnt); +} + +static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t fn) +{ + struct gsm_bts_trx *trx = fl1->priv; + struct gsm_bts *bts = trx->bts; + struct osmo_phsap_prim l1sap; + + /* increment the primitive count for the alive timer */ + fl1->alive_prim_cnt++; + + /* ignore every time indication, except for c0 */ + if (trx != bts->c0) { + /* Returning 0 will log an error */ + return 1; + } + + if (trx_id != trx->nr) { + LOGP(DL1C, LOGL_FATAL, + "TRX id %d from response does not match the L1 context trx %d\n", + trx_id, trx->nr); + return 0; + } + + 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; + + return l1sap_up(trx, &l1sap); +} + +static int handle_ph_readytosend_ind(struct octphy_hdl *fl1, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = fl1->priv; + struct gsm_bts *bts = trx->bts; + struct osmo_phsap_prim *l1sap; + struct gsm_time g_time; + uint8_t chan_nr, link_id; + uint32_t fn; + int rc; + uint32_t t3p; + uint8_t ts_num, sc, sapi; + + struct msgb *resp_msg; + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req; + + /* Retrive the data */ + fn = evt->ulFrameNumber; + ts_num = (uint8_t) evt->LchId.byTimeslotNb; + sc = (uint8_t) evt->LchId.bySubChannelNb; + sapi = (uint8_t) evt->LchId.bySAPI; + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGP(DL1P, "Rx PH-RTS.ind %02u/%02u/%02u SAPI=%s\n", + g_time.t1, g_time.t2, g_time.t3, + get_value_string(octphy_l1sapi_names, sapi)); + + /* in case we need to forward primitive to common part */ + chan_nr = chan_nr_by_sapi(trx->ts[ts_num].pchan, sapi, sc, ts_num, fn); + if (chan_nr) { + if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH) + link_id = 0x40; + else + link_id = 0; + + 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 (sapi == cOCTVC1_GSM_SAPI_ENUM_TCHF + || sapi == cOCTVC1_GSM_SAPI_ENUM_TCHH) { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + 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); + } + + /* 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 = (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) + msgb_put(resp_msg, sizeof(*data_req)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_DEF(data_req); + + l1if_fill_msg_hdr(&data_req->Header, resp_msg, fl1, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID); + + data_req_from_rts_ind(data_req, evt); + + switch (sapi) { + /* TODO: SCH via L1SAP */ + case cOCTVC1_GSM_SAPI_ENUM_SCH: + /* compute T3prime */ + t3p = (g_time.t3 - 1) / 10; + /* fill SCH burst with data */ + data_req->Data.ulDataLength = 4; + data_req->Data.abyDataContent[0] = + (bts->bsic << 2) | (g_time.t1 >> 9); + data_req->Data.abyDataContent[1] = (g_time.t1 >> 1); + data_req->Data.abyDataContent[2] = + (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1); + data_req->Data.abyDataContent[3] = (t3p & 1); + break; + case cOCTVC1_GSM_SAPI_ENUM_PRACH: +#if 0 + /* in case we decide to send an empty frame... */ + + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD + *empty_frame_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD + *) msgSendBuffer; + + empty_req_from_rts_ind(empty_frame_req, evt); + + /* send empty frame request */ + rc = Logical_Channel_Empty_Frame_Cmd(empty_frame_req); + if (cOCTVC1_RC_OK != rc) { + LOGP(DL1P, LOGL_ERROR, + "Sending Empty Frame Request Failed! (%s)\n", + octvc1_rc2string(rc)); + } + break; +#endif + default: + LOGP(DL1P, LOGL_ERROR, "SAPI %s not handled via L1SAP!\n", + get_value_string(octphy_l1sapi_names, sapi)); +#if 0 + data_req->Data.ulDataLength = GSM_MACBLOCK_LEN; + memcpy(data_req->Data.abyDataContent, fill_frame, + GSM_MACBLOCK_LEN); +#endif + break; + } + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); + + return l1if_req_compl(fl1, resp_msg, NULL, NULL); +} + +static int handle_ph_data_ind(struct octphy_hdl *fl1, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *data_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = fl1->priv; + uint8_t chan_nr, link_id; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + uint8_t *data; + uint16_t len; + int8_t rssi; + int rc; + + uint8_t sapi = (uint8_t) data_ind->LchId.bySAPI; + uint8_t ts_num = (uint8_t) data_ind->LchId.byTimeslotNb; + uint8_t sc = (uint8_t) data_ind->LchId.bySubChannelNb; + + /* Need to combine two 16bit MSB and LSB to form 32bit FN */ + fn = data_ind->Data.ulFrameNumber; + + /* chan_nr and link_id */ + chan_nr = chan_nr_by_sapi(trx->ts[ts_num].pchan, sapi, sc, ts_num, fn); + if (!chan_nr) { + LOGP(DL1C, LOGL_ERROR, + "Rx PH-DATA.ind for unknown L1 SAPI %s\n", + get_value_string(octphy_l1sapi_names, sapi)); + msgb_free(l1p_msg); + return ENOTSUP; + } + + if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH) + link_id = 0x40; + else + link_id = 0; + + memset(&l1sap, 0, sizeof(l1sap)); + + /* uplink measurement */ + process_meas_res(trx, chan_nr, &data_ind->MeasurementInfo); + + /* FIXME: check min_qual_norm! */ + + DEBUGP(DL1C, "Rx PH-DATA.ind %s: %s", + get_value_string(octphy_l1sapi_names, sapi), + osmo_hexdump(data_ind->Data.abyDataContent, + data_ind->Data.ulDataLength)); + dump_meas_res(LOGL_DEBUG, &data_ind->MeasurementInfo); + + /* check for TCH */ + if (sapi == cOCTVC1_GSM_SAPI_ENUM_TCHF || + sapi == cOCTVC1_GSM_SAPI_ENUM_TCHH) { + /* TCH speech frame handling */ + rc = l1if_tch_rx(trx, chan_nr, data_ind); + msgb_free(l1p_msg); + return rc; + } + + /* get rssi */ + rssi = (int8_t) data_ind->MeasurementInfo.sRSSIDbm; + /* get data pointer and length */ + data = data_ind->Data.abyDataContent; + len = data_ind->Data.ulDataLength; + /* pull lower header part before data */ + msgb_pull(l1p_msg, data - l1p_msg->data); + /* trim remaining data to it's size, to get rid of upper header part */ + rc = msgb_trim(l1p_msg, len); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive data\n"); + l1p_msg->l2h = l1p_msg->data; + /* push new l1 header */ + l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap)); + /* fill header */ + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + l1sap->u.data.rssi = rssi; + + return l1sap_up(trx, l1sap); +} + +static int handle_ph_rach_ind(struct octphy_hdl *fl1, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *ra_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = fl1->priv; + struct gsm_bts *bts = trx->bts; + struct gsm_bts_role_bts *btsb = bts_role_bts(bts); + struct gsm_lchan *lchan; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + uint8_t ra, acc_delay; + int rc; + + /* increment number of busy RACH slots, if required */ + if (trx == bts->c0 && + ra_ind->MeasurementInfo.sRSSIDbm >= btsb->load.rach.busy_thresh) { + btsb->load.rach.busy++; + } + /* FIXME: Check min_qual_rach */ + + lchan = get_lchan_by_lchid(trx, &ra_ind->LchId); + if (trx == bts->c0 && !(lchan && lchan->ho.active == HANDOVER_ENABLED)) + btsb->load.rach.access++; + + dump_meas_res(LOGL_DEBUG, &ra_ind->MeasurementInfo); + + if (ra_ind->ulMsgLength != 1) { + LOGP(DL1C, LOGL_ERROR, "Rx PH-RACH.ind has lenghth %d > 1\n", + ra_ind->ulMsgLength); + msgb_free(l1p_msg); + return 0; + } + + fn = ra_ind->ulFrameNumber; + ra = ra_ind->abyMsg[0]; + /* check for under/overflow / sign */ + if (ra_ind->MeasurementInfo.sBurstTiming < 0) + acc_delay = 0; + else + acc_delay = ra_ind->MeasurementInfo.sBurstTiming >> 2; + + 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); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, + l1p_msg); + l1sap->u.rach_ind.ra = ra; + l1sap->u.rach_ind.acc_delay = acc_delay; + l1sap->u.rach_ind.fn = fn; + if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4) + l1sap->u.rach_ind.chan_nr = 0x88; + else + l1sap->u.rach_ind.chan_nr = gsm_lchan2chan_nr(lchan); + + return l1sap_up(trx, l1sap); +} + +static int rx_gsm_trx_time_ind(struct msgb *msg) +{ + struct octphy_hdl *fl1h = msg->dst; + tOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT *tind = + (tOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT *) msg->l2h; + + mOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT_SWAP(tind); + + return handle_mph_time_ind(fl1h, tind->TrxId.byTrxId, tind->ulFrameNumber); +} + +/* Receive a response (to a prior command) from the PHY */ +static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t return_code = ntohl(mh->ulReturnCode); + struct octphy_hdl *fl1h = msg->dst; + struct wait_l1_conf *wlc; + int rc; + + /* check if anyone has registered a call-back for the given + * command_id and transaction, and call them back */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (wlc->prim_id == msg_id && wlc->trans_id == trans_id) { + llist_del(&wlc->list); + if (wlc->cb) + rc = wlc->cb(fl1h->priv, msg, wlc->cb_data); + else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + return rc; + } + } + + /* ignore unhandled responses that went ok. The caller might just not + * be interested in them (as we are in case of DATA.req and + * EMPTY-FRAME.req */ + if (return_code != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_NOTICE, "Rx Unexpected response %s (trans_id=0x%x)\n", + get_value_string(octphy_cid_vals, msg_id), trans_id); + } + msgb_free(msg); + return 0; +} + +static int rx_gsm_clockmgr_status_ind(struct msgb *msg) +{ + struct octphy_hdl *fl1h = msg->dst; + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT *evt = + (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT *) msg->l2h; + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT_SWAP(evt); + + LOGP(DL1C, LOGL_NOTICE, "Rx ClkMgr Status Change Event: " + "%s -> %s\n", + get_value_string(octphy_clkmgr_state_vals, evt->ulPreviousState), + get_value_string(octphy_clkmgr_state_vals, evt->ulState)); + + fl1h->clkmgr_state = evt->ulState; + + return 0; +} + +static int rx_gsm_trx_status_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT *) msg->l2h; + + mOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT_SWAP(evt); + + if (evt->ulStatus == cOCTVC1_GSM_TRX_STATUS_ENUM_RADIO_READY) + LOGP(DL1C, LOGL_INFO, "Rx TRX Status Event: READY\n"); + else + LOGP(DL1C, LOGL_ERROR, "Rx TRX Status Event: %u\n", + evt->ulStatus); + + return 0; +} + +/* DATA indication from PHY */ +static int rx_gsm_trx_lchan_data_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *) msg->l2h; + mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT_SWAP(evt); + + return handle_ph_data_ind(msg->dst, evt, msg); +} + +/* Ready-to-Send indication from PHY */ +static int rx_gsm_trx_rts_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *) msg->l2h; + mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT_SWAP(evt); + + return handle_ph_readytosend_ind(msg->dst, evt, msg); +} + +/* RACH receive indication from PHY */ +static int rx_gsm_trx_rach_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *) msg->l2h; + mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT_SWAP(evt); + + return handle_ph_rach_ind(msg->dst, evt, msg); +} + +/* Receive a notification (indication) from PHY */ +static int rx_octvc1_notif(struct msgb *msg, uint32_t msg_id) +{ + const char *evt_name = get_value_string(octphy_eid_vals, msg_id); + + LOGP(DL1P, LOGL_DEBUG, "Rx NOTIF %s\n", evt_name); + + switch (msg_id) { + case cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID: + return rx_gsm_trx_time_ind(msg); + case cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EID: + return rx_gsm_clockmgr_status_ind(msg); + case cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID: + return rx_gsm_trx_status_ind(msg); + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID: + return rx_gsm_trx_lchan_data_ind(msg); + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID: + return rx_gsm_trx_rts_ind(msg); + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID: + return rx_gsm_trx_rach_ind(msg); + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RAW_DATA_INDICATION_EID: + LOGP(DL1P, LOGL_NOTICE, "Rx Unhandled event %s (%u)\n", + evt_name, msg_id); + break; + default: + LOGP(DL1P, LOGL_NOTICE, "Rx Unknown event %s (%u)\n", + evt_name, msg_id); + } + + return 0; +} + +static int rx_octvc1_event_msg(struct msgb *msg) +{ + tOCTVC1_EVENT_HEADER *eh = (tOCTVC1_EVENT_HEADER *) msg->l2h; + uint32_t event_id = ntohl(eh->ulEventId); + uint32_t length = ntohl(eh->ulLength); + /* DO NOT YET SWAP HEADER HERE, as downstream functions want to + * swap it */ + + /* OCTSDKAN5001 Chapter 6.1 */ + if (length < 12 || length > 1024) { + LOGP(DL1C, LOGL_ERROR, "Rx EVENT length %u invalid\n", length); + return -1; + } + + /* verify / ensure length */ + if (msgb_l2len(msg) < length) { + LOGP(DL1C, LOGL_ERROR, "Rx EVENT msgb_l2len(%u) < " + "event_msg_length (%u)\n", msgb_l2len(msg), length); + return -1; + } + + return rx_octvc1_notif(msg, event_id); +} + +static int rx_octvc1_ctrl_msg(struct msgb *msg) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t length = ntohl(mh->ulLength); + uint32_t type_r_cmdid = ntohl(mh->ul_Type_R_CmdId); + uint32_t msg_type = (type_r_cmdid >> cOCTVC1_MSG_TYPE_BIT_OFFSET) & + cOCTVC1_MSG_TYPE_BIT_MASK; + uint32_t msg_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) & + cOCTVC1_MSG_ID_BIT_MASK; + uint32_t return_code = ntohl(mh->ulReturnCode); + const char *msg_name = get_value_string(octphy_cid_vals, msg_id); + + /* DO NOT YET SWAP HEADER HERE, as downstream functions want to + * swap it */ + + /* OCTSDKAN5001 Chapter 3.1 */ + if (length < 24 || length > 1024) { + LOGP(DL1C, LOGL_ERROR, "Rx CTRL length %u invalid\n", length); + return -1; + } + + /* verify / ensure length */ + if (msgb_l2len(msg) < length) { + LOGP(DL1C, LOGL_ERROR, "Rx CTRL msgb_l2len(%u) < " + "ctrl_msg_length (%u)\n", msgb_l2len(msg), length); + return -1; + } + + LOGP(DL1P, LOGL_DEBUG, "Rx %s.resp (rc=%s(%x))\n", msg_name, + octvc1_rc2string(return_code), return_code); + + if (return_code != cOCTVC1_RC_OK) { + LOGP(DL1P, LOGL_ERROR, "%s failed, rc=%s\n", + msg_name, octvc1_rc2string(return_code)); + } + + switch (msg_type) { + case cOCTVC1_MSG_TYPE_RESPONSE: + return rx_octvc1_resp(msg, msg_id, ntohl(mh->ulTransactionId)); + case cOCTVC1_MSG_TYPE_NOTIFICATION: + case cOCTVC1_MSG_TYPE_COMMAND: + case cOCTVC1_MSG_TYPE_SUPERVISORY: + LOGP(DL1C, LOGL_NOTICE, "Rx unhandled msg_type %s (%u)\n", + msg_name, msg_type); + break; + default: + LOGP(DL1P, LOGL_NOTICE, "Rx unknown msg_type %s (%u)\n", + msg_name, msg_type); + } + + return 0; +} + +static int rx_octvc1_data_f_msg(struct msgb *msg) +{ + tOCTVOCNET_PKT_DATA_F_HEADER *datafh = + (tOCTVOCNET_PKT_DATA_F_HEADER *) msg->l2h; + uint32_t log_obj_port = ntohl(datafh->VocNetHeader.ulLogicalObjPktPort); + + msg->l2h = (uint8_t *) datafh + sizeof(*datafh); + + if (log_obj_port == + cOCTVOCNET_PKT_DATA_LOGICAL_OBJ_PKT_PORT_EVENT_SESSION) { + uint32_t sub_type = ntohl(datafh->ulSubType) & 0xF; + if (sub_type == cOCTVOCNET_PKT_SUBTYPE_API_EVENT) { + return rx_octvc1_event_msg(msg); + } else { + LOGP(DL1C, LOGL_ERROR, "Unknown DATA_F " + "subtype 0x%x\n", sub_type); + } + } else { + LOGP(DL1C, LOGL_ERROR, "Unknown logical object pkt port 0x%x\n", + log_obj_port); + } + + return 0; +} + +/* main receive routine for messages coming up from OCTPHY */ +static int rx_octphy_msg(struct msgb *msg) +{ + tOCTVOCNET_PKT_CTL_HEADER *ctlh; + + /* we assume that the packets start right with the OCTPKT header + * and that the ethernet hardware header has already been + * stripped before */ + msg->l1h = msg->data; + + uint32_t ch = ntohl(*(uint32_t *) msg->data); + uint32_t format = (ch >> cOCTVOCNET_PKT_FORMAT_BIT_OFFSET) + & cOCTVOCNET_PKT_FORMAT_BIT_MASK; + uint32_t len = (ch >> cOCTVOCNET_PKT_LENGTH_BIT_OFFSET) + & cOCTVOCNET_PKT_LENGTH_MASK; + + if (len > msgb_length(msg)) { + LOGP(DL1C, LOGL_ERROR, "Received length (%u) > length " + "as per packt header (%u)\n", msgb_length(msg), + len); + return -1; + } + + /* we first need to decode the common OCTPKT header and dispatch + * based on contrl (command/resp) or data (event=indication) */ + switch (format) { + case cOCTVOCNET_PKT_FORMAT_CTRL: + ctlh = (tOCTVOCNET_PKT_CTL_HEADER *) (msg->l1h + 4); + /* FIXME: check src/dest fifo, socket ID */ + msg->l2h = (uint8_t *) ctlh + sizeof(*ctlh); + return rx_octvc1_ctrl_msg(msg); + case cOCTVOCNET_PKT_FORMAT_F: + msg->l2h = msg->l1h + 4; + return rx_octvc1_data_f_msg(msg); + default: + LOGP(DL1C, LOGL_ERROR, "Rx Unknown pkt_format 0x%x\n", + format); + break; + } + + return 0; +} + +/*********************************************************************** + * octphy socket / main loop integration + ***********************************************************************/ + +static int octphy_read_cb(struct osmo_fd *ofd) +{ + struct sockaddr_ll sll; + socklen_t sll_len = sizeof(sll); + int rc; + struct msgb *msg = msgb_alloc_headroom(1500, 24, "PHY Rx"); + + if (!msg) + return -ENOMEM; + + /* this is the fl1h over which the message was received */ + msg->dst = ofd->data; + + rc = recvfrom(ofd->fd, msg->data, msgb_tailroom(msg), 0, + (struct sockaddr *) &sll, &sll_len); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Error in recvfrom(): %s\n", + strerror(errno)); + msgb_free(msg); + return rc; + } + msgb_put(msg, rc); + + return rx_octphy_msg(msg); +} + +static int octphy_write_cb(struct osmo_fd *fd, struct msgb *msg) +{ + struct octphy_hdl *fl1h = fd->data; + int rc; + + /* send the message down the socket */ + rc = sendto(fd->fd, msg->data, msgb_length(msg), 0, + (struct sockaddr *) &fl1h->phy_addr, + sizeof(fl1h->phy_addr)); + + /* core write uqueue takes care of free() */ + if (rc < 0) { + LOGP(DL1P, LOGL_ERROR, "Tx to PHY has failed: %s\n", + strerror(errno)); + } + + return rc; +} + +int l1if_open(struct octphy_hdl *fl1h) +{ + struct ifreq ifr; + int sfd, rc; + char *phy_dev = fl1h->netdev_name; + + LOGP(DL1C, LOGL_NOTICE, "Opening L1 interface for OctPHY (%s)\n", + phy_dev); + + sfd = osmo_sock_packet_init(SOCK_DGRAM, cOCTPKT_HDR_ETHERTYPE, + phy_dev, OSMO_SOCK_F_NONBLOCK); + if (sfd < 0) { + LOGP(DL1C, LOGL_FATAL, "Error opening PHY socket: %s\n", + strerror(errno)); + return -EIO; + } + + /* resolve the string device name to an ifindex */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, phy_dev, sizeof(ifr.ifr_name)); + rc = ioctl(sfd, SIOCGIFINDEX, &ifr); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Error using network device %s: %s\n", + phy_dev, strerror(errno)); + close(sfd); + return -EIO; + } + + fl1h->session_id = rand(); + + /* set fl1h->phy_addr, which we use as sendto() destionation */ + fl1h->phy_addr.sll_family = AF_PACKET; + fl1h->phy_addr.sll_protocol = htons(cOCTPKT_HDR_ETHERTYPE); + fl1h->phy_addr.sll_ifindex = ifr.ifr_ifindex; + fl1h->phy_addr.sll_hatype = ARPHRD_ETHER; + fl1h->phy_addr.sll_halen = 6; + /* sll_addr is filled by bts_model_vty code */ + + /* Write queue / osmo_fd registration */ + osmo_wqueue_init(&fl1h->phy_wq, 10); + fl1h->phy_wq.write_cb = octphy_write_cb; + fl1h->phy_wq.read_cb = octphy_read_cb; + fl1h->phy_wq.bfd.fd = sfd; + fl1h->phy_wq.bfd.when = BSC_FD_READ; + fl1h->phy_wq.bfd.cb = osmo_wqueue_bfd_cb; + fl1h->phy_wq.bfd.data = fl1h; + rc = osmo_fd_register(&fl1h->phy_wq.bfd); + if (rc < 0) { + close(sfd); + return -EIO; + } + + return 0; +} + +int l1if_close(struct octphy_hdl *fl1h) +{ + osmo_fd_unregister(&fl1h->phy_wq.bfd); + close(fl1h->phy_wq.bfd.fd); + talloc_free(fl1h); + + return 0; +} diff --git a/src/osmo-bts-octphy/l1_if.h b/src/osmo-bts-octphy/l1_if.h new file mode 100644 index 000000000..54a5504c5 --- /dev/null +++ b/src/osmo-bts-octphy/l1_if.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include + +#include + +struct octphy_hdl { + /* packet socket to talk with PHY */ + struct osmo_wqueue phy_wq; + /* MAC address of th PHY */ + struct sockaddr_ll phy_addr; + /* Network device name */ + char *netdev_name; + + /* address parameters of the PHY */ + uint32_t session_id; + uint32_t next_trans_id; + uint32_t socket_id; + + /* clock manager state */ + uint32_t clkmgr_state; + + struct { + uint32_t rf_port_index; + uint32_t rx_gain_db; + uint32_t tx_atten_db; + } config; + + struct llist_head wlc_list; + + /* private pointer, points back to TRX */ + void *priv; + + struct osmo_timer_list alive_timer; + uint32_t alive_prim_cnt; +}; + +static inline struct octphy_hdl *trx_octphy_hdl(struct gsm_bts_trx *trx) +{ + return trx->role_bts.l1h; +} + +void l1if_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, struct msgb *msg, + struct octphy_hdl *fl1h, uint32_t msg_type, uint32_t api_cmd); + +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 octphy_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data); + +#include +struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx, + tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id); + +int l1if_open(struct octphy_hdl *fl1h); +int l1if_close(struct octphy_hdl *hdl); + +int l1if_trx_open(struct gsm_bts_trx *trx); +int l1if_trx_close_all(struct gsm_bts *bts); +int l1if_enable_events(struct gsm_bts_trx *trx); + +int l1if_activate_rf(struct octphy_hdl *fl1h, int on); + +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT * + data_ind); + +struct msgb *l1p_msgb_alloc(void); + +/* tch.c */ +void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type, + uint8_t *data, uint32_t *len, const uint8_t *rtp_pl, + unsigned int rtp_pl_len); + +tOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM +osmocom_to_octphy_band(enum gsm_band osmo_band, unsigned int arfcn); diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c new file mode 100644 index 000000000..68e6cf1cd --- /dev/null +++ b/src/osmo-bts-octphy/l1_oml.c @@ -0,0 +1,1399 @@ +/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte + * + * based on a copy of osmo-bts-sysmo/l1_oml.c, which is + * Copyright (C) 2011 by Harald Welte + * Copyright (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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "l1_if.h" +#include "l1_oml.h" +#include "l1_utils.h" +#include "octphy_hw_api.h" + +#include +#include +#include +#include + +/* Map OSMOCOM logical channel type to OctPHY Logical channel type */ +static tOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM pchan_to_logChComb[_GSM_PCHAN_MAX] = +{ + [GSM_PCHAN_NONE] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY, + [GSM_PCHAN_CCCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH, + [GSM_PCHAN_CCCH_SDCCH4] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_SACCHC4, + [GSM_PCHAN_TCH_F] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF, + [GSM_PCHAN_TCH_H] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH, + [GSM_PCHAN_SDCCH8_SACCH8C] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_SACCHC8, + // TODO - watch out below two!!! + [GSM_PCHAN_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF, + [GSM_PCHAN_TCH_F_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF, + [GSM_PCHAN_UNKNOWN] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY +}; + +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; + tOCTVC1_GSM_SAPI_ENUM sapi; + tOCTVC1_GSM_DIRECTION_ENUM dir; + enum sapi_cmd_type type; + int (*callback) (struct gsm_lchan * lchan, int status); +}; + +struct sapi_dir { + tOCTVC1_GSM_SAPI_ENUM sapi; + tOCTVC1_GSM_DIRECTION_ENUM dir; +}; + +static const struct sapi_dir ccch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_FCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_BCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_RACH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir tchf_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir tchh_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir sdcch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir cbch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_CBCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + /* Does the CBCH really have a SACCH in Downlink */ + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, +}; + +static const struct sapi_dir pdtch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +struct lchan_sapis { + const struct sapi_dir *sapis; + uint32_t 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 uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R }; + +extern uint8_t rach_detected_LA_g; +extern uint8_t rach_detected_Other_g; + +static int opstart_compl(struct gsm_abis_mo *mo) +{ + /* TODO: Send NACK in case of error! */ + + /* Set to Operational State: Enabled */ + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + + /* 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); + mo->bts->c0->ts[0].lchan[4].rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[4]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } + } + + /* Send OPSTART ack */ + return oml_mo_opstart_ack(mo); +} + +static +tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM lchan_to_GsmL1_SubCh_t(const struct gsm_lchan + * lchan) +{ + switch (lchan->ts->pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + if (lchan->type == GSM_LCHAN_CCCH) + return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL; + /* fall-through */ + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_SDCCH8_SACCH8C: + return (tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM) lchan->nr; + case GSM_PCHAN_NONE: + case GSM_PCHAN_CCCH: + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_UNKNOWN: + default: + return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL; + } + return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL; +} + +static void clear_amr_params(tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config) +{ + /* common for the SIGN, V1 and EFR: */ + int i; + p_Config->byCmiPhase = 0; + p_Config->byInitRate = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; + /* 4 AMR active codec set */ + for (i = 0; i < cOCTVC1_GSM_RATE_LIST_SIZE; i++) + p_Config->abyRate[i] = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; +} + +static void lchan2lch_par(struct gsm_lchan *lchan, + tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config) +{ + 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) { + clear_amr_params(p_Config); + } + break; + + case GSM48_CMODE_SPEECH_V1: + clear_amr_params(p_Config); + break; + + case GSM48_CMODE_SPEECH_EFR: + clear_amr_params(p_Config); + break; + + case GSM48_CMODE_SPEECH_AMR: + p_Config->byCmiPhase = 1; /* FIXME? */ + p_Config->byInitRate = + (tOCTVC1_GSM_AMR_CODEC_MODE_ENUM) + amr_get_initial_mode(lchan); + + /* initialize to clean state */ + for (j = 0; j < cOCTVC1_GSM_RATE_LIST_SIZE; j++) + p_Config->abyRate[j] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; + + j = 0; + if (mr_conf->m4_75) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_4_75; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m5_15) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_15; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m5_90) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_90; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m6_70) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_6_70; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m7_40) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_40; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m7_95) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_95; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m10_2) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_10_2; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m12_2) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_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; + + } +} + +/*********************************************************************** + * CORE SAPI QUEUE HANDLING + ***********************************************************************/ + +static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status); +static void sapi_queue_send(struct gsm_lchan *lchan); + +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 lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h; + struct gsm_lchan *lchan; + uint8_t sapi; + uint8_t direction; + uint8_t status; + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ar); + OSMO_ASSERT(ar->TrxId.byTrxId == trx->nr); + + lchan = get_lchan_by_lchid(trx, &ar->LchId); + sapi = ar->LchId.bySAPI; + direction = ar->LchId.byDirection; + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, direction)); + + if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s\n", + get_value_string(octphy_l1sapi_names, sapi)); + status = LCHAN_SAPI_S_ERROR; + } else { + status = LCHAN_SAPI_S_ASSIGNED; + } + + switch (direction) { + case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS: + lchan->sapis_dl[sapi] = status; + break; + case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS: + lchan->sapis_ul[sapi] = status; + break; + default: + LOGP(DL1C, LOGL_ERROR, "Unknown direction %d\n", + ar->LchId.byDirection); + break; + } + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + return -1; + } + + sapi_queue_dispatch(lchan, ar->Header.ulReturnCode); + + msgb_free(resp); + + return 0; +} + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *lac; + + lac = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *) + msgb_put(msg, sizeof(*lac)); + l1if_fill_msg_hdr(&lac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID); + + lac->TrxId.byTrxId = lchan->ts->trx->nr; + lac->LchId.byTimeslotNb = lchan->ts->nr; + lac->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan); + lac->LchId.bySAPI = cmd->sapi; + lac->LchId.byDirection = cmd->dir; + + lac->Config.byTimingAdvance = lchan->rqd_ta; + lac->Config.byBSIC = lchan->ts->trx->bts->bsic; + + lchan2lch_par(lchan, &lac->Config); + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(lac); + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, cmd->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, cmd->dir)); + + return l1if_req_compl(fl1h, msg, lchan_act_compl_cb, NULL); +} + + +static tOCTVC1_GSM_CIPHERING_ID_ENUM rsl2l1_ciph[] = { + [0] = cOCTVC1_GSM_CIPHERING_ID_ENUM_UNUSED, + [1] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_0, + [2] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_1, + [3] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_2, + [4] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_3 +}; + +static int set_ciph_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *pcr = + (tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *) resp->l2h; + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + + mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP_SWAP(pcr); + + if (pcr->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, "Error: Cipher Request Failed!\n\n"); + LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + exit(-1); + } + + OSMO_ASSERT(pcr->TrxId.byTrxId == trx->nr); + ts = &trx->ts[pcr->TrxId.byTrxId]; + /* for some strange reason the response does not tell which + * sub-channel, only th request contains this information :( */ + lchan = &ts->lchan[(unsigned long) data]; + + /* TODO: This state machine should be shared accross BTS models? */ + switch (lchan->ciph_state) { + case LCHAN_CIPH_RX_REQ: + lchan->ciph_state = LCHAN_CIPH_RX_CONF; + break; + case LCHAN_CIPH_RX_CONF_TX_REQ: + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + break; + case LCHAN_CIPH_RXTX_REQ: + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + break; + case LCHAN_CIPH_NONE: + break; + default: + LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state); + } + + msgb_free(resp); + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got ciphering conf with empty queue\n", + gsm_lchan_name(lchan)); + return 0; + } + sapi_queue_dispatch(lchan, pcr->Header.ulReturnCode); + + return 0; +} + +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *pcc; + + pcc = (tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *) + msgb_put(msg, sizeof(*pcc)); + l1if_fill_msg_hdr(&pcc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID); + + pcc->PchId.byTimeslotNb = lchan->ts->nr; + pcc->ulSubchannelNb = lchan_to_GsmL1_SubCh_t(lchan); + pcc->ulDirection = cmd->dir; + pcc->Config.ulCipherId = rsl2l1_ciph[lchan->encr.alg_id]; + memcpy(pcc->Config.abyKey, lchan->encr.key, lchan->encr.key_len); + + LOGP(DL1C, LOGL_INFO, "%s SET_CIPHERING (ALG=%u %s)\n", + gsm_lchan_name(lchan), pcc->Config.ulCipherId, + get_value_string(octphy_dir_names, pcc->ulDirection)); + + mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD_SWAP(pcc); + + /* we have to save the lchan number in this strange way, as the + * PHY does not return the ulSubchannelNr in the response to + * this command */ + return l1if_req_compl(fl1h, msg, set_ciph_compl_cb, (void *)(unsigned long) lchan->nr); +} + + +/** + * 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 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); +} + +static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) +{ + /* FIXME: Error handling. There is no NACK... */ + if (status != cOCTVC1_RC_OK && 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); + 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 == cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS) { + if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL; + } else if (dir == cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS) { + 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 lchan_deactivate_sapis(struct gsm_lchan *lchan) +{ + struct octphy_hdl *fl1h = trx_octphy_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 == cOCTVC1_GSM_SAPI_ENUM_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); + } + + /* 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 int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *ldr = + (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h; + struct gsm_lchan *lchan; + struct sapi_cmd *cmd; + uint8_t status; + + mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ldr); + OSMO_ASSERT(ldr->TrxId.byTrxId == trx->nr); + + lchan = get_lchan_by_lchid(trx, &ldr->LchId); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, ldr->LchId.byDirection)); + + if (ldr->Header.ulReturnCode == cOCTVC1_RC_OK) { + DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n", + get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI), + ldr->LchId.byTimeslotNb); + status = LCHAN_SAPI_S_NONE; + } else { + LOGP(DL1C, LOGL_ERROR, + "Error deactivating L1 SAPI %s on TS %u\n", + get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI), + ldr->LchId.byTimeslotNb); + status = LCHAN_SAPI_S_ERROR; + } + + switch (ldr->LchId.byDirection) { + case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS: + lchan->sapis_dl[ldr->LchId.bySAPI] = status; + break; + case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS: + lchan->sapis_ul[ldr->LchId.bySAPI] = status; + break; + } + + 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 != ldr->LchId.bySAPI || + cmd->dir != ldr->LchId.byDirection || + 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, + ldr->LchId.bySAPI, ldr->LchId.byDirection); + goto err; + } + + sapi_queue_dispatch(lchan, status); + +err: + msgb_free(resp); + return 0; +} + +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *ldc; + + ldc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *) + msgb_put(msg, sizeof(*ldc)); + l1if_fill_msg_hdr(&ldc->Header, msg, fl1h,cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID); + + ldc->LchId.byTimeslotNb = lchan->ts->nr; + ldc->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan); + ldc->LchId.byDirection = cmd->dir; + ldc->LchId.bySAPI = cmd->sapi; + + mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(ldc); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, cmd->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, cmd->dir)); + + return l1if_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL); + +} + +/** + * 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 = 0; + 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: + /* TODO: Mode modif not supported by OctPHY currently */ + /* 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, cOCTVC1_GSM_SAPI_ENUM_SACCH, + cOCTVC1_GSM_ID_DIRECTION_ENUM_TX_BTS_MS); + res |= + check_sapi_release(lchan, cOCTVC1_GSM_SAPI_ENUM_SACCH, + cOCTVC1_GSM_ID_DIRECTION_ENUM_RX_BTS_MS); + break; + case SAPI_CMD_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = lchan_deactivate_sapis(lchan); + break; + case SAPI_CMD_DEACTIVATE: + res = 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_NOTICE, + "%s End of queue encountered. Now empty? %d\n", + gsm_lchan_name(lchan), llist_empty(&lchan->sapi_cmds)); + return; + } + + sapi_queue_send(lchan); +} + +/* we regularly check if the 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 octphy_hdl *fl1h = data; + + if (fl1h->alive_prim_cnt == 0) { + LOGP(DL1C, LOGL_FATAL, "L1 is no longer sending primitives!\n"); + exit(23); + } + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); +} + +/*********************************************************************** + * RSL DEACTIVATE SACCH + ***********************************************************************/ + +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); +} + +static int lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + enqueue_sacch_rel_marker(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 lchan_deactivate_sacch(lchan); +} + + +/*********************************************************************** + * RSL CHANNEL RELEASE + ***********************************************************************/ + +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); +} + +static int lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + lchan->ciph_state = 0; /* FIXME: do this in common *.c */ + enqueue_rel_marker(lchan); + 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) + return 0; + lchan_deactivate(lchan); + return 0; +} + + +/*********************************************************************** + * SET CIPHERING + ***********************************************************************/ + +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 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 = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + else + dir = cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS; + + enqueue_sapi_ciphering_cmd(lchan, dir); + + return 0; +} + + +/*********************************************************************** + * RSL MODE MODIFY + ***********************************************************************/ + +/* Mode modify is currently not supported by OctPHY */ +static void enqueue_sapi_logchpar_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_LOGCH_PARAM; + queue_sapi_command(lchan, cmd); +} + + +/* Mode modify is currently not supported by OctPHY */ +static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction) +{ + enqueue_sapi_logchpar_cmd(lchan, direction); + + return 0; +} + +/* Mode modify is currently not supported by OctPHY */ +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, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS); + tx_confreq_logchpar(lchan, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS); + + /* FIXME: update encryption */ + + return 0; +} + + +/*********************************************************************** + * LCHAN / SAPI ACTIVATION + ***********************************************************************/ + +static int sapi_activate_cb(struct gsm_lchan *lchan, int status) +{ + if (status != cOCTVC1_RC_OK) { + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, + RSL_ERR_EQUIPMENT_FAIL); + 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); + + 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 octphy_hdl *fl1h = trx_octphy_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); + + DEBUGP(DL1C, "lchan_act called\n"); + + if (!llist_empty(&lchan->sapi_cmds)) + LOGP(DL1C, LOGL_ERROR, + "%s Trying to activate lchan, but commands in queue\n", + gsm_lchan_name(lchan)); + + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + + if (sapi == cOCTVC1_GSM_SAPI_ENUM_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); + } + + lchan_init_lapdm(lchan); + + return 0; +} + +int l1if_rsl_chan_act(struct gsm_lchan *lchan) +{ + lchan_activate(lchan); + return 0; +} + +static int enable_events_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *mser = + (tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *) resp->l2h; + + mOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP_SWAP(mser); + + LOGP(DL1C, LOGL_INFO, "Rx ENABLE-EVT-REC.resp\n"); + + msgb_free(resp); + + return 0; +} + +int l1if_enable_events(struct gsm_bts_trx *trx) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD *mse; + + mse = (tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD *) + msgb_put(msg, sizeof(*mse)); + l1if_fill_msg_hdr(&mse->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CID); + mse->ulEvtActiveFlag = cOCT_TRUE; + + mOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_CMD_SWAP(mse); + + LOGP(DL1C, LOGL_INFO, "Tx ENABLE-EVT-REC.req\n"); + + return l1if_req_compl(fl1h, msg, enable_events_compl_cb, 0); +} + +/* call-back once the TRX_OPEN_CID response arrives */ +static int trx_open_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_OPEN_RSP *or = + (tOCTVC1_GSM_MSG_TRX_OPEN_RSP *) resp->l2h; + + mOCTVC1_GSM_MSG_TRX_OPEN_RSP_SWAP(or); + + OSMO_ASSERT(or->TrxId.byTrxId == trx->nr); + + LOGP(DL1C, LOGL_INFO, "TRX-OPEN.resp(trx=%u) = %s\n", + trx->nr, octvc1_rc2string(or->Header.ulReturnCode)); + + /* FIXME: check for ulReturnCode == OK */ + if (or->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, "TRX-OPEN failed: %s\n", + octvc1_rc2string(or->Header.ulReturnCode)); + msgb_free(resp); + exit(1); + } + + msgb_free(resp); + + opstart_compl(&trx->mo); + + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + octphy_hw_get_pcb_info(fl1h); + octphy_hw_get_rf_port_info(fl1h, 0); + octphy_hw_get_rf_ant_rx_config(fl1h, 0, 0); + octphy_hw_get_rf_ant_tx_config(fl1h, 0, 0); + octphy_hw_get_rf_ant_rx_config(fl1h, 0, 1); + octphy_hw_get_rf_ant_tx_config(fl1h, 0, 1); + octphy_hw_get_clock_sync_info(fl1h); + + /* Temporary fix for enabling events after TRX Close + Reopen */ + return l1if_enable_events(trx); +} + +int l1if_trx_open(struct gsm_bts_trx *trx) +{ + /* putting it all together */ + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_OPEN_CMD *oc; + + oc = (tOCTVC1_GSM_MSG_TRX_OPEN_CMD *) msgb_put(msg, sizeof(*oc)); + l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_OPEN_CID); + oc->ulRfPortIndex = fl1h->config.rf_port_index; + oc->TrxId.byTrxId = trx->nr; + oc->Config.ulBand = osmocom_to_octphy_band(trx->bts->band, trx->arfcn); + oc->Config.usArfcn = trx->arfcn; + oc->Config.usTsc = trx->bts->bsic & 0x7; + oc->Config.usBcchArfcn = trx->bts->c0->arfcn; + oc->RfConfig.ulRxGainDb = fl1h->config.rx_gain_db; + /* FIXME: compute this based on nominal transmit power, etc. */ + oc->RfConfig.ulTxAttndB = fl1h->config.tx_atten_db; + + LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, " + "tsc=%u, rx_gain=%u, tx_atten=%u)\n", + oc->TrxId.byTrxId, oc->ulRfPortIndex, oc->Config.usArfcn, + oc->Config.usTsc, oc->RfConfig.ulRxGainDb, + oc->RfConfig.ulTxAttndB); + + mOCTVC1_GSM_MSG_TRX_OPEN_CMD_SWAP(oc); + + return l1if_req_compl(fl1h, msg, trx_open_compl_cb, NULL); +} + +static int trx_close_all_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *car = + (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *) resp->l2h; + + mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP_SWAP(car); + + msgb_free(resp); + + return 0; +} + +int l1if_trx_close_all(struct gsm_bts *bts) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(bts->c0); + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *cac; + + cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *) + msgb_put(msg, sizeof(*cac)); + l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID); + + mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD_SWAP(cac); + + return l1if_req_compl(fl1h, msg, trx_close_all_cb, NULL); +} + + +uint32_t trx_get_hlayer1(struct gsm_bts_trx * trx) +{ + return 0; +} + +static int trx_init(struct gsm_bts_trx *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); */ + } + + l1if_trx_close_all(trx->bts); + + return l1if_trx_open(trx); +} + +/*********************************************************************** + * PHYSICAL CHANNE ACTIVATION + ***********************************************************************/ + +static int pchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h; + uint8_t ts_nr; + struct gsm_bts_trx_ts *ts; + struct gsm_abis_mo *mo; + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar); + ts_nr = ar->PchId.byTimeslotNb; + + OSMO_ASSERT(ar->TrxId.byTrxId == trx->nr); + OSMO_ASSERT(ts_nr <= ARRAY_SIZE(trx->ts)); + + ts = &trx->ts[ts_nr]; + + LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.conf(trx=%u, ts=%u, chcomb=%u) = %s\n", + ts->trx->nr, ts->nr, ts->pchan, + octvc1_rc2string(ar->Header.ulReturnCode)); + + if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, + "PCHAN-ACT failed: %s\n\n", + octvc1_rc2string(ar->Header.ulReturnCode)); + LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + exit(-1); + } + + trx = ts->trx; + mo = &trx->ts[ar->PchId.byTimeslotNb].mo; + + msgb_free(resp); + + return opstart_compl(mo); +} + +static int ts_connect(struct gsm_bts_trx_ts *ts) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *oc = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *) oc; + + oc = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *) msgb_put(msg, sizeof(*oc)); + l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID); + + oc->TrxId.byTrxId = ts->trx->nr; + oc->PchId.byTimeslotNb = ts->nr; + oc->ulChannelType = pchan_to_logChComb[ts->pchan]; + + /* TODO: how should we know the payload type here? Also, why + * would the payload type have to be the same for both halves of + * a TCH/H ? */ + switch (oc->ulChannelType) { + case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF: + oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE; + break; + case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH: + oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE; + break; + } + + LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.req(trx=%u, ts=%u, chcomb=%u)\n", + ts->trx->nr, ts->nr, ts->pchan); + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD_SWAP(oc); + + return l1if_req_compl(fl1h, msg, pchan_act_compl_cb, NULL); +} + +/*********************************************************************** + * BTS MODEL CALLBACKS + ***********************************************************************/ + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + /* TODO: How to do this ? */ + return 0; +} + +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; +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + int i; + for (i = 0; i < bts->num_trx; i++) { + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i); + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + l1if_activate_rf(fl1h, 1); + } + return 0; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + /* TODO: implement this properly */ + /* blindly accept all state changes */ + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + struct octphy_hdl *fl1 = trx_octphy_hdl(trx); + return l1if_activate_rf(fl1, 0); +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + /* FIXME: close only one TRX */ + return l1if_trx_close_all(trx->bts); +} + + +/* callback from OML */ +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, + struct tlv_parsed *new_attr, void *obj) +{ + /* FIXME: check if the attributes are valid */ + return 0; +} + +/* callback from OML */ +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ + if (kind == NM_OC_RADIO_CARRIER) { + struct gsm_bts_trx *trx = obj; + /*struct octphy_hdl *fl1h = trx_octphy_hdl(trx); */ + + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + } + 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 = -1; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + rc = trx_init(obj); + break; + case NM_OC_CHANNEL: + rc = ts_connect(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); + break; + default: + rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); + } + return rc; +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ +#warning "Implement bts_model_change_power based on TRX_MODIFY_RF_CID" + return 0; +} diff --git a/src/osmo-bts-octphy/l1_oml.h b/src/osmo-bts-octphy/l1_oml.h new file mode 100644 index 000000000..4729df5b5 --- /dev/null +++ b/src/osmo-bts-octphy/l1_oml.h @@ -0,0 +1,18 @@ +#pragma once + +#include "l1_if.h" + +/* channel control */ +int l1if_rsl_chan_act(struct gsm_lchan *lchan); +int l1if_rsl_chan_rel(struct gsm_lchan *lchan); +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan); +int l1if_rsl_mode_modify(struct gsm_lchan *lchan); + +int l1if_set_ciphering(struct gsm_lchan *lchan, int dir_downlink); + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); + +int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, + const uint8_t * attr_ids, unsigned int num_attr_ids); + +int lchan_activate(struct gsm_lchan *lchan); diff --git a/src/osmo-bts-octphy/l1_tch.c b/src/osmo-bts-octphy/l1_tch.c new file mode 100644 index 000000000..3c91fcce2 --- /dev/null +++ b/src/osmo-bts-octphy/l1_tch.c @@ -0,0 +1,402 @@ +/* Traffic Channel (TCH) part of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte + * + * based on a copy of osmo-bts-sysmo/l1_tch.c, which is + * Copyright (C) 2011-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +#include "l1_if.h" + +#define GSM_FR_BITS 260 +#define GSM_EFR_BITS 244 + +#define GSM_FR_BYTES 33 /* TS 101318 Chapter 5.1: 260 bits + 4bit sig */ +#define GSM_HR_BYTES 14 /* TS 101318 Chapter 5.2: 112 bits, no sig */ +#define GSM_EFR_BYTES 31 /* TS 101318 Chapter 5.3: 244 bits + 4bit sig */ + +/* input octet-aligned, output not octet-aligned */ +void osmo_nibble_shift_right(uint8_t *out, const uint8_t *in, + unsigned int num_nibbles) +{ + unsigned int i; + unsigned int num_whole_bytes = num_nibbles / 2; + + /* first byte: upper nibble empty, lower nibble from src */ + out[0] = (in[0] >> 4); + + /* bytes 1.. */ + for (i = 1; i < num_whole_bytes; i++) + out[i] = ((in[i - 1] & 0xF) << 4) | (in[i] >> 4); + + /* shift the last nibble, in case there's an odd count */ + i = num_whole_bytes; + if (num_nibbles & 1) + out[i] = ((in[i - 1] & 0xF) << 4) | (in[i] >> 4); + else + out[i] = (in[i - 1] & 0xF) << 4; +} + +/* input unaligned, output octet-aligned */ +void osmo_nibble_shift_left_unal(uint8_t *out, const uint8_t *in, + unsigned int num_nibbles) +{ + unsigned int i; + unsigned int num_whole_bytes = num_nibbles / 2; + + for (i = 0; i < num_whole_bytes; i++) + out[i] = ((in[i] & 0xF) << 4) | (in[i + 1] >> 4); + + /* shift the last nibble, in case there's an odd count */ + i = num_whole_bytes; + if (num_nibbles & 1) + out[i] = (in[i] & 0xF) << 4; +} + +struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1C-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 */ + + 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 + */ +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; +} + +static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, uint8_t payload_len) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1C-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 */ + 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 +#warning "We don't support EFR with L1 that doesn't support RTP mode!" +#else + memcpy(l1_payload, rtp_payload, payload_len); + +#endif + return payload_len; +} + +static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1C, LOGL_ERROR, "L1 HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return NULL; + } + + cur = msgb_put(msg, GSM_HR_BYTES); + memcpy(cur, l1_payload, GSM_HR_BYTES); + +#ifndef USE_L1_RTP_MODE + /* reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(cur, GSM_HR_BYTES); +#endif /* USE_L1_RTP_MODE */ + + 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) +{ + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1C, LOGL_ERROR, "RTP HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return 0; + } + + memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); + +#ifndef USE_L1_RTP_MODE + /* 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; +} + + +/* brief receive a traffic L1 primitive for a given lchan */ +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT * + data_ind) +{ + uint32_t payload_type = data_ind->Data.ulPayloadType; + uint8_t *payload = data_ind->Data.abyDataContent; + + uint8_t payload_len; + struct msgb *rmsg = NULL; + struct gsm_lchan *lchan = + &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + + if (data_ind->Data.ulDataLength < 1) { + LOGP(DL1C, LOGL_ERROR, "chan_nr %d Rx Payload size 0\n", + chan_nr); + return -EINVAL; + } + payload_len = data_ind->Data.ulDataLength; + + switch (payload_type) { + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE: + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_ENH_FULL_RATE: + if (lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_FULL_RATE: + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_HALF_RATE: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + default: + LOGP(DL1C, LOGL_NOTICE, + "%s Rx Payload Type %d is unsupported\n", + gsm_lchan_name(lchan), payload_type); + break; + } + + LOGP(DL1C, LOGL_DEBUG, "%s Rx codec frame (%u): %s\n", + gsm_lchan_name(lchan), payload_len, osmo_hexdump(payload, + payload_len)); + + switch (payload_type) { + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE: + rmsg = l1_to_rtppayload_fr(payload, payload_len); + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE: + /* Currently not supported */ +#if 0 + rmsg = l1_to_rtppayload_hr(payload, payload_len); + break; +#endif + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_ENH_FULL_RATE: + /* Currently not supported */ +#if 0 + rmsg = l1_to_rtppayload_efr(payload, payload_len); + break; +#endif + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_FULL_RATE: + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_HALF_RATE: + /* Currently not supported */ +#if 0 + rmsg = l1_to_rtppayload_amr(payload, payload_len, + &lchan->tch.amr_mr); +#else + LOGP(DL1C, LOGL_ERROR, "OctPHY only supports FR!\n"); + return -1; +#endif + break; + } + + if (rmsg) { + struct osmo_phsap_prim *l1sap; + + LOGP(DL1C, LOGL_DEBUG, "%s Rx -> RTP: %s\n", + gsm_lchan_name(lchan), osmo_hexdump(rmsg->data, + rmsg->len)); + + /* add l1sap header */ + rmsg->l2h = rmsg->data; + msgb_push(rmsg, sizeof(*l1sap)); + rmsg->l1h = rmsg->data; + l1sap = msgb_l1sap_prim(rmsg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, + PRIM_OP_INDICATION, rmsg); + l1sap->u.tch.chan_nr = chan_nr; + + return l1sap_up(trx, l1sap); + } + + return 0; + +err_payload_match: + LOGP(DL1C, LOGL_ERROR, + "%s Rx Payload Type %d incompatible with lchan\n", + gsm_lchan_name(lchan), payload_type); + return -EINVAL; +} + +#define RTP_MSGB_ALLOC_SIZE 512 + +/*! \brief function for incoming RTP via TCH.req + * \param rs RTP Socket + * \param[in] rtp_pl buffer containing RTP payload + * \param[in] rtp_pl_len length of \a rtp_pl + * + * 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. + */ +void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type, + uint8_t *data, uint32_t *len, const uint8_t *rtp_pl, + unsigned int rtp_pl_len) +{ + uint8_t *l1_payload; + int rc = -1; + + DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(rtp_pl, rtp_pl_len)); + + l1_payload = &data[0]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) { + *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE; + rc = rtppayload_to_l1_fr(l1_payload, + rtp_pl, rtp_pl_len); + } else { + *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE; + /* Not supported currently */ + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len); + } + break; + case GSM48_CMODE_SPEECH_EFR: + /* Not supported currently */ +#if 0 + *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_EFR; + rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, rtp_pl_len); + break; +#endif + case GSM48_CMODE_SPEECH_AMR: + /* Not supported currently */ +#if 0 + *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR; + rc = rtppayload_to_l1_amr(l1_payload, rtp_pl, rtp_pl_len); + break; +#endif + LOGP(DRTP, LOGL_ERROR, "OctPHY only supports FR!\n"); + 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; + } + + *len = rc; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); +} diff --git a/src/osmo-bts-octphy/l1_utils.c b/src/osmo-bts-octphy/l1_utils.c new file mode 100644 index 000000000..fd399a65e --- /dev/null +++ b/src/osmo-bts-octphy/l1_utils.c @@ -0,0 +1,130 @@ +/* Layer 1 (PHY) Utilities of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include "l1_utils.h" +#include +#include +#include + +const struct value_string octphy_l1sapi_names[23] = +{ + { cOCTVC1_GSM_SAPI_ENUM_IDLE, "IDLE" }, + { cOCTVC1_GSM_SAPI_ENUM_FCCH, "FCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_SCH, "SCH" }, + { cOCTVC1_GSM_SAPI_ENUM_SACCH, "SACCH" }, + { cOCTVC1_GSM_SAPI_ENUM_SDCCH, "SDCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_BCCH, "BCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH,"PCH_AGCH" }, + { cOCTVC1_GSM_SAPI_ENUM_CBCH, "CBCH" }, + { cOCTVC1_GSM_SAPI_ENUM_RACH, "RACH" }, + { cOCTVC1_GSM_SAPI_ENUM_TCHF, "TCH/F" }, + { cOCTVC1_GSM_SAPI_ENUM_FACCHF, "FACCH/F" }, + { cOCTVC1_GSM_SAPI_ENUM_TCHH, "TCH/H" }, + { cOCTVC1_GSM_SAPI_ENUM_FACCHH, "FACCH/H" }, + { cOCTVC1_GSM_SAPI_ENUM_NCH, "NCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PDTCH, "PDTCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PACCH, "PACCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PBCCH, "PBCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PAGCH, "PAGCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PPCH, "PPCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PNCH, "PNCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PTCCH, "PTCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PRACH, "PRACH" }, + { 0, NULL } +}; + +const struct value_string octphy_dir_names[5] = +{ + { cOCTVC1_GSM_DIRECTION_ENUM_NONE, "None" }, + { cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS, "TX_BTS_MS(DL)" }, + { cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS, "RX_BTS_MS(UL)" }, + { cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS | cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS, "BOTH" }, + { 0, NULL } +}; + +const struct value_string octphy_clkmgr_state_vals[8] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNINITIALIZE, "UNINITIALIZED" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE, "IDLE" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_NO_EXT_CLOCK, "NO_EXT_CLOCK" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOCKED, "LOCKED" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNLOCKED, "UNLOCKED" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_ERROR, "ERROR" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_DISABLE, "DISABLED" }, + { 0, NULL } +}; + +const struct value_string octphy_cid_vals[35] = { + { cOCTVC1_GSM_MSG_TRX_OPEN_CID, "TRX-OPEN" }, + { cOCTVC1_GSM_MSG_TRX_CLOSE_CID, "TRX-CLOSE" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_CID, "TRX-STATUS" }, + { cOCTVC1_GSM_MSG_TRX_INFO_CID, "TRX-INFO" }, + { cOCTVC1_GSM_MSG_TRX_RESET_CID, "TRX-RESET" }, + { cOCTVC1_GSM_MSG_TRX_MODIFY_CID, "TRX-MODIFY" }, + { cOCTVC1_GSM_MSG_TRX_LIST_CID, "TRX-LIST" }, + { cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID, "TRX-CLOSE-ALL" }, + { cOCTVC1_GSM_MSG_TRX_START_RECORD_CID, "RECORD-START" }, + { cOCTVC1_GSM_MSG_TRX_STOP_RECORD_CID, "RECORD-STOP" }, + { cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID, "LCHAN-ACT" }, + { cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID, "LCHAN-DEACT" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_LOGICAL_CHANNEL_CID, "LCHAN-STATUS" }, + { cOCTVC1_GSM_MSG_TRX_INFO_LOGICAL_CHANNEL_CID, "LCHAN-INFO" }, + { cOCTVC1_GSM_MSG_TRX_LIST_LOGICAL_CHANNEL_CID, "LCHAN-LIST" }, + { cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CID, + "LCHAN-EMPTY-FRAME" }, + { cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID, "LCHAN-DATA" }, + { cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID, "PCHAN-ACT" }, + { cOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CID, "PCHAN-DEACT" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_PHYSICAL_CHANNEL_CID, "PCHAN-STATUS" }, + { cOCTVC1_GSM_MSG_TRX_RESET_PHYSICAL_CHANNEL_CID, "PCHAN-RESET" }, + { cOCTVC1_GSM_MSG_TRX_LIST_PHYSICAL_CHANNEL_CID, "PCHAN-LIST" }, + { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_CID, "PCHAN-INFO" }, + { cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID, + "PCHAN-CIPH-MODIFY" }, + { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_CIPHERING_CID, + "PCHAN-CIPH-INFO" }, + { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_MEASUREMENT_CID, + "PCHAN-MEASUREMENT" }, + { cOCTVC1_GSM_MSG_TRX_INFO_RF_CID, "RF-INFO" }, + { cOCTVC1_GSM_MSG_TRX_MODIFY_RF_CID, "RF-MODIFY" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_LIST_CID, "TAP-FILTER-LIST" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_INFO_CID, "TAP-FILTER-INFO" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_STATS_CID, "TAP-FILTER-STATS" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_MODIFY_CID, "TAP-FILTER-MODIFY" }, + { cOCTVC1_GSM_MSG_TRX_START_LOGICAL_CHANNEL_RAW_DATA_INDICATIONS_CID, + "LCHAN-RAW-DATA-START" }, + { cOCTVC1_GSM_MSG_TRX_STOP_LOGICAL_CHANNEL_RAW_DATA_INDICATIONS_CID, + "LCHAN-RAW-DATA-STOP" }, + { 0, NULL } +}; + +const struct value_string octphy_eid_vals[7] = { + { cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID, "TIME.ind" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID, "TRX-STATUS-CHG.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID, + "LCHAN-DATA.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID, + "LCHAN-RTS.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID, + "LCHAN-RACH.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RAW_DATA_INDICATION_EID, + "LCHAN-RAW-DATA.ind" }, + { 0, NULL } +}; diff --git a/src/osmo-bts-octphy/l1_utils.h b/src/osmo-bts-octphy/l1_utils.h new file mode 100644 index 000000000..cec9f75ee --- /dev/null +++ b/src/osmo-bts-octphy/l1_utils.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +const struct value_string octphy_l1sapi_names[23]; +const struct value_string octphy_dir_names[5]; +const struct value_string octphy_clkmgr_state_vals[8]; +const struct value_string octphy_cid_vals[35]; +const struct value_string octphy_eid_vals[7]; diff --git a/src/osmo-bts-octphy/main.c b/src/osmo-bts-octphy/main.c new file mode 100644 index 000000000..1cb7cfc27 --- /dev/null +++ b/src/osmo-bts-octphy/main.c @@ -0,0 +1,305 @@ +/* Main program of osmo-bts for OCTPHY-2G */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte + * + * based on a copy of osmo-bts-sysmo/main.c, which is + * Copyright (C) 2011-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" + +int pcu_direct = 0; +#define RF_LOCK_PATH "/var/lock/bts_rf_lock" + +static const char *config_file = "osmo-bts.cfg"; +static int daemonize = 0; +static int rt_prio = -1; +static char *gsmtap_ip = 0; + +static void print_help() +{ + printf( "Some useful options:\n" + " -h --help this text\n" + " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n" + " -D --daemonize For the process into a background daemon\n" + " -c --config-file Specify the filename of the config file\n" + " -s --disable-color Don't use colors in stderr log output\n" + " -T --timestamp Prefix every log line with a timestamp\n" + " -V --version Print version information and exit\n" + " -e --log-level Set a global log-level\n" + " -r --realtime PRIO Use SCHED_RR with the specified priority\n" + " -i --gsmtap-ip The destination IP used for GSMTAP.\n" + ); +} + +/* FIXME: finally get some option parsing code into libosmocore */ +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + /* FIXME: all those are generic Osmocom app options */ + { "help", 0, 0, 'h' }, + { "debug", 1, 0, 'd' }, + { "daemonize", 0, 0, 'D' }, + { "config-file", 1, 0, 'c' }, + { "disable-color", 0, 0, 's' }, + { "timestamp", 0, 0, 'T' }, + { "version", 0, 0, 'V' }, + { "log-level", 1, 0, 'e' }, + { "realtime", 1, 0, 'r' }, + { "gsmtap-ip", 1, 0, 'i' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "hc:d:Dc:sTVe:r:i:m:l:", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + break; + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'd': + log_parse_category_mask(osmo_stderr_target, optarg); + break; + case 'D': + daemonize = 1; + break; + case 'c': + config_file = strdup(optarg); + break; + case 'T': + log_set_print_timestamp(osmo_stderr_target, 1); + break; + case 'V': + print_version(1); + exit(0); + break; + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 'r': + rt_prio = atoi(optarg); + break; + case 'i': + gsmtap_ip = optarg; + break; + default: + break; + } + } +} + +static struct gsm_bts *bts; + +static void signal_handler(int signal) +{ + fprintf(stderr, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + //osmo_signal_dispatch(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL); + bts_shutdown(bts, "SIGINT"); + break; + case SIGABRT: + case SIGUSR1: + case SIGUSR2: + talloc_report_full(tall_bts_ctx, stderr); + break; + default: + break; + } +} + +static int write_pid_file(char *procname) +{ + FILE *outf; + char tmp[PATH_MAX + 1]; + + snprintf(tmp, sizeof(tmp) - 1, "/var/run/%s.pid", procname); + tmp[PATH_MAX - 1] = '\0'; + + outf = fopen(tmp, "w"); + if (!outf) + return -1; + + fprintf(outf, "%d\n", getpid()); + + fclose(outf); + + return 0; +} + +int main(int argc, char **argv) +{ + struct stat st; + struct sched_param param; + struct gsm_bts_role_bts *btsb; + struct e1inp_line *line; + void *tall_msgb_ctx; + int rc; + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb"); + msgb_set_talloc_ctx(tall_msgb_ctx); + + bts_log_init(NULL); + + bts = gsm_bts_alloc(tall_bts_ctx); + vty_init(&bts_vty_info); + e1inp_vty_init(); + bts_vty_init(bts, &bts_log_info); + + handle_options(argc, argv); + + /* enable realtime priority for us */ + if (rt_prio != -1) { + memset(¶m, 0, sizeof(param)); + param.sched_priority = rt_prio; + rc = sched_setscheduler(getpid(), SCHED_RR, ¶m); + if (rc != 0) { + fprintf(stderr, + "Setting SCHED_RR priority(%d) failed: %s\n", + param.sched_priority, strerror(errno)); + exit(1); + } + } + + if (gsmtap_ip) { + gsmtap = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); + if (!gsmtap) { + fprintf(stderr, "Failed during gsmtap_init()\n"); + exit(1); + } + gsmtap_source_add_sink(gsmtap); + } + + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to to open bts\n"); + exit(1); + } + btsb = bts_role_bts(bts); + btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + abis_init(bts); + + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", + config_file); + exit(1); + } + + if (stat(RF_LOCK_PATH, &st) == 0) { + LOGP(DL1C, LOGL_NOTICE, + "Not starting BTS due to RF_LOCK file present\n"); + exit(23); + } + write_pid_file("osmo-bts"); + + rc = telnet_init(tall_bts_ctx, NULL, 4241); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + if (pcu_sock_init()) { + fprintf(stderr, "PCU L1 socket failed\n"); + exit(-1); + } + + signal(SIGINT, &signal_handler); + //signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + if (!btsb->bsc_oml_host) { + fprintf(stderr, + "Cannot start BTS without knowing BSC OML IP\n"); + exit(1); + } + + line = abis_open(bts, btsb->bsc_oml_host, "OsmoBTS-OCTPHY"); + if (!line) { + fprintf(stderr, "unable to connect to BSC\n"); + exit(1); + } + + /* Open L1 interface */ + rc = l1if_open(bts->c0->role_bts.l1h); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Cannot open L1 Interface\n"); + exit(1); + } + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + log_reset_context(); + osmo_select_main(0); + } + +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} diff --git a/src/osmo-bts-octphy/octphy_hw_api.c b/src/osmo-bts-octphy/octphy_hw_api.c new file mode 100644 index 000000000..5291742f6 --- /dev/null +++ b/src/osmo-bts-octphy/octphy_hw_api.c @@ -0,0 +1,364 @@ +/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2015 Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include + +#include +#include + +#include + +#include "l1_if.h" +#include "l1_oml.h" +#include "l1_utils.h" + +#include +#include +#include + +/* Chapter 12.1 */ +static int get_pcb_info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + tOCTVC1_HW_MSG_PCB_INFO_RSP *pir = + (tOCTVC1_HW_MSG_PCB_INFO_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_PCB_INFO_RSP_SWAP(pir); + + LOGP(DL1C, LOGL_INFO, "HW-PCB-INFO.resp: Name=%s %s, Serial=%s, " + "FileName=%s, InfoSource=%u, InfoState=%u, GpsName=%s, " + "WiFiName=%s\n", pir->szName, pir->ulDeviceId ? "SEC" : "PRI", + pir->szSerial, pir->szFilename, pir->ulInfoSource, + pir->ulInfoState, pir->szGpsName, pir->szWifiName); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.1 */ +int octphy_hw_get_pcb_info(struct octphy_hdl *fl1h) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_PCB_INFO_CMD *pic; + + pic = (tOCTVC1_HW_MSG_PCB_INFO_CMD *) msgb_put(msg, sizeof(*pic)); + + l1if_fill_msg_hdr(&pic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_PCB_INFO_CID); + + mOCTVC1_HW_MSG_PCB_INFO_CMD_SWAP(pic); + + return l1if_req_compl(fl1h, msg, get_pcb_info_compl_cb, NULL); +} + +/* Chapter 12.9 */ +static int rf_port_info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_INFO_RSP *pir = + (tOCTVC1_HW_MSG_RF_PORT_INFO_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_INFO_RSP_SWAP(pir); + + LOGP(DL1C, LOGL_INFO, "RF-PORT-INFO.resp Idx=%u, InService=%u, " + "hOwner=0x%x, Id=%u, FreqMin=%u, FreqMax=%u\n", + pir->ulPortIndex, pir->ulInService, pir->hOwner, + pir->ulPortInterfaceId, pir->ulFrequencyMinKhz, + pir->ulFrequencyMaxKhz); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.9 */ +int octphy_hw_get_rf_port_info(struct octphy_hdl *fl1h, uint32_t index) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_INFO_CMD *pic; + + pic = (tOCTVC1_HW_MSG_RF_PORT_INFO_CMD *) msgb_put(msg, sizeof(*pic)); + + l1if_fill_msg_hdr(&pic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_INFO_CID); + + pic->ulPortIndex = index; + + mOCTVC1_HW_MSG_RF_PORT_INFO_CMD_SWAP(pic); + + return l1if_req_compl(fl1h, msg, rf_port_info_compl_cb, NULL); +} + +static const struct value_string radio_std_vals[] = { + { cOCTVC1_RADIO_STANDARD_ENUM_GSM, "GSM" }, + { cOCTVC1_RADIO_STANDARD_ENUM_UMTS, "UMTS" }, + { cOCTVC1_RADIO_STANDARD_ENUM_LTE, "LTE" }, + { cOCTVC1_RADIO_STANDARD_ENUM_INVALID, "INVALID" }, + { 0, NULL } +}; + +/* Chapter 12.10 */ +static int rf_port_stats_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *psr = + (tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_STATS_RSP_SWAP(psr); + + LOGP(DL1C, LOGL_INFO, "RF-PORT-STATS.resp Idx=%u RadioStandard=%s, " + "Rx(Bytes=%u, Overflow=%u, AvgBps=%u, Period=%uus, Freq=%u) " + "Tx(Bytes=%i, Underflow=%u, AvgBps=%u, Period=%uus, Freq=%u)\n", + psr->ulPortIndex, + get_value_string(radio_std_vals, psr->ulRadioStandard), + psr->RxStats.ulRxByteCnt, psr->RxStats.ulRxOverflowCnt, + psr->RxStats.ulRxAverageBytePerSecond, + psr->RxStats.ulRxAveragePeriodUs, + psr->RxStats.ulFrequencyKhz, + psr->TxStats.ulTxByteCnt, psr->TxStats.ulTxUnderflowCnt, + psr->TxStats.ulTxAverageBytePerSecond, + psr->TxStats.ulTxAveragePeriodUs, + psr->TxStats.ulFrequencyKhz); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.10 */ +int octphy_hw_get_rf_port_stats(struct octphy_hdl *fl1h, uint32_t index) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_STATS_CMD *psc; + + psc = (tOCTVC1_HW_MSG_RF_PORT_STATS_CMD *) msgb_put(msg, sizeof(*psc)); + + l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_STATS_CID); + + psc->ulPortIndex = index; + psc->ulResetStatsFlag = cOCT_FALSE; + + mOCTVC1_HW_MSG_RF_PORT_STATS_CMD_SWAP(psc); + + return l1if_req_compl(fl1h, msg, rf_port_stats_compl_cb, NULL); +} + +static const struct value_string rx_gain_mode_vals[] = { + { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_MGC, "Manual" }, + { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_AGC_FAST_ATK, "Automatic (fast)" }, + { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_AGC_SLOW_ATK, "Automatic (slow)" }, + { 0, NULL } +}; + +/* Chapter 12.13 */ +static int rf_ant_rx_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP *arc = + (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP_SWAP(arc); + + LOGP(DL1C, LOGL_INFO, "ANT-RX-CONFIG.resp(Port=%u, Ant=%u): %s, " + "Gain %d dB, GainCtrlMode=%s\n", + arc->ulPortIndex, arc->ulAntennaIndex, + arc->ulEnableFlag ? "Enabled" : "Disabled", + arc->lRxGaindB/512, + get_value_string(rx_gain_mode_vals, arc->ulRxGainMode)); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.13 */ +int octphy_hw_get_rf_ant_rx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD *psc; + + psc = (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD *) + msgb_put(msg, sizeof(*psc)); + + l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CID); + + psc->ulPortIndex = port_idx; + psc->ulAntennaIndex = ant_idx; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD_SWAP(psc); + + return l1if_req_compl(fl1h, msg, rf_ant_rx_compl_cb, NULL); + +} + +/* Chapter 12.14 */ +static int rf_ant_tx_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP *atc = + (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP_SWAP(atc); + + LOGP(DL1C, LOGL_INFO, "ANT-TX-CONFIG.resp(Port=%u, Ant=%u): %s, " + "Gain %d dB\n", + atc->ulPortIndex, atc->ulAntennaIndex, + atc->ulEnableFlag ? "Enabled" : "Disabled", + atc->lTxGaindB/512); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.14 */ +int octphy_hw_get_rf_ant_tx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD *psc; + + psc = (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD *) + msgb_put(msg, sizeof(*psc)); + + l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CID); + + psc->ulPortIndex = port_idx; + psc->ulAntennaIndex = ant_idx; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD_SWAP(psc); + + return l1if_req_compl(fl1h, msg, rf_ant_tx_compl_cb, NULL); + +} + +static const struct value_string clocksync_source_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_1HZ, "1 Hz" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_10MHZ, "10 MHz" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_30_72MHZ, "30.72 MHz" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_1HZ_EXT, "1 Hz (ext)"}, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_NONE, "None" }, + { 0, NULL } +}; + +static const struct value_string clocksync_sel_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_AUTOSELECT, + "Autoselect" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_CONFIG_FILE, + "Config File" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_HOST_APPLICATION, + "Host Application" }, + { 0, NULL } +}; + +static const struct value_string clocksync_source_state_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_STATE_ENUM_INVALID, "Invalid" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_STATE_ENUM_VALID, "Valid" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_STATE_ENUM_UNSPECIFIED, + "Unspecified" }, + { 0, NULL } +}; + +static const struct value_string clocksync_state_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNINITIALIZE, + "Uninitialized" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE, "Idle" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_NO_EXT_CLOCK, + "No External Clock" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOCKED, "Locked" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNLOCKED,"Unlocked" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_ERROR, "Error" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_DISABLE, "Disabled" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOSS_EXT_CLOCK, + "Loss of Ext Clock" }, + { 0, NULL } +}; + +/* Chapter 12.15 */ +static int get_clock_sync_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP *cir = + (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP_SWAP(cir); + + LOGP(DL1C, LOGL_INFO, "CLOCK-SYNC-MGR-INFO.resp Reference=%s ", + get_value_string(clocksync_source_vals, cir->ulClkSourceRef)); + LOGPC(DL1C, LOGL_INFO, "Selection=%s)\n", + get_value_string(clocksync_sel_vals, cir->ulClkSourceSelection)); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.15 */ +int octphy_hw_get_clock_sync_info(struct octphy_hdl *fl1h) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD *cic; + + cic = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD *) + msgb_put(msg, sizeof(*cic)); + l1if_fill_msg_hdr(&cic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CID); + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD_SWAP(cic); + + return l1if_req_compl(fl1h, msg, get_clock_sync_compl_cb, NULL); +} + +/* Chapter 12.16 */ +static int get_clock_sync_stats_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *csr = + (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP_SWAP(csr); + + LOGP(DL1C, LOGL_INFO, "CLOCK-SYNC-MGR-STATS.resp State=%s, " + "ClockError=%d DroppedCycles=%d, PllFreqHz=%u, PllFract=%u, " + "SlipCnt=%u SyncLosses=%u SourceState=%u, DacValue=%u\n", + get_value_string(clocksync_state_vals, csr->ulState), + csr->lClockError, csr->lDroppedCycles, csr->ulPllFreqHz, + csr->ulPllFractionalFreqHz, csr->ulSlipCnt, + csr->ulSyncLosseCnt, csr->ulSourceState, csr->ulDacValue); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.16 */ +int octphy_hw_get_clock_sync_stats(struct octphy_hdl *fl1h) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD *csc; + + csc = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD *) + msgb_put(msg, sizeof(*csc)); + l1if_fill_msg_hdr(&csc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CID); + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD_SWAP(csc); + + return l1if_req_compl(fl1h, msg, get_clock_sync_stats_cb, NULL); +} + diff --git a/src/osmo-bts-octphy/octphy_hw_api.h b/src/osmo-bts-octphy/octphy_hw_api.h new file mode 100644 index 000000000..bc8ab68d1 --- /dev/null +++ b/src/osmo-bts-octphy/octphy_hw_api.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "l1_if.h" + +int octphy_hw_get_pcb_info(struct octphy_hdl *fl1h); +int octphy_hw_get_rf_port_info(struct octphy_hdl *fl1h, uint32_t index); +int octphy_hw_get_rf_port_stats(struct octphy_hdl *fl1h, uint32_t index); +int octphy_hw_get_rf_ant_rx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx); +int octphy_hw_get_rf_ant_tx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx); +int octphy_hw_get_clock_sync_info(struct octphy_hdl *fl1h); +int octphy_hw_get_clock_sync_stats(struct octphy_hdl *fl1h); diff --git a/src/osmo-bts-octphy/octphy_vty.c b/src/osmo-bts-octphy/octphy_vty.c new file mode 100644 index 000000000..0f18e4c8c --- /dev/null +++ b/src/osmo-bts-octphy/octphy_vty.c @@ -0,0 +1,193 @@ +/* VTY interface for osmo-bts OCTPHY integration */ + +/* (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "l1_if.h" +#include "l1_utils.h" +#include "octphy_hw_api.h" + +#define TRX_STR "Transceiver related commands\n" "TRX number\n" + +#define SHOW_TRX_STR \ + SHOW_STR \ + TRX_STR + +static struct gsm_bts *vty_bts; + +/* configuration */ + +DEFUN(cfg_bts_phy_hwaddr, cfg_bts_phy_hwaddr_cmd, + "phy-hw-addr HWADDR", + "Configure the hardware addess of the OCTPHY\n" + "hardware address in aa:bb:cc:dd:ee:ff format\n") +{ + struct gsm_bts *bts = vty->index; + struct gsm_bts_trx *trx = bts->c0; + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + int rc; + + rc = osmo_macaddr_parse(fl1h->phy_addr.sll_addr, argv[0]); + if (rc < 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_phy_netdev, cfg_bts_phy_netdev_cmd, + "phy-netdev NAME", + "Configure the hardware device towards the OCTPHY\n" + "Ethernet device name\n") +{ + struct gsm_bts *bts = vty->index; + struct gsm_bts_trx *trx = bts->c0; + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + + if (fl1h->netdev_name) + talloc_free(fl1h->netdev_name); + fl1h->netdev_name = talloc_strdup(fl1h, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_rf_port_idx, cfg_trx_rf_port_idx_cmd, + "rf-port-index <0-255>", + "Configure the RF Port for this TRX\n" + "RF Port Index\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + + fl1h->config.rf_port_index = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_rx_gain_db, cfg_trx_rx_gain_db_cmd, + "rx-gain <0-73>", + "Configure the Rx Gain in dB\n" + "Rx gain in dB\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + + fl1h->config.rx_gain_db = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_tx_atten_db, cfg_trx_tx_atten_db_cmd, + "tx-attenuation <0-359>", + "Configure the Tx Attenuation in quarter-dB\n" + "Tx attenuation in quarter-dB\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + + fl1h->config.tx_atten_db = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(get_rf_port_stats, get_rf_port_stats_cmd, + "get-rf-port-stats <0-1>", + "Obtain statistics for the RF Port\n" + "RF Port Number\n") +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(vty_bts->c0); + + octphy_hw_get_rf_port_stats(fl1h, atoi(argv[0])); + + return CMD_SUCCESS; +} + +DEFUN(get_clk_sync_stats, get_clk_sync_stats_cmd, + "get-clk-sync-stats", + "Obtain statistics for the Clock Sync Manager\n") +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(vty_bts->c0); + + octphy_hw_get_clock_sync_stats(fl1h); + + return CMD_SUCCESS; +} + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + struct gsm_bts_role_bts *btsb = bts_role_bts(bts); + struct octphy_hdl *fl1h = trx_octphy_hdl(bts->c0); + + if (btsb->auto_band) + vty_out(vty, " auto-band%s", VTY_NEWLINE); + + vty_out(vty, " phy-hw-addr %02x:%02x:%02x:%02x:%02x:%02x%s", + fl1h->phy_addr.sll_addr[0], fl1h->phy_addr.sll_addr[1], + fl1h->phy_addr.sll_addr[2], fl1h->phy_addr.sll_addr[3], + fl1h->phy_addr.sll_addr[4], fl1h->phy_addr.sll_addr[5], + VTY_NEWLINE); +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + + vty_out(vty, " rx-gain %u%s", fl1h->config.rx_gain_db, + VTY_NEWLINE); + vty_out(vty, " tx-attenuation %u%s", fl1h->config.tx_atten_db, + VTY_NEWLINE); +} + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + install_element(BTS_NODE, &cfg_bts_phy_hwaddr_cmd); + install_element(BTS_NODE, &cfg_bts_phy_netdev_cmd); + + install_element(TRX_NODE, &cfg_trx_rf_port_idx_cmd); + install_element(TRX_NODE, &cfg_trx_rx_gain_db_cmd); + install_element(TRX_NODE, &cfg_trx_tx_atten_db_cmd); + + install_element_ve(&get_rf_port_stats_cmd); + install_element_ve(&get_clk_sync_stats_cmd); + + return 0; +} diff --git a/src/osmo-bts-octphy/octpkt.c b/src/osmo-bts-octphy/octpkt.c new file mode 100644 index 000000000..a6167f50c --- /dev/null +++ b/src/osmo-bts-octphy/octpkt.c @@ -0,0 +1,190 @@ +/* Utility routines for dealing with OCTPKT/OCTVC1 in msgb */ + +/* Copyright (c) 2015 Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "octpkt.h" + +/* push a common header (1 dword) to the start of a msgb */ +void octpkt_push_common_hdr(struct msgb *msg, uint8_t format, + uint8_t trace, uint32_t ptype) +{ + uint32_t ch; + uint32_t *chptr; + uint32_t tot_len = msgb_length(msg) + sizeof(ch); + + ch = ((format & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_FORMAT_BIT_MASK) + << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_FORMAT_BIT_OFFSET) | + ((trace & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_TRACE_BIT_MASK) + << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_TRACE_BIT_OFFSET) | + ((ptype & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_CONTROL_PROTOCOL_TYPE_BIT_MASK) + << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_CONTROL_PROTOCOL_TYPE_BIT_OFFSET) | + (tot_len & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_LENGTH_BIT_MASK); + + chptr = (uint32_t *) msgb_push(msg, sizeof(ch)); + *chptr = htonl(ch); +} + +/* push a control header (3 dwords) to the start of a msgb. This format + * is used for command and response packets */ +void octvocnet_push_ctl_hdr(struct msgb *msg, uint32_t dest_fifo_id, + uint32_t src_fifo_id, uint32_t socket_id) +{ + tOCTVOCNET_PKT_CTL_HEADER *ch; + + ch = (tOCTVOCNET_PKT_CTL_HEADER *) msgb_push(msg, sizeof(*ch)); + + ch->ulDestFifoId = htonl(dest_fifo_id); + ch->ulSourceFifoId = htonl(src_fifo_id); + ch->ulSocketId = htonl(socket_id); +} + +/* push a data header (3 dwords) to the start of a msgb. This format is + * used for event packets */ +static void octvocnet_push_data_hdr(struct msgb *msg, + uint32_t log_obj_hdl, + uint32_t log_obj_pkt_port, + uint32_t dest_fifo_id) +{ + tOCTVOCNET_PKT_DATA_HEADER *dh; + + dh = (tOCTVOCNET_PKT_DATA_HEADER *) msgb_push(msg, sizeof(*dh)); + + dh->hLogicalObj = htonl(log_obj_hdl); + dh->ulLogicalObjPktPort = htonl(log_obj_pkt_port); + dh->ulDestFifoId = htonl(dest_fifo_id); +} + +/* common msg_header shared by all control messages. host byte order! */ +void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len, + uint32_t sess_id, uint32_t trans_id, + uint32_t user_info, uint32_t msg_type, + uint32_t flags, uint32_t api_cmd) +{ + uint32_t type_r_cmdid; + type_r_cmdid = ((msg_type & cOCTVC1_MSG_TYPE_BIT_MASK) + << cOCTVC1_MSG_TYPE_BIT_OFFSET) | + ((api_cmd & cOCTVC1_MSG_ID_BIT_MASK) + << cOCTVC1_MSG_ID_BIT_OFFSET); + /* Resync? Flags? */ + + mh->ulLength = len; + mh->ulTransactionId = trans_id; + mh->ul_Type_R_CmdId = type_r_cmdid; + mh->ulSessionId = sess_id; + mh->ulReturnCode = 0; + mh->ulUserInfo = user_info; +} + +static void octvc1_push_msg_hdr(struct msgb *msg, + uint32_t sess_id, + uint32_t trans_id, + uint32_t user_info, + uint32_t msg_type, + uint32_t flags, + uint32_t api_cmd) +{ + tOCTVC1_MSG_HEADER *mh; + uint32_t len = msgb_length(msg); + + mh = (tOCTVC1_MSG_HEADER *) msgb_push(msg, sizeof(*mh)); + + octvc1_fill_msg_hdr(mh, len, sess_id, trans_id, user_info, msg_type, flags, api_cmd); +} + +#include +#include +#include +#include +#include + +/*! \brief Initialize a packet socket + * \param[in] tye Socket type like SOCK_RAW or SOCK_DGRAM + * \param[in] proto The link-layer protocol in network byte order + * \param[in] bind_dev The name of the interface to bind to (if any) + * \param[in] flags flags like \ref OSMO_SOCK_F_BIND + * + * This function creates a new packet socket of \a type and \a proto + * and optionally bnds to it, if stated in the \a flags parameter. + */ +int osmo_sock_packet_init(uint16_t type, uint16_t proto, const char *bind_dev, + unsigned int flags) +{ + int sfd, rc, on = 1; + + if (flags & OSMO_SOCK_F_CONNECT) + return -EINVAL; + + sfd = socket(AF_PACKET, type, proto); + if (sfd < 0) + return -1; + + if (flags & OSMO_SOCK_F_NONBLOCK) { + if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) { + perror("cannot set this socket unblocking"); + close(sfd); + return -EINVAL; + } + } + + if (bind_dev) { + struct sockaddr_ll sa; + struct ifreq ifr; + + /* resolve the string device name to an ifindex */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, bind_dev, sizeof(ifr.ifr_name)); + rc = ioctl(sfd, SIOCGIFINDEX, &ifr); + if (rc < 0) + goto err; + + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = htons(proto); + sa.sll_ifindex = ifr.ifr_ifindex; + /* according to the packet(7) man page, bind() will only + * use sll_protocol nad sll_ifindex */ + rc = bind(sfd, (struct sockaddr *)&sa, sizeof(sa)); + if (rc < 0) + goto err; + } + + return sfd; +err: + close(sfd); + return -1; +} diff --git a/src/osmo-bts-octphy/octpkt.h b/src/osmo-bts-octphy/octpkt.h new file mode 100644 index 000000000..fcffec02c --- /dev/null +++ b/src/osmo-bts-octphy/octpkt.h @@ -0,0 +1,22 @@ +#pragma once +#include + +/* push a common header (1 dword) to the start of a msgb */ +void octpkt_push_common_hdr(struct msgb *msg, uint8_t format, + uint8_t trace, uint32_t ptype); + +/* common msg_header shared by all control messages */ +void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len, + uint32_t sess_id, uint32_t trans_id, + uint32_t user_info, uint32_t msg_type, + uint32_t flags, uint32_t api_cmd); + +/* push a control header (3 dwords) to the start of a msgb. This format + * is used for command and response packets */ +void octvocnet_push_ctl_hdr(struct msgb *msg, uint32_t dest_fifo_id, + uint32_t src_fifo_id, uint32_t socket_id); + +int osmo_sock_packet_init(uint16_t type, uint16_t proto, const char *bind_dev, + unsigned int flags); + +int tx_trx_open(struct gsm_bts_trx *trx); From dad89e50a220c9b634dd04600cfed76f4b4f7622 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Thu, 5 Nov 2015 22:55:02 +0100 Subject: [PATCH 2/9] OCTPHY: Replace '-lortp' with the proper pkg-config/autofoo version --- src/osmo-bts-octphy/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/osmo-bts-octphy/Makefile.am b/src/osmo-bts-octphy/Makefile.am index 07fc6bce2..cce492947 100644 --- a/src/osmo-bts-octphy/Makefile.am +++ b/src/osmo-bts-octphy/Makefile.am @@ -1,6 +1,6 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR) -I$(OCTSDR2G_INCDIR) -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) -COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -lortp +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(ORTP_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) EXTRA_DIST = l1_if.h l1_oml.h l1_utils.h l1_octInit.h l1_octIoMsg.h From a9003acb1c4c1a697115d37f7ea28c4650b9681a Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Fri, 13 Nov 2015 23:06:23 +0100 Subject: [PATCH 3/9] OCTPHY: Ensure we write the phy-netdev parameter When writing the config file from the command line, we must not forget to write the phy-netdev parameter, otherwise the program will fail to re-start later :/ --- src/osmo-bts-octphy/octphy_vty.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/osmo-bts-octphy/octphy_vty.c b/src/osmo-bts-octphy/octphy_vty.c index 0f18e4c8c..1b3a3652a 100644 --- a/src/osmo-bts-octphy/octphy_vty.c +++ b/src/osmo-bts-octphy/octphy_vty.c @@ -155,6 +155,10 @@ void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) struct gsm_bts_role_bts *btsb = bts_role_bts(bts); struct octphy_hdl *fl1h = trx_octphy_hdl(bts->c0); + if (fl1h->netdev_name) + vty_out(vty, " phy-netdev %s%s", fl1h->netdev_name, + VTY_NEWLINE); + if (btsb->auto_band) vty_out(vty, " auto-band%s", VTY_NEWLINE); From bca8d3b8f83af76303fe9923955bea25ef9f7e43 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Fri, 13 Nov 2015 23:09:24 +0100 Subject: [PATCH 4/9] OCTPHY: Exit gracefully if config file specifies no phy-netdev --- src/osmo-bts-octphy/l1_if.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c index d104fa753..1ed8dee86 100644 --- a/src/osmo-bts-octphy/l1_if.c +++ b/src/osmo-bts-octphy/l1_if.c @@ -1306,6 +1306,11 @@ int l1if_open(struct octphy_hdl *fl1h) int sfd, rc; char *phy_dev = fl1h->netdev_name; + if (!phy_dev) { + LOGP(DL1C, LOGL_ERROR, "You have to specify a phy-netdev\n"); + return -EINVAL; + } + LOGP(DL1C, LOGL_NOTICE, "Opening L1 interface for OctPHY (%s)\n", phy_dev); From 8d198f3598f7e0928f7b4001088dfbf007873791 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Mon, 4 Jan 2016 20:05:41 +0100 Subject: [PATCH 5/9] OCTPHY: Fix various memory leaks and add comments on msgb ownership --- src/common/l1sap.c | 2 +- src/osmo-bts-octphy/l1_if.c | 101 ++++++++++++++++++++++++----------- src/osmo-bts-octphy/l1_oml.c | 32 +++++++++-- 3 files changed, 98 insertions(+), 37 deletions(-) diff --git a/src/common/l1sap.c b/src/common/l1sap.c index 9f578d4c2..9d048e0a9 100644 --- a/src/common/l1sap.c +++ b/src/common/l1sap.c @@ -894,7 +894,7 @@ static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx, return 0; } -/* any L1 prim received from bts model */ +/* any L1 prim received from bts model, takes ownership of the msgb */ int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { struct msgb *msg = l1sap->oph.msg; diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c index 1ed8dee86..f259661fd 100644 --- a/src/osmo-bts-octphy/l1_if.c +++ b/src/osmo-bts-octphy/l1_if.c @@ -404,7 +404,6 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id); rc = -EINVAL; - msgb_free(msg); goto done; } @@ -446,8 +445,6 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD_SWAP(empty_frame_req); } - msgb_free(msg); - rc = l1if_req_compl(fl1h, l1msg, NULL, NULL); done: return rc; @@ -531,7 +528,6 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD_SWAP(empty_frame_req); } - msgb_free(msg); return l1if_req_compl(fl1h, nmsg, NULL, NULL); } @@ -588,12 +584,14 @@ static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, return rc; } -/* primitive from common part */ +/* primitive from common part. We are taking ownership of msgb */ 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); @@ -610,10 +608,9 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) rc = -EINVAL; } - if (rc) - msgb_free(msg); - return rc; + msgb_free(msg); + return rc; } int bts_model_init(struct gsm_bts *bts) @@ -644,7 +641,7 @@ int bts_model_init(struct gsm_bts *bts) * handling of messages coming up from PHY ***********************************************************************/ -static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, +static void process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, tOCTVC1_GSM_MEASUREMENT_INFO * m) { struct osmo_phsap_prim l1sap; @@ -659,7 +656,9 @@ static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, l1sap.u.info.u.meas_ind.ber10k = (unsigned int)(m->usBERCnt * 100); l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->sRSSIDbm * -1); - return l1sap_up(trx, &l1sap); + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + l1sap_up(trx, &l1sap); } static void dump_meas_res(int ll, tOCTVC1_GSM_MEASUREMENT_INFO * m) @@ -679,10 +678,8 @@ static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t fl1->alive_prim_cnt++; /* ignore every time indication, except for c0 */ - if (trx != bts->c0) { - /* Returning 0 will log an error */ - return 1; - } + if (trx != bts->c0) + return 0; if (trx_id != trx->nr) { LOGP(DL1C, LOGL_FATAL, @@ -697,7 +694,9 @@ static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t l1sap.u.info.type = PRIM_INFO_TIME; l1sap.u.info.u.time_ind.fn = fn; - return l1sap_up(trx, &l1sap); + l1sap_up(trx, &l1sap); + + return 0; } static int handle_ph_readytosend_ind(struct octphy_hdl *fl1, @@ -756,7 +755,10 @@ static int handle_ph_readytosend_ind(struct octphy_hdl *fl1, l1sap->u.data.fn = fn; } - return l1sap_up(trx, l1sap); + l1sap_up(trx, l1sap); + + /* return '1' to indicate l1sap_up has taken msgb ownership */ + return 1; } /* in all other cases, we need to allocate a new PH-DATA.ind @@ -848,7 +850,6 @@ static int handle_ph_data_ind(struct octphy_hdl *fl1, LOGP(DL1C, LOGL_ERROR, "Rx PH-DATA.ind for unknown L1 SAPI %s\n", get_value_string(octphy_l1sapi_names, sapi)); - msgb_free(l1p_msg); return ENOTSUP; } @@ -875,7 +876,6 @@ static int handle_ph_data_ind(struct octphy_hdl *fl1, sapi == cOCTVC1_GSM_SAPI_ENUM_TCHH) { /* TCH speech frame handling */ rc = l1if_tch_rx(trx, chan_nr, data_ind); - msgb_free(l1p_msg); return rc; } @@ -902,7 +902,10 @@ static int handle_ph_data_ind(struct octphy_hdl *fl1, l1sap->u.data.fn = fn; l1sap->u.data.rssi = rssi; - return l1sap_up(trx, l1sap); + l1sap_up(trx, l1sap); + + /* return '1' to indicate that l1sap_up has taken msgb ownership */ + return 1; } static int handle_ph_rach_ind(struct octphy_hdl *fl1, @@ -961,7 +964,10 @@ static int handle_ph_rach_ind(struct octphy_hdl *fl1, else l1sap->u.rach_ind.chan_nr = gsm_lchan2chan_nr(lchan); - return l1sap_up(trx, l1sap); + l1sap_up(trx, l1sap); + + /* return '1' to indicate l1sap_up has taken msgb ownership */ + return 1; } static int rx_gsm_trx_time_ind(struct msgb *msg) @@ -989,9 +995,11 @@ static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) llist_for_each_entry(wlc, &fl1h->wlc_list, list) { if (wlc->prim_id == msg_id && wlc->trans_id == trans_id) { llist_del(&wlc->list); - if (wlc->cb) + if (wlc->cb) { + /* call-back function must take msgb + * ownership. */ rc = wlc->cb(fl1h->priv, msg, wlc->cb_data); - else { + } else { rc = 0; msgb_free(msg); } @@ -1078,22 +1086,31 @@ static int rx_gsm_trx_rach_ind(struct msgb *msg) static int rx_octvc1_notif(struct msgb *msg, uint32_t msg_id) { const char *evt_name = get_value_string(octphy_eid_vals, msg_id); + int rc = 0; LOGP(DL1P, LOGL_DEBUG, "Rx NOTIF %s\n", evt_name); + /* called functions MUST NOT take ownership of the msgb, + * as it is free()d below - unless they return 1 */ switch (msg_id) { case cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID: - return rx_gsm_trx_time_ind(msg); + rc = rx_gsm_trx_time_ind(msg); + break; case cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EID: - return rx_gsm_clockmgr_status_ind(msg); + rc = rx_gsm_clockmgr_status_ind(msg); + break; case cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID: - return rx_gsm_trx_status_ind(msg); + rc = rx_gsm_trx_status_ind(msg); + break; case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID: - return rx_gsm_trx_lchan_data_ind(msg); + rc = rx_gsm_trx_lchan_data_ind(msg); + break; case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID: - return rx_gsm_trx_rts_ind(msg); + rc = rx_gsm_trx_rts_ind(msg); + break; case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID: - return rx_gsm_trx_rach_ind(msg); + rc = rx_gsm_trx_rach_ind(msg); + break; case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RAW_DATA_INDICATION_EID: LOGP(DL1P, LOGL_NOTICE, "Rx Unhandled event %s (%u)\n", evt_name, msg_id); @@ -1103,7 +1120,11 @@ static int rx_octvc1_notif(struct msgb *msg, uint32_t msg_id) evt_name, msg_id); } - return 0; + /* Special return value '1' means: do not free */ + if (rc != 1) + msgb_free(msg); + + return rc; } static int rx_octvc1_event_msg(struct msgb *msg) @@ -1117,6 +1138,7 @@ static int rx_octvc1_event_msg(struct msgb *msg) /* OCTSDKAN5001 Chapter 6.1 */ if (length < 12 || length > 1024) { LOGP(DL1C, LOGL_ERROR, "Rx EVENT length %u invalid\n", length); + msgb_free(msg); return -1; } @@ -1124,6 +1146,7 @@ static int rx_octvc1_event_msg(struct msgb *msg) if (msgb_l2len(msg) < length) { LOGP(DL1C, LOGL_ERROR, "Rx EVENT msgb_l2len(%u) < " "event_msg_length (%u)\n", msgb_l2len(msg), length); + msgb_free(msg); return -1; } @@ -1148,6 +1171,7 @@ static int rx_octvc1_ctrl_msg(struct msgb *msg) /* OCTSDKAN5001 Chapter 3.1 */ if (length < 24 || length > 1024) { LOGP(DL1C, LOGL_ERROR, "Rx CTRL length %u invalid\n", length); + msgb_free(msg); return -1; } @@ -1155,6 +1179,7 @@ static int rx_octvc1_ctrl_msg(struct msgb *msg) if (msgb_l2len(msg) < length) { LOGP(DL1C, LOGL_ERROR, "Rx CTRL msgb_l2len(%u) < " "ctrl_msg_length (%u)\n", msgb_l2len(msg), length); + msgb_free(msg); return -1; } @@ -1166,6 +1191,7 @@ static int rx_octvc1_ctrl_msg(struct msgb *msg) msg_name, octvc1_rc2string(return_code)); } + /* called functions must take ownership of msgb */ switch (msg_type) { case cOCTVC1_MSG_TYPE_RESPONSE: return rx_octvc1_resp(msg, msg_id, ntohl(mh->ulTransactionId)); @@ -1174,10 +1200,12 @@ static int rx_octvc1_ctrl_msg(struct msgb *msg) case cOCTVC1_MSG_TYPE_SUPERVISORY: LOGP(DL1C, LOGL_NOTICE, "Rx unhandled msg_type %s (%u)\n", msg_name, msg_type); + msgb_free(msg); break; default: LOGP(DL1P, LOGL_NOTICE, "Rx unknown msg_type %s (%u)\n", msg_name, msg_type); + msgb_free(msg); } return 0; @@ -1195,6 +1223,7 @@ static int rx_octvc1_data_f_msg(struct msgb *msg) cOCTVOCNET_PKT_DATA_LOGICAL_OBJ_PKT_PORT_EVENT_SESSION) { uint32_t sub_type = ntohl(datafh->ulSubType) & 0xF; if (sub_type == cOCTVOCNET_PKT_SUBTYPE_API_EVENT) { + /* called function must take msgb ownership */ return rx_octvc1_event_msg(msg); } else { LOGP(DL1C, LOGL_ERROR, "Unknown DATA_F " @@ -1205,6 +1234,7 @@ static int rx_octvc1_data_f_msg(struct msgb *msg) log_obj_port); } + msgb_free(msg); return 0; } @@ -1212,6 +1242,7 @@ static int rx_octvc1_data_f_msg(struct msgb *msg) static int rx_octphy_msg(struct msgb *msg) { tOCTVOCNET_PKT_CTL_HEADER *ctlh; + int rc = 0; /* we assume that the packets start right with the OCTPKT header * and that the ethernet hardware header has already been @@ -1228,6 +1259,7 @@ static int rx_octphy_msg(struct msgb *msg) LOGP(DL1C, LOGL_ERROR, "Received length (%u) > length " "as per packt header (%u)\n", msgb_length(msg), len); + msgb_free(msg); return -1; } @@ -1238,17 +1270,22 @@ static int rx_octphy_msg(struct msgb *msg) ctlh = (tOCTVOCNET_PKT_CTL_HEADER *) (msg->l1h + 4); /* FIXME: check src/dest fifo, socket ID */ msg->l2h = (uint8_t *) ctlh + sizeof(*ctlh); - return rx_octvc1_ctrl_msg(msg); + /* called function must take msgb ownership */ + rc = rx_octvc1_ctrl_msg(msg); + break; case cOCTVOCNET_PKT_FORMAT_F: msg->l2h = msg->l1h + 4; - return rx_octvc1_data_f_msg(msg); + /* called function must take msgb ownership */ + rc = rx_octvc1_data_f_msg(msg); + break; default: LOGP(DL1C, LOGL_ERROR, "Rx Unknown pkt_format 0x%x\n", format); + msgb_free(msg); break; } - return 0; + return rc; } /*********************************************************************** diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c index 68e6cf1cd..1e2bf44ed 100644 --- a/src/osmo-bts-octphy/l1_oml.c +++ b/src/osmo-bts-octphy/l1_oml.c @@ -357,6 +357,9 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void * uint8_t direction; uint8_t status; + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ar); OSMO_ASSERT(ar->TrxId.byTrxId == trx->nr); @@ -395,11 +398,12 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void * LOGP(DL1C, LOGL_ERROR, "%s Got activation confirmation with empty queue\n", gsm_lchan_name(lchan)); - return -1; + goto err; } sapi_queue_dispatch(lchan, ar->Header.ulReturnCode); +err: msgb_free(resp); return 0; @@ -454,11 +458,15 @@ static int set_ciph_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *d struct gsm_bts_trx_ts *ts; struct gsm_lchan *lchan; + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP_SWAP(pcr); if (pcr->Header.ulReturnCode != cOCTVC1_RC_OK) { LOGP(DL1C, LOGL_ERROR, "Error: Cipher Request Failed!\n\n"); LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + msgb_free(resp); exit(-1); } @@ -485,16 +493,16 @@ static int set_ciph_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *d LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state); } - msgb_free(resp); - if (llist_empty(&lchan->sapi_cmds)) { LOGP(DL1C, LOGL_ERROR, "%s Got ciphering conf with empty queue\n", gsm_lchan_name(lchan)); - return 0; + goto err; } sapi_queue_dispatch(lchan, pcr->Header.ulReturnCode); +err: + msgb_free(resp); return 0; } @@ -654,6 +662,9 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void struct sapi_cmd *cmd; uint8_t status; + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ldr); OSMO_ASSERT(ldr->TrxId.byTrxId == trx->nr); @@ -1063,6 +1074,9 @@ static int enable_events_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, vo tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *mser = (tOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP *) resp->l2h; + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + mOCTVC1_MAIN_MSG_API_SYSTEM_MODIFY_SESSION_EVT_RSP_SWAP(mser); LOGP(DL1C, LOGL_INFO, "Rx ENABLE-EVT-REC.resp\n"); @@ -1097,6 +1111,9 @@ static int trx_open_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *d tOCTVC1_GSM_MSG_TRX_OPEN_RSP *or = (tOCTVC1_GSM_MSG_TRX_OPEN_RSP *) resp->l2h; + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + mOCTVC1_GSM_MSG_TRX_OPEN_RSP_SWAP(or); OSMO_ASSERT(or->TrxId.byTrxId == trx->nr); @@ -1165,6 +1182,9 @@ static int trx_close_all_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *da tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *car = (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *) resp->l2h; + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP_SWAP(car); msgb_free(resp); @@ -1221,6 +1241,9 @@ static int pchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void * struct gsm_bts_trx_ts *ts; struct gsm_abis_mo *mo; + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar); ts_nr = ar->PchId.byTimeslotNb; @@ -1238,6 +1261,7 @@ static int pchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void * "PCHAN-ACT failed: %s\n\n", octvc1_rc2string(ar->Header.ulReturnCode)); LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + msgb_free(resp); exit(-1); } From 0c017618cd335f88712174154575ce9569a4634c Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Fri, 15 Jan 2016 18:24:03 +0100 Subject: [PATCH 6/9] OCTPHY: Block PHY indications until it is confirmed open When re-starting OsmoBTS after unclean shutdown, the PHY is already sending notifications (PH-DATA.ind, PH-TIME.ind, etc.) for the previous physical channel / timeslot configuration. At the point those messages are received, OsmoBTS might not even have A-bis OML up yet, and thus has no clue how to process such messages (and subsequently likely crashes). Let's block such primitives from passing further up the code until we have received the TRX-OPEN response. --- src/osmo-bts-octphy/l1_if.c | 8 ++++++++ src/osmo-bts-octphy/l1_if.h | 3 +++ src/osmo-bts-octphy/l1_oml.c | 1 + 3 files changed, 12 insertions(+) diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c index f259661fd..9c523fb55 100644 --- a/src/osmo-bts-octphy/l1_if.c +++ b/src/osmo-bts-octphy/l1_if.c @@ -1086,8 +1086,16 @@ static int rx_gsm_trx_rach_ind(struct msgb *msg) static int rx_octvc1_notif(struct msgb *msg, uint32_t msg_id) { const char *evt_name = get_value_string(octphy_eid_vals, msg_id); + struct octphy_hdl *fl1h = msg->dst; int rc = 0; + if (!fl1h->opened) { + LOGP(DL1P, LOGL_NOTICE, "Rx NOTIF %s: Ignoring as PHY TRX " + "hasn't been re-opened yet\n", evt_name); + msgb_free(msg); + return 0; + } + LOGP(DL1P, LOGL_DEBUG, "Rx NOTIF %s\n", evt_name); /* called functions MUST NOT take ownership of the msgb, diff --git a/src/osmo-bts-octphy/l1_if.h b/src/osmo-bts-octphy/l1_if.h index 54a5504c5..ce9568071 100644 --- a/src/osmo-bts-octphy/l1_if.h +++ b/src/osmo-bts-octphy/l1_if.h @@ -45,6 +45,9 @@ struct octphy_hdl { struct osmo_timer_list alive_timer; uint32_t alive_prim_cnt; + + /* were we already (re)opened after OsmoBTS start */ + int opened; }; static inline struct octphy_hdl *trx_octphy_hdl(struct gsm_bts_trx *trx) diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c index 1e2bf44ed..d682b841a 100644 --- a/src/osmo-bts-octphy/l1_oml.c +++ b/src/osmo-bts-octphy/l1_oml.c @@ -1141,6 +1141,7 @@ static int trx_open_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *d octphy_hw_get_rf_ant_rx_config(fl1h, 0, 1); octphy_hw_get_rf_ant_tx_config(fl1h, 0, 1); octphy_hw_get_clock_sync_info(fl1h); + fl1h->opened = 1; /* Temporary fix for enabling events after TRX Close + Reopen */ return l1if_enable_events(trx); From 7bd2251dcb15552d424b53e0e0e85a4d8150868c Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Fri, 15 Jan 2016 19:12:04 +0100 Subject: [PATCH 7/9] OCTPHY: Print NOTICE message if we receive supervisory frame --- src/osmo-bts-octphy/l1_if.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c index 9c523fb55..e1f8af0d2 100644 --- a/src/osmo-bts-octphy/l1_if.c +++ b/src/osmo-bts-octphy/l1_if.c @@ -1161,6 +1161,30 @@ static int rx_octvc1_event_msg(struct msgb *msg) return rx_octvc1_notif(msg, event_id); } +/* Receive a supervisory message from the PHY */ +static int rx_octvc1_supv(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t return_code = ntohl(mh->ulReturnCode); + tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *rej; + + switch (msg_id) { + case cOCTVC1_CTRL_MSG_MODULE_REJECT_SID: + rej = (tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *) mh; + mOCTVC1_CTRL_MSG_MODULE_REJECT_SPV_SWAP(rej); + LOGP(DL1C, LOGL_NOTICE, "Rx REJECT_SID (ExpectedTID=0x%08x, " + "RejectedCmdID=0x%08x)\n", rej->ulExpectedTransactionId, + rej->ulRejectedCmdId); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "Rx unhandled supervisory msg_id " + "%u: ReturnCode:%u\n", msg_id, return_code); + break; + } + + return 0; +} + static int rx_octvc1_ctrl_msg(struct msgb *msg) { tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; @@ -1203,9 +1227,10 @@ static int rx_octvc1_ctrl_msg(struct msgb *msg) switch (msg_type) { case cOCTVC1_MSG_TYPE_RESPONSE: return rx_octvc1_resp(msg, msg_id, ntohl(mh->ulTransactionId)); + case cOCTVC1_MSG_TYPE_SUPERVISORY: + return rx_octvc1_supv(msg, msg_id, ntohl(mh->ulTransactionId)); case cOCTVC1_MSG_TYPE_NOTIFICATION: case cOCTVC1_MSG_TYPE_COMMAND: - case cOCTVC1_MSG_TYPE_SUPERVISORY: LOGP(DL1C, LOGL_NOTICE, "Rx unhandled msg_type %s (%u)\n", msg_name, msg_type); msgb_free(msg); From 3e98f942e51ca5f39a09adcb3d0d68d93b1588b0 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sat, 16 Jan 2016 10:18:24 +0100 Subject: [PATCH 8/9] OCTPHY: Implement command re-transmission after message loss We re-use the 'wait_l1_conf' structure for implementing the unacknowledge command window towards the PHY. This means that thre will unconditionally be a 'wait_l1_conf' now, even for requests where the caller didn't provide a call-back. --- src/osmo-bts-octphy/l1_if.c | 229 ++++++++++++++++++++++++++++++------ src/osmo-bts-octphy/l1_if.h | 20 ++++ 2 files changed, 213 insertions(+), 36 deletions(-) diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c index e1f8af0d2..2c029177b 100644 --- a/src/osmo-bts-octphy/l1_if.c +++ b/src/osmo-bts-octphy/l1_if.c @@ -1,7 +1,7 @@ /* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ /* Copyright (c) 2014 Octasic Inc. All rights reserved. - * Copyright (c) 2015 Harald Welte + * Copyright (c) 2015-2016 Harald Welte * * based on a copy of osmo-bts-sysmo/l1_if.c, which is * Copyright (C) 2011-2014 by Harald Welte @@ -58,6 +58,13 @@ #define cPKTAPI_FIFO_ID_MSG 0xAAAA0001 +/* maximum window of unacknowledged commands */ +#define UNACK_CMD_WINDOW 8 +/* maximum number of re-transmissions of a command */ +#define MAX_RETRANS 3 +/* timeout until which we expect PHY to respond */ +#define CMD_TIMEOUT 5 + /* allocate a msgb for a Layer1 primitive */ struct msgb *l1p_msgb_alloc(void) { @@ -137,17 +144,28 @@ struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx, /* TODO: Unify with sysmobts? */ struct wait_l1_conf { + /* list of wait_l1_conf in the phy handle */ struct llist_head list; + /* expiration timer */ struct osmo_timer_list timer; + /* primtivie / command ID */ uint32_t prim_id; + /* transaction ID */ uint32_t trans_id; + /* copy of the msgb containing the command */ + struct msgb *cmd_msg; + /* call-back to call on response */ l1if_compl_cb *cb; + /* data to hand to call-back on response */ void *cb_data; + /* number of re-transmissions so far */ + uint32_t num_retrans; }; static void release_wlc(struct wait_l1_conf *wlc) { osmo_timer_del(&wlc->timer); + msgb_free(wlc->cmd_msg); talloc_free(wlc); } @@ -155,11 +173,64 @@ static void l1if_req_timeout(void *data) { struct wait_l1_conf *wlc = data; + /* FIXME: Implement re-transmission of command on timer expiration */ + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", get_value_string(octphy_cid_vals, wlc->prim_id)); exit(23); } +/* FIXME: this should be in libosmocore */ +static struct llist_head *llist_first(struct llist_head *head) +{ + if (llist_empty(head)) + return NULL; + return head->next; +} + +static void check_refill_window(struct octphy_hdl *fl1h, struct wait_l1_conf *recent) +{ + struct wait_l1_conf *wlc; + int space = UNACK_CMD_WINDOW - fl1h->wlc_list_len; + int i; + + for (i = 0; i < space; i++) { + /* get head of queue */ + struct llist_head *first = llist_first(&fl1h->wlc_postponed); + struct msgb *msg; + if (!first) + break; + wlc = llist_entry(first, struct wait_l1_conf, list); + + /* remove from head of postponed queue */ + llist_del(&wlc->list); + fl1h->wlc_postponed_len--; + + /* add to window */ + llist_add_tail(&wlc->list, &fl1h->wlc_list); + fl1h->wlc_list_len++; + + if (wlc != recent) { + LOGP(DL1C, LOGL_INFO, "Txing formerly postponed " + "command %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, wlc->prim_id), + wlc->trans_id); + } + msg = msgb_copy(wlc->cmd_msg, "Tx from wlc_postponed"); + /* queue for execution and response handling */ + if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "Tx Write queue full. dropping msg\n"); + llist_del(&wlc->list); + msgb_free(msg); + exit(24); + } + /* schedule a timer for CMD_TIMEOUT seconds. If PHY fails to + * respond, we terminate */ + osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0); + + } +} + /* send a request(command) to L1, scheduling a call-back to be executed * on receiving the response*/ int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, @@ -173,9 +244,9 @@ int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, uint32_t type_r_cmdid = ntohl(msg_hdr->ul_Type_R_CmdId); uint32_t cmd_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) & cOCTVC1_MSG_ID_BIT_MASK; - LOGP(DL1C, LOGL_DEBUG, "l1if_req_compl(msg_len=%u, cmd_id=%s) %s\n", + LOGP(DL1C, LOGL_DEBUG, "l1if_req_compl(msg_len=%u, cmd_id=%s, trans_id=%u)\n", msgb_length(msg), get_value_string(octphy_cid_vals, cmd_id), - osmo_hexdump(msg->data, msgb_length(msg))); + ntohl(msg_hdr->ulTransactionId)); /* push the two common headers in front */ octvocnet_push_ctl_hdr(msg, cOCTVC1_FIFO_ID_MGW_CONTROL, @@ -183,26 +254,28 @@ int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, octpkt_push_common_hdr(msg, cOCTVOCNET_PKT_FORMAT_CTRL, 0, cOCTPKT_HDR_CONTROL_PROTOCOL_TYPE_ENUM_OCTVOCNET); - if (cb) { - wlc = talloc_zero(fl1h, struct wait_l1_conf); - wlc->cb = cb; - wlc->cb_data = data; - wlc->prim_id = cmd_id; - wlc->trans_id = ntohl(msg_hdr->ulTransactionId); + wlc = talloc_zero(fl1h, struct wait_l1_conf); + wlc->cmd_msg = msg; + wlc->cb = cb; + wlc->cb_data = data; + wlc->prim_id = cmd_id; + wlc->trans_id = ntohl(msg_hdr->ulTransactionId); + wlc->timer.data = wlc; + wlc->timer.cb = l1if_req_timeout; - /* schedule a timer for 10 seconds. If PHY fails to - * respond, we terminate */ - wlc->timer.data = wlc; - wlc->timer.cb = l1if_req_timeout; - osmo_timer_schedule(&wlc->timer, 10, 0); + /* unconditionally add t to the tail of postponed commands */ + llist_add_tail(&wlc->list, &fl1h->wlc_postponed); + fl1h->wlc_postponed_len++; - llist_add_tail(&wlc->list, &fl1h->wlc_list); - } + /* check if the unacknowledged window has some space to transmit */ + check_refill_window(fl1h, wlc); - /* queue for execution and response handling */ - if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) != 0) { - LOGP(DL1C, LOGL_ERROR, "Tx Write queue full. dropping msg\n"); - msgb_free(msg); + /* if any messages are in the queue, it must be at least 'our' message, + * as we always enqueue from the tail */ + if (fl1h->wlc_postponed_len) { + fl1h->stats.wlc_postponed++; + LOGP(DL1C, LOGL_INFO, "Postponed command %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, cmd_id), wlc->trans_id); } return 0; @@ -622,6 +695,7 @@ int bts_model_init(struct gsm_bts *bts) return -ENOMEM; INIT_LLIST_HEAD(&fl1h->wlc_list); + INIT_LLIST_HEAD(&fl1h->wlc_postponed); fl1h->priv = bts->c0; bts->c0->role_bts.l1h = fl1h; /* FIXME: what is the nominal transmit power of the PHY/board? */ @@ -981,20 +1055,73 @@ static int rx_gsm_trx_time_ind(struct msgb *msg) return handle_mph_time_ind(fl1h, tind->TrxId.byTrxId, tind->ulFrameNumber); } +/* mark this message as RETRANSMIT of a previous msg */ +static void msg_set_retrans_flag(struct msgb *msg) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t type_r_cmdid = ntohl(mh->ul_Type_R_CmdId); + type_r_cmdid |= cOCTVC1_MSG_RETRANSMIT_FLAG; + mh->ul_Type_R_CmdId = htonl(type_r_cmdid); +} + +/* re-transmit all commands in the window that have a transaction ID lower than + * trans_id */ +static int retransmit_wlc_upto(struct octphy_hdl *fl1h, uint32_t trans_id) +{ + struct wait_l1_conf *wlc; + int count = 0; + + LOGP(DL1C, LOGL_INFO, "Retransmitting up to trans_id=%u\n", trans_id); + + /* trans_id represents the trans_id of the just-received response, we + * therefore need to re-send any commands with a lower trans_id */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (wlc->trans_id <= trans_id) { + struct msgb *msg; + if (wlc->num_retrans >= MAX_RETRANS) { + LOGP(DL1C, LOGL_ERROR, "Command %s: maximum " + "number of retransmissions reached\n", + get_value_string(octphy_cid_vals, + wlc->prim_id)); + exit(24); + } + wlc->num_retrans++; + msg = msgb_copy(wlc->cmd_msg, "PHY CMD Retrans"); + msg_set_retrans_flag(msg); + osmo_wqueue_enqueue(&fl1h->phy_wq, msg); + osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0); + count++; + LOGP(DL1C, LOGL_INFO, "Re-transmitting %s " + "(trans_id=%u, attempt %u)\n", + get_value_string(octphy_cid_vals, wlc->prim_id), + wlc->trans_id, wlc->num_retrans); + } + } + + return count; +} + /* Receive a response (to a prior command) from the PHY */ static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) { tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + struct llist_head *first; uint32_t return_code = ntohl(mh->ulReturnCode); struct octphy_hdl *fl1h = msg->dst; - struct wait_l1_conf *wlc; + struct wait_l1_conf *wlc = NULL; int rc; - /* check if anyone has registered a call-back for the given - * command_id and transaction, and call them back */ - llist_for_each_entry(wlc, &fl1h->wlc_list, list) { - if (wlc->prim_id == msg_id && wlc->trans_id == trans_id) { + LOGP(DL1C, LOGL_DEBUG, "rx_octvc1_resp(msg_id=%s, trans_id=%u)\n", + get_value_string(octphy_cid_vals, msg_id), trans_id); + + /* check if the response is for the oldest (first) entry in wlc_list */ + first = llist_first(&fl1h->wlc_list); + if (first) { + wlc = llist_entry(first, struct wait_l1_conf, list); + if (wlc->trans_id == trans_id) { + /* process the received response */ llist_del(&wlc->list); + fl1h->wlc_list_len--; if (wlc->cb) { /* call-back function must take msgb * ownership. */ @@ -1004,19 +1131,41 @@ static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) msgb_free(msg); } release_wlc(wlc); + /* check if there are postponed wlcs and re-fill the window */ + check_refill_window(fl1h, NULL); return rc; } } - /* ignore unhandled responses that went ok. The caller might just not - * be interested in them (as we are in case of DATA.req and - * EMPTY-FRAME.req */ + LOGP(DL1C, LOGL_NOTICE, "Sequence error: Rx response (cmd=%s, trans_id=%u) " + "for cmd != oldest entry in window (trans_id=%u)!!\n", + get_value_string(octphy_cid_vals, msg_id), trans_id, + wlc ? wlc->trans_id : 0); + + /* check if the response is for any of the other entries in wlc_list */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (wlc->prim_id == msg_id && wlc->trans_id == trans_id) { + /* it is assumed that all of the previous response + * message(s) have been lost, and we need to + * re-transmit older messages from the window */ + rc = retransmit_wlc_upto(fl1h, trans_id); + fl1h->stats.retrans_cmds_trans_id += rc; + /* do not process the received response, we rather wait + * for the in-order retransmissions to arrive */ + msgb_free(msg); + return 0; + } + } + + /* ignore unhandled responses that went ok, but let the user know about + * failing ones. */ if (return_code != cOCTVC1_RC_OK) { - LOGP(DL1C, LOGL_NOTICE, "Rx Unexpected response %s (trans_id=0x%x)\n", + LOGP(DL1C, LOGL_NOTICE, "Rx Unexpected response %s (trans_id=%u)\n", get_value_string(octphy_cid_vals, msg_id), trans_id); } msgb_free(msg); return 0; + } static int rx_gsm_clockmgr_status_ind(struct msgb *msg) @@ -1164,17 +1313,25 @@ static int rx_octvc1_event_msg(struct msgb *msg) /* Receive a supervisory message from the PHY */ static int rx_octvc1_supv(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) { + struct octphy_hdl *fl1h = msg->dst; tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; - uint32_t return_code = ntohl(mh->ulReturnCode); tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *rej; + uint32_t return_code = ntohl(mh->ulReturnCode); + uint32_t rejected_msg_id; + int rc; switch (msg_id) { case cOCTVC1_CTRL_MSG_MODULE_REJECT_SID: rej = (tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *) mh; mOCTVC1_CTRL_MSG_MODULE_REJECT_SPV_SWAP(rej); - LOGP(DL1C, LOGL_NOTICE, "Rx REJECT_SID (ExpectedTID=0x%08x, " - "RejectedCmdID=0x%08x)\n", rej->ulExpectedTransactionId, - rej->ulRejectedCmdId); + rejected_msg_id = (rej->ulRejectedCmdId >> cOCTVC1_MSG_ID_BIT_OFFSET) & + cOCTVC1_MSG_ID_BIT_MASK; + LOGP(DL1C, LOGL_NOTICE, "Rx REJECT_SID (TID=%u, " + "ExpectedTID=0x%08x, RejectedCmdID=%s)\n", + trans_id, rej->ulExpectedTransactionId, + get_value_string(octphy_cid_vals, rejected_msg_id)); + rc = retransmit_wlc_upto(fl1h, trans_id); + fl1h->stats.retrans_cmds_supv += rc; break; default: LOGP(DL1C, LOGL_NOTICE, "Rx unhandled supervisory msg_id " @@ -1289,9 +1446,9 @@ static int rx_octphy_msg(struct msgb *msg) & cOCTVOCNET_PKT_LENGTH_MASK; if (len > msgb_length(msg)) { - LOGP(DL1C, LOGL_ERROR, "Received length (%u) > length " - "as per packt header (%u)\n", msgb_length(msg), - len); + LOGP(DL1C, LOGL_ERROR, "Received length (%u) < length " + "as per packet header (%u): %s\n", msgb_length(msg), + len, osmo_hexdump(msgb_data(msg), msgb_length(msg))); msgb_free(msg); return -1; } diff --git a/src/osmo-bts-octphy/l1_if.h b/src/osmo-bts-octphy/l1_if.h index ce9568071..be2338b32 100644 --- a/src/osmo-bts-octphy/l1_if.h +++ b/src/osmo-bts-octphy/l1_if.h @@ -38,7 +38,27 @@ struct octphy_hdl { uint32_t tx_atten_db; } config; + /* This is a list of outstanding commands sent to the PHY, for which we + * currently still wait for a response. Represented by 'struct + * wait_l1_conf' in l1_if.c - Octasic calls this the 'Unacknowledged + * Command Window' */ struct llist_head wlc_list; + int wlc_list_len; + struct { + /* messages retransmitted due to discontinuity of transaction + * ID in responses from PHY */ + uint32_t retrans_cmds_trans_id; + /* messages retransmitted due to supervisory messages by PHY */ + uint32_t retrans_cmds_supv; + /* number of commands/wlcs that we ever had to postpone */ + uint32_t wlc_postponed; + } stats; + + /* This is a list of wait_la_conf that OsmoBTS wanted to transmit to + * the PHY, but which couldn't yet been sent as the unacknowledged + * command window was full. */ + struct llist_head wlc_postponed; + int wlc_postponed_len; /* private pointer, points back to TRX */ void *priv; From 866f9b979d0c8a36e76db8272693d59bfba9c0f5 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sat, 16 Jan 2016 13:11:35 +0100 Subject: [PATCH 9/9] OCTPHY: Obtain information from PHY and expose it in VTY This adds 'show trx 0 system-information' command to the VTY indicating various version information obtained from the DSP/PHY. --- src/osmo-bts-octphy/l1_if.c | 5 +- src/osmo-bts-octphy/l1_if.h | 12 +++++ src/osmo-bts-octphy/l1_oml.c | 93 +++++++++++++++++++++++++++++++- src/osmo-bts-octphy/octphy_vty.c | 30 ++++++++++- 4 files changed, 136 insertions(+), 4 deletions(-) diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c index 2c029177b..6d8932e43 100644 --- a/src/osmo-bts-octphy/l1_if.c +++ b/src/osmo-bts-octphy/l1_if.c @@ -1357,8 +1357,9 @@ static int rx_octvc1_ctrl_msg(struct msgb *msg) /* DO NOT YET SWAP HEADER HERE, as downstream functions want to * swap it */ - /* OCTSDKAN5001 Chapter 3.1 */ - if (length < 24 || length > 1024) { + /* FIXME: OCTSDKAN5001 Chapter 3.1 states max size is 1024, but we see + * larger messages in practise */ + if (length < 24 || length > 2048) { LOGP(DL1C, LOGL_ERROR, "Rx CTRL length %u invalid\n", length); msgb_free(msg); return -1; diff --git a/src/osmo-bts-octphy/l1_if.h b/src/osmo-bts-octphy/l1_if.h index be2338b32..427786592 100644 --- a/src/osmo-bts-octphy/l1_if.h +++ b/src/osmo-bts-octphy/l1_if.h @@ -38,6 +38,18 @@ struct octphy_hdl { uint32_t tx_atten_db; } config; + struct { + struct { + char *name; + char *description; + char *version; + } app; + struct { + char *platform; + char *version; + } system; + } info; + /* This is a list of outstanding commands sent to the PHY, for which we * currently still wait for a response. Represented by 'struct * wait_l1_conf' in l1_if.c - Octasic calls this the 'Unacknowledged diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c index d682b841a..0a2b9ef75 100644 --- a/src/osmo-bts-octphy/l1_oml.c +++ b/src/osmo-bts-octphy/l1_oml.c @@ -1,7 +1,7 @@ /* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ /* Copyright (c) 2014 Octasic Inc. All rights reserved. - * Copyright (c) 2015 Harald Welte + * Copyright (c) 2015-2016 Harald Welte * * based on a copy of osmo-bts-sysmo/l1_oml.c, which is * Copyright (C) 2011 by Harald Welte @@ -1105,6 +1105,94 @@ int l1if_enable_events(struct gsm_bts_trx *trx) return l1if_req_compl(fl1h, msg, enable_events_compl_cb, 0); } +#define talloc_replace(dst, ctx, src) \ + do { \ + if (dst) \ + talloc_free(dst); \ + dst = talloc_strdup(ctx, (const char *) src); \ + } while (0) + +static int app_info_sys_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + struct octphy_hdl *fl1h = resp->dst; + tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *aisr = + (tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP_SWAP(aisr); + + LOGP(DL1C, LOGL_INFO, "Rx APP-INFO-SYSTEM.resp (platform='%s', version='%s')\n", + aisr->szPlatform, aisr->szVersion); + + talloc_replace(fl1h->info.system.platform, fl1h, aisr->szPlatform); + talloc_replace(fl1h->info.system.version, fl1h, aisr->szVersion); + + msgb_free(resp); + + return 0; +} + +int l1if_check_app_sys_version(struct gsm_bts_trx *trx) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *ais; + + ais = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *) + msgb_put(msg, sizeof(*ais)); + l1if_fill_msg_hdr(&ais->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CID); + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD_SWAP(ais); + + LOGP(DL1C, LOGL_INFO, "Tx APP-INFO-SYSTEM.req\n"); + + return l1if_req_compl(fl1h, msg, app_info_sys_compl_cb, 0); +} + +static int app_info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + struct octphy_hdl *fl1h = resp->dst; + tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *air = + (tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP_SWAP(air); + + LOGP(DL1C, LOGL_INFO, "Rx APP-INFO.resp (name='%s', desc='%s', ver='%s')\n", + air->szName, air->szDescription, air->szVersion); + + talloc_replace(fl1h->info.app.name, fl1h, air->szName); + talloc_replace(fl1h->info.app.description, fl1h, air->szDescription); + talloc_replace(fl1h->info.app.version, fl1h, air->szVersion); + + msgb_free(resp); + + return 0; +} + + +int l1if_check_app_version(struct gsm_bts_trx *trx) +{ + struct octphy_hdl *fl1h = trx_octphy_hdl(trx); + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *ai; + + ai = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *) msgb_put(msg, sizeof(*ai)); + l1if_fill_msg_hdr(&ai->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_MAIN_MSG_APPLICATION_INFO_CID); + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD_SWAP(ai); + + LOGP(DL1C, LOGL_INFO, "Tx APP-INFO.req\n"); + + return l1if_req_compl(fl1h, msg, app_info_compl_cb, 0); +} + /* call-back once the TRX_OPEN_CID response arrives */ static int trx_open_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { @@ -1227,6 +1315,9 @@ static int trx_init(struct gsm_bts_trx *trx) l1if_trx_close_all(trx->bts); + l1if_check_app_version(trx); + l1if_check_app_sys_version(trx); + return l1if_trx_open(trx); } diff --git a/src/osmo-bts-octphy/octphy_vty.c b/src/osmo-bts-octphy/octphy_vty.c index 1b3a3652a..55c9099c9 100644 --- a/src/osmo-bts-octphy/octphy_vty.c +++ b/src/osmo-bts-octphy/octphy_vty.c @@ -1,6 +1,6 @@ /* VTY interface for osmo-bts OCTPHY integration */ -/* (C) 2015 by Harald Welte +/* (C) 2015-2016 by Harald Welte * * All Rights Reserved * @@ -179,6 +179,33 @@ void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) VTY_NEWLINE); } +DEFUN(show_sys_info, show_sys_info_cmd, + "show trx <0-255> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct octphy_hdl *fl1h; + int i; + + if (!trx) { + vty_out(vty, "Cannot find TRX number %u%s", + trx_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = trx_octphy_hdl(trx); + + vty_out(vty, "System Platform: '%s', Version: '%s'%s", + fl1h->info.system.platform, fl1h->info.system.version, + VTY_NEWLINE); + vty_out(vty, "Application Name: '%s', Description: '%s', Version: '%s'%s", + fl1h->info.app.name, fl1h->info.app.description, + fl1h->info.app.version, VTY_NEWLINE); + + return CMD_SUCCESS; +} + + int bts_model_vty_init(struct gsm_bts *bts) { vty_bts = bts; @@ -192,6 +219,7 @@ int bts_model_vty_init(struct gsm_bts *bts) install_element_ve(&get_rf_port_stats_cmd); install_element_ve(&get_clk_sync_stats_cmd); + install_element_ve(&show_sys_info_cmd); return 0; }