1843 lines
54 KiB
C
1843 lines
54 KiB
C
/* 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 <osmocom/core/fsm.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 <osmo-bts/bts.h>
|
|
#include <osmo-bts/nm_common_fsm.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)
|
|
{
|
|
if (on) {
|
|
/* signal availability */
|
|
osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL);
|
|
osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL);
|
|
} else {
|
|
osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL);
|
|
osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL);
|
|
}
|
|
|
|
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_OSMO_DYN:
|
|
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_OSMO_DYN);
|
|
|
|
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;
|
|
}
|
|
|
|
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,
|
|
msgb_l2(msg), 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 */
|
|
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;
|
|
|
|
/* order alphabetically */
|
|
#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)
|
|
osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH);
|
|
#endif
|
|
osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS);
|
|
osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS);
|
|
osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1);
|
|
osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1);
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
#define LOG_FMT_MEAS "Meas: RSSI %d dBm, Burst Timing %d Quarter of bits: %d, BER Error Count %d, BER Toatal Bit count %d in last decoded frame"
|
|
#define LOG_PARAM_MEAS(meas_param) (meas_param)->sRSSIDbm, (meas_param)->sBurstTiming, (meas_param)->sBurstTiming4x, (meas_param)->usBERCnt, (meas_param)->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;
|
|
}
|
|
|
|
/* octv1_gsm_api.h does not have an end marker for CTVC1_GSM_SAPI_ENUM */
|
|
#define _OCTVC1_GSM_SAPI_ENUM_LENGTH (cOCTVC1_GSM_SAPI_ENUM_PRACH + 1)
|
|
|
|
static const enum l1sap_common_sapi common_sapi_by_oct_sapi[] = {
|
|
[cOCTVC1_GSM_SAPI_ENUM_IDLE] = L1SAP_COMMON_SAPI_IDLE,
|
|
[cOCTVC1_GSM_SAPI_ENUM_FCCH] = L1SAP_COMMON_SAPI_FCCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_SCH] = L1SAP_COMMON_SAPI_SCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_SACCH] = L1SAP_COMMON_SAPI_SACCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_SDCCH] = L1SAP_COMMON_SAPI_SDCCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_BCCH] = L1SAP_COMMON_SAPI_BCCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH] = L1SAP_COMMON_SAPI_PCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_CBCH] = L1SAP_COMMON_SAPI_CBCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_RACH] = L1SAP_COMMON_SAPI_RACH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_TCHF] = L1SAP_COMMON_SAPI_TCH_F,
|
|
[cOCTVC1_GSM_SAPI_ENUM_FACCHF] = L1SAP_COMMON_SAPI_FACCH_F,
|
|
[cOCTVC1_GSM_SAPI_ENUM_TCHH] = L1SAP_COMMON_SAPI_TCH_H,
|
|
[cOCTVC1_GSM_SAPI_ENUM_FACCHH] = L1SAP_COMMON_SAPI_FACCH_H,
|
|
[cOCTVC1_GSM_SAPI_ENUM_NCH] = L1SAP_COMMON_SAPI_NCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_PDTCH] = L1SAP_COMMON_SAPI_PDTCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_PACCH] = L1SAP_COMMON_SAPI_PACCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_PBCCH] = L1SAP_COMMON_SAPI_PBCCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_PAGCH] = L1SAP_COMMON_SAPI_PAGCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_PPCH] = L1SAP_COMMON_SAPI_PPCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_PNCH] = L1SAP_COMMON_SAPI_PNCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_PTCCH] = L1SAP_COMMON_SAPI_PTCCH,
|
|
[cOCTVC1_GSM_SAPI_ENUM_PRACH] = L1SAP_COMMON_SAPI_PRACH,
|
|
};
|
|
|
|
static enum l1sap_common_sapi get_common_sapi(tOCT_UINT8 sapi)
|
|
{
|
|
if (sapi >= _OCTVC1_GSM_SAPI_ENUM_LENGTH)
|
|
return L1SAP_COMMON_SAPI_UNKNOWN;
|
|
return common_sapi_by_oct_sapi[sapi];
|
|
}
|
|
|
|
static void set_log_ctx_sapi(tOCT_UINT8 sapi)
|
|
{
|
|
l1sap_log_ctx_sapi = get_common_sapi(sapi);
|
|
log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi);
|
|
}
|
|
|
|
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;
|
|
|
|
set_log_ctx_sapi(evt->LchId.bySAPI);
|
|
|
|
/* Retrieve 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;
|
|
|
|
set_log_ctx_sapi(data_ind->LchId.bySAPI);
|
|
|
|
/* 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 conversion 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;
|
|
|
|
set_log_ctx_sapi(ra_ind->LchId.bySAPI);
|
|
|
|
LOGPFN(DL1C, LOGL_DEBUG, ra_ind->ulFrameNumber, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n",
|
|
LOG_PARAM_MEAS(&ra_ind->MeasurementInfo));
|
|
|
|
if (ra_ind->ulMsgLength != 1) {
|
|
LOGPFN(DL1C, LOGL_ERROR, ra_ind->ulFrameNumber,
|
|
"Rx PH-RACH.ind has length %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 = RSL_CHAN_RACH;
|
|
} 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);
|
|
if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) < 0) {
|
|
LOGP(DL1C, LOGL_ERROR, "Queue full on wlc retransmit\n");
|
|
msgb_free(msg);
|
|
return 0;
|
|
}
|
|
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 control (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;
|
|
osmo_fd_setup(&fl1h->phy_wq.bfd, sfd, OSMO_FD_READ, osmo_wqueue_bfd_cb, fl1h, 0);
|
|
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;
|
|
}
|