/* Interface handler for NuRAN Wireless Litecell 1.5 L1 */

/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
 * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
 *
 * Based on sysmoBTS:
 *     (C) 2011-2014 by Harald Welte <laforge@gnumonks.org>
 *     (C) 2014 by Holger Hans Peter Freyther
 *
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/lapdm.h>

#include <osmo-bts/logging.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/oml.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/phy_link.h>
#include <osmo-bts/paging.h>
#include <osmo-bts/measurement.h>
#include <osmo-bts/pcu_if.h>
#include <osmo-bts/handover.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/l1sap.h>
#include <osmo-bts/msg_utils.h>
#include <osmo-bts/dtx_dl_amr_fsm.h>
#include <osmo-bts/nm_common_fsm.h>

#include <nrw/litecell15/litecell15.h>
#include <nrw/litecell15/gsml1prim.h>
#include <nrw/litecell15/gsml1const.h>
#include <nrw/litecell15/gsml1types.h>

#include "lc15bts.h"
#include "l1_if.h"
#include "l1_transp.h"
#include "hw_misc.h"
#include "misc/lc15bts_par.h"
#include "misc/lc15bts_bid.h"
#include "utils.h"

extern unsigned int dsp_trace;

struct wait_l1_conf {
	struct llist_head list;		/* internal linked list */
	struct osmo_timer_list timer;	/* timer for L1 timeout */
	unsigned int conf_prim_id;	/* primitive we expect in response */
	HANDLE conf_hLayer3;		/* layer 3 handle we expect in response */
	unsigned int is_sys_prim;	/* is this a system (1) or L1 (0) primitive */
	l1if_compl_cb *cb;
	void *cb_data;
};

static void release_wlc(struct wait_l1_conf *wlc)
{
	osmo_timer_del(&wlc->timer);
	talloc_free(wlc);
}

static void l1if_req_timeout(void *data)
{
	struct wait_l1_conf *wlc = data;

	if (wlc->is_sys_prim)
		LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n",
			get_value_string(lc15bts_sysprim_names, wlc->conf_prim_id));
	else
		LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n",
			get_value_string(lc15bts_l1prim_names, wlc->conf_prim_id));
	exit(23);
}

static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim)
{
	switch (prim->id) {
	case GsmL1_PrimId_MphInitReq:
		return prim->u.mphInitReq.hLayer3;
	case GsmL1_PrimId_MphCloseReq:
		return prim->u.mphCloseReq.hLayer3;
	case GsmL1_PrimId_MphConnectReq:
		return prim->u.mphConnectReq.hLayer3;
	case GsmL1_PrimId_MphDisconnectReq:
		return prim->u.mphDisconnectReq.hLayer3;
	case GsmL1_PrimId_MphActivateReq:
		return prim->u.mphActivateReq.hLayer3;
	case GsmL1_PrimId_MphDeactivateReq:
		return prim->u.mphDeactivateReq.hLayer3;
	case GsmL1_PrimId_MphConfigReq:
		return prim->u.mphConfigReq.hLayer3;
	case GsmL1_PrimId_MphMeasureReq:
		return prim->u.mphMeasureReq.hLayer3;
	case GsmL1_PrimId_MphInitCnf:
		return prim->u.mphInitCnf.hLayer3;
	case GsmL1_PrimId_MphCloseCnf:
		return prim->u.mphCloseCnf.hLayer3;
	case GsmL1_PrimId_MphConnectCnf:
		return prim->u.mphConnectCnf.hLayer3;
	case GsmL1_PrimId_MphDisconnectCnf:
		return prim->u.mphDisconnectCnf.hLayer3;
	case GsmL1_PrimId_MphActivateCnf:
		return prim->u.mphActivateCnf.hLayer3;
	case GsmL1_PrimId_MphDeactivateCnf:
		return prim->u.mphDeactivateCnf.hLayer3;
	case GsmL1_PrimId_MphConfigCnf:
		return prim->u.mphConfigCnf.hLayer3;
	case GsmL1_PrimId_MphMeasureCnf:
		return prim->u.mphMeasureCnf.hLayer3;
	case GsmL1_PrimId_MphTimeInd:
	case GsmL1_PrimId_MphSyncInd:
	case GsmL1_PrimId_PhEmptyFrameReq:
	case GsmL1_PrimId_PhDataReq:
	case GsmL1_PrimId_PhConnectInd:
	case GsmL1_PrimId_PhReadyToSendInd:
	case GsmL1_PrimId_PhDataInd:
	case GsmL1_PrimId_PhRaInd:
		break;
	default:
		LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id);
		break;
	}
	return 0;
}

static int _l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
		   int is_system_prim, l1if_compl_cb *cb, void *data)
{
	struct wait_l1_conf *wlc;
	struct osmo_wqueue *wqueue;
	unsigned int timeout_secs;

	/* allocate new wsc and store reference to mutex and conf_id */
	wlc = talloc_zero(fl1h, struct wait_l1_conf);
	wlc->cb = cb;
	wlc->cb_data = data;

	/* Make sure we actually have received a REQUEST type primitive */
	if (is_system_prim == 0) {
		GsmL1_Prim_t *l1p = msgb_l1prim(msg);

		LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n",
			get_value_string(lc15bts_l1prim_names, l1p->id));

		if (lc15bts_get_l1prim_type(l1p->id) != L1P_T_REQ) {
			LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n",
				get_value_string(lc15bts_l1prim_names, l1p->id));
			talloc_free(wlc);
			return -EINVAL;
		}
		wlc->is_sys_prim = 0;
		wlc->conf_prim_id = lc15bts_get_l1prim_conf(l1p->id);
		wlc->conf_hLayer3 = l1p_get_hLayer3(l1p);
		wqueue = &fl1h->write_q[MQ_L1_WRITE];
		timeout_secs = 30;
	} else {
		Litecell15_Prim_t *sysp = msgb_sysprim(msg);

		LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n",
			get_value_string(lc15bts_sysprim_names, sysp->id));

		if (lc15bts_get_sysprim_type(sysp->id) != L1P_T_REQ) {
			LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n",
				get_value_string(lc15bts_sysprim_names, sysp->id));
			talloc_free(wlc);
			return -EINVAL;
		}
		wlc->is_sys_prim = 1;
		wlc->conf_prim_id = lc15bts_get_sysprim_conf(sysp->id);
		wqueue = &fl1h->write_q[MQ_SYS_WRITE];
		timeout_secs = 30;
	}

	/* enqueue the message in the queue and add wsc to list */
	if (osmo_wqueue_enqueue(wqueue, msg) != 0) {
		/* So we will get a timeout but the log message might help */
		LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n",
			is_system_prim ? "system primitive" : "gsm");
		msgb_free(msg);
	}
	llist_add(&wlc->list, &fl1h->wlc_list);

	/* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */
	wlc->timer.data = wlc;
	wlc->timer.cb = l1if_req_timeout;
	osmo_timer_schedule(&wlc->timer, timeout_secs, 0);

	return 0;
}

/* send a request primitive to the L1 and schedule completion call-back */
int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
		   l1if_compl_cb *cb, void *data)
{
	return _l1if_req_compl(fl1h, msg, 1, cb, data);
}

int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
		   l1if_compl_cb *cb, void *data)
{
	return _l1if_req_compl(fl1h, msg, 0, cb, data);
}

/* allocate a msgb containing a GsmL1_Prim_t */
struct msgb *l1p_msgb_alloc(void)
{
	struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim");

	if (msg)
		msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t));

	return msg;
}

/* allocate a msgb containing a Litecell15_Prim_t */
struct msgb *sysp_msgb_alloc(void)
{
	struct msgb *msg = msgb_alloc(sizeof(Litecell15_Prim_t), "sys_prim");

	if (msg)
		msg->l1h = msgb_put(msg, sizeof(Litecell15_Prim_t));

	return msg;
}

static GsmL1_PhDataReq_t *
data_req_from_rts_ind(GsmL1_Prim_t *l1p,
		const GsmL1_PhReadyToSendInd_t *rts_ind)
{
	GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;

	l1p->id = GsmL1_PrimId_PhDataReq;

	/* copy fields from PH-RSS.ind */
	data_req->hLayer1	= rts_ind->hLayer1;
	data_req->u8Tn 		= rts_ind->u8Tn;
	data_req->u32Fn		= rts_ind->u32Fn;
	data_req->sapi		= rts_ind->sapi;
	data_req->subCh		= rts_ind->subCh;
	data_req->u8BlockNbr	= rts_ind->u8BlockNbr;

	return data_req;
}

static GsmL1_PhEmptyFrameReq_t *
empty_req_from_rts_ind(GsmL1_Prim_t *l1p,
			const GsmL1_PhReadyToSendInd_t *rts_ind)
{
	GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;

	l1p->id = GsmL1_PrimId_PhEmptyFrameReq;

	empty_req->hLayer1 = rts_ind->hLayer1;
	empty_req->u8Tn = rts_ind->u8Tn;
	empty_req->u32Fn = rts_ind->u32Fn;
	empty_req->sapi = rts_ind->sapi;
	empty_req->subCh = rts_ind->subCh;
	empty_req->u8BlockNbr = rts_ind->u8BlockNbr;

	return empty_req;
}

/* fill PH-DATA.req from l1sap primitive */
static GsmL1_PhDataReq_t *
data_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1,
		uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch,
		uint8_t block_nr, uint8_t len)
{
	GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;

	l1p->id = GsmL1_PrimId_PhDataReq;

	/* copy fields from PH-RSS.ind */
	data_req->hLayer1	= (HANDLE)fl1->hLayer1;
	data_req->u8Tn 		= tn;
	data_req->u32Fn		= fn;
	data_req->sapi		= sapi;
	data_req->subCh		= sub_ch;
	data_req->u8BlockNbr	= block_nr;

	data_req->msgUnitParam.u8Size = len;

	return data_req;
}

/* fill PH-EMPTY_FRAME.req from l1sap primitive */
static GsmL1_PhEmptyFrameReq_t *
empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1,
		     uint8_t tn, uint32_t fn, uint8_t sapi,
		     uint8_t subch, uint8_t block_nr)
{
	GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;

	l1p->id = GsmL1_PrimId_PhEmptyFrameReq;

	empty_req->hLayer1 = (HANDLE)fl1->hLayer1;
	empty_req->u8Tn = tn;
	empty_req->u32Fn = fn;
	empty_req->sapi = sapi;
	empty_req->subCh = subch;
	empty_req->u8BlockNbr = block_nr;

	return empty_req;
}

static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
		       struct osmo_phsap_prim *l1sap, bool use_cache)
{
	struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
	struct msgb *l1msg = l1p_msgb_alloc();
	struct gsm_lchan *lchan;
	uint32_t u32Fn;
	uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0;
	uint8_t chan_nr, link_id;
	int len;

	if (!msg) {
		LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "PH-DATA.req without msg. Please fix!\n");
		abort();
	}

	len = (msg->l2h) ? msgb_l2len(msg) : 0;

	chan_nr = l1sap->u.data.chan_nr;
	link_id = l1sap->u.data.link_id;
	u32Fn = l1sap->u.data.fn;
	u8Tn = L1SAP_CHAN2TS(chan_nr);
	subCh = 0x1f;
	lchan = get_lchan_by_chan_nr(trx, chan_nr);
	if (L1SAP_IS_LINK_SACCH(link_id)) {
		sapi = GsmL1_Sapi_Sacch;
		if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr))
			subCh = l1sap_chan2ss(chan_nr);
	} else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) {
		if (ts_is_pdch(&trx->ts[u8Tn])) {
			if (L1SAP_IS_PTCCH(u32Fn)) {
				sapi = GsmL1_Sapi_Ptcch;
				u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn);
			} else {
				sapi = GsmL1_Sapi_Pdtch;
				u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn);
			}
		} else {
			sapi = GsmL1_Sapi_FacchF;
			u8BlockNbr = (u32Fn % 13) >> 2;
		}
	} else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
		subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
		sapi = GsmL1_Sapi_FacchH;
		u8BlockNbr = (u32Fn % 26) >> 3;
	} else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
		subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr);
		sapi = GsmL1_Sapi_Sdcch;
	} else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
		subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr);
		sapi = GsmL1_Sapi_Sdcch;
	} else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
		sapi = GsmL1_Sapi_Bcch;
	} else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
		sapi = GsmL1_Sapi_Cbch;
	} else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
		/* The sapi depends on DSP configuration, not
		 * on the actual SYSTEM INFORMATION 3. */
		u8BlockNbr = l1sap_fn2ccch_block(u32Fn);
		if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ"))
			sapi = GsmL1_Sapi_Pch;
		else
			sapi = GsmL1_Sapi_Agch;
	} else {
		LOGPLCFN(lchan, u32Fn, DL1C, LOGL_NOTICE, "unknown prim %d op %d chan_nr %d link_id %d\n",
			 l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id);
		msgb_free(l1msg);
		return -EINVAL;
	}

	/* convert l1sap message to GsmL1 primitive, keep payload */
	if (len) {
		/* data request */
		GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
		data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len);
		if (use_cache)
			memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer,
			       lchan->tch.dtx.facch, msgb_l2len(msg));
		else if (dtx_dl_amr_enabled(lchan) &&
			 ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) ||
			 (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) ||
			 (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) {
			if (sapi == GsmL1_Sapi_FacchF) {
				sapi = GsmL1_Sapi_TchF;
			}
			if (sapi == GsmL1_Sapi_FacchH) {
				sapi = GsmL1_Sapi_TchH;
				subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
				u8BlockNbr = (u32Fn % 13) >> 2;
			}
			if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) {
				/* FACCH interruption of DTX silence */
				/* cache FACCH data */
				memcpy(lchan->tch.dtx.facch, msg->l2h,
				       msgb_l2len(msg));
				/* prepare ONSET or INH message */
				if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F)
					l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
								GsmL1_TchPlType_Amr_Onset;
				else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F)
					l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
								GsmL1_TchPlType_Amr_SidUpdateInH;
				else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F)
					l1p->u.phDataReq.msgUnitParam.u8Buffer[0] =
								GsmL1_TchPlType_Amr_SidFirstInH;
				/* ignored CMR/CMI pair */
				l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0;
				l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0;
				/* update length */
				data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi,
						    subCh, u8BlockNbr, 3);
				/* update FN so it can be checked by TCH silence
				   resume handler */
				lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
			}
		} else if (dtx_dl_amr_enabled(lchan) &&
			   lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) {
			/* update FN so it can be checked by TCH silence
			   resume handler */
			lchan->tch.dtx.fn = LCHAN_FN_DUMMY;
		}
		else {
			OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer));
			memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h,
			       msgb_l2len(msg));
		}
		LOGPLCFN(lchan, u32Fn, DL1P, LOGL_DEBUG, "PH-DATA.req(%s)\n",
			 osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, l1p->u.phDataReq.msgUnitParam.u8Size));
	} else {
		/* empty frame */
		GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);

		empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
	}

	/* send message to DSP's queue */
	if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) {
		LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n");
		msgb_free(l1msg);
	} else
		dtx_int_signal(lchan);

	if (dtx_recursion(lchan))
		ph_data_req(trx, msg, l1sap, true);
	return 0;
}

static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
		      struct osmo_phsap_prim *l1sap, bool use_cache, bool marker)
{
	struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
	struct gsm_lchan *lchan;
	uint32_t u32Fn;
	uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi;
	uint8_t chan_nr;
	GsmL1_Prim_t *l1p;
	struct msgb *nmsg = NULL;
	int rc = -1;

	chan_nr = l1sap->u.tch.chan_nr;
	u32Fn = l1sap->u.tch.fn;
	u8Tn = L1SAP_CHAN2TS(chan_nr);
	u8BlockNbr = (u32Fn % 13) >> 2;
	if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
		subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
		sapi = GsmL1_Sapi_TchH;
	} else {
		subCh = 0x1f;
		sapi = GsmL1_Sapi_TchF;
	}

	lchan = get_lchan_by_chan_nr(trx, chan_nr);

	/* create new message and fill data */
	if (msg) {
		/* create new message */
		nmsg = l1p_msgb_alloc();
		if (!nmsg)
			return -ENOMEM;
		l1p = msgb_l1prim(nmsg);
		rc = l1if_tch_encode(lchan,
				     l1p->u.phDataReq.msgUnitParam.u8Buffer,
				     &l1p->u.phDataReq.msgUnitParam.u8Size,
				     msgb_l2(msg), msgb_l2len(msg), u32Fn, use_cache,
				     l1sap->u.tch.marker);
		if (rc < 0) {
		/* no data encoded for L1: smth will be generated below */
			msgb_free(nmsg);
			nmsg = NULL;
		}
	}

	/* no message/data, we might generate an empty traffic msg or re-send
	   cached SID in case of DTX */
	if (!nmsg)
		nmsg = gen_empty_tch_msg(lchan, u32Fn);

	/* no traffic message, we generate an empty msg */
	if (!nmsg) {
		nmsg = l1p_msgb_alloc();
		if (!nmsg)
			return -ENOMEM;
	}

	l1p = msgb_l1prim(nmsg);

	/* if we provide data, or if data is already in nmsg */
	if (l1p->u.phDataReq.msgUnitParam.u8Size) {
		/* data request */
		data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh,
				    u8BlockNbr,
				    l1p->u.phDataReq.msgUnitParam.u8Size);
	} else {
		/* empty frame */
		if (trx->bts->dtxd && trx != trx->bts->c0)
			lchan->tch.dtx.dl_active = true;
		empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
	}
	/* send message to DSP's queue */
	if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg) < 0) {
		LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n");
		msgb_free(nmsg);
		return -ENOBUFS;
	}

	if (dtx_is_first_p1(lchan))
		dtx_dispatch(lchan, E_FIRST);
	else
		dtx_int_signal(lchan);

	if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */
		return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false);

	return 0;
}

static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg,
		        struct osmo_phsap_prim *l1sap)
{
	struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
	uint8_t chan_nr;
	struct gsm_lchan *lchan;
	int rc = 0;

	switch (l1sap->u.info.type) {
	case PRIM_INFO_ACT_CIPH:
		chan_nr = l1sap->u.info.u.ciph_req.chan_nr;
		lchan = get_lchan_by_chan_nr(trx, chan_nr);
		if (l1sap->u.info.u.ciph_req.uplink) {
			l1if_set_ciphering(fl1, lchan, 0);
			lchan->ciph_state = LCHAN_CIPH_RX_REQ;
		}
		if (l1sap->u.info.u.ciph_req.downlink) {
			l1if_set_ciphering(fl1, lchan, 1);
			lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
		}
		if (l1sap->u.info.u.ciph_req.downlink
		 && l1sap->u.info.u.ciph_req.uplink)
			lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
		break;
	case PRIM_INFO_ACTIVATE:
	case PRIM_INFO_DEACTIVATE:
	case PRIM_INFO_MODIFY:
		chan_nr = l1sap->u.info.u.act_req.chan_nr;
		lchan = get_lchan_by_chan_nr(trx, chan_nr);
		if (l1sap->u.info.type == PRIM_INFO_ACTIVATE)
			l1if_rsl_chan_act(lchan);
		else if (l1sap->u.info.type == PRIM_INFO_MODIFY) {
			if (lchan->ho.active == HANDOVER_WAIT_FRAME)
				l1if_rsl_chan_mod(lchan);
			else
				l1if_rsl_mode_modify(lchan);
		} else if (l1sap->u.info.u.act_req.sacch_only)
			l1if_rsl_deact_sacch(lchan);
		else
			l1if_rsl_chan_rel(lchan);
		break;
	default:
		LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n",
			l1sap->u.info.type);
		rc = -EINVAL;
	}

	return rc;
}

/* primitive from common part */
int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
{
	struct msgb *msg = l1sap->oph.msg;
	int rc = 0;

	/* called functions MUST NOT take ownership of msgb, as it is
	 * free()d below */
	switch (OSMO_PRIM_HDR(&l1sap->oph)) {
	case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
		rc = ph_data_req(trx, msg, l1sap, false);
		break;
	case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
		rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker);
		break;
	case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
		rc = mph_info_req(trx, msg, l1sap);
		break;
	default:
		LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
			l1sap->oph.primitive, l1sap->oph.operation);
		rc = -EINVAL;
	}

	msgb_free(msg);

	return rc;
}

static int handle_mph_time_ind(struct lc15l1_hdl *fl1,
				GsmL1_MphTimeInd_t *time_ind,
				struct msgb *msg)
{
	struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1);
	struct gsm_bts *bts = trx->bts;
	struct osmo_phsap_prim l1sap;
	uint32_t fn;

	/* increment the primitive count for the alive timer */
	fl1->alive_prim_cnt++;

	/* ignore every time indication, except for c0 */
	if (trx != bts->c0) {
		msgb_free(msg);
		return 0;
	}

	fn = time_ind->u32Fn;

	memset(&l1sap, 0, sizeof(l1sap));
	osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
		PRIM_OP_INDICATION, NULL);
	l1sap.u.info.type = PRIM_INFO_TIME;
	l1sap.u.info.u.time_ind.fn = fn;

	msgb_free(msg);

	return l1sap_up(trx, &l1sap);
}

static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts)
{
	switch (ts->pchan) {
	case GSM_PCHAN_TCH_F_PDCH:
		if (ts->flags & TS_F_PDCH_ACTIVE)
			return GSM_PCHAN_PDCH;
		return GSM_PCHAN_TCH_F;
	case GSM_PCHAN_OSMO_DYN:
		return ts->dyn.pchan_is;
	default:
		return ts->pchan;
	}
}

static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts,
			       GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh,
			       uint8_t u8Tn, uint32_t u32Fn)
{
	uint8_t cbits = 0;
	enum gsm_phys_chan_config pchan = pick_pchan(ts);
	OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH);
	OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN);

	switch (sapi) {
	case GsmL1_Sapi_Bcch:
		cbits = 0x10;
		break;
	case GsmL1_Sapi_Cbch:
		cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */
		break;
	case GsmL1_Sapi_Sacch:
		switch(pchan) {
		case GSM_PCHAN_TCH_F:
			cbits = 0x01;
			break;
		case GSM_PCHAN_TCH_H:
			cbits = 0x02 + subCh;
			break;
		case GSM_PCHAN_CCCH_SDCCH4:
		case GSM_PCHAN_CCCH_SDCCH4_CBCH:
			cbits = 0x04 + subCh;
			break;
		case GSM_PCHAN_SDCCH8_SACCH8C:
		case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
			cbits = 0x08 + subCh;
			break;
		default:
			LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n",
				pchan);
			return 0;
		}
		break;
	case GsmL1_Sapi_Sdcch:
		switch(pchan) {
		case GSM_PCHAN_CCCH_SDCCH4:
		case GSM_PCHAN_CCCH_SDCCH4_CBCH:
			cbits = 0x04 + subCh;
			break;
		case GSM_PCHAN_SDCCH8_SACCH8C:
		case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
			cbits = 0x08 + subCh;
			break;
		default:
			LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n",
				pchan);
			return 0;
		}
		break;
	case GsmL1_Sapi_Agch:
	case GsmL1_Sapi_Pch:
		cbits = 0x12;
		break;
	case GsmL1_Sapi_Pdtch:
	case GsmL1_Sapi_Pacch:
		switch(pchan) {
		case GSM_PCHAN_PDCH:
			cbits = 0x01;
			break;
		default:
			LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n",
				pchan);
			return 0;
		}
		break;
	case GsmL1_Sapi_TchF:
		cbits = 0x01;
		break;
	case GsmL1_Sapi_TchH:
		cbits = 0x02 + subCh;
		break;
	case GsmL1_Sapi_FacchF:
		cbits = 0x01;
		break;
	case GsmL1_Sapi_FacchH:
		cbits = 0x02 + subCh;
		break;
	case GsmL1_Sapi_Ptcch:
		if (!L1SAP_IS_PTCCH(u32Fn)) {
			LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame "
				"number other than 12, got it at %u (%u). "
				"Please fix!\n", u32Fn % 52, u32Fn);
			abort();
		}
		switch(pchan) {
		case GSM_PCHAN_PDCH:
			cbits = 0x01;
			break;
		default:
			LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n",
				pchan);
			return 0;
		}
		break;
	default:
		return 0;
	}

	/* not reached due to default case above */
	return (cbits << 3) | u8Tn;
}

static const enum l1sap_common_sapi common_sapi_by_sapi_t[] = {
	[GsmL1_Sapi_Idle]	= L1SAP_COMMON_SAPI_IDLE,
	[GsmL1_Sapi_Fcch]	= L1SAP_COMMON_SAPI_FCCH,
	[GsmL1_Sapi_Sch]	= L1SAP_COMMON_SAPI_SCH,
	[GsmL1_Sapi_Sacch]	= L1SAP_COMMON_SAPI_SACCH,
	[GsmL1_Sapi_Sdcch]	= L1SAP_COMMON_SAPI_SDCCH,
	[GsmL1_Sapi_Bcch]	= L1SAP_COMMON_SAPI_BCCH,
	[GsmL1_Sapi_Pch]	= L1SAP_COMMON_SAPI_PCH,
	[GsmL1_Sapi_Agch]	= L1SAP_COMMON_SAPI_AGCH,
	[GsmL1_Sapi_Cbch]	= L1SAP_COMMON_SAPI_CBCH,
	[GsmL1_Sapi_Rach]	= L1SAP_COMMON_SAPI_RACH,
	[GsmL1_Sapi_TchF]	= L1SAP_COMMON_SAPI_TCH_F,
	[GsmL1_Sapi_FacchF]	= L1SAP_COMMON_SAPI_FACCH_F,
	[GsmL1_Sapi_TchH]	= L1SAP_COMMON_SAPI_TCH_H,
	[GsmL1_Sapi_FacchH]	= L1SAP_COMMON_SAPI_FACCH_H,
	[GsmL1_Sapi_Nch]	= L1SAP_COMMON_SAPI_NCH,
	[GsmL1_Sapi_Pdtch]	= L1SAP_COMMON_SAPI_PDTCH,
	[GsmL1_Sapi_Pacch]	= L1SAP_COMMON_SAPI_PACCH,
	[GsmL1_Sapi_Pbcch]	= L1SAP_COMMON_SAPI_PBCCH,
	[GsmL1_Sapi_Pagch]	= L1SAP_COMMON_SAPI_PAGCH,
	[GsmL1_Sapi_Ppch]	= L1SAP_COMMON_SAPI_PPCH,
	[GsmL1_Sapi_Pnch]	= L1SAP_COMMON_SAPI_PNCH,
	[GsmL1_Sapi_Ptcch]	= L1SAP_COMMON_SAPI_PTCCH,
	[GsmL1_Sapi_Prach]	= L1SAP_COMMON_SAPI_PRACH,
};

static enum l1sap_common_sapi get_common_sapi(GsmL1_Sapi_t sapi)
{
	if (sapi >= GsmL1_Sapi_NUM)
		return L1SAP_COMMON_SAPI_UNKNOWN;
	return common_sapi_by_sapi_t[sapi];
}

static void set_log_ctx_sapi(GsmL1_Sapi_t 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 lc15l1_hdl *fl1,
				     GsmL1_PhReadyToSendInd_t *rts_ind,
				     struct msgb *l1p_msg)
{
	struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1);
	struct gsm_bts *bts = trx->bts;
	struct msgb *resp_msg;
	GsmL1_PhDataReq_t *data_req;
	GsmL1_MsgUnitParam_t *msu_param;
	struct gsm_time g_time;
	uint32_t t3p;
	int rc;
	struct osmo_phsap_prim *l1sap;
	uint8_t chan_nr, link_id;
	uint32_t fn;

	set_log_ctx_sapi(rts_ind->sapi);

	/* check if primitive should be handled by common part */
	chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi,
		rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn);
	if (chan_nr) {
		fn = rts_ind->u32Fn;
		if (rts_ind->sapi == GsmL1_Sapi_Sacch)
			link_id = LID_SACCH;
		else
			link_id = LID_DEDIC;
		/* recycle the msgb and use it for the L1 primitive,
		 * which means that we (or our caller) must not free it */
		rc = msgb_trim(l1p_msg, sizeof(*l1sap));
		if (rc < 0)
			MSGB_ABORT(l1p_msg, "No room for primitive\n");
		l1sap = msgb_l1sap_prim(l1p_msg);
		if (rts_ind->sapi == GsmL1_Sapi_TchF
		 || rts_ind->sapi == GsmL1_Sapi_TchH) {
			osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS,
				PRIM_OP_INDICATION, l1p_msg);
			l1sap->u.tch.chan_nr = chan_nr;
			l1sap->u.tch.fn = fn;
		} else {
			osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
				PRIM_OP_INDICATION, l1p_msg);
			l1sap->u.data.link_id = link_id;
			l1sap->u.data.chan_nr = chan_nr;
			l1sap->u.data.fn = fn;
		}

		return l1sap_up(trx, l1sap);
	}

	gsm_fn2gsmtime(&g_time, rts_ind->u32Fn);

	DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n",
		get_value_string(lc15bts_l1sapi_names, rts_ind->sapi));

	/* in all other cases, we need to allocate a new PH-DATA.ind
	 * primitive msgb and start to fill it */
	resp_msg = l1p_msgb_alloc();
	data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
	msu_param = &data_req->msgUnitParam;

	/* set default size */
	msu_param->u8Size = GSM_MACBLOCK_LEN;

	switch (rts_ind->sapi) {
	case GsmL1_Sapi_Sch:
		/* compute T3prime */
		t3p = (g_time.t3 - 1) / 10;
		/* fill SCH burst with data */
		msu_param->u8Size = 4;
		msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9);
		msu_param->u8Buffer[1] = (g_time.t1 >> 1);
		msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1);
		msu_param->u8Buffer[3] = (t3p & 1);
		break;
	case GsmL1_Sapi_Prach:
		goto empty_frame;
		break;
	default:
		memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN);
		break;
	}
tx:

	/* transmit */
	if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) {
		LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n");
		msgb_free(resp_msg);
	}

	/* free the msgb, as we have not handed it to l1sap and thus
	 * need to release its memory */
	msgb_free(l1p_msg);
	return 0;

empty_frame:
	/* in case we decide to send an empty frame... */
	empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);

	goto tx;
}

#define LOG_FMT_MEAS "Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, BER %-3.2f, Timing %d"
#define LOG_PARAM_MEAS(meas_param) (meas_param)->fRssi, (meas_param)->fLinkQuality, (meas_param)->fBer, (meas_param)->i16BurstTiming

static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr,
				GsmL1_MeasParam_t *m, uint32_t fn)
{
	struct osmo_phsap_prim l1sap;
	memset(&l1sap, 0, sizeof(l1sap));
	osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO,
		PRIM_OP_INDICATION, NULL);
	l1sap.u.info.type = PRIM_INFO_MEAS;
	l1sap.u.info.u.meas_ind.chan_nr = chan_nr;
	l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming*64;
	l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000);
	l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1);
	l1sap.u.info.u.meas_ind.fn = fn;

	/* l1sap wants to take msgb ownership.  However, as there is no
	 * msg, it will msgb_free(l1sap.oph.msg == NULL) */
	return l1sap_up(trx, &l1sap);
}

static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_ind,
			      struct msgb *l1p_msg)
{
	struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1);
	uint8_t chan_nr, link_id;
	struct osmo_phsap_prim *l1sap;
	uint32_t fn;
	struct gsm_time g_time;
	uint8_t *data, len;
	int rc = 0;
	int8_t rssi;

	set_log_ctx_sapi(data_ind->sapi);

	chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi,
		data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn);
	fn = data_ind->u32Fn;
	link_id =  (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC;
	gsm_fn2gsmtime(&g_time, fn);

	if (!chan_nr) {
		LOGPGT(DL1C, LOGL_ERROR, &g_time, "PH-DATA-INDICATION for unknown sapi %s (%d)\n",
		     get_value_string(lc15bts_l1sapi_names, data_ind->sapi), data_ind->sapi);
		msgb_free(l1p_msg);
		return ENOTSUP;
	}

	process_meas_res(trx, chan_nr, &data_ind->measParam, fn);


	DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s, " LOG_FMT_MEAS "\n",
		get_value_string(lc15bts_l1sapi_names, data_ind->sapi), (uint32_t)data_ind->hLayer2,
		osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size),
		LOG_PARAM_MEAS(&data_ind->measParam));

	/* check for TCH */
	if (data_ind->sapi == GsmL1_Sapi_TchF
	 || data_ind->sapi == GsmL1_Sapi_TchH) {
		/* TCH speech frame handling */
		rc = l1if_tch_rx(trx, chan_nr, l1p_msg);
		msgb_free(l1p_msg);
		return rc;
	}

	/* get rssi */
	rssi = (int8_t) (data_ind->measParam.fRssi);
	/* get data pointer and length */
	data = data_ind->msgUnitParam.u8Buffer;
	len = data_ind->msgUnitParam.u8Size;
	/* pull lower header part before data */
	msgb_pull(l1p_msg, data - l1p_msg->data);
	/* trim remaining data to it's size, to get rid of upper header part */
	rc = msgb_trim(l1p_msg, len);
	if (rc < 0)
		MSGB_ABORT(l1p_msg, "No room for primitive data\n");
	l1p_msg->l2h = l1p_msg->data;
	/* push new l1 header */
	l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap));
	/* fill header */
	l1sap = msgb_l1sap_prim(l1p_msg);
	osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
		PRIM_OP_INDICATION, l1p_msg);
	l1sap->u.data.link_id = link_id;
	l1sap->u.data.chan_nr = chan_nr;
	l1sap->u.data.fn = fn;
	l1sap->u.data.rssi = rssi;
	l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000;
	l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64;
	l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10;

	return l1sap_up(trx, l1sap);
}

static int handle_ph_ra_ind(struct lc15l1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
			    struct msgb *l1p_msg)
{
	struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1);
	struct gsm_lchan *lchan;
	struct osmo_phsap_prim *l1sap;
	int rc;
	struct ph_rach_ind_param rach_ind_param;

	set_log_ctx_sapi(ra_ind->sapi);
	LOGPFN(DL1C, LOGL_DEBUG, ra_ind->u32Fn, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n",
	       LOG_PARAM_MEAS(&ra_ind->measParam));

	if ((ra_ind->msgUnitParam.u8Size != 1) &&
		(ra_ind->msgUnitParam.u8Size != 2)) {
		LOGPFN(DL1P, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", ra_ind->sapi);
		msgb_free(l1p_msg);
		return 0;
	}

	/* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */
	rach_ind_param = (struct ph_rach_ind_param) {
		/* .chan_nr set below */
		/* .ra set below */
		.acc_delay = 0,
		.fn = ra_ind->u32Fn,
		/* .is_11bit set below */
		/* .burst_type set below */
		.rssi = (int8_t) ra_ind->measParam.fRssi,
		.ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0),
		.acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64,
		.lqual_cb = (int16_t) ra_ind->measParam.fLinkQuality * 10, /* centiBels */
	};

	lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ra_ind->hLayer2);
	if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH ||
	    lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 ||
	    lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
		rach_ind_param.chan_nr = RSL_CHAN_RACH;
	else
		rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan);

	if (ra_ind->msgUnitParam.u8Size == 2) {
		uint16_t temp;
		uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0];
		ra = ra << 3;
		temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7);
		ra = ra | temp;
		rach_ind_param.is_11bit = 1;
		rach_ind_param.ra = ra;
	} else {
		rach_ind_param.is_11bit = 0;
		rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0];
	}

	/* the old legacy full-bits acc_delay cannot express negative values */
	if (ra_ind->measParam.i16BurstTiming > 0)
		rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2;

	/* mapping of the burst type, the values are specific to
	 * osmo-bts-litecell15 */
	switch (ra_ind->burstType) {
	case GsmL1_BurstType_Access_0:
		rach_ind_param.burst_type =
			GSM_L1_BURST_TYPE_ACCESS_0;
		break;
	case GsmL1_BurstType_Access_1:
		rach_ind_param.burst_type =
			GSM_L1_BURST_TYPE_ACCESS_1;
		break;
	case GsmL1_BurstType_Access_2:
		rach_ind_param.burst_type =
			GSM_L1_BURST_TYPE_ACCESS_2;
		break;
	default:
		rach_ind_param.burst_type =
			GSM_L1_BURST_TYPE_NONE;
		break;
	}

	/* msgb_trim() invalidates ra_ind, make that abundantly clear: */
	ra_ind = NULL;
	rc = msgb_trim(l1p_msg, sizeof(*l1sap));
	if (rc < 0)
		MSGB_ABORT(l1p_msg, "No room for primitive data\n");
	l1sap = msgb_l1sap_prim(l1p_msg);
	osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION,
		l1p_msg);
	l1sap->u.rach_ind = rach_ind_param;

	return l1sap_up(trx, l1sap);
}

/* handle any random indication from the L1 */
static int l1if_handle_ind(struct lc15l1_hdl *fl1, struct msgb *msg)
{
	GsmL1_Prim_t *l1p = msgb_l1prim(msg);
	int rc = 0;

	/* all the below called functions must take ownership of the msgb */
	switch (l1p->id) {
	case GsmL1_PrimId_MphTimeInd:
		rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg);
		break;
	case GsmL1_PrimId_MphSyncInd:
		msgb_free(msg);
		break;
	case GsmL1_PrimId_PhConnectInd:
		msgb_free(msg);
		break;
	case GsmL1_PrimId_PhReadyToSendInd:
		rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd,
					       msg);
		break;
	case GsmL1_PrimId_PhDataInd:
		rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg);
		break;
	case GsmL1_PrimId_PhRaInd:
		rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg);
		break;
	default:
		msgb_free(msg);
	}

	return rc;
}

static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc)
{
	if (wlc->is_sys_prim != 0)
		return 0;
	if (l1p->id != wlc->conf_prim_id)
		return 0;
	if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3)
		return 0;
	return 1;
}

int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg)
{
	GsmL1_Prim_t *l1p = msgb_l1prim(msg);
	struct wait_l1_conf *wlc;
	int rc;

	switch (l1p->id) {
	case GsmL1_PrimId_MphTimeInd:
		/* silent, don't clog the log file */
		break;
	default:
		LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n",
			get_value_string(lc15bts_l1prim_names, l1p->id), wq);
	}

	/* check if this is a resposne to a sync-waiting request */
	llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
		if (is_prim_compat(l1p, wlc)) {
			llist_del(&wlc->list);
			if (wlc->cb) {
				/* call-back function must take
				 * ownership of msgb */
				rc = wlc->cb(lc15l1_hdl_trx(fl1h), msg,
					     wlc->cb_data);
			} else {
				rc = 0;
				msgb_free(msg);
			}
			release_wlc(wlc);
			return rc;
		}
	}

	/* if we reach here, it is not a Conf for a pending Req */
	return l1if_handle_ind(fl1h, msg);
}

int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg)
{
	Litecell15_Prim_t *sysp = msgb_sysprim(msg);
	struct wait_l1_conf *wlc;
	int rc;

	LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n",
		get_value_string(lc15bts_sysprim_names, sysp->id));

	/* check if this is a resposne to a sync-waiting request */
	llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
		/* the limitation here is that we cannot have multiple callers
		 * sending the same primitive */
		if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) {
			llist_del(&wlc->list);
			if (wlc->cb) {
				/* call-back function must take
				 * ownership of msgb */
				rc = wlc->cb(lc15l1_hdl_trx(fl1h), msg,
					     wlc->cb_data);
			} else {
				rc = 0;
				msgb_free(msg);
			}
			release_wlc(wlc);
			return rc;
		}
	}
	/* if we reach here, it is not a Conf for a pending Req */
	return l1if_handle_ind(fl1h, msg);
}

static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
				void *data)
{
	Litecell15_Prim_t *sysp = msgb_sysprim(resp);
	GsmL1_Status_t status;
	int on = 0;

	if (sysp->id == Litecell15_PrimId_ActivateRfCnf)
		on = 1;

	if (on)
		status = sysp->u.activateRfCnf.status;
	else
		status = sysp->u.deactivateRfCnf.status;

	LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE",
		get_value_string(lc15bts_l1status_names, status));

	struct bts_lc15_priv *bts_lc15 = trx->bts->model_priv;

	if (on) {
		if (status != GsmL1_Status_Success) {
			LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n",
				get_value_string(lc15bts_l1status_names, status));
			bts_shutdown(trx->bts, "RF-ACT failure");
		} else {
			if (bts_lc15->led_ctrl_mode == LC15_LED_CONTROL_BTS)
				bts_update_status(BTS_STATUS_RF_ACTIVE, 1);
               }

		/* 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 {
		if (bts_lc15->led_ctrl_mode == LC15_LED_CONTROL_BTS)
			bts_update_status(BTS_STATUS_RF_ACTIVE, 0);
		osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL);
		osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL);
	}

	msgb_free(resp);

	return 0;
}

/* activate or de-activate the entire RF-Frontend */
int l1if_activate_rf(struct lc15l1_hdl *hdl, int on)
{
	struct msgb *msg = sysp_msgb_alloc();
	Litecell15_Prim_t *sysp = msgb_sysprim(msg);
	struct phy_instance *pinst = hdl->phy_inst;

	if (on) {
		sysp->id = Litecell15_PrimId_ActivateRfReq;
		sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0;
		sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct;

		sysp->u.activateRfReq.u8UnusedTsMode = pinst->u.lc15.pedestal_mode;

		sysp->u.activateRfReq.u8McCorrMode = 0;

		/* diversity mode: 0: SISO-A, 1: SISO-B, 2: MRC */
		sysp->u.activateRfReq.u8DiversityMode = pinst->u.lc15.diversity_mode;

		/* maximum cell size in quarter-bits, 90 == 12.456 km */
		sysp->u.activateRfReq.u8MaxCellSize = pinst->u.lc15.max_cell_size;

#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
		/* auto tx power adjustment mode 0:none, 1: automatic*/
		sysp->u.activateRfReq.u8EnAutoPowerAdjust = pinst->u.lc15.tx_pwr_adj_mode;
#endif

	} else {
		sysp->id = Litecell15_PrimId_DeactivateRfReq;
	}

	return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL);
}

static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) {
		struct gsm_lchan *lchan = &ts->lchan[i];

		if (!is_muted)
			continue;

		if (lchan->state != LCHAN_S_ACTIVE)
			continue;

		/* skip channels that might be active for another reason */
		if (lchan->type == GSM_LCHAN_CCCH)
			continue;
		if (lchan->type == GSM_LCHAN_PDTCH)
			continue;

		if (lchan->s <= 0)
			continue;

		lchan->s = 0;
		rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL);
	}
}

static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
			    void *data)
{
	struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
	Litecell15_Prim_t *sysp = msgb_sysprim(resp);
	GsmL1_Status_t status;

	status = sysp->u.muteRfCnf.status;

	if (status != GsmL1_Status_Success) {
		LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n",
		     get_value_string(lc15bts_l1status_names, status));
		oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0);
	} else {
		int i;

		LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n",
		     get_value_string(lc15bts_l1status_names, status));
		bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]);
		oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1);

		osmo_static_assert(
			ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute),
			ts_array_size);

		for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i)
			mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]);
	}

	msgb_free(resp);

	return 0;
}

/* mute/unmute RF time slots */
int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb)
{
	struct msgb *msg = sysp_msgb_alloc();
	Litecell15_Prim_t *sysp = msgb_sysprim(msg);

	LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n",
	     mute[0], mute[1], mute[2], mute[3],
	     mute[4], mute[5], mute[6], mute[7]
	    );

	sysp->id = Litecell15_PrimId_MuteRfReq;
	memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute));
	/* save for later use */
	memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute));

	return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL);
}

/* call-back on arrival of DSP+FPGA version + band capability */
static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
			 void *data)
{
	Litecell15_Prim_t *sysp = msgb_sysprim(resp);
	Litecell15_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf;
	struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
	int rc;

	fl1h->hw_info.dsp_version[0] = sic->dspVersion.major;
	fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor;
	fl1h->hw_info.dsp_version[2] = sic->dspVersion.build;

	fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major;
	fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor;
	fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build;

	LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\n",
		sic->dspVersion.major, sic->dspVersion.minor,
		sic->dspVersion.build, sic->fpgaVersion.major,
		sic->fpgaVersion.minor, sic->fpgaVersion.build);

	if (!(fl1h->hw_info.band_support & trx->bts->band))
		LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n",
		     gsm_band_name(trx->bts->band));

	/* Frequency bands indicated to the BSC */
	switch (fl1h->hw_info.band_support) {
	case GSM_BAND_450:
		trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_450;
		break;
	case GSM_BAND_480:
		trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_480;
		break;
	case GSM_BAND_850:
		trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_850;
		break;
	case GSM_BAND_900:
		trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PGSM;
		/* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_EGSM? */
		/* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_RGSM? */
		break;
	case GSM_BAND_1800:
		trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_DCS;
		break;
	case GSM_BAND_1900:
		trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PCS;
		break;
	}

	/* Request the activation */
	l1if_activate_rf(fl1h, 1);

	/* load calibration tables */
	rc = calib_load(fl1h);
	if (rc < 0)
		LOGP(DL1C, LOGL_ERROR, "Operating without calibration; "
			"unable to load tables!\n");

	msgb_free(resp);
	return 0;
}

/* request DSP+FPGA code versions */
static int l1if_get_info(struct lc15l1_hdl *hdl)
{
	struct msgb *msg = sysp_msgb_alloc();
	Litecell15_Prim_t *sysp = msgb_sysprim(msg);

	sysp->id = Litecell15_PrimId_SystemInfoReq;

	return l1if_req_compl(hdl, msg, info_compl_cb, NULL);
}

static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
			  void *data)
{
	struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
	Litecell15_Prim_t *sysp = msgb_sysprim(resp);
	GsmL1_Status_t status = sysp->u.layer1ResetCnf.status;

	LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n",
		get_value_string(lc15bts_l1status_names, status));

	msgb_free(resp);

	/* If we're coming out of reset .. */
	if (status != GsmL1_Status_Success) {
		LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n",
			get_value_string(lc15bts_l1status_names, status));
		bts_shutdown(trx->bts, "L1-RESET failure");
	}

	/* as we cannot get the current DSP trace flags, we simply
	 * set them to zero (or whatever dsp_trace_f has been initialized to */
	l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f);

	/* obtain version information on DSP/FPGA and band capabilities */
	l1if_get_info(fl1h);

	return 0;
}

int l1if_reset(struct lc15l1_hdl *hdl)
{
	struct msgb *msg = sysp_msgb_alloc();
	Litecell15_Prim_t *sysp = msgb_sysprim(msg);
	sysp->id = Litecell15_PrimId_Layer1ResetReq;

	return l1if_req_compl(hdl, msg, reset_compl_cb, NULL);
}

/* set the trace flags within the DSP */
int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags)
{
	struct msgb *msg = sysp_msgb_alloc();
	Litecell15_Prim_t *sysp = msgb_sysprim(msg);

	LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n",
		flags);

	sysp->id = Litecell15_PrimId_SetTraceFlagsReq;
	sysp->u.setTraceFlagsReq.u32Tf = flags;

	hdl->dsp_trace_f = flags;

	/* There is no confirmation we could wait for */
	if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) {
		LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n");
		msgb_free(msg);
		return -EAGAIN;
	}
	return 0;
}

static int get_hwinfo(struct lc15l1_hdl *fl1h)
{
	int rc;

	rc = lc15bts_rev_get();
	if (rc < 0) {
		LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS revision: %d\n", rc);
		return rc;
	}
	fl1h->hw_info.ver_major = rc;

	rc = lc15bts_model_get();
	if (rc < 0) {
		LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS model: %d\n", rc);
		return rc;
	}
	fl1h->hw_info.ver_minor = rc;

	rc = lc15bts_option_get(LC15BTS_OPTION_BAND);
	if (rc < 0) {
		LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS_OPTION_BAND: %d\n", rc);
		return rc;
	}

	switch (rc) {
	case LC15BTS_BAND_850:
		fl1h->hw_info.band_support = GSM_BAND_850;
		break;
	case LC15BTS_BAND_900:
		fl1h->hw_info.band_support = GSM_BAND_900;
		break;
	case LC15BTS_BAND_1800:
		fl1h->hw_info.band_support = GSM_BAND_1800;
		break;
	case LC15BTS_BAND_1900:
		fl1h->hw_info.band_support = GSM_BAND_1900;
		break;
	default:
		LOGP(DL1C, LOGL_ERROR, "Unexpected LC15BTS_BAND value: %d\n", rc);
		return -1;
	}

	LOGP(DL1C, LOGL_INFO, "BTS hw support band %s\n", gsm_band_name(fl1h->hw_info.band_support));

	return 0;
}

struct lc15l1_hdl *l1if_open(struct phy_instance *pinst)
{
	struct lc15l1_hdl *fl1h;
	int rc;

	LOGP(DL1C, LOGL_INFO, "Litecell 1.5 BTS L1IF compiled against API headers "
			"v%u.%u.%u\n", LITECELL15_API_VERSION >> 16,
			(LITECELL15_API_VERSION >> 8) & 0xff,
			 LITECELL15_API_VERSION & 0xff);

	fl1h = talloc_zero(pinst, struct lc15l1_hdl);
	if (!fl1h)
		return NULL;
	INIT_LLIST_HEAD(&fl1h->wlc_list);

	fl1h->phy_inst = pinst;
	fl1h->dsp_trace_f = pinst->u.lc15.dsp_trace_f;

	get_hwinfo(fl1h);

	rc = l1if_transport_open(MQ_SYS_WRITE, fl1h);
	if (rc < 0) {
		talloc_free(fl1h);
		return NULL;
	}

	rc = l1if_transport_open(MQ_L1_WRITE, fl1h);
	if (rc < 0) {
		l1if_transport_close(MQ_SYS_WRITE, fl1h);
		talloc_free(fl1h);
		return NULL;
	}

	return fl1h;
}

int l1if_close(struct lc15l1_hdl *fl1h)
{
	l1if_transport_close(MQ_L1_WRITE, fl1h);
	l1if_transport_close(MQ_SYS_WRITE, fl1h);
	return 0;
}

#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
static int dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
{
	Litecell15_Prim_t *sysp = msgb_sysprim(resp);
	Litecell15_IsAliveCnf_t *sac = &sysp->u.isAliveCnf;
	struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);

	fl1h->hw_alive.dsp_alive_cnt++;
	LOGP(DL1C, LOGL_DEBUG, "Rx SYS prim %s, status=%d (%d)\n",
			get_value_string(lc15bts_sysprim_names, sysp->id), sac->status, trx->nr);

	msgb_free(resp);
	return 0;
}

static void dsp_alive_timer_cb(void *data)
{
	struct lc15l1_hdl *fl1h = data;
	struct gsm_bts_trx *trx = fl1h->phy_inst->trx;
	struct msgb *msg = sysp_msgb_alloc();
	int rc;

	Litecell15_Prim_t *sys_prim =  msgb_sysprim(msg);
	sys_prim->id = Litecell15_PrimId_IsAliveReq;

	if (fl1h->hw_alive.dsp_alive_cnt == 0) {
		LOGP(DL1C, LOGL_ERROR, "Timeout waiting for SYS prim %s primitive (%d)\n",
				get_value_string(lc15bts_sysprim_names, sys_prim->id + 1), trx->nr);

		if( fl1h->phy_inst->trx ){
			fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
		}
	}

	LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s (%d)\n",
			get_value_string(lc15bts_sysprim_names, sys_prim->id), trx->nr);

	rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL);
	if (rc < 0) {
		LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(lc15bts_sysprim_names, sys_prim->id));
		return;
	}

	/* restart timer */
	fl1h->hw_alive.dsp_alive_cnt = 0;
	osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0);

	return;
}
#endif

int bts_model_phy_link_open(struct phy_link *plink)
{
	struct phy_instance *pinst = phy_instance_by_num(plink, 0);

	OSMO_ASSERT(pinst);

	if (!pinst->trx) {
		LOGP(DL1C, LOGL_NOTICE, "Ignoring phy link %d instance %d "
		     "because no TRX is associated with it\n", plink->num, pinst->num);
		return 0;
	}
	phy_link_state_set(plink, PHY_LINK_CONNECTING);

	pinst->u.lc15.hdl = l1if_open(pinst);
	if (!pinst->u.lc15.hdl) {
		LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n");
		return -EIO;
	}

	/* Set default PHY parameters */
	if (!pinst->u.lc15.max_cell_size)
		pinst->u.lc15.max_cell_size = LC15_BTS_MAX_CELL_SIZE_DEFAULT;

	if (!pinst->u.lc15.diversity_mode)
		pinst->u.lc15.diversity_mode = LC15_BTS_DIVERSITY_MODE_DEFAULT;

	if (!pinst->u.lc15.pedestal_mode)
		pinst->u.lc15.pedestal_mode = LC15_BTS_PEDESTAL_MODE_DEFAULT;

#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
	if (!pinst->u.lc15.dsp_alive_period)
		pinst->u.lc15.dsp_alive_period = LC15_BTS_DSP_ALIVE_TMR_DEFAULT;

	if (!pinst->u.lc15.tx_pwr_adj_mode)
		pinst->u.lc15.tx_pwr_adj_mode = LC15_BTS_TX_PWR_ADJ_DEFAULT;

	if (!pinst->u.lc15.tx_pwr_red_8psk)
		pinst->u.lc15.tx_pwr_red_8psk = LC15_BTS_TX_RED_PWR_8PSK_DEFAULT;

	if (!pinst->u.lc15.tx_c0_idle_pwr_red)
		pinst->u.lc15.tx_c0_idle_pwr_red = LC15_BTS_TX_C0_IDLE_RED_PWR_DEFAULT;
#endif

	struct lc15l1_hdl *fl1h = pinst->u.lc15.hdl;
	fl1h->dsp_trace_f = dsp_trace;

	l1if_reset(pinst->u.lc15.hdl);

	phy_link_state_set(plink, PHY_LINK_CONNECTED);

#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7)
	/* Send first IS_ALIVE primitive */
	struct msgb *msg = sysp_msgb_alloc();
	int rc;

	Litecell15_Prim_t *sys_prim =  msgb_sysprim(msg);
	sys_prim->id = Litecell15_PrimId_IsAliveReq;

	rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL);
	if (rc < 0) {
		LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(lc15bts_sysprim_names, sys_prim->id));
		return -EIO;
	}

	/* initialize DSP heart beat alive timer */
	osmo_timer_setup(&fl1h->hw_alive.dsp_alive_timer, dsp_alive_timer_cb, fl1h);
	fl1h->hw_alive.dsp_alive_cnt = 0;
	fl1h->hw_alive.dsp_alive_period = pinst->u.lc15.dsp_alive_period;
	osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0);
#endif

	return 0;
}
