hsl: BTS support

This patch adds the BTS support for the hsl driver.

It includes two examples under the tests/ directory.
diff --git a/src/input/hsl.c b/src/input/hsl.c
index 36061f5..7058f48 100644
--- a/src/input/hsl.c
+++ b/src/input/hsl.c
@@ -76,6 +76,15 @@
 static void hsl_drop(struct e1inp_line *line, struct osmo_fd *bfd)
 {
 	line->ops->sign_link_down(line);
+
+	if (bfd->fd != -1) {
+		osmo_fd_unregister(bfd);
+		close(bfd->fd);
+		bfd->fd = -1;
+	}
+	/* put the virtual E1 line that we cloned for this socket, if
+	 * it becomes unused, it gets released. */
+	e1inp_line_put(line);
 }
 
 static int process_hsl_rsl(struct msgb *msg, struct e1inp_line *line,
@@ -205,9 +214,8 @@
 	ts_want_write(e1i_ts);
 }
 
-static int handle_ts1_write(struct osmo_fd *bfd)
+static int __handle_ts1_write(struct osmo_fd *bfd, struct e1inp_line *line)
 {
-	struct e1inp_line *line = bfd->data;
 	unsigned int ts_nr = bfd->priv_nr;
 	struct e1inp_ts *e1i_ts = &line->ts[ts_nr-1];
 	struct e1inp_sign_link *sign_link;
@@ -260,6 +268,20 @@
 	return ret;
 }
 
+static int handle_ts1_write(struct osmo_fd *bfd)
+{
+	struct e1inp_line *line = bfd->data;
+
+	return __handle_ts1_write(bfd, line);
+}
+
+int hsl_bts_write(struct ipa_client_link *link)
+{
+	struct e1inp_line *line = link->line;
+
+	return __handle_ts1_write(link->ofd, line);
+}
+
 /* callback from select.c in case one of the fd's can be read/written */
 static int hsl_fd_cb(struct osmo_fd *bfd, unsigned int what)
 {
@@ -301,7 +323,7 @@
 	int ret;
 	int idx = 0;
 	int i;
-	struct e1inp_line *line = listen_bfd->data;
+	struct e1inp_line *line;
 	struct e1inp_ts *e1i_ts;
 	struct osmo_fd *bfd;
 	struct sockaddr_in sa;
@@ -318,6 +340,12 @@
 	LOGP(DINP, LOGL_NOTICE, "accept()ed new HSL link from %s\n",
 		inet_ntoa(sa.sin_addr));
 
+	/* clone virtual E1 line for this new signalling link. */
+	line = e1inp_line_clone(tall_hsl_ctx, listen_bfd->data);
+	if (line == NULL) {
+		LOGP(DINP, LOGL_ERROR, "could not clone E1 line\n");
+		return -1;
+	}
 	/* create virrtual E1 timeslots for signalling */
 	e1inp_ts_config_sign(&line->ts[1-1], line);
 
@@ -337,7 +365,7 @@
 	if (ret < 0) {
 		LOGP(DINP, LOGL_ERROR, "could not register FD\n");
 		close(bfd->fd);
-		talloc_free(line);
+		e1inp_line_put(line);
 		return ret;
 	}
 
@@ -346,7 +374,74 @@
 
 static int hsl_bts_process(struct ipa_client_link *link, struct msgb *msg)
 {
-	/* XXX: not implemented yet. */
+	struct ipaccess_head *hh;
+	struct e1inp_sign_link *sign_link;
+	struct e1inp_ts *e1i_ts = &link->line->ts[0];
+
+	hh = (struct ipaccess_head *) msg->data;
+	if (hh->proto == HSL_PROTO_DEBUG) {
+		LOGP(DINP, LOGL_NOTICE, "HSL debug: %s\n",
+						msg->data + sizeof(*hh));
+		msgb_free(msg);
+		return 0;
+	}
+	sign_link = e1inp_lookup_sign_link(e1i_ts, hh->proto, 0);
+	if (!sign_link) {
+		LOGP(DINP, LOGL_ERROR, "no matching signalling link for "
+			"hh->proto=0x%02x\n", hh->proto);
+		msgb_free(msg);
+		return -EIO;
+	}
+	msg->dst = sign_link;
+
+	/* XXX better use e1inp_ts_rx? */
+	if (!link->line->ops->sign_link) {
+		LOGP(DINP, LOGL_ERROR, "Fix your application, "
+			"no action set for signalling messages.\n");
+		return -ENOENT;
+	}
+	link->line->ops->sign_link(msg);
+	return 0;
+}
+
+static int hsl_bts_connect(struct ipa_client_link *link)
+{
+	struct msgb *msg;
+	uint8_t *serno;
+	char serno_buf[16];
+	struct hsl_unit *unit = link->line->ops->data;
+	struct e1inp_sign_link *sign_link;
+
+	/* send the minimal message to identify this BTS. */
+	msg = ipa_msg_alloc(0);
+	if (!msg)
+		return -ENOMEM;
+
+	*msgb_put(msg, 1) = 0x80;
+	*msgb_put(msg, 1) = 0x80;
+	*msgb_put(msg, 1) = unit->swversion;
+	snprintf(serno_buf, sizeof(serno_buf), "%llx", unit->serno);
+	serno = msgb_put(msg, strlen(serno_buf)+1);
+	memcpy(serno, serno_buf, strlen(serno_buf));
+	ipa_msg_push_header(msg, 0);
+	send(link->ofd->fd, msg->data, msg->len, 0);
+	msgb_free(msg);
+
+	/* ... and enable the signalling link. */
+	if (!link->line->ops->sign_link_up) {
+		LOGP(DINP, LOGL_ERROR,
+			"Unable to set signal link, closing socket.\n");
+		ipa_client_link_close(link);
+		return -EINVAL;
+	}
+	sign_link = link->line->ops->sign_link_up(&unit,
+						  link->line, E1INP_SIGN_NONE);
+	if (sign_link == NULL) {
+		LOGP(DINP, LOGL_ERROR,
+		     "Unable to set signal link, closing socket.\n");
+		ipa_client_link_close(link);
+		return -EINVAL;
+	}
 	return 0;
 }
 
@@ -380,10 +475,13 @@
 		LOGP(DINP, LOGL_NOTICE, "enabling hsl BTS mode\n");
 
 		link = ipa_client_link_create(tall_hsl_ctx,
-						&line->ts[0], "hsl", 0,
-						addr, HSL_TCP_PORT,
-						hsl_bts_process, NULL,
-						NULL);
+					      &line->ts[E1INP_SIGN_OML-1],
+					      "hsl", E1INP_SIGN_OML,
+					      addr, HSL_TCP_PORT,
+					      hsl_bts_connect,
+					      hsl_bts_process,
+					      hsl_bts_write,
+					      NULL);
 		if (link == NULL) {
 			LOGP(DINP, LOGL_ERROR, "cannot create BTS link: %s\n",
 				strerror(errno));
diff --git a/src/input/ipa.c b/src/input/ipa.c
index c65a0ec..c8ee781 100644
--- a/src/input/ipa.c
+++ b/src/input/ipa.c
@@ -24,9 +24,26 @@
 
 #define IPA_ALLOC_SIZE 1200
 
-static struct msgb *ipa_msg_alloc(void)
+struct msgb *ipa_msg_alloc(int headroom)
 {
-	return msgb_alloc(IPA_ALLOC_SIZE, "Abis/IP");
+	struct msgb *nmsg;
+
+	headroom += sizeof(struct ipaccess_head);
+
+	nmsg = msgb_alloc_headroom(1200 + headroom, headroom, "Abis/IP");
+	if (!nmsg)
+		return NULL;
+	return nmsg;
+}
+
+void ipa_msg_push_header(struct msgb *msg, uint8_t proto)
+{
+	struct ipaccess_head *hh;
+
+	msg->l2h = msg->data;
+	hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh));
+	hh->proto = proto;
+	hh->len = htons(msgb_l2len(msg));
 }
 
 int ipa_msg_recv(int fd, struct msgb **rmsg)
@@ -35,7 +52,7 @@
 	struct ipaccess_head *hh;
 	int len, ret;
 
-	msg = ipa_msg_alloc();
+	msg = ipa_msg_alloc(0);
 	if (msg == NULL)
 		return -ENOMEM;
 
@@ -166,6 +183,8 @@
 		ofd->when &= ~BSC_FD_WRITE;
 		LOGP(DINP, LOGL_NOTICE, "connection done.\n");
 		link->state = IPA_CLIENT_LINK_STATE_CONNECTED;
+		if (link->connect_cb)
+			link->connect_cb(link);
 		break;
 	case IPA_CLIENT_LINK_STATE_CONNECTED:
 		if (what & BSC_FD_READ) {
@@ -188,6 +207,7 @@
 struct ipa_client_link *
 ipa_client_link_create(void *ctx, struct e1inp_ts *ts, const char *driver_name,
 		       int priv_nr, const char *addr, uint16_t port,
+		       int (*connect_cb)(struct ipa_client_link *link),
 		       int (*read_cb)(struct ipa_client_link *link,
 				      struct msgb *msgb),
 		       int (*write_cb)(struct ipa_client_link *link),
@@ -226,6 +246,7 @@
 	ipa_link->timer.data = ipa_link;
 	ipa_link->addr = talloc_strdup(ipa_link, addr);
 	ipa_link->port = port;
+	ipa_link->connect_cb = connect_cb;
 	ipa_link->read_cb = read_cb;
 	ipa_link->write_cb = write_cb;
 	ipa_link->line = ts->line;
diff --git a/src/input/ipaccess.c b/src/input/ipaccess.c
index 2acac89..1864683 100644
--- a/src/input/ipaccess.c
+++ b/src/input/ipaccess.c
@@ -528,7 +528,7 @@
 	return ret;
 }
 
-int handle_ts1_write(struct osmo_fd *bfd)
+static int handle_ts1_write(struct osmo_fd *bfd)
 {
 	struct e1inp_line *line = bfd->data;
 
@@ -655,28 +655,6 @@
 	return 0;
 }
 
-static struct msgb *abis_msgb_alloc(int headroom)
-{
-	struct msgb *nmsg;
-
-	headroom += sizeof(struct ipaccess_head);
-
-	nmsg = msgb_alloc_headroom(1200 + headroom, headroom, "dummy BTS");
-	if (!nmsg)
-		return NULL;
-	return nmsg;
-}
-
-static void abis_push_ipa(struct msgb *msg, uint8_t proto)
-{
-	struct ipaccess_head *nhh;
-
-	msg->l2h = msg->data;
-	nhh = (struct ipaccess_head *) msgb_push(msg, sizeof(*nhh));
-	nhh->proto = proto;
-	nhh->len = htons(msgb_l2len(msg));
-}
-
 static struct msgb *
 ipa_bts_id_resp(struct ipaccess_unit *dev, uint8_t *data, int len)
 {
@@ -684,7 +662,7 @@
 	char str[64];
 	uint8_t *tag;
 
-	nmsg = abis_msgb_alloc(0);
+	nmsg = ipa_msg_alloc(0);
 	if (!nmsg)
 		return NULL;
 
@@ -744,7 +722,7 @@
 		data += 2;
 		len -= 2;
 	}
-	abis_push_ipa(nmsg, IPAC_PROTO_IPACCESS);
+	ipa_msg_push_header(nmsg, IPAC_PROTO_IPACCESS);
 	return nmsg;
 }
 
@@ -752,12 +730,12 @@
 {
 	struct msgb *nmsg2;
 
-	nmsg2 = abis_msgb_alloc(0);
+	nmsg2 = ipa_msg_alloc(0);
 	if (!nmsg2)
 		return NULL;
 
 	*msgb_put(nmsg2, 1) = IPAC_MSGT_ID_ACK;
-	abis_push_ipa(nmsg2, IPAC_PROTO_IPACCESS);
+	ipa_msg_push_header(nmsg2, IPAC_PROTO_IPACCESS);
 
 	return nmsg2;
 }
@@ -893,6 +871,7 @@
 					      &line->ts[E1INP_SIGN_OML-1],
 					      "ipa", E1INP_SIGN_OML,
 					      addr, IPA_TCP_PORT_OML,
+					      NULL,
 					      ipaccess_bts_cb,
 					      ipaccess_bts_write_cb,
 					      NULL);
@@ -912,6 +891,7 @@
 						  &line->ts[E1INP_SIGN_RSL-1],
 						  "ipa", E1INP_SIGN_RSL,
 						  addr, IPA_TCP_PORT_RSL,
+						  NULL,
 						  ipaccess_bts_cb,
 						  ipaccess_bts_write_cb,
 						  NULL);
diff --git a/src/ipa_proxy.c b/src/ipa_proxy.c
index 2ad4654..c79b544 100644
--- a/src/ipa_proxy.c
+++ b/src/ipa_proxy.c
@@ -188,6 +188,7 @@
 	conn->dst = ipa_client_link_create(NULL, NULL, NULL, 0,
 					   route->shared->dst.inst->net.addr,
 					   route->shared->dst.inst->net.port,
+					   NULL,
 					   ipa_sock_dst_cb,
 					   ipa_client_write_default_cb,
 					   conn);