summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sys/conf/files11
-rw-r--r--sys/netbt/bt_proto.c23
-rw-r--r--sys/netbt/hci.h13
-rw-r--r--sys/netbt/hci_event.c4
-rw-r--r--sys/netbt/hci_ioctl.c312
-rw-r--r--sys/netbt/hci_link.c4
-rw-r--r--sys/netbt/hci_socket.c20
-rw-r--r--sys/netbt/hci_unit.c11
-rw-r--r--sys/netbt/l2cap.h9
-rw-r--r--sys/netbt/l2cap_misc.c17
-rw-r--r--sys/netbt/l2cap_socket.c407
-rw-r--r--sys/netbt/l2cap_upper.c505
-rw-r--r--sys/netbt/rfcomm.h425
-rw-r--r--sys/netbt/rfcomm_dlc.c414
-rw-r--r--sys/netbt/rfcomm_session.c1690
-rw-r--r--sys/netbt/rfcomm_socket.c420
-rw-r--r--sys/netbt/rfcomm_upper.c502
-rw-r--r--sys/netbt/sco.h4
-rw-r--r--sys/netbt/sco_socket.c380
-rw-r--r--sys/netbt/sco_upper.c360
20 files changed, 5476 insertions, 55 deletions
diff --git a/sys/conf/files b/sys/conf/files
index 8475d8b2303..c80941aae85 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1,4 +1,4 @@
-# $OpenBSD: files,v 1.404 2007/05/31 18:16:59 dlg Exp $
+# $OpenBSD: files,v 1.405 2007/06/01 02:46:11 uwe Exp $
# $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $
# @(#)files.newconf 7.5 (Berkeley) 5/10/93
@@ -828,6 +828,7 @@ file netatalk/ddp_usrreq.c netatalk
file netbt/bt_input.c bluetooth needs-flag
file netbt/bt_proto.c bluetooth
file netbt/hci_event.c bluetooth
+file netbt/hci_ioctl.c bluetooth
file netbt/hci_link.c bluetooth
file netbt/hci_misc.c bluetooth
file netbt/hci_socket.c bluetooth
@@ -835,6 +836,14 @@ file netbt/hci_unit.c bluetooth
file netbt/l2cap_lower.c bluetooth
file netbt/l2cap_misc.c bluetooth
file netbt/l2cap_signal.c bluetooth
+file netbt/l2cap_socket.c bluetooth
+file netbt/l2cap_upper.c bluetooth
+file netbt/rfcomm_dlc.c bluetooth
+file netbt/rfcomm_session.c bluetooth
+file netbt/rfcomm_socket.c bluetooth
+file netbt/rfcomm_upper.c bluetooth
+file netbt/sco_socket.c bluetooth
+file netbt/sco_upper.c bluetooth
file netnatm/natm_pcb.c natm
file netnatm/natm_proto.c natm
file netnatm/natm.c natm
diff --git a/sys/netbt/bt_proto.c b/sys/netbt/bt_proto.c
index e7ac31effdc..1bc8c573fae 100644
--- a/sys/netbt/bt_proto.c
+++ b/sys/netbt/bt_proto.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: bt_proto.c,v 1.2 2007/05/30 08:10:03 uwe Exp $ */
+/* $OpenBSD: bt_proto.c,v 1.3 2007/06/01 02:46:11 uwe Exp $ */
/*
* Copyright (c) 2004 Alexander Yurchenko <grange@openbsd.org>
*
@@ -22,8 +22,11 @@
#include <sys/timeout.h>
#include <netbt/bluetooth.h>
-#include <netbt/hci.h>
#include <netbt/bt_var.h>
+#include <netbt/hci.h>
+#include <netbt/l2cap.h>
+#include <netbt/rfcomm.h>
+#include <netbt/sco.h>
struct domain btdomain;
@@ -31,33 +34,31 @@ struct protosw btsw[] = {
{ SOCK_RAW, &btdomain, BTPROTO_HCI,
PR_ATOMIC | PR_ADDR,
NULL/*input*/, NULL/*output*/, NULL/*ctlinput*/,
- NULL/*hci_ctloutput*/, NULL/*hci_usrreq*/, NULL/*init*/,
+ hci_ctloutput, hci_usrreq, NULL/*init*/,
NULL/*fasttimo*/, NULL/*slowtimo*/, NULL/*drain*/,
NULL/*sysctl*/
},
-#if 0
- { SOCK_RAW, &btdomain, BLUETOOTH_PROTO_L2CAP,
- PR_ATOMIC | PR_ADDR,
+ { SOCK_SEQPACKET, &btdomain, BTPROTO_SCO,
+ PR_ATOMIC | PR_CONNREQUIRED,
NULL/*input*/, NULL/*output*/, NULL/*ctlinput*/,
- NULL/*ctloutput*/, l2cap_raw_usrreq, l2cap_raw_init,
+ sco_ctloutput, sco_usrreq, NULL/*init*/,
NULL/*fasttimo*/, NULL/*slowtimo*/, NULL/*drain*/,
NULL/*sysctl*/
},
- { SOCK_SEQPACKET, &btdomain, BLUETOOTH_PROTO_L2CAP,
+ { SOCK_SEQPACKET, &btdomain, BTPROTO_L2CAP,
PR_ATOMIC | PR_CONNREQUIRED,
NULL/*input*/, NULL/*output*/, NULL/*ctlinput*/,
l2cap_ctloutput, l2cap_usrreq, l2cap_init,
NULL/*fasttimo*/, NULL/*slowtimo*/, NULL/*drain*/,
NULL/*sysctl*/
},
- { SOCK_STREAM, &btdomain, BLUETOOTH_PROTO_RFCOMM,
- PR_ATOMIC | PR_CONNREQUIRED,
+ { SOCK_STREAM, &btdomain, BTPROTO_RFCOMM,
+ PR_CONNREQUIRED | PR_WANTRCVD,
NULL/*input*/, NULL/*output*/, NULL/*ctlinput*/,
rfcomm_ctloutput, rfcomm_usrreq, rfcomm_init,
NULL/*fasttimo*/, NULL/*slowtimo*/, NULL/*drain*/,
NULL/*sysctl*/
}
-#endif
};
struct domain btdomain = {
diff --git a/sys/netbt/hci.h b/sys/netbt/hci.h
index b6916c2a433..581451c5da8 100644
--- a/sys/netbt/hci.h
+++ b/sys/netbt/hci.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: hci.h,v 1.5 2007/05/31 23:50:19 uwe Exp $ */
+/* $OpenBSD: hci.h,v 1.6 2007/06/01 02:46:11 uwe Exp $ */
/* $NetBSD: hci.h,v 1.10 2007/04/21 06:15:23 plunky Exp $ */
/*-
@@ -55,7 +55,7 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $Id: hci.h,v 1.5 2007/05/31 23:50:19 uwe Exp $
+ * $Id: hci.h,v 1.6 2007/06/01 02:46:11 uwe Exp $
* $FreeBSD: src/sys/netgraph/bluetooth/include/ng_hci.h,v 1.6 2005/01/07 01:45:43 imp Exp $
*/
@@ -2202,7 +2202,7 @@ void hci_memo_free(struct hci_memo *);
/* hci_socket.c */
void hci_drop(void *);
-int hci_usrreq(struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *, struct proc *);
+int hci_usrreq(struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *);
int hci_ctloutput(int, struct socket *, int, int, struct mbuf **);
void hci_mtap(struct mbuf *, struct hci_unit *);
@@ -2221,6 +2221,13 @@ void hci_output_cmd(struct hci_unit *, struct mbuf *);
void hci_output_acl(struct hci_unit *, struct mbuf *);
void hci_output_sco(struct hci_unit *, struct mbuf *);
+/* XXX mimic NetBSD for now, although we don't have these interfaces */
+#define M_GETCTX(m, t) ((t)(m)->m_pkthdr.rcvif)
+#define M_SETCTX(m, c) ((m)->m_pkthdr.rcvif = (void *)(c))
+#define splraiseipl(ipl) splbio() /* XXX */
+#define ENOLINK ENOENT /* XXX */
+#define EPASSTHROUGH ENOTTY /* XXX */
+
#endif /* _KERNEL */
#endif /* _NETBT_HCI_H_ */
diff --git a/sys/netbt/hci_event.c b/sys/netbt/hci_event.c
index 98d418552f7..4807e0be570 100644
--- a/sys/netbt/hci_event.c
+++ b/sys/netbt/hci_event.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: hci_event.c,v 1.2 2007/05/31 23:50:19 uwe Exp $ */
+/* $OpenBSD: hci_event.c,v 1.3 2007/06/01 02:46:11 uwe Exp $ */
/* $NetBSD: hci_event.c,v 1.6 2007/04/21 06:15:23 plunky Exp $ */
/*-
@@ -44,8 +44,6 @@
#include <netbt/hci.h>
#include <netbt/sco.h>
-#define splraiseipl(ipl) splbio() /* XXX */
-
static void hci_event_inquiry_result(struct hci_unit *, struct mbuf *);
static void hci_event_command_status(struct hci_unit *, struct mbuf *);
static void hci_event_command_compl(struct hci_unit *, struct mbuf *);
diff --git a/sys/netbt/hci_ioctl.c b/sys/netbt/hci_ioctl.c
new file mode 100644
index 00000000000..44a6fbff94d
--- /dev/null
+++ b/sys/netbt/hci_ioctl.c
@@ -0,0 +1,312 @@
+/* $OpenBSD: hci_ioctl.c,v 1.1 2007/06/01 02:46:11 uwe Exp $ */
+/* $NetBSD: hci_ioctl.c,v 1.5 2007/01/04 19:07:03 elad Exp $ */
+
+/*-
+ * Copyright (c) 2005 Iain Hibbert.
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/domain.h>
+#include <sys/ioctl.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/hci.h>
+#include <netbt/l2cap.h>
+#include <netbt/rfcomm.h>
+
+#ifdef BLUETOOTH_DEBUG
+#define BDADDR(bd) (bd).b[5], (bd).b[4], (bd).b[3], \
+ (bd).b[2], (bd).b[1], (bd).b[0]
+
+static void
+hci_dump(void)
+{
+ struct hci_unit *unit;
+ struct hci_link *link;
+ struct l2cap_channel *chan;
+ struct rfcomm_session *rs;
+ struct rfcomm_dlc *dlc;
+
+ printf("HCI:\n");
+ TAILQ_FOREACH(unit, &hci_unit_list, hci_next) {
+ printf("UNIT %s: flags 0x%4.4x, "
+ "num_cmd=%d, num_acl=%d, num_sco=%d\n",
+ unit->hci_devname, unit->hci_flags,
+ unit->hci_num_cmd_pkts,
+ unit->hci_num_acl_pkts,
+ unit->hci_num_sco_pkts);
+ TAILQ_FOREACH(link, &unit->hci_links, hl_next) {
+ printf("+HANDLE #%d: %s "
+ "raddr=%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x, "
+ "state %d, refcnt %d\n",
+ link->hl_handle,
+ (link->hl_type == HCI_LINK_ACL ? "ACL":"SCO"),
+ BDADDR(link->hl_bdaddr),
+ link->hl_state, link->hl_refcnt);
+ }
+ }
+
+ printf("L2CAP:\n");
+ LIST_FOREACH(chan, &l2cap_active_list, lc_ncid) {
+ printf("CID #%d state %d, psm=0x%4.4x, "
+ "laddr=%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x, "
+ "raddr=%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
+ chan->lc_lcid, chan->lc_state, chan->lc_raddr.bt_psm,
+ BDADDR(chan->lc_laddr.bt_bdaddr),
+ BDADDR(chan->lc_raddr.bt_bdaddr));
+ }
+
+ LIST_FOREACH(chan, &l2cap_listen_list, lc_ncid) {
+ printf("LISTEN psm=0x%4.4x, "
+ "laddr=%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
+ chan->lc_laddr.bt_psm,
+ BDADDR(chan->lc_laddr.bt_bdaddr));
+ }
+
+ printf("RFCOMM:\n");
+ LIST_FOREACH(rs, &rfcomm_session_active, rs_next) {
+ chan = rs->rs_l2cap;
+ printf("SESSION: state=%d, flags=0x%4.4x, psm 0x%4.4x "
+ "laddr=%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x, "
+ "raddr=%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
+ rs->rs_state, rs->rs_flags, chan->lc_raddr.bt_psm,
+ BDADDR(chan->lc_laddr.bt_bdaddr),
+ BDADDR(chan->lc_raddr.bt_bdaddr));
+ LIST_FOREACH(dlc, &rs->rs_dlcs, rd_next) {
+ printf("+DLC channel=%d, dlci=%d, "
+ "state=%d, flags=0x%4.4x, rxcred=%d, rxsize=%ld, "
+ "txcred=%d, pending=%d, txqlen=%d\n",
+ dlc->rd_raddr.bt_channel, dlc->rd_dlci,
+ dlc->rd_state, dlc->rd_flags,
+ dlc->rd_rxcred, (unsigned long)dlc->rd_rxsize,
+ dlc->rd_txcred, dlc->rd_pending,
+ (dlc->rd_txbuf ? dlc->rd_txbuf->m_pkthdr.len : 0));
+ }
+ }
+
+ LIST_FOREACH(rs, &rfcomm_session_listen, rs_next) {
+ chan = rs->rs_l2cap;
+ printf("LISTEN: psm 0x%4.4x, "
+ "laddr=%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
+ chan->lc_laddr.bt_psm,
+ BDADDR(chan->lc_laddr.bt_bdaddr));
+ LIST_FOREACH(dlc, &rs->rs_dlcs, rd_next)
+ printf("+DLC channel=%d\n", dlc->rd_laddr.bt_channel);
+ }
+}
+
+#undef BDADDR
+#endif
+
+int
+hci_ioctl(unsigned long cmd, void *data, struct proc *p)
+{
+ struct btreq *btr = data;
+ struct hci_unit *unit;
+ int s, err = 0;
+
+ DPRINTFN(1, "cmd %#lx\n", cmd);
+
+ switch(cmd) {
+#ifdef BLUETOOTH_DEBUG
+ case SIOCBTDUMP:
+ hci_dump();
+ return 0;
+#endif
+ /*
+ * Get unit info based on address rather than name
+ */
+ case SIOCGBTINFOA:
+ unit = hci_unit_lookup(&btr->btr_bdaddr);
+ if (unit == NULL)
+ return ENXIO;
+
+ break;
+
+ /*
+ * The remaining ioctl's all use the same btreq structure and
+ * index on the name of the device, so we look that up first.
+ */
+ case SIOCNBTINFO:
+ /* empty name means give the first unit */
+ if (btr->btr_name[0] == '\0') {
+ unit = NULL;
+ break;
+ }
+
+ /* else fall through and look it up */
+ case SIOCGBTINFO:
+ case SIOCSBTFLAGS:
+ case SIOCSBTPOLICY:
+ case SIOCSBTPTYPE:
+ case SIOCGBTSTATS:
+ case SIOCZBTSTATS:
+ case SIOCSBTSCOMTU:
+ TAILQ_FOREACH(unit, &hci_unit_list, hci_next) {
+ if (strncmp(unit->hci_devname, btr->btr_name,
+ HCI_DEVNAME_SIZE) == 0)
+ break;
+ }
+
+ if (unit == NULL)
+ return ENXIO;
+
+ break;
+
+ default: /* not one of mine */
+ return EPASSTHROUGH;
+ }
+
+ switch(cmd) {
+ case SIOCNBTINFO: /* get next info */
+ if (unit)
+ unit = TAILQ_NEXT(unit, hci_next);
+ else
+ unit = TAILQ_FIRST(&hci_unit_list);
+
+ if (unit == NULL) {
+ err = ENXIO;
+ break;
+ }
+
+ /* and fall through to */
+ case SIOCGBTINFO: /* get unit info */
+ case SIOCGBTINFOA: /* get info by address */
+ memset(btr, 0, sizeof(struct btreq));
+ strlcpy(btr->btr_name, unit->hci_devname, HCI_DEVNAME_SIZE);
+ bdaddr_copy(&btr->btr_bdaddr, &unit->hci_bdaddr);
+
+ btr->btr_flags = unit->hci_flags;
+
+ btr->btr_num_cmd = unit->hci_num_cmd_pkts;
+ btr->btr_num_acl = unit->hci_num_acl_pkts;
+ btr->btr_num_sco = unit->hci_num_sco_pkts;
+ btr->btr_acl_mtu = unit->hci_max_acl_size;
+ btr->btr_sco_mtu = unit->hci_max_sco_size;
+
+ btr->btr_packet_type = unit->hci_packet_type;
+ btr->btr_link_policy = unit->hci_link_policy;
+ break;
+
+ case SIOCSBTFLAGS: /* set unit flags (privileged) */
+ err = suser(p, 0);
+ if (err)
+ break;
+
+ if ((unit->hci_flags & BTF_UP)
+ && (btr->btr_flags & BTF_UP) == 0) {
+ hci_disable(unit);
+ unit->hci_flags &= ~BTF_UP;
+ }
+
+ s = splraiseipl(unit->hci_ipl);
+ unit->hci_flags |= (btr->btr_flags & BTF_INIT);
+ splx(s);
+
+ if ((unit->hci_flags & BTF_UP) == 0
+ && (btr->btr_flags & BTF_UP)) {
+ err = hci_enable(unit);
+ if (err)
+ break;
+
+ s = splraiseipl(unit->hci_ipl);
+ unit->hci_flags |= BTF_UP;
+ splx(s);
+ }
+
+ btr->btr_flags = unit->hci_flags;
+ break;
+
+ case SIOCSBTPOLICY: /* set unit link policy (privileged) */
+ err = suser(p, 0);
+ if (err)
+ break;
+
+ unit->hci_link_policy = btr->btr_link_policy;
+ unit->hci_link_policy &= unit->hci_lmp_mask;
+ btr->btr_link_policy = unit->hci_link_policy;
+ break;
+
+ case SIOCSBTPTYPE: /* set unit packet types (privileged) */
+ err = suser(p, 0);
+ if (err)
+ break;
+
+ unit->hci_packet_type = btr->btr_packet_type;
+ unit->hci_packet_type &= unit->hci_acl_mask;
+ btr->btr_packet_type = unit->hci_packet_type;
+ break;
+
+ case SIOCGBTSTATS: /* get unit statistics */
+ s = splraiseipl(unit->hci_ipl);
+ memcpy(&btr->btr_stats, &unit->hci_stats,
+ sizeof(struct bt_stats));
+ splx(s);
+ break;
+
+ case SIOCZBTSTATS: /* get & reset unit statistics */
+ err = suser(p, 0);
+ if (err)
+ break;
+
+ s = splraiseipl(unit->hci_ipl);
+ memcpy(&btr->btr_stats, &unit->hci_stats,
+ sizeof(struct bt_stats));
+ memset(&unit->hci_stats, 0, sizeof(struct bt_stats));
+ splx(s);
+
+ break;
+
+ case SIOCSBTSCOMTU: /* set sco_mtu value for unit */
+ /*
+ * This is a temporary ioctl and may not be supported
+ * in the future. The need is that if SCO packets are
+ * sent to USB bluetooth controllers that are not an
+ * integer number of frame sizes, the USB bus locks up.
+ */
+ err = suser(p, 0);
+ if (err)
+ break;
+
+ unit->hci_max_sco_size = btr->btr_sco_mtu;
+ break;
+
+ default:
+ err = EFAULT;
+ break;
+ }
+
+ return err;
+}
diff --git a/sys/netbt/hci_link.c b/sys/netbt/hci_link.c
index 6542568b1f6..9b7c9955b05 100644
--- a/sys/netbt/hci_link.c
+++ b/sys/netbt/hci_link.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: hci_link.c,v 1.1 2007/05/30 03:42:53 uwe Exp $ */
+/* $OpenBSD: hci_link.c,v 1.2 2007/06/01 02:46:11 uwe Exp $ */
/* $NetBSD: hci_link.c,v 1.11 2007/04/21 06:15:23 plunky Exp $ */
/*-
@@ -748,7 +748,6 @@ hci_acl_complete(struct hci_link *link, int num)
struct hci_link *
hci_sco_newconn(struct hci_unit *unit, bdaddr_t *bdaddr)
{
-#ifdef notyet /* XXX */
struct sockaddr_bt laddr, raddr;
struct sco_pcb *pcb, *new;
struct hci_link *sco, *acl;
@@ -805,7 +804,6 @@ hci_sco_newconn(struct hci_unit *unit, bdaddr_t *bdaddr)
new->sp_mtu = unit->hci_max_sco_size;
return sco;
}
-#endif
return NULL;
}
diff --git a/sys/netbt/hci_socket.c b/sys/netbt/hci_socket.c
index a74a8b62ef4..431a44b0f4f 100644
--- a/sys/netbt/hci_socket.c
+++ b/sys/netbt/hci_socket.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: hci_socket.c,v 1.1 2007/05/30 03:42:53 uwe Exp $ */
+/* $OpenBSD: hci_socket.c,v 1.2 2007/06/01 02:46:11 uwe Exp $ */
/* $NetBSD: hci_socket.c,v 1.10 2007/03/31 18:17:13 plunky Exp $ */
/*-
@@ -218,22 +218,20 @@ static void
hci_cmdwait_flush(struct socket *so)
{
struct hci_unit *unit;
- //struct socket *ctx;
- //struct mbuf *m;
+ struct socket *ctx;
+ struct mbuf *m;
DPRINTF("flushing %p\n", so);
TAILQ_FOREACH(unit, &hci_unit_list, hci_next) {
-#ifdef notyet /* XXX */
- m = MBUFQ_FIRST(&unit->hci_cmdwait);
+ IF_POLL(&unit->hci_cmdwait, m);
while (m != NULL) {
ctx = M_GETCTX(m, struct socket *);
if (ctx == so)
M_SETCTX(m, NULL);
- m = MBUFQ_NEXT(m);
+ m = m->m_nextpkt;
}
-#endif
}
}
@@ -292,9 +290,7 @@ hci_send(struct hci_pcb *pcb, struct mbuf *m, bdaddr_t *addr)
goto bad;
}
sbappendrecord(&pcb->hp_socket->so_snd, m0);
-#ifdef notyet /* XXX */
M_SETCTX(m, pcb->hp_socket); /* enable drop callback */
-#endif
DPRINTFN(2, "(%s) opcode (%03x|%04x)\n", unit->hci_devname,
HCI_OGF(letoh16(hdr.opcode)), HCI_OCF(letoh16(hdr.opcode)));
@@ -332,7 +328,7 @@ bad:
*/
int
hci_usrreq(struct socket *up, int req, struct mbuf *m,
- struct mbuf *nam, struct mbuf *ctl, struct proc *l)
+ struct mbuf *nam, struct mbuf *ctl)
{
struct hci_pcb *pcb = (struct hci_pcb *)up->so_pcb;
struct sockaddr_bt *sa;
@@ -343,10 +339,8 @@ hci_usrreq(struct socket *up, int req, struct mbuf *m,
#endif
switch(req) {
-#ifdef notyet /* XXX */
case PRU_CONTROL:
- return hci_ioctl((unsigned long)m, (void *)nam, l);
-#endif
+ return hci_ioctl((unsigned long)m, (void *)nam, curproc);
case PRU_ATTACH:
if (pcb)
diff --git a/sys/netbt/hci_unit.c b/sys/netbt/hci_unit.c
index 5bd4aa3b5fd..355f6d300bd 100644
--- a/sys/netbt/hci_unit.c
+++ b/sys/netbt/hci_unit.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: hci_unit.c,v 1.2 2007/05/31 23:50:19 uwe Exp $ */
+/* $OpenBSD: hci_unit.c,v 1.3 2007/06/01 02:46:11 uwe Exp $ */
/* $NetBSD: hci_unit.c,v 1.4 2007/03/30 20:47:03 plunky Exp $ */
/*-
@@ -49,8 +49,6 @@
#include <netbt/bluetooth.h>
#include <netbt/hci.h>
-#define splraiseipl(ipl) splbio() /* XXX */
-
struct hci_unit_list hci_unit_list = TAILQ_HEAD_INITIALIZER(hci_unit_list);
/*
@@ -267,6 +265,7 @@ hci_send_cmd(struct hci_unit *unit, uint16_t opcode, void *buf, uint8_t len)
p->opcode = htole16(opcode);
p->length = len;
m->m_pkthdr.len = m->m_len = sizeof(hci_cmd_hdr_t);
+ M_SETCTX(m, NULL);
if (len) {
KASSERT(buf != NULL);
@@ -363,12 +362,10 @@ another:
unit->hci_devname);
TAILQ_FOREACH(link, &unit->hci_links, hl_next) {
-#ifdef notyet /* XXX */
if (link == M_GETCTX(m, struct hci_link *)) {
hci_sco_complete(link, 1);
break;
}
-#endif
}
unit->hci_num_sco_pkts++;
@@ -435,9 +432,7 @@ hci_input_sco(struct hci_unit *unit, struct mbuf *m)
void
hci_output_cmd(struct hci_unit *unit, struct mbuf *m)
{
-#ifdef notyet
void *arg;
-#endif
int s;
hci_mtap(m, unit);
@@ -451,11 +446,9 @@ hci_output_cmd(struct hci_unit *unit, struct mbuf *m)
* If context is set, this was from a HCI raw socket
* and a record needs to be dropped from the sockbuf.
*/
-#ifdef notyet /* XXX */
arg = M_GETCTX(m, void *);
if (arg != NULL)
hci_drop(arg);
-#endif
s = splraiseipl(unit->hci_ipl);
IF_ENQUEUE(&unit->hci_cmdq, m);
diff --git a/sys/netbt/l2cap.h b/sys/netbt/l2cap.h
index c99ae184e76..227a3527341 100644
--- a/sys/netbt/l2cap.h
+++ b/sys/netbt/l2cap.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: l2cap.h,v 1.3 2007/05/30 03:42:53 uwe Exp $ */
+/* $OpenBSD: l2cap.h,v 1.4 2007/06/01 02:46:11 uwe Exp $ */
/* $NetBSD: l2cap.h,v 1.5 2007/04/21 06:15:23 plunky Exp $ */
/*-
@@ -55,7 +55,7 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $Id: l2cap.h,v 1.3 2007/05/30 03:42:53 uwe Exp $
+ * $Id: l2cap.h,v 1.4 2007/06/01 02:46:11 uwe Exp $
* $FreeBSD: src/sys/netgraph/bluetooth/include/l2cap.h,v 1.4 2005/08/31 18:13:23 emax Exp $
*/
@@ -354,6 +354,8 @@ typedef union {
#ifdef _KERNEL
+#include <net/if.h> /* for struct ifqueue */
+
LIST_HEAD(l2cap_channel_list, l2cap_channel);
/* global variables */
@@ -448,6 +450,7 @@ void l2cap_recv_frame(struct mbuf *, struct hci_link *);
int l2cap_start(struct l2cap_channel *);
/* l2cap_misc.c */
+void l2cap_init(void);
int l2cap_setmode(struct l2cap_channel *);
int l2cap_cid_alloc(struct l2cap_channel *);
struct l2cap_channel *l2cap_cid_lookup(uint16_t);
@@ -464,7 +467,7 @@ int l2cap_send_disconnect_req(struct l2cap_channel *);
int l2cap_send_connect_rsp(struct hci_link *, uint8_t, uint16_t, uint16_t, uint16_t);
/* l2cap_socket.c */
-int l2cap_usrreq(struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *, struct proc *);
+int l2cap_usrreq(struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *);
int l2cap_ctloutput(int, struct socket *, int, int, struct mbuf **);
/* l2cap_upper.c */
diff --git a/sys/netbt/l2cap_misc.c b/sys/netbt/l2cap_misc.c
index 9507f8daac8..583fe70be15 100644
--- a/sys/netbt/l2cap_misc.c
+++ b/sys/netbt/l2cap_misc.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: l2cap_misc.c,v 1.1 2007/05/30 03:42:53 uwe Exp $ */
+/* $OpenBSD: l2cap_misc.c,v 1.2 2007/06/01 02:46:11 uwe Exp $ */
/* $NetBSD: l2cap_misc.c,v 1.3 2007/04/21 06:15:23 plunky Exp $ */
/*-
@@ -51,12 +51,6 @@ struct l2cap_channel_list
struct pool l2cap_req_pool;
struct pool l2cap_pdu_pool;
-#ifdef notyet /* XXX */
-POOL_INIT(l2cap_req_pool, sizeof(struct l2cap_req), 0, 0, 0, "l2cap_req", NULL,
- IPL_SOFTNET);
-POOL_INIT(l2cap_pdu_pool, sizeof(struct l2cap_pdu), 0, 0, 0, "l2cap_pdu", NULL,
- IPL_SOFTNET);
-#endif
const l2cap_qos_t l2cap_default_qos = {
0, /* flags */
@@ -74,6 +68,15 @@ const l2cap_qos_t l2cap_default_qos = {
int l2cap_response_timeout = 30; /* seconds */
int l2cap_response_extended_timeout = 180; /* seconds */
+void
+l2cap_init(void)
+{
+ pool_init(&l2cap_req_pool, sizeof(struct l2cap_req), 0, 0, 0,
+ "l2cap_req", NULL);
+ pool_init(&l2cap_pdu_pool, sizeof(struct l2cap_pdu), 0, 0, 0,
+ "l2cap_pdu", NULL);
+}
+
/*
* Set Link Mode on channel
*/
diff --git a/sys/netbt/l2cap_socket.c b/sys/netbt/l2cap_socket.c
new file mode 100644
index 00000000000..cc24a086539
--- /dev/null
+++ b/sys/netbt/l2cap_socket.c
@@ -0,0 +1,407 @@
+/* $OpenBSD: l2cap_socket.c,v 1.1 2007/06/01 02:46:11 uwe Exp $ */
+/* $NetBSD: l2cap_socket.c,v 1.7 2007/04/21 06:15:23 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2005 Iain Hibbert.
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+/* load symbolic names */
+#ifdef BLUETOOTH_DEBUG
+#define PRUREQUESTS
+#define PRCOREQUESTS
+#endif
+
+#include <sys/param.h>
+#include <sys/domain.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/systm.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/hci.h> /* XXX for EPASSTHROUGH */
+#include <netbt/l2cap.h>
+
+/*
+ * L2CAP Sockets
+ *
+ * SOCK_SEQPACKET - normal L2CAP connection
+ *
+ * SOCK_DGRAM - connectionless L2CAP - XXX not yet
+ */
+
+static void l2cap_connecting(void *);
+static void l2cap_connected(void *);
+static void l2cap_disconnected(void *, int);
+static void *l2cap_newconn(void *, struct sockaddr_bt *, struct sockaddr_bt *);
+static void l2cap_complete(void *, int);
+static void l2cap_linkmode(void *, int);
+static void l2cap_input(void *, struct mbuf *);
+
+static const struct btproto l2cap_proto = {
+ l2cap_connecting,
+ l2cap_connected,
+ l2cap_disconnected,
+ l2cap_newconn,
+ l2cap_complete,
+ l2cap_linkmode,
+ l2cap_input,
+};
+
+/* sysctl variables */
+int l2cap_sendspace = 4096;
+int l2cap_recvspace = 4096;
+
+/*
+ * User Request.
+ * up is socket
+ * m is either
+ * optional mbuf chain containing message
+ * ioctl command (PRU_CONTROL)
+ * nam is either
+ * optional mbuf chain containing an address
+ * ioctl data (PRU_CONTROL)
+ * optionally protocol number (PRU_ATTACH)
+ * message flags (PRU_RCVD)
+ * ctl is either
+ * optional mbuf chain containing socket options
+ * optional interface pointer (PRU_CONTROL, PRU_PURGEIF)
+ * l is pointer to process requesting action (if any)
+ *
+ * we are responsible for disposing of m and ctl if
+ * they are mbuf chains
+ */
+int
+l2cap_usrreq(struct socket *up, int req, struct mbuf *m,
+ struct mbuf *nam, struct mbuf *ctl)
+{
+ struct l2cap_channel *pcb = up->so_pcb;
+ struct sockaddr_bt *sa;
+ struct mbuf *m0;
+ int err = 0;
+
+#ifdef notyet /* XXX */
+ DPRINTFN(2, "%s\n", prurequests[req]);
+#endif
+
+ switch (req) {
+ case PRU_CONTROL:
+ return EPASSTHROUGH;
+
+#ifdef notyet /* XXX */
+ case PRU_PURGEIF:
+ return EOPNOTSUPP;
+#endif
+
+ case PRU_ATTACH:
+ if (pcb != NULL)
+ return EINVAL;
+
+ /*
+ * For L2CAP socket PCB we just use an l2cap_channel structure
+ * since we have nothing to add..
+ */
+ err = soreserve(up, l2cap_sendspace, l2cap_recvspace);
+ if (err)
+ return err;
+
+ return l2cap_attach((struct l2cap_channel **)&up->so_pcb,
+ &l2cap_proto, up);
+ }
+
+ if (pcb == NULL) {
+ err = EINVAL;
+ goto release;
+ }
+
+ switch(req) {
+ case PRU_DISCONNECT:
+ soisdisconnecting(up);
+ return l2cap_disconnect(pcb, up->so_linger);
+
+ case PRU_ABORT:
+ l2cap_disconnect(pcb, 0);
+ soisdisconnected(up);
+ /* fall through to */
+ case PRU_DETACH:
+ return l2cap_detach((struct l2cap_channel **)&up->so_pcb);
+
+ case PRU_BIND:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+
+ if (sa->bt_len != sizeof(struct sockaddr_bt))
+ return EINVAL;
+
+ if (sa->bt_family != AF_BLUETOOTH)
+ return EAFNOSUPPORT;
+
+ return l2cap_bind(pcb, sa);
+
+ case PRU_CONNECT:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+
+ if (sa->bt_len != sizeof(struct sockaddr_bt))
+ return EINVAL;
+
+ if (sa->bt_family != AF_BLUETOOTH)
+ return EAFNOSUPPORT;
+
+ soisconnecting(up);
+ return l2cap_connect(pcb, sa);
+
+ case PRU_PEERADDR:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+ nam->m_len = sizeof(struct sockaddr_bt);
+ return l2cap_peeraddr(pcb, sa);
+
+ case PRU_SOCKADDR:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+ nam->m_len = sizeof(struct sockaddr_bt);
+ return l2cap_sockaddr(pcb, sa);
+
+ case PRU_SHUTDOWN:
+ socantsendmore(up);
+ break;
+
+ case PRU_SEND:
+ KASSERT(m != NULL);
+ if (m->m_pkthdr.len == 0)
+ break;
+
+ if (m->m_pkthdr.len > pcb->lc_omtu) {
+ err = EMSGSIZE;
+ break;
+ }
+
+ m0 = m_copym(m, 0, M_COPYALL, M_DONTWAIT);
+ if (m0 == NULL) {
+ err = ENOMEM;
+ break;
+ }
+
+ if (ctl) /* no use for that */
+ m_freem(ctl);
+
+ sbappendrecord(&up->so_snd, m);
+ return l2cap_send(pcb, m0);
+
+ case PRU_SENSE:
+ return 0; /* (no release) */
+
+ case PRU_RCVD:
+ case PRU_RCVOOB:
+ return EOPNOTSUPP; /* (no release) */
+
+ case PRU_LISTEN:
+ return l2cap_listen(pcb);
+
+ case PRU_ACCEPT:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+ nam->m_len = sizeof(struct sockaddr_bt);
+ return l2cap_peeraddr(pcb, sa);
+
+ case PRU_CONNECT2:
+ case PRU_SENDOOB:
+ case PRU_FASTTIMO:
+ case PRU_SLOWTIMO:
+ case PRU_PROTORCV:
+ case PRU_PROTOSEND:
+ err = EOPNOTSUPP;
+ break;
+
+ default:
+ UNKNOWN(req);
+ err = EOPNOTSUPP;
+ break;
+ }
+
+release:
+ if (m) m_freem(m);
+ if (ctl) m_freem(ctl);
+ return err;
+}
+
+/*
+ * l2cap_ctloutput(request, socket, level, optname, opt)
+ *
+ * Apply configuration commands to channel. This corresponds to
+ * "Reconfigure Channel Request" in the L2CAP specification.
+ */
+int
+l2cap_ctloutput(int req, struct socket *so, int level,
+ int optname, struct mbuf **opt)
+{
+ struct l2cap_channel *pcb = so->so_pcb;
+ struct mbuf *m;
+ int err = 0;
+
+#ifdef notyet /* XXX */
+ DPRINTFN(2, "%s\n", prcorequests[req]);
+#endif
+
+ if (pcb == NULL)
+ return EINVAL;
+
+ if (level != BTPROTO_L2CAP)
+ return ENOPROTOOPT;
+
+ switch(req) {
+ case PRCO_GETOPT:
+ m = m_get(M_WAIT, MT_SOOPTS);
+ m->m_len = l2cap_getopt(pcb, optname, mtod(m, void *));
+ if (m->m_len == 0) {
+ m_freem(m);
+ m = NULL;
+ err = ENOPROTOOPT;
+ }
+ *opt = m;
+ break;
+
+ case PRCO_SETOPT:
+ m = *opt;
+ KASSERT(m != NULL);
+ err = l2cap_setopt(pcb, optname, mtod(m, void *));
+ m_freem(m);
+ break;
+
+ default:
+ err = ENOPROTOOPT;
+ break;
+ }
+
+ return err;
+}
+
+/**********************************************************************
+ *
+ * L2CAP Protocol socket callbacks
+ *
+ */
+
+static void
+l2cap_connecting(void *arg)
+{
+ struct socket *so = arg;
+
+ DPRINTF("Connecting\n");
+ soisconnecting(so);
+}
+
+static void
+l2cap_connected(void *arg)
+{
+ struct socket *so = arg;
+
+ DPRINTF("Connected\n");
+ soisconnected(so);
+}
+
+static void
+l2cap_disconnected(void *arg, int err)
+{
+ struct socket *so = arg;
+
+ DPRINTF("Disconnected (%d)\n", err);
+
+ so->so_error = err;
+ soisdisconnected(so);
+}
+
+static void *
+l2cap_newconn(void *arg, struct sockaddr_bt *laddr,
+ struct sockaddr_bt *raddr)
+{
+ struct socket *so = arg;
+
+ DPRINTF("New Connection\n");
+ so = sonewconn(so, 0);
+ if (so == NULL)
+ return NULL;
+
+ soisconnecting(so);
+
+ return so->so_pcb;
+}
+
+static void
+l2cap_complete(void *arg, int count)
+{
+ struct socket *so = arg;
+
+ while (count-- > 0)
+ sbdroprecord(&so->so_snd);
+
+ sowwakeup(so);
+}
+
+static void
+l2cap_linkmode(void *arg, int new)
+{
+ struct socket *so = arg;
+ int mode;
+
+ DPRINTF("auth %s, encrypt %s, secure %s\n",
+ (new & L2CAP_LM_AUTH ? "on" : "off"),
+ (new & L2CAP_LM_ENCRYPT ? "on" : "off"),
+ (new & L2CAP_LM_SECURE ? "on" : "off"));
+
+ (void)l2cap_getopt(so->so_pcb, SO_L2CAP_LM, &mode);
+ if (((mode & L2CAP_LM_AUTH) && !(new & L2CAP_LM_AUTH))
+ || ((mode & L2CAP_LM_ENCRYPT) && !(new & L2CAP_LM_ENCRYPT))
+ || ((mode & L2CAP_LM_SECURE) && !(new & L2CAP_LM_SECURE)))
+ l2cap_disconnect(so->so_pcb, 0);
+}
+
+static void
+l2cap_input(void *arg, struct mbuf *m)
+{
+ struct socket *so = arg;
+
+ if (m->m_pkthdr.len > sbspace(&so->so_rcv)) {
+ printf("%s: packet (%d bytes) dropped (socket buffer full)\n",
+ __func__, m->m_pkthdr.len);
+ m_freem(m);
+ return;
+ }
+
+ DPRINTFN(10, "received %d bytes\n", m->m_pkthdr.len);
+
+ sbappendrecord(&so->so_rcv, m);
+ sorwakeup(so);
+}
diff --git a/sys/netbt/l2cap_upper.c b/sys/netbt/l2cap_upper.c
new file mode 100644
index 00000000000..b33d51815f9
--- /dev/null
+++ b/sys/netbt/l2cap_upper.c
@@ -0,0 +1,505 @@
+/* $OpenBSD: l2cap_upper.c,v 1.1 2007/06/01 02:46:11 uwe Exp $ */
+/* $NetBSD: l2cap_upper.c,v 1.8 2007/04/29 20:23:36 msaitoh Exp $ */
+
+/*-
+ * Copyright (c) 2005 Iain Hibbert.
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/systm.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/hci.h>
+#include <netbt/l2cap.h>
+
+/*******************************************************************************
+ *
+ * L2CAP Channel - Upper Protocol API
+ */
+
+/*
+ * l2cap_attach(handle, btproto, upper)
+ *
+ * attach new l2cap_channel to handle, populate
+ * with reasonable defaults
+ */
+int
+l2cap_attach(struct l2cap_channel **handle,
+ const struct btproto *proto, void *upper)
+{
+ struct l2cap_channel *chan;
+
+ KASSERT(handle != NULL);
+ KASSERT(proto != NULL);
+ KASSERT(upper != NULL);
+
+ chan = malloc(sizeof(struct l2cap_channel), M_BLUETOOTH,
+ M_NOWAIT);
+ if (chan == NULL)
+ return ENOMEM;
+ bzero(chan, sizeof *chan);
+
+ chan->lc_proto = proto;
+ chan->lc_upper = upper;
+
+ chan->lc_state = L2CAP_CLOSED;
+
+ chan->lc_lcid = L2CAP_NULL_CID;
+ chan->lc_rcid = L2CAP_NULL_CID;
+
+ chan->lc_laddr.bt_len = sizeof(struct sockaddr_bt);
+ chan->lc_laddr.bt_family = AF_BLUETOOTH;
+ chan->lc_laddr.bt_psm = L2CAP_PSM_ANY;
+
+ chan->lc_raddr.bt_len = sizeof(struct sockaddr_bt);
+ chan->lc_raddr.bt_family = AF_BLUETOOTH;
+ chan->lc_raddr.bt_psm = L2CAP_PSM_ANY;
+
+ chan->lc_imtu = L2CAP_MTU_DEFAULT;
+ chan->lc_omtu = L2CAP_MTU_DEFAULT;
+ chan->lc_flush = L2CAP_FLUSH_TIMO_DEFAULT;
+
+ memcpy(&chan->lc_iqos, &l2cap_default_qos, sizeof(l2cap_qos_t));
+ memcpy(&chan->lc_oqos, &l2cap_default_qos, sizeof(l2cap_qos_t));
+
+ *handle = chan;
+ return 0;
+}
+
+/*
+ * l2cap_bind(l2cap_channel, sockaddr)
+ *
+ * set local address of channel
+ */
+int
+l2cap_bind(struct l2cap_channel *chan, struct sockaddr_bt *addr)
+{
+
+ memcpy(&chan->lc_laddr, addr, sizeof(struct sockaddr_bt));
+ return 0;
+}
+
+/*
+ * l2cap_sockaddr(l2cap_channel, sockaddr)
+ *
+ * get local address of channel
+ */
+int
+l2cap_sockaddr(struct l2cap_channel *chan, struct sockaddr_bt *addr)
+{
+
+ memcpy(addr, &chan->lc_laddr, sizeof(struct sockaddr_bt));
+ return 0;
+}
+
+/*
+ * l2cap_connect(l2cap_channel, sockaddr)
+ *
+ * Initiate a connection to destination. This corresponds to
+ * "Open Channel Request" in the L2CAP specification and will
+ * result in one of the following:
+ *
+ * proto->connected(upper)
+ * proto->disconnected(upper, error)
+ *
+ * and, optionally
+ * proto->connecting(upper)
+ */
+int
+l2cap_connect(struct l2cap_channel *chan, struct sockaddr_bt *dest)
+{
+ struct hci_unit *unit;
+ int err;
+
+ memcpy(&chan->lc_raddr, dest, sizeof(struct sockaddr_bt));
+
+ if (L2CAP_PSM_INVALID(chan->lc_raddr.bt_psm))
+ return EINVAL;
+
+ if (bdaddr_any(&chan->lc_raddr.bt_bdaddr))
+ return EDESTADDRREQ;
+
+ /* set local address if it needs setting */
+ if (bdaddr_any(&chan->lc_laddr.bt_bdaddr)) {
+ err = hci_route_lookup(&chan->lc_laddr.bt_bdaddr,
+ &chan->lc_raddr.bt_bdaddr);
+ if (err)
+ return err;
+ }
+
+ unit = hci_unit_lookup(&chan->lc_laddr.bt_bdaddr);
+ if (unit == NULL)
+ return EHOSTUNREACH;
+
+ /* attach to active list */
+ err = l2cap_cid_alloc(chan);
+ if (err)
+ return err;
+
+ /* open link to remote device */
+ chan->lc_link = hci_acl_open(unit, &chan->lc_raddr.bt_bdaddr);
+ if (chan->lc_link == NULL)
+ return EHOSTUNREACH;
+
+ /* set the link mode */
+ err = l2cap_setmode(chan);
+ if (err == EINPROGRESS) {
+ chan->lc_state = L2CAP_WAIT_SEND_CONNECT_REQ;
+ (*chan->lc_proto->connecting)(chan->lc_upper);
+ return 0;
+ }
+ if (err)
+ goto fail;
+
+ /*
+ * We can queue a connect request now even though the link may
+ * not yet be open; Our mode setting is assured, and the queue
+ * will be started automatically at the right time.
+ */
+ chan->lc_state = L2CAP_WAIT_RECV_CONNECT_RSP;
+ err = l2cap_send_connect_req(chan);
+ if (err)
+ goto fail;
+
+ return 0;
+
+fail:
+ chan->lc_state = L2CAP_CLOSED;
+ hci_acl_close(chan->lc_link, err);
+ chan->lc_link = NULL;
+ return err;
+}
+
+/*
+ * l2cap_peeraddr(l2cap_channel, sockaddr)
+ *
+ * get remote address of channel
+ */
+int
+l2cap_peeraddr(struct l2cap_channel *chan, struct sockaddr_bt *addr)
+{
+
+ memcpy(addr, &chan->lc_raddr, sizeof(struct sockaddr_bt));
+ return 0;
+}
+
+/*
+ * l2cap_disconnect(l2cap_channel, linger)
+ *
+ * Initiate L2CAP disconnection. This corresponds to
+ * "Close Channel Request" in the L2CAP specification
+ * and will result in a call to
+ *
+ * proto->disconnected(upper, error)
+ *
+ * when the disconnection is complete. If linger is set,
+ * the call will not be made until data has flushed from
+ * the queue.
+ */
+int
+l2cap_disconnect(struct l2cap_channel *chan, int linger)
+{
+ int err = 0;
+
+ if (chan->lc_state == L2CAP_CLOSED
+ || chan->lc_state == L2CAP_WAIT_DISCONNECT)
+ return EINVAL;
+
+ chan->lc_flags |= L2CAP_SHUTDOWN;
+
+ /*
+ * no need to do anything unless the queue is empty or
+ * we are not lingering..
+ */
+ if ((IF_IS_EMPTY(&chan->lc_txq) && chan->lc_pending == 0)
+ || linger == 0) {
+ chan->lc_state = L2CAP_WAIT_DISCONNECT;
+ err = l2cap_send_disconnect_req(chan);
+ if (err)
+ l2cap_close(chan, err);
+ }
+ return err;
+}
+
+/*
+ * l2cap_detach(handle)
+ *
+ * Detach l2cap channel from handle & close it down
+ */
+int
+l2cap_detach(struct l2cap_channel **handle)
+{
+ struct l2cap_channel *chan;
+
+ chan = *handle;
+ *handle = NULL;
+
+ if (chan->lc_state != L2CAP_CLOSED)
+ l2cap_close(chan, 0);
+
+ if (chan->lc_lcid != L2CAP_NULL_CID) {
+ LIST_REMOVE(chan, lc_ncid);
+ chan->lc_lcid = L2CAP_NULL_CID;
+ }
+
+ IF_PURGE(&chan->lc_txq);
+
+ /*
+ * Could implement some kind of delayed expunge to make sure that the
+ * CID is really dead before it becomes available for reuse?
+ */
+
+ free(chan, M_BLUETOOTH);
+ return 0;
+}
+
+/*
+ * l2cap_listen(l2cap_channel)
+ *
+ * Use this channel as a listening post (until detached). This will
+ * result in calls to:
+ *
+ * proto->newconn(upper, laddr, raddr)
+ *
+ * for incoming connections matching the psm and local address of the
+ * channel (NULL psm/address are permitted and match any protocol/device).
+ *
+ * The upper layer should create and return a new channel.
+ *
+ * You cannot use this channel for anything else subsequent to this call
+ */
+int
+l2cap_listen(struct l2cap_channel *chan)
+{
+ struct l2cap_channel *used, *prev = NULL;
+
+ if (chan->lc_lcid != L2CAP_NULL_CID)
+ return EINVAL;
+
+ if (chan->lc_laddr.bt_psm != L2CAP_PSM_ANY
+ && L2CAP_PSM_INVALID(chan->lc_laddr.bt_psm))
+ return EADDRNOTAVAIL;
+
+ /*
+ * This CID is irrelevant, as the channel is not stored on the active
+ * list and the socket code does not allow operations on listening
+ * sockets, but we set it so the detach code knows to LIST_REMOVE the
+ * channel.
+ */
+ chan->lc_lcid = L2CAP_SIGNAL_CID;
+
+ /*
+ * The list of listening channels is stored in an order such that new
+ * listeners dont usurp current listeners, but that specific listening
+ * takes precedence over promiscuous, and the connect request code can
+ * easily use the first matching entry.
+ */
+ LIST_FOREACH(used, &l2cap_listen_list, lc_ncid) {
+ if (used->lc_laddr.bt_psm < chan->lc_laddr.bt_psm)
+ break;
+
+ if (used->lc_laddr.bt_psm == chan->lc_laddr.bt_psm
+ && bdaddr_any(&used->lc_laddr.bt_bdaddr)
+ && !bdaddr_any(&chan->lc_laddr.bt_bdaddr))
+ break;
+
+ prev = used;
+ }
+
+ if (prev == NULL)
+ LIST_INSERT_HEAD(&l2cap_listen_list, chan, lc_ncid);
+ else
+ LIST_INSERT_AFTER(prev, chan, lc_ncid);
+
+ return 0;
+}
+
+/*
+ * l2cap_send(l2cap_channel, mbuf)
+ *
+ * Output SDU on channel described by channel. This corresponds
+ * to "Send Data Request" in the L2CAP specification. The upper
+ * layer will be notified when SDU's have completed sending by a
+ * call to:
+ *
+ * proto->complete(upper, n)
+ *
+ * (currently n == 1)
+ *
+ * Note: I'm not sure how this will work out, but I think that
+ * if outgoing Retransmission Mode or Flow Control Mode is
+ * negotiated then this call will not be made until the SDU has
+ * been acknowleged by the peer L2CAP entity. For 'Best Effort'
+ * it will be made when the packet has cleared the controller
+ * buffers.
+ *
+ * We only support Basic mode so far, so encapsulate with a
+ * B-Frame header and start sending if we are not already
+ */
+int
+l2cap_send(struct l2cap_channel *chan, struct mbuf *m)
+{
+ l2cap_hdr_t *hdr;
+ int plen;
+
+ if (chan->lc_state == L2CAP_CLOSED) {
+ m_freem(m);
+ return ENOTCONN;
+ }
+
+ plen = m->m_pkthdr.len;
+
+ DPRINTFN(5, "send %d bytes on CID #%d (pending = %d)\n",
+ plen, chan->lc_lcid, chan->lc_pending);
+
+ /* Encapsulate with B-Frame */
+ M_PREPEND(m, sizeof(l2cap_hdr_t), M_DONTWAIT);
+ if (m == NULL)
+ return ENOMEM;
+
+ hdr = mtod(m, l2cap_hdr_t *);
+ hdr->length = htole16(plen);
+ hdr->dcid = htole16(chan->lc_rcid);
+
+ /* Queue it on our list */
+ IF_ENQUEUE(&chan->lc_txq, m);
+
+ /* If we are not sending, then start doing so */
+ if (chan->lc_pending == 0)
+ return l2cap_start(chan);
+
+ return 0;
+}
+
+/*
+ * l2cap_setopt(l2cap_channel, opt, addr)
+ *
+ * Apply configuration options to channel. This corresponds to
+ * "Configure Channel Request" in the L2CAP specification.
+ *
+ * for SO_L2CAP_LM, the settings will take effect when the
+ * channel is established. If the channel is already open,
+ * a call to
+ * proto->linkmode(upper, new)
+ *
+ * will be made when the change is complete.
+ */
+int
+l2cap_setopt(struct l2cap_channel *chan, int opt, void *addr)
+{
+ int mode, err = 0;
+ uint16_t mtu;
+
+ switch (opt) {
+ case SO_L2CAP_IMTU: /* set Incoming MTU */
+ mtu = *(uint16_t *)addr;
+ if (mtu < L2CAP_MTU_MINIMUM)
+ err = EINVAL;
+ else if (chan->lc_state == L2CAP_CLOSED)
+ chan->lc_imtu = mtu;
+ else
+ err = EBUSY;
+
+ break;
+
+ case SO_L2CAP_LM: /* set link mode */
+ mode = *(int *)addr;
+ mode &= (L2CAP_LM_SECURE | L2CAP_LM_ENCRYPT | L2CAP_LM_AUTH);
+
+ if (mode & L2CAP_LM_SECURE)
+ mode |= L2CAP_LM_ENCRYPT;
+
+ if (mode & L2CAP_LM_ENCRYPT)
+ mode |= L2CAP_LM_AUTH;
+
+ chan->lc_mode = mode;
+
+ if (chan->lc_state == L2CAP_OPEN)
+ err = l2cap_setmode(chan);
+
+ break;
+
+ case SO_L2CAP_OQOS: /* set Outgoing QoS flow spec */
+ case SO_L2CAP_FLUSH: /* set Outgoing Flush Timeout */
+ default:
+ err = ENOPROTOOPT;
+ break;
+ }
+
+ return err;
+}
+
+/*
+ * l2cap_getopt(l2cap_channel, opt, addr)
+ *
+ * Return configuration parameters.
+ */
+int
+l2cap_getopt(struct l2cap_channel *chan, int opt, void *addr)
+{
+
+ switch (opt) {
+ case SO_L2CAP_IMTU: /* get Incoming MTU */
+ *(uint16_t *)addr = chan->lc_imtu;
+ return sizeof(uint16_t);
+
+ case SO_L2CAP_OMTU: /* get Outgoing MTU */
+ *(uint16_t *)addr = chan->lc_omtu;
+ return sizeof(uint16_t);
+
+ case SO_L2CAP_IQOS: /* get Incoming QoS flow spec */
+ memcpy(addr, &chan->lc_iqos, sizeof(l2cap_qos_t));
+ return sizeof(l2cap_qos_t);
+
+ case SO_L2CAP_OQOS: /* get Outgoing QoS flow spec */
+ memcpy(addr, &chan->lc_oqos, sizeof(l2cap_qos_t));
+ return sizeof(l2cap_qos_t);
+
+ case SO_L2CAP_FLUSH: /* get Flush Timeout */
+ *(uint16_t *)addr = chan->lc_flush;
+ return sizeof(uint16_t);
+
+ case SO_L2CAP_LM: /* get link mode */
+ *(int *)addr = chan->lc_mode;
+ return sizeof(int);
+
+ default:
+ break;
+ }
+
+ return 0;
+}
diff --git a/sys/netbt/rfcomm.h b/sys/netbt/rfcomm.h
new file mode 100644
index 00000000000..5af53ac4063
--- /dev/null
+++ b/sys/netbt/rfcomm.h
@@ -0,0 +1,425 @@
+/* $OpenBSD: rfcomm.h,v 1.1 2007/06/01 02:46:11 uwe Exp $ */
+/* $NetBSD: rfcomm.h,v 1.3 2007/04/21 06:15:23 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Written by Iain Hibbert for Itronix Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*-
+ * Copyright (c) 2001-2003 Maksim Yevmenkin <m_evmenkin@yahoo.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $Id: rfcomm.h,v 1.1 2007/06/01 02:46:11 uwe Exp $
+ * $FreeBSD: src/sys/netgraph/bluetooth/include/ng_btsocket_rfcomm.h,v 1.4 2005/01/11 01:39:53 emax Exp $
+ */
+
+#ifndef _NETBT_RFCOMM_H_
+#define _NETBT_RFCOMM_H_
+
+#include <sys/types.h>
+
+/*************************************************************************
+ *************************************************************************
+ ** RFCOMM **
+ *************************************************************************
+ *************************************************************************/
+
+#define RFCOMM_MTU_MAX 32767
+#define RFCOMM_MTU_MIN 23
+#define RFCOMM_MTU_DEFAULT 127
+
+#define RFCOMM_CREDITS_MAX 255 /* in any single packet */
+#define RFCOMM_CREDITS_DEFAULT 7 /* default initial value */
+
+#define RFCOMM_CHANNEL_MIN 1
+#define RFCOMM_CHANNEL_MAX 30
+
+/* RFCOMM frame types */
+#define RFCOMM_FRAME_SABM 0x2f
+#define RFCOMM_FRAME_DISC 0x43
+#define RFCOMM_FRAME_UA 0x63
+#define RFCOMM_FRAME_DM 0x0f
+#define RFCOMM_FRAME_UIH 0xef
+
+/* RFCOMM MCC commands */
+#define RFCOMM_MCC_TEST 0x08 /* Test */
+#define RFCOMM_MCC_FCON 0x28 /* Flow Control on */
+#define RFCOMM_MCC_FCOFF 0x18 /* Flow Control off */
+#define RFCOMM_MCC_MSC 0x38 /* Modem Status Command */
+#define RFCOMM_MCC_RPN 0x24 /* Remote Port Negotiation */
+#define RFCOMM_MCC_RLS 0x14 /* Remote Line Status */
+#define RFCOMM_MCC_PN 0x20 /* Port Negotiation */
+#define RFCOMM_MCC_NSC 0x04 /* Non Supported Command */
+
+/* RFCOMM modem signals */
+#define RFCOMM_MSC_FC 0x02 /* Flow Control asserted */
+#define RFCOMM_MSC_RTC 0x04 /* Ready To Communicate */
+#define RFCOMM_MSC_RTR 0x08 /* Ready To Receive */
+#define RFCOMM_MSC_IC 0x40 /* Incomming Call (RING) */
+#define RFCOMM_MSC_DV 0x80 /* Data Valid */
+
+/* RPN parameters - baud rate */
+#define RFCOMM_RPN_BR_2400 0x0
+#define RFCOMM_RPN_BR_4800 0x1
+#define RFCOMM_RPN_BR_7200 0x2
+#define RFCOMM_RPN_BR_9600 0x3
+#define RFCOMM_RPN_BR_19200 0x4
+#define RFCOMM_RPN_BR_38400 0x5
+#define RFCOMM_RPN_BR_57600 0x6
+#define RFCOMM_RPN_BR_115200 0x7
+#define RFCOMM_RPN_BR_230400 0x8
+
+/* RPN parameters - data bits */
+#define RFCOMM_RPN_DATA_5 0x0
+#define RFCOMM_RPN_DATA_6 0x1
+#define RFCOMM_RPN_DATA_7 0x2
+#define RFCOMM_RPN_DATA_8 0x3
+
+/* RPN parameters - stop bit */
+#define RFCOMM_RPN_STOP_1 0
+#define RFCOMM_RPN_STOP_15 1
+
+/* RPN parameters - parity enable */
+#define RFCOMM_RPN_PARITY_NONE 0x0
+
+/* RPN parameters - parity type */
+#define RFCOMM_RPN_PARITY_ODD 0x0
+#define RFCOMM_RPN_PARITY_EVEN 0x1
+#define RFCOMM_RPN_PARITY_MARK 0x2
+#define RFCOMM_RPN_PARITY_SPACE 0x3
+
+/* RPN parameters - default line_setting */
+#define RFCOMM_RPN_8_N_1 0x03
+
+/* RPN parameters - flow control */
+#define RFCOMM_RPN_XON_CHAR 0x11
+#define RFCOMM_RPN_XOFF_CHAR 0x13
+#define RFCOMM_RPN_FLOW_NONE 0x00
+
+/* RPN parameters - mask */
+#define RFCOMM_RPN_PM_RATE 0x0001
+#define RFCOMM_RPN_PM_DATA 0x0002
+#define RFCOMM_RPN_PM_STOP 0x0004
+#define RFCOMM_RPN_PM_PARITY 0x0008
+#define RFCOMM_RPN_PM_PTYPE 0x0010
+#define RFCOMM_RPN_PM_XON 0x0020
+#define RFCOMM_RPN_PM_XOFF 0x0040
+
+#define RFCOMM_RPN_PM_FLOW 0x3f00
+
+#define RFCOMM_RPN_PM_ALL 0x3f7f
+
+/* RFCOMM command frame header */
+struct rfcomm_cmd_hdr
+{
+ uint8_t address;
+ uint8_t control;
+ uint8_t length;
+ uint8_t fcs;
+} __attribute__ ((__packed__));
+
+/* RFCOMM MSC command */
+struct rfcomm_mcc_msc
+{
+ uint8_t address;
+ uint8_t modem;
+ uint8_t brk;
+} __attribute__ ((__packed__));
+
+/* RFCOMM RPN command */
+struct rfcomm_mcc_rpn
+{
+ uint8_t dlci;
+ uint8_t bit_rate;
+ uint8_t line_settings;
+ uint8_t flow_control;
+ uint8_t xon_char;
+ uint8_t xoff_char;
+ uint16_t param_mask;
+} __attribute__ ((__packed__));
+
+/* RFCOMM RLS command */
+struct rfcomm_mcc_rls
+{
+ uint8_t address;
+ uint8_t status;
+} __attribute__ ((__packed__));
+
+/* RFCOMM PN command */
+struct rfcomm_mcc_pn
+{
+ uint8_t dlci;
+ uint8_t flow_control;
+ uint8_t priority;
+ uint8_t ack_timer;
+ uint16_t mtu;
+ uint8_t max_retrans;
+ uint8_t credits;
+} __attribute__ ((__packed__));
+
+/* RFCOMM frame parsing macros */
+#define RFCOMM_DLCI(b) (((b) & 0xfc) >> 2)
+#define RFCOMM_TYPE(b) (((b) & 0xef))
+
+#define RFCOMM_EA(b) (((b) & 0x01))
+#define RFCOMM_CR(b) (((b) & 0x02) >> 1)
+#define RFCOMM_PF(b) (((b) & 0x10) >> 4)
+
+#define RFCOMM_CHANNEL(dlci) (((dlci) >> 1) & 0x2f)
+#define RFCOMM_DIRECTION(dlci) ((dlci) & 0x1)
+
+#define RFCOMM_MKADDRESS(cr, dlci) \
+ ((((dlci) & 0x3f) << 2) | ((cr) << 1) | 0x01)
+
+#define RFCOMM_MKCONTROL(type, pf) ((((type) & 0xef) | ((pf) << 4)))
+#define RFCOMM_MKDLCI(dir, channel) ((((channel) & 0x1f) << 1) | (dir))
+
+/* RFCOMM MCC macros */
+#define RFCOMM_MCC_TYPE(b) (((b) & 0xfc) >> 2)
+#define RFCOMM_MCC_LENGTH(b) (((b) & 0xfe) >> 1)
+#define RFCOMM_MKMCC_TYPE(cr, type) ((((type) << 2) | ((cr) << 1) | 0x01))
+
+/* RPN macros */
+#define RFCOMM_RPN_DATA_BITS(line) ((line) & 0x3)
+#define RFCOMM_RPN_STOP_BITS(line) (((line) >> 2) & 0x1)
+#define RFCOMM_RPN_PARITY(line) (((line) >> 3) & 0x1)
+
+/*************************************************************************
+ *************************************************************************
+ ** SOCK_STREAM RFCOMM sockets **
+ *************************************************************************
+ *************************************************************************/
+
+/* Socket options */
+#define SO_RFCOMM_MTU 1 /* mtu */
+#define SO_RFCOMM_FC_INFO 2 /* flow control info (below) */
+#define SO_RFCOMM_LM 3 /* link mode */
+
+/* Flow control information */
+struct rfcomm_fc_info {
+ uint8_t lmodem; /* modem signals (local) */
+ uint8_t rmodem; /* modem signals (remote) */
+ uint8_t tx_cred; /* TX credits */
+ uint8_t rx_cred; /* RX credits */
+ uint8_t cfc; /* credit flow control */
+ uint8_t reserved;
+};
+
+/* RFCOMM link mode flags */
+#define RFCOMM_LM_AUTH (1<<0) /* want authentication */
+#define RFCOMM_LM_ENCRYPT (1<<1) /* want encryption */
+#define RFCOMM_LM_SECURE (1<<2) /* want secured link */
+
+#ifdef _KERNEL
+
+/* sysctl variables */
+extern int rfcomm_sendspace;
+extern int rfcomm_recvspace;
+extern int rfcomm_mtu_default;
+extern int rfcomm_ack_timeout;
+extern int rfcomm_mcc_timeout;
+
+/*
+ * Bluetooth RFCOMM session data
+ * One L2CAP connection == one RFCOMM session
+ */
+
+/* Credit note */
+struct rfcomm_credit {
+ struct rfcomm_dlc *rc_dlc; /* owner */
+ uint16_t rc_len; /* length */
+ SIMPLEQ_ENTRY(rfcomm_credit) rc_next; /* next credit */
+};
+
+/* RFCOMM session data (one L2CAP channel) */
+struct rfcomm_session {
+ struct l2cap_channel *rs_l2cap; /* L2CAP pointer */
+ uint16_t rs_flags; /* session flags */
+ uint16_t rs_state; /* session state */
+ uint16_t rs_mtu; /* default MTU */
+
+ SIMPLEQ_HEAD(,rfcomm_credit) rs_credits; /* credit notes */
+ LIST_HEAD(,rfcomm_dlc) rs_dlcs; /* DLC list */
+
+ struct timeout rs_timeout; /* timeout */
+
+ LIST_ENTRY(rfcomm_session) rs_next; /* next session */
+};
+
+LIST_HEAD(rfcomm_session_list, rfcomm_session);
+extern struct rfcomm_session_list rfcomm_session_active;
+extern struct rfcomm_session_list rfcomm_session_listen;
+
+/* Session state */
+#define RFCOMM_SESSION_CLOSED 0
+#define RFCOMM_SESSION_WAIT_CONNECT 1
+#define RFCOMM_SESSION_OPEN 2
+#define RFCOMM_SESSION_WAIT_DISCONNECT 3
+#define RFCOMM_SESSION_LISTEN 4
+
+/* Session flags */
+#define RFCOMM_SESSION_INITIATOR (1 << 0) /* we are initiator */
+#define RFCOMM_SESSION_CFC (1 << 1) /* credit flow control */
+#define RFCOMM_SESSION_LFC (1 << 2) /* local flow control */
+#define RFCOMM_SESSION_RFC (1 << 3) /* remote flow control */
+#define RFCOMM_SESSION_FREE (1 << 4) /* self lock out for free */
+
+#define IS_INITIATOR(rs) ((rs)->rs_flags & RFCOMM_SESSION_INITIATOR)
+
+/* Bluetooth RFCOMM DLC data (connection) */
+struct rfcomm_dlc {
+ struct rfcomm_session *rd_session; /* RFCOMM session */
+ uint8_t rd_dlci; /* RFCOMM DLCI */
+
+ uint16_t rd_flags; /* DLC flags */
+ uint16_t rd_state; /* DLC state */
+ uint16_t rd_mtu; /* MTU */
+ int rd_mode; /* link mode */
+
+ struct sockaddr_bt rd_laddr; /* local address */
+ struct sockaddr_bt rd_raddr; /* remote address */
+
+ uint8_t rd_lmodem; /* local modem signls */
+ uint8_t rd_rmodem; /* remote modem signals */
+
+ int rd_rxcred; /* receive credits (sent) */
+ size_t rd_rxsize; /* receive buffer (bytes, avail) */
+ int rd_txcred; /* transmit credits (unused) */
+ int rd_pending; /* packets sent but not complete */
+
+ struct timeout rd_timeout; /* timeout */
+ struct mbuf *rd_txbuf; /* transmit buffer */
+
+ const struct btproto *rd_proto; /* upper layer callbacks */
+ void *rd_upper; /* upper layer argument */
+
+ LIST_ENTRY(rfcomm_dlc) rd_next; /* next dlc on session */
+};
+
+/*
+ * Credit Flow Control works in the following way.
+ *
+ * txcred is how many packets we can send. Received credit
+ * is added to this value, and it is decremented each time
+ * we send a packet.
+ *
+ * rxsize is the number of bytes that are available in the
+ * upstream receive buffer.
+ *
+ * rxcred is the number of credits that we have previously
+ * sent that are still unused. This value will be decreased
+ * for each packet we receive and we will add to it when we
+ * send credits. We calculate the amount of credits to send
+ * by the cunning formula "(space / mtu) - sent" so that if
+ * we get a bunch of small packets, we can continue sending
+ * credits without risking buffer overflow.
+ */
+
+/* DLC flags */
+#define RFCOMM_DLC_DETACH (1 << 0) /* DLC to be detached */
+#define RFCOMM_DLC_SHUTDOWN (1 << 1) /* DLC to be shutdown */
+
+/* DLC state */
+#define RFCOMM_DLC_CLOSED 0 /* no session */
+#define RFCOMM_DLC_WAIT_SESSION 1 /* waiting for session */
+#define RFCOMM_DLC_WAIT_CONNECT 2 /* waiting for connect */
+#define RFCOMM_DLC_WAIT_SEND_SABM 3 /* waiting to send SABM */
+#define RFCOMM_DLC_WAIT_SEND_UA 4 /* waiting to send UA */
+#define RFCOMM_DLC_WAIT_RECV_UA 5 /* waiting to receive UA */
+#define RFCOMM_DLC_OPEN 6 /* can send/receive */
+#define RFCOMM_DLC_WAIT_DISCONNECT 7 /* waiting for disconnect */
+#define RFCOMM_DLC_LISTEN 8 /* listening DLC */
+
+/*
+ * Bluetooth RFCOMM socket kernel prototypes
+ */
+
+struct socket;
+
+/* rfcomm_dlc.c */
+struct rfcomm_dlc *rfcomm_dlc_lookup(struct rfcomm_session *, int);
+struct rfcomm_dlc *rfcomm_dlc_newconn(struct rfcomm_session *, int);
+void rfcomm_dlc_close(struct rfcomm_dlc *, int);
+void rfcomm_dlc_timeout(void *);
+int rfcomm_dlc_setmode(struct rfcomm_dlc *);
+int rfcomm_dlc_connect(struct rfcomm_dlc *);
+int rfcomm_dlc_open(struct rfcomm_dlc *);
+void rfcomm_dlc_start(struct rfcomm_dlc *);
+
+/* rfcomm_session.c */
+void rfcomm_init(void);
+struct rfcomm_session *rfcomm_session_alloc(struct rfcomm_session_list *, struct sockaddr_bt *);
+struct rfcomm_session *rfcomm_session_lookup(struct sockaddr_bt *, struct sockaddr_bt *);
+void rfcomm_session_free(struct rfcomm_session *);
+int rfcomm_session_send_frame(struct rfcomm_session *, int, int);
+int rfcomm_session_send_uih(struct rfcomm_session *, struct rfcomm_dlc *, int, struct mbuf *);
+int rfcomm_session_send_mcc(struct rfcomm_session *, int, uint8_t, void *, int);
+
+/* rfcomm_socket.c */
+int rfcomm_usrreq(struct socket *, int, struct mbuf *, struct mbuf *, struct mbuf *);
+int rfcomm_ctloutput(int, struct socket *, int, int, struct mbuf **);
+
+/* rfcomm_upper.c */
+int rfcomm_attach(struct rfcomm_dlc **, const struct btproto *, void *);
+int rfcomm_bind(struct rfcomm_dlc *, struct sockaddr_bt *);
+int rfcomm_sockaddr(struct rfcomm_dlc *, struct sockaddr_bt *);
+int rfcomm_connect(struct rfcomm_dlc *, struct sockaddr_bt *);
+int rfcomm_peeraddr(struct rfcomm_dlc *, struct sockaddr_bt *);
+int rfcomm_disconnect(struct rfcomm_dlc *, int);
+int rfcomm_detach(struct rfcomm_dlc **);
+int rfcomm_listen(struct rfcomm_dlc *);
+int rfcomm_send(struct rfcomm_dlc *, struct mbuf *);
+int rfcomm_rcvd(struct rfcomm_dlc *, size_t);
+int rfcomm_setopt(struct rfcomm_dlc *, int, void *);
+int rfcomm_getopt(struct rfcomm_dlc *, int, void *);
+
+#endif /* _KERNEL */
+
+#endif /* _NETBT_RFCOMM_H_ */
diff --git a/sys/netbt/rfcomm_dlc.c b/sys/netbt/rfcomm_dlc.c
new file mode 100644
index 00000000000..0cdcaf299be
--- /dev/null
+++ b/sys/netbt/rfcomm_dlc.c
@@ -0,0 +1,414 @@
+/* $OpenBSD: rfcomm_dlc.c,v 1.1 2007/06/01 02:46:12 uwe Exp $ */
+/* $NetBSD: rfcomm_dlc.c,v 1.3 2007/04/21 06:15:23 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Written by Iain Hibbert for Itronix Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/hci.h>
+#include <netbt/l2cap.h>
+#include <netbt/rfcomm.h>
+
+/*
+ * rfcomm_dlc_lookup(rfcomm_session, dlci)
+ *
+ * Find DLC on session with matching dlci
+ */
+struct rfcomm_dlc *
+rfcomm_dlc_lookup(struct rfcomm_session *rs, int dlci)
+{
+ struct rfcomm_dlc *dlc;
+
+ LIST_FOREACH(dlc, &rs->rs_dlcs, rd_next) {
+ if (dlc->rd_dlci == dlci)
+ break;
+ }
+
+ return dlc;
+}
+
+/*
+ * rfcomm_dlc_newconn(rfcomm_session, dlci)
+ *
+ * handle a new dlc request (since its called from a couple of places)
+ */
+struct rfcomm_dlc *
+rfcomm_dlc_newconn(struct rfcomm_session *rs, int dlci)
+{
+ struct rfcomm_session *ls;
+ struct rfcomm_dlc *new, *dlc, *any, *best;
+ struct sockaddr_bt laddr, raddr, addr;
+ int chan;
+
+ /*
+ * Search amongst the listening DLC community for the best match for
+ * address & channel. We keep listening DLC's hanging on listening
+ * sessions in a last first order, so scan the entire bunch and keep
+ * a note of the best address and BDADDR_ANY matches in order to find
+ * the oldest and most specific match.
+ */
+ l2cap_sockaddr(rs->rs_l2cap, &laddr);
+ l2cap_peeraddr(rs->rs_l2cap, &raddr);
+ chan = RFCOMM_CHANNEL(dlci);
+ new = NULL;
+
+ any = best = NULL;
+ LIST_FOREACH(ls, &rfcomm_session_listen, rs_next) {
+ l2cap_sockaddr(ls->rs_l2cap, &addr);
+
+ if (addr.bt_psm != laddr.bt_psm)
+ continue;
+
+ if (bdaddr_same(&laddr.bt_bdaddr, &addr.bt_bdaddr)) {
+ LIST_FOREACH(dlc, &ls->rs_dlcs, rd_next) {
+ if (dlc->rd_laddr.bt_channel == chan)
+ best = dlc;
+ }
+ }
+
+ if (bdaddr_any(&addr.bt_bdaddr)) {
+ LIST_FOREACH(dlc, &ls->rs_dlcs, rd_next) {
+ if (dlc->rd_laddr.bt_channel == chan)
+ any = dlc;
+ }
+ }
+ }
+
+ dlc = best ? best : any;
+
+ /* XXX
+ * Note that if this fails, we could have missed a chance to open
+ * a connection - really need to rewrite the strategy for storing
+ * listening DLC's so all can be checked in turn..
+ */
+ if (dlc != NULL)
+ new = (*dlc->rd_proto->newconn)(dlc->rd_upper, &laddr, &raddr);
+
+ if (new == NULL) {
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_DM, dlci);
+ return NULL;
+ }
+
+ new->rd_dlci = dlci;
+ new->rd_mtu = rfcomm_mtu_default;
+ new->rd_mode = dlc->rd_mode;
+
+ memcpy(&new->rd_laddr, &laddr, sizeof(struct sockaddr_bt));
+ new->rd_laddr.bt_channel = chan;
+
+ memcpy(&new->rd_raddr, &raddr, sizeof(struct sockaddr_bt));
+ new->rd_raddr.bt_channel = chan;
+
+ new->rd_session = rs;
+ new->rd_state = RFCOMM_DLC_WAIT_CONNECT;
+ LIST_INSERT_HEAD(&rs->rs_dlcs, new, rd_next);
+
+ return new;
+}
+
+/*
+ * rfcomm_dlc_close(dlc, error)
+ *
+ * detach DLC from session and clean up
+ */
+void
+rfcomm_dlc_close(struct rfcomm_dlc *dlc, int err)
+{
+ struct rfcomm_session *rs;
+ struct rfcomm_credit *credit;
+
+ KASSERT(dlc->rd_state != RFCOMM_DLC_CLOSED);
+
+ /* Clear credit history */
+ rs = dlc->rd_session;
+ SIMPLEQ_FOREACH(credit, &rs->rs_credits, rc_next)
+ if (credit->rc_dlc == dlc)
+ credit->rc_dlc = NULL;
+
+ timeout_del(&dlc->rd_timeout);
+
+ LIST_REMOVE(dlc, rd_next);
+ dlc->rd_session = NULL;
+ dlc->rd_state = RFCOMM_DLC_CLOSED;
+
+ (*dlc->rd_proto->disconnected)(dlc->rd_upper, err);
+
+ /*
+ * It is the responsibility of the party who sends the last
+ * DISC(dlci) to disconnect the session, but we will schedule
+ * an expiry just in case that doesnt happen..
+ */
+ if (LIST_EMPTY(&rs->rs_dlcs)) {
+ if (rs->rs_state == RFCOMM_SESSION_LISTEN)
+ rfcomm_session_free(rs);
+ else
+ timeout_add(&rs->rs_timeout,
+ rfcomm_ack_timeout * hz);
+ }
+}
+
+/*
+ * rfcomm_dlc_timeout(dlc)
+ *
+ * DLC timeout function is schedUled when we sent any of SABM,
+ * DISC, MCC_MSC, or MCC_PN and should be cancelled when we get
+ * the relevant response. There is nothing to do but shut this
+ * DLC down.
+ */
+void
+rfcomm_dlc_timeout(void *arg)
+{
+ struct rfcomm_dlc *dlc = arg;
+ int s;
+
+ s = splsoftnet();
+
+ if (dlc->rd_state != RFCOMM_DLC_CLOSED)
+ rfcomm_dlc_close(dlc, ETIMEDOUT);
+ else if (dlc->rd_flags & RFCOMM_DLC_DETACH)
+ free(dlc, M_BLUETOOTH);
+
+ splx(s);
+}
+
+/*
+ * rfcomm_dlc_setmode(rfcomm_dlc)
+ *
+ * Set link mode for DLC. This is only called when the session is
+ * already open, so we don't need to worry about any previous mode
+ * settings.
+ */
+int
+rfcomm_dlc_setmode(struct rfcomm_dlc *dlc)
+{
+ int mode = 0;
+
+ KASSERT(dlc->rd_session != NULL);
+ KASSERT(dlc->rd_session->rs_state == RFCOMM_SESSION_OPEN);
+
+ DPRINTF("dlci %d, auth %s, encrypt %s, secure %s\n", dlc->rd_dlci,
+ (dlc->rd_mode & RFCOMM_LM_AUTH ? "yes" : "no"),
+ (dlc->rd_mode & RFCOMM_LM_ENCRYPT ? "yes" : "no"),
+ (dlc->rd_mode & RFCOMM_LM_SECURE ? "yes" : "no"));
+
+ if (dlc->rd_mode & RFCOMM_LM_AUTH)
+ mode |= L2CAP_LM_AUTH;
+
+ if (dlc->rd_mode & RFCOMM_LM_ENCRYPT)
+ mode |= L2CAP_LM_ENCRYPT;
+
+ if (dlc->rd_mode & RFCOMM_LM_SECURE)
+ mode |= L2CAP_LM_SECURE;
+
+ return l2cap_setopt(dlc->rd_session->rs_l2cap, SO_L2CAP_LM, &mode);
+}
+
+/*
+ * rfcomm_dlc_connect(rfcomm_dlc)
+ *
+ * initiate DLC connection (session is already connected)
+ */
+int
+rfcomm_dlc_connect(struct rfcomm_dlc *dlc)
+{
+ struct rfcomm_mcc_pn pn;
+ int err = 0;
+
+ KASSERT(dlc->rd_session != NULL);
+ KASSERT(dlc->rd_session->rs_state == RFCOMM_SESSION_OPEN);
+ KASSERT(dlc->rd_state == RFCOMM_DLC_WAIT_SESSION);
+
+ /*
+ * If we have not already sent a PN on the session, we must send
+ * a PN to negotiate Credit Flow Control, and this setting will
+ * apply to all future connections for this session. We ask for
+ * this every time, in order to establish initial credits.
+ */
+ memset(&pn, 0, sizeof(pn));
+ pn.dlci = dlc->rd_dlci;
+ pn.priority = dlc->rd_dlci | 0x07;
+ pn.mtu = htole16(dlc->rd_mtu);
+
+ pn.flow_control = 0xf0;
+ dlc->rd_rxcred = (dlc->rd_rxsize / dlc->rd_mtu);
+ dlc->rd_rxcred = min(dlc->rd_rxcred, RFCOMM_CREDITS_DEFAULT);
+ pn.credits = dlc->rd_rxcred;
+
+ err = rfcomm_session_send_mcc(dlc->rd_session, 1,
+ RFCOMM_MCC_PN, &pn, sizeof(pn));
+ if (err)
+ return err;
+
+ dlc->rd_state = RFCOMM_DLC_WAIT_CONNECT;
+ timeout_add(&dlc->rd_timeout, rfcomm_mcc_timeout * hz);
+
+ return 0;
+}
+
+/*
+ * rfcomm_dlc_open(rfcomm_dlc)
+ *
+ * send "Modem Status Command" and mark DLC as open.
+ */
+int
+rfcomm_dlc_open(struct rfcomm_dlc *dlc)
+{
+ struct rfcomm_mcc_msc msc;
+ int err;
+
+ KASSERT(dlc->rd_session != NULL);
+ KASSERT(dlc->rd_session->rs_state == RFCOMM_SESSION_OPEN);
+
+ memset(&msc, 0, sizeof(msc));
+ msc.address = RFCOMM_MKADDRESS(1, dlc->rd_dlci);
+ msc.modem = dlc->rd_lmodem & 0xfe; /* EA = 0 */
+ msc.brk = 0x00 | 0x01; /* EA = 1 */
+
+ err = rfcomm_session_send_mcc(dlc->rd_session, 1,
+ RFCOMM_MCC_MSC, &msc, sizeof(msc));
+ if (err)
+ return err;
+
+ timeout_add(&dlc->rd_timeout, rfcomm_mcc_timeout * hz);
+
+ dlc->rd_state = RFCOMM_DLC_OPEN;
+ (*dlc->rd_proto->connected)(dlc->rd_upper);
+
+ return 0;
+}
+
+/*
+ * rfcomm_dlc_start(rfcomm_dlc)
+ *
+ * Start sending data (and/or credits) for DLC. Our strategy is to
+ * send anything we can down to the l2cap layer. When credits run
+ * out, data will naturally bunch up. When not using credit flow
+ * control, we limit the number of packets we have pending to reduce
+ * flow control lag.
+ * We should deal with channel priority somehow.
+ */
+void
+rfcomm_dlc_start(struct rfcomm_dlc *dlc)
+{
+ struct rfcomm_session *rs = dlc->rd_session;
+ struct mbuf *m;
+ int len, credits;
+
+ KASSERT(rs != NULL);
+ KASSERT(rs->rs_state == RFCOMM_SESSION_OPEN);
+ KASSERT(dlc->rd_state == RFCOMM_DLC_OPEN);
+
+ for (;;) {
+ credits = 0;
+ len = dlc->rd_mtu;
+ if (rs->rs_flags & RFCOMM_SESSION_CFC) {
+ credits = (dlc->rd_rxsize / dlc->rd_mtu);
+ credits -= dlc->rd_rxcred;
+ credits = min(credits, RFCOMM_CREDITS_MAX);
+
+ if (credits > 0)
+ len--;
+
+ if (dlc->rd_txcred == 0)
+ len = 0;
+ } else {
+ if (rs->rs_flags & RFCOMM_SESSION_RFC)
+ break;
+
+ if (dlc->rd_rmodem & RFCOMM_MSC_FC)
+ break;
+
+ if (dlc->rd_pending > RFCOMM_CREDITS_DEFAULT)
+ break;
+ }
+
+ if (dlc->rd_txbuf == NULL)
+ len = 0;
+
+ if (len == 0) {
+ if (credits == 0)
+ break;
+
+ /*
+ * No need to send small numbers of credits on their
+ * own unless the other end hasn't many left.
+ */
+ if (credits < RFCOMM_CREDITS_DEFAULT
+ && dlc->rd_rxcred > RFCOMM_CREDITS_DEFAULT)
+ break;
+
+ m = NULL;
+ } else {
+ /*
+ * take what data we can from (front of) txbuf
+ */
+ m = dlc->rd_txbuf;
+ if (len < m->m_pkthdr.len) {
+ dlc->rd_txbuf = m_split(m, len, M_DONTWAIT);
+ if (dlc->rd_txbuf == NULL) {
+ dlc->rd_txbuf = m;
+ break;
+ }
+ } else {
+ dlc->rd_txbuf = NULL;
+ len = m->m_pkthdr.len;
+ }
+ }
+
+ DPRINTFN(10, "dlci %d send %d bytes, %d credits, rxcred = %d\n",
+ dlc->rd_dlci, len, credits, dlc->rd_rxcred);
+
+ if (rfcomm_session_send_uih(rs, dlc, credits, m)) {
+ printf("%s: lost %d bytes on DLCI %d\n",
+ __func__, len, dlc->rd_dlci);
+
+ break;
+ }
+
+ dlc->rd_pending++;
+
+ if (rs->rs_flags & RFCOMM_SESSION_CFC) {
+ if (len > 0)
+ dlc->rd_txcred--;
+
+ if (credits > 0)
+ dlc->rd_rxcred += credits;
+ }
+ }
+}
diff --git a/sys/netbt/rfcomm_session.c b/sys/netbt/rfcomm_session.c
new file mode 100644
index 00000000000..f1d88643f54
--- /dev/null
+++ b/sys/netbt/rfcomm_session.c
@@ -0,0 +1,1690 @@
+/* $OpenBSD: rfcomm_session.c,v 1.1 2007/06/01 02:46:12 uwe Exp $ */
+/* $NetBSD: rfcomm_session.c,v 1.9 2007/04/21 06:15:23 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Written by Iain Hibbert for Itronix Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+#include <sys/types.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/hci.h>
+#include <netbt/l2cap.h>
+#include <netbt/rfcomm.h>
+
+/******************************************************************************
+ *
+ * RFCOMM Multiplexer Sessions sit directly on L2CAP channels, and can
+ * multiplex up to 30 incoming and 30 outgoing connections.
+ * Only one Multiplexer is allowed between any two devices.
+ */
+
+static void rfcomm_session_timeout(void *);
+static void rfcomm_session_recv_sabm(struct rfcomm_session *, int);
+static void rfcomm_session_recv_disc(struct rfcomm_session *, int);
+static void rfcomm_session_recv_ua(struct rfcomm_session *, int);
+static void rfcomm_session_recv_dm(struct rfcomm_session *, int);
+static void rfcomm_session_recv_uih(struct rfcomm_session *, int, int, struct mbuf *, int);
+static void rfcomm_session_recv_mcc(struct rfcomm_session *, struct mbuf *);
+static void rfcomm_session_recv_mcc_test(struct rfcomm_session *, int, struct mbuf *);
+static void rfcomm_session_recv_mcc_fcon(struct rfcomm_session *, int);
+static void rfcomm_session_recv_mcc_fcoff(struct rfcomm_session *, int);
+static void rfcomm_session_recv_mcc_msc(struct rfcomm_session *, int, struct mbuf *);
+static void rfcomm_session_recv_mcc_rpn(struct rfcomm_session *, int, struct mbuf *);
+static void rfcomm_session_recv_mcc_rls(struct rfcomm_session *, int, struct mbuf *);
+static void rfcomm_session_recv_mcc_pn(struct rfcomm_session *, int, struct mbuf *);
+static void rfcomm_session_recv_mcc_nsc(struct rfcomm_session *, int, struct mbuf *);
+
+/* L2CAP callbacks */
+static void rfcomm_session_connecting(void *);
+static void rfcomm_session_connected(void *);
+static void rfcomm_session_disconnected(void *, int);
+static void *rfcomm_session_newconn(void *, struct sockaddr_bt *, struct sockaddr_bt *);
+static void rfcomm_session_complete(void *, int);
+static void rfcomm_session_linkmode(void *, int);
+static void rfcomm_session_input(void *, struct mbuf *);
+
+static const struct btproto rfcomm_session_proto = {
+ rfcomm_session_connecting,
+ rfcomm_session_connected,
+ rfcomm_session_disconnected,
+ rfcomm_session_newconn,
+ rfcomm_session_complete,
+ rfcomm_session_linkmode,
+ rfcomm_session_input,
+};
+
+struct rfcomm_session_list
+ rfcomm_session_active = LIST_HEAD_INITIALIZER(rfcomm_session_active);
+
+struct rfcomm_session_list
+ rfcomm_session_listen = LIST_HEAD_INITIALIZER(rfcomm_session_listen);
+
+struct pool rfcomm_credit_pool;
+
+/*
+ * RFCOMM System Parameters (see section 5.3)
+ */
+int rfcomm_mtu_default = 127; /* bytes */
+int rfcomm_ack_timeout = 20; /* seconds */
+int rfcomm_mcc_timeout = 20; /* seconds */
+
+/*
+ * Reversed CRC table as per TS 07.10 Annex B.3.5
+ */
+static const uint8_t crctable[256] = { /* reversed, 8-bit, poly=0x07 */
+ 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75,
+ 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b,
+ 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69,
+ 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67,
+
+ 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d,
+ 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43,
+ 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51,
+ 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f,
+
+ 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05,
+ 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b,
+ 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19,
+ 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17,
+
+ 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d,
+ 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33,
+ 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21,
+ 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f,
+
+ 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95,
+ 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b,
+ 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89,
+ 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87,
+
+ 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad,
+ 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3,
+ 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1,
+ 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf,
+
+ 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5,
+ 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb,
+ 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9,
+ 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7,
+
+ 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd,
+ 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3,
+ 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1,
+ 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf
+};
+
+#define FCS(f, d) crctable[(f) ^ (d)]
+
+/*
+ * rfcomm_init()
+ *
+ * initialize the "credit pool".
+ */
+void
+rfcomm_init(void)
+{
+ pool_init(&rfcomm_credit_pool, 0, 0, 0, 0, "rfcomm_credit", NULL);
+}
+
+/*
+ * rfcomm_session_alloc(list, sockaddr)
+ *
+ * allocate a new session and fill in the blanks, then
+ * attach session to front of specified list (active or listen)
+ */
+struct rfcomm_session *
+rfcomm_session_alloc(struct rfcomm_session_list *list,
+ struct sockaddr_bt *laddr)
+{
+ struct rfcomm_session *rs;
+ int err;
+
+ rs = malloc(sizeof(*rs), M_BLUETOOTH, M_NOWAIT);
+ if (rs == NULL)
+ return NULL;
+ bzero(rs, sizeof *rs);
+
+ rs->rs_state = RFCOMM_SESSION_CLOSED;
+
+ timeout_set(&rs->rs_timeout, rfcomm_session_timeout, rs);
+
+ SIMPLEQ_INIT(&rs->rs_credits);
+ LIST_INIT(&rs->rs_dlcs);
+
+ err = l2cap_attach(&rs->rs_l2cap, &rfcomm_session_proto, rs);
+ if (err) {
+ free(rs, M_BLUETOOTH);
+ return NULL;
+ }
+
+ (void)l2cap_getopt(rs->rs_l2cap, SO_L2CAP_OMTU, &rs->rs_mtu);
+
+ if (laddr->bt_psm == L2CAP_PSM_ANY)
+ laddr->bt_psm = L2CAP_PSM_RFCOMM;
+
+ (void)l2cap_bind(rs->rs_l2cap, laddr);
+
+ LIST_INSERT_HEAD(list, rs, rs_next);
+
+ return rs;
+}
+
+/*
+ * rfcomm_session_free(rfcomm_session)
+ *
+ * release a session, including any cleanup
+ */
+void
+rfcomm_session_free(struct rfcomm_session *rs)
+{
+ struct rfcomm_credit *credit;
+
+ KASSERT(rs != NULL);
+ KASSERT(LIST_EMPTY(&rs->rs_dlcs));
+
+ rs->rs_state = RFCOMM_SESSION_CLOSED;
+
+ /*
+ * If the callout is already invoked we have no way to stop it,
+ * but it will call us back right away (there are no DLC's) so
+ * not to worry.
+ */
+ timeout_del(&rs->rs_timeout);
+ if (timeout_triggered(&rs->rs_timeout))
+ return;
+
+ /*
+ * Take care that rfcomm_session_disconnected() doesnt call
+ * us back either as it will do if the l2cap_channel has not
+ * been closed when we detach it..
+ */
+ if (rs->rs_flags & RFCOMM_SESSION_FREE)
+ return;
+
+ rs->rs_flags |= RFCOMM_SESSION_FREE;
+
+ /* throw away any remaining credit notes */
+ while ((credit = SIMPLEQ_FIRST(&rs->rs_credits)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&rs->rs_credits, rc_next);
+ pool_put(&rfcomm_credit_pool, credit);
+ }
+
+ KASSERT(SIMPLEQ_EMPTY(&rs->rs_credits));
+
+ /* Goodbye! */
+ LIST_REMOVE(rs, rs_next);
+ l2cap_detach(&rs->rs_l2cap);
+ free(rs, M_BLUETOOTH);
+}
+
+/*
+ * rfcomm_session_lookup(sockaddr, sockaddr)
+ *
+ * Find active rfcomm session matching src and dest addresses
+ * when src is BDADDR_ANY match any local address
+ */
+struct rfcomm_session *
+rfcomm_session_lookup(struct sockaddr_bt *src, struct sockaddr_bt *dest)
+{
+ struct rfcomm_session *rs;
+ struct sockaddr_bt addr;
+
+ LIST_FOREACH(rs, &rfcomm_session_active, rs_next) {
+ if (rs->rs_state == RFCOMM_SESSION_CLOSED)
+ continue;
+
+ l2cap_sockaddr(rs->rs_l2cap, &addr);
+
+ if (bdaddr_same(&src->bt_bdaddr, &addr.bt_bdaddr) == 0)
+ if (bdaddr_any(&src->bt_bdaddr) == 0)
+ continue;
+
+ l2cap_peeraddr(rs->rs_l2cap, &addr);
+
+ if (addr.bt_psm != dest->bt_psm)
+ continue;
+
+ if (bdaddr_same(&dest->bt_bdaddr, &addr.bt_bdaddr))
+ break;
+ }
+
+ return rs;
+}
+
+/*
+ * rfcomm_session_timeout(rfcomm_session)
+ *
+ * Session timeouts are scheduled when a session is left or
+ * created with no DLCs, and when SABM(0) or DISC(0) are
+ * sent.
+ *
+ * So, if it is in an open state with DLC's attached then
+ * we leave it alone, otherwise the session is lost.
+ */
+static void
+rfcomm_session_timeout(void *arg)
+{
+ struct rfcomm_session *rs = arg;
+ struct rfcomm_dlc *dlc;
+ int s;
+
+ KASSERT(rs != NULL);
+
+ s = splsoftnet();
+
+ if (rs->rs_state != RFCOMM_SESSION_OPEN) {
+ DPRINTF("timeout\n");
+ rs->rs_state = RFCOMM_SESSION_CLOSED;
+
+ while (!LIST_EMPTY(&rs->rs_dlcs)) {
+ dlc = LIST_FIRST(&rs->rs_dlcs);
+
+ rfcomm_dlc_close(dlc, ETIMEDOUT);
+ }
+ }
+
+ if (LIST_EMPTY(&rs->rs_dlcs)) {
+ DPRINTF("expiring\n");
+ rfcomm_session_free(rs);
+ }
+ splx(s);
+}
+
+/***********************************************************************
+ *
+ * RFCOMM Session L2CAP protocol callbacks
+ *
+ */
+
+static void
+rfcomm_session_connecting(void *arg)
+{
+ /* struct rfcomm_session *rs = arg; */
+
+ DPRINTF("Connecting\n");
+}
+
+static void
+rfcomm_session_connected(void *arg)
+{
+ struct rfcomm_session *rs = arg;
+
+ DPRINTF("Connected\n");
+
+ /*
+ * L2CAP is open.
+ *
+ * If we are initiator, we can send our SABM(0)
+ * a timeout should be active?
+ *
+ * We must take note of the L2CAP MTU because currently
+ * the L2CAP implementation can only do Basic Mode.
+ */
+ l2cap_getopt(rs->rs_l2cap, SO_L2CAP_OMTU, &rs->rs_mtu);
+
+ rs->rs_mtu -= 6; /* (RFCOMM overhead could be this big) */
+ if (rs->rs_mtu < RFCOMM_MTU_MIN) {
+ rfcomm_session_disconnected(rs, EINVAL);
+ return;
+ }
+
+ if (IS_INITIATOR(rs)) {
+ int err;
+
+ err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_SABM, 0);
+ if (err)
+ rfcomm_session_disconnected(rs, err);
+
+ timeout_add(&rs->rs_timeout, rfcomm_ack_timeout * hz);
+ }
+}
+
+static void
+rfcomm_session_disconnected(void *arg, int err)
+{
+ struct rfcomm_session *rs = arg;
+ struct rfcomm_dlc *dlc;
+
+ DPRINTF("Disconnected\n");
+
+ rs->rs_state = RFCOMM_SESSION_CLOSED;
+
+ while (!LIST_EMPTY(&rs->rs_dlcs)) {
+ dlc = LIST_FIRST(&rs->rs_dlcs);
+
+ rfcomm_dlc_close(dlc, err);
+ }
+
+ rfcomm_session_free(rs);
+}
+
+static void *
+rfcomm_session_newconn(void *arg, struct sockaddr_bt *laddr,
+ struct sockaddr_bt *raddr)
+{
+ struct rfcomm_session *new, *rs = arg;
+
+ DPRINTF("New Connection\n");
+
+ /*
+ * Incoming session connect request. We should return a new
+ * session pointer if this is acceptable. The L2CAP layer
+ * passes local and remote addresses, which we must check as
+ * only one RFCOMM session is allowed between any two devices
+ */
+ new = rfcomm_session_lookup(laddr, raddr);
+ if (new != NULL)
+ return NULL;
+
+ new = rfcomm_session_alloc(&rfcomm_session_active, laddr);
+ if (new == NULL)
+ return NULL;
+
+ new->rs_mtu = rs->rs_mtu;
+ new->rs_state = RFCOMM_SESSION_WAIT_CONNECT;
+
+ /*
+ * schedule an expiry so that if nothing comes of it we
+ * can punt.
+ */
+ timeout_add(&new->rs_timeout, rfcomm_mcc_timeout * hz);
+
+ return new->rs_l2cap;
+}
+
+static void
+rfcomm_session_complete(void *arg, int count)
+{
+ struct rfcomm_session *rs = arg;
+ struct rfcomm_credit *credit;
+ struct rfcomm_dlc *dlc;
+
+ /*
+ * count L2CAP packets are 'complete', meaning that they are cleared
+ * our buffers (for best effort) or arrived safe (for guaranteed) so
+ * we can take it off our list and pass the message on, so that
+ * eventually the data can be removed from the sockbuf
+ */
+ while (count-- > 0) {
+ credit = SIMPLEQ_FIRST(&rs->rs_credits);
+#ifdef DIAGNOSTIC
+ if (credit == NULL) {
+ printf("%s: too many packets completed!\n", __func__);
+ break;
+ }
+#endif
+ dlc = credit->rc_dlc;
+ if (dlc != NULL) {
+ dlc->rd_pending--;
+ (*dlc->rd_proto->complete)
+ (dlc->rd_upper, credit->rc_len);
+
+ /*
+ * if not using credit flow control, we may push
+ * more data now
+ */
+ if ((rs->rs_flags & RFCOMM_SESSION_CFC) == 0
+ && dlc->rd_state == RFCOMM_DLC_OPEN) {
+ rfcomm_dlc_start(dlc);
+ }
+
+ /*
+ * When shutdown is indicated, we are just waiting to
+ * clear outgoing data.
+ */
+ if ((dlc->rd_flags & RFCOMM_DLC_SHUTDOWN)
+ && dlc->rd_txbuf == NULL && dlc->rd_pending == 0) {
+ dlc->rd_state = RFCOMM_DLC_WAIT_DISCONNECT;
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC,
+ dlc->rd_dlci);
+ timeout_add(&dlc->rd_timeout,
+ rfcomm_ack_timeout * hz);
+ }
+ }
+
+ SIMPLEQ_REMOVE_HEAD(&rs->rs_credits, rc_next);
+ pool_put(&rfcomm_credit_pool, credit);
+ }
+
+ /*
+ * If session is closed, we are just waiting to clear the queue
+ */
+ if (rs->rs_state == RFCOMM_SESSION_CLOSED) {
+ if (SIMPLEQ_EMPTY(&rs->rs_credits))
+ l2cap_disconnect(rs->rs_l2cap, 0);
+ }
+}
+
+/*
+ * Link Mode changed
+ *
+ * This is called when a mode change is complete. Proceed with connections
+ * where appropriate, or pass the new mode to any active DLCs.
+ */
+static void
+rfcomm_session_linkmode(void *arg, int new)
+{
+ struct rfcomm_session *rs = arg;
+ struct rfcomm_dlc *dlc, *next;
+ int err, mode = 0;
+
+ DPRINTF("auth %s, encrypt %s, secure %s\n",
+ (new & L2CAP_LM_AUTH ? "on" : "off"),
+ (new & L2CAP_LM_ENCRYPT ? "on" : "off"),
+ (new & L2CAP_LM_SECURE ? "on" : "off"));
+
+ if (new & L2CAP_LM_AUTH)
+ mode |= RFCOMM_LM_AUTH;
+
+ if (new & L2CAP_LM_ENCRYPT)
+ mode |= RFCOMM_LM_ENCRYPT;
+
+ if (new & L2CAP_LM_SECURE)
+ mode |= RFCOMM_LM_SECURE;
+
+ next = LIST_FIRST(&rs->rs_dlcs);
+ while ((dlc = next) != NULL) {
+ next = LIST_NEXT(dlc, rd_next);
+
+ switch (dlc->rd_state) {
+ case RFCOMM_DLC_WAIT_SEND_SABM: /* we are connecting */
+ if ((mode & dlc->rd_mode) != dlc->rd_mode) {
+ rfcomm_dlc_close(dlc, ECONNABORTED);
+ } else {
+ err = rfcomm_session_send_frame(rs,
+ RFCOMM_FRAME_SABM, dlc->rd_dlci);
+ if (err) {
+ rfcomm_dlc_close(dlc, err);
+ } else {
+ dlc->rd_state = RFCOMM_DLC_WAIT_RECV_UA;
+ timeout_add(&dlc->rd_timeout,
+ rfcomm_ack_timeout * hz);
+ break;
+ }
+ }
+
+ /*
+ * If we aborted the connection and there are no more DLCs
+ * on the session, it is our responsibility to disconnect.
+ */
+ if (!LIST_EMPTY(&rs->rs_dlcs))
+ break;
+
+ rs->rs_state = RFCOMM_SESSION_WAIT_DISCONNECT;
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC, 0);
+ timeout_add(&rs->rs_timeout, rfcomm_ack_timeout * hz);
+ break;
+
+ case RFCOMM_DLC_WAIT_SEND_UA: /* they are connecting */
+ if ((mode & dlc->rd_mode) != dlc->rd_mode) {
+ rfcomm_session_send_frame(rs,
+ RFCOMM_FRAME_DM, dlc->rd_dlci);
+ rfcomm_dlc_close(dlc, ECONNABORTED);
+ break;
+ }
+
+ err = rfcomm_session_send_frame(rs,
+ RFCOMM_FRAME_UA, dlc->rd_dlci);
+ if (err) {
+ rfcomm_session_send_frame(rs,
+ RFCOMM_FRAME_DM, dlc->rd_dlci);
+ rfcomm_dlc_close(dlc, err);
+ break;
+ }
+
+ err = rfcomm_dlc_open(dlc);
+ if (err) {
+ rfcomm_session_send_frame(rs,
+ RFCOMM_FRAME_DM, dlc->rd_dlci);
+ rfcomm_dlc_close(dlc, err);
+ break;
+ }
+
+ break;
+
+ case RFCOMM_DLC_WAIT_RECV_UA:
+ case RFCOMM_DLC_OPEN: /* already established */
+ (*dlc->rd_proto->linkmode)(dlc->rd_upper, mode);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/*
+ * Receive data from L2CAP layer for session. There is always exactly one
+ * RFCOMM frame contained in each L2CAP frame.
+ */
+static void
+rfcomm_session_input(void *arg, struct mbuf *m)
+{
+ struct rfcomm_session *rs = arg;
+ int dlci, len, type, pf;
+ uint8_t fcs, b;
+
+ KASSERT(m != NULL);
+ KASSERT(rs != NULL);
+
+ /*
+ * UIH frames: FCS is only calculated on address and control fields
+ * For other frames: FCS is calculated on address, control and length
+ * Length may extend to two octets
+ */
+ fcs = 0xff;
+
+ if (m->m_pkthdr.len < 4) {
+ DPRINTF("short frame (%d), discarded\n", m->m_pkthdr.len);
+ goto done;
+ }
+
+ /* address - one octet */
+ m_copydata(m, 0, 1, &b);
+ m_adj(m, 1);
+ fcs = FCS(fcs, b);
+ dlci = RFCOMM_DLCI(b);
+
+ /* control - one octet */
+ m_copydata(m, 0, 1, &b);
+ m_adj(m, 1);
+ fcs = FCS(fcs, b);
+ type = RFCOMM_TYPE(b);
+ pf = RFCOMM_PF(b);
+
+ /* length - may be two octets */
+ m_copydata(m, 0, 1, &b);
+ m_adj(m, 1);
+ if (type != RFCOMM_FRAME_UIH)
+ fcs = FCS(fcs, b);
+ len = (b >> 1) & 0x7f;
+
+ if (RFCOMM_EA(b) == 0) {
+ if (m->m_pkthdr.len < 2) {
+ DPRINTF("short frame (%d, EA = 0), discarded\n",
+ m->m_pkthdr.len);
+ goto done;
+ }
+
+ m_copydata(m, 0, 1, &b);
+ m_adj(m, 1);
+ if (type != RFCOMM_FRAME_UIH)
+ fcs = FCS(fcs, b);
+
+ len |= (b << 7);
+ }
+
+ /* FCS byte is last octet in frame */
+ m_copydata(m, m->m_pkthdr.len - 1, 1, &b);
+ m_adj(m, -1);
+ fcs = FCS(fcs, b);
+
+ if (fcs != 0xcf) {
+ DPRINTF("Bad FCS value (%#2.2x), frame discarded\n", fcs);
+ goto done;
+ }
+
+ DPRINTFN(10, "dlci %d, type %2.2x, len = %d\n", dlci, type, len);
+
+ switch (type) {
+ case RFCOMM_FRAME_SABM:
+ if (pf)
+ rfcomm_session_recv_sabm(rs, dlci);
+ break;
+
+ case RFCOMM_FRAME_DISC:
+ if (pf)
+ rfcomm_session_recv_disc(rs, dlci);
+ break;
+
+ case RFCOMM_FRAME_UA:
+ if (pf)
+ rfcomm_session_recv_ua(rs, dlci);
+ break;
+
+ case RFCOMM_FRAME_DM:
+ rfcomm_session_recv_dm(rs, dlci);
+ break;
+
+ case RFCOMM_FRAME_UIH:
+ rfcomm_session_recv_uih(rs, dlci, pf, m, len);
+ return; /* (no release) */
+
+ default:
+ UNKNOWN(type);
+ break;
+ }
+
+done:
+ m_freem(m);
+}
+
+/***********************************************************************
+ *
+ * RFCOMM Session receive processing
+ */
+
+/*
+ * rfcomm_session_recv_sabm(rfcomm_session, dlci)
+ *
+ * Set Asyncrhonous Balanced Mode - open the channel.
+ */
+static void
+rfcomm_session_recv_sabm(struct rfcomm_session *rs, int dlci)
+{
+ struct rfcomm_dlc *dlc;
+ int err;
+
+ DPRINTFN(5, "SABM(%d)\n", dlci);
+
+ if (dlci == 0) { /* Open Session */
+ rs->rs_state = RFCOMM_SESSION_OPEN;
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_UA, 0);
+ LIST_FOREACH(dlc, &rs->rs_dlcs, rd_next) {
+ if (dlc->rd_state == RFCOMM_DLC_WAIT_SESSION)
+ rfcomm_dlc_connect(dlc);
+ }
+ return;
+ }
+
+ if (rs->rs_state != RFCOMM_SESSION_OPEN) {
+ DPRINTF("session was not even open!\n");
+ return;
+ }
+
+ /* validate direction bit */
+ if ((IS_INITIATOR(rs) && !RFCOMM_DIRECTION(dlci))
+ || (!IS_INITIATOR(rs) && RFCOMM_DIRECTION(dlci))) {
+ DPRINTF("Invalid direction bit on DLCI\n");
+ return;
+ }
+
+ /*
+ * look for our DLC - this may exist if we received PN
+ * already, or we may have to fabricate a new one.
+ */
+ dlc = rfcomm_dlc_lookup(rs, dlci);
+ if (dlc == NULL) {
+ dlc = rfcomm_dlc_newconn(rs, dlci);
+ if (dlc == NULL)
+ return; /* (DM is sent) */
+ }
+
+ /*
+ * ..but if this DLC is not waiting to connect, they did
+ * something wrong, ignore it.
+ */
+ if (dlc->rd_state != RFCOMM_DLC_WAIT_CONNECT)
+ return;
+
+ /* set link mode */
+ err = rfcomm_dlc_setmode(dlc);
+ if (err == EINPROGRESS) {
+ dlc->rd_state = RFCOMM_DLC_WAIT_SEND_UA;
+ (*dlc->rd_proto->connecting)(dlc->rd_upper);
+ return;
+ }
+ if (err)
+ goto close;
+
+ err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_UA, dlci);
+ if (err)
+ goto close;
+
+ /* and mark it open */
+ err = rfcomm_dlc_open(dlc);
+ if (err)
+ goto close;
+
+ return;
+
+close:
+ rfcomm_dlc_close(dlc, err);
+}
+
+/*
+ * Receive Disconnect Command
+ */
+static void
+rfcomm_session_recv_disc(struct rfcomm_session *rs, int dlci)
+{
+ struct rfcomm_dlc *dlc;
+
+ DPRINTFN(5, "DISC(%d)\n", dlci);
+
+ if (dlci == 0) {
+ /*
+ * Disconnect Session
+ *
+ * We set the session state to CLOSED so that when
+ * the UA frame is clear the session will be closed
+ * automatically. We wont bother to close any DLC's
+ * just yet as there should be none. In the unlikely
+ * event that something is left, it will get flushed
+ * out as the session goes down.
+ */
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_UA, 0);
+ rs->rs_state = RFCOMM_SESSION_CLOSED;
+ return;
+ }
+
+ dlc = rfcomm_dlc_lookup(rs, dlci);
+ if (dlc == NULL) {
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_DM, dlci);
+ return;
+ }
+
+ rfcomm_dlc_close(dlc, ECONNRESET);
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_UA, dlci);
+}
+
+/*
+ * Receive Unnumbered Acknowledgement Response
+ *
+ * This should be a response to a DISC or SABM frame that we
+ * have previously sent. If unexpected, ignore it.
+ */
+static void
+rfcomm_session_recv_ua(struct rfcomm_session *rs, int dlci)
+{
+ struct rfcomm_dlc *dlc;
+
+ DPRINTFN(5, "UA(%d)\n", dlci);
+
+ if (dlci == 0) {
+ switch (rs->rs_state) {
+ case RFCOMM_SESSION_WAIT_CONNECT: /* We sent SABM */
+ timeout_del(&rs->rs_timeout);
+ rs->rs_state = RFCOMM_SESSION_OPEN;
+ LIST_FOREACH(dlc, &rs->rs_dlcs, rd_next) {
+ if (dlc->rd_state == RFCOMM_DLC_WAIT_SESSION)
+ rfcomm_dlc_connect(dlc);
+ }
+ break;
+
+ case RFCOMM_SESSION_WAIT_DISCONNECT: /* We sent DISC */
+ timeout_del(&rs->rs_timeout);
+ rs->rs_state = RFCOMM_SESSION_CLOSED;
+ l2cap_disconnect(rs->rs_l2cap, 0);
+ break;
+
+ default:
+ DPRINTF("Received spurious UA(0)!\n");
+ break;
+ }
+
+ return;
+ }
+
+ /*
+ * If we have no DLC on this dlci, we may have aborted
+ * without shutting down properly, so check if the session
+ * needs disconnecting.
+ */
+ dlc = rfcomm_dlc_lookup(rs, dlci);
+ if (dlc == NULL)
+ goto check;
+
+ switch (dlc->rd_state) {
+ case RFCOMM_DLC_WAIT_RECV_UA: /* We sent SABM */
+ rfcomm_dlc_open(dlc);
+ return;
+
+ case RFCOMM_DLC_WAIT_DISCONNECT: /* We sent DISC */
+ rfcomm_dlc_close(dlc, 0);
+ break;
+
+ default:
+ DPRINTF("Received spurious UA(%d)!\n", dlci);
+ return;
+ }
+
+check: /* last one out turns out the light */
+ if (LIST_EMPTY(&rs->rs_dlcs)) {
+ rs->rs_state = RFCOMM_SESSION_WAIT_DISCONNECT;
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC, 0);
+ timeout_add(&rs->rs_timeout, rfcomm_ack_timeout * hz);
+ }
+}
+
+/*
+ * Receive Disconnected Mode Response
+ *
+ * If this does not apply to a known DLC then we may ignore it.
+ */
+static void
+rfcomm_session_recv_dm(struct rfcomm_session *rs, int dlci)
+{
+ struct rfcomm_dlc *dlc;
+
+ DPRINTFN(5, "DM(%d)\n", dlci);
+
+ dlc = rfcomm_dlc_lookup(rs, dlci);
+ if (dlc == NULL)
+ return;
+
+ if (dlc->rd_state == RFCOMM_DLC_WAIT_CONNECT)
+ rfcomm_dlc_close(dlc, ECONNREFUSED);
+ else
+ rfcomm_dlc_close(dlc, ECONNRESET);
+}
+
+/*
+ * Receive Unnumbered Information with Header check (MCC or data packet)
+ */
+static void
+rfcomm_session_recv_uih(struct rfcomm_session *rs, int dlci,
+ int pf, struct mbuf *m, int len)
+{
+ struct rfcomm_dlc *dlc;
+ uint8_t credits = 0;
+
+ DPRINTFN(10, "UIH(%d)\n", dlci);
+
+ if (dlci == 0) {
+ rfcomm_session_recv_mcc(rs, m);
+ return;
+ }
+
+ if (m->m_pkthdr.len != len + pf) {
+ DPRINTF("Bad Frame Length (%d), frame discarded\n",
+ m->m_pkthdr.len);
+
+ goto discard;
+ }
+
+ dlc = rfcomm_dlc_lookup(rs, dlci);
+ if (dlc == NULL) {
+ DPRINTF("UIH received for non existent DLC, discarded\n");
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_DM, dlci);
+ goto discard;
+ }
+
+ if (dlc->rd_state != RFCOMM_DLC_OPEN) {
+ DPRINTF("non-open DLC (state = %d), discarded\n",
+ dlc->rd_state);
+ goto discard;
+ }
+
+ /* if PF is set, credits were included */
+ if (rs->rs_flags & RFCOMM_SESSION_CFC) {
+ if (pf != 0) {
+ if (m->m_pkthdr.len < sizeof(credits)) {
+ DPRINTF("Bad PF value, UIH discarded\n");
+ goto discard;
+ }
+
+ m_copydata(m, 0, sizeof(credits), &credits);
+ m_adj(m, sizeof(credits));
+
+ dlc->rd_txcred += credits;
+
+ if (credits > 0 && dlc->rd_txbuf != NULL)
+ rfcomm_dlc_start(dlc);
+ }
+
+ if (len == 0)
+ goto discard;
+
+ if (dlc->rd_rxcred == 0) {
+ DPRINTF("Credit limit reached, UIH discarded\n");
+ goto discard;
+ }
+
+ if (len > dlc->rd_rxsize) {
+ DPRINTF("UIH frame exceeds rxsize, discarded\n");
+ goto discard;
+ }
+
+ dlc->rd_rxcred--;
+ dlc->rd_rxsize -= len;
+ }
+
+ (*dlc->rd_proto->input)(dlc->rd_upper, m);
+ return;
+
+discard:
+ m_freem(m);
+}
+
+/*
+ * Receive Multiplexer Control Command
+ */
+static void
+rfcomm_session_recv_mcc(struct rfcomm_session *rs, struct mbuf *m)
+{
+ int type, cr, len;
+ uint8_t b;
+
+ /*
+ * Extract MCC header.
+ *
+ * Fields are variable length using extension bit = 1 to signify the
+ * last octet in the sequence.
+ *
+ * Only single octet types are defined in TS 07.10/RFCOMM spec
+ *
+ * Length can realistically only use 15 bits (max RFCOMM MTU)
+ */
+ if (m->m_pkthdr.len < sizeof(b)) {
+ DPRINTF("Short MCC header, discarded\n");
+ goto release;
+ }
+
+ m_copydata(m, 0, sizeof(b), &b);
+ m_adj(m, sizeof(b));
+
+ if (RFCOMM_EA(b) == 0) { /* verify no extensions */
+ DPRINTF("MCC type EA = 0, discarded\n");
+ goto release;
+ }
+
+ type = RFCOMM_MCC_TYPE(b);
+ cr = RFCOMM_CR(b);
+
+ len = 0;
+ do {
+ if (m->m_pkthdr.len < sizeof(b)) {
+ DPRINTF("Short MCC header, discarded\n");
+ goto release;
+ }
+
+ m_copydata(m, 0, sizeof(b), &b);
+ m_adj(m, sizeof(b));
+
+ len = (len << 7) | (b >> 1);
+ len = min(len, RFCOMM_MTU_MAX);
+ } while (RFCOMM_EA(b) == 0);
+
+ if (len != m->m_pkthdr.len) {
+ DPRINTF("Incorrect MCC length, discarded\n");
+ goto release;
+ }
+
+ DPRINTFN(2, "MCC %s type %2.2x (%d bytes)\n",
+ (cr ? "command" : "response"), type, len);
+
+ /*
+ * pass to command handler
+ */
+ switch(type) {
+ case RFCOMM_MCC_TEST: /* Test */
+ rfcomm_session_recv_mcc_test(rs, cr, m);
+ break;
+
+ case RFCOMM_MCC_FCON: /* Flow Control On */
+ rfcomm_session_recv_mcc_fcon(rs, cr);
+ break;
+
+ case RFCOMM_MCC_FCOFF: /* Flow Control Off */
+ rfcomm_session_recv_mcc_fcoff(rs, cr);
+ break;
+
+ case RFCOMM_MCC_MSC: /* Modem Status Command */
+ rfcomm_session_recv_mcc_msc(rs, cr, m);
+ break;
+
+ case RFCOMM_MCC_RPN: /* Remote Port Negotiation */
+ rfcomm_session_recv_mcc_rpn(rs, cr, m);
+ break;
+
+ case RFCOMM_MCC_RLS: /* Remote Line Status */
+ rfcomm_session_recv_mcc_rls(rs, cr, m);
+ break;
+
+ case RFCOMM_MCC_PN: /* Parameter Negotiation */
+ rfcomm_session_recv_mcc_pn(rs, cr, m);
+ break;
+
+ case RFCOMM_MCC_NSC: /* Non Supported Command */
+ rfcomm_session_recv_mcc_nsc(rs, cr, m);
+ break;
+
+ default:
+ b = RFCOMM_MKMCC_TYPE(cr, type);
+ rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_NSC, &b, sizeof(b));
+ }
+
+release:
+ m_freem(m);
+}
+
+/*
+ * process TEST command/response
+ */
+static void
+rfcomm_session_recv_mcc_test(struct rfcomm_session *rs, int cr, struct mbuf *m)
+{
+ void *data;
+ int len;
+
+ if (cr == 0) /* ignore ack */
+ return;
+
+ /*
+ * we must send all the data they included back as is
+ */
+
+ len = m->m_pkthdr.len;
+ if (len > RFCOMM_MTU_MAX)
+ return;
+
+ data = malloc(len, M_BLUETOOTH, M_NOWAIT);
+ if (data == NULL)
+ return;
+
+ m_copydata(m, 0, len, data);
+ rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_TEST, data, len);
+ free(data, M_BLUETOOTH);
+}
+
+/*
+ * process Flow Control ON command/response
+ */
+static void
+rfcomm_session_recv_mcc_fcon(struct rfcomm_session *rs, int cr)
+{
+
+ if (cr == 0) /* ignore ack */
+ return;
+
+ rs->rs_flags |= RFCOMM_SESSION_RFC;
+ rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_FCON, NULL, 0);
+}
+
+/*
+ * process Flow Control OFF command/response
+ */
+static void
+rfcomm_session_recv_mcc_fcoff(struct rfcomm_session *rs, int cr)
+{
+
+ if (cr == 0) /* ignore ack */
+ return;
+
+ rs->rs_flags &= ~RFCOMM_SESSION_RFC;
+ rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_FCOFF, NULL, 0);
+}
+
+/*
+ * process Modem Status Command command/response
+ */
+static void
+rfcomm_session_recv_mcc_msc(struct rfcomm_session *rs, int cr, struct mbuf *m)
+{
+ struct rfcomm_mcc_msc msc; /* (3 octets) */
+ struct rfcomm_dlc *dlc;
+ int len = 0;
+
+ /* [ADDRESS] */
+ if (m->m_pkthdr.len < sizeof(msc.address))
+ return;
+
+ m_copydata(m, 0, sizeof(msc.address), &msc.address);
+ m_adj(m, sizeof(msc.address));
+ len += sizeof(msc.address);
+
+ dlc = rfcomm_dlc_lookup(rs, RFCOMM_DLCI(msc.address));
+
+ if (cr == 0) { /* ignore acks */
+ if (dlc != NULL)
+ timeout_del(&dlc->rd_timeout);
+
+ return;
+ }
+
+ if (dlc == NULL) {
+ rfcomm_session_send_frame(rs, RFCOMM_FRAME_DM,
+ RFCOMM_DLCI(msc.address));
+ return;
+ }
+
+ /* [SIGNALS] */
+ if (m->m_pkthdr.len < sizeof(msc.modem))
+ return;
+
+ m_copydata(m, 0, sizeof(msc.modem), &msc.modem);
+ m_adj(m, sizeof(msc.modem));
+ len += sizeof(msc.modem);
+
+ dlc->rd_rmodem = msc.modem;
+ /* XXX how do we signal this upstream? */
+
+ if (RFCOMM_EA(msc.modem) == 0) {
+ if (m->m_pkthdr.len < sizeof(msc.brk))
+ return;
+
+ m_copydata(m, 0, sizeof(msc.brk), &msc.brk);
+ m_adj(m, sizeof(msc.brk));
+ len += sizeof(msc.brk);
+
+ /* XXX how do we signal this upstream? */
+ }
+
+ rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_MSC, &msc, len);
+}
+
+/*
+ * process Remote Port Negotiation command/response
+ */
+static void
+rfcomm_session_recv_mcc_rpn(struct rfcomm_session *rs, int cr, struct mbuf *m)
+{
+ struct rfcomm_mcc_rpn rpn;
+ uint16_t mask;
+
+ if (cr == 0) /* ignore ack */
+ return;
+
+ /* default values */
+ rpn.bit_rate = RFCOMM_RPN_BR_9600;
+ rpn.line_settings = RFCOMM_RPN_8_N_1;
+ rpn.flow_control = RFCOMM_RPN_FLOW_NONE;
+ rpn.xon_char = RFCOMM_RPN_XON_CHAR;
+ rpn.xoff_char = RFCOMM_RPN_XOFF_CHAR;
+
+ if (m->m_pkthdr.len == sizeof(rpn)) {
+ m_copydata(m, 0, sizeof(rpn), (caddr_t)&rpn);
+ rpn.param_mask = RFCOMM_RPN_PM_ALL;
+ } else if (m->m_pkthdr.len == 1) {
+ m_copydata(m, 0, 1, (caddr_t)&rpn);
+ rpn.param_mask = letoh16(rpn.param_mask);
+ } else {
+ DPRINTF("Bad RPN length (%d)\n", m->m_pkthdr.len);
+ return;
+ }
+
+ mask = 0;
+
+ if (rpn.param_mask & RFCOMM_RPN_PM_RATE)
+ mask |= RFCOMM_RPN_PM_RATE;
+
+ if (rpn.param_mask & RFCOMM_RPN_PM_DATA
+ && RFCOMM_RPN_DATA_BITS(rpn.line_settings) == RFCOMM_RPN_DATA_8)
+ mask |= RFCOMM_RPN_PM_DATA;
+
+ if (rpn.param_mask & RFCOMM_RPN_PM_STOP
+ && RFCOMM_RPN_STOP_BITS(rpn.line_settings) == RFCOMM_RPN_STOP_1)
+ mask |= RFCOMM_RPN_PM_STOP;
+
+ if (rpn.param_mask & RFCOMM_RPN_PM_PARITY
+ && RFCOMM_RPN_PARITY(rpn.line_settings) == RFCOMM_RPN_PARITY_NONE)
+ mask |= RFCOMM_RPN_PM_PARITY;
+
+ if (rpn.param_mask & RFCOMM_RPN_PM_XON
+ && rpn.xon_char == RFCOMM_RPN_XON_CHAR)
+ mask |= RFCOMM_RPN_PM_XON;
+
+ if (rpn.param_mask & RFCOMM_RPN_PM_XOFF
+ && rpn.xoff_char == RFCOMM_RPN_XOFF_CHAR)
+ mask |= RFCOMM_RPN_PM_XOFF;
+
+ if (rpn.param_mask & RFCOMM_RPN_PM_FLOW
+ && rpn.flow_control == RFCOMM_RPN_FLOW_NONE)
+ mask |= RFCOMM_RPN_PM_FLOW;
+
+ rpn.param_mask = htole16(mask);
+
+ rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_RPN, &rpn, sizeof(rpn));
+}
+
+/*
+ * process Remote Line Status command/response
+ */
+static void
+rfcomm_session_recv_mcc_rls(struct rfcomm_session *rs, int cr, struct mbuf *m)
+{
+ struct rfcomm_mcc_rls rls;
+
+ if (cr == 0) /* ignore ack */
+ return;
+
+ if (m->m_pkthdr.len != sizeof(rls)) {
+ DPRINTF("Bad RLS length %d\n", m->m_pkthdr.len);
+ return;
+ }
+
+ m_copydata(m, 0, sizeof(rls), (caddr_t)&rls);
+
+ /*
+ * So far as I can tell, we just send back what
+ * they sent us. This signifies errors that seem
+ * irrelevent for RFCOMM over L2CAP.
+ */
+ rls.address |= 0x03; /* EA = 1, CR = 1 */
+ rls.status &= 0x0f; /* only 4 bits valid */
+
+ rfcomm_session_send_mcc(rs, 0, RFCOMM_MCC_RLS, &rls, sizeof(rls));
+}
+
+/*
+ * process Parameter Negotiation command/response
+ */
+static void
+rfcomm_session_recv_mcc_pn(struct rfcomm_session *rs, int cr, struct mbuf *m)
+{
+ struct rfcomm_dlc *dlc;
+ struct rfcomm_mcc_pn pn;
+ int err;
+
+ if (m->m_pkthdr.len != sizeof(pn)) {
+ DPRINTF("Bad PN length %d\n", m->m_pkthdr.len);
+ return;
+ }
+
+ m_copydata(m, 0, sizeof(pn), (caddr_t)&pn);
+
+ pn.dlci &= 0x3f;
+ pn.mtu = letoh16(pn.mtu);
+
+ dlc = rfcomm_dlc_lookup(rs, pn.dlci);
+ if (cr) { /* Command */
+ /*
+ * If there is no DLC present, this is a new
+ * connection so attempt to make one
+ */
+ if (dlc == NULL) {
+ dlc = rfcomm_dlc_newconn(rs, pn.dlci);
+ if (dlc == NULL)
+ return; /* (DM is sent) */
+ }
+
+ /* accept any valid MTU, and offer it back */
+ pn.mtu = min(pn.mtu, RFCOMM_MTU_MAX);
+ pn.mtu = min(pn.mtu, rs->rs_mtu);
+ pn.mtu = max(pn.mtu, RFCOMM_MTU_MIN);
+ dlc->rd_mtu = pn.mtu;
+ pn.mtu = htole16(pn.mtu);
+
+ /* credits are only set before DLC is open */
+ if (dlc->rd_state == RFCOMM_DLC_WAIT_CONNECT
+ && (pn.flow_control & 0xf0) == 0xf0) {
+ rs->rs_flags |= RFCOMM_SESSION_CFC;
+ dlc->rd_txcred = pn.credits & 0x07;
+
+ dlc->rd_rxcred = (dlc->rd_rxsize / dlc->rd_mtu);
+ dlc->rd_rxcred = min(dlc->rd_rxcred,
+ RFCOMM_CREDITS_DEFAULT);
+
+ pn.flow_control = 0xe0;
+ pn.credits = dlc->rd_rxcred;
+ } else {
+ pn.flow_control = 0x00;
+ pn.credits = 0x00;
+ }
+
+ /* unused fields must be ignored and set to zero */
+ pn.ack_timer = 0;
+ pn.max_retrans = 0;
+
+ /* send our response */
+ err = rfcomm_session_send_mcc(rs, 0,
+ RFCOMM_MCC_PN, &pn, sizeof(pn));
+ if (err)
+ goto close;
+
+ } else { /* Response */
+ /* ignore responses with no matching DLC */
+ if (dlc == NULL)
+ return;
+
+ timeout_del(&dlc->rd_timeout);
+
+ if (pn.mtu > RFCOMM_MTU_MAX || pn.mtu > dlc->rd_mtu) {
+ dlc->rd_state = RFCOMM_DLC_WAIT_DISCONNECT;
+ err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC,
+ pn.dlci);
+ if (err)
+ goto close;
+
+ timeout_add(&dlc->rd_timeout,
+ rfcomm_ack_timeout * hz);
+ return;
+ }
+ dlc->rd_mtu = pn.mtu;
+
+ /* if DLC is not waiting to connect, we are done */
+ if (dlc->rd_state != RFCOMM_DLC_WAIT_CONNECT)
+ return;
+
+ /* set initial credits according to RFCOMM spec */
+ if ((pn.flow_control & 0xf0) == 0xe0) {
+ rs->rs_flags |= RFCOMM_SESSION_CFC;
+ dlc->rd_txcred = (pn.credits & 0x07);
+ }
+
+ timeout_add(&dlc->rd_timeout, rfcomm_ack_timeout * hz);
+
+ /* set link mode */
+ err = rfcomm_dlc_setmode(dlc);
+ if (err == EINPROGRESS) {
+ dlc->rd_state = RFCOMM_DLC_WAIT_SEND_SABM;
+ (*dlc->rd_proto->connecting)(dlc->rd_upper);
+ return;
+ }
+ if (err)
+ goto close;
+
+ /* we can proceed now */
+ err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_SABM, pn.dlci);
+ if (err)
+ goto close;
+
+ dlc->rd_state = RFCOMM_DLC_WAIT_RECV_UA;
+ }
+ return;
+
+close:
+ rfcomm_dlc_close(dlc, err);
+}
+
+/*
+ * process Non Supported Command command/response
+ */
+static void
+rfcomm_session_recv_mcc_nsc(struct rfcomm_session *rs,
+ int cr, struct mbuf *m)
+{
+ struct rfcomm_dlc *dlc, *next;
+
+ /*
+ * Since we did nothing that is not mandatory,
+ * we just abort the whole session..
+ */
+
+ next = LIST_FIRST(&rs->rs_dlcs);
+ while ((dlc = next) != NULL) {
+ next = LIST_NEXT(dlc, rd_next);
+ rfcomm_dlc_close(dlc, ECONNABORTED);
+ }
+
+ rfcomm_session_free(rs);
+}
+
+/***********************************************************************
+ *
+ * RFCOMM Session outward frame/uih/mcc building
+ */
+
+/*
+ * SABM/DISC/DM/UA frames are all minimal and mostly identical.
+ */
+int
+rfcomm_session_send_frame(struct rfcomm_session *rs, int type, int dlci)
+{
+ struct rfcomm_cmd_hdr *hdr;
+ struct rfcomm_credit *credit;
+ struct mbuf *m;
+ uint8_t fcs, cr;
+
+ credit = pool_get(&rfcomm_credit_pool, PR_NOWAIT);
+ if (credit == NULL)
+ return ENOMEM;
+
+ m = m_gethdr(M_DONTWAIT, MT_DATA);
+ if (m == NULL) {
+ pool_put(&rfcomm_credit_pool, credit);
+ return ENOMEM;
+ }
+
+ /*
+ * The CR (command/response) bit identifies the frame either as a
+ * commmand or a response and is used along with the DLCI to form
+ * the address. Commands contain the non-initiator address, whereas
+ * responses contain the initiator address, so the CR value is
+ * also dependent on the session direction.
+ */
+ if (type == RFCOMM_FRAME_UA || type == RFCOMM_FRAME_DM)
+ cr = IS_INITIATOR(rs) ? 0 : 1;
+ else
+ cr = IS_INITIATOR(rs) ? 1 : 0;
+
+ hdr = mtod(m, struct rfcomm_cmd_hdr *);
+ hdr->address = RFCOMM_MKADDRESS(cr, dlci);
+ hdr->control = RFCOMM_MKCONTROL(type, 1); /* PF = 1 */
+ hdr->length = (0x00 << 1) | 0x01; /* len = 0x00, EA = 1 */
+
+ fcs = 0xff;
+ fcs = FCS(fcs, hdr->address);
+ fcs = FCS(fcs, hdr->control);
+ fcs = FCS(fcs, hdr->length);
+ fcs = 0xff - fcs; /* ones complement */
+ hdr->fcs = fcs;
+
+ m->m_pkthdr.len = m->m_len = sizeof(struct rfcomm_cmd_hdr);
+
+ /* empty credit note */
+ credit->rc_dlc = NULL;
+ credit->rc_len = m->m_pkthdr.len;
+ SIMPLEQ_INSERT_TAIL(&rs->rs_credits, credit, rc_next);
+
+ DPRINTFN(5, "dlci %d type %2.2x (%d bytes, fcs=%#2.2x)\n",
+ dlci, type, m->m_pkthdr.len, fcs);
+
+ return l2cap_send(rs->rs_l2cap, m);
+}
+
+/*
+ * rfcomm_session_send_uih(rfcomm_session, rfcomm_dlc, credits, mbuf)
+ *
+ * UIH frame is per DLC data or Multiplexer Control Commands
+ * when no DLC is given. Data mbuf is optional (just credits
+ * will be sent in that case)
+ */
+int
+rfcomm_session_send_uih(struct rfcomm_session *rs, struct rfcomm_dlc *dlc,
+ int credits, struct mbuf *m)
+{
+ struct rfcomm_credit *credit;
+ struct mbuf *m0 = NULL;
+ int err, len;
+ uint8_t fcs, *hdr;
+
+ KASSERT(rs != NULL);
+
+ len = (m == NULL) ? 0 : m->m_pkthdr.len;
+ KASSERT(!(credits == 0 && len == 0));
+
+ /*
+ * Make a credit note for the completion notification
+ */
+ credit = pool_get(&rfcomm_credit_pool, PR_NOWAIT);
+ if (credit == NULL)
+ goto nomem;
+
+ credit->rc_len = len;
+ credit->rc_dlc = dlc;
+
+ /*
+ * Wrap UIH frame information around payload.
+ *
+ * [ADDRESS] [CONTROL] [LENGTH] [CREDITS] [...] [FCS]
+ *
+ * Address is one octet.
+ * Control is one octet.
+ * Length is one or two octets.
+ * Credits may be one octet.
+ *
+ * FCS is one octet and calculated on address and
+ * control octets only.
+ *
+ * If there are credits to be sent, we will set the PF
+ * flag and include them in the frame.
+ */
+ m0 = m_gethdr(M_DONTWAIT, MT_DATA);
+ if (m0 == NULL)
+ goto nomem;
+
+ MH_ALIGN(m0, 5); /* (max 5 header octets) */
+ hdr = mtod(m0, uint8_t *);
+
+ /* CR bit is set according to the initiator of the session */
+ *hdr = RFCOMM_MKADDRESS((IS_INITIATOR(rs) ? 1 : 0),
+ (dlc ? dlc->rd_dlci : 0));
+ fcs = FCS(0xff, *hdr);
+ hdr++;
+
+ /* PF bit is set if credits are being sent */
+ *hdr = RFCOMM_MKCONTROL(RFCOMM_FRAME_UIH, (credits > 0 ? 1 : 0));
+ fcs = FCS(fcs, *hdr);
+ hdr++;
+
+ if (len < (1 << 7)) {
+ *hdr++ = ((len << 1) & 0xfe) | 0x01; /* 7 bits, EA = 1 */
+ } else {
+ *hdr++ = ((len << 1) & 0xfe); /* 7 bits, EA = 0 */
+ *hdr++ = ((len >> 7) & 0xff); /* 8 bits, no EA */
+ }
+
+ if (credits > 0)
+ *hdr++ = (uint8_t)credits;
+
+ m0->m_len = hdr - mtod(m0, uint8_t *);
+
+ /* Append payload */
+ m0->m_next = m;
+ m = NULL;
+
+ m0->m_pkthdr.len = m0->m_len + len;
+
+ /* Append FCS */
+ fcs = 0xff - fcs; /* ones complement */
+ len = m0->m_pkthdr.len;
+ m_copyback(m0, len, sizeof(fcs), &fcs);
+ if (m0->m_pkthdr.len != len + sizeof(fcs))
+ goto nomem;
+
+ DPRINTFN(10, "dlci %d, pktlen %d (%d data, %d credits), fcs=%#2.2x\n",
+ dlc ? dlc->rd_dlci : 0, m0->m_pkthdr.len, credit->rc_len,
+ credits, fcs);
+
+ /*
+ * UIH frame ready to go..
+ */
+ err = l2cap_send(rs->rs_l2cap, m0);
+ if (err)
+ goto fail;
+
+ SIMPLEQ_INSERT_TAIL(&rs->rs_credits, credit, rc_next);
+ return 0;
+
+nomem:
+ err = ENOMEM;
+
+ if (m0 != NULL)
+ m_freem(m0);
+
+ if (m != NULL)
+ m_freem(m);
+
+fail:
+ if (credit != NULL)
+ pool_put(&rfcomm_credit_pool, credit);
+
+ return err;
+}
+
+/*
+ * send Multiplexer Control Command (or Response) on session
+ */
+int
+rfcomm_session_send_mcc(struct rfcomm_session *rs, int cr,
+ uint8_t type, void *data, int len)
+{
+ struct mbuf *m;
+ uint8_t *hdr;
+ int hlen;
+
+ m = m_gethdr(M_DONTWAIT, MT_DATA);
+ if (m == NULL)
+ return ENOMEM;
+
+ hdr = mtod(m, uint8_t *);
+
+ /*
+ * Technically the type field can extend past one octet, but none
+ * currently defined will do that.
+ */
+ *hdr++ = RFCOMM_MKMCC_TYPE(cr, type);
+
+ /*
+ * In the frame, the max length size is 2 octets (15 bits) whereas
+ * no max length size is specified for MCC commands. We must allow
+ * for 3 octets since for MCC frames we use 7 bits + EA in each.
+ *
+ * Only test data can possibly be that big.
+ *
+ * XXX Should we check this against the MTU?
+ */
+ if (len < (1 << 7)) {
+ *hdr++ = ((len << 1) & 0xfe) | 0x01; /* 7 bits, EA = 1 */
+ } else if (len < (1 << 14)) {
+ *hdr++ = ((len << 1) & 0xfe); /* 7 bits, EA = 0 */
+ *hdr++ = ((len >> 6) & 0xfe) | 0x01; /* 7 bits, EA = 1 */
+ } else if (len < (1 << 15)) {
+ *hdr++ = ((len << 1) & 0xfe); /* 7 bits, EA = 0 */
+ *hdr++ = ((len >> 6) & 0xfe); /* 7 bits, EA = 0 */
+ *hdr++ = ((len >> 13) & 0x02) | 0x01; /* 1 bit, EA = 1 */
+ } else {
+ DPRINTF("incredible length! (%d)\n", len);
+ m_freem(m);
+ return EMSGSIZE;
+ }
+
+ /*
+ * add command data (to same mbuf if possible)
+ */
+ hlen = hdr - mtod(m, uint8_t *);
+
+ if (len > 0) {
+ m->m_pkthdr.len = m->m_len = MHLEN;
+ m_copyback(m, hlen, len, data);
+ if (m->m_pkthdr.len != max(MHLEN, hlen + len)) {
+ m_freem(m);
+ return ENOMEM;
+ }
+ }
+
+ m->m_pkthdr.len = hlen + len;
+ m->m_len = min(MHLEN, m->m_pkthdr.len);
+
+ DPRINTFN(5, "%s type %2.2x len %d\n",
+ (cr ? "command" : "response"), type, m->m_pkthdr.len);
+
+ return rfcomm_session_send_uih(rs, NULL, 0, m);
+}
diff --git a/sys/netbt/rfcomm_socket.c b/sys/netbt/rfcomm_socket.c
new file mode 100644
index 00000000000..6484bb8cb32
--- /dev/null
+++ b/sys/netbt/rfcomm_socket.c
@@ -0,0 +1,420 @@
+/* $OpenBSD: rfcomm_socket.c,v 1.1 2007/06/01 02:46:12 uwe Exp $ */
+/* $NetBSD: rfcomm_socket.c,v 1.7 2007/04/21 06:15:23 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Written by Iain Hibbert for Itronix Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+/* load symbolic names */
+#ifdef BLUETOOTH_DEBUG
+#define PRUREQUESTS
+#define PRCOREQUESTS
+#endif
+
+#include <sys/param.h>
+#include <sys/domain.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/systm.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/hci.h> /* XXX for EPASSTHROUGH */
+#include <netbt/rfcomm.h>
+
+/****************************************************************************
+ *
+ * RFCOMM SOCK_STREAM Sockets - serial line emulation
+ *
+ */
+
+static void rfcomm_connecting(void *);
+static void rfcomm_connected(void *);
+static void rfcomm_disconnected(void *, int);
+static void *rfcomm_newconn(void *, struct sockaddr_bt *, struct sockaddr_bt *);
+static void rfcomm_complete(void *, int);
+static void rfcomm_linkmode(void *, int);
+static void rfcomm_input(void *, struct mbuf *);
+
+static const struct btproto rfcomm_proto = {
+ rfcomm_connecting,
+ rfcomm_connected,
+ rfcomm_disconnected,
+ rfcomm_newconn,
+ rfcomm_complete,
+ rfcomm_linkmode,
+ rfcomm_input,
+};
+
+/* sysctl variables */
+int rfcomm_sendspace = 4096;
+int rfcomm_recvspace = 4096;
+
+/*
+ * User Request.
+ * up is socket
+ * m is either
+ * optional mbuf chain containing message
+ * ioctl command (PRU_CONTROL)
+ * nam is either
+ * optional mbuf chain containing an address
+ * ioctl data (PRU_CONTROL)
+ * optionally protocol number (PRU_ATTACH)
+ * message flags (PRU_RCVD)
+ * ctl is either
+ * optional mbuf chain containing socket options
+ * optional interface pointer (PRU_CONTROL, PRU_PURGEIF)
+ * l is pointer to process requesting action (if any)
+ *
+ * we are responsible for disposing of m and ctl if
+ * they are mbuf chains
+ */
+int
+rfcomm_usrreq(struct socket *up, int req, struct mbuf *m,
+ struct mbuf *nam, struct mbuf *ctl)
+{
+ struct rfcomm_dlc *pcb = up->so_pcb;
+ struct sockaddr_bt *sa;
+ struct mbuf *m0;
+ int err = 0;
+
+#ifdef notyet /* XXX */
+ DPRINTFN(2, "%s\n", prurequests[req]);
+#endif
+
+ switch (req) {
+ case PRU_CONTROL:
+ return EPASSTHROUGH;
+
+#ifdef notyet /* XXX */
+ case PRU_PURGEIF:
+ return EOPNOTSUPP;
+#endif
+
+ case PRU_ATTACH:
+ if (pcb != NULL)
+ return EINVAL;
+
+ /*
+ * Since we have nothing to add, we attach the DLC
+ * structure directly to our PCB pointer.
+ */
+ err = rfcomm_attach((struct rfcomm_dlc **)&up->so_pcb,
+ &rfcomm_proto, up);
+ if (err)
+ return err;
+
+ err = soreserve(up, rfcomm_sendspace, rfcomm_recvspace);
+ if (err)
+ return err;
+
+ err = rfcomm_rcvd(up->so_pcb, sbspace(&up->so_rcv));
+ if (err)
+ return err;
+
+ return 0;
+ }
+
+ if (pcb == NULL) {
+ err = EINVAL;
+ goto release;
+ }
+
+ switch(req) {
+ case PRU_DISCONNECT:
+ soisdisconnecting(up);
+ return rfcomm_disconnect(pcb, up->so_linger);
+
+ case PRU_ABORT:
+ rfcomm_disconnect(pcb, 0);
+ soisdisconnected(up);
+ /* fall through to */
+ case PRU_DETACH:
+ return rfcomm_detach((struct rfcomm_dlc **)&up->so_pcb);
+
+ case PRU_BIND:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+
+ if (sa->bt_len != sizeof(struct sockaddr_bt))
+ return EINVAL;
+
+ if (sa->bt_family != AF_BLUETOOTH)
+ return EAFNOSUPPORT;
+
+ return rfcomm_bind(pcb, sa);
+
+ case PRU_CONNECT:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+
+ if (sa->bt_len != sizeof(struct sockaddr_bt))
+ return EINVAL;
+
+ if (sa->bt_family != AF_BLUETOOTH)
+ return EAFNOSUPPORT;
+
+ soisconnecting(up);
+ return rfcomm_connect(pcb, sa);
+
+ case PRU_PEERADDR:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+ nam->m_len = sizeof(struct sockaddr_bt);
+ return rfcomm_peeraddr(pcb, sa);
+
+ case PRU_SOCKADDR:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+ nam->m_len = sizeof(struct sockaddr_bt);
+ return rfcomm_sockaddr(pcb, sa);
+
+ case PRU_SHUTDOWN:
+ socantsendmore(up);
+ break;
+
+ case PRU_SEND:
+ KASSERT(m != NULL);
+
+ if (ctl) /* no use for that */
+ m_freem(ctl);
+
+ m0 = m_copym(m, 0, M_COPYALL, M_DONTWAIT);
+ if (m0 == NULL)
+ return ENOMEM;
+
+ sbappendstream(&up->so_snd, m);
+
+ return rfcomm_send(pcb, m0);
+
+ case PRU_SENSE:
+ return 0; /* (no release) */
+
+ case PRU_RCVD:
+ return rfcomm_rcvd(pcb, sbspace(&up->so_rcv));
+
+ case PRU_RCVOOB:
+ return EOPNOTSUPP; /* (no release) */
+
+ case PRU_LISTEN:
+ return rfcomm_listen(pcb);
+
+ case PRU_ACCEPT:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+ nam->m_len = sizeof(struct sockaddr_bt);
+ return rfcomm_peeraddr(pcb, sa);
+
+ case PRU_CONNECT2:
+ case PRU_SENDOOB:
+ case PRU_FASTTIMO:
+ case PRU_SLOWTIMO:
+ case PRU_PROTORCV:
+ case PRU_PROTOSEND:
+ err = EOPNOTSUPP;
+ break;
+
+ default:
+ UNKNOWN(req);
+ err = EOPNOTSUPP;
+ break;
+ }
+
+release:
+ if (m) m_freem(m);
+ if (ctl) m_freem(ctl);
+ return err;
+}
+
+/*
+ * rfcomm_ctloutput(request, socket, level, optname, opt)
+ *
+ */
+int
+rfcomm_ctloutput(int req, struct socket *so, int level,
+ int optname, struct mbuf **opt)
+{
+ struct rfcomm_dlc *pcb = so->so_pcb;
+ struct mbuf *m;
+ int err = 0;
+
+#ifdef notyet /* XXX */
+ DPRINTFN(2, "%s\n", prcorequests[req]);
+#endif
+
+ if (pcb == NULL)
+ return EINVAL;
+
+ if (level != BTPROTO_RFCOMM)
+ return ENOPROTOOPT;
+
+ switch(req) {
+ case PRCO_GETOPT:
+ m = m_get(M_WAIT, MT_SOOPTS);
+ m->m_len = rfcomm_getopt(pcb, optname, mtod(m, void *));
+ if (m->m_len == 0) {
+ m_freem(m);
+ m = NULL;
+ err = ENOPROTOOPT;
+ }
+ *opt = m;
+ break;
+
+ case PRCO_SETOPT:
+ m = *opt;
+ KASSERT(m != NULL);
+ err = rfcomm_setopt(pcb, optname, mtod(m, void *));
+ m_freem(m);
+ break;
+
+ default:
+ err = ENOPROTOOPT;
+ break;
+ }
+
+ return err;
+}
+
+/**********************************************************************
+ *
+ * RFCOMM callbacks
+ */
+
+static void
+rfcomm_connecting(void *arg)
+{
+ /* struct socket *so = arg; */
+
+ KASSERT(arg != NULL);
+ DPRINTF("Connecting\n");
+}
+
+static void
+rfcomm_connected(void *arg)
+{
+ struct socket *so = arg;
+
+ KASSERT(so != NULL);
+ DPRINTF("Connected\n");
+ soisconnected(so);
+}
+
+static void
+rfcomm_disconnected(void *arg, int err)
+{
+ struct socket *so = arg;
+
+ KASSERT(so != NULL);
+ DPRINTF("Disconnected\n");
+
+ so->so_error = err;
+ soisdisconnected(so);
+}
+
+static void *
+rfcomm_newconn(void *arg, struct sockaddr_bt *laddr,
+ struct sockaddr_bt *raddr)
+{
+ struct socket *so = arg;
+
+ DPRINTF("New Connection\n");
+ so = sonewconn(so, 0);
+ if (so == NULL)
+ return NULL;
+
+ soisconnecting(so);
+
+ return so->so_pcb;
+}
+
+/*
+ * rfcomm_complete(rfcomm_dlc, length)
+ *
+ * length bytes are sent and may be removed from socket buffer
+ */
+static void
+rfcomm_complete(void *arg, int length)
+{
+ struct socket *so = arg;
+
+ sbdrop(&so->so_snd, length);
+ sowwakeup(so);
+}
+
+/*
+ * rfcomm_linkmode(rfcomm_dlc, new)
+ *
+ * link mode change notification.
+ */
+static void
+rfcomm_linkmode(void *arg, int new)
+{
+ struct socket *so = arg;
+ int mode;
+
+ DPRINTF("auth %s, encrypt %s, secure %s\n",
+ (new & RFCOMM_LM_AUTH ? "on" : "off"),
+ (new & RFCOMM_LM_ENCRYPT ? "on" : "off"),
+ (new & RFCOMM_LM_SECURE ? "on" : "off"));
+
+ (void)rfcomm_getopt(so->so_pcb, SO_RFCOMM_LM, &mode);
+ if (((mode & RFCOMM_LM_AUTH) && !(new & RFCOMM_LM_AUTH))
+ || ((mode & RFCOMM_LM_ENCRYPT) && !(new & RFCOMM_LM_ENCRYPT))
+ || ((mode & RFCOMM_LM_SECURE) && !(new & RFCOMM_LM_SECURE)))
+ rfcomm_disconnect(so->so_pcb, 0);
+}
+
+/*
+ * rfcomm_input(rfcomm_dlc, mbuf)
+ */
+static void
+rfcomm_input(void *arg, struct mbuf *m)
+{
+ struct socket *so = arg;
+
+ KASSERT(so != NULL);
+
+ if (m->m_pkthdr.len > sbspace(&so->so_rcv)) {
+ printf("%s: %d bytes dropped (socket buffer full)\n",
+ __func__, m->m_pkthdr.len);
+ m_freem(m);
+ return;
+ }
+
+ DPRINTFN(10, "received %d bytes\n", m->m_pkthdr.len);
+
+ sbappendstream(&so->so_rcv, m);
+ sorwakeup(so);
+}
diff --git a/sys/netbt/rfcomm_upper.c b/sys/netbt/rfcomm_upper.c
new file mode 100644
index 00000000000..4d6849733db
--- /dev/null
+++ b/sys/netbt/rfcomm_upper.c
@@ -0,0 +1,502 @@
+/* $OpenBSD: rfcomm_upper.c,v 1.1 2007/06/01 02:46:12 uwe Exp $ */
+/* $NetBSD: rfcomm_upper.c,v 1.6 2007/04/21 06:15:23 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Written by Iain Hibbert for Itronix Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/hci.h>
+#include <netbt/l2cap.h>
+#include <netbt/rfcomm.h>
+
+/****************************************************************************
+ *
+ * RFCOMM DLC - Upper Protocol API
+ *
+ * Currently the only 'Port Emulation Entity' is the RFCOMM socket code
+ * but it is should be possible to provide a pseudo-device for a direct
+ * tty interface.
+ */
+
+/*
+ * rfcomm_attach(handle, proto, upper)
+ *
+ * attach a new RFCOMM DLC to handle, populate with reasonable defaults
+ */
+int
+rfcomm_attach(struct rfcomm_dlc **handle,
+ const struct btproto *proto, void *upper)
+{
+ struct rfcomm_dlc *dlc;
+
+ KASSERT(handle != NULL);
+ KASSERT(proto != NULL);
+ KASSERT(upper != NULL);
+
+ dlc = malloc(sizeof(struct rfcomm_dlc), M_BLUETOOTH, M_NOWAIT);
+ if (dlc == NULL)
+ return ENOMEM;
+ bzero(dlc, sizeof *dlc);
+
+ dlc->rd_state = RFCOMM_DLC_CLOSED;
+ dlc->rd_mtu = rfcomm_mtu_default;
+
+ dlc->rd_proto = proto;
+ dlc->rd_upper = upper;
+
+ dlc->rd_laddr.bt_len = sizeof(struct sockaddr_bt);
+ dlc->rd_laddr.bt_family = AF_BLUETOOTH;
+ dlc->rd_laddr.bt_psm = L2CAP_PSM_RFCOMM;
+
+ dlc->rd_raddr.bt_len = sizeof(struct sockaddr_bt);
+ dlc->rd_raddr.bt_family = AF_BLUETOOTH;
+ dlc->rd_raddr.bt_psm = L2CAP_PSM_RFCOMM;
+
+ dlc->rd_lmodem = RFCOMM_MSC_RTC | RFCOMM_MSC_RTR | RFCOMM_MSC_DV;
+
+ timeout_set(&dlc->rd_timeout, rfcomm_dlc_timeout, dlc);
+
+ *handle = dlc;
+ return 0;
+}
+
+/*
+ * rfcomm_bind(dlc, sockaddr)
+ *
+ * bind DLC to local address
+ */
+int
+rfcomm_bind(struct rfcomm_dlc *dlc, struct sockaddr_bt *addr)
+{
+
+ memcpy(&dlc->rd_laddr, addr, sizeof(struct sockaddr_bt));
+ return 0;
+}
+
+/*
+ * rfcomm_sockaddr(dlc, sockaddr)
+ *
+ * return local address
+ */
+int
+rfcomm_sockaddr(struct rfcomm_dlc *dlc, struct sockaddr_bt *addr)
+{
+
+ memcpy(addr, &dlc->rd_laddr, sizeof(struct sockaddr_bt));
+ return 0;
+}
+
+/*
+ * rfcomm_connect(dlc, sockaddr)
+ *
+ * Initiate connection of RFCOMM DLC to remote address.
+ */
+int
+rfcomm_connect(struct rfcomm_dlc *dlc, struct sockaddr_bt *dest)
+{
+ struct rfcomm_session *rs;
+ int err = 0;
+
+ if (dlc->rd_state != RFCOMM_DLC_CLOSED)
+ return EISCONN;
+
+ memcpy(&dlc->rd_raddr, dest, sizeof(struct sockaddr_bt));
+
+ if (dlc->rd_raddr.bt_channel < RFCOMM_CHANNEL_MIN
+ || dlc->rd_raddr.bt_channel > RFCOMM_CHANNEL_MAX
+ || bdaddr_any(&dlc->rd_raddr.bt_bdaddr))
+ return EDESTADDRREQ;
+
+ if (dlc->rd_raddr.bt_psm == L2CAP_PSM_ANY)
+ dlc->rd_raddr.bt_psm = L2CAP_PSM_RFCOMM;
+ else if (dlc->rd_raddr.bt_psm != L2CAP_PSM_RFCOMM
+ && (dlc->rd_raddr.bt_psm < 0x1001
+ || L2CAP_PSM_INVALID(dlc->rd_raddr.bt_psm)))
+ return EINVAL;
+
+ /*
+ * We are allowed only one RFCOMM session between any 2 Bluetooth
+ * devices, so see if there is a session already otherwise create
+ * one and set it connecting.
+ */
+ rs = rfcomm_session_lookup(&dlc->rd_laddr, &dlc->rd_raddr);
+ if (rs == NULL) {
+ rs = rfcomm_session_alloc(&rfcomm_session_active,
+ &dlc->rd_laddr);
+ if (rs == NULL)
+ return ENOMEM;
+
+ rs->rs_flags |= RFCOMM_SESSION_INITIATOR;
+ rs->rs_state = RFCOMM_SESSION_WAIT_CONNECT;
+
+ err = l2cap_connect(rs->rs_l2cap, &dlc->rd_raddr);
+ if (err) {
+ rfcomm_session_free(rs);
+ return err;
+ }
+
+ /*
+ * This session will start up automatically when its
+ * L2CAP channel is connected.
+ */
+ }
+
+ /* construct DLC */
+ dlc->rd_dlci = RFCOMM_MKDLCI(IS_INITIATOR(rs) ? 0:1, dest->bt_channel);
+ if (rfcomm_dlc_lookup(rs, dlc->rd_dlci))
+ return EBUSY;
+
+ l2cap_sockaddr(rs->rs_l2cap, &dlc->rd_laddr);
+
+ /*
+ * attach the DLC to the session and start it off
+ */
+ dlc->rd_session = rs;
+ dlc->rd_state = RFCOMM_DLC_WAIT_SESSION;
+ LIST_INSERT_HEAD(&rs->rs_dlcs, dlc, rd_next);
+
+ if (rs->rs_state == RFCOMM_SESSION_OPEN)
+ err = rfcomm_dlc_connect(dlc);
+
+ return err;
+}
+
+/*
+ * rfcomm_peeraddr(dlc, sockaddr)
+ *
+ * return remote address
+ */
+int
+rfcomm_peeraddr(struct rfcomm_dlc *dlc, struct sockaddr_bt *addr)
+{
+
+ memcpy(addr, &dlc->rd_raddr, sizeof(struct sockaddr_bt));
+ return 0;
+}
+
+/*
+ * rfcomm_disconnect(dlc, linger)
+ *
+ * disconnect RFCOMM DLC
+ */
+int
+rfcomm_disconnect(struct rfcomm_dlc *dlc, int linger)
+{
+ struct rfcomm_session *rs = dlc->rd_session;
+ int err = 0;
+
+ KASSERT(dlc != NULL);
+
+ switch (dlc->rd_state) {
+ case RFCOMM_DLC_CLOSED:
+ case RFCOMM_DLC_LISTEN:
+ return EINVAL;
+
+ case RFCOMM_DLC_WAIT_SEND_UA:
+ err = rfcomm_session_send_frame(rs,
+ RFCOMM_FRAME_DM, dlc->rd_dlci);
+
+ /* fall through */
+ case RFCOMM_DLC_WAIT_SESSION:
+ case RFCOMM_DLC_WAIT_CONNECT:
+ case RFCOMM_DLC_WAIT_SEND_SABM:
+ rfcomm_dlc_close(dlc, 0);
+ break;
+
+ case RFCOMM_DLC_OPEN:
+ if (dlc->rd_txbuf != NULL && linger != 0) {
+ dlc->rd_flags |= RFCOMM_DLC_SHUTDOWN;
+ break;
+ }
+
+ /* else fall through */
+ case RFCOMM_DLC_WAIT_RECV_UA:
+ dlc->rd_state = RFCOMM_DLC_WAIT_DISCONNECT;
+ err = rfcomm_session_send_frame(rs, RFCOMM_FRAME_DISC,
+ dlc->rd_dlci);
+ timeout_add(&dlc->rd_timeout, rfcomm_ack_timeout * hz);
+ break;
+
+ case RFCOMM_DLC_WAIT_DISCONNECT:
+ err = EALREADY;
+ break;
+
+ default:
+ UNKNOWN(dlc->rd_state);
+ break;
+ }
+
+ return err;
+}
+
+/*
+ * rfcomm_detach(handle)
+ *
+ * detach RFCOMM DLC from handle
+ */
+int
+rfcomm_detach(struct rfcomm_dlc **handle)
+{
+ struct rfcomm_dlc *dlc = *handle;
+
+ if (dlc->rd_state != RFCOMM_DLC_CLOSED)
+ rfcomm_dlc_close(dlc, 0);
+
+ if (dlc->rd_txbuf != NULL) {
+ m_freem(dlc->rd_txbuf);
+ dlc->rd_txbuf = NULL;
+ }
+
+ dlc->rd_upper = NULL;
+ *handle = NULL;
+
+ /*
+ * If callout is invoking we can't free the DLC so
+ * mark it and let the callout release it.
+ */
+ if (timeout_triggered(&dlc->rd_timeout))
+ dlc->rd_flags |= RFCOMM_DLC_DETACH;
+ else
+ free(dlc, M_BLUETOOTH);
+
+ return 0;
+}
+
+/*
+ * rfcomm_listen(dlc)
+ *
+ * This DLC is a listener. We look for an existing listening session
+ * with a matching address to attach to or else create a new one on
+ * the listeners list.
+ */
+int
+rfcomm_listen(struct rfcomm_dlc *dlc)
+{
+ struct rfcomm_session *rs, *any, *best;
+ struct sockaddr_bt addr;
+ int err;
+
+ if (dlc->rd_state != RFCOMM_DLC_CLOSED)
+ return EISCONN;
+
+ if (dlc->rd_laddr.bt_channel < RFCOMM_CHANNEL_MIN
+ || dlc->rd_laddr.bt_channel > RFCOMM_CHANNEL_MAX)
+ return EADDRNOTAVAIL;
+
+ if (dlc->rd_laddr.bt_psm == L2CAP_PSM_ANY)
+ dlc->rd_laddr.bt_psm = L2CAP_PSM_RFCOMM;
+ else if (dlc->rd_laddr.bt_psm != L2CAP_PSM_RFCOMM
+ && (dlc->rd_laddr.bt_psm < 0x1001
+ || L2CAP_PSM_INVALID(dlc->rd_laddr.bt_psm)))
+ return EADDRNOTAVAIL;
+
+ any = best = NULL;
+ LIST_FOREACH(rs, &rfcomm_session_listen, rs_next) {
+ l2cap_sockaddr(rs->rs_l2cap, &addr);
+
+ if (addr.bt_psm != dlc->rd_laddr.bt_psm)
+ continue;
+
+ if (bdaddr_same(&dlc->rd_laddr.bt_bdaddr, &addr.bt_bdaddr))
+ best = rs;
+
+ if (bdaddr_any(&addr.bt_bdaddr))
+ any = rs;
+ }
+
+ rs = best ? best : any;
+ if (rs == NULL) {
+ rs = rfcomm_session_alloc(&rfcomm_session_listen,
+ &dlc->rd_laddr);
+ if (rs == NULL)
+ return ENOMEM;
+
+ rs->rs_state = RFCOMM_SESSION_LISTEN;
+
+ err = l2cap_listen(rs->rs_l2cap);
+ if (err) {
+ rfcomm_session_free(rs);
+ return err;
+ }
+ }
+
+ dlc->rd_session = rs;
+ dlc->rd_state = RFCOMM_DLC_LISTEN;
+ LIST_INSERT_HEAD(&rs->rs_dlcs, dlc, rd_next);
+
+ return 0;
+}
+
+/*
+ * rfcomm_send(dlc, mbuf)
+ *
+ * Output data on DLC. This is streamed data, so we add it
+ * to our buffer and start the the DLC, which will assemble
+ * packets and send them if it can.
+ */
+int
+rfcomm_send(struct rfcomm_dlc *dlc, struct mbuf *m)
+{
+
+ if (dlc->rd_txbuf != NULL) {
+ dlc->rd_txbuf->m_pkthdr.len += m->m_pkthdr.len;
+ m_cat(dlc->rd_txbuf, m);
+ } else {
+ dlc->rd_txbuf = m;
+ }
+
+ if (dlc->rd_state == RFCOMM_DLC_OPEN)
+ rfcomm_dlc_start(dlc);
+
+ return 0;
+}
+
+/*
+ * rfcomm_rcvd(dlc, space)
+ *
+ * Indicate space now available in receive buffer
+ *
+ * This should be used to give an initial value of the receive buffer
+ * size when the DLC is attached and anytime data is cleared from the
+ * buffer after that.
+ */
+int
+rfcomm_rcvd(struct rfcomm_dlc *dlc, size_t space)
+{
+
+ KASSERT(dlc != NULL);
+
+ dlc->rd_rxsize = space;
+
+ /*
+ * if we are using credit based flow control, we may
+ * want to send some credits..
+ */
+ if (dlc->rd_state == RFCOMM_DLC_OPEN
+ && (dlc->rd_session->rs_flags & RFCOMM_SESSION_CFC))
+ rfcomm_dlc_start(dlc);
+
+ return 0;
+}
+
+/*
+ * rfcomm_setopt(dlc, option, addr)
+ *
+ * set DLC options
+ */
+int
+rfcomm_setopt(struct rfcomm_dlc *dlc, int opt, void *addr)
+{
+ int mode, err = 0;
+ uint16_t mtu;
+
+ switch (opt) {
+ case SO_RFCOMM_MTU:
+ mtu = *(uint16_t *)addr;
+ if (mtu < RFCOMM_MTU_MIN || mtu > RFCOMM_MTU_MAX)
+ err = EINVAL;
+ else if (dlc->rd_state == RFCOMM_DLC_CLOSED)
+ dlc->rd_mtu = mtu;
+ else
+ err = EBUSY;
+
+ break;
+
+ case SO_RFCOMM_LM:
+ mode = *(int *)addr;
+ mode &= (RFCOMM_LM_SECURE | RFCOMM_LM_ENCRYPT | RFCOMM_LM_AUTH);
+
+ if (mode & RFCOMM_LM_SECURE)
+ mode |= RFCOMM_LM_ENCRYPT;
+
+ if (mode & RFCOMM_LM_ENCRYPT)
+ mode |= RFCOMM_LM_AUTH;
+
+ dlc->rd_mode = mode;
+
+ if (dlc->rd_state == RFCOMM_DLC_OPEN)
+ err = rfcomm_dlc_setmode(dlc);
+
+ break;
+
+ default:
+ err = ENOPROTOOPT;
+ break;
+ }
+ return err;
+}
+
+/*
+ * rfcomm_getopt(dlc, option, addr)
+ *
+ * get DLC options
+ */
+int
+rfcomm_getopt(struct rfcomm_dlc *dlc, int opt, void *addr)
+{
+ struct rfcomm_fc_info *fc;
+
+ switch (opt) {
+ case SO_RFCOMM_MTU:
+ *(uint16_t *)addr = dlc->rd_mtu;
+ return sizeof(uint16_t);
+
+ case SO_RFCOMM_FC_INFO:
+ fc = addr;
+ memset(fc, 0, sizeof(*fc));
+ fc->lmodem = dlc->rd_lmodem;
+ fc->rmodem = dlc->rd_rmodem;
+ fc->tx_cred = max(dlc->rd_txcred, 0xff);
+ fc->rx_cred = max(dlc->rd_rxcred, 0xff);
+ if (dlc->rd_session
+ && (dlc->rd_session->rs_flags & RFCOMM_SESSION_CFC))
+ fc->cfc = 1;
+
+ return sizeof(*fc);
+
+ case SO_RFCOMM_LM:
+ *(int *)addr = dlc->rd_mode;
+ return sizeof(int);
+
+ default:
+ break;
+ }
+
+ return 0;
+}
diff --git a/sys/netbt/sco.h b/sys/netbt/sco.h
index 0d0be27ff5c..95849af88a4 100644
--- a/sys/netbt/sco.h
+++ b/sys/netbt/sco.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sco.h,v 1.1 2007/05/30 03:42:53 uwe Exp $ */
+/* $OpenBSD: sco.h,v 1.2 2007/06/01 02:46:12 uwe Exp $ */
/* $NetBSD: sco.h,v 1.2 2006/07/26 10:20:56 tron Exp $ */
/*-
@@ -65,7 +65,7 @@ struct socket;
extern int sco_sendspace;
extern int sco_recvspace;
int sco_usrreq(struct socket *, int, struct mbuf *,
- struct mbuf *, struct mbuf *, struct proc *);
+ struct mbuf *, struct mbuf *);
int sco_ctloutput(int, struct socket *, int, int, struct mbuf **);
/* sco_upper.c */
diff --git a/sys/netbt/sco_socket.c b/sys/netbt/sco_socket.c
new file mode 100644
index 00000000000..c0cf4bf5611
--- /dev/null
+++ b/sys/netbt/sco_socket.c
@@ -0,0 +1,380 @@
+/* $OpenBSD: sco_socket.c,v 1.1 2007/06/01 02:46:12 uwe Exp $ */
+/* $NetBSD: sco_socket.c,v 1.9 2007/04/21 06:15:23 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+/* load symbolic names */
+#ifdef BLUETOOTH_DEBUG
+#define PRUREQUESTS
+#define PRCOREQUESTS
+#endif
+
+#include <sys/param.h>
+#include <sys/domain.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/protosw.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/systm.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/hci.h>
+#include <netbt/sco.h>
+
+/*******************************************************************************
+ *
+ * SCO SOCK_SEQPACKET sockets - low latency audio data
+ */
+
+static void sco_connecting(void *);
+static void sco_connected(void *);
+static void sco_disconnected(void *, int);
+static void *sco_newconn(void *, struct sockaddr_bt *, struct sockaddr_bt *);
+static void sco_complete(void *, int);
+static void sco_linkmode(void *, int);
+static void sco_input(void *, struct mbuf *);
+
+static const struct btproto sco_proto = {
+ sco_connecting,
+ sco_connected,
+ sco_disconnected,
+ sco_newconn,
+ sco_complete,
+ sco_linkmode,
+ sco_input,
+};
+
+int sco_sendspace = 4096;
+int sco_recvspace = 4096;
+
+/*
+ * User Request.
+ * up is socket
+ * m is either
+ * optional mbuf chain containing message
+ * ioctl command (PRU_CONTROL)
+ * nam is either
+ * optional mbuf chain containing an address
+ * ioctl data (PRU_CONTROL)
+ * optionally, protocol number (PRU_ATTACH)
+ * ctl is optional mbuf chain containing socket options
+ * l is pointer to process requesting action (if any)
+ *
+ * we are responsible for disposing of m and ctl if
+ * they are mbuf chains
+ */
+int
+sco_usrreq(struct socket *up, int req, struct mbuf *m,
+ struct mbuf *nam, struct mbuf *ctl)
+{
+ struct sco_pcb *pcb = (struct sco_pcb *)up->so_pcb;
+ struct sockaddr_bt *sa;
+ struct mbuf *m0;
+ int err = 0;
+
+#ifdef notyet /* XXX */
+ DPRINTFN(2, "%s\n", prurequests[req]);
+#endif
+
+ switch(req) {
+ case PRU_CONTROL:
+ return EOPNOTSUPP;
+
+#ifdef notyet /* XXX */
+ case PRU_PURGEIF:
+ return EOPNOTSUPP;
+#endif
+
+ case PRU_ATTACH:
+ if (pcb)
+ return EINVAL;
+
+ err = soreserve(up, sco_sendspace, sco_recvspace);
+ if (err)
+ return err;
+
+ return sco_attach((struct sco_pcb **)&up->so_pcb,
+ &sco_proto, up);
+ }
+
+ /* anything after here *requires* a pcb */
+ if (pcb == NULL) {
+ err = EINVAL;
+ goto release;
+ }
+
+ switch(req) {
+ case PRU_DISCONNECT:
+ soisdisconnecting(up);
+ return sco_disconnect(pcb, up->so_linger);
+
+ case PRU_ABORT:
+ sco_disconnect(pcb, 0);
+ soisdisconnected(up);
+ /* fall through to */
+ case PRU_DETACH:
+ return sco_detach((struct sco_pcb **)&up->so_pcb);
+
+ case PRU_BIND:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+
+ if (sa->bt_len != sizeof(struct sockaddr_bt))
+ return EINVAL;
+
+ if (sa->bt_family != AF_BLUETOOTH)
+ return EAFNOSUPPORT;
+
+ return sco_bind(pcb, sa);
+
+ case PRU_CONNECT:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+
+ if (sa->bt_len != sizeof(struct sockaddr_bt))
+ return EINVAL;
+
+ if (sa->bt_family != AF_BLUETOOTH)
+ return EAFNOSUPPORT;
+
+ soisconnecting(up);
+ return sco_connect(pcb, sa);
+
+ case PRU_PEERADDR:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+ nam->m_len = sizeof(struct sockaddr_bt);
+ return sco_peeraddr(pcb, sa);
+
+ case PRU_SOCKADDR:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+ nam->m_len = sizeof(struct sockaddr_bt);
+ return sco_sockaddr(pcb, sa);
+
+ case PRU_SHUTDOWN:
+ socantsendmore(up);
+ break;
+
+ case PRU_SEND:
+ KASSERT(m != NULL);
+ if (m->m_pkthdr.len == 0)
+ break;
+
+ if (m->m_pkthdr.len > pcb->sp_mtu) {
+ err = EMSGSIZE;
+ break;
+ }
+
+ m0 = m_copym(m, 0, M_COPYALL, M_DONTWAIT);
+ if (m0 == NULL) {
+ err = ENOMEM;
+ break;
+ }
+
+ if (ctl) /* no use for that */
+ m_freem(ctl);
+
+ sbappendrecord(&up->so_snd, m);
+ return sco_send(pcb, m0);
+
+ case PRU_SENSE:
+ return 0; /* (no sense - Doh!) */
+
+ case PRU_RCVD:
+ case PRU_RCVOOB:
+ return EOPNOTSUPP; /* (no release) */
+
+ case PRU_LISTEN:
+ return sco_listen(pcb);
+
+ case PRU_ACCEPT:
+ KASSERT(nam != NULL);
+ sa = mtod(nam, struct sockaddr_bt *);
+ nam->m_len = sizeof(struct sockaddr_bt);
+ return sco_peeraddr(pcb, sa);
+
+ case PRU_CONNECT2:
+ case PRU_SENDOOB:
+ case PRU_FASTTIMO:
+ case PRU_SLOWTIMO:
+ case PRU_PROTORCV:
+ case PRU_PROTOSEND:
+ err = EOPNOTSUPP;
+ break;
+
+ default:
+ UNKNOWN(req);
+ err = EOPNOTSUPP;
+ break;
+ }
+
+release:
+ if (m) m_freem(m);
+ if (ctl) m_freem(ctl);
+ return err;
+}
+
+/*
+ * get/set socket options
+ */
+int
+sco_ctloutput(int req, struct socket *so, int level,
+ int optname, struct mbuf **opt)
+{
+ struct sco_pcb *pcb = (struct sco_pcb *)so->so_pcb;
+ struct mbuf *m;
+ int err = 0;
+
+#ifdef notyet /* XXX */
+ DPRINTFN(2, "req %s\n", prcorequests[req]);
+#endif
+
+ if (pcb == NULL)
+ return EINVAL;
+
+ if (level != BTPROTO_SCO)
+ return ENOPROTOOPT;
+
+ switch(req) {
+ case PRCO_GETOPT:
+ m = m_get(M_WAIT, MT_SOOPTS);
+ m->m_len = sco_getopt(pcb, optname, mtod(m, uint8_t *));
+ if (m->m_len == 0) {
+ m_freem(m);
+ m = NULL;
+ err = ENOPROTOOPT;
+ }
+ *opt = m;
+ break;
+
+ case PRCO_SETOPT:
+ m = *opt;
+ KASSERT(m != NULL);
+ err = sco_setopt(pcb, optname, mtod(m, uint8_t *));
+ m_freem(m);
+ break;
+
+ default:
+ err = ENOPROTOOPT;
+ break;
+ }
+
+ return err;
+}
+
+/*****************************************************************************
+ *
+ * SCO Protocol socket callbacks
+ *
+ */
+static void
+sco_connecting(void *arg)
+{
+ struct socket *so = arg;
+
+ DPRINTF("Connecting\n");
+ soisconnecting(so);
+}
+
+static void
+sco_connected(void *arg)
+{
+ struct socket *so = arg;
+
+ DPRINTF("Connected\n");
+ soisconnected(so);
+}
+
+static void
+sco_disconnected(void *arg, int err)
+{
+ struct socket *so = arg;
+
+ DPRINTF("Disconnected (%d)\n", err);
+
+ so->so_error = err;
+ soisdisconnected(so);
+}
+
+static void *
+sco_newconn(void *arg, struct sockaddr_bt *laddr,
+ struct sockaddr_bt *raddr)
+{
+ struct socket *so = arg;
+
+ DPRINTF("New Connection\n");
+ so = sonewconn(so, 0);
+ if (so == NULL)
+ return NULL;
+
+ soisconnecting(so);
+ return so->so_pcb;
+}
+
+static void
+sco_complete(void *arg, int num)
+{
+ struct socket *so = arg;
+
+ while (num-- > 0)
+ sbdroprecord(&so->so_snd);
+
+ sowwakeup(so);
+}
+
+static void
+sco_linkmode(void *arg, int mode)
+{
+}
+
+static void
+sco_input(void *arg, struct mbuf *m)
+{
+ struct socket *so = arg;
+
+ /*
+ * since this data is time sensitive, if the buffer
+ * is full we just dump data until the latest one
+ * will fit.
+ */
+
+ while (m->m_pkthdr.len > sbspace(&so->so_rcv))
+ sbdroprecord(&so->so_rcv);
+
+ DPRINTFN(10, "received %d bytes\n", m->m_pkthdr.len);
+
+ sbappendrecord(&so->so_rcv, m);
+ sorwakeup(so);
+}
diff --git a/sys/netbt/sco_upper.c b/sys/netbt/sco_upper.c
new file mode 100644
index 00000000000..f867367c5db
--- /dev/null
+++ b/sys/netbt/sco_upper.c
@@ -0,0 +1,360 @@
+/* $OpenBSD: sco_upper.c,v 1.1 2007/06/01 02:46:12 uwe Exp $ */
+/* $NetBSD: sco_upper.c,v 1.6 2007/03/30 20:47:03 plunky Exp $ */
+
+/*-
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Written by Iain Hibbert for Itronix Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ * or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/hci.h>
+#include <netbt/sco.h>
+
+/****************************************************************************
+ *
+ * SCO - Upper Protocol API
+ */
+
+struct sco_pcb_list sco_pcb = LIST_HEAD_INITIALIZER(sco_pcb);
+
+/*
+ * sco_attach(handle, proto, upper)
+ *
+ * Attach a new instance of SCO pcb to handle
+ */
+int
+sco_attach(struct sco_pcb **handle,
+ const struct btproto *proto, void *upper)
+{
+ struct sco_pcb *pcb;
+
+ KASSERT(handle != NULL);
+ KASSERT(proto != NULL);
+ KASSERT(upper != NULL);
+
+ pcb = malloc(sizeof(struct sco_pcb), M_BLUETOOTH, M_NOWAIT);
+ if (pcb == NULL)
+ return ENOMEM;
+ bzero(pcb, sizeof *pcb);
+
+ pcb->sp_proto = proto;
+ pcb->sp_upper = upper;
+
+ LIST_INSERT_HEAD(&sco_pcb, pcb, sp_next);
+
+ *handle = pcb;
+ return 0;
+}
+
+/*
+ * sco_bind(pcb, sockaddr)
+ *
+ * Bind SCO pcb to local address
+ */
+int
+sco_bind(struct sco_pcb *pcb, struct sockaddr_bt *addr)
+{
+
+ bdaddr_copy(&pcb->sp_laddr, &addr->bt_bdaddr);
+ return 0;
+}
+
+/*
+ * sco_sockaddr(pcb, sockaddr)
+ *
+ * Copy local address of PCB to sockaddr
+ */
+int
+sco_sockaddr(struct sco_pcb *pcb, struct sockaddr_bt *addr)
+{
+
+ memset(addr, 0, sizeof(struct sockaddr_bt));
+ addr->bt_len = sizeof(struct sockaddr_bt);
+ addr->bt_family = AF_BLUETOOTH;
+ bdaddr_copy(&addr->bt_bdaddr, &pcb->sp_laddr);
+ return 0;
+}
+
+/*
+ * sco_connect(pcb, sockaddr)
+ *
+ * Initiate a SCO connection to the destination address.
+ */
+int
+sco_connect(struct sco_pcb *pcb, struct sockaddr_bt *dest)
+{
+ hci_add_sco_con_cp cp;
+ struct hci_unit *unit;
+ struct hci_link *acl, *sco;
+ int err;
+
+ if (pcb->sp_flags & SP_LISTENING)
+ return EINVAL;
+
+ bdaddr_copy(&pcb->sp_raddr, &dest->bt_bdaddr);
+
+ if (bdaddr_any(&pcb->sp_raddr))
+ return EDESTADDRREQ;
+
+ if (bdaddr_any(&pcb->sp_laddr)) {
+ err = hci_route_lookup(&pcb->sp_laddr, &pcb->sp_raddr);
+ if (err)
+ return err;
+ }
+
+ unit = hci_unit_lookup(&pcb->sp_laddr);
+ if (unit == NULL)
+ return ENETDOWN;
+
+ /*
+ * We must have an already open ACL connection before we open the SCO
+ * connection, and since SCO connections dont happen on their own we
+ * will not open one, the application wanting this should have opened
+ * it previously.
+ */
+ acl = hci_link_lookup_bdaddr(unit, &pcb->sp_raddr, HCI_LINK_ACL);
+ if (acl == NULL || acl->hl_state != HCI_LINK_OPEN)
+ return EHOSTUNREACH;
+
+ sco = hci_link_alloc(unit);
+ if (sco == NULL)
+ return ENOMEM;
+
+ sco->hl_type = HCI_LINK_SCO;
+ bdaddr_copy(&sco->hl_bdaddr, &pcb->sp_raddr);
+
+ sco->hl_link = hci_acl_open(unit, &pcb->sp_raddr);
+ KASSERT(sco->hl_link == acl);
+
+ cp.con_handle = htole16(acl->hl_handle);
+ cp.pkt_type = htole16(0x00e0); /* HV1, HV2, HV3 */
+ err = hci_send_cmd(unit, HCI_CMD_ADD_SCO_CON, &cp, sizeof(cp));
+ if (err) {
+ hci_link_free(sco, err);
+ return err;
+ }
+
+ sco->hl_sco = pcb;
+ pcb->sp_link = sco;
+
+ pcb->sp_mtu = unit->hci_max_sco_size;
+ return 0;
+}
+
+/*
+ * sco_peeraddr(pcb, sockaddr)
+ *
+ * Copy remote address of SCO pcb to sockaddr
+ */
+int
+sco_peeraddr(struct sco_pcb *pcb, struct sockaddr_bt *addr)
+{
+
+ memset(addr, 0, sizeof(struct sockaddr_bt));
+ addr->bt_len = sizeof(struct sockaddr_bt);
+ addr->bt_family = AF_BLUETOOTH;
+ bdaddr_copy(&addr->bt_bdaddr, &pcb->sp_raddr);
+ return 0;
+}
+
+/*
+ * sco_disconnect(pcb, linger)
+ *
+ * Initiate disconnection of connected SCO pcb
+ */
+int
+sco_disconnect(struct sco_pcb *pcb, int linger)
+{
+ hci_discon_cp cp;
+ struct hci_link *sco;
+ int err;
+
+ sco = pcb->sp_link;
+ if (sco == NULL)
+ return EINVAL;
+
+ cp.con_handle = htole16(sco->hl_handle);
+ cp.reason = 0x13; /* "Remote User Terminated Connection" */
+
+ err = hci_send_cmd(sco->hl_unit, HCI_CMD_DISCONNECT, &cp, sizeof(cp));
+ if (err || linger == 0) {
+ sco->hl_sco = NULL;
+ pcb->sp_link = NULL;
+ hci_link_free(sco, err);
+ }
+
+ return err;
+}
+
+/*
+ * sco_detach(handle)
+ *
+ * Detach SCO pcb from handle and clear up
+ */
+int
+sco_detach(struct sco_pcb **handle)
+{
+ struct sco_pcb *pcb;
+
+ KASSERT(handle != NULL);
+ pcb = *handle;
+ *handle = NULL;
+
+ if (pcb == NULL)
+ return EINVAL;
+
+ if (pcb->sp_link != NULL) {
+ sco_disconnect(pcb, 0);
+ pcb->sp_link = NULL;
+ }
+
+ LIST_REMOVE(pcb, sp_next);
+ free(pcb, M_BLUETOOTH);
+ return 0;
+}
+
+/*
+ * sco_listen(pcb)
+ *
+ * Mark pcb as a listener.
+ */
+int
+sco_listen(struct sco_pcb *pcb)
+{
+
+ if (pcb->sp_link != NULL)
+ return EINVAL;
+
+ pcb->sp_flags |= SP_LISTENING;
+ return 0;
+}
+
+/*
+ * sco_send(pcb, mbuf)
+ *
+ * Send data on SCO pcb.
+ *
+ * Gross hackage, we just output the packet directly onto the unit queue.
+ * This will work fine for one channel per unit, but for more channels it
+ * really needs fixing. We set the context so that when the packet is sent,
+ * we can drop a record from the socket buffer.
+ */
+int
+sco_send(struct sco_pcb *pcb, struct mbuf *m)
+{
+ hci_scodata_hdr_t *hdr;
+ int plen;
+
+ if (pcb->sp_link == NULL) {
+ m_freem(m);
+ return EINVAL;
+ }
+
+ plen = m->m_pkthdr.len;
+ DPRINTFN(10, "%d bytes\n", plen);
+
+ /*
+ * This is a temporary limitation, as USB devices cannot
+ * handle SCO packet sizes that are not an integer number
+ * of Isochronous frames. See ubt(4)
+ */
+ if (plen != pcb->sp_mtu) {
+ m_freem(m);
+ return EMSGSIZE;
+ }
+
+ M_PREPEND(m, sizeof(hci_scodata_hdr_t), M_DONTWAIT);
+ if (m == NULL)
+ return ENOMEM;
+
+ hdr = mtod(m, hci_scodata_hdr_t *);
+ hdr->type = HCI_SCO_DATA_PKT;
+ hdr->con_handle = htole16(pcb->sp_link->hl_handle);
+ hdr->length = plen;
+
+ pcb->sp_pending++;
+ M_SETCTX(m, pcb->sp_link);
+ hci_output_sco(pcb->sp_link->hl_unit, m);
+
+ return 0;
+}
+
+/*
+ * sco_setopt(pcb, option, addr)
+ *
+ * Set SCO pcb options
+ */
+int
+sco_setopt(struct sco_pcb *pcb, int opt, void *addr)
+{
+ int err = 0;
+
+ switch (opt) {
+ default:
+ err = ENOPROTOOPT;
+ break;
+ }
+
+ return err;
+}
+
+/*
+ * sco_getopt(pcb, option, addr)
+ *
+ * Get SCO pcb options
+ */
+int
+sco_getopt(struct sco_pcb *pcb, int opt, void *addr)
+{
+
+ switch (opt) {
+ case SO_SCO_MTU:
+ *(uint16_t *)addr = pcb->sp_mtu;
+ return sizeof(uint16_t);
+
+ case SO_SCO_HANDLE:
+ if (pcb->sp_link) {
+ *(uint16_t *)addr = pcb->sp_link->hl_handle;
+ return sizeof(uint16_t);
+ }
+ break;
+
+ default:
+ break;
+ }
+ return 0;
+}