mISDN: optionally bypass kernel LAPD code and use userspace LAPD

The problem with kernel LAPD mainly is that you can only have one
signalling timeslot in each E1 line/interface.  However, with many BTS
attached to the same line, we need multiple signalling slots per line.

This allows the user to have a per-line selection between kernel-LAPD
(misdn) and userspace-LAPD (misdn_lapd) drivers in the config file.
diff --git a/src/e1_input_vty.c b/src/e1_input_vty.c
index aa70805..bcc0251 100644
--- a/src/e1_input_vty.c
+++ b/src/e1_input_vty.c
@@ -38,8 +38,9 @@
 
 /* CONFIG */
 
-#define E1_DRIVER_NAMES		"(misdn|dahdi|ipa|hsl)"
-#define E1_DRIVER_HELP		"mISDN supported E1 Card\n" \
+#define E1_DRIVER_NAMES		"(misdn|misdn_lapd|dahdi|ipa|hsl)"
+#define E1_DRIVER_HELP		"mISDN supported E1 Card (kernel LAPD)\n" \
+				"mISDN supported E1 Card (userspace LAPD)\n" \
 				"DAHDI supported E1/T1/J1 Card\n" \
 				"IPA TCP/IP input" \
 				"HSL TCP/IP input"
diff --git a/src/input/misdn.c b/src/input/misdn.c
index 2af81f2..716c1e7 100644
--- a/src/input/misdn.c
+++ b/src/input/misdn.c
@@ -1,6 +1,6 @@
 /* OpenBSC Abis input driver for mISDNuser */
 
-/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2008-2011 by Harald Welte <laforge@gnumonks.org>
  * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
  *
  * All Rights Reserved
@@ -20,6 +20,19 @@
  *
  */
 
+/*! \file misdn.c
+ *  \brief Osmocom A-bis input driver for mISDN
+ *
+ * This driver has two modes of operations, exported via two different
+ * \ref e1_input_driver structures: 
+ * "misdn" is the classic version and it uses the in-kernel LAPD
+ * implementation.  This is somewhat limited in e.g. the fact that
+ * you can only have one E1 timeslot in signaling mode.
+ * "misdn_lapd" is a newer version which uses userspace LAPD code
+ * contained in libosmo-abis.  It offers the same flexibilty as the
+ * DAHDI driver, i.e. any number of signaling slots.
+ */
+
 #include "internal.h"
 
 #include <stdio.h>
@@ -46,10 +59,16 @@
 #include <osmocom/core/msgb.h>
 #include <osmocom/core/logging.h>
 #include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/lapd.h>
 #include <osmocom/core/talloc.h>
 
 #define TS1_ALLOC_SIZE	300
 
+/*! \brief driver-specific data for \ref e1inp_line::driver_data */
+struct misdn_line {
+	int use_userspace_lapd;
+};
+
 const struct value_string prim_names[] = {
 	{ PH_CONTROL_IND, "PH_CONTROL_IND" },
 	{ PH_DATA_IND, "PH_DATA_IND" },
@@ -70,6 +89,7 @@
 static int handle_ts1_read(struct osmo_fd *bfd)
 {
 	struct e1inp_line *line = bfd->data;
+	struct misdn_line *mline = line->driver_data;
 	unsigned int ts_nr = bfd->priv_nr;
 	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
 	struct e1inp_sign_link *link;
@@ -145,6 +165,10 @@
 	case DL_UNITDATA_IND:
 		msg->l2h = msg->data + MISDN_HEADER_LEN;
 		DEBUGP(DLMI, "RX: %s\n", osmo_hexdump(msgb_l2(msg), ret - MISDN_HEADER_LEN));
+		if (mline->use_userspace_lapd) {
+			LOGP(DLMI, LOGL_ERROR, "DL_DATA_IND but userspace LAPD ?!?\n");
+			return -EIO;
+		}
 		ret = e1inp_rx_ts(e1i_ts, msg, l2addr.tei, l2addr.sapi);
 		break;
 	case PH_ACTIVATE_IND:
@@ -155,6 +179,17 @@
 		DEBUGP(DLMI, "PH_DEACTIVATE_IND: channel(%d) sapi(%d) tei(%d)\n",
 		l2addr.channel, l2addr.sapi, l2addr.tei);
 		break;
+	case PH_DATA_IND:
+		if (!mline->use_userspace_lapd) {
+			LOGP(DLMI, LOGL_ERROR, "PH_DATA_IND but kernel LAPD ?!?\n");
+			return -EIO;
+		}
+		/* remove the Misdn Header */
+		msgb_pull(msg, MISDN_HEADER_LEN);
+		/* hand into the LAPD code */
+		DEBUGP(DLMI, "RX: %s\n", osmo_hexdump(msg->data, msg->len));
+		ret = e1inp_rx_ts_lapd(e1i_ts, msg);
+		break;
 	default:
 		break;
 	}
@@ -185,6 +220,7 @@
 static int handle_ts1_write(struct osmo_fd *bfd)
 {
 	struct e1inp_line *line = bfd->data;
+	struct misdn_line *mline = line->driver_data;
 	unsigned int ts_nr = bfd->priv_nr;
 	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
 	struct e1inp_sign_link *sign_link;
@@ -203,26 +239,35 @@
 		return 0;
 	}
 
-	l2_data = msg->data;
+	if (mline->use_userspace_lapd) {
+		DEBUGP(DLMI, "TX %u/%u/%u: %s\n",
+			line->num, sign_link->tei, sign_link->sapi,
+			osmo_hexdump(msg->data, msg->len));
+		lapd_transmit(e1i_ts->lapd, sign_link->tei,
+				sign_link->sapi, msg->data, msg->len);
+	} else {
+		l2_data = msg->data;
 
-	/* prepend the mISDNhead */
-	hh = (struct mISDNhead *) msgb_push(msg, sizeof(*hh));
-	hh->prim = DL_DATA_REQ;
+		/* prepend the mISDNhead */
+		hh = (struct mISDNhead *) msgb_push(msg, sizeof(*hh));
+		hh->prim = DL_DATA_REQ;
 
-	DEBUGP(DLMI, "TX channel(%d) TEI(%d) SAPI(%d): %s\n",
-		sign_link->driver.misdn.channel, sign_link->tei,
-		sign_link->sapi, osmo_hexdump(l2_data, msg->len - MISDN_HEADER_LEN));
+		DEBUGP(DLMI, "TX channel(%d) TEI(%d) SAPI(%d): %s\n",
+			sign_link->driver.misdn.channel, sign_link->tei,
+			sign_link->sapi, osmo_hexdump(l2_data, msg->len - MISDN_HEADER_LEN));
 
-	/* construct the sockaddr */
-	sa.family = AF_ISDN;
-	sa.sapi = sign_link->sapi;
-	sa.dev = sign_link->tei;
-	sa.channel = sign_link->driver.misdn.channel;
+		/* construct the sockaddr */
+		sa.family = AF_ISDN;
+		sa.sapi = sign_link->sapi;
+		sa.dev = sign_link->tei;
+		sa.channel = sign_link->driver.misdn.channel;
 
-	ret = sendto(bfd->fd, msg->data, msg->len, 0,
-		     (struct sockaddr *)&sa, sizeof(sa));
-	if (ret < 0)
-		fprintf(stderr, "%s sendto failed %d\n", __func__, ret);
+		ret = sendto(bfd->fd, msg->data, msg->len, 0,
+			     (struct sockaddr *)&sa, sizeof(sa));
+		if (ret < 0)
+			fprintf(stderr, "%s sendto failed %d\n", __func__, ret);
+	}
+
 	msgb_free(msg);
 
 	/* set tx delay timer for next event */
@@ -233,6 +278,29 @@
 	return ret;
 }
 
+/*! \brief call-back from LAPD code, called when it wants to Tx data */
+static void misdn_write_msg(uint8_t *data, int len, void *cbdata)
+{
+	struct osmo_fd *bfd = cbdata;
+	struct e1inp_line *line = bfd->data;
+	unsigned int ts_nr = bfd->priv_nr;
+	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
+	struct msgb *msg = msgb_alloc(1024, "mISDN PH_DATA_REQ");
+	struct mISDNhead *hh;
+	int ret;
+
+	hh = (struct mISDNhead *) msgb_put(msg, MISDN_HEADER_LEN);
+	hh->prim = PH_DATA_REQ;
+
+	memcpy(msgb_put(msg, len), data, len);
+
+	ret = write(bfd->fd, msg->data, msg->len);
+	if (ret < 0)
+		LOGP(DLMI, LOGL_NOTICE, "write failed %d\n", ret);
+
+	msgb_free(msg);
+}
+
 #define BCHAN_TX_GRAN	160
 /* write to a B channel TS */
 static int handle_tsX_write(struct osmo_fd *bfd)
@@ -367,6 +435,7 @@
 }
 
 static int mi_e1_line_update(struct e1inp_line *line);
+static int mi_e1_line_update_lapd(struct e1inp_line *line);
 
 struct e1inp_driver misdn_driver = {
 	.name = "misdn",
@@ -375,8 +444,16 @@
 	.line_update = &mi_e1_line_update,
 };
 
+struct e1inp_driver misdn_lapd_driver = {
+	.name = "misdn_lapd",
+	.want_write = ts_want_write,
+	.default_delay = 50000,
+	.line_update = &mi_e1_line_update_lapd,
+};
+
 static int mi_e1_setup(struct e1inp_line *line, int release_l2)
 {
+	struct misdn_line *mline = line->driver_data;
 	int ts, ret;
 
 	/* TS0 is CRC4, don't need any fd for it */
@@ -395,7 +472,10 @@
 			continue;
 			break;
 		case E1INP_TS_TYPE_SIGN:
-			bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_NT);
+			if (mline->use_userspace_lapd)
+				bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_B_HDLC);
+			else
+				bfd->fd = socket(PF_ISDN, SOCK_DGRAM, ISDN_P_LAPD_NT);
 			bfd->when = BSC_FD_READ;
 			break;
 		case E1INP_TS_TYPE_TRAU:
@@ -418,11 +498,16 @@
 		addr.dev = line->num;
 		switch (e1i_ts->type) {
 		case E1INP_TS_TYPE_SIGN:
-			addr.channel = 0;
-			/* SAPI not supported yet in kernel */
-			//addr.sapi = e1inp_ts->sign.sapi;
-			addr.sapi = 0;
-			addr.tei = GROUP_TEI;
+			if (mline->use_userspace_lapd) {
+				addr.channel = ts;
+				e1i_ts->lapd = lapd_instance_alloc(1, misdn_write_msg, bfd);
+			} else {
+				addr.channel = 0;
+				/* SAPI not supported yet in kernel */
+				//addr.sapi = e1inp_ts->sign.sapi;
+				addr.sapi = 0;
+				addr.tei = GROUP_TEI;
+			}
 			break;
 		case E1INP_TS_TYPE_TRAU:
 			addr.channel = ts;
@@ -441,11 +526,14 @@
 		}
 
 		if (e1i_ts->type == E1INP_TS_TYPE_SIGN) {
-			ret = ioctl(bfd->fd, IMCLEAR_L2, &release_l2);
-			if (ret < 0) {
-				fprintf(stderr, "could not send IOCTL IMCLEAN_L2 %s\n", strerror(errno));
-				return -EIO;
-			}
+			if (!mline->use_userspace_lapd) {
+				ret = ioctl(bfd->fd, IMCLEAR_L2, &release_l2);
+				if (ret < 0) {
+					fprintf(stderr, "could not send IOCTL IMCLEAN_L2 %s\n", strerror(errno));
+					return -EIO;
+				}
+			} else
+				activate_bchan(line, ts, 1);
 		}
 
 		/* FIXME: only activate B-Channels once we start to
@@ -464,12 +552,13 @@
 	return 0;
 }
 
-static int mi_e1_line_update(struct e1inp_line *line)
+static int _mi_e1_line_update(struct e1inp_line *line)
 {
 	struct mISDN_devinfo devinfo;
 	int sk, ret, cnt;
 
-	if (line->driver != &misdn_driver)
+	if (line->driver != &misdn_driver &&
+	    line->driver != &misdn_lapd_driver)
 		return -EINVAL;
 
 	/* open the ISDN card device */
@@ -517,8 +606,35 @@
 	return 0;
 }
 
+static int mi_e1_line_update(struct e1inp_line *line)
+{
+	struct misdn_line *ml;
+
+	if (!line->driver_data)
+		line->driver_data = talloc_zero(line, struct misdn_line);
+
+	ml = line->driver_data;
+	ml->use_userspace_lapd = 0;
+
+	return _mi_e1_line_update(line);
+}
+
+static int mi_e1_line_update_lapd(struct e1inp_line *line)
+{
+	struct misdn_line *ml;
+
+	if (!line->driver_data)
+		line->driver_data = talloc_zero(line, struct misdn_line);
+
+	ml = line->driver_data;
+	ml->use_userspace_lapd = 1;
+
+	return _mi_e1_line_update(line);
+}
+
 void e1inp_misdn_init(void)
 {
 	/* register the driver with the core */
 	e1inp_driver_register(&misdn_driver);
+	e1inp_driver_register(&misdn_lapd_driver);
 }