diff options
Diffstat (limited to 'src/gsm/lapdm.c')
| -rw-r--r-- | src/gsm/lapdm.c | 1249 | 
1 files changed, 1249 insertions, 0 deletions
| diff --git a/src/gsm/lapdm.c b/src/gsm/lapdm.c new file mode 100644 index 00000000..1c08113e --- /dev/null +++ b/src/gsm/lapdm.c @@ -0,0 +1,1249 @@ +/* GSM LAPDm (TS 04.06) implementation */ + +/* (C) 2010-2011 by Harald Welte <laforge@gnumonks.org> + * (C) 2010-2011 by Andreas Eversberg <jolly@eversberg.eu> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +/*! \addtogroup lapdm + *  @{ + */ + +/*! \file lapdm.c */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> + +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/prim.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/lapdm.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +/* TS 04.06 Figure 4 / Section 3.2 */ +#define LAPDm_LPD_NORMAL  0 +#define LAPDm_LPD_SMSCB	  1 +#define LAPDm_SAPI_NORMAL 0 +#define LAPDm_SAPI_SMS	  3 +#define LAPDm_ADDR(lpd, sapi, cr) ((((lpd) & 0x3) << 5) | (((sapi) & 0x7) << 2) | (((cr) & 0x1) << 1) | 0x1) + +#define LAPDm_ADDR_LPD(addr) (((addr) >> 5) & 0x3) +#define LAPDm_ADDR_SAPI(addr) (((addr) >> 2) & 0x7) +#define LAPDm_ADDR_CR(addr) (((addr) >> 1) & 0x1) +#define LAPDm_ADDR_EA(addr) ((addr) & 0x1) + +/* TS 04.06 Table 3 / Section 3.4.3 */ +#define LAPDm_CTRL_I(nr, ns, p)	((((nr) & 0x7) << 5) | (((p) & 0x1) << 4) | (((ns) & 0x7) << 1)) +#define LAPDm_CTRL_S(nr, s, p)	((((nr) & 0x7) << 5) | (((p) & 0x1) << 4) | (((s) & 0x3) << 2) | 0x1) +#define LAPDm_CTRL_U(u, p)	((((u) & 0x1c) << (5-2)) | (((p) & 0x1) << 4) | (((u) & 0x3) << 2) | 0x3) + +#define LAPDm_CTRL_is_I(ctrl)	(((ctrl) & 0x1) == 0) +#define LAPDm_CTRL_is_S(ctrl)	(((ctrl) & 0x3) == 1) +#define LAPDm_CTRL_is_U(ctrl)	(((ctrl) & 0x3) == 3) + +#define LAPDm_CTRL_U_BITS(ctrl)	((((ctrl) & 0xC) >> 2) | ((ctrl) & 0xE0) >> 3) +#define LAPDm_CTRL_PF_BIT(ctrl)	(((ctrl) >> 4) & 0x1) + +#define LAPDm_CTRL_S_BITS(ctrl)	(((ctrl) & 0xC) >> 2) + +#define LAPDm_CTRL_I_Ns(ctrl)	(((ctrl) & 0xE) >> 1) +#define LAPDm_CTRL_Nr(ctrl)	(((ctrl) & 0xE0) >> 5) + +#define LAPDm_LEN(len)	((len << 2) | 0x1) +#define LAPDm_MORE	0x2 +#define LAPDm_EL	0x1 + +#define LAPDm_U_UI	0x0 + +/* TS 04.06 Section 5.8.3 */ +#define N201_AB_SACCH		18 +#define N201_AB_SDCCH		20 +#define N201_AB_FACCH		20 +#define N201_Bbis		23 +#define N201_Bter_SACCH		21 +#define N201_Bter_SDCCH		23 +#define N201_Bter_FACCH		23 +#define N201_B4			19 + +/* 5.8.2.1 N200 during establish and release */ +#define N200_EST_REL		5 +/* 5.8.2.1 N200 during timer recovery state */ +#define N200_TR_SACCH		5 +#define N200_TR_SDCCH		23 +#define N200_TR_FACCH_FR	34 +#define N200_TR_EFACCH_FR	48 +#define N200_TR_FACCH_HR	29 +/* FIXME: set N200 depending on chan_nr */ +#define N200 N200_TR_SDCCH + +enum lapdm_format { +	LAPDm_FMT_A, +	LAPDm_FMT_B, +	LAPDm_FMT_Bbis, +	LAPDm_FMT_Bter, +	LAPDm_FMT_B4, +}; + +static int lapdm_send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg); +static int send_rslms_dlsap(struct osmo_dlsap_prim *dp, +	struct lapd_msg_ctx *lctx); + +static void lapdm_dl_init(struct lapdm_datalink *dl, +			  struct lapdm_entity *entity, int t200) +{ +	memset(dl, 0, sizeof(*dl)); +	dl->entity = entity; +	lapd_dl_init(&dl->dl, 1, 8, 200); +	dl->dl.reestablish = 0; /* GSM uses no reestablish */ +	dl->dl.send_ph_data_req = lapdm_send_ph_data_req; +	dl->dl.send_dlsap = send_rslms_dlsap; +	dl->dl.n200_est_rel = N200_EST_REL; +	dl->dl.n200 = N200; +	dl->dl.t203_sec = 0; dl->dl.t203_usec = 0; +	dl->dl.t200_sec = t200; dl->dl.t200_usec = 0; +} + +/*! \brief initialize a LAPDm entity and all datalinks inside + *  \param[in] le LAPDm entity + *  \param[in] mode \ref lapdm_mode (BTS/MS) + */ +void lapdm_entity_init(struct lapdm_entity *le, enum lapdm_mode mode, int t200) +{ +	unsigned int i; + +	for (i = 0; i < ARRAY_SIZE(le->datalink); i++) +		lapdm_dl_init(&le->datalink[i], le, t200); + +	lapdm_entity_set_mode(le, mode); +} + +/*! \brief initialize a LAPDm channel and all its channels + *  \param[in] lc \ref lapdm_channel to be initialized + *  \param[in] mode \ref lapdm_mode (BTS/MS) + * + * This really is a convenience wrapper around calling \ref + * lapdm_entity_init twice. + */ +void lapdm_channel_init(struct lapdm_channel *lc, enum lapdm_mode mode) +{ +	lapdm_entity_init(&lc->lapdm_acch, mode, 2); +	/* FIXME: this depends on chan type */ +	lapdm_entity_init(&lc->lapdm_dcch, mode, 1); +} + +/*! \brief flush and release all resoures in LAPDm entity */ +void lapdm_entity_exit(struct lapdm_entity *le) +{ +	unsigned int i; +	struct lapdm_datalink *dl; + +	for (i = 0; i < ARRAY_SIZE(le->datalink); i++) { +		dl = &le->datalink[i]; +		lapd_dl_exit(&dl->dl); +	} +} + +/* \brief lfush and release all resources in LAPDm channel + * + * A convenience wrapper calling \ref lapdm_entity_exit on both + * entities inside the \ref lapdm_channel + */ +void lapdm_channel_exit(struct lapdm_channel *lc) +{ +	lapdm_entity_exit(&lc->lapdm_acch); +	lapdm_entity_exit(&lc->lapdm_dcch); +} + +static struct lapdm_datalink *datalink_for_sapi(struct lapdm_entity *le, uint8_t sapi) +{ +	switch (sapi) { +	case LAPDm_SAPI_NORMAL: +		return &le->datalink[0]; +	case LAPDm_SAPI_SMS: +		return &le->datalink[1]; +	default: +		return NULL; +	} +} + +/* remove the L2 header from a MSGB */ +static inline unsigned char *msgb_pull_l2h(struct msgb *msg) +{ +	unsigned char *ret = msgb_pull(msg, msg->l3h - msg->l2h); +	msg->l2h = NULL; +	return ret; +} + +/* Append padding (if required) */ +static void lapdm_pad_msgb(struct msgb *msg, uint8_t n201) +{ +	int pad_len = n201 - msgb_l2len(msg); +	uint8_t *data; + +	if (pad_len < 0) { +		LOGP(DLLAPD, LOGL_ERROR, +		     "cannot pad message that is already too big!\n"); +		return; +	} + +	data = msgb_put(msg, pad_len); +	memset(data, 0x2B, pad_len); +} + +/* input function that L2 calls when sending messages up to L3 */ +static int rslms_sendmsg(struct msgb *msg, struct lapdm_entity *le) +{ +	if (!le->l3_cb) { +		msgb_free(msg); +		return -EIO; +	} + +	/* call the layer2 message handler that is registered */ +	return le->l3_cb(msg, le, le->l3_ctx); +} + +/* write a frame into the tx queue */ +static int tx_ph_data_enqueue(struct lapdm_datalink *dl, struct msgb *msg, +				uint8_t chan_nr, uint8_t link_id, uint8_t pad) +{ +	struct lapdm_entity *le = dl->entity; +	struct osmo_phsap_prim pp; + +	/* if there is a pending message, queue it */ +	if (le->tx_pending || le->flags & LAPDM_ENT_F_POLLING_ONLY) { +		*msgb_push(msg, 1) = pad; +		*msgb_push(msg, 1) = link_id; +		*msgb_push(msg, 1) = chan_nr; +		msgb_enqueue(&dl->dl.tx_queue, msg); +		return -EBUSY; +	} + +	osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA, +			PRIM_OP_REQUEST, msg); +	pp.u.data.chan_nr = chan_nr; +	pp.u.data.link_id = link_id; + +	/* send the frame now */ +	le->tx_pending = 0; /* disabled flow control */ +	lapdm_pad_msgb(msg, pad); + +	return le->l1_prim_cb(&pp.oph, le->l1_ctx); +} + +static struct msgb *tx_dequeue_msgb(struct lapdm_entity *le) +{ +	struct lapdm_datalink *dl; +	int last = le->last_tx_dequeue; +	int i = last, n = ARRAY_SIZE(le->datalink); +	struct msgb *msg = NULL; + +	/* round-robin dequeue */ +	do { +		/* next */ +		i = (i + 1) % n; +		dl = &le->datalink[i]; +		if ((msg = msgb_dequeue(&dl->dl.tx_queue))) +			break; +	} while (i != last); + +	if (msg) { +		/* Set last dequeue position */ +		le->last_tx_dequeue = i; +	} + +	return msg; +} + +/*! \brief dequeue a msg that's pending transmission via L1 and wrap it into + * a osmo_phsap_prim */ +int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp) +{ +	struct msgb *msg; +	uint8_t pad; + +	msg = tx_dequeue_msgb(le); +	if (!msg) +		return -ENODEV; + +	/* if we have a message, send PH-DATA.req */ +	osmo_prim_init(&pp->oph, SAP_GSM_PH, PRIM_PH_DATA, +			PRIM_OP_REQUEST, msg); + +	/* Pull chan_nr and link_id */ +	pp->u.data.chan_nr = *msg->data; +	msgb_pull(msg, 1); +	pp->u.data.link_id = *msg->data; +	msgb_pull(msg, 1); +	pad = *msg->data; +	msgb_pull(msg, 1); + +	/* Pad the frame, we can transmit now */ +	lapdm_pad_msgb(msg, pad); + +	return 0; +} + +/* get next frame from the tx queue. because the ms has multiple datalinks, + * each datalink's queue is read round-robin. + */ +static int l2_ph_data_conf(struct msgb *msg, struct lapdm_entity *le) +{ +	struct osmo_phsap_prim pp; + +	/* we may send again */ +	le->tx_pending = 0; + +	/* free confirm message */ +	if (msg) +		msgb_free(msg); + +	if (lapdm_phsap_dequeue_prim(le, &pp) < 0) { +		/* no message in all queues */ + +		/* If user didn't request PH-EMPTY_FRAME.req, abort */ +		if (!(le->flags & LAPDM_ENT_F_EMPTY_FRAME)) +			return 0; + +		/* otherwise, send PH-EMPTY_FRAME.req */ +		osmo_prim_init(&pp.oph, SAP_GSM_PH, +				PRIM_PH_EMPTY_FRAME, +				PRIM_OP_REQUEST, NULL); +	} else { +		le->tx_pending = 1; +	} + +	return le->l1_prim_cb(&pp.oph, le->l1_ctx); +} + +/* Create RSLms various RSLms messages */ +static int send_rslms_rll_l3(uint8_t msg_type, struct lapdm_msg_ctx *mctx, +			     struct msgb *msg) +{ +	/* Add the RSL + RLL header */ +	rsl_rll_push_l3(msg, msg_type, mctx->chan_nr, mctx->link_id, 1); + +	/* send off the RSLms message to L3 */ +	return rslms_sendmsg(msg, mctx->dl->entity); +} + +/* Take a B4 format message from L1 and create RSLms UNIT DATA IND */ +static int send_rslms_rll_l3_ui(struct lapdm_msg_ctx *mctx, struct msgb *msg) +{ +	uint8_t l3_len = msg->tail - (uint8_t *)msgb_l3(msg); +	struct abis_rsl_rll_hdr *rllh; + +	/* Add the RSL + RLL header */ +	msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len); +	msgb_push(msg, 2 + 2); +	rsl_rll_push_hdr(msg, RSL_MT_UNIT_DATA_IND, mctx->chan_nr, +		mctx->link_id, 1); +	rllh = (struct abis_rsl_rll_hdr *)msgb_l2(msg); + +	rllh->data[0] = RSL_IE_TIMING_ADVANCE; +	rllh->data[1] = mctx->ta_ind; + +	rllh->data[2] = RSL_IE_MS_POWER; +	rllh->data[3] = mctx->tx_power_ind; +	 +	return rslms_sendmsg(msg, mctx->dl->entity); +} + +static int send_rll_simple(uint8_t msg_type, struct lapdm_msg_ctx *mctx) +{ +	struct msgb *msg; + +	msg = rsl_rll_simple(msg_type, mctx->chan_nr, mctx->link_id, 1); + +	/* send off the RSLms message to L3 */ +	return rslms_sendmsg(msg, mctx->dl->entity); +} + +static int rsl_rll_error(uint8_t cause, struct lapdm_msg_ctx *mctx) +{ +	struct msgb *msg; + +	LOGP(DLLAPD, LOGL_NOTICE, "sending MDL-ERROR-IND %d\n", cause); +	msg = rsl_rll_simple(RSL_MT_ERROR_IND, mctx->chan_nr, mctx->link_id, 1); +	msgb_tlv_put(msg, RSL_IE_RLM_CAUSE, 1, &cause); +	return rslms_sendmsg(msg, mctx->dl->entity); +} + +/* DLSAP L2 -> L3 (RSLms) */ +static int send_rslms_dlsap(struct osmo_dlsap_prim *dp, +	struct lapd_msg_ctx *lctx) +{ +	struct lapd_datalink *dl = lctx->dl; +	struct lapdm_datalink *mdl = +		container_of(dl, struct lapdm_datalink, dl); +	struct lapdm_msg_ctx *mctx = &mdl->mctx; +	uint8_t rll_msg = 0; + +	switch (OSMO_PRIM_HDR(&dp->oph)) { +	case OSMO_PRIM(PRIM_DL_EST, PRIM_OP_INDICATION): +		rll_msg = RSL_MT_EST_IND; +		break; +	case OSMO_PRIM(PRIM_DL_EST, PRIM_OP_CONFIRM): +		rll_msg = RSL_MT_EST_CONF; +		break; +	case OSMO_PRIM(PRIM_DL_DATA, PRIM_OP_INDICATION): +		rll_msg = RSL_MT_DATA_IND; +		break; +	case OSMO_PRIM(PRIM_DL_UNIT_DATA, PRIM_OP_INDICATION): +		return send_rslms_rll_l3_ui(mctx, dp->oph.msg); +	case OSMO_PRIM(PRIM_DL_REL, PRIM_OP_INDICATION): +		rll_msg = RSL_MT_REL_IND; +		break; +	case OSMO_PRIM(PRIM_DL_REL, PRIM_OP_CONFIRM): +		rll_msg = RSL_MT_REL_CONF; +		break; +	case OSMO_PRIM(PRIM_DL_SUSP, PRIM_OP_CONFIRM): +		rll_msg = RSL_MT_SUSP_CONF; +		break; +	case OSMO_PRIM(PRIM_MDL_ERROR, PRIM_OP_INDICATION): +		rsl_rll_error(dp->u.error_ind.cause, mctx); +		if (dp->oph.msg) +			msgb_free(dp->oph.msg); +		return 0; +	} + +	if (!rll_msg) { +		LOGP(DLLAPD, LOGL_ERROR, "Unsupported op %d, prim %d. Please " +			"fix!\n", dp->oph.primitive, dp->oph.operation); +		return -EINVAL; +	} + +	if (!dp->oph.msg) +		return send_rll_simple(rll_msg, mctx); + +	return send_rslms_rll_l3(rll_msg, mctx, dp->oph.msg); +} + +/* send a data frame to layer 1 */ +static int lapdm_send_ph_data_req(struct lapd_msg_ctx *lctx, struct msgb *msg) +{ +	uint8_t l3_len = msg->tail - msg->data; +	struct lapd_datalink *dl = lctx->dl; +	struct lapdm_datalink *mdl = +		container_of(dl, struct lapdm_datalink, dl); +	struct lapdm_msg_ctx *mctx = &mdl->mctx; +	int format = lctx->format; + +	/* prepend l2 header */ +	msg->l2h = msgb_push(msg, 3); +	msg->l2h[0] = LAPDm_ADDR(lctx->lpd, lctx->sapi, lctx->cr); +					/* EA is set here too */ +	switch (format) { +	case LAPD_FORM_I: +		msg->l2h[1] = LAPDm_CTRL_I(lctx->n_recv, lctx->n_send, +						lctx->p_f); +		break; +	case LAPD_FORM_S: +		msg->l2h[1] = LAPDm_CTRL_S(lctx->n_recv, lctx->s_u, lctx->p_f); +		break; +	case LAPD_FORM_U: +		msg->l2h[1] = LAPDm_CTRL_U(lctx->s_u, lctx->p_f); +		break; +	default: +		msgb_free(msg); +		return -EINVAL; +	} +	msg->l2h[2] = LAPDm_LEN(l3_len); /* EL is set here too */ +	if (lctx->more) +		msg->l2h[2] |= LAPDm_MORE; + +	/* add ACCH header with last indicated tx-power and TA */ +	if ((mctx->link_id & 0x40)) { +		struct lapdm_entity *le = mdl->entity; + +		msg->l2h = msgb_push(msg, 2); +		msg->l2h[0] = le->tx_power; +		msg->l2h[1] = le->ta; +	} + +	return tx_ph_data_enqueue(mctx->dl, msg, mctx->chan_nr, mctx->link_id, +			23); +} + +/* input into layer2 (from layer 1) */ +static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le, +	uint8_t chan_nr, uint8_t link_id) +{ +	uint8_t cbits = chan_nr >> 3; +	uint8_t sapi; /* we cannot take SAPI from link_id, as L1 has no clue */ +	struct lapdm_msg_ctx mctx; +	struct lapd_msg_ctx lctx; +	int rc = 0; +	int n201; + +	/* when we reach here, we have a msgb with l2h pointing to the raw +	 * 23byte mac block. The l1h has already been purged. */ + +	memset(&mctx, 0, sizeof(mctx)); +	mctx.chan_nr = chan_nr; +	mctx.link_id = link_id; + +	/* check for L1 chan_nr/link_id and determine LAPDm hdr format */ +	if (cbits == 0x10 || cbits == 0x12) { +		/* Format Bbis is used on BCCH and CCCH(PCH, NCH and AGCH) */ +		mctx.lapdm_fmt = LAPDm_FMT_Bbis; +		n201 = N201_Bbis; +		sapi = 0; +	} else { +		if (mctx.link_id & 0x40) { +			/* It was received from network on SACCH */ + +			/* If UI on SACCH sent by BTS, lapdm_fmt must be B4 */ +			if (le->mode == LAPDM_MODE_MS +			 && LAPDm_CTRL_is_U(msg->l2h[3]) +			 && LAPDm_CTRL_U_BITS(msg->l2h[3]) == 0) { +				mctx.lapdm_fmt = LAPDm_FMT_B4; +				n201 = N201_B4; +				LOGP(DLLAPD, LOGL_INFO, "fmt=B4\n"); +			} else { +				mctx.lapdm_fmt = LAPDm_FMT_B; +				n201 = N201_AB_SACCH; +				LOGP(DLLAPD, LOGL_INFO, "fmt=B\n"); +			} +			/* SACCH frames have a two-byte L1 header that +			 * OsmocomBB L1 doesn't strip */ +			mctx.tx_power_ind = msg->l2h[0] & 0x1f; +			mctx.ta_ind = msg->l2h[1]; +			msgb_pull(msg, 2); +			msg->l2h += 2; +			sapi = (msg->l2h[0] >> 2) & 7; +		} else { +			mctx.lapdm_fmt = LAPDm_FMT_B; +			LOGP(DLLAPD, LOGL_INFO, "fmt=B\n"); +			n201 = N201_AB_SDCCH; +			sapi = (msg->l2h[0] >> 2) & 7; +		} +	} + +	mctx.dl = datalink_for_sapi(le, sapi); +	/* G.2.1 No action on frames containing an unallocated SAPI. */ +	if (!mctx.dl) { +		LOGP(DLLAPD, LOGL_NOTICE, "Received frame for unsupported " +			"SAPI %d!\n", sapi); +		msgb_free(msg); +		return -EIO; +	} + +	switch (mctx.lapdm_fmt) { +	case LAPDm_FMT_A: +	case LAPDm_FMT_B: +	case LAPDm_FMT_B4: +		lctx.dl = &mctx.dl->dl; +		/* obtain SAPI from address field */ +		mctx.link_id |= LAPDm_ADDR_SAPI(msg->l2h[0]); +		/* G.2.3 EA bit set to "0" is not allowed in GSM */ +		if (!LAPDm_ADDR_EA(msg->l2h[0])) { +			LOGP(DLLAPD, LOGL_NOTICE, "EA bit 0 is not allowed in " +				"GSM\n"); +			msgb_free(msg); +			rsl_rll_error(RLL_CAUSE_FRM_UNIMPL, &mctx); +			return -EINVAL; +		} +		/* adress field */ +		lctx.lpd = LAPDm_ADDR_LPD(msg->l2h[0]); +		lctx.sapi = LAPDm_ADDR_SAPI(msg->l2h[0]); +		lctx.cr = LAPDm_ADDR_CR(msg->l2h[0]); +		/* command field */ +		if (LAPDm_CTRL_is_I(msg->l2h[1])) { +			lctx.format = LAPD_FORM_I; +			lctx.n_send = LAPDm_CTRL_I_Ns(msg->l2h[1]); +			lctx.n_recv = LAPDm_CTRL_Nr(msg->l2h[1]); +		} else if (LAPDm_CTRL_is_S(msg->l2h[1])) { +			lctx.format = LAPD_FORM_S; +			lctx.n_recv = LAPDm_CTRL_Nr(msg->l2h[1]); +			lctx.s_u = LAPDm_CTRL_S_BITS(msg->l2h[1]); +		} else if (LAPDm_CTRL_is_U(msg->l2h[1])) { +			lctx.format = LAPD_FORM_U; +			lctx.s_u = LAPDm_CTRL_U_BITS(msg->l2h[1]); +		} else +			lctx.format = LAPD_FORM_UKN; +		lctx.p_f = LAPDm_CTRL_PF_BIT(msg->l2h[1]); +		if (lctx.sapi != LAPDm_SAPI_NORMAL +		 && lctx.sapi != LAPDm_SAPI_SMS +		 && lctx.format == LAPD_FORM_U +		 && lctx.s_u == LAPDm_U_UI) { +			/* 5.3.3 UI frames with invalid SAPI values shall be +			 * discarded +			 */ +			LOGP(DLLAPD, LOGL_INFO, "sapi=%u (discarding)\n", +				lctx.sapi); +			msgb_free(msg); +			return 0; +		} +		if (mctx.lapdm_fmt == LAPDm_FMT_B4) { +			lctx.n201 = n201; +			lctx.length = n201; +			lctx.more = 0; +			msg->l3h = msg->l2h + 2; +			msgb_pull_l2h(msg); +		} else { +			/* length field */ +			if (!(msg->l2h[2] & LAPDm_EL)) { +				/* G.4.1 If the EL bit is set to "0", an +				 * MDL-ERROR-INDICATION primitive with cause +				 * "frame not implemented" is sent to the +				 * mobile management entity. */ +				LOGP(DLLAPD, LOGL_NOTICE, "we don't support " +					"multi-octet length\n"); +				msgb_free(msg); +				rsl_rll_error(RLL_CAUSE_FRM_UNIMPL, &mctx); +				return -EINVAL; +			} +			lctx.n201 = n201; +			lctx.length = msg->l2h[2] >> 2; +			lctx.more = !!(msg->l2h[2] & LAPDm_MORE); +			msg->l3h = msg->l2h + 3; +			msgb_pull_l2h(msg); +		} +		/* store context for messages from lapd */ +		memcpy(&mctx.dl->mctx, &mctx, sizeof(mctx.dl->mctx)); +		/* send to LAPD */ +		rc = lapd_ph_data_ind(msg, &lctx); +		break; +	case LAPDm_FMT_Bter: +		/* FIXME */ +		msgb_free(msg); +		break; +	case LAPDm_FMT_Bbis: +		/* directly pass up to layer3 */ +		LOGP(DLLAPD, LOGL_INFO, "fmt=Bbis UI\n"); +		msg->l3h = msg->l2h; +		msgb_pull_l2h(msg); +		rc = send_rslms_rll_l3(RSL_MT_UNIT_DATA_IND, &mctx, msg); +		break; +	default: +		msgb_free(msg); +	} + +	return rc; +} + +/* input into layer2 (from layer 1) */ +static int l2_ph_rach_ind(struct lapdm_entity *le, uint8_t ra, uint32_t fn, uint8_t acc_delay) +{ +	struct abis_rsl_cchan_hdr *ch; +	struct gsm48_req_ref req_ref; +	struct gsm_time gt; +	struct msgb *msg = msgb_alloc_headroom(512, 64, "RSL CHAN RQD"); + +	msg->l2h = msgb_push(msg, sizeof(*ch)); +	ch = (struct abis_rsl_cchan_hdr *)msg->l2h; +	rsl_init_cchan_hdr(ch, RSL_MT_CHAN_RQD); +	ch->chan_nr = RSL_CHAN_RACH; + +	/* generate a RSL CHANNEL REQUIRED message */ +	gsm_fn2gsmtime(>, fn); +	req_ref.ra = ra; +	req_ref.t1 = gt.t1; /* FIXME: modulo? */ +	req_ref.t2 = gt.t2; +	req_ref.t3_low = gt.t3 & 7; +	req_ref.t3_high = gt.t3 >> 3; + +	msgb_tv_fixed_put(msg, RSL_IE_REQ_REFERENCE, 3, (uint8_t *) &req_ref); +	msgb_tv_put(msg, RSL_IE_ACCESS_DELAY, acc_delay); + +	return rslms_sendmsg(msg, le); +} + +static int l2_ph_chan_conf(struct msgb *msg, struct lapdm_entity *le, uint32_t frame_nr); + +/*! \brief Receive a PH-SAP primitive from L1 */ +int lapdm_phsap_up(struct osmo_prim_hdr *oph, struct lapdm_entity *le) +{ +	struct osmo_phsap_prim *pp = (struct osmo_phsap_prim *) oph; +	int rc = 0; + +	if (oph->sap != SAP_GSM_PH) { +		LOGP(DLLAPD, LOGL_ERROR, "primitive for unknown SAP %u\n", +			oph->sap); +		return -ENODEV; +	} + +	switch (oph->primitive) { +	case PRIM_PH_DATA: +		if (oph->operation != PRIM_OP_INDICATION) { +			LOGP(DLLAPD, LOGL_ERROR, "PH_DATA is not INDICATION %u\n", +				oph->operation); +			return -ENODEV; +		} +		rc = l2_ph_data_ind(oph->msg, le, pp->u.data.chan_nr, +				    pp->u.data.link_id); +		break; +	case PRIM_PH_RTS: +		if (oph->operation != PRIM_OP_INDICATION) { +			LOGP(DLLAPD, LOGL_ERROR, "PH_RTS is not INDICATION %u\n", +				oph->operation); +			return -ENODEV; +		} +		rc = l2_ph_data_conf(oph->msg, le); +		break; +	case PRIM_PH_RACH: +		switch (oph->operation) { +		case PRIM_OP_INDICATION: +			rc = l2_ph_rach_ind(le, pp->u.rach_ind.ra, pp->u.rach_ind.fn, +					    pp->u.rach_ind.acc_delay); +			break; +		case PRIM_OP_CONFIRM: +			rc = l2_ph_chan_conf(oph->msg, le, pp->u.rach_ind.fn); +			break; +		default: +			return -EIO; +		} +		break; +	} + +	return rc; +} + + +/* L3 -> L2 / RSLMS -> LAPDm */ + +/* Set LAPDm context for established connection */ +static int set_lapdm_context(struct lapdm_datalink *dl, uint8_t chan_nr, +	uint8_t link_id, int n201, uint8_t sapi) +{ +	memset(&dl->mctx, 0, sizeof(dl->mctx)); +	dl->mctx.dl = dl; +	dl->mctx.chan_nr = chan_nr; +	dl->mctx.link_id = link_id; +	dl->dl.lctx.dl = &dl->dl; +	dl->dl.lctx.n201 = n201; +	dl->dl.lctx.sapi = sapi; + +	return 0; +} + +/* L3 requests establishment of data link */ +static int rslms_rx_rll_est_req(struct msgb *msg, struct lapdm_datalink *dl) +{ +	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); +	uint8_t chan_nr = rllh->chan_nr; +	uint8_t link_id = rllh->link_id; +	uint8_t sapi = rllh->link_id & 7; +	struct tlv_parsed tv; +	uint8_t length; +	uint8_t n201 = (rllh->link_id & 0x40) ? N201_AB_SACCH : N201_AB_SDCCH; +	struct osmo_dlsap_prim dp; + +	/* Set LAPDm context for established connection */ +	set_lapdm_context(dl, chan_nr, link_id, n201, sapi); + +	rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg) - sizeof(*rllh)); +	if (TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { +		msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); +		/* contention resolution establishment procedure */ +		if (sapi != 0) { +			/* According to clause 6, the contention resolution +			 * procedure is only permitted with SAPI value 0 */ +			LOGP(DLLAPD, LOGL_ERROR, "SAPI != 0 but contention" +				"resolution (discarding)\n"); +			msgb_free(msg); +			return send_rll_simple(RSL_MT_REL_IND, &dl->mctx); +		} +		/* transmit a SABM command with the P bit set to "1". The SABM +		 * command shall contain the layer 3 message unit */ +		length = TLVP_LEN(&tv, RSL_IE_L3_INFO); +	} else { +		/* normal establishment procedure */ +		msg->l3h = msg->l2h + sizeof(*rllh); +		length = 0; +	} + +	/* check if the layer3 message length exceeds N201 */ +	if (length > n201) { +		LOGP(DLLAPD, LOGL_ERROR, "frame too large: %d > N201(%d) " +			"(discarding)\n", length, n201); +		msgb_free(msg); +		return send_rll_simple(RSL_MT_REL_IND, &dl->mctx); +	} + +	/* Remove RLL header from msgb and set length to L3-info */ +	msgb_pull_l2h(msg); +	msg->len = length; +	msg->tail = msg->l3h + length; + +	/* prepare prim */ +	osmo_prim_init(&dp.oph, 0, PRIM_DL_EST, PRIM_OP_REQUEST, msg); + +	/* send to L2 */ +	return lapd_recv_dlsap(&dp, &dl->dl.lctx); +} + +/* L3 requests transfer of unnumbered information */ +static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl) +{ +	struct lapdm_entity *le = dl->entity; +	int ui_bts = (le->mode == LAPDM_MODE_BTS); +	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); +	uint8_t chan_nr = rllh->chan_nr; +	uint8_t link_id = rllh->link_id; +	uint8_t sapi = link_id & 7; +	struct tlv_parsed tv; +	int length; + +	/* check if the layer3 message length exceeds N201 */ + +	rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); + +	if (TLVP_PRESENT(&tv, RSL_IE_TIMING_ADVANCE)) { +		le->ta = *TLVP_VAL(&tv, RSL_IE_TIMING_ADVANCE); +	} +	if (TLVP_PRESENT(&tv, RSL_IE_MS_POWER)) { +		le->tx_power = *TLVP_VAL(&tv, RSL_IE_MS_POWER); +	} +	if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { +		LOGP(DLLAPD, LOGL_ERROR, "unit data request without message " +			"error\n"); +		msgb_free(msg); +		return -EINVAL; +	} +	msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); +	length = TLVP_LEN(&tv, RSL_IE_L3_INFO); +	/* check if the layer3 message length exceeds N201 */ +	if (length + 4 + !ui_bts > 23) { +		LOGP(DLLAPD, LOGL_ERROR, "frame too large: %d > N201(%d) " +			"(discarding)\n", length, 18 + ui_bts); +		msgb_free(msg); +		return -EIO; +	} + +	LOGP(DLLAPD, LOGL_INFO, "sending unit data (tx_power=%d, ta=%d)\n", +		le->tx_power, le->ta); + +	/* Remove RLL header from msgb and set length to L3-info */ +	msgb_pull_l2h(msg); +	msg->len = length; +	msg->tail = msg->l3h + length; + +	/* Push L1 + LAPDm header on msgb */ +	msg->l2h = msgb_push(msg, 4 + !ui_bts); +	msg->l2h[0] = le->tx_power; +	msg->l2h[1] = le->ta; +	msg->l2h[2] = LAPDm_ADDR(LAPDm_LPD_NORMAL, sapi, dl->dl.cr.loc2rem.cmd); +	msg->l2h[3] = LAPDm_CTRL_U(LAPDm_U_UI, 0); +	if (!ui_bts) +		msg->l2h[4] = LAPDm_LEN(length); + +	/* Tramsmit */ +	return tx_ph_data_enqueue(dl, msg, chan_nr, link_id, 23); +} + +/* L3 requests transfer of acknowledged information */ +static int rslms_rx_rll_data_req(struct msgb *msg, struct lapdm_datalink *dl) +{ +	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); +	struct tlv_parsed tv; +	int length; +	struct osmo_dlsap_prim dp; + +	rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); +	if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { +		LOGP(DLLAPD, LOGL_ERROR, "data request without message " +			"error\n"); +		msgb_free(msg); +		return -EINVAL; +	} +	msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); +	length = TLVP_LEN(&tv, RSL_IE_L3_INFO); + +	/* Remove RLL header from msgb and set length to L3-info */ +	msgb_pull_l2h(msg); +	msg->len = length; +	msg->tail = msg->l3h + length; + +	/* prepare prim */ +	osmo_prim_init(&dp.oph, 0, PRIM_DL_DATA, PRIM_OP_REQUEST, msg); + +	/* send to L2 */ +	return lapd_recv_dlsap(&dp, &dl->dl.lctx); +} + +/* L3 requests suspension of data link */ +static int rslms_rx_rll_susp_req(struct msgb *msg, struct lapdm_datalink *dl) +{ +	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); +	uint8_t sapi = rllh->link_id & 7; +	struct osmo_dlsap_prim dp; + +	if (sapi != 0) { +		LOGP(DLLAPD, LOGL_ERROR, "SAPI != 0 while suspending\n"); +		msgb_free(msg); +		return -EINVAL; +	} + +	/* prepare prim */ +	osmo_prim_init(&dp.oph, 0, PRIM_DL_SUSP, PRIM_OP_REQUEST, msg); + +	/* send to L2 */ +	return lapd_recv_dlsap(&dp, &dl->dl.lctx); +} + +/* L3 requests resume of data link */ +static int rslms_rx_rll_res_req(struct msgb *msg, struct lapdm_datalink *dl) +{ +	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); +	int msg_type = rllh->c.msg_type; +	uint8_t chan_nr = rllh->chan_nr; +	uint8_t link_id = rllh->link_id; +	uint8_t sapi = rllh->link_id & 7; +	struct tlv_parsed tv; +	uint8_t length; +	uint8_t n201 = (rllh->link_id & 0x40) ? N201_AB_SACCH : N201_AB_SDCCH; +	struct osmo_dlsap_prim dp; + +	/* Set LAPDm context for established connection */ +	set_lapdm_context(dl, chan_nr, link_id, n201, sapi); + +	rsl_tlv_parse(&tv, rllh->data, msgb_l2len(msg)-sizeof(*rllh)); +	if (!TLVP_PRESENT(&tv, RSL_IE_L3_INFO)) { +		LOGP(DLLAPD, LOGL_ERROR, "resume without message error\n"); +		msgb_free(msg); +		return send_rll_simple(RSL_MT_REL_IND, &dl->mctx); +	} +	msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); +	length = TLVP_LEN(&tv, RSL_IE_L3_INFO); + +	/* Remove RLL header from msgb and set length to L3-info */ +	msgb_pull_l2h(msg); +	msg->len = length; +	msg->tail = msg->l3h + length; + +	/* prepare prim */ +	osmo_prim_init(&dp.oph, 0, (msg_type == RSL_MT_RES_REQ) ? PRIM_DL_RES +		: PRIM_DL_RECON, PRIM_OP_REQUEST, msg); + +	/* send to L2 */ +	return lapd_recv_dlsap(&dp, &dl->dl.lctx); +} + +/* L3 requests release of data link */ +static int rslms_rx_rll_rel_req(struct msgb *msg, struct lapdm_datalink *dl) +{ +	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); +	uint8_t mode = 0; +	struct osmo_dlsap_prim dp; + +	/* get release mode */ +	if (rllh->data[0] == RSL_IE_RELEASE_MODE) +		mode = rllh->data[1] & 1; + +	/* Pull rllh */ +	msgb_pull_l2h(msg); + +	/* 04.06 3.8.3: No information field is permitted with the DISC +	 * command. */ +	msg->len = 0; +	msg->tail = msg->l3h = msg->data; + +	/* prepare prim */ +	osmo_prim_init(&dp.oph, 0, PRIM_DL_REL, PRIM_OP_REQUEST, msg); +	dp.u.rel_req.mode = mode; + +	/* send to L2 */ +	return lapd_recv_dlsap(&dp, &dl->dl.lctx); +} + +/* L3 requests channel in idle state */ +static int rslms_rx_chan_rqd(struct lapdm_channel *lc, struct msgb *msg) +{ +	struct abis_rsl_cchan_hdr *cch = msgb_l2(msg); +	void *l1ctx = lc->lapdm_dcch.l1_ctx; +	struct osmo_phsap_prim pp; + +	osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_RACH, +			PRIM_OP_REQUEST, NULL); + +	if (msgb_l2len(msg) < sizeof(*cch) + 4 + 2 + 2) { +		LOGP(DLLAPD, LOGL_ERROR, "Message too short for CHAN RQD!\n"); +		return -EINVAL; +	} +	if (cch->data[0] != RSL_IE_REQ_REFERENCE) { +		LOGP(DLLAPD, LOGL_ERROR, "Missing REQ REFERENCE IE\n"); +		return -EINVAL; +	} +	pp.u.rach_req.ra = cch->data[1]; +	pp.u.rach_req.offset = ((cch->data[2] & 0x7f) << 8) | cch->data[3]; +	pp.u.rach_req.is_combined_ccch = cch->data[2] >> 7; + +	if (cch->data[4] != RSL_IE_ACCESS_DELAY) { +		LOGP(DLLAPD, LOGL_ERROR, "Missing ACCESS_DELAY IE\n"); +		return -EINVAL; +	} +	/* TA = 0 - delay */ +	pp.u.rach_req.ta = 0 - cch->data[5]; + +	if (cch->data[6] != RSL_IE_MS_POWER) { +		LOGP(DLLAPD, LOGL_ERROR, "Missing MS POWER IE\n"); +		return -EINVAL; +	} +	pp.u.rach_req.tx_power = cch->data[7]; + +	msgb_free(msg); + +	return lc->lapdm_dcch.l1_prim_cb(&pp.oph, l1ctx); +} + +/* L1 confirms channel request */ +static int l2_ph_chan_conf(struct msgb *msg, struct lapdm_entity *le, uint32_t frame_nr) +{ +	struct abis_rsl_cchan_hdr *ch; +	struct gsm_time tm; +	struct gsm48_req_ref *ref; + +	gsm_fn2gsmtime(&tm, frame_nr); + +	msgb_pull_l2h(msg); +	msg->l2h = msgb_push(msg, sizeof(*ch) + sizeof(*ref)); +	ch = (struct abis_rsl_cchan_hdr *)msg->l2h; +	rsl_init_cchan_hdr(ch, RSL_MT_CHAN_CONF); +	ch->chan_nr = RSL_CHAN_RACH; +	ch->data[0] = RSL_IE_REQ_REFERENCE; +	ref = (struct gsm48_req_ref *) (ch->data + 1); +	ref->t1 = tm.t1; +	ref->t2 = tm.t2; +	ref->t3_low = tm.t3 & 0x7; +	ref->t3_high = tm.t3 >> 3; +	 +	return rslms_sendmsg(msg, le); +} + +/* incoming RSLms RLL message from L3 */ +static int rslms_rx_rll(struct msgb *msg, struct lapdm_channel *lc) +{ +	struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); +	int msg_type = rllh->c.msg_type; +	uint8_t sapi = rllh->link_id & 7; +	struct lapdm_entity *le; +	struct lapdm_datalink *dl; +	int rc = 0; + +	if (msgb_l2len(msg) < sizeof(*rllh)) { +		LOGP(DLLAPD, LOGL_ERROR, "Message too short for RLL hdr!\n"); +		msgb_free(msg); +		return -EINVAL; +	} + +	if (rllh->link_id & 0x40) +		le = &lc->lapdm_acch; +	else +		le = &lc->lapdm_dcch; + +	/* G.2.1 No action schall be taken on frames containing an unallocated +	 * SAPI. +	 */ +	dl = datalink_for_sapi(le, sapi); +	if (!dl) { +		LOGP(DLLAPD, LOGL_ERROR, "No instance for SAPI %d!\n", sapi); +		msgb_free(msg); +		return -EINVAL; +	} + +	LOGP(DLLAPD, LOGL_INFO, "(%p) RLL Message '%s' received. (sapi %d)\n", +		lc->name, rsl_msg_name(msg_type), sapi); + +	switch (msg_type) { +	case RSL_MT_UNIT_DATA_REQ: +		rc = rslms_rx_rll_udata_req(msg, dl); +		break; +	case RSL_MT_EST_REQ: +		rc = rslms_rx_rll_est_req(msg, dl); +		break; +	case RSL_MT_DATA_REQ: +		rc = rslms_rx_rll_data_req(msg, dl); +		break; +	case RSL_MT_SUSP_REQ: +		rc = rslms_rx_rll_susp_req(msg, dl); +		break; +	case RSL_MT_RES_REQ: +		rc = rslms_rx_rll_res_req(msg, dl); +		break; +	case RSL_MT_RECON_REQ: +		rc = rslms_rx_rll_res_req(msg, dl); +		break; +	case RSL_MT_REL_REQ: +		rc = rslms_rx_rll_rel_req(msg, dl); +		break; +	default: +		LOGP(DLLAPD, LOGL_NOTICE, "Message unsupported.\n"); +		msgb_free(msg); +		rc = -EINVAL; +	} + +	return rc; +} + +/* incoming RSLms COMMON CHANNEL message from L3 */ +static int rslms_rx_com_chan(struct msgb *msg, struct lapdm_channel *lc) +{ +	struct abis_rsl_cchan_hdr *cch = msgb_l2(msg); +	int msg_type = cch->c.msg_type; +	int rc = 0; + +	if (msgb_l2len(msg) < sizeof(*cch)) { +		LOGP(DLLAPD, LOGL_ERROR, "Message too short for COM CHAN hdr!\n"); +		return -EINVAL; +	} + +	switch (msg_type) { +	case RSL_MT_CHAN_RQD: +		/* create and send RACH request */ +		rc = rslms_rx_chan_rqd(lc, msg); +		break; +	default: +		LOGP(DLLAPD, LOGL_NOTICE, "Unknown COMMON CHANNEL msg %d!\n", +			msg_type); +		msgb_free(msg); +		return 0; +	} + +	return rc; +} + +/*! \brief Receive a RSLms \ref msgb from Layer 3 */ +int lapdm_rslms_recvmsg(struct msgb *msg, struct lapdm_channel *lc) +{ +	struct abis_rsl_common_hdr *rslh = msgb_l2(msg); +	int rc = 0; + +	if (msgb_l2len(msg) < sizeof(*rslh)) { +		LOGP(DLLAPD, LOGL_ERROR, "Message t | 
