diff options
-rw-r--r-- | sys/conf/files | 11 | ||||
-rw-r--r-- | sys/netbt/bt_proto.c | 23 | ||||
-rw-r--r-- | sys/netbt/hci.h | 13 | ||||
-rw-r--r-- | sys/netbt/hci_event.c | 4 | ||||
-rw-r--r-- | sys/netbt/hci_ioctl.c | 312 | ||||
-rw-r--r-- | sys/netbt/hci_link.c | 4 | ||||
-rw-r--r-- | sys/netbt/hci_socket.c | 20 | ||||
-rw-r--r-- | sys/netbt/hci_unit.c | 11 | ||||
-rw-r--r-- | sys/netbt/l2cap.h | 9 | ||||
-rw-r--r-- | sys/netbt/l2cap_misc.c | 17 | ||||
-rw-r--r-- | sys/netbt/l2cap_socket.c | 407 | ||||
-rw-r--r-- | sys/netbt/l2cap_upper.c | 505 | ||||
-rw-r--r-- | sys/netbt/rfcomm.h | 425 | ||||
-rw-r--r-- | sys/netbt/rfcomm_dlc.c | 414 | ||||
-rw-r--r-- | sys/netbt/rfcomm_session.c | 1690 | ||||
-rw-r--r-- | sys/netbt/rfcomm_socket.c | 420 | ||||
-rw-r--r-- | sys/netbt/rfcomm_upper.c | 502 | ||||
-rw-r--r-- | sys/netbt/sco.h | 4 | ||||
-rw-r--r-- | sys/netbt/sco_socket.c | 380 | ||||
-rw-r--r-- | sys/netbt/sco_upper.c | 360 |
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; +} |