[PATCH 2/7] Add PH-/MPH-/TCH-SAP interface to common part of osmo-bts

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/OpenBSC@lists.osmocom.org/.

Andreas Eversberg jolly at eversberg.eu
Tue Feb 5 10:04:00 UTC 2013


Instead of handling primitives directly at layer 1 specific code,
osmo-bts handles primitives at common code. Therefore a new file
'l1sap.c' is added and integrated.

The l1sap.c file:
- receives PH-DATA indications and forwards them to layer 2.
- checks for RF link loss and notifies BSC.
- receives TCH indications and forwards them via RTP.
- receives PH-RTS indications and sends PH-DATA requests with content
  according to its logical channel.
- receives TCH-RTS indications and sends TCH requests with content
  received via RTP or loopback from TCH indications.
- send MPH-INFO requests to activate, deactivate and modify logical
  channels and handles their confirms.
- receives MPH-INFO indications with measurements from tranceiver.
- forwards received and transmitted PH-DATA to GSMTAP.

Therefore some obsolete bts_model_* calls have been replaced by
bts_model_l1sap_down to send primitves down to layer 1 specific code.
---
 include/osmo-bts/Makefile.am      |    3 +-
 include/osmo-bts/bts.h            |    2 +
 include/osmo-bts/bts_model.h      |   12 +-
 include/osmo-bts/gsm_data.h       |    1 +
 include/osmo-bts/l1sap.h          |   71 +++
 include/osmo-bts/rsl.h            |    2 +-
 include/osmo-bts/vty.h            |    4 +-
 src/common/Makefile.am            |    2 +-
 src/common/bts.c                  |    8 +
 src/common/l1sap.c                |  948 +++++++++++++++++++++++++++++++++++++
 src/common/logging.c              |    4 +-
 src/common/measurement.c          |    5 +
 src/common/pcu_sock.c             |   15 +-
 src/common/rsl.c                  |   29 +-
 src/common/vty.c                  |  131 +++++-
 src/osmo-bts-sysmo/l1_if.c        |    6 +
 src/osmo-bts-sysmo/main.c         |    2 +-
 src/osmo-bts-sysmo/oml.c          |    4 +-
 src/osmo-bts-sysmo/sysmobts_vty.c |   27 -
 tests/stubs.c                     |    3 +
 20 files changed, 1206 insertions(+), 73 deletions(-)
 create mode 100644 include/osmo-bts/l1sap.h
 create mode 100644 src/common/l1sap.c

diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am
index 456d416..d8c0002 100644
--- a/include/osmo-bts/Makefile.am
+++ b/include/osmo-bts/Makefile.am
@@ -1,2 +1,3 @@
 noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h logging.h measurement.h \
-		 oml.h paging.h rsl.h signal.h vty.h amr.h pcu_if.h pcuif_proto.h
+		 oml.h paging.h rsl.h signal.h vty.h amr.h pcu_if.h \
+		 pcuif_proto.h l1sap.h
diff --git a/include/osmo-bts/bts.h b/include/osmo-bts/bts.h
index 583c1eb..6b91c82 100644
--- a/include/osmo-bts/bts.h
+++ b/include/osmo-bts/bts.h
@@ -27,5 +27,7 @@ int lchan_init_lapdm(struct gsm_lchan *lchan);
 
 void load_timer_start(struct gsm_bts *bts);
 
+struct gsm_time *get_time(struct gsm_bts *bts);
+
 #endif /* _BTS_H */
 
diff --git a/include/osmo-bts/bts_model.h b/include/osmo-bts/bts_model.h
index bfa6bd8..3b2855a 100644
--- a/include/osmo-bts/bts_model.h
+++ b/include/osmo-bts/bts_model.h
@@ -12,8 +12,6 @@
 
 int bts_model_init(struct gsm_bts *bts);
 
-struct gsm_time *bts_model_get_time(struct gsm_bts *bts);
-
 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);
@@ -27,20 +25,14 @@ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
 int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo,
 			    void *obj, uint8_t adm_state);
 
-int bts_model_rsl_chan_act(struct gsm_lchan *lchan, struct tlv_parsed *tp);
-int bts_model_rsl_chan_rel(struct gsm_lchan *lchan);
-int bts_model_rsl_deact_sacch(struct gsm_lchan *lchan);
-int bts_model_rsl_mode_modify(struct gsm_lchan *lchan);
-
 int bts_model_trx_deact_rf(struct gsm_bts_trx *trx);
 int bts_model_trx_close(struct gsm_bts_trx *trx);
 
-void bts_model_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl,
-			 unsigned int rtp_pl_len);
-
 int bts_model_vty_init(struct gsm_bts *bts);
 
 void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts);
 void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx);
 
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap);
+
 #endif
diff --git a/include/osmo-bts/gsm_data.h b/include/osmo-bts/gsm_data.h
index c6cd7e4..980f8ef 100644
--- a/include/osmo-bts/gsm_data.h
+++ b/include/osmo-bts/gsm_data.h
@@ -58,6 +58,7 @@ struct gsm_bts_role_bts {
 	struct {
 		uint8_t tc4_ctr;
 	} si;
+	struct gsm_time gsm_time;
 	uint8_t radio_link_timeout;
 
 	/* used by the sysmoBTS to adjust band */
diff --git a/include/osmo-bts/l1sap.h b/include/osmo-bts/l1sap.h
new file mode 100644
index 0000000..a1dc303
--- /dev/null
+++ b/include/osmo-bts/l1sap.h
@@ -0,0 +1,71 @@
+#ifndef L1SAP_H
+#define L1SAP_H
+
+/* timeslot and subslot from chan_nr */
+#define L1SAP_CHAN2TS(chan_nr) (chan_nr & 7)
+#define L1SAP_CHAN2SS_TCHH(chan_nr) ((chan_nr >> 3) & 1)
+#define L1SAP_CHAN2SS_SDCCH4(chan_nr) ((chan_nr >> 3) & 3)
+#define L1SAP_CHAN2SS_SDCCH8(chan_nr) ((chan_nr >> 3) & 7)
+
+/* logical channel from chan_nr + link_id */
+#define L1SAP_IS_LINK_SACCH(link_id) ((link_id & 0xC0) == 0x40)
+#define L1SAP_IS_CHAN_TCHF(chan_nr) ((chan_nr & 0xf8) == 0x08)
+#define L1SAP_IS_CHAN_TCHH(chan_nr) ((chan_nr & 0xf0) == 0x10)
+#define L1SAP_IS_CHAN_SDCCH4(chan_nr) ((chan_nr & 0xe0) == 0x20)
+#define L1SAP_IS_CHAN_SDCCH8(chan_nr) ((chan_nr & 0xc0) == 0x40)
+#define L1SAP_IS_CHAN_BCCH(chan_nr) ((chan_nr & 0xf8) == 0x80)
+#define L1SAP_IS_CHAN_RACH(chan_nr) ((chan_nr & 0xf8) == 0x88)
+#define L1SAP_IS_CHAN_AGCH_PCH(chan_nr) ((chan_nr & 0xf8) == 0x90)
+
+/* rach type from ra */
+#define L1SAP_IS_PACKET_RACH(ra) ((ra & 0xf0) == 0x70)
+
+/* CCCH block from frame number */
+#define L1SAP_FN2CCCHBLOCK(fn) ((fn % 51) / 5 - 1)
+
+/* PTCH layout from frame number */
+#define L1SAP_FN2MACBLOCK(fn) ((fn % 52) / 4)
+#define L1SAP_FN2PTCCHBLOCK(fn) ((fn / 52) & 7)
+#define L1SAP_IS_PTCCH(fn) ((fn % 52) == 12)
+
+/* subslot from any chan_nr */
+static inline uint8_t l1sap_chan2ss(uint8_t chan_nr)
+{
+	if (L1SAP_IS_CHAN_SDCCH8(chan_nr))
+		return L1SAP_CHAN2SS_SDCCH8(chan_nr);
+	if (L1SAP_IS_CHAN_SDCCH4(chan_nr))
+		return L1SAP_CHAN2SS_SDCCH4(chan_nr);
+	if (L1SAP_IS_CHAN_TCHH(chan_nr))
+		return L1SAP_CHAN2SS_TCHH(chan_nr);
+	return 0;
+}
+
+
+/* allocate a msgb containing a osmo_phsap_prim + optional l2 data */
+struct msgb *l1sap_msgb_alloc(unsigned int l2_len);
+
+/* any L1 prim received from bts model */
+int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap);
+
+/* pcu (socket interface) sends us a data request primitive */
+int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn,
+	uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len);
+
+/* call-back function for incoming RTP */
+void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl,
+	unsigned int rtp_pl_len);
+
+/* channel control */
+int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr);
+int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr);
+int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr);
+int l1sap_chan_modify(struct gsm_bts_trx *trx, uint8_t chan_nr);
+
+extern const struct value_string gsmtap_sapi_names[];
+extern struct gsmtap_inst *gsmtap;
+extern uint32_t gsmtap_sapi_mask;
+extern uint8_t gsmtap_sapi_acch;
+
+#define msgb_l1sap_prim(msg) ((struct osmo_phsap_prim *)(msg)->l1h)
+
+#endif /* L1SAP_H */
diff --git a/include/osmo-bts/rsl.h b/include/osmo-bts/rsl.h
index 251cd23..eae7845 100644
--- a/include/osmo-bts/rsl.h
+++ b/include/osmo-bts/rsl.h
@@ -7,7 +7,7 @@ int rsl_tx_chan_rqd(struct gsm_bts_trx *trx, struct gsm_time *gtime,
 		    uint8_t ra, uint8_t acc_delay);
 int rsl_tx_est_ind(struct gsm_lchan *lchan, uint8_t link_id, uint8_t *data, int len);
 
-int rsl_tx_chan_act_ack(struct gsm_lchan *lchan, struct gsm_time *gtime);
+int rsl_tx_chan_act_ack(struct gsm_lchan *lchan);
 int rsl_tx_chan_act_nack(struct gsm_lchan *lchan, uint8_t cause);
 int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause);
 int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan);
diff --git a/include/osmo-bts/vty.h b/include/osmo-bts/vty.h
index 1bc7e71..9b113a8 100644
--- a/include/osmo-bts/vty.h
+++ b/include/osmo-bts/vty.h
@@ -15,8 +15,10 @@ extern struct cmd_element ournode_end_cmd;
 enum node_type bts_vty_go_parent(struct vty *vty);
 int bts_vty_is_config_node(struct vty *vty, int node);
 
-int bts_vty_init(const struct log_info *cat);
+int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat);
 
 extern struct vty_app_info bts_vty_info;
 
+char *osmo_str_tolower(const char *in);
+
 #endif
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index b2f6f8e..1c6ff9e 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -5,4 +5,4 @@ LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS)
 noinst_LIBRARIES = libbts.a
 libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \
 		   rsl.c vty.c paging.c measurement.c amr.c lchan.c \
-		   load_indication.c pcu_sock.c
+		   load_indication.c pcu_sock.c l1sap.c
diff --git a/src/common/bts.c b/src/common/bts.c
index e899ebd..c495fcd 100644
--- a/src/common/bts.c
+++ b/src/common/bts.c
@@ -239,3 +239,11 @@ int bts_supports_cipher(struct gsm_bts_role_bts *bts, int rsl_cipher)
 	sup =  (1 << (rsl_cipher - 2)) & bts->support.ciphers;
 	return sup > 0;
 }
+
+struct gsm_time *get_time(struct gsm_bts *bts)
+{
+	struct gsm_bts_role_bts *btsb = bts->role;
+
+	return &btsb->gsm_time;
+}
+
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
new file mode 100644
index 0000000..ad833ea
--- /dev/null
+++ b/src/common/l1sap.c
@@ -0,0 +1,948 @@
+/* L1 SAP primitives */
+
+/* (C) 2011 by Harald Welte <laforge at gnumonks.org>
+ * (C) 2013 by Andreas Eversberg <jolly at eversberg.eu>
+ *
+ * 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/msgb.h>
+#include <osmocom/gsm/l1sap.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/trau/osmo_ortp.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/bts_model.h>
+
+static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap);
+
+static const uint8_t fill_frame[GSM_MACBLOCK_LEN] = {
+        0x03, 0x03, 0x01, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
+        0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
+        0x2B, 0x2B, 0x2B
+};
+
+/* allocate a msgb containing a osmo_phsap_prim + optional l2 data */
+struct msgb *l1sap_msgb_alloc(unsigned int l2_len)
+{
+	struct msgb *msg = msgb_alloc(sizeof(struct osmo_phsap_prim) + l2_len,
+		"l1sap_prim");
+
+	if (!msg)
+		return NULL;
+
+	msg->l1h = msgb_put(msg, sizeof(struct osmo_phsap_prim));
+
+	return msg;
+}
+
+static int l1sap_tx_ciph_req(struct gsm_bts_trx *trx, uint8_t chan_nr,
+	uint8_t downlink, uint8_t uplink)
+{
+	struct osmo_phsap_prim l1sap_ciph;
+
+	osmo_prim_init(&l1sap_ciph.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+		PRIM_OP_REQUEST, NULL);
+	l1sap_ciph.u.info.type = PRIM_INFO_ACT_CIPH;
+	l1sap_ciph.u.info.u.ciph_req.chan_nr = chan_nr;
+	l1sap_ciph.u.info.u.ciph_req.downlink = downlink;
+	l1sap_ciph.u.info.u.ciph_req.uplink = uplink;
+
+	return l1sap_down(trx, &l1sap_ciph);
+}
+
+
+/* check if the message is a GSM48_MT_RR_CIPH_M_CMD, and if yes, enable
+ * uni-directional de-cryption on the uplink. We need this ugly layering
+ * violation as we have no way of passing down L3 metadata (RSL CIPHERING CMD)
+ * to this point in L1 */
+static int check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan,
+	uint8_t chan_nr)
+{
+
+	/* only do this if we are in the right state */
+	switch (lchan->ciph_state) {
+	case LCHAN_CIPH_NONE:
+	case LCHAN_CIPH_RX_REQ:
+		break;
+	default:
+		return 0;
+	}
+
+	/* First byte (Address Field) of LAPDm header) */
+	if (msg->data[0] != 0x03)
+		return 0;
+	/* First byte (protocol discriminator) of RR */
+	if ((msg->data[3] & 0xF) != GSM48_PDISC_RR)
+		return 0;
+	/* 2nd byte (msg type) of RR */
+	if ((msg->data[4] & 0x3F) != GSM48_MT_RR_CIPH_M_CMD)
+		return 0;
+
+	l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 0, 1);
+
+	return 1;
+}
+
+struct gsmtap_inst *gsmtap = NULL;
+uint32_t gsmtap_sapi_mask = 0;
+uint8_t gsmtap_sapi_acch = 0;
+
+const struct value_string gsmtap_sapi_names[] = {
+	{ GSMTAP_CHANNEL_BCCH,	"BCCH" },
+	{ GSMTAP_CHANNEL_CCCH,	"CCCH" },
+	{ GSMTAP_CHANNEL_RACH,	"RACH" },
+	{ GSMTAP_CHANNEL_AGCH,	"AGCH" },
+	{ GSMTAP_CHANNEL_PCH,	"PCH" },
+	{ GSMTAP_CHANNEL_SDCCH,	"SDCCH" },
+	{ GSMTAP_CHANNEL_TCH_F,	"TCH/F" },
+	{ GSMTAP_CHANNEL_TCH_H,	"TCH/H" },
+	{ GSMTAP_CHANNEL_PACCH,	"PACCH" },
+	{ GSMTAP_CHANNEL_PDCH,	"PDTCH" },
+	{ GSMTAP_CHANNEL_PTCCH,	"PTCCH" },
+	{ GSMTAP_CHANNEL_CBCH51,"CBCH" },
+	{ GSMTAP_CHANNEL_ACCH,  "SACCH" },
+	{ 0, NULL }
+};
+
+static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+	struct msgb *msg = l1sap->oph.msg;
+	uint8_t *data;
+	int len;
+	uint8_t chan_type = 0, tn = 0, ss = 0;
+	uint32_t fn;
+	uint8_t chan_nr, link_id;
+	uint16_t uplink = GSMTAP_ARFCN_F_UPLINK;
+
+	if (!gsmtap)
+		return 0;
+
+	switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+	case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+		uplink = 0;
+		/* fall through */
+	case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION):
+		data = msg->data + sizeof(struct osmo_phsap_prim);
+		len = msg->len - sizeof(struct osmo_phsap_prim);
+		fn = l1sap->u.data.fn;
+		tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr);
+		chan_nr = l1sap->u.data.chan_nr;
+		link_id = l1sap->u.data.link_id;
+		if (L1SAP_IS_CHAN_TCHF(chan_nr)) {
+			if (trx->ts[tn].pchan == GSM_PCHAN_PDCH) {
+				if (L1SAP_IS_PTCCH(fn)) {
+					chan_type = GSMTAP_CHANNEL_PTCCH;
+					ss = L1SAP_FN2PTCCHBLOCK(fn);
+					if (l1sap->oph.primitive
+							== PRIM_OP_INDICATION) {
+						if (data[0] == 7)
+							return -EINVAL;
+						data++;
+						len--;
+					}
+				} else {
+					chan_type = GSMTAP_CHANNEL_PACCH;
+				}
+			} else
+				chan_type = GSMTAP_CHANNEL_TCH_F;
+		} else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+			ss = L1SAP_CHAN2SS_TCHH(chan_nr);
+			chan_type = GSMTAP_CHANNEL_TCH_H;
+		} else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
+			ss = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+			chan_type = GSMTAP_CHANNEL_SDCCH;
+		} else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
+			ss = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+			chan_type = GSMTAP_CHANNEL_SDCCH;
+		} else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+			chan_type = GSMTAP_CHANNEL_BCCH;
+		} else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+#warning Set BS_AG_BLKS_RES
+			/* The sapi depends on DSP configuration, not
+			 * on the actual SYSTEM INFORMATION 3. */
+			if (L1SAP_FN2CCCHBLOCK(fn) >= 1)
+				chan_type = GSMTAP_CHANNEL_PCH;
+			else
+				chan_type = GSMTAP_CHANNEL_AGCH;
+		}
+		if (L1SAP_IS_LINK_SACCH(link_id))
+			chan_type |= GSMTAP_CHANNEL_ACCH;
+		break;
+	case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION):
+		chan_type = GSMTAP_CHANNEL_RACH;
+		fn = l1sap->u.rach_ind.fn;
+		tn = L1SAP_CHAN2TS(l1sap->u.rach_ind.chan_nr);
+		chan_nr = l1sap->u.rach_ind.chan_nr;
+		if (L1SAP_IS_CHAN_TCHH(chan_nr))
+			ss = L1SAP_CHAN2SS_TCHH(chan_nr);
+		else if (L1SAP_IS_CHAN_SDCCH4(chan_nr))
+			ss = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+		else if (L1SAP_IS_CHAN_SDCCH8(chan_nr))
+			ss = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+		data = &l1sap->u.rach_ind.ra;
+		len = 1;
+		break;
+	default:
+		return -ENOTSUP;
+	}
+
+	if (len == 0)
+		return 0;
+	if ((chan_type & GSMTAP_CHANNEL_ACCH)) {
+		if (!gsmtap_sapi_acch)
+			return 0;
+	} else {
+		if (!((1 << (chan_type & 31)) & gsmtap_sapi_mask))
+			return 0;
+	}
+
+	gsmtap_send(gsmtap, trx->arfcn | uplink, tn, chan_type, ss, fn, 0, 0,
+		data, len);
+
+	return 0;
+}
+
+/* time information received from bts model */
+static int l1sap_info_time_ind(struct gsm_bts_trx *trx,
+	struct osmo_phsap_prim *l1sap,
+	struct info_time_ind_param *info_time_ind)
+{
+	struct gsm_bts *bts = trx->bts;
+	struct gsm_bts_role_bts *btsb = bts->role;
+
+	DEBUGP(DL1P, "MPH_INFO time ind %u\n", info_time_ind->fn);
+
+	/* Update our data structures with the current GSM time */
+	gsm_fn2gsmtime(&btsb->gsm_time, info_time_ind->fn);
+
+	/* Update time on PCU interface */
+	pcu_tx_time_ind(info_time_ind->fn);
+
+	/* check if the measurement period of some lchan has ended
+	 * and pre-compute the respective measurement */
+	trx_meas_check_compute(trx, info_time_ind->fn - 1);
+
+	/* increment 'total' for every possible rach */
+	if (bts->c0->ts[0].pchan != GSM_PCHAN_CCCH_SDCCH4
+	 || (info_time_ind->fn % 51) < 27)
+		btsb->load.rach.total++;
+
+	return 0;
+}
+
+/* measurement information received from bts model */
+static int l1sap_info_meas_ind(struct gsm_bts_trx *trx,
+	struct osmo_phsap_prim *l1sap,
+	struct info_meas_ind_param *info_meas_ind)
+{
+	struct bts_ul_meas ulm;
+	struct gsm_lchan *lchan;
+
+	DEBUGP(DL1P, "MPH_INFO meas ind chan_nr=%02x\n",
+		info_meas_ind->chan_nr);
+
+	lchan = &trx->ts[L1SAP_CHAN2TS(info_meas_ind->chan_nr)]
+				.lchan[l1sap_chan2ss(info_meas_ind->chan_nr)];
+
+	/* in the GPRS case we are not interested in measurement
+	 * processing.  The PCU will take care of it */
+	if (lchan->type == GSM_LCHAN_PDTCH)
+		return 0;
+
+	memset(&ulm, 0, sizeof(ulm));
+	ulm.ta_offs_qbits = info_meas_ind->ta_offs_qbits;
+	ulm.ber10k = info_meas_ind->ber10k;
+	ulm.inv_rssi = info_meas_ind->inv_rssi;
+
+	lchan_new_ul_meas(lchan, &ulm);
+
+	return 0;
+}
+
+/* any L1 MPH_INFO indication prim recevied from bts model */
+static int l1sap_mph_info_ind(struct gsm_bts_trx *trx,
+	 struct osmo_phsap_prim *l1sap, struct mph_info_param *info)
+{
+	int rc = 0;
+
+	switch (info->type) {
+	case PRIM_INFO_TIME:
+		rc = l1sap_info_time_ind(trx, l1sap, &info->u.time_ind);
+		break;
+	case PRIM_INFO_MEAS:
+		rc = l1sap_info_meas_ind(trx, l1sap, &info->u.meas_ind);
+		break;
+	default:
+		LOGP(DL1P, LOGL_NOTICE, "unknown MPH_INFO ind type %d\n",
+			info->type);
+		break;
+	}
+
+	return rc;
+}
+
+/* activation confirm received from bts model */
+static int l1sap_info_act_cnf(struct gsm_bts_trx *trx,
+	struct osmo_phsap_prim *l1sap,
+	struct info_act_cnf_param *info_act_cnf)
+{
+	struct gsm_lchan *lchan;
+
+	LOGP(DL1P, LOGL_INFO, "activate confirm chan_nr=%02x trx=%d\n",
+		info_act_cnf->chan_nr, trx->nr);
+
+	lchan = &trx->ts[L1SAP_CHAN2TS(info_act_cnf->chan_nr)]
+				.lchan[l1sap_chan2ss(info_act_cnf->chan_nr)];
+
+	if (info_act_cnf->cause)
+		rsl_tx_chan_act_nack(lchan, info_act_cnf->cause);
+	else
+		rsl_tx_chan_act_ack(lchan);
+
+	return 0;
+}
+
+/* activation confirm received from bts model */
+static int l1sap_info_rel_cnf(struct gsm_bts_trx *trx,
+	struct osmo_phsap_prim *l1sap,
+	struct info_act_cnf_param *info_act_cnf)
+{
+	struct gsm_lchan *lchan;
+
+	LOGP(DL1P, LOGL_INFO, "deactivate confirm chan_nr=%02x trx=%d\n",
+		info_act_cnf->chan_nr, trx->nr);
+
+	lchan = &trx->ts[L1SAP_CHAN2TS(info_act_cnf->chan_nr)]
+				.lchan[l1sap_chan2ss(info_act_cnf->chan_nr)];
+
+	rsl_tx_rf_rel_ack(lchan);
+
+	return 0;
+}
+
+/* any L1 MPH_INFO confirm prim recevied from bts model */
+static int l1sap_mph_info_cnf(struct gsm_bts_trx *trx,
+	 struct osmo_phsap_prim *l1sap, struct mph_info_param *info)
+{
+	int rc = 0;
+
+	switch (info->type) {
+	case PRIM_INFO_ACTIVATE:
+		rc = l1sap_info_act_cnf(trx, l1sap, &info->u.act_cnf);
+		break;
+	case PRIM_INFO_DEACTIVATE:
+		rc = l1sap_info_rel_cnf(trx, l1sap, &info->u.act_cnf);
+		break;
+	default:
+		LOGP(DL1P, LOGL_NOTICE, "unknown MPH_INFO cnf type %d\n",
+			info->type);
+		break;
+	}
+
+	return rc;
+}
+
+/* PH-RTS-IND prim recevied from bts model */
+static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx,
+	 struct osmo_phsap_prim *l1sap, struct ph_data_param *rts_ind)
+{
+	struct msgb *msg = l1sap->oph.msg;
+	struct gsm_time g_time;
+	struct gsm_lchan *lchan;
+	uint8_t chan_nr, link_id;
+	uint8_t tn, ss;
+	uint32_t fn;
+	uint8_t *p, *si;
+	struct lapdm_entity *le;
+	struct osmo_phsap_prim pp;
+	int rc;
+
+	chan_nr = rts_ind->chan_nr;
+	link_id = rts_ind->link_id;
+	fn = rts_ind->fn;
+	tn = L1SAP_CHAN2TS(chan_nr);
+
+	gsm_fn2gsmtime(&g_time, fn);
+
+	DEBUGP(DL1P, "Rx PH-RTS.ind %02u/%02u/%02u chan_nr=%d link_id=%d\n",
+		g_time.t1, g_time.t2, g_time.t3, chan_nr, link_id);
+
+	if (trx->ts[tn].pchan == GSM_PCHAN_PDCH) {
+		if (L1SAP_IS_PTCCH(rts_ind->fn)) {
+			pcu_tx_rts_req(&trx->ts[tn], 1, fn, 1 /* ARFCN */,
+				L1SAP_FN2PTCCHBLOCK(fn));
+
+			return 0;
+		}
+		pcu_tx_rts_req(&trx->ts[tn], 0, fn, 0 /* ARFCN */,
+			L1SAP_FN2MACBLOCK(fn));
+
+		return 0;
+	}
+
+	/* reuse PH-RTS.ind for PH-DATA.req */
+	if (!msg) {
+		LOGP(DL1P, LOGL_FATAL, "RTS without msg to be reused. Please "
+			"fix!\n");
+		abort();
+	}
+	msgb_trim(msg, sizeof(*l1sap));
+	osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST,
+		msg);
+	msg->l2h = msg->l1h + sizeof(*l1sap);
+
+	if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+		p = msgb_put(msg, GSM_MACBLOCK_LEN);
+		/* get them from bts->si_buf[] */
+		si = bts_sysinfo_get(trx->bts, &g_time);
+		if (si)
+			memcpy(p, si, GSM_MACBLOCK_LEN);
+		else
+			memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
+	} else if (!(chan_nr & 0x80)) { /* only TCH/F, TCH/H, SDCCH/4 and SDCCH/8 have C5 bit cleared */
+		if (L1SAP_IS_CHAN_TCHH(chan_nr))
+			ss = L1SAP_CHAN2SS_TCHH(chan_nr); /* TCH/H */
+		else if (L1SAP_IS_CHAN_SDCCH4(chan_nr))
+			ss = L1SAP_CHAN2SS_SDCCH4(chan_nr); /* SDCCH/4 */
+		else if (L1SAP_IS_CHAN_SDCCH8(chan_nr))
+			ss = L1SAP_CHAN2SS_SDCCH8(chan_nr); /* SDCCH/8 */
+		else
+			ss = 0; /* TCH/F */
+		lchan = &trx->ts[tn].lchan[ss];
+		if (L1SAP_IS_LINK_SACCH(link_id)) {
+			p = msgb_put(msg, GSM_MACBLOCK_LEN);
+			/* L1-header, if not set/modified by layer 1 */
+			p[0] = lchan->ms_power;
+			p[1] = lchan->rqd_ta;
+			le = &lchan->lapdm_ch.lapdm_acch;
+		} else
+			le = &lchan->lapdm_ch.lapdm_dcch;
+		rc = lapdm_phsap_dequeue_prim(le, &pp);
+		if (rc < 0) {
+			if (L1SAP_IS_LINK_SACCH(link_id)) {
+				/* No SACCH data from LAPDM pending, send SACCH filling */
+				uint8_t *si = lchan_sacch_get(lchan, &g_time);
+				if (si) {
+					/* The +2 is empty space where the DSP inserts the L1 hdr */
+					memcpy(p + 2, si, GSM_MACBLOCK_LEN - 2);
+				} else
+					memcpy(p + 2, fill_frame, GSM_MACBLOCK_LEN - 2);
+			} else if ((!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_TCHH(chan_nr))
+				|| lchan->rsl_cmode == RSL_CMOD_SPD_SIGN) {
+				/* send fill frame only, if not TCH/x != Signalling, otherwise send empty frame */
+				p = msgb_put(msg, GSM_MACBLOCK_LEN);
+				memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
+			} /* else the message remains empty, so TCH frames are sent */
+		} else {
+			/* The +2 is empty space where the DSP inserts the L1 hdr */
+			if (L1SAP_IS_LINK_SACCH(link_id))
+				memcpy(p + 2, pp.oph.msg->data + 2, GSM_MACBLOCK_LEN - 2);
+			else {
+				p = msgb_put(msg, GSM_MACBLOCK_LEN);
+				memcpy(p, pp.oph.msg->data, GSM_MACBLOCK_LEN);
+				/* check if it is a RR CIPH MODE CMD. if yes, enable RX ciphering */
+				check_for_ciph_cmd(pp.oph.msg, lchan, chan_nr);
+			}
+			msgb_free(pp.oph.msg);
+		}
+	} else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+		p = msgb_put(msg, GSM_MACBLOCK_LEN);
+#warning Set BS_AG_BLKS_RES
+		/* The sapi depends on DSP configuration, not
+		 * on the actual SYSTEM INFORMATION 3. */
+		struct gsm_bts_role_bts *btsb = trx->bts->role;
+		if (L1SAP_FN2CCCHBLOCK(fn) >= 1) {
+			/* PCH */
+			paging_gen_msg(btsb->paging_state, p, &g_time);
+		} else {
+			/* AGCH */
+			/* special queue of messages from IMM ASS CMD */
+			struct msgb *amsg = bts_agch_dequeue(trx->bts);
+			if (!amsg)
+				memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
+			else {
+				memcpy(p, amsg->data, amsg->len);
+				msgb_free(amsg);
+			}
+		}
+	}
+
+	DEBUGP(DL1P, "Tx PH-DATA.req %02u/%02u/%02u chan_nr=%d link_id=%d\n",
+		g_time.t1, g_time.t2, g_time.t3, chan_nr, link_id);
+
+	l1sap_down(trx, l1sap);
+
+	/* don't free, because we forwarded data */
+	return 1;
+}
+
+/* TCH-RTS-IND prim recevied from bts model */
+static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx,
+	struct osmo_phsap_prim *l1sap, struct ph_tch_param *rts_ind)
+{
+	struct msgb *resp_msg;
+	struct osmo_phsap_prim *resp_l1sap, empty_l1sap;
+	struct gsm_time g_time;
+	struct gsm_lchan *lchan;
+	uint8_t chan_nr;
+	uint8_t tn, ss;
+	uint32_t fn;
+
+	chan_nr = rts_ind->chan_nr;
+	fn = rts_ind->fn;
+	tn = L1SAP_CHAN2TS(chan_nr);
+
+	gsm_fn2gsmtime(&g_time, fn);
+
+	DEBUGP(DL1P, "Rx TCH-RTS.ind %02u/%02u/%02u chan_nr=%d\n",
+		g_time.t1, g_time.t2, g_time.t3, chan_nr);
+
+	/* get timeslot and subslot */
+	tn = L1SAP_CHAN2TS(chan_nr);
+	if (L1SAP_IS_CHAN_TCHH(chan_nr))
+		ss = L1SAP_CHAN2SS_TCHH(chan_nr); /* TCH/H */
+	else
+		ss = 0; /* TCH/F */
+	lchan = &trx->ts[tn].lchan[ss];
+
+	if (!lchan->loopback && lchan->abis_ip.rtp_socket) {
+		osmo_rtp_socket_poll(lchan->abis_ip.rtp_socket);
+		/* FIXME: we _assume_ that we never miss TDMA
+		 * frames and that we always get to this point
+		 * for every to-be-transmitted voice frame.  A
+		 * better solution would be to compute
+		 * rx_user_ts based on how many TDMA frames have
+		 * elapsed since the last call */
+		lchan->abis_ip.rtp_socket->rx_user_ts += GSM_RTP_DURATION;
+	}
+	/* get a msgb from the dl_tx_queue */
+	resp_msg = msgb_dequeue(&lchan->dl_tch_queue);
+	if (!resp_msg) {
+		LOGP(DL1P, LOGL_DEBUG, "%s DL TCH Tx queue underrun\n",
+			gsm_lchan_name(lchan));
+		resp_l1sap = &empty_l1sap;
+	} else {
+		resp_msg->l2h = resp_msg->data;
+		msgb_push(resp_msg, sizeof(*resp_l1sap));
+		resp_msg->l1h = resp_msg->data;
+		resp_l1sap = msgb_l1sap_prim(resp_msg);
+	}
+
+	memset(resp_l1sap, 0, sizeof(*resp_l1sap));
+	osmo_prim_init(&resp_l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_REQUEST,
+		resp_msg);
+	resp_l1sap->u.tch.chan_nr = chan_nr;
+	resp_l1sap->u.tch.fn = fn;
+
+	DEBUGP(DL1P, "Tx TCH.req %02u/%02u/%02u chan_nr=%d\n",
+		g_time.t1, g_time.t2, g_time.t3, chan_nr);
+
+	l1sap_down(trx, resp_l1sap);
+
+	return 0;
+}
+
+/* DATA received from bts model */
+static int l1sap_ph_data_ind(struct gsm_bts_trx *trx,
+	 struct osmo_phsap_prim *l1sap, struct ph_data_param *data_ind)
+{
+	struct msgb *msg = l1sap->oph.msg;
+	struct gsm_time g_time;
+	struct gsm_lchan *lchan;
+	struct lapdm_entity *le;
+	uint8_t *data = msg->l2h;
+	int len = msgb_l2len(msg);
+	uint8_t chan_nr, link_id;
+	uint8_t tn, ss;
+	uint32_t fn;
+	int8_t rssi;
+
+	rssi = data_ind->rssi;
+	chan_nr = data_ind->chan_nr;
+	link_id = data_ind->link_id;
+	fn = data_ind->fn;
+	tn = L1SAP_CHAN2TS(chan_nr);
+	ss = l1sap_chan2ss(chan_nr);
+
+	gsm_fn2gsmtime(&g_time, fn);
+
+	DEBUGP(DL1P, "Rx PH-DATA.ind %02u/%02u/%02u chan_nr=%d link_id=%d\n",
+		g_time.t1, g_time.t2, g_time.t3, chan_nr, link_id);
+
+	if (trx->ts[tn].pchan == GSM_PCHAN_PDCH) {
+		if (len == 0)
+			return -EINVAL;
+		if (L1SAP_IS_PTCCH(fn)) {
+			pcu_tx_data_ind(&trx->ts[tn], 1, fn,
+				0 /* ARFCN */, L1SAP_FN2PTCCHBLOCK(fn),
+				data, len, rssi);
+
+			return 0;
+		}
+		/* drop incomplete UL block */
+		if (data[0] != 7)
+			return 0;
+		/* PDTCH / PACCH frame handling */
+		pcu_tx_data_ind(&trx->ts[tn], 0, fn, 0 /* ARFCN */,
+			L1SAP_FN2MACBLOCK(fn), data + 1, len - 1, rssi);
+
+		return 0;
+	}
+
+	lchan = &trx->ts[tn].lchan[ss];
+
+	/* bad frame */
+	if (len == 0) {
+		if (L1SAP_IS_LINK_SACCH(link_id) && lchan->s > 0) {
+			/* count down radio link counter S */
+			lchan->s--;
+			DEBUGP(DMEAS, "counting down radio link counter S=%d\n",
+				lchan->s);
+			if (lchan->s == 0)
+				rsl_tx_conn_fail(lchan,
+					RSL_ERR_RADIO_LINK_FAIL);
+		}
+
+		return -EINVAL;
+	}
+
+	if (L1SAP_IS_LINK_SACCH(link_id)) {
+		struct gsm_bts_role_bts *btsb = trx->bts->role;
+
+		le = &lchan->lapdm_ch.lapdm_acch;
+		/* count up radio link counter S */
+		if (lchan->s < btsb->radio_link_timeout) {
+			lchan->s += 2;
+			if (lchan->s > btsb->radio_link_timeout)
+				lchan->s = btsb->radio_link_timeout;
+			DEBUGP(DMEAS, "counting up radio link counter S=%d\n",
+				lchan->s);
+		}
+		/* save the SACCH L1 header in the lchan struct for RSL MEAS RES */
+		if (len < 2) {
+			LOGP(DL1P, LOGL_NOTICE, "SACCH with size %u<2 !?!\n",
+				len);
+			return -EINVAL;
+		}
+		/* Some brilliant engineer decided that the ordering of
+		 * fields on the Um interface is different from the
+		 * order of fields in RLS. See TS 04.04 (Chapter 7.2)
+		 * vs. TS 08.58 (Chapter 9.3.10). */
+		lchan->meas.l1_info[0] = data[0] << 3;
+		lchan->meas.l1_info[0] |= ((data[0] >> 5) & 1) << 2;
+		lchan->meas.l1_info[1] = data[1];
+		lchan->meas.flags |= LC_UL_M_F_L1_VALID;
+	} else
+		le = &lchan->lapdm_ch.lapdm_dcch;
+
+	/* if this is the first valid message after enabling Rx
+	 * decryption, we have to enable Tx encryption */
+	if (lchan->ciph_state == LCHAN_CIPH_RX_CONF) {
+		/* HACK: check if it's an I frame, in order to
+		 * ignore some still buffered/queued UI frames received
+		 * before decryption was enabled */
+		if (data[0] == 0x01 && (data[1] & 0x01) == 0) {
+			l1sap_tx_ciph_req(trx, chan_nr, 1, 0);
+		}
+	}
+
+	/* SDCCH, SACCH and FACCH all go to LAPDm */
+	msgb_pull(msg, (msg->l2h - msg->data));
+	msg->l1h = NULL;
+	lapdm_phsap_up(&l1sap->oph, le);
+
+	/* don't free, because we forwarded data */
+	return 1;
+}
+
+/* TCH received from bts model */
+static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap,
+	struct ph_tch_param *tch_ind)
+{
+	struct msgb *msg = l1sap->oph.msg;
+	struct gsm_time g_time;
+	struct gsm_lchan *lchan;
+	uint8_t tn, ss, chan_nr;
+	uint32_t fn;
+
+	chan_nr = tch_ind->chan_nr;
+	fn = tch_ind->fn;
+	tn = L1SAP_CHAN2TS(chan_nr);
+	if (L1SAP_IS_CHAN_TCHH(chan_nr))
+		ss = L1SAP_CHAN2SS_TCHH(chan_nr);
+	else
+		ss = 0;
+	lchan = &trx->ts[tn].lchan[ss];
+
+	gsm_fn2gsmtime(&g_time, fn);
+
+	DEBUGP(DL1P, "Rx TCH.ind %02u/%02u/%02u chan_nr=%d\n",
+		g_time.t1, g_time.t2, g_time.t3, chan_nr);
+
+	msgb_pull(msg, sizeof(*l1sap));
+
+	/* hand msg to RTP code for transmission */
+	if (lchan->abis_ip.rtp_socket)
+		osmo_rtp_send_frame(lchan->abis_ip.rtp_socket,
+			msg->data, msg->len, 160);
+
+	/* if loopback is enabled, also queue received RTP data */
+	if (lchan->loopback) {
+		struct msgb *tmp;
+		int count = 0;
+
+		 /* make sure the queue doesn't get too long */
+		llist_for_each_entry(tmp, &lchan->dl_tch_queue, list)
+		count++;
+		while (count >= 1) {
+			tmp = msgb_dequeue(&lchan->dl_tch_queue);
+			msgb_free(tmp);
+			count--;
+		}
+
+		msgb_enqueue(&lchan->dl_tch_queue, msg);
+	}
+
+	return 0;
+}
+
+/* RACH received from bts model */
+static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx,
+	 struct osmo_phsap_prim *l1sap, struct ph_rach_ind_param *rach_ind)
+{
+	struct gsm_bts *bts = trx->bts;
+	struct gsm_bts_role_bts *btsb = bts->role;
+	struct lapdm_channel *lc;
+
+	DEBUGP(DL1P, "Rx PH-RA.ind");
+
+	lc = &trx->ts[0].lchan[4].lapdm_ch;
+	if (!lc) {
+		LOGP(DL1P, LOGL_ERROR, "unable to resolve LAPD channel\n");
+		return -ENODEV;
+	}
+
+	/* check for under/overflow / sign */
+	if (rach_ind->acc_delay > btsb->max_ta) {
+		LOGP(DL1P, LOGL_INFO, "ignoring RACH request %u > max_ta(%u)\n",
+		     rach_ind->acc_delay, btsb->max_ta);
+		return 0;
+	}
+
+	/* check for packet access */
+	if (trx == bts->c0
+	 && L1SAP_IS_PACKET_RACH(rach_ind->ra)) {
+		LOGP(DL1P, LOGL_INFO, "RACH for packet access\n");
+		pcu_tx_rach_ind(bts, rach_ind->acc_delay << 2,
+			rach_ind->ra, rach_ind->fn);
+
+		return 0;
+	}
+
+	LOGP(DL1P, LOGL_INFO, "RACH for RR access (toa=%d, ra=%d)\n",
+		rach_ind->acc_delay, rach_ind->ra);
+	lapdm_phsap_up(&l1sap->oph, &lc->lapdm_dcch);
+
+	return 0;
+}
+
+/* any L1 prim received from bts model */
+int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+	struct msgb *msg = l1sap->oph.msg;
+	int rc = 0;
+
+	switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+	case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_INDICATION):
+		rc = l1sap_mph_info_ind(trx, l1sap, &l1sap->u.info);
+		break;
+	case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_CONFIRM):
+		rc = l1sap_mph_info_cnf(trx, l1sap, &l1sap->u.info);
+		break;
+	case OSMO_PRIM(PRIM_PH_RTS, PRIM_OP_INDICATION):
+		rc = l1sap_ph_rts_ind(trx, l1sap, &l1sap->u.data);
+		break;
+	case OSMO_PRIM(PRIM_TCH_RTS, PRIM_OP_INDICATION):
+		rc = l1sap_tch_rts_ind(trx, l1sap, &l1sap->u.tch);
+		break;
+	case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION):
+		to_gsmtap(trx, l1sap);
+		rc = l1sap_ph_data_ind(trx, l1sap, &l1sap->u.data);
+		break;
+	case OSMO_PRIM(PRIM_TCH, PRIM_OP_INDICATION):
+		rc = l1sap_tch_ind(trx, l1sap, &l1sap->u.tch);
+		break;
+	case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION):
+		to_gsmtap(trx, l1sap);
+		rc = l1sap_ph_rach_ind(trx, l1sap, &l1sap->u.rach_ind);
+		break;
+	default:
+		LOGP(DL1P, LOGL_NOTICE, "unknown prim %d op %d\n",
+			l1sap->oph.primitive, l1sap->oph.operation);
+		break;
+	}
+
+	/* Special return value '1' means: do not free */
+	if (rc != 1 && msg)
+		msgb_free(msg);
+
+	return rc;
+}
+
+/* any L1 prim sent to bts model */
+static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+	if (OSMO_PRIM_HDR(&l1sap->oph) ==
+				 OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST))
+		to_gsmtap(trx, l1sap);
+
+	return bts_model_l1sap_down(trx, l1sap);
+}
+
+/* pcu (socket interface) sends us a data request primitive */
+int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn,
+	uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len)
+{
+	struct msgb *msg;
+	struct osmo_phsap_prim *l1sap;
+	struct gsm_time g_time;
+
+	gsm_fn2gsmtime(&g_time, fn);
+
+	DEBUGP(DL1P, "TX packet data %02u/%02u/%02u is_ptcch=%d trx=%d ts=%d "
+		"block_nr=%d, arfcn=%d, len=%d\n", g_time.t1, g_time.t2,
+		g_time.t3, is_ptcch, ts->trx->nr, ts->nr, block_nr, arfcn, len);
+	
+	msg = l1sap_msgb_alloc(len);
+	l1sap = msgb_l1sap_prim(msg);
+	osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST,
+		msg);
+	l1sap->u.data.chan_nr = 0x08 | ts->nr;
+	l1sap->u.data.link_id = 0x00;
+	l1sap->u.data.fn = fn;
+	msg->l2h = msgb_put(msg, len);
+	memcpy(msg->l2h, data, len);
+
+	return l1sap_down(ts->trx, l1sap);
+}
+
+/*! \brief call-back function for incoming RTP */
+void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl,
+                         unsigned int rtp_pl_len)
+{
+	struct gsm_lchan *lchan = rs->priv;
+	struct msgb *msg, *tmp;
+	struct osmo_phsap_prim *l1sap;
+	int count = 0;
+
+	msg = l1sap_msgb_alloc(rtp_pl_len);
+	if (!msg)
+		return;
+	memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len);
+	msgb_pull(msg, sizeof(*l1sap));
+
+
+	 /* make sure the queue doesn't get too long */
+	llist_for_each_entry(tmp, &lchan->dl_tch_queue, list)
+	count++;
+	while (count >= 2) {
+		tmp = msgb_dequeue(&lchan->dl_tch_queue);
+		msgb_free(tmp);
+		count--;
+	}
+
+	msgb_enqueue(&lchan->dl_tch_queue, msg);
+}
+
+static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr,
+		enum osmo_mph_info_type type, uint8_t sacch_only)
+{
+	struct osmo_phsap_prim l1sap;
+
+	memset(&l1sap, 0, sizeof(l1sap));
+	osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_REQUEST,
+		NULL);
+	l1sap.u.info.type = type;
+	l1sap.u.info.u.act_req.chan_nr = chan_nr;
+	l1sap.u.info.u.act_req.sacch_only = sacch_only;
+
+	return l1sap_down(trx, &l1sap);
+}
+
+int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr)
+{
+	struct gsm_bts_role_bts *btsb = trx->bts->role;
+	struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)]
+				.lchan[l1sap_chan2ss(chan_nr)];
+
+	LOGP(DL1P, LOGL_INFO, "activating channel chan_nr=%02x trx=%d\n",
+		chan_nr, trx->nr);
+
+	lchan->sacch_deact = 0;
+	lchan->s = btsb->radio_link_timeout;
+
+	return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_ACTIVATE, 0);
+}
+
+int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr)
+{
+	LOGP(DL1P, LOGL_INFO, "deactivating channel chan_nr=%02x trx=%d\n",
+		chan_nr, trx->nr);
+
+	return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE,
+		0);
+}
+
+int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr)
+{
+	struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)]
+				.lchan[l1sap_chan2ss(chan_nr)];
+
+	LOGP(DL1P, LOGL_INFO, "deactivating sacch chan_nr=%02x trx=%d\n",
+		chan_nr, trx->nr);
+
+	lchan->sacch_deact = 1;
+
+	return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE,
+		1);
+}
+
+int l1sap_chan_modify(struct gsm_bts_trx *trx, uint8_t chan_nr)
+{
+	LOGP(DL1P, LOGL_INFO, "modifying channel chan_nr=%02x trx=%d\n",
+		chan_nr, trx->nr);
+
+	return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_MODIFY, 0);
+}
diff --git a/src/common/logging.c b/src/common/logging.c
index 98fd205..f56846e 100644
--- a/src/common/logging.c
+++ b/src/common/logging.c
@@ -69,8 +69,8 @@ static struct log_info_cat bts_log_info_cat[] = {
 	[DL1C] = {
 		.name = "DL1C",
 		.description = "Layer 1",
-		.loglevel = LOGL_INFO,
-		.enabled = 1,
+		.color = "\033[1;32m",
+		.enabled = 1, .loglevel = LOGL_NOTICE,
 	},
 	[DL1P] = {
 		.name = "DL1P",
diff --git a/src/common/measurement.c b/src/common/measurement.c
index 774962d..ff80a9f 100644
--- a/src/common/measurement.c
+++ b/src/common/measurement.c
@@ -89,6 +89,11 @@ static int is_meas_complete(enum gsm_phys_chan_config pchan, unsigned int ts,
 /* receive a L1 uplink measurement from L1 */
 int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm)
 {
+	/* in the GPRS case we are not interested in measurement
+	 * processing.  The PCU will take care of it */
+	if (lchan->type == GSM_LCHAN_PDTCH)
+		return 0;
+
 	DEBUGP(DMEAS, "%s adding measurement, num_ul_meas=%d\n",
 		gsm_lchan_name(lchan), lchan->meas.num_ul_meas);
 
diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c
index a90caac..76ecf22 100644
--- a/src/common/pcu_sock.c
+++ b/src/common/pcu_sock.c
@@ -39,7 +39,7 @@
 #include <osmo-bts/bts.h>
 #include <osmo-bts/rsl.h>
 #include <osmo-bts/signal.h>
-#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
 
 uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx);
 
@@ -57,10 +57,6 @@ static const char *sapi_string[] = {
 	[PCU_IF_SAPI_PTCCH] = 	"PTCCH",
 };
 
-/* FIXME: common l1if include ? */
-int l1if_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn,
-        uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len);
-
 static int pcu_sock_send(struct gsm_network *net, struct msgb *msg);
 /* FIXME: move this to libosmocore */
 int osmo_unixsock_listen(struct osmo_fd *bfd, int type, const char *path);
@@ -511,7 +507,7 @@ static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type,
 		}
 		ts = &trx->ts[data_req->ts_nr];
 		is_ptcch = (data_req->sapi == PCU_IF_SAPI_PTCCH);
-		rc = l1if_pdch_req(ts, is_ptcch, data_req->fn, data_req->arfcn,
+		rc = l1sap_pdch_req(ts, is_ptcch, data_req->fn, data_req->arfcn,
 			data_req->block_nr, data_req->data, data_req->len);
 		break;
 	default:
@@ -544,9 +540,9 @@ static int pcu_rx_act_req(struct gsm_bts *bts,
 		return -EINVAL;
 	}
 	if (act_req->activate)
-		bts_model_rsl_chan_act(lchan, NULL);
+		l1sap_chan_act(trx, gsm_lchan2chan_nr(lchan));
 	else
-		bts_model_rsl_chan_rel(lchan);
+		l1sap_chan_rel(trx, gsm_lchan2chan_nr(lchan));
 
 	return 0;
 }
@@ -650,7 +646,8 @@ static void pcu_sock_close(struct pcu_sock_state *state)
 			ts = &trx->ts[j];
 			if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
 			 && ts->pchan == GSM_PCHAN_PDCH)
-				bts_model_rsl_chan_rel(ts->lchan);
+				l1sap_chan_rel(trx,
+					gsm_lchan2chan_nr(ts->lchan));
 		}
 	}
 
diff --git a/src/common/rsl.c b/src/common/rsl.c
index 616ae0d..150f686 100644
--- a/src/common/rsl.c
+++ b/src/common/rsl.c
@@ -41,9 +41,9 @@
 #include <osmo-bts/oml.h>
 #include <osmo-bts/amr.h>
 #include <osmo-bts/signal.h>
-#include <osmo-bts/bts_model.h>
 #include <osmo-bts/measurement.h>
 #include <osmo-bts/pcu_if.h>
+#include <osmo-bts/l1sap.h>
 
 //#define FAKE_CIPH_MODE_COMPL
 
@@ -499,8 +499,9 @@ int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan)
 }
 
 /* 8.4.2 sending CHANnel ACTIVation ACKnowledge */
-int rsl_tx_chan_act_ack(struct gsm_lchan *lchan, struct gsm_time *gtime)
+int rsl_tx_chan_act_ack(struct gsm_lchan *lchan)
 {
+	struct gsm_time *gtime = get_time(lchan->ts->trx->bts);
 	struct msgb *msg;
 	uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
 	uint8_t ie[2];
@@ -760,14 +761,12 @@ static int rsl_rx_chan_activ(struct msgb *msg)
 		dch->chan_nr, type, lchan->tch_mode);
 
 	/* actually activate the channel in the BTS */
-	return  bts_model_rsl_chan_act(msg->lchan, &tp);
+	return l1sap_chan_act(lchan->ts->trx, dch->chan_nr);
 }
 
 /* 8.4.14 RF CHANnel RELease is received */
-static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan)
+static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr)
 {
-	int rc;
-
 	if (lchan->abis_ip.rtp_socket) {
 		rsl_tx_ipac_dlcx_ind(lchan, RSL_ERR_NORMAL_UNSPEC);
 		osmo_rtp_socket_free(lchan->abis_ip.rtp_socket);
@@ -775,11 +774,11 @@ static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan)
 		msgb_queue_flush(&lchan->dl_tch_queue);
 	}
 
-	rc = bts_model_rsl_chan_rel(lchan);
+	l1sap_chan_rel(lchan->ts->trx, chan_nr);
 
 	lapdm_channel_exit(&lchan->lapdm_ch);
 
-	return rc;
+	return 0;
 }
 
 #ifdef FAKE_CIPH_MODE_COMPL
@@ -962,10 +961,10 @@ static int rsl_tx_mode_modif_ack(struct gsm_lchan *lchan)
 /* 8.4.9 MODE MODIFY */
 static int rsl_rx_mode_modif(struct msgb *msg)
 {
+	struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
 	struct gsm_lchan *lchan = msg->lchan;
 	struct rsl_ie_chan_mode *cm;
 	struct tlv_parsed tp;
-	int rc;
 
 	rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
 
@@ -1005,12 +1004,12 @@ static int rsl_rx_mode_modif(struct msgb *msg)
 	/* 9.3.53 MultiRate Control */
 	/* 9.3.54 Supported Codec Types */
 
-	rc = bts_model_rsl_mode_modify(msg->lchan);
+	l1sap_chan_modify(lchan->ts->trx, dch->chan_nr);
 
 	/* FIXME: delay this until L1 says OK? */
-	rsl_tx_mode_modif_ack(msg->lchan);
+	rsl_tx_mode_modif_ack(lchan);
 
-	return rc;
+	return 0;
 }
 
 /* 8.4.20 SACCH INFO MODify */
@@ -1314,7 +1313,7 @@ static int rsl_rx_ipac_XXcx(struct msgb *msg)
 					  OSMO_RTP_P_JITBUF,
 					  btsb->rtp_jitter_buf_ms);
 		lchan->abis_ip.rtp_socket->priv = lchan;
-		lchan->abis_ip.rtp_socket->rx_cb = &bts_model_rtp_rx_cb;
+		lchan->abis_ip.rtp_socket->rx_cb = &l1sap_rtp_rx_cb;
 
 		if (connect_ip && connect_port) {
 			/* if CRCX specifies a remote IP, we can bind()
@@ -1646,13 +1645,13 @@ static int rsl_rx_dchan(struct gsm_bts_trx *trx, struct msgb *msg)
 		ret = rsl_rx_chan_activ(msg);
 		break;
 	case RSL_MT_RF_CHAN_REL:
-		ret = rsl_rx_rf_chan_rel(msg->lchan);
+		ret = rsl_rx_rf_chan_rel(msg->lchan, dch->chan_nr);
 		break;
 	case RSL_MT_SACCH_INFO_MODIFY:
 		ret = rsl_rx_sacch_inf_mod(msg);
 		break;
 	case RSL_MT_DEACTIVATE_SACCH:
-		ret = bts_model_rsl_deact_sacch(msg->lchan);
+		ret = l1sap_chan_deact_sacch(trx, dch->chan_nr);
 		break;
 	case RSL_MT_ENCR_CMD:
 		ret = rsl_rx_encr_cmd(msg);
diff --git a/src/common/vty.c b/src/common/vty.c
index 5eddc8d..4daa92c 100644
--- a/src/common/vty.c
+++ b/src/common/vty.c
@@ -22,12 +22,15 @@
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <ctype.h>
 
 #include <osmocom/core/talloc.h>
 #include <osmocom/gsm/abis_nm.h>
 #include <osmocom/vty/vty.h>
 #include <osmocom/vty/command.h>
 #include <osmocom/vty/logging.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/core/gsmtap.h>
 
 #include <osmocom/trau/osmo_ortp.h>
 
@@ -42,7 +45,7 @@
 #include <osmo-bts/bts_model.h>
 #include <osmo-bts/measurement.h>
 #include <osmo-bts/vty.h>
-
+#include <osmo-bts/l1sap.h>
 
 enum node_type bts_vty_go_parent(struct vty *vty)
 {
@@ -171,10 +174,38 @@ DEFUN(cfg_bts_trx, cfg_bts_trx_cmd,
 	return CMD_SUCCESS;
 }
 
+/* FIXME: move to libosmocore ? */
+static char buf_casecnvt[256];
+char *osmo_str_tolower(const char *in)
+{
+	int len, i;
+
+	if (!in)
+		return NULL;
+
+	len = strlen(in);
+	if (len > sizeof(buf_casecnvt))
+		len = sizeof(buf_casecnvt);
+
+	for (i = 0; i < len; i++) {
+		buf_casecnvt[i] = tolower(in[i]);
+		if (in[i] == '\0')
+			break;
+	}
+	if (i < sizeof(buf_casecnvt))
+		buf_casecnvt[i] = '\0';
+
+	/* just to make sure we're always zero-terminated */
+	buf_casecnvt[sizeof(buf_casecnvt)-1] = '\0';
+
+	return buf_casecnvt;
+}
+
 static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
 {
 	struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
 	struct gsm_bts_trx *trx;
+	int i;
 
 	vty_out(vty, "bts %u%s", bts->nr, VTY_NEWLINE);
 	if (bts->description)
@@ -190,6 +221,17 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
 	vty_out(vty, " paging lifetime %u%s", paging_get_lifetime(btsb->paging_state),
 		VTY_NEWLINE);
 
+	for (i = 0; i < 32; i++) {
+		if (gsmtap_sapi_mask & (1 << i)) {
+			const char *name = get_value_string(gsmtap_sapi_names, i);
+			vty_out(vty, " gsmtap-sapi %s%s", osmo_str_tolower(name), VTY_NEWLINE);
+		}
+	}
+	if (gsmtap_sapi_acch) {
+		const char *name = get_value_string(gsmtap_sapi_names, GSMTAP_CHANNEL_ACCH);
+		vty_out(vty, " gsmtap-sapi %s%s", osmo_str_tolower(name), VTY_NEWLINE);
+	}
+
 	bts_model_config_write_bts(vty, bts);
 
 	llist_for_each_entry(trx, &bts->trx_list, list) {
@@ -475,6 +517,36 @@ static struct gsm_lchan *resolve_lchan(struct gsm_network *net,
 	"logical channel commands\n"	\
 	"logical channel number\n"
 
+DEFUN(cfg_trx_gsmtap_sapi, cfg_trx_gsmtap_sapi_cmd,
+	"HIDDEN", "HIDDEN")
+{
+	int sapi;
+
+	sapi = get_string_value(gsmtap_sapi_names, argv[0]);
+
+	if (sapi == GSMTAP_CHANNEL_ACCH)
+		gsmtap_sapi_acch = 1;
+	else
+		gsmtap_sapi_mask |= (1 << sapi);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_no_gsmtap_sapi, cfg_trx_no_gsmtap_sapi_cmd,
+	"HIDDEN", "HIDDEN")
+{
+	int sapi;
+
+	sapi = get_string_value(gsmtap_sapi_names, argv[0]);
+
+	if (sapi == GSMTAP_CHANNEL_ACCH)
+		gsmtap_sapi_acch = 0;
+	else
+		gsmtap_sapi_mask &= ~(1 << sapi);
+
+	return CMD_SUCCESS;
+}
+
 DEFUN(bts_t_t_l_jitter_buf,
 	bts_t_t_l_jitter_buf_cmd,
 	"bts <0-0> trx <0-0> ts <0-7> lchan <0-1> rtp jitter-buffer <0-10000>",
@@ -501,8 +573,58 @@ DEFUN(bts_t_t_l_jitter_buf,
 	return CMD_SUCCESS;
 }
 
-int bts_vty_init(const struct log_info *cat)
+DEFUN(bts_t_t_l_loopback,
+	bts_t_t_l_loopback_cmd,
+	"bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback",
+	BTS_T_T_L_STR "Set loopback\n")
 {
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_lchan *lchan;
+
+	lchan = resolve_lchan(net, argv, 0);
+	if (!lchan) {
+		vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	lchan->loopback = 1;
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(no_bts_t_t_l_loopback,
+	no_bts_t_t_l_loopback_cmd,
+	"no bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback",
+	NO_STR BTS_T_T_L_STR "Set loopback\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	struct gsm_lchan *lchan;
+
+	lchan = resolve_lchan(net, argv, 0);
+	if (!lchan) {
+		vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	lchan->loopback = 0;
+
+	return CMD_SUCCESS;
+}
+
+int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat)
+{
+	cfg_trx_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+						"gsmtap-sapi (",
+						"|",")", VTY_DO_LOWER);
+	cfg_trx_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+						"GSMTAP SAPI\n",
+						"\n", "", 0);
+
+	cfg_trx_no_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+						"no gsmtap-sapi (",
+						"|",")", VTY_DO_LOWER);
+	cfg_trx_no_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names,
+						NO_STR "GSMTAP SAPI\n",
+						"\n", "", 0);
+
 	install_element_ve(&show_bts_cmd);
 
 	logging_vty_add_cmds(cat);
@@ -520,12 +642,17 @@ int bts_vty_init(const struct log_info *cat)
 	install_element(BTS_NODE, &cfg_bts_paging_queue_size_cmd);
 	install_element(BTS_NODE, &cfg_bts_paging_lifetime_cmd);
 
+	install_element(BTS_NODE, &cfg_trx_gsmtap_sapi_cmd);
+	install_element(BTS_NODE, &cfg_trx_no_gsmtap_sapi_cmd);
+
 	/* add and link to TRX config node */
 	install_element(BTS_NODE, &cfg_bts_trx_cmd);
 	install_node(&trx_node, config_write_dummy);
 	install_default(TRX_NODE);
 
 	install_element(ENABLE_NODE, &bts_t_t_l_jitter_buf_cmd);
+	install_element(ENABLE_NODE, &bts_t_t_l_loopback_cmd);
+	install_element(ENABLE_NODE, &no_bts_t_t_l_loopback_cmd);
 
 	return 0;
 }
diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c
index 16f1523..6efb9d6 100644
--- a/src/osmo-bts-sysmo/l1_if.c
+++ b/src/osmo-bts-sysmo/l1_if.c
@@ -1303,3 +1303,9 @@ int l1if_close(struct femtol1_hdl *fl1h)
 	l1if_transport_close(MQ_SYS_WRITE, fl1h);
 	return 0;
 }
+
+/* temporary stub to make this patch compile */
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+	return -ENOTSUP;
+}
diff --git a/src/osmo-bts-sysmo/main.c b/src/osmo-bts-sysmo/main.c
index 595a6eb..7e7f761 100644
--- a/src/osmo-bts-sysmo/main.c
+++ b/src/osmo-bts-sysmo/main.c
@@ -255,7 +255,7 @@ int main(int argc, char **argv)
 	bts_log_init(NULL);
 
 	vty_init(&bts_vty_info);
-	bts_vty_init(&bts_log_info);
+	bts_vty_init(bts, &bts_log_info);
 
 	handle_options(argc, argv);
 
diff --git a/src/osmo-bts-sysmo/oml.c b/src/osmo-bts-sysmo/oml.c
index faef025..aeddad7 100644
--- a/src/osmo-bts-sysmo/oml.c
+++ b/src/osmo-bts-sysmo/oml.c
@@ -868,10 +868,8 @@ static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
 	if (lchan->state != LCHAN_S_ACT_REQ)
 		return 0;
 
-	struct gsm_time *time;
 	lchan_set_state(lchan, LCHAN_S_ACTIVE);
-	time = bts_model_get_time(lchan->ts->trx->bts);
-	rsl_tx_chan_act_ack(lchan, time);
+	rsl_tx_chan_act_ack(lchan);
 
 	/* set the initial ciphering parameters for both directions */
 	l1if_set_ciphering(fl1h, lchan, 0);
diff --git a/src/osmo-bts-sysmo/sysmobts_vty.c b/src/osmo-bts-sysmo/sysmobts_vty.c
index 5bc948e..61deda4 100644
--- a/src/osmo-bts-sysmo/sysmobts_vty.c
+++ b/src/osmo-bts-sysmo/sysmobts_vty.c
@@ -444,33 +444,6 @@ void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
 		vty_out(vty, " auto-band%s", VTY_NEWLINE);
 }
 
-/* FIXME: move to libosmocore ? */
-static char buf_casecnvt[256];
-char *osmo_str_tolower(const char *in)
-{
-	int len, i;
-
-	if (!in)
-		return NULL;
-
-	len = strlen(in);
-	if (len > sizeof(buf_casecnvt))
-		len = sizeof(buf_casecnvt);
-
-	for (i = 0; i < len; i++) {
-		buf_casecnvt[i] = tolower(in[i]);
-		if (in[i] == '\0')
-			break;
-	}
-	if (i < sizeof(buf_casecnvt))
-		buf_casecnvt[i] = '\0';
-
-	/* just to make sure we're always zero-terminated */
-	buf_casecnvt[sizeof(buf_casecnvt)-1] = '\0';
-
-	return buf_casecnvt;
-}
-
 void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
 {
 	struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx);
diff --git a/tests/stubs.c b/tests/stubs.c
index c46bb4a..c0089c5 100644
--- a/tests/stubs.c
+++ b/tests/stubs.c
@@ -46,3 +46,6 @@ int l1if_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn,
 
 uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
 { return 0; }
+
+int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{ return 0; }
-- 
1.7.3.4


--------------010307000202050200040407--




More information about the OpenBSC mailing list