aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bts-octphy/l1_if.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bts-octphy/l1_if.c')
-rw-r--r--src/osmo-bts-octphy/l1_if.c1806
1 files changed, 1806 insertions, 0 deletions
diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c
new file mode 100644
index 00000000..f149c048
--- /dev/null
+++ b/src/osmo-bts-octphy/l1_if.c
@@ -0,0 +1,1806 @@
+/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */
+
+/* Copyright (c) 2014 Octasic Inc. All rights reserved.
+ * Copyright (c) 2015-2016 Harald Welte <laforge@gnumonks.org>
+ *
+ * based on a copy of osmo-bts-sysmo/l1_if.c, which is
+ * Copyright (C) 2011-2014 by Harald Welte <laforge@gnumonks.org>
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/socket.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/handover.h>
+
+#include "l1_if.h"
+#include "l1_oml.h"
+#include "l1_utils.h"
+
+#include "octpkt.h"
+#include <octphy/octvc1/main/octvc1_main_version.h>
+
+/* NOTE: The octphy GPRS frame number handling changed with
+ * OCTSDR-2G-02.07.00-B1314-BETA. From that version on, each ph_data_ind must
+ * subtract 3 from the frame number before passing the frame to the PCU */
+#define cOCTVC1_MAIN_VERSION_ID_FN_PARADIGM_CHG 0x41c0522
+
+#include <octphy/octpkt/octpkt_hdr.h>
+#define OCTVC1_RC2STRING_DECLARE
+#include <octphy/octvc1/octvc1_rc2string.h>
+#define OCTVC1_ID2STRING_DECLARE
+#include <octphy/octvc1/octvc1_id2string.h>
+#include <octphy/octvc1/gsm/octvc1_gsm_evt_swap.h>
+#define OCTVC1_OPT_DECLARE_DEFAULTS
+#include <octphy/octvc1/gsm/octvc1_gsm_default.h>
+#include <octphy/octvc1/main/octvc1_main_default.h>
+
+#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)
+{
+ 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 == 0)
+ return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_E_900;
+ else 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_bts_trx *trx_by_l1h(struct octphy_hdl *fl1h, unsigned int trx_id)
+{
+ struct phy_instance *pinst;
+
+ pinst = phy_instance_by_num(fl1h->phy_link, trx_id);
+ if (!pinst)
+ return NULL;
+
+ return pinst->trx;
+}
+
+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 {
+ /* 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);
+}
+
+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,
+ 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, trans_id=%u)\n",
+ msgb_length(msg), octvc1_id2string(cmd_id),
+ ntohl(msg_hdr->ulTransactionId));
+
+ /* 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);
+
+ 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;
+
+ /* unconditionally add t to the tail of postponed commands */
+ llist_add_tail(&wlc->list, &fl1h->wlc_postponed);
+ fl1h->wlc_postponed_len++;
+
+ /* check if the unacknowledged window has some space to transmit */
+ check_refill_window(fl1h, wlc);
+
+ /* 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;
+}
+
+/* For OctPHY, this only about sending state changes to BSC */
+int l1if_activate_rf(struct gsm_bts_trx *trx, int on)
+{
+ int i;
+ 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 enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return GSM_PCHAN_PDCH;
+ return GSM_PCHAN_TCH_F;
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is;
+ default:
+ return ts->pchan;
+ }
+}
+
+static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
+ tOCTVC1_GSM_SAPI_ENUM sapi, uint8_t subCh,
+ uint8_t u8Tn, uint32_t u32Fn)
+{
+ uint8_t cbits = 0;
+ enum gsm_phys_chan_config pchan = pick_pchan(ts);
+
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
+ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH);
+
+ switch (sapi) {
+ case cOCTVC1_GSM_SAPI_ENUM_BCCH:
+ cbits = 0x10;
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_CBCH:
+ cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */
+ 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:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", pchan);
+ return 0;
+ }
+ break;
+ case cOCTVC1_GSM_SAPI_ENUM_SDCCH:
+ switch (pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", pchan);
+ return 0;
+ }
+ break;
+ case 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 phy_instance *pinst = trx_phy_instance(trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct msgb *l1msg = NULL;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, sapi = 0;
+ uint8_t chan_nr, link_id;
+ int len;
+ int rc;
+
+ if (!msg) {
+ LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "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) && !L1SAP_IS_CHAN_PDCH(chan_nr))
+ subCh = l1sap_chan2ss(chan_nr);
+ } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) {
+ if (ts_is_pdch(&trx->ts[u8Tn])) {
+ if (L1SAP_IS_PTCCH(u32Fn)) {
+ sapi = 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_CBCH(chan_nr)) {
+ sapi = cOCTVC1_GSM_SAPI_ENUM_CBCH;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ sapi = cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH;
+ } else {
+ LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d chan_nr %d link_id %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id);
+ rc = -EINVAL;
+ goto done;
+ }
+
+ if (len) {
+ /* create new PHY primitive in l1msg, copying payload */
+
+ l1msg = l1p_msgb_alloc();
+ if (!l1msg) {
+ LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-DATA.req msg alloc failed\n");
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ 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 = pinst->u.octphy.trx_id;
+ 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, Don't send Empty frame to PHY */
+ rc = 0;
+ goto done;
+ }
+
+ 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 phy_instance *pinst = trx_phy_instance(trx);
+ struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl;
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, sapi;
+ 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)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = cOCTVC1_GSM_SAPI_ENUM_TCHH;
+ } else {
+ subCh = 0xf1;
+ sapi = cOCTVC1_GSM_SAPI_ENUM_TCHF;
+ }
+
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ /* create new message and fill data */
+ if (msg) {
+ nmsg = l1p_msgb_alloc();
+ if (!nmsg) {
+ LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-TCH.req msg alloc failed\n");
+ return -ENOMEM;
+ }
+
+ 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 = pinst->u.octphy.trx_id;
+ 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 {
+ /* No data available, Don't send Empty frame to PHY */
+ return 0;
+ }
+
+ 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 chan_nr;
+ struct gsm_lchan *lchan;
+ int rc = 0;
+
+ switch (l1sap->u.info.type) {
+ case PRIM_INFO_ACT_CIPH:
+ chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ if (l1sap->u.info.u.ciph_req.uplink) {
+ l1if_set_ciphering(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;
+ lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ if (l1sap->u.info.type == PRIM_INFO_ACTIVATE)
+ l1if_rsl_chan_act(lchan);
+ else if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
+#pragma message ("Mode Modify is currently not supported for Octasic PHY (OS#3015)")
+ /* 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. 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);
+ 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;
+ }
+
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int trx_close_all_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data)
+{
+ 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);
+
+ /* we now know that the PHY link is connected */
+ phy_link_state_set(fl1->phy_link, PHY_LINK_CONNECTED);
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int phy_link_trx_close_all(struct phy_link *plink)
+{
+ struct octphy_hdl *fl1h = plink->u.octphy.hdl;
+ 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);
+}
+
+int bts_model_phy_link_open(struct phy_link *plink)
+{
+ if (plink->u.octphy.hdl)
+ l1if_close(plink->u.octphy.hdl);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTING);
+
+ plink->u.octphy.hdl = l1if_open(plink);
+ if (!plink->u.octphy.hdl) {
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ return -1;
+ }
+
+ /* do we need to iterate over the list of instances and do some
+ * instance-specific initialization? */
+
+ /* close all TRXs that might still exist in this link from
+ * previous execitions / sessions */
+ phy_link_trx_close_all(plink);
+
+ /* in the call-back to the above we will set the link state to
+ * connected */
+
+ return 0;
+}
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ LOGP(DL1C, LOGL_NOTICE, "model_init()\n");
+
+ bts->variant = BTS_OSMO_OCTPHY;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+ /* FIXME: what is the nominal transmit power of the PHY/board? */
+ bts->c0->nominal_power = 15;
+
+ gsm_bts_set_feature(bts, BTS_FEAT_GPRS);
+ gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
+#if defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4) && defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8)
+ gsm_bts_set_feature(bts, BTS_FEAT_CBCH);
+#endif
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
+ gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_trx_init(struct gsm_bts_trx *trx)
+{
+ return 0;
+}
+
+/***********************************************************************
+ * handling of messages coming up from PHY
+ ***********************************************************************/
+
+/* When the measurement indication is received from the phy, the phy will
+ * automatically stamp it with the frame number that matches the frame
+ * number of the SACCH channel that marks the end of the measurement
+ * period. (e.g. fn%104=90, on a TCH/H, TS0). However, the upper layers
+ * expect the frame number to be aligned to the next SACCH frame after,
+ * after the end of the measurement period that has just passed. (e.g.
+ * (fn%104=10, on a TCH/H, TS0). The following function remaps the frame
+ * number in order to match the higher layers expectations.
+ * See also: 3GPP TS 05.02 Clause 7 Table 1 of 9 Mapping of logical channels
+ * onto physical channels (see subclauses 6.3, 6.4, 6.5) */
+static uint32_t translate_tch_meas_rep_fn104_reverse(uint32_t fn)
+{
+ uint8_t new_fn_mod;
+ uint8_t fn_mod;
+
+ fn_mod = fn % 104;
+
+ switch (fn_mod) {
+ case 103:
+ new_fn_mod = 25;
+ break;
+ case 12:
+ new_fn_mod = 38;
+ break;
+ case 25:
+ new_fn_mod = 51;
+ break;
+ case 38:
+ new_fn_mod = 64;
+ break;
+ case 51:
+ new_fn_mod = 77;
+ break;
+ case 64:
+ new_fn_mod = 90;
+ break;
+ case 77:
+ new_fn_mod = 103;
+ break;
+ case 90:
+ new_fn_mod = 12;
+ break;
+ default:
+ /* No translation for frame numbers
+ * fall out of the raster */
+ new_fn_mod = fn_mod;
+ }
+
+ return (fn - fn_mod) + new_fn_mod;
+}
+
+static unsigned int oct_meas2ber10k(const tOCTVC1_GSM_MEASUREMENT_INFO *m)
+{
+ if (m->usBERTotalBitCnt != 0) {
+ return (unsigned int)((m->usBERCnt * BER_10K) / m->usBERTotalBitCnt);
+ } else {
+ return 0;
+ }
+}
+
+static int oct_meas2rssi_dBm(const tOCTVC1_GSM_MEASUREMENT_INFO *m)
+{
+ /* rssi is in q8 format */
+ return (m->sRSSIDbm >> 8);
+}
+
+static void process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ uint32_t fn, uint32_t data_len,
+ 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;
+
+ /* Update Timing offset for valid radio block */
+ if (data_len != 0) {
+ /* burst timing in 1x */
+ l1sap.u.info.u.meas_ind.ta_offs_256bits = m->sBurstTiming4x*64;
+ } else {
+ /* FIXME, In current implementation, OCTPHY would send DATA_IND
+ * for all radio blocks (valid or invalid) But timing offset
+ * is only correct for valid block. so we need different
+ * counter to accumulate Timing offset.. even we add zero for
+ * invalid block.. timing offset average calucation would not
+ * correct. */
+ l1sap.u.info.u.meas_ind.ta_offs_256bits = 0;
+ }
+
+ l1sap.u.info.u.meas_ind.ber10k = oct_meas2ber10k(m);
+
+ /* rssi is in q8 format */
+ l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) oct_meas2rssi_dBm(m);
+
+ /* copy logical frame number to MEAS IND data structure */
+ l1sap.u.info.u.meas_ind.fn = translate_tch_meas_rep_fn104_reverse(fn);
+
+ /* 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)
+{
+ LOGP(DMEAS, ll,
+ "Meas: RSSI %d dBm, Burst Timing %d Quarter of bits :%d, "
+ "BER Error Count %d , BER Toatal Bit count %d in last decoded frame\n",
+ m->sRSSIDbm, m->sBurstTiming, m->sBurstTiming4x, 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 = trx_by_l1h(fl1, trx_id);
+ 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 != trx->bts->c0)
+ 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;
+
+ l1sap_up(trx, &l1sap);
+
+ return 0;
+}
+
+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 = trx_by_l1h(fl1, evt->TrxId.byTrxId);
+ 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);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n",
+ 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], sapi, sc, ts_num, fn);
+ if (chan_nr) {
+ if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH)
+ link_id = LID_SACCH;
+ else
+ link_id = LID_DEDIC;
+
+ 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;
+ }
+
+ 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
+ * 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) {
+ LOGPGT(DL1P, LOGL_ERROR, &g_time,
+ "Sending Empty Frame Request Failed! (%s)\n",
+ octvc1_rc2string(rc));
+ }
+ break;
+#endif
+ default:
+ LOGPGT(DL1P, LOGL_ERROR, &g_time, "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 = trx_by_l1h(fl1, data_ind->TrxId.byTrxId);
+ uint8_t chan_nr, link_id;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t fn;
+ uint8_t *data;
+ uint16_t len;
+ int16_t snr;
+ 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], sapi, sc, ts_num, fn);
+ if (!chan_nr) {
+ LOGPFN(DL1C, LOGL_ERROR, fn, "Rx PH-DATA.ind for unknown L1 SAPI %s\n",
+ get_value_string(octphy_l1sapi_names, sapi));
+ return ENOTSUP;
+ }
+
+ if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH)
+ link_id = LID_SACCH;
+ else
+ link_id = LID_DEDIC;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+
+ /* uplink measurement */
+ process_meas_res(trx, chan_nr, fn, data_ind->Data.ulDataLength,
+ &data_ind->MeasurementInfo);
+
+ DEBUGPFN(DL1C, fn, "Rx PH-DATA.ind %s: %s data_len:%d \n",
+ get_value_string(octphy_l1sapi_names, sapi),
+ osmo_hexdump(data_ind->Data.abyDataContent, data_ind->Data.ulDataLength),
+ data_ind->Data.ulDataLength);
+
+ /* 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);
+ return rc;
+ }
+
+ /* 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;
+
+#if (cOCTVC1_MAIN_VERSION_ID >= cOCTVC1_MAIN_VERSION_ID_FN_PARADIGM_CHG)
+ if (sapi == cOCTVC1_GSM_SAPI_ENUM_PDTCH) {
+ /* FIXME::PCU is expecting encode frame number*/
+ l1sap->u.data.fn = fn - 3;
+ } else
+ l1sap->u.data.fn = fn;
+#else
+ l1sap->u.data.fn = fn;
+#endif
+
+ l1sap->u.data.rssi = oct_meas2rssi_dBm(&data_ind->MeasurementInfo);
+ l1sap->u.data.ber10k = oct_meas2ber10k(&data_ind->MeasurementInfo);
+
+ /* burst timing in 1x but PCU is expecting 4X */
+ l1sap->u.data.ta_offs_256bits = data_ind->MeasurementInfo.sBurstTiming4x*64;
+ snr = data_ind->MeasurementInfo.sSNRDb;
+ /* FIXME: better converion formulae for SnR -> C / I?
+ l1sap->u.data.lqual_cb = (snr ? snr : (snr - 65536)) * 10 / 256;
+ LOGP(DL1C, LOGL_ERROR, "SnR: raw %d, computed %d\n", snr, l1sap->u.data.lqual_cb);
+ */
+ l1sap->u.data.lqual_cb = (snr ? snr : (snr - 65536)) * 100;
+ l1sap->u.data.pdch_presence_info = PRES_INFO_BOTH; /* FIXME: consider EDGE support */
+
+ 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,
+ tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *ra_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = trx_by_l1h(fl1, ra_ind->TrxId.byTrxId);
+ struct osmo_phsap_prim *l1sap;
+ int rc;
+ struct ph_rach_ind_param rach_ind_param;
+
+ dump_meas_res(LOGL_DEBUG, &ra_ind->MeasurementInfo);
+
+ if (ra_ind->ulMsgLength != 1) {
+ LOGPFN(DL1C, LOGL_ERROR, ra_ind->ulFrameNumber,
+ "Rx PH-RACH.ind has lenghth %d > 1\n", ra_ind->ulMsgLength);
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */
+ rach_ind_param = (struct ph_rach_ind_param) {
+ /* .chan_nr set below */
+ .ra = ra_ind->abyMsg[0],
+ /* .acc_delay set below */
+ .fn = ra_ind->ulFrameNumber,
+ .is_11bit = 0,
+ /* .burst_type remains unset */
+ .rssi = oct_meas2rssi_dBm(&ra_ind->MeasurementInfo),
+ .ber10k = oct_meas2ber10k(&ra_ind->MeasurementInfo),
+ .acc_delay_256bits = ra_ind->MeasurementInfo.sBurstTiming4x * 64,
+ };
+
+ if (ra_ind->LchId.bySubChannelNb == cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL &&
+ ra_ind->LchId.bySAPI == cOCTVC1_GSM_SAPI_ENUM_RACH) {
+ rach_ind_param.chan_nr = 0x88;
+ } else {
+ struct gsm_lchan *lchan = get_lchan_by_lchid(trx, &ra_ind->LchId);
+ rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan);
+ }
+
+ /* check for under/overflow / sign */
+ if (ra_ind->MeasurementInfo.sBurstTiming < 0)
+ rach_ind_param.acc_delay = 0;
+ else
+ rach_ind_param.acc_delay = ra_ind->MeasurementInfo.sBurstTiming;
+
+ /* msgb_trim() invalidates ra_ind, make that abundantly clear: */
+ ra_ind = NULL;
+ rc = msgb_trim(l1p_msg, sizeof(*l1sap));
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive\n");
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION,
+ l1p_msg);
+ l1sap->u.rach_ind = rach_ind_param;
+
+ 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)
+{
+ 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);
+}
+
+/* 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 = NULL;
+ int rc;
+
+ LOGP(DL1C, LOGL_DEBUG, "rx_octvc1_resp(msg_id=%s, trans_id=%u)\n",
+ octvc1_rc2string(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. */
+ rc = wlc->cb(fl1h, msg, wlc->cb_data);
+ } else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ /* check if there are postponed wlcs and re-fill the window */
+ check_refill_window(fl1h, NULL);
+ return rc;
+ }
+ }
+
+ 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=%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)
+{
+ 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);
+ 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,
+ * as it is free()d below - unless they return 1 */
+ switch (msg_id) {
+ case cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID:
+ rc = rx_gsm_trx_time_ind(msg);
+ break;
+ case cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EID:
+ rc = rx_gsm_clockmgr_status_ind(msg);
+ break;
+ case cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID:
+ rc = rx_gsm_trx_status_ind(msg);
+ break;
+ case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID:
+ rc = rx_gsm_trx_lchan_data_ind(msg);
+ break;
+ case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID:
+ rc = rx_gsm_trx_rts_ind(msg);
+ break;
+ case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID:
+ 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);
+ break;
+ default:
+ LOGP(DL1P, LOGL_NOTICE, "Rx Unknown event %s (%u)\n",
+ evt_name, msg_id);
+ }
+
+ /* 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)
+{
+ 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);
+ msgb_free(msg);
+ 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);
+ msgb_free(msg);
+ return -1;
+ }
+
+ 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)
+{
+ struct octphy_hdl *fl1h = msg->dst;
+ tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h;
+ 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);
+ 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 "
+ "%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;
+ 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 */
+
+ /* 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;
+ }
+
+ /* 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);
+ msgb_free(msg);
+ 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));
+ }
+
+ /* 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));
+ 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:
+ 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;
+}
+
+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) {
+ /* called function must take msgb ownership */
+ 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);
+ }
+
+ msgb_free(msg);
+ return 0;
+}
+
+/* main receive routine for messages coming up from OCTPHY */
+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
+ * 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 packet header (%u): %s\n", msgb_length(msg),
+ len, osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+ msgb_free(msg);
+ 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);
+ /* called function must take msgb ownership */
+ rc = rx_octvc1_ctrl_msg(msg);
+ break;
+ case cOCTVOCNET_PKT_FORMAT_F:
+ msg->l2h = msg->l1h + 4;
+ /* 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 rc;
+}
+
+void bts_model_phy_link_set_defaults(struct phy_link *plink)
+{
+ /* configure some reasonable defaults, to be overridden by VTY */
+ plink->u.octphy.rf_port_index = 0;
+ plink->u.octphy.rx_gain_db = 70;
+ plink->u.octphy.tx_atten_db = 0;
+ plink->u.octphy.over_sample_16x = true;
+}
+
+void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
+{
+ pinst->u.octphy.trx_id = pinst->num;
+}
+
+/***********************************************************************
+ * 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;
+}
+
+struct octphy_hdl *l1if_open(struct phy_link *plink)
+{
+ struct octphy_hdl *fl1h;
+ struct ifreq ifr;
+ int sfd, rc;
+ char *phy_dev = plink->u.octphy.netdev_name;
+
+ fl1h = talloc_zero(plink, struct octphy_hdl);
+ if (!fl1h)
+ return NULL;
+
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+ INIT_LLIST_HEAD(&fl1h->wlc_postponed);
+ fl1h->phy_link = plink;
+
+ if (!phy_dev) {
+ LOGP(DL1C, LOGL_ERROR, "You have to specify a octphy net-device\n");
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ 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));
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ /* 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);
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ fl1h->session_id = rand();
+
+ /* set fl1h->phy_addr, which we use as sendto() destination */
+ 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 = ETH_ALEN;
+ /* plink->phy_addr.sll_addr is filled by bts_model_vty code */
+ memcpy(fl1h->phy_addr.sll_addr, plink->u.octphy.phy_addr.sll_addr,
+ ETH_ALEN);
+
+ /* 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);
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ return fl1h;
+}
+
+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;
+}