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

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

#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/fsm.h>

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

#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/oml.h>
#include <osmo-bts/rsl.h>
#include <osmo-bts/amr.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/phy_link.h>
#include <osmo-bts/handover.h>
#include <osmo-bts/l1sap.h>
#include <osmo-bts/nm_common_fsm.h>

#include "l1_if.h"
#include "oc2gbts.h"
#include "utils.h"

static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi);

static int mph_info_chan_confirm(struct gsm_lchan *lchan,
			enum osmo_mph_info_type type, uint8_t cause)
{
	struct osmo_phsap_prim l1sap;

	memset(&l1sap, 0, sizeof(l1sap));
	osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
		NULL);
	l1sap.u.info.type = type;
	l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan);
	l1sap.u.info.u.act_cnf.cause = cause;

	return l1sap_up(lchan->ts->trx, &l1sap);
}

enum sapi_cmd_type {
	SAPI_CMD_ACTIVATE,
	SAPI_CMD_CONFIG_CIPHERING,
	SAPI_CMD_CONFIG_LOGCH_PARAM,
	SAPI_CMD_SACCH_REL_MARKER,
	SAPI_CMD_REL_MARKER,
	SAPI_CMD_DEACTIVATE,
};

struct sapi_cmd {
	struct llist_head entry;
	GsmL1_Sapi_t sapi;
	GsmL1_Dir_t dir;
	enum sapi_cmd_type type;
	int (*callback)(struct gsm_lchan *lchan, int status);
};

static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = {
	[GSM_PCHAN_NONE]		= GsmL1_LogChComb_0,
	[GSM_PCHAN_CCCH]		= GsmL1_LogChComb_IV,
	[GSM_PCHAN_CCCH_SDCCH4] 	= GsmL1_LogChComb_V,
	[GSM_PCHAN_CCCH_SDCCH4_CBCH] 	= GsmL1_LogChComb_V,
	[GSM_PCHAN_TCH_F]		= GsmL1_LogChComb_I,
	[GSM_PCHAN_TCH_H]		= GsmL1_LogChComb_II,
	[GSM_PCHAN_SDCCH8_SACCH8C]	= GsmL1_LogChComb_VII,
	[GSM_PCHAN_SDCCH8_SACCH8C_CBCH]	= GsmL1_LogChComb_VII,
	[GSM_PCHAN_PDCH]		= GsmL1_LogChComb_XIII,
	[GSM_PCHAN_UNKNOWN]		= GsmL1_LogChComb_0,
	/*
	 * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_OSMO_DYN should not be
	 * part of this, only "real" pchan values will be looked up here.
	 * See the callers of ts_connect_as().
	 */
};

static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb);

static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct oc2gl1_hdl *gl1,
		       uint32_t hLayer3_uint32)
{
	HANDLE hLayer3;
	prim->id = id;

	osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit);
	hLayer3 = (void*)hLayer3_uint32;

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

static uint32_t l1p_handle_for_trx(struct gsm_bts_trx *trx)
{
	struct gsm_bts *bts = trx->bts;

	osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit);
	osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit);

	return   bts->nr << 24
	       | trx->nr << 16;
}

static uint32_t l1p_handle_for_ts(struct gsm_bts_trx_ts *ts)
{
	osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit);

	return   l1p_handle_for_trx(ts->trx)
	       | ts->nr << 8;
}


static uint32_t l1p_handle_for_lchan(struct gsm_lchan *lchan)
{
	osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit);

	return   l1p_handle_for_ts(lchan->ts)
	       | lchan->nr;
}

GsmL1_Status_t prim_status(GsmL1_Prim_t *prim)
{
	switch (prim->id) {
	case GsmL1_PrimId_MphInitCnf:
		return prim->u.mphInitCnf.status;
	case GsmL1_PrimId_MphCloseCnf:
		return prim->u.mphCloseCnf.status;
	case GsmL1_PrimId_MphConnectCnf:
		return prim->u.mphConnectCnf.status;
	case GsmL1_PrimId_MphDisconnectCnf:
		return prim->u.mphDisconnectCnf.status;
	case GsmL1_PrimId_MphActivateCnf:
		return prim->u.mphActivateCnf.status;
	case GsmL1_PrimId_MphDeactivateCnf:
		return prim->u.mphDeactivateCnf.status;
	case GsmL1_PrimId_MphConfigCnf:
		return prim->u.mphConfigCnf.status;
	case GsmL1_PrimId_MphMeasureCnf:
		return prim->u.mphMeasureCnf.status;
	default:
		break;
	}
	return GsmL1_Status_Success;
}

#if 0
static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data)
{
	struct msgb *resp_msg = data;
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);

	if (prim_status(l1p) != GsmL1_Status_Success) {
		LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
			get_value_string(oc2gbts_l1prim_names, l1p->id),
			get_value_string(oc2gbts_l1status_names, cc->status));
		return 0;
	}

	msgb_free(l1_msg);

	return abis_nm_sendmsg(msg);
}
#endif

int lchan_activate(struct gsm_lchan *lchan);

static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg)
{
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_Status_t status = prim_status(l1p);
	struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr);

	if (status != GsmL1_Status_Success) {
		LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
			get_value_string(oc2gbts_l1prim_names, l1p->id),
			get_value_string(oc2gbts_l1status_names, status));
		msgb_free(l1_msg);
		switch (mo->obj_class) {
		case NM_OC_RADIO_CARRIER:
			return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK,
						      (void*)(intptr_t)NM_NACK_CANT_PERFORM);
		case NM_OC_CHANNEL:
			return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, NM_EV_OPSTART_NACK,
						      (void*)(intptr_t)NM_NACK_CANT_PERFORM);
		default:
			OSMO_ASSERT(0);
		}
	}

	msgb_free(l1_msg);
	switch (mo->obj_class) {
	case NM_OC_RADIO_CARRIER:
		return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL);
	case NM_OC_CHANNEL:
		/* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
		if (mo->obj_inst.trx_nr == 0 &&
		    mo->obj_inst.ts_nr == 0) {
			struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts);
			DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n");
			mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
				LCHAN_REL_ACT_OML;
			lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]);
			if (cbch) {
				cbch->rel_act_kind = LCHAN_REL_ACT_OML;
				lchan_activate(cbch);
			}
		}
		return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi,
					      NM_EV_OPSTART_ACK, NULL);
	default:
		OSMO_ASSERT(0);
	}
}

static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
			    void *data)
{
	struct gsm_abis_mo *mo;
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;

	mo = &trx->ts[cnf->u8Tn].mo;
	return opstart_compl(mo, l1_msg);
}

static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp,
			       void *data)
{
	Oc2g_Prim_t *sysp = msgb_sysprim(resp);
	GsmL1_Status_t status;

	status = sysp->u.muteRfCnf.status;

	if (status != GsmL1_Status_Success) {
		LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n",
			get_value_string(oc2gbts_l1status_names, status));
		bts_shutdown(trx->bts, "RF-MUTE failure");
	}

	msgb_free(resp);

	return 0;
}

static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
			     void *data)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);

	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf;

	LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n",
		get_value_string(oc2gbts_l1status_names, ic->status));

	/* store layer1 handle */
	if (ic->status != GsmL1_Status_Success) {
		LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n",
			get_value_string(oc2gbts_l1status_names, ic->status));
		bts_shutdown(trx->bts, "MPH-INIT failure");
	}

	fl1h->hLayer1 = ic->hLayer1;

	/* If the TRX was already locked the MphInit would have undone it */
	if (trx->mo.nm_state.administrative == NM_STATE_LOCKED)
		trx_rf_lock(trx, 1, trx_mute_on_init_cb);

	/* apply initial values for Tx power backoff for 8-PSK */
	/*trx->max_power_backoff_8psk = fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk;
	l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk);
	LOGP(DL1C, LOGL_INFO, "%s Applied initial 8-PSK Tx power backoff of %d dB\n",
			gsm_trx_name(fl1h->phy_inst->trx),
			fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk);
	*/

	/* apply initial values for Tx C0 idle slot power reduction */
	/*trx->c0_idle_power_red = fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red;
	l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red);
	LOGP(DL1C, LOGL_INFO, "%s Applied initial C0 idle slot power reduction of %d dB\n",
			gsm_trx_name(fl1h->phy_inst->trx),
			fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red);
	*/

	/* Begin to ramp up the power */
	power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL);

	return opstart_compl(&trx->mo, l1_msg);
}

int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids,
			   unsigned int num_attr_ids)
{
	unsigned int i;

	if (!mo->nm_attr)
		return 0;

	for (i = 0; i < num_attr_ids; i++) {
		if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i]))
			return 0;
	}
	return 1;
}

static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R };

/* initialize the layer1 */
static int trx_init(struct gsm_bts_trx *trx)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
	struct msgb *msg;
	GsmL1_MphInitReq_t *mi_req;
	GsmL1_DeviceParam_t *dev_par;
	int rc, oc2g_band;

	if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr,
				    ARRAY_SIZE(trx_rqd_attr))) {
		/* HACK: spec says we need to decline, but openbsc
		 * doesn't deal with this very well */
		return oml_mo_opstart_ack(&trx->mo);
		//return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM);
	}

	/* Update TRX band */
	rc = gsm_arfcn2band_rc(trx->arfcn, &trx->bts->band);
	if (rc) {
		/* FIXME: abort initialization? */
		LOGP(DL1C, LOGL_ERROR, "Could not pick GSM band "
			"for ARFCN %u\n", trx->arfcn);
		trx->bts->band = 0x00;
	}

	oc2g_band = oc2gbts_select_oc2g_band(trx, trx->arfcn);
	if (oc2g_band < 0) {
		LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n",
			gsm_band_name(trx->bts->band));
	}

	msg = l1p_msgb_alloc();
	mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h,
			   l1p_handle_for_trx(trx));
	dev_par = &mi_req->deviceParam;
	dev_par->devType = GsmL1_DevType_TxdRxu;
	dev_par->freqBand = oc2g_band;
	dev_par->u16Arfcn = trx->arfcn;
	dev_par->u16BcchArfcn = trx->bts->c0->arfcn;
	dev_par->u8NbTsc = BTS_TSC(trx->bts);

	if (!trx_ms_pwr_ctrl_is_osmo(trx)) {
		/* Target is in the middle between lower and upper RxLev thresholds */
		int lower_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.lower_thresh);
		int upper_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.upper_thresh);
		dev_par->fRxPowerLevel = (float) (lower_dbm + upper_dbm) / 2;
	} else {
		dev_par->fRxPowerLevel = 0.0;
	}

	dev_par->fTxPowerLevel = 0.0;
	LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, "
		"TxPower % 2.2f dBm\n", dev_par->freqBand, dev_par->u16Arfcn, dev_par->u8NbTsc,
		dev_par->fRxPowerLevel, dev_par->fTxPowerLevel);

	/* send MPH-INIT-REQ, wait for MPH-INIT-CNF */
	return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL);
}

uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx)
{
	const struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);

	return fl1h->hLayer1;
}

static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
			      void *data)
{
	msgb_free(l1_msg);
	bts_model_trx_close_cb(trx, 0);
	return 0;
}

void bts_model_trx_close(struct gsm_bts_trx *trx)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
	struct msgb *msg;
	int rc;

	msg = l1p_msgb_alloc();
	prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h,
		  l1p_handle_for_trx(trx));
	LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr);

	rc = l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL);
	if (rc < 0)
		bts_model_trx_close_cb(trx, rc);
}

static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
	uint8_t mute[8];
	int i;

	for (i = 0; i < ARRAY_SIZE(mute); ++i)
		mute[i] = locked ? 1 : 0;

	return l1if_mute_rf(fl1h, mute, cb);
}

int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8],
		       int success)
{
	if (success) {
		int i;
		int is_locked = 1;

		for (i = 0; i < 8; ++i)
			if (!mute_state[i])
				is_locked = 0;

		mo->nm_state.administrative =
			is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
		mo->procedure_pending = 0;
		return oml_mo_statechg_ack(mo);
	} else {
		mo->procedure_pending = 0;
		return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
	}
}

static int ts_connect_as(struct gsm_bts_trx_ts *ts,
			 enum gsm_phys_chan_config pchan,
			 l1if_compl_cb *cb, void *data)
{
	struct msgb *msg = l1p_msgb_alloc();
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx);
	GsmL1_MphConnectReq_t *cr;

	if (pchan == GSM_PCHAN_TCH_F_PDCH
	    || pchan == GSM_PCHAN_OSMO_DYN) {
		LOGP(DL1C, LOGL_ERROR,
		     "%s Requested TS connect as %s,"
		     " expected a specific pchan instead\n",
		     gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan));
		return -EINVAL;
	}

	cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h,
		       l1p_handle_for_ts(ts));
	cr->u8Tn = ts->nr;
	cr->logChComb = pchan_to_logChComb[pchan];

	return l1if_gsm_req_compl(fl1h, msg, cb, NULL);
}

static int ts_opstart(struct gsm_bts_trx_ts *ts)
{
	enum gsm_phys_chan_config pchan = ts->pchan;
	switch (pchan) {
	case GSM_PCHAN_OSMO_DYN:
		ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE;
		/* First connect as NONE, until first RSL CHAN ACT. */
		pchan = GSM_PCHAN_NONE;
		break;
	case GSM_PCHAN_TCH_F_PDCH:
		/* First connect as TCH/F, expecting PDCH ACT. */
		pchan = GSM_PCHAN_TCH_F;
		break;
	default:
		/* simply use ts->pchan */
		break;
	}
	return ts_connect_as(ts, pchan, opstart_compl_cb, NULL);
}

GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan)
{
	switch (lchan->type) {
	case GSM_LCHAN_TCH_F:
		return GsmL1_Sapi_TchF;
	case GSM_LCHAN_TCH_H:
		return GsmL1_Sapi_TchH;
	default:
		LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "cannot determine L1 SAPI\n");
		break;
	}
	return GsmL1_Sapi_Idle;
}

GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan)
{
	enum gsm_phys_chan_config pchan = lchan->ts->pchan;

	if (pchan == GSM_PCHAN_OSMO_DYN)
		pchan = lchan->ts->dyn.pchan_want;

	switch (pchan) {
	case GSM_PCHAN_CCCH_SDCCH4:
	case GSM_PCHAN_CCCH_SDCCH4_CBCH:
		if (lchan->type == GSM_LCHAN_CCCH)
			return GsmL1_SubCh_NA;
		/* fall-through */
	case GSM_PCHAN_TCH_H:
	case GSM_PCHAN_SDCCH8_SACCH8C:
	case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
		return lchan->nr;
	case GSM_PCHAN_NONE:
	case GSM_PCHAN_CCCH:
	case GSM_PCHAN_TCH_F:
	case GSM_PCHAN_PDCH:
	case GSM_PCHAN_UNKNOWN:
	default:
	/* case GSM_PCHAN_OSMO_DYN: is caught above */
		return GsmL1_SubCh_NA;
	}

	return GsmL1_SubCh_NA;
}

struct sapi_dir {
	GsmL1_Sapi_t sapi;
	GsmL1_Dir_t dir;
};

static const struct sapi_dir ccch_sapis[] = {
	{ GsmL1_Sapi_Fcch,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Sch, 	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Bcch,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Agch,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Pch,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Rach,	GsmL1_Dir_RxUplink },
};

static const struct sapi_dir tchf_sapis[] = {
	{ GsmL1_Sapi_TchF,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_TchF,	GsmL1_Dir_RxUplink },
	{ GsmL1_Sapi_FacchF,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_FacchF,	GsmL1_Dir_RxUplink },
	{ GsmL1_Sapi_Sacch,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Sacch,	GsmL1_Dir_RxUplink },
};

static const struct sapi_dir tchh_sapis[] = {
	{ GsmL1_Sapi_TchH,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_TchH,	GsmL1_Dir_RxUplink },
	{ GsmL1_Sapi_FacchH, 	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_FacchH, 	GsmL1_Dir_RxUplink },
	{ GsmL1_Sapi_Sacch,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Sacch,	GsmL1_Dir_RxUplink },
};

static const struct sapi_dir sdcch_sapis[] = {
	{ GsmL1_Sapi_Sdcch, 	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Sdcch, 	GsmL1_Dir_RxUplink },
	{ GsmL1_Sapi_Sacch,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Sacch,	GsmL1_Dir_RxUplink },
};

static const struct sapi_dir cbch_sapis[] = {
	{ GsmL1_Sapi_Cbch, 	GsmL1_Dir_TxDownlink },
	/* Does the CBCH really have a SACCH in Downlink? */
	{ GsmL1_Sapi_Sacch,	GsmL1_Dir_TxDownlink },
};

static const struct sapi_dir pdtch_sapis[] = {
	{ GsmL1_Sapi_Pdtch,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Pdtch,	GsmL1_Dir_RxUplink },
	{ GsmL1_Sapi_Ptcch,	GsmL1_Dir_TxDownlink },
	{ GsmL1_Sapi_Prach,	GsmL1_Dir_RxUplink },
#if 0
	{ GsmL1_Sapi_Ptcch,	GsmL1_Dir_RxUplink },
	{ GsmL1_Sapi_Pacch,	GsmL1_Dir_TxDownlink },
#endif
};

struct lchan_sapis {
	const struct sapi_dir *sapis;
	unsigned int num_sapis;
};

static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = {
	[GSM_LCHAN_SDCCH] = {
		.sapis = sdcch_sapis,
		.num_sapis = ARRAY_SIZE(sdcch_sapis),
	},
	[GSM_LCHAN_TCH_F] = {
		.sapis = tchf_sapis,
		.num_sapis = ARRAY_SIZE(tchf_sapis),
	},
	[GSM_LCHAN_TCH_H] = {
		.sapis = tchh_sapis,
		.num_sapis = ARRAY_SIZE(tchh_sapis),
	},
	[GSM_LCHAN_CCCH] = {
		.sapis = ccch_sapis,
		.num_sapis = ARRAY_SIZE(ccch_sapis),
	},
	[GSM_LCHAN_PDTCH] = {
		.sapis = pdtch_sapis,
		.num_sapis = ARRAY_SIZE(pdtch_sapis),
	},
	[GSM_LCHAN_CBCH] = {
		.sapis = cbch_sapis,
		.num_sapis = ARRAY_SIZE(cbch_sapis),
	},
};

static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd);
static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd);

static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir);
static int lchan_deactivate_sapis(struct gsm_lchan *lchan);

/**
 * Execute the first SAPI command of the queue. In case of the markers
 * this method is re-entrant so we need to make sure to remove a command
 * from the list before calling a function that will queue a command.
 *
 * \return 0 in case no Gsm Request was sent, 1 otherwise
 */
static int sapi_queue_exeute(struct gsm_lchan *lchan)
{
	int res;
	struct sapi_cmd *cmd;

	cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);

	switch (cmd->type) {
	case SAPI_CMD_ACTIVATE:
		mph_send_activate_req(lchan, cmd);
		res = 1;
		break;
	case SAPI_CMD_CONFIG_CIPHERING:
		mph_send_config_ciphering(lchan, cmd);
		res = 1;
		break;
	case SAPI_CMD_CONFIG_LOGCH_PARAM:
		mph_send_config_logchpar(lchan, cmd);
		res = 1;
		break;
	case SAPI_CMD_SACCH_REL_MARKER:
		llist_del(&cmd->entry);
		talloc_free(cmd);
		res = check_sapi_release(lchan, GsmL1_Sapi_Sacch,
					GsmL1_Dir_TxDownlink);
		res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch,
					GsmL1_Dir_RxUplink);
		break;
	case SAPI_CMD_REL_MARKER:
		llist_del(&cmd->entry);
		talloc_free(cmd);
		res = lchan_deactivate_sapis(lchan);
		break;
	case SAPI_CMD_DEACTIVATE:
		mph_send_deactivate_req(lchan, cmd);
		res = 1;
		break;
	default:
		LOGP(DL1C, LOGL_NOTICE,
			"Unimplemented command type %d\n", cmd->type);
		llist_del(&cmd->entry);
		talloc_free(cmd);
		res = 0;
		abort();
		break;
	}

	return res;
}

static void sapi_queue_send(struct gsm_lchan *lchan)
{
	int res;

	do {
		res = sapi_queue_exeute(lchan);
	} while (res == 0 && !llist_empty(&lchan->sapi_cmds));
}

static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status)
{
	int end;
	struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next,
			struct sapi_cmd, entry);
	llist_del(&cmd->entry);
	end = llist_empty(&lchan->sapi_cmds);

	if (cmd->callback)
		cmd->callback(lchan, status);
	talloc_free(cmd);

	if (end || llist_empty(&lchan->sapi_cmds)) {
		LOGPLCHAN(lchan, DL1C, LOGL_DEBUG, "End of SAPI cmd queue encountered.%s\n",
			  llist_empty(&lchan->sapi_cmds) ? " Queue is now empty." : " More pending.");
		return;
	}

	sapi_queue_send(lchan);
}

/**
 * Queue and possible execute a SAPI command. Return 1 in case the command was
 * already executed and 0 in case if it was only put into the queue
 */
static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
{
	int start = llist_empty(&lchan->sapi_cmds);
	llist_add_tail(&cmd->entry, &lchan->sapi_cmds);

	if (!start)
		return 0;

	sapi_queue_send(lchan);
	return 1;
}

static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
			      void *data)
{
	enum lchan_sapi_state status;
	struct sapi_cmd *cmd;
	struct gsm_lchan *lchan;
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf;

	/* get the lchan from the information we supplied */
	lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3);
	if (!lchan) {
		LOGP(DL1C, LOGL_ERROR,
			"Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3);
		goto err;
	}

	LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.conf (%s ",
		  get_value_string(oc2gbts_l1sapi_names, ic->sapi));
	LOGPC(DL1C, LOGL_INFO, "%s)\n",
		get_value_string(oc2gbts_dir_names, ic->dir));

	if (ic->status == GsmL1_Status_Success) {
		DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n",
			get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn);
		status = LCHAN_SAPI_S_ASSIGNED;
	} else {
		LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n",
			get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn,
			get_value_string(oc2gbts_l1status_names, ic->status));
		status = LCHAN_SAPI_S_ERROR;
	}

	if (ic->dir & GsmL1_Dir_TxDownlink)
		lchan->sapis_dl[ic->sapi] = status;
	if (ic->dir & GsmL1_Dir_RxUplink)
		lchan->sapis_ul[ic->sapi] = status;

	if (llist_empty(&lchan->sapi_cmds)) {
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got activation confirmation with empty queue\n");
		goto err;
	}

	cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
	if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
			cmd->type != SAPI_CMD_ACTIVATE) {
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n",
			  cmd->sapi, cmd->dir, ic->sapi, ic->dir);
		goto err;
	}

	sapi_queue_dispatch(lchan, ic->status);

err:
	msgb_free(l1_msg);

	return 0;
}

uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan)
{
	return 0xBB
		| (lchan->nr << 8)
		| (lchan->ts->nr << 16)
		| (lchan->ts->trx->nr << 24);
}

/* obtain a ptr to the lapdm_channel for a given hLayer */
struct gsm_lchan *
l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2)
{
	uint8_t magic = hLayer2 & 0xff;
	uint8_t ts_nr = (hLayer2 >> 16) & 0xff;
	uint8_t lchan_nr = (hLayer2 >> 8)& 0xff;
	struct gsm_bts_trx_ts *ts;

	if (magic != 0xBB)
		return NULL;

	/* FIXME: if we actually run on the BTS, the 32bit field is large
	 * enough to simply put a pointer inside. */
	if (ts_nr >= ARRAY_SIZE(trx->ts))
		return NULL;

	ts = &trx->ts[ts_nr];

	if (lchan_nr >= ARRAY_SIZE(ts->lchan))
		return NULL;

	return &ts->lchan[lchan_nr];
}

/* we regularly check if the DSP L1 is still sending us primitives.
 * if not, we simply stop the BTS program (and be re-spawned) */
static void alive_timer_cb(void *data)
{
	struct oc2gl1_hdl *fl1h = data;

	if (fl1h->alive_prim_cnt == 0) {
		LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n");
		exit(23);
	}
	fl1h->alive_prim_cnt = 0;
	osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
}

static void clear_amr_params(GsmL1_LogChParam_t *lch_par)
{
	int j;
	/* common for the SIGN, V1 and EFR: */
	lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA;
	lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset;
	for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
		lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
}

static void set_payload_format(GsmL1_LogChParam_t *lch_par)
{
	lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp;
}

static int lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
{
	struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
	struct gsm48_multi_rate_conf *mr_conf =
			(struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie;
	int j;

	LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s tch_mode=0x%02x\n", __func__, lchan->tch_mode);

	switch (lchan->tch_mode) {
	case GSM48_CMODE_SIGN:
		/* we have to set some TCH payload type even if we don't
		 * know yet what codec we will use later on */
		if (lchan->type == GSM_LCHAN_TCH_F)
			lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
		else
			lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
		clear_amr_params(lch_par);
		break;
	case GSM48_CMODE_SPEECH_V1:
		if (lchan->type == GSM_LCHAN_TCH_F)
			lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
		else
			lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
		set_payload_format(lch_par);
		clear_amr_params(lch_par);
		break;
	case GSM48_CMODE_SPEECH_EFR:
		lch_par->tch.tchPlType = GsmL1_TchPlType_Efr;
		set_payload_format(lch_par);
		clear_amr_params(lch_par);
		break;
	case GSM48_CMODE_SPEECH_AMR:
		lch_par->tch.tchPlType = GsmL1_TchPlType_Amr;
		set_payload_format(lch_par);
		/* At call set-up, after every successful handover and after a channel mode modify, the
		 * default phase (odd) shall be used in downlink direction. */
		 lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd;
		lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan);

		/* initialize to clean state */
		for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
			lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;

		j = 0;
		if (mr_conf->m4_75)
			lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75;
		if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
			break;

		if (mr_conf->m5_15)
			lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15;
		if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
			break;

		if (mr_conf->m5_90)
			lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9;
		if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
			break;

		if (mr_conf->m6_70)
			lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7;
		if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
			break;

		if (mr_conf->m7_40)
			lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4;
		if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
			break;

		if (mr_conf->m7_95)
			lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95;
		if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
			break;

		if (mr_conf->m10_2)
			lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2;
		if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
			break;
		if (mr_conf->m12_2)
			lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2;
		break;
	case GSM48_CMODE_DATA_14k5:
	case GSM48_CMODE_DATA_12k0:
	case GSM48_CMODE_DATA_6k0:
	case GSM48_CMODE_DATA_3k6:
	default:
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel mode %s is not supported!\n",
			  gsm48_chan_mode_name(lchan->tch_mode));
		return -ENOTSUP;
	}

	return 0;
}

static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
	struct msgb *msg = l1p_msgb_alloc();
	int sapi = cmd->sapi;
	int dir = cmd->dir;
	int rc;
	GsmL1_MphActivateReq_t *act_req;
	GsmL1_LogChParam_t *lch_par;

	act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq,
			    fl1h, l1p_handle_for_lchan(lchan));
	lch_par = &act_req->logChPrm;
	act_req->u8Tn = lchan->ts->nr;
	act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
	act_req->dir = dir;
	act_req->sapi = sapi;
	act_req->hLayer2 = (HANDLE *)l1if_lchan_to_hLayer(lchan);
	act_req->hLayer3 = act_req->hLayer2;

	switch (act_req->sapi) {
	case GsmL1_Sapi_Rach:
		lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic;
		break;
	case GsmL1_Sapi_Agch:
		lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name);
		break;
	case GsmL1_Sapi_TchH:
	case GsmL1_Sapi_TchF:
		if ((rc = lchan2lch_par(lch_par, lchan)) != 0) {
			talloc_free(msg);
			return rc;
		}
		/*
		 * Be sure that every packet is received, even if it
		 * fails. In this case the length might be lower or 0.
		 */
		act_req->fBFILevel = -200.0f;
		break;
	case GsmL1_Sapi_Ptcch:
		lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic;
		break;
	case GsmL1_Sapi_Prach:
		lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic;
		break;
	case GsmL1_Sapi_Sacch:
		/*
		 * For the SACCH we need to set the u8MsPowerLevel when
		 * doing manual MS power control.
		 */
		if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
			lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
		/* fall through */
	case GsmL1_Sapi_Pdtch:
	case GsmL1_Sapi_Pacch:
		/*
		 * Be sure that every packet is received, even if it
		 * fails. In this case the length might be lower or 0.
		 */
		act_req->fBFILevel = -200.0f;
		break;
	default:
		break;
	}

	LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.req (hL2=0x%08x, %s ",
		  (uint32_t)act_req->hLayer2, get_value_string(oc2gbts_l1sapi_names, act_req->sapi));
	dump_lch_par(LOGL_INFO, lch_par, act_req->sapi);
	LOGPC(DL1C, LOGL_INFO, "%s)\n",
		get_value_string(oc2gbts_dir_names, act_req->dir));

	/* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
	return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL);
}

static void sapi_clear_queue(struct llist_head *queue)
{
	struct sapi_cmd *next, *tmp;

	llist_for_each_entry_safe(next, tmp, queue, entry) {
		llist_del(&next->entry);
		talloc_free(next);
	}
}

static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);

	/* FIXME: Error handling */
	if (status != GsmL1_Status_Success) {
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "act failed mark broken due status: %d\n", status);
		lchan_set_state(lchan, LCHAN_S_BROKEN);
		sapi_clear_queue(&lchan->sapi_cmds);
		mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD);
		return -1;
	}

	if (!llist_empty(&lchan->sapi_cmds))
		return 0;

	if (lchan->state != LCHAN_S_ACT_REQ)
		return 0;

	lchan_set_state(lchan, LCHAN_S_ACTIVE);
	mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0);

	/* set the initial ciphering parameters for both directions */
	l1if_set_ciphering(fl1h, lchan, 1);
	l1if_set_ciphering(fl1h, lchan, 0);
	if (lchan->encr.alg_id)
		lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
	else
		lchan->ciph_state = LCHAN_CIPH_NONE;

	return 0;
}

static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir)
{
	struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);

	cmd->sapi = sapi;
	cmd->dir = dir;
	cmd->type = SAPI_CMD_ACTIVATE;
	cmd->callback = sapi_activate_cb;
	queue_sapi_command(lchan, cmd);
}

int lchan_activate(struct gsm_lchan *lchan)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
	const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
	unsigned int i;

	lchan_set_state(lchan, LCHAN_S_ACT_REQ);

	if (!llist_empty(&lchan->sapi_cmds))
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to activate lchan, but commands in queue\n");

	/* For handover, always start the main channel immediately. lchan->want_dl_sacch_active indicates whether dl
	 * SACCH should be activated. Also, for HO, start the RACH SAPI. */
	if (lchan->ho.active == HANDOVER_ENABLED || rsl_chan_rt_is_asci(lchan->rsl_chan_rt))
		enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);

	for (i = 0; i < s4l->num_sapis; i++) {
		int sapi = s4l->sapis[i].sapi;
		int dir = s4l->sapis[i].dir;

		if (sapi == GsmL1_Sapi_Sch) {
			/* once we activate the SCH, we should get MPH-TIME.ind */
			fl1h->alive_timer.cb = alive_timer_cb;
			fl1h->alive_timer.data = fl1h;
			fl1h->alive_prim_cnt = 0;
			osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
		}

		/* For handover, possibly postpone activating the dl SACCH until the HO RACH is received. */
		if (sapi == GsmL1_Sapi_Sacch && dir == GsmL1_Dir_TxDownlink
		    && !lchan->want_dl_sacch_active)
			continue;

		enqueue_sapi_act_cmd(lchan, sapi, dir);
	}
	return 0;
}

const struct value_string oc2gbts_l1cfgt_names[] = {
	{ GsmL1_ConfigParamId_SetNbTsc, 	"Set NB TSC" },
	{ GsmL1_ConfigParamId_SetTxPowerLevel,	"Set Tx power level" },
	{ GsmL1_ConfigParamId_SetLogChParams,	"Set logical channel params" },
	{ GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" },
	{ GsmL1_ConfigParamId_Set8pskPowerReduction,	"Set 8PSK Tx power reduction" },
	{ 0, NULL }
};

static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi)
{
	int i;

	switch (sapi) {
	case GsmL1_Sapi_Rach:
		LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic);
		break;
	case GsmL1_Sapi_Agch:
		LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ",
		      lch_par->agch.u8NbrOfAgch);
		break;
	case GsmL1_Sapi_Sacch:
		LOGPC(DL1C, logl, "MS Power Level 0x%02x",
			lch_par->sacch.u8MsPowerLevel);
		break;
	case GsmL1_Sapi_TchF:
	case GsmL1_Sapi_TchH:
		LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (",
			lch_par->tch.amrCmiPhase,
			lch_par->tch.amrInitCodecMode);
		for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) {
			LOGPC(DL1C, logl, "%x ",
			lch_par->tch.amrActiveCodecSet[i]);
		}
		break;
	/* FIXME: PRACH / PTCCH */
	default:
		break;
	}
	LOGPC(DL1C, logl, ")\n");
}

static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
				  void *data)
{
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;

	LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
		gsm_trx_name(trx),
		get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId));

	LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n",
		cc->cfgParams.setTxPowerLevel.fTxPowerLevel);

	power_trx_change_compl(trx,
		(int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000));

	msgb_free(l1_msg);

	return 0;
}

static int chmod_txpower_backoff_8psk_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
				  void *data)
{
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;

	LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
		gsm_trx_name(trx),
		get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId));

	LOGPC(DL1C, LOGL_INFO, "Backoff %u dB\n",
		cc->cfgParams.set8pskPowerReduction.u8PowerReduction);

	msgb_free(l1_msg);

	return 0;
}

static int chmod_max_cell_size_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
				  void *data)
{
	Oc2g_Prim_t *sysp = msgb_sysprim(resp);
	Oc2g_SetMaxCellSizeCnf_t *sac = &sysp->u.setMaxCellSizeCnf;

	LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n",
			gsm_trx_name(trx),
			get_value_string(oc2gbts_sysprim_names, sysp->id),
			get_value_string(oc2gbts_l1status_names, sac->status));

	msgb_free(resp);

	return 0;
}

static int chmod_c0_idle_pwr_red_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
				  void *data)
{
	Oc2g_Prim_t *sysp = msgb_sysprim(resp);
	Oc2g_SetC0IdleSlotPowerReductionCnf_t *sac = &sysp->u.setC0IdleSlotPowerReductionCnf;

	LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n",
			gsm_trx_name(trx),
			get_value_string(oc2gbts_sysprim_names, sysp->id),
			get_value_string(oc2gbts_l1status_names, sac->status));

	msgb_free(resp);

	return 0;
}

static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
				void *data)
{
	struct gsm_lchan *lchan;
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;

	/* get the lchan from the information we supplied */
	lchan = l1if_hLayer_to_lchan(trx, (uint32_t)cc->hLayer3);
	if (!lchan) {
		LOGP(DL1C, LOGL_ERROR,
			"Failed to find lchan for hLayer3=0x%x\n", (uint32_t)cc->hLayer3);
		goto err;
	}

	LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.conf (%s) ",
		  get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId));

	switch (cc->cfgParamId) {
	case GsmL1_ConfigParamId_SetLogChParams:
		dump_lch_par(LOGL_INFO,
			     &cc->cfgParams.setLogChParams.logChParams,
			     cc->cfgParams.setLogChParams.sapi);

		sapi_queue_dispatch(lchan, cc->status);
		break;
	case GsmL1_ConfigParamId_SetCipheringParams:
		switch (lchan->ciph_state) {
		case LCHAN_CIPH_RX_REQ:
			LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n");
			lchan->ciph_state = LCHAN_CIPH_RX_CONF;
			break;
		case LCHAN_CIPH_RX_CONF_TX_REQ:
			LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n");
			lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
			break;
		case LCHAN_CIPH_RXTX_REQ:
			LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n");
			lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
			break;
		case LCHAN_CIPH_NONE:
			LOGPC(DL1C, LOGL_INFO, "\n");
			break;
		default:
			LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state);
			break;
		}
		if (llist_empty(&lchan->sapi_cmds)) {
			LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got ciphering conf with empty queue\n");
			goto err;
		}

		sapi_queue_dispatch(lchan, cc->status);
		break;
	case GsmL1_ConfigParamId_SetNbTsc:
	default:
		LOGPC(DL1C, LOGL_INFO, "\n");
		break;
	}

err:
	msgb_free(l1_msg);

	return 0;
}

static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
{
	struct gsm_bts_trx *trx = lchan->ts->trx;
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
	struct msgb *msg = l1p_msgb_alloc();
	GsmL1_MphConfigReq_t *conf_req;
	GsmL1_LogChParam_t *lch_par;
	int rc;

	/* channel mode, encryption and/or multirate have changed */

	/* update multi-rate config */
	conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
			     l1p_handle_for_lchan(lchan));
	conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams;
	conf_req->cfgParams.setLogChParams.sapi = cmd->sapi;
	conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr;
	conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
	conf_req->cfgParams.setLogChParams.dir = cmd->dir;
	conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);

	lch_par = &conf_req->cfgParams.setLogChParams.logChParams;
	if ((rc = lchan2lch_par(lch_par, lchan)) != 0) {
		talloc_free(msg);
		return rc;
	}

	/* Update the MS Power Level */
	if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx))
		lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;

	/* FIXME: update encryption */

	LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.req (%s) ",
		  get_value_string(oc2gbts_l1sapi_names, conf_req->cfgParams.setLogChParams.sapi));
	LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ",
			conf_req->cfgParams.setLogChParams.u8Tn,
			conf_req->cfgParams.setLogChParams.subCh,
			conf_req->cfgParams.setLogChParams.dir);
	dump_lch_par(LOGL_INFO,
			&conf_req->cfgParams.setLogChParams.logChParams,
			conf_req->cfgParams.setLogChParams.sapi);

	return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
}

static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi)
{
	struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);

	cmd->dir = dir;
	cmd->sapi = sapi;
	cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM;
	queue_sapi_command(lchan, cmd);
}

static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction)
{
	enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan));
	return 0;
}

int l1if_set_txpower(struct oc2gl1_hdl *fl1h, float tx_power)
{
	struct msgb *msg = l1p_msgb_alloc();
	GsmL1_MphConfigReq_t *conf_req;

	conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0);
	conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel;
	conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power;

	return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL);
}

int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff)
{
	struct msgb *msg = l1p_msgb_alloc();
	GsmL1_MphConfigReq_t *conf_req;

	conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0);
	conf_req->cfgParamId = GsmL1_ConfigParamId_Set8pskPowerReduction;
	conf_req->cfgParams.set8pskPowerReduction.u8PowerReduction = backoff;

	return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_backoff_8psk_compl_cb, NULL);
}

int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size)
{
	struct msgb *msg =  sysp_msgb_alloc();
	Oc2g_Prim_t *sys_prim =  msgb_sysprim(msg);
	sys_prim->id = Oc2g_PrimId_SetMaxCellSizeReq;
	sys_prim->u.setMaxCellSizeReq.u8MaxCellSize = cell_size;

	LOGP(DL1C, LOGL_INFO, "%s Set max cell size = %d qbits\n",
			gsm_trx_name(fl1h->phy_inst->trx),
			cell_size);

	return l1if_req_compl(fl1h, msg, chmod_max_cell_size_compl_cb, NULL);

}

int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red)
{
	struct msgb *msg =  sysp_msgb_alloc();
	Oc2g_Prim_t *sys_prim =  msgb_sysprim(msg);
	sys_prim->id = Oc2g_PrimId_SetC0IdleSlotPowerReductionReq;
	sys_prim->u.setC0IdleSlotPowerReductionReq.u8PowerReduction = red;

	LOGP(DL1C, LOGL_INFO, "%s Set C0 idle slot power reduction = %d dB\n",
			gsm_trx_name(fl1h->phy_inst->trx),
			red);

	return l1if_req_compl(fl1h, msg, chmod_c0_idle_pwr_red_compl_cb, NULL);
}

const enum GsmL1_CipherId_t rsl2l1_ciph[] = {
	[0]	= GsmL1_CipherId_A50,
	[1]	= GsmL1_CipherId_A50,
	[2]	= GsmL1_CipherId_A51,
	[3]	= GsmL1_CipherId_A52,
	[4]	= GsmL1_CipherId_A53,
};

static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
	struct msgb *msg = l1p_msgb_alloc();
	struct GsmL1_MphConfigReq_t *cfgr;

	cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h,
			 l1p_handle_for_lchan(lchan));

	cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams;
	cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr;
	cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
	cfgr->cfgParams.setCipheringParams.dir = cmd->dir;
	cfgr->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);

	if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph))
		return -EINVAL;
	cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id];

	LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "SET_CIPHERING (ALG=%u %s)\n",
		   cfgr->cfgParams.setCipheringParams.cipherId,
		   get_value_string(oc2gbts_dir_names, cfgr->cfgParams.setCipheringParams.dir));

	memcpy(cfgr->cfgParams.setCipheringParams.u8Kc,
	       lchan->encr.key, lchan->encr.key_len);

	return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
}

static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir)
{
	struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);

	cmd->dir = dir;
	cmd->type = SAPI_CMD_CONFIG_CIPHERING;
	queue_sapi_command(lchan, cmd);
}

int l1if_set_ciphering(struct oc2gl1_hdl *fl1h,
			  struct gsm_lchan *lchan,
			  int dir_downlink)
{
	int dir;

	/* ignore the request when the channel is not active */
	if (lchan->state != LCHAN_S_ACTIVE)
		return -1;

	if (dir_downlink)
		dir = GsmL1_Dir_TxDownlink;
	else
		dir = GsmL1_Dir_RxUplink;

	enqueue_sapi_ciphering_cmd(lchan, dir);

	return 0;
}

int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
{
	if (lchan->state != LCHAN_S_ACTIVE)
		return -1;

	enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch);
	return 0;
}

int l1if_rsl_mode_modify(struct gsm_lchan *lchan)
{
	if (lchan->state != LCHAN_S_ACTIVE)
		return -1;

	/* channel mode, encryption and/or multirate have changed */

	/* update multi-rate config */
	tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink);
	tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink);

	/* FIXME: update encryption */

	return 0;
}

static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
				void *data)
{
	enum lchan_sapi_state status;
	struct sapi_cmd *cmd;
	struct gsm_lchan *lchan;
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf;

	lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3);
	if (!lchan) {
		LOGP(DL1C, LOGL_ERROR,
			"Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3);
		goto err;
	}

	LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.conf (%s ",
		  get_value_string(oc2gbts_l1sapi_names, ic->sapi));
	LOGPC(DL1C, LOGL_INFO, "%s)\n",
		get_value_string(oc2gbts_dir_names, ic->dir));

	if (ic->status == GsmL1_Status_Success) {
		DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n",
			get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn);
		status = LCHAN_SAPI_S_NONE;
	} else {
		LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n",
			get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn,
			get_value_string(oc2gbts_l1status_names, ic->status));
		status = LCHAN_SAPI_S_ERROR;
	}

	if (ic->dir & GsmL1_Dir_TxDownlink)
		lchan->sapis_dl[ic->sapi] = status;
	if (ic->dir & GsmL1_Dir_RxUplink)
		lchan->sapis_ul[ic->sapi] = status;


	if (llist_empty(&lchan->sapi_cmds)) {
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got de-activation confirmation with empty queue\n");
		goto err;
	}

	cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
	if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
			cmd->type != SAPI_CMD_DEACTIVATE) {
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n",
			  cmd->sapi, cmd->dir, ic->sapi, ic->dir);
		goto err;
	}

	sapi_queue_dispatch(lchan, ic->status);

err:
	msgb_free(l1_msg);
	return 0;
}

static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
	struct msgb *msg = l1p_msgb_alloc();
	GsmL1_MphDeactivateReq_t *deact_req;

	deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq,
			      fl1h, l1p_handle_for_lchan(lchan));
	deact_req->u8Tn = lchan->ts->nr;
	deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
	deact_req->dir = cmd->dir;
	deact_req->sapi = cmd->sapi;
	deact_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);

	LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.req (%s ",
		  get_value_string(oc2gbts_l1sapi_names, deact_req->sapi));
	LOGPC(DL1C, LOGL_INFO, "%s)\n",
		get_value_string(oc2gbts_dir_names, deact_req->dir));

	/* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
	return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL);
}

static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status)
{
	/* FIXME: Error handling. There is no NACK... */
	if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) {
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "is now broken. Stopping the release.\n");
		lchan_set_state(lchan, LCHAN_S_BROKEN);
		sapi_clear_queue(&lchan->sapi_cmds);
		mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
		return -1;
	}

	if (!llist_empty(&lchan->sapi_cmds))
		return 0;

	/* Don't send an REL ACK on SACCH deactivate */
	if (lchan->state != LCHAN_S_REL_REQ)
		return 0;

	lchan_set_state(lchan, LCHAN_S_NONE);
	mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);

	/* Reactivate CCCH due to SI3 update in RSL */
	if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) {
		lchan->rel_act_kind = LCHAN_REL_ACT_RSL;
		lchan_activate(lchan);
	}
	return 0;
}

static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir)
{
	struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);

	cmd->sapi = sapi;
	cmd->dir = dir;
	cmd->type = SAPI_CMD_DEACTIVATE;
	cmd->callback = sapi_deactivate_cb;
	return queue_sapi_command(lchan, cmd);
}

/*
 * Release the SAPI if it was allocated. E.g. the SACCH might be already
 * deactivated or during a hand-over the TCH was not allocated yet.
 */
static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
{
	/* check if we should schedule a release */
	if (dir & GsmL1_Dir_TxDownlink) {
		if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED)
			return 0;
		lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL;
	} else if (dir & GsmL1_Dir_RxUplink) {
		if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED)
			return 0;
		lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL;
	}

	/* now schedule the command and maybe dispatch it */
	return enqueue_sapi_deact_cmd(lchan, sapi, dir);
}

static int release_sapi_ul_rach(struct gsm_lchan *lchan)
{
	return check_sapi_release(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink);
}

static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
{
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx);
	const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
	int i, res;

	res = 0;

	/* The order matters.. the Facch needs to be released first */
	for (i = s4l->num_sapis-1; i >= 0; i--) {
		/* Stop the alive timer once we deactivate the SCH */
		if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch)
			osmo_timer_del(&fl1h->alive_timer);

		/* Release if it was allocated */
		res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir);
	}

	/* always attempt to disable the RACH burst */
	res |= release_sapi_ul_rach(lchan);

	/* nothing was queued */
	if (res == 0) {
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "all SAPIs already released?\n");
		lchan_set_state(lchan, LCHAN_S_BROKEN);
		mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
	}

	return res;
}

static void enqueue_rel_marker(struct gsm_lchan *lchan)
{
	struct sapi_cmd *cmd;

	/* remember we need to release all active SAPIs */
	cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
	cmd->type = SAPI_CMD_REL_MARKER;
	queue_sapi_command(lchan, cmd);
}

int bts_model_lchan_deactivate(struct gsm_lchan *lchan)
{
	lchan_set_state(lchan, LCHAN_S_REL_REQ);
	enqueue_rel_marker(lchan);
	return 0;
}

static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan)
{
	struct sapi_cmd *cmd;

	/* remember we need to check if the SACCH is allocated */
	cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
	cmd->type = SAPI_CMD_SACCH_REL_MARKER;
	queue_sapi_command(lchan, cmd);
}

int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan)
{
	enqueue_sacch_rel_marker(lchan);
	return 0;
}

/* callback from OML */
int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type,
			struct tlv_parsed *old_attr, struct tlv_parsed *new_attr,
			void *obj)
{
	/* FIXME: more checks if the attributes are valid */
	return 0;
}

/* callback from OML */
int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg,
			struct gsm_abis_mo *mo, void *obj)
{
	struct abis_om_fom_hdr *foh = msgb_l3(msg);
	struct gsm_bts_trx *trx;
	struct oc2gl1_hdl *fl1h;
	uint8_t cell_size;

	switch (foh->msg_type) {
	case NM_MT_SET_RADIO_ATTR:
		trx = obj;
		fl1h = trx_oc2gl1_hdl(trx);
		/* convert max TA to max cell size in qbits */
		cell_size = bts->max_ta << 2;

		/* We do not need to check for L1 handle
		 * because the max cell size parameter can receive before MphInit */
		if (fl1h->phy_inst->u.oc2g.max_cell_size != cell_size) {
			/* instruct L1 to apply max cell size */
			l1if_set_max_cell_size(fl1h, cell_size);
			/* update current max cell size */
			fl1h->phy_inst->u.oc2g.max_cell_size = cell_size;
		}

		/* Did we go through MphInit yet? If yes fire and forget */
		if (fl1h->hLayer1) {
			power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL);

			if (fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk != trx->max_power_backoff_8psk) {
				/* update current Tx power backoff for 8-PSK */
				fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk = trx->max_power_backoff_8psk;
				/* instruct L1 to apply Tx power backoff for 8 PSK */
				l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk);
			}

			if (fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red != trx->c0_idle_power_red) {
				/* update current C0 idle slot Tx power reduction */
				fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red = trx->c0_idle_power_red;
				/* instruct L1 to apply C0 idle slot power reduction */
				l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red);
			}
		}
		break;
	}

	return 0;
}

/* callback from OML */
int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
		      void *obj)
{
	struct gsm_bts_bb_trx *bb_transc;
	struct gsm_bts_trx* trx;
	struct gsm_bts_trx_ts *ts;
	int rc;

	switch (mo->obj_class) {
	case NM_OC_SITE_MANAGER:
	case NM_OC_BTS:
	case NM_OC_BASEB_TRANSC:
	case NM_OC_GPRS_NSE:
	case NM_OC_GPRS_CELL:
	case NM_OC_GPRS_NSVC:
		rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
		break;
	case NM_OC_RADIO_CARRIER:
		trx = (struct gsm_bts_trx *) obj;
		rc = trx_init(trx);
		break;
	case NM_OC_CHANNEL:
		ts = (struct gsm_bts_trx_ts*) obj;
		rc = ts_opstart(ts);
		break;
	default:
		rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP);
	}
	return rc;
}

int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
			    void *obj, uint8_t adm_state)
{
	int rc = -EINVAL;
	int granted = 0;

	switch (mo->obj_class) {
	case NM_OC_RADIO_CARRIER:

		if (mo->procedure_pending) {
			LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: "
			     "pending procedure on RC %d\n",
			     ((struct gsm_bts_trx *)obj)->nr);
			return 0;
		}
		mo->procedure_pending = 1;
		switch (adm_state) {
		case NM_STATE_LOCKED:
			rc = trx_rf_lock(obj, 1, NULL);
			break;
		case NM_STATE_UNLOCKED:
			rc = trx_rf_lock(obj, 0, NULL);
			break;
		default:
			granted = 1;
			break;
		}

		if (!granted && rc == 0)
			/* in progress, will send ack/nack after completion */
			return 0;

		mo->procedure_pending = 0;

		break;
	default:
		/* blindly accept all state changes */
		granted = 1;
		break;
	}

	if (granted) {
		mo->nm_state.administrative = adm_state;
		return oml_mo_statechg_ack(mo);
	} else
		return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);

}

int l1if_rsl_chan_act(struct gsm_lchan *lchan)
{
	//uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE);
	//uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE);
	lchan_activate(lchan);
	return 0;
}

/**
 * Modify the given lchan in the handover scenario. This is a lot like
 * second channel activation but with some additional activation.
 */
int l1if_rsl_chan_mod(struct gsm_lchan *lchan)
{
	if (lchan->ho.active == HANDOVER_NONE)
		return -1;

	LOGPLCHAN(lchan, DHO, LOGL_ERROR, "modifying channel for handover\n");

	/* Give up listening to RACH bursts */
	release_sapi_ul_rach(lchan);

	/* All the normal SAPIs have already been activated, only DL SACCH may still be missing. */
	if (lchan->want_dl_sacch_active)
		enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink);

	return 0;
}

int l1if_rsl_chan_rel(struct gsm_lchan *lchan)
{
	/* A duplicate RF Release Request, ignore it */
	if (lchan->state == LCHAN_S_REL_REQ) {
		LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "already in release request state.\n");
		return 0;
	}

	lchan_deactivate(lchan);
	return 0;
}

int l1if_rsl_deact_sacch(struct gsm_lchan *lchan)
{
	/* Only de-activate the SACCH if the lchan is active */
	if (lchan->state != LCHAN_S_ACTIVE)
		return 0;
	return bts_model_lchan_deactivate_sacch(lchan);
}

int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
{
	struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx);

	return l1if_activate_rf(fl1, 0);
}

int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
{
	return l1if_set_txpower(trx_oc2gl1_hdl(trx), ((float) p_trxout_mdBm)/1000.0);
}

static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
			    void *data)
{
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf;
	struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
	OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);

	LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "Rx mphDisconnectCnf\n");

	cb_ts_disconnected(ts);

	msgb_free(l1_msg);

	return 0;
}

int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
{
	struct msgb *msg = l1p_msgb_alloc();
	struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx);
	GsmL1_MphDisconnectReq_t *cr;

	LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "TS disconnect\n");
	cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h,
		       l1p_handle_for_ts(ts));
	cr->u8Tn = ts->nr;

	return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL);
}

static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
			 void *data)
{
	GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
	GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
	struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn];
	OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS);

	LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "%s Rx mphConnectCnf flags=%s%s%s\n",
		  gsm_pchan_name(ts->pchan),
		  ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "",
		  ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "",
		  ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : "");

	cb_ts_connected(ts, 0);

	msgb_free(l1_msg);

	return 0;
}

void bts_model_ts_connect(struct gsm_bts_trx_ts *ts,
			 enum gsm_phys_chan_config as_pchan)
{
	int rc;

	rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL);
	if (rc)
		cb_ts_connected(ts, rc);
}
