diff options
Diffstat (limited to 'sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c')
-rw-r--r-- | sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c | 1222 |
1 files changed, 1222 insertions, 0 deletions
diff --git a/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c b/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c new file mode 100644 index 0000000..3b24afd --- /dev/null +++ b/sys/netgraph/bluetooth/socket/ng_btsocket_l2cap_raw.c @@ -0,0 +1,1222 @@ +/* + * ng_btsocket_l2cap_raw.c + * + * Copyright (c) 2001-2002 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: ng_btsocket_l2cap_raw.c,v 1.1 2002/09/04 21:44:00 max Exp $ + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/domain.h> +#include <sys/errno.h> +#include <sys/filedesc.h> +#include <sys/ioccom.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/mutex.h> +#include <sys/protosw.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sysctl.h> +#include <sys/taskqueue.h> +#include <netgraph/ng_message.h> +#include <netgraph/netgraph.h> +#include <bitstring.h> +#include "ng_bluetooth.h" +#include "ng_hci.h" +#include "ng_l2cap.h" +#include "ng_btsocket.h" +#include "ng_btsocket_l2cap.h" + +/* MALLOC define */ +#ifdef NG_SEPARATE_MALLOC +MALLOC_DEFINE(M_NETGRAPH_BTSOCKET_L2CAP_RAW, "netgraph_btsocks_l2cap_raw", + "Netgraph Bluetooth raw L2CAP sockets"); +#else +#define M_NETGRAPH_BTSOCKET_L2CAP_RAW M_NETGRAPH +#endif /* NG_SEPARATE_MALLOC */ + +/* Netgraph node methods */ +static ng_constructor_t ng_btsocket_l2cap_raw_node_constructor; +static ng_rcvmsg_t ng_btsocket_l2cap_raw_node_rcvmsg; +static ng_shutdown_t ng_btsocket_l2cap_raw_node_shutdown; +static ng_newhook_t ng_btsocket_l2cap_raw_node_newhook; +static ng_connect_t ng_btsocket_l2cap_raw_node_connect; +static ng_rcvdata_t ng_btsocket_l2cap_raw_node_rcvdata; +static ng_disconnect_t ng_btsocket_l2cap_raw_node_disconnect; + +static void ng_btsocket_l2cap_raw_input (void *, int); +static void ng_btsocket_l2cap_raw_rtclean (void *, int); +static void ng_btsocket_l2cap_raw_get_token (u_int32_t *); + +/* Netgraph type descriptor */ +static struct ng_type typestruct = { + NG_ABI_VERSION, + NG_BTSOCKET_L2CAP_RAW_NODE_TYPE, /* typename */ + NULL, /* modevent */ + ng_btsocket_l2cap_raw_node_constructor, /* constructor */ + ng_btsocket_l2cap_raw_node_rcvmsg, /* control message */ + ng_btsocket_l2cap_raw_node_shutdown, /* destructor */ + ng_btsocket_l2cap_raw_node_newhook, /* new hook */ + NULL, /* find hook */ + ng_btsocket_l2cap_raw_node_connect, /* connect hook */ + ng_btsocket_l2cap_raw_node_rcvdata, /* data */ + ng_btsocket_l2cap_raw_node_disconnect, /* disconnect hook */ + NULL /* node command list */ +}; + +/* Globals */ +extern int ifqmaxlen; +static u_int32_t ng_btsocket_l2cap_raw_debug_level; +static u_int32_t ng_btsocket_l2cap_raw_ioctl_timeout; +static node_p ng_btsocket_l2cap_raw_node; +static struct ng_bt_itemq ng_btsocket_l2cap_raw_queue; +static struct mtx ng_btsocket_l2cap_raw_queue_mtx; +static struct task ng_btsocket_l2cap_raw_queue_task; +static LIST_HEAD(, ng_btsocket_l2cap_raw_pcb) ng_btsocket_l2cap_raw_sockets; +static struct mtx ng_btsocket_l2cap_raw_sockets_mtx; +static u_int32_t ng_btsocket_l2cap_raw_token; +static struct mtx ng_btsocket_l2cap_raw_token_mtx; +static LIST_HEAD(, ng_btsocket_l2cap_rtentry) ng_btsocket_l2cap_raw_rt; +static struct mtx ng_btsocket_l2cap_raw_rt_mtx; +static struct task ng_btsocket_l2cap_raw_rt_task; + +/* Sysctl tree */ +SYSCTL_DECL(_net_bluetooth_l2cap_sockets); +SYSCTL_NODE(_net_bluetooth_l2cap_sockets, OID_AUTO, raw, CTLFLAG_RW, + 0, "Bluetooth raw L2CAP sockets family"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, debug_level, + CTLFLAG_RW, + &ng_btsocket_l2cap_raw_debug_level, NG_BTSOCKET_WARN_LEVEL, + "Bluetooth raw L2CAP sockets debug level"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, ioctl_timeout, + CTLFLAG_RW, + &ng_btsocket_l2cap_raw_ioctl_timeout, 5, + "Bluetooth raw L2CAP sockets ioctl timeout"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, queue_len, + CTLFLAG_RD, + &ng_btsocket_l2cap_raw_queue.len, 0, + "Bluetooth raw L2CAP sockets input queue length"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, queue_maxlen, + CTLFLAG_RD, + &ng_btsocket_l2cap_raw_queue.maxlen, 0, + "Bluetooth raw L2CAP sockets input queue max. length"); +SYSCTL_INT(_net_bluetooth_l2cap_sockets_raw, OID_AUTO, queue_drops, + CTLFLAG_RD, + &ng_btsocket_l2cap_raw_queue.drops, 0, + "Bluetooth raw L2CAP sockets input queue drops"); + +/* Debug */ +#define NG_BTSOCKET_L2CAP_RAW_INFO \ + if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_INFO_LEVEL) \ + printf + +#define NG_BTSOCKET_L2CAP_RAW_WARN \ + if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_WARN_LEVEL) \ + printf + +#define NG_BTSOCKET_L2CAP_RAW_ERR \ + if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_ERR_LEVEL) \ + printf + +#define NG_BTSOCKET_L2CAP_RAW_ALERT \ + if (ng_btsocket_l2cap_raw_debug_level >= NG_BTSOCKET_ALERT_LEVEL) \ + printf + +/***************************************************************************** + ***************************************************************************** + ** Netgraph node interface + ***************************************************************************** + *****************************************************************************/ + +/* + * Netgraph node constructor. Do not allow to create node of this type. + */ + +static int +ng_btsocket_l2cap_raw_node_constructor(node_p node) +{ + return (EINVAL); +} /* ng_btsocket_l2cap_raw_node_constructor */ + +/* + * Do local shutdown processing. Let old node go and create new fresh one. + */ + +static int +ng_btsocket_l2cap_raw_node_shutdown(node_p node) +{ + int error = 0; + + NG_NODE_UNREF(node); + + /* Create new node */ + error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_raw_node); + if (error != 0) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not create Netgraph node, error=%d\n", __func__, error); + + ng_btsocket_l2cap_raw_node = NULL; + + return (error); + } + + error = ng_name_node(ng_btsocket_l2cap_raw_node, + NG_BTSOCKET_L2CAP_RAW_NODE_TYPE); + if (error != NULL) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not name Netgraph node, error=%d\n", __func__, error); + + NG_NODE_UNREF(ng_btsocket_l2cap_raw_node); + ng_btsocket_l2cap_raw_node = NULL; + + return (error); + } + + return (0); +} /* ng_btsocket_l2cap_raw_node_shutdown */ + +/* + * We allow any hook to be connected to the node. + */ + +static int +ng_btsocket_l2cap_raw_node_newhook(node_p node, hook_p hook, char const *name) +{ + return (0); +} /* ng_btsocket_l2cap_raw_node_newhook */ + +/* + * Just say "YEP, that's OK by me!" + */ + +static int +ng_btsocket_l2cap_raw_node_connect(hook_p hook) +{ + NG_HOOK_SET_PRIVATE(hook, NULL); + NG_HOOK_REF(hook); /* Keep extra reference to the hook */ + + return (0); +} /* ng_btsocket_l2cap_raw_node_connect */ + +/* + * Hook disconnection. Schedule route cleanup task + */ + +static int +ng_btsocket_l2cap_raw_node_disconnect(hook_p hook) +{ + /* + * If hook has private information than we must have this hook in + * the routing table and must schedule cleaning for the routing table. + * Otherwise hook was connected but we never got "hook_info" message, + * so we have never added this hook to the routing table and it save + * to just delete it. + */ + + if (NG_HOOK_PRIVATE(hook) != NULL) + return (taskqueue_enqueue(taskqueue_swi, + &ng_btsocket_l2cap_raw_rt_task)); + + NG_HOOK_UNREF(hook); /* Remove extra reference */ + + return (0); +} /* ng_btsocket_l2cap_raw_node_disconnect */ + +/* + * Process incoming messages + */ + +static int +ng_btsocket_l2cap_raw_node_rcvmsg(node_p node, item_p item, hook_p hook) +{ + struct ng_mesg *msg = NGI_MSG(item); /* item still has message */ + int error = 0; + + if (msg != NULL && msg->header.typecookie == NGM_L2CAP_COOKIE) { + mtx_lock(&ng_btsocket_l2cap_raw_queue_mtx); + if (NG_BT_ITEMQ_FULL(&ng_btsocket_l2cap_raw_queue)) { + NG_BTSOCKET_L2CAP_RAW_ERR( +"%s: Input queue is full\n", __func__); + + NG_BT_ITEMQ_DROP(&ng_btsocket_l2cap_raw_queue); + NG_FREE_ITEM(item); + error = ENOBUFS; + } else { + if (hook != NULL) { + NG_HOOK_REF(hook); + NGI_SET_HOOK(item, hook); + } + + NG_BT_ITEMQ_ENQUEUE(&ng_btsocket_l2cap_raw_queue, item); + error = taskqueue_enqueue(taskqueue_swi, + &ng_btsocket_l2cap_raw_queue_task); + } + mtx_unlock(&ng_btsocket_l2cap_raw_queue_mtx); + } else { + NG_FREE_ITEM(item); + error = EINVAL; + } + + return (error); +} /* ng_btsocket_l2cap_raw_node_rcvmsg */ + +/* + * Receive data on a hook + */ + +static int +ng_btsocket_l2cap_raw_node_rcvdata(hook_p hook, item_p item) +{ + NG_FREE_ITEM(item); + + return (EINVAL); +} /* ng_btsocket_l2cap_raw_node_rcvdata */ + +/***************************************************************************** + ***************************************************************************** + ** Socket interface + ***************************************************************************** + *****************************************************************************/ + +/* + * L2CAP sockets input routine + */ + +static void +ng_btsocket_l2cap_raw_input(void *context, int pending) +{ + item_p item = NULL; + hook_p hook = NULL; + struct ng_mesg *msg = NULL; + + for (;;) { + mtx_lock(&ng_btsocket_l2cap_raw_queue_mtx); + NG_BT_ITEMQ_DEQUEUE(&ng_btsocket_l2cap_raw_queue, item); + mtx_unlock(&ng_btsocket_l2cap_raw_queue_mtx); + + if (item == NULL) + break; + + KASSERT((item->el_flags & NGQF_TYPE) == NGQF_MESG, +("%s: invalid item type=%ld\n", __func__, (item->el_flags & NGQF_TYPE))); + + NGI_GET_MSG(item, msg); + NGI_GET_HOOK(item, hook); + NG_FREE_ITEM(item); + + switch (msg->header.cmd) { + case NGM_L2CAP_NODE_HOOK_INFO: { + ng_btsocket_l2cap_rtentry_t *rt = NULL; + + if (hook == NULL || NG_HOOK_NOT_VALID(hook) || + msg->header.arglen != sizeof(bdaddr_t)) + break; + + if (bcmp(msg->data, NG_HCI_BDADDR_ANY, + sizeof(bdaddr_t)) == 0) + break; + + rt = (ng_btsocket_l2cap_rtentry_t *) + NG_HOOK_PRIVATE(hook); + if (rt == NULL) { + MALLOC(rt, ng_btsocket_l2cap_rtentry_p, + sizeof(*rt), + M_NETGRAPH_BTSOCKET_L2CAP_RAW, + M_NOWAIT|M_ZERO); + if (rt == NULL) + break; + + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + LIST_INSERT_HEAD(&ng_btsocket_l2cap_raw_rt, + rt, next); + mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); + + NG_HOOK_SET_PRIVATE(hook, rt); + } + + bcopy(msg->data, &rt->src, sizeof(rt->src)); + rt->hook = hook; + + NG_BTSOCKET_L2CAP_RAW_INFO( +"%s: Updating hook \"%s\", src bdaddr=%x:%x:%x:%x:%x:%x\n", + __func__, NG_HOOK_NAME(hook), + rt->src.b[5], rt->src.b[4], rt->src.b[3], + rt->src.b[2], rt->src.b[1], rt->src.b[0]); + } break; + + case NGM_L2CAP_NODE_GET_FLAGS: + case NGM_L2CAP_NODE_GET_DEBUG: + case NGM_L2CAP_NODE_GET_CON_LIST: + case NGM_L2CAP_NODE_GET_CHAN_LIST: + case NGM_L2CAP_L2CA_PING: + case NGM_L2CAP_L2CA_GET_INFO: { + ng_btsocket_l2cap_raw_pcb_p pcb = NULL; + + if (msg->header.token == 0 || + !(msg->header.flags & NGF_RESP)) + break; + + mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); + + LIST_FOREACH(pcb, &ng_btsocket_l2cap_raw_sockets, next) + if (pcb->token == msg->header.token) { + pcb->msg = msg; + msg = NULL; + wakeup(&pcb->msg); + break; + } + + mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); + } break; + + default: + NG_BTSOCKET_L2CAP_RAW_WARN( +"%s: Unknown message, cmd=%d\n", __func__, msg->header.cmd); + break; + } + + if (hook != NULL) + NG_HOOK_UNREF(hook); /* remove extra reference */ + + NG_FREE_MSG(msg); /* Checks for msg != NULL */ + } +} /* ng_btsocket_l2cap_raw_default_msg_input */ + +/* + * Route cleanup task. Gets scheduled when hook is disconnected. Here we + * will find all sockets that use "invalid" hook and disconnect them. + */ + +static void +ng_btsocket_l2cap_raw_rtclean(void *context, int pending) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = NULL; + ng_btsocket_l2cap_rtentry_p rt = NULL; + + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); + + /* + * First disconnect all sockets that use "invalid" hook + */ + + LIST_FOREACH(pcb, &ng_btsocket_l2cap_raw_sockets, next) + if (pcb->rt != NULL && + pcb->rt->hook != NULL && NG_HOOK_NOT_VALID(pcb->rt->hook)) { + if (pcb->so != NULL && + pcb->so->so_state & SS_ISCONNECTED) + soisdisconnected(pcb->so); + + pcb->rt = NULL; + } + + /* + * Now cleanup routing table + */ + + rt = LIST_FIRST(&ng_btsocket_l2cap_raw_rt); + while (rt != NULL) { + ng_btsocket_l2cap_rtentry_p rt_next = LIST_NEXT(rt, next); + + if (rt->hook != NULL && NG_HOOK_NOT_VALID(rt->hook)) { + LIST_REMOVE(rt, next); + + NG_HOOK_SET_PRIVATE(rt->hook, NULL); + NG_HOOK_UNREF(rt->hook); /* Remove extra reference */ + + bzero(rt, sizeof(*rt)); + FREE(rt, M_NETGRAPH_BTSOCKET_L2CAP_RAW); + } + + rt = rt_next; + } + + mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); + mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); +} /* ng_btsocket_l2cap_raw_rtclean */ + +/* + * Initialize everything + */ + +void +ng_btsocket_l2cap_raw_init(void) +{ + int error = 0; + + ng_btsocket_l2cap_raw_node = NULL; + ng_btsocket_l2cap_raw_debug_level = NG_BTSOCKET_WARN_LEVEL; + ng_btsocket_l2cap_raw_ioctl_timeout = 5; + + /* Register Netgraph node type */ + error = ng_newtype(&typestruct); + if (error != 0) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not register Netgraph node type, error=%d\n", __func__, error); + + return; + } + + /* Create Netgrapg node */ + error = ng_make_node_common(&typestruct, &ng_btsocket_l2cap_raw_node); + if (error != 0) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not create Netgraph node, error=%d\n", __func__, error); + + ng_btsocket_l2cap_raw_node = NULL; + + return; + } + + error = ng_name_node(ng_btsocket_l2cap_raw_node, + NG_BTSOCKET_L2CAP_RAW_NODE_TYPE); + if (error != 0) { + NG_BTSOCKET_L2CAP_RAW_ALERT( +"%s: Could not name Netgraph node, error=%d\n", __func__, error); + + NG_NODE_UNREF(ng_btsocket_l2cap_raw_node); + ng_btsocket_l2cap_raw_node = NULL; + + return; + } + + /* Create input queue */ + NG_BT_ITEMQ_INIT(&ng_btsocket_l2cap_raw_queue, ifqmaxlen); + mtx_init(&ng_btsocket_l2cap_raw_queue_mtx, + "btsocks_l2cap_queue_mtx", NULL, MTX_DEF); + TASK_INIT(&ng_btsocket_l2cap_raw_queue_task, 0, + ng_btsocket_l2cap_raw_input, NULL); + + /* Create list of sockets */ + LIST_INIT(&ng_btsocket_l2cap_raw_sockets); + mtx_init(&ng_btsocket_l2cap_raw_sockets_mtx, + "btsocks_l2cap_sockets_mtx", NULL, MTX_DEF); + + /* Tokens */ + ng_btsocket_l2cap_raw_token = 0; + mtx_init(&ng_btsocket_l2cap_raw_token_mtx, + "btsocks_l2cap_token_mtx", NULL, MTX_DEF); + + /* Routing table */ + LIST_INIT(&ng_btsocket_l2cap_raw_rt); + mtx_init(&ng_btsocket_l2cap_raw_rt_mtx, + "btsocks_l2cap_rt_mtx", NULL, MTX_DEF); + TASK_INIT(&ng_btsocket_l2cap_raw_rt_task, 0, + ng_btsocket_l2cap_raw_rtclean, NULL); +} /* ng_btsocket_l2cap_raw_init */ + +/* + * Abort connection on socket + */ + +int +ng_btsocket_l2cap_raw_abort(struct socket *so) +{ + return (ng_btsocket_l2cap_raw_detach(so)); +} /* ng_btsocket_l2cap_raw_abort */ + +/* + * Create and attach new socket + */ + +int +ng_btsocket_l2cap_raw_attach(struct socket *so, int proto, struct thread *td) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + int error; + + if (pcb != NULL) + return (EISCONN); + + if (ng_btsocket_l2cap_raw_node == NULL) + return (EPROTONOSUPPORT); + if (so->so_type != SOCK_RAW) + return (ESOCKTNOSUPPORT); + if ((error = suser(td)) != 0) + return (error); + + /* Reserve send and receive space if it is not reserved yet */ + error = soreserve(so, NG_BTSOCKET_L2CAP_RAW_SENDSPACE, + NG_BTSOCKET_L2CAP_RAW_RECVSPACE); + if (error != 0) + return (error); + + /* Allocate the PCB */ + MALLOC(pcb, ng_btsocket_l2cap_raw_pcb_p, sizeof(*pcb), + M_NETGRAPH_BTSOCKET_L2CAP_RAW, M_WAITOK | M_ZERO); + if (pcb == NULL) + return (ENOMEM); + + /* Link the PCB and the socket */ + so->so_pcb = (caddr_t) pcb; + pcb->so = so; + + /* Add the PCB to the list */ + mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); + LIST_INSERT_HEAD(&ng_btsocket_l2cap_raw_sockets, pcb, next); + mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); + + return (0); +} /* ng_btsocket_l2cap_raw_attach */ + +/* + * Bind socket + */ + +int +ng_btsocket_l2cap_raw_bind(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_l2cap_raw_pcb_t *pcb = so2l2cap_raw_pcb(so); + struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + if (sa == NULL) + return (EINVAL); + if (sa->l2cap_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->l2cap_len != sizeof(*sa)) + return (EINVAL); + if (bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) + return (EINVAL); + + bcopy(&sa->l2cap_bdaddr, &pcb->src, sizeof(pcb->src)); + + return (0); +} /* ng_btsocket_l2cap_raw_bind */ + +/* + * Connect socket + */ + +int +ng_btsocket_l2cap_raw_connect(struct socket *so, struct sockaddr *nam, + struct thread *td) +{ + ng_btsocket_l2cap_raw_pcb_t *pcb = so2l2cap_raw_pcb(so); + struct sockaddr_l2cap *sa = (struct sockaddr_l2cap *) nam; + ng_btsocket_l2cap_rtentry_t *rt = NULL; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + if (sa == NULL) + return (EINVAL); + if (sa->l2cap_family != AF_BLUETOOTH) + return (EAFNOSUPPORT); + if (sa->l2cap_len != sizeof(*sa)) + return (EINVAL); + if (bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(bdaddr_t)) == 0) + return (EINVAL); + if (bcmp(&sa->l2cap_bdaddr, &pcb->src, sizeof(pcb->src)) != 0) + return (EADDRNOTAVAIL); + + /* + * Find hook with specified source address + */ + + pcb->rt = NULL; + + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + + LIST_FOREACH(rt, &ng_btsocket_l2cap_raw_rt, next) { + if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) + continue; + + if (bcmp(&pcb->src, &rt->src, sizeof(rt->src)) == 0) + break; + } + + if (rt != NULL) { + pcb->rt = rt; + soisconnected(so); + } + + mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); + + return ((pcb->rt != NULL)? 0 : ENETDOWN); +} /* ng_btsocket_l2cap_raw_connect */ + +/* + * Find hook that matches source address + */ + +static ng_btsocket_l2cap_rtentry_p +ng_btsocket_l2cap_raw_find_src_route(bdaddr_p src) +{ + ng_btsocket_l2cap_rtentry_p rt = NULL; + + if (bcmp(src, NG_HCI_BDADDR_ANY, sizeof(*src)) != 0) { + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + + LIST_FOREACH(rt, &ng_btsocket_l2cap_raw_rt, next) + if (rt->hook != NULL && NG_HOOK_IS_VALID(rt->hook) && + bcmp(src, &rt->src, sizeof(*src)) == 0) + break; + + mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); + } + + return (rt); +} /* ng_btsocket_l2cap_raw_find_src_route */ + +/* + * Find first hook does does not match destination address + */ + +static ng_btsocket_l2cap_rtentry_p +ng_btsocket_l2cap_raw_find_dst_route(bdaddr_p dst) +{ + ng_btsocket_l2cap_rtentry_p rt = NULL; + + if (bcmp(dst, NG_HCI_BDADDR_ANY, sizeof(*dst)) != 0) { + mtx_lock(&ng_btsocket_l2cap_raw_rt_mtx); + + LIST_FOREACH(rt, &ng_btsocket_l2cap_raw_rt, next) + if (rt->hook != NULL && NG_HOOK_IS_VALID(rt->hook) && + bcmp(dst, &rt->src, sizeof(*dst)) != 0) + break; + + mtx_unlock(&ng_btsocket_l2cap_raw_rt_mtx); + } + + return (rt); +} /* ng_btsocket_l2cap_raw_find_dst_route */ + +/* + * Process ioctl's calls on socket + */ + +int +ng_btsocket_l2cap_raw_control(struct socket *so, u_long cmd, caddr_t data, + struct ifnet *ifp, struct thread *td) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + bdaddr_t *src = (bdaddr_t *) data; + ng_btsocket_l2cap_rtentry_p rt = pcb->rt; + struct ng_mesg *msg = NULL; + int error = 0; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + if (rt == NULL || rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) { + if (cmd == SIOC_L2CAP_L2CA_PING || + cmd == SIOC_L2CAP_L2CA_GET_INFO) + rt = ng_btsocket_l2cap_raw_find_dst_route(src + 1); + else + rt = ng_btsocket_l2cap_raw_find_src_route(src); + + if (rt == NULL) + return (EHOSTUNREACH); + } + + bcopy(&rt->src, src, sizeof(*src)); + + switch (cmd) { + case SIOC_L2CAP_NODE_GET_FLAGS: { + struct ng_btsocket_l2cap_raw_node_flags *p = + (struct ng_btsocket_l2cap_raw_node_flags *) data; + + ng_btsocket_l2cap_raw_get_token(&pcb->token); + pcb->msg = NULL; + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_GET_FLAGS, + 0, M_WAITOK); + if (msg == NULL) { + pcb->token = 0; + error = ENOMEM; + break; + } + msg->header.token = pcb->token; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + rt->hook, NULL); + if (error != 0) { + pcb->token = 0; + break; + } + + error = tsleep(&pcb->msg, PZERO|PCATCH, "l2ctl", + ng_btsocket_l2cap_raw_ioctl_timeout * hz); + if (error != 0) { + pcb->token = 0; + break; + } + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_NODE_GET_FLAGS) + p->flags = *((ng_l2cap_node_flags_ep *) + (pcb->msg->data)); + else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + pcb->token = 0; + } break; + + case SIOC_L2CAP_NODE_GET_DEBUG: { + struct ng_btsocket_l2cap_raw_node_debug *p = + (struct ng_btsocket_l2cap_raw_node_debug *) data; + + ng_btsocket_l2cap_raw_get_token(&pcb->token); + pcb->msg = NULL; + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_GET_DEBUG, + 0, M_WAITOK); + if (msg == NULL) { + pcb->token = 0; + error = ENOMEM; + break; + } + msg->header.token = pcb->token; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + rt->hook, NULL); + if (error != 0) { + pcb->token = 0; + break; + } + + error = tsleep(&pcb->msg, PZERO|PCATCH, "l2ctl", + ng_btsocket_l2cap_raw_ioctl_timeout * hz); + if (error != 0) { + pcb->token = 0; + break; + } + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_NODE_GET_DEBUG) + p->debug = *((ng_l2cap_node_debug_ep *) + (pcb->msg->data)); + else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + pcb->token = 0; + } break; + + case SIOC_L2CAP_NODE_SET_DEBUG: { + struct ng_btsocket_l2cap_raw_node_debug *p = + (struct ng_btsocket_l2cap_raw_node_debug *) data; + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_SET_DEBUG, + sizeof(ng_l2cap_node_debug_ep), M_WAITOK); + if (msg == NULL) { + error = ENOMEM; + break; + } + + *((ng_l2cap_node_debug_ep *)(msg->data)) = p->debug; + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, + msg, rt->hook, NULL); + } break; + + case SIOC_L2CAP_NODE_GET_CON_LIST: { + struct ng_btsocket_l2cap_raw_con_list *p = + (struct ng_btsocket_l2cap_raw_con_list *) data; + ng_l2cap_node_con_list_ep *p1 = NULL; + ng_l2cap_node_con_ep *p2 = NULL; + + if (p->num_connections == 0 || + p->num_connections > NG_L2CAP_MAX_CON_NUM || + p->connections == NULL) { + error = EINVAL; + break; + } + + ng_btsocket_l2cap_raw_get_token(&pcb->token); + pcb->msg = NULL; + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_GET_CON_LIST, + 0, M_WAITOK); + if (msg == NULL) { + pcb->token = 0; + error = ENOMEM; + break; + } + msg->header.token = pcb->token; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + rt->hook, NULL); + if (error != 0) { + pcb->token = 0; + break; + } + + error = tsleep(&pcb->msg, PZERO|PCATCH, "l2ctl", + ng_btsocket_l2cap_raw_ioctl_timeout * hz); + if (error != 0) { + pcb->token = 0; + break; + } + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_NODE_GET_CON_LIST) { + /* Return data back to user space */ + p1 = (ng_l2cap_node_con_list_ep *)(pcb->msg->data); + p2 = (ng_l2cap_node_con_ep *)(p1 + 1); + + p->num_connections = min(p->num_connections, + p1->num_connections); + if (p->num_connections > 0) + error = copyout((caddr_t) p2, + (caddr_t) p->connections, + p->num_connections * sizeof(*p2)); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + pcb->token = 0; + } break; + + case SIOC_L2CAP_NODE_GET_CHAN_LIST: { + struct ng_btsocket_l2cap_raw_chan_list *p = + (struct ng_btsocket_l2cap_raw_chan_list *) data; + ng_l2cap_node_chan_list_ep *p1 = NULL; + ng_l2cap_node_chan_ep *p2 = NULL; + + if (p->num_channels == 0 || + p->num_channels > NG_L2CAP_MAX_CHAN_NUM || + p->channels == NULL) { + error = EINVAL; + break; + } + + ng_btsocket_l2cap_raw_get_token(&pcb->token); + pcb->msg = NULL; + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, + NGM_L2CAP_NODE_GET_CHAN_LIST, 0, M_WAITOK); + if (msg == NULL) { + pcb->token = 0; + error = ENOMEM; + break; + } + msg->header.token = pcb->token; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + rt->hook, NULL); + if (error != 0) { + pcb->token = 0; + break; + } + + error = tsleep(&pcb->msg, PZERO|PCATCH, "l2ctl", + ng_btsocket_l2cap_raw_ioctl_timeout * hz); + if (error != 0) { + pcb->token = 0; + break; + } + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_NODE_GET_CHAN_LIST) { + /* Return data back to user space */ + p1 = (ng_l2cap_node_chan_list_ep *)(pcb->msg->data); + p2 = (ng_l2cap_node_chan_ep *)(p1 + 1); + + p->num_channels = min(p->num_channels, + p1->num_channels); + if (p->num_channels > 0) + error = copyout((caddr_t) p2, + (caddr_t) p->channels, + p->num_channels * sizeof(*p2)); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + pcb->token = 0; + } break; + + case SIOC_L2CAP_L2CA_PING: { + struct ng_btsocket_l2cap_raw_ping *p = + (struct ng_btsocket_l2cap_raw_ping *) data; + ng_l2cap_l2ca_ping_ip *ip = NULL; + ng_l2cap_l2ca_ping_op *op = NULL; + + if ((p->echo_size != 0 && p->echo_data == NULL) || + p->echo_size > NG_L2CAP_MAX_ECHO_SIZE) { + error = EINVAL; + break; + } + + /* Loop back local ping */ + if (bcmp(&p->echo_dst, &rt->src, sizeof(rt->src)) == 0) { + p->result = 0; + break; + } + + ng_btsocket_l2cap_raw_get_token(&pcb->token); + pcb->msg = NULL; + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, + NGM_L2CAP_L2CA_PING, sizeof(*ip) + p->echo_size, + M_WAITOK); + if (msg == NULL) { + pcb->token = 0; + error = ENOMEM; + break; + } + msg->header.token = pcb->token; + + ip = (ng_l2cap_l2ca_ping_ip *)(msg->data); + bcopy(&p->echo_dst, &ip->bdaddr, sizeof(ip->bdaddr)); + ip->echo_size = p->echo_size; + + if (ip->echo_size > 0) { + error = copyin(p->echo_data, ip + 1, p->echo_size); + if (error != 0) { + NG_FREE_MSG(msg); + pcb->token = 0; + break; + } + } + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + rt->hook, NULL); + if (error != 0) { + pcb->token = 0; + break; + } + + error = tsleep(&pcb->msg, PZERO|PCATCH, "l2ctl", + bluetooth_l2cap_rtx_timeout()); + if (error != 0) { + pcb->token = 0; + break; + } + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_L2CA_PING) { + /* Return data back to the user space */ + op = (ng_l2cap_l2ca_ping_op *)(pcb->msg->data); + p->result = op->result; + p->echo_size = min(p->echo_size, op->echo_size); + + if (p->echo_size > 0) + error = copyout(op + 1, p->echo_data, + p->echo_size); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + pcb->token = 0; + } break; + + case SIOC_L2CAP_L2CA_GET_INFO: { + struct ng_btsocket_l2cap_raw_get_info *p = + (struct ng_btsocket_l2cap_raw_get_info *) data; + ng_l2cap_l2ca_get_info_ip *ip = NULL; + ng_l2cap_l2ca_get_info_op *op = NULL; + + if ((p->info_size != 0 && p->info_data == NULL) || + bcmp(&p->info_dst, &rt->src, sizeof(rt->src)) == 0) { + error = EINVAL; + break; + } + + ng_btsocket_l2cap_raw_get_token(&pcb->token); + pcb->msg = NULL; + + NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, + NGM_L2CAP_L2CA_GET_INFO, sizeof(*ip) + p->info_size, + M_WAITOK); + if (msg == NULL) { + pcb->token = 0; + error = ENOMEM; + break; + } + msg->header.token = pcb->token; + + ip = (ng_l2cap_l2ca_get_info_ip *)(msg->data); + bcopy(&p->info_dst, &ip->bdaddr, sizeof(ip->bdaddr)); + ip->info_type = p->info_type; + + NG_SEND_MSG_HOOK(error, ng_btsocket_l2cap_raw_node, msg, + rt->hook, NULL); + if (error != 0) { + pcb->token = 0; + break; + } + + error = tsleep(&pcb->msg, PZERO|PCATCH, "l2ctl", + bluetooth_l2cap_rtx_timeout()); + if (error != 0) { + pcb->token = 0; + break; + } + + if (pcb->msg != NULL && + pcb->msg->header.cmd == NGM_L2CAP_L2CA_GET_INFO) { + /* Return data back to the user space */ + op = (ng_l2cap_l2ca_get_info_op *)(pcb->msg->data); + p->result = op->result; + p->info_size = min(p->info_size, op->info_size); + + if (p->info_size > 0) + error = copyout(op + 1, p->info_data, + p->info_size); + } else + error = EINVAL; + + NG_FREE_MSG(pcb->msg); /* checks for != NULL */ + pcb->token = 0; + } break; + + default: + error = EINVAL; + break; + } + + return (error); +} /* ng_btsocket_l2cap_raw_control */ + +/* + * Detach and destroy socket + */ + +int +ng_btsocket_l2cap_raw_detach(struct socket *so) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + so->so_pcb = NULL; + sotryfree(so); + + mtx_lock(&ng_btsocket_l2cap_raw_sockets_mtx); + LIST_REMOVE(pcb, next); + mtx_unlock(&ng_btsocket_l2cap_raw_sockets_mtx); + + bzero(pcb, sizeof(*pcb)); + FREE(pcb, M_NETGRAPH_BTSOCKET_L2CAP_RAW); + + return (0); +} /* ng_btsocket_l2cap_raw_detach */ + +/* + * Disconnect socket + */ + +int +ng_btsocket_l2cap_raw_disconnect(struct socket *so) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + pcb->rt = NULL; + soisdisconnected(so); + + return (0); +} /* ng_btsocket_l2cap_raw_disconnect */ + +/* + * Get peer address + */ + +int +ng_btsocket_l2cap_raw_peeraddr(struct socket *so, struct sockaddr **nam) +{ + return (EOPNOTSUPP); +} /* ng_btsocket_l2cap_raw_peeraddr */ + +/* + * Send data to socket + */ + +int +ng_btsocket_l2cap_raw_send(struct socket *so, int flags, struct mbuf *m, + struct sockaddr *nam, struct mbuf *control, struct thread *td) +{ + NG_FREE_M(m); /* Checks for m != NULL */ + NG_FREE_M(control); + + return (EOPNOTSUPP); +} /* ng_btsocket_l2cap_raw_send */ + +/* + * Get socket address + */ + +int +ng_btsocket_l2cap_raw_sockaddr(struct socket *so, struct sockaddr **nam) +{ + ng_btsocket_l2cap_raw_pcb_p pcb = so2l2cap_raw_pcb(so); + struct sockaddr_l2cap sa; + + if (pcb == NULL) + return (EINVAL); + if (ng_btsocket_l2cap_raw_node == NULL) + return (EINVAL); + + bcopy(&pcb->src, &sa.l2cap_bdaddr, sizeof(sa.l2cap_bdaddr)); + sa.l2cap_psm = 0; + sa.l2cap_len = sizeof(sa); + sa.l2cap_family = AF_BLUETOOTH; + + *nam = dup_sockaddr((struct sockaddr *) &sa, 0); + + return ((*nam == NULL)? ENOMEM : 0); +} /* ng_btsocket_l2cap_raw_sockaddr */ + +/* + * Get next token + */ + +static void +ng_btsocket_l2cap_raw_get_token(u_int32_t *token) +{ + mtx_lock(&ng_btsocket_l2cap_raw_token_mtx); + + if (++ ng_btsocket_l2cap_raw_token == 0) + ng_btsocket_l2cap_raw_token = 1; + + *token = ng_btsocket_l2cap_raw_token; + + mtx_unlock(&ng_btsocket_l2cap_raw_token_mtx); +} /* ng_btsocket_l2cap_raw_get_token */ + |