USB2: ng_ubt2 patch

Maksim Yevmenkin maksim.yevmenkin at gmail.com
Wed Jan 14 17:05:54 PST 2009


dear bluetooth users,

please find attached patch for ng_ubt2 (well it is almost a complete rewrite).

this is an early preview and likely to have bugs. i briefly kicked the
tires to make sure it compiles and did some very quick testing, i.e.
run few hci commands, sdp transfers etc.

this is my first attempt to write something under usb2, so any
review/comments/suggestions/etc. are greatly appreciated.

thanks,
max
-------------- next part --------------
Index: ng_ubt2.c
===================================================================
--- ng_ubt2.c	(revision 186800)
+++ ng_ubt2.c	(working copy)
@@ -3,7 +3,7 @@
  */
 
 /*-
- * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin at yahoo.com>
+ * Copyright (c) 2001-2009 Maksim Yevmenkin <m_evmenkin at yahoo.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -31,6 +31,69 @@
  * $FreeBSD$
  */
 
+/*
+ * NOTE: ng_ubt2 driver has a split personality. On one side it is
+ * a USB2 device driver and on the other it is a Netgraph node. This
+ * driver will *NOT* create traditional /dev/ enties, only Netgraph 
+ * node.
+ *
+ * NOTE ON LOCKS USED: ng_ubt2 drives uses 3 locks (mutexes)
+ *
+ * 1) sc_if_0_mtx - lock for device's interface #0. This lock is used
+ *    by USB2 for any USB request going over device's interface #0, i.e.
+ *    interrupt, control and bulk transfers.
+ * 
+ * 2) sc_if_1_mtx - lock for device's interface #1. This lock is used
+ *    by USB2 for any USB request going over device's interface #1, i.e
+ *    isoc. transfers.
+ * 
+ * 3) sc_mbufq_mtx - lock for mbufq and task flags. This lock is used
+ *    to protect device's outgoing mbuf queues and task flags. This lock
+ *    *SHOULD NOT* be grabbed for long time. In fact, think of it as a 
+ *    spin lock.
+ *
+ * NOTE ON LOCKING STRATEGY: ng_ubt2 driver operates in 3 different contexts.
+ *
+ * 1) USB context. This is where all the USB related stuff happens. All
+ *    callbacks run in this context. All callbacks are called (by USB2) with
+ *    appropriate interface lock held. It is (generally) allowed to grab
+ *    any additional locks.
+ *
+ * 2) Netgraph context. This is where all the Netgraph related happens. Since
+ *    we mark node as WRITER, the Netgraph node will be "locked" (from Netgraph
+ *    point of view). Any variable that is only modified from the Netgraph
+ *    context does not require any additonal locking. It is generally *NOT*
+ *    allowed to grab *ANY* additional lock. Whatever you do, *DO NOT* not
+ *    grab any long-sleep lock in the Netgraph context. In fact, the only
+ *    lock that is allowed in Netgraph context is the sc_mbufq_mtx lock.
+ *
+ * 3) Taskqueue context. This is where ubt_task runs. Since we are NOT allowed
+ *    to grab any locks in the Netgraph context, and, USB2 requires us to
+ *    grab interface lock before doing things with transfers, we need to
+ *    transition from the Netgraph context to the Taskqueue context before
+ *    we can call into USB2 subsystem.
+ *
+ * So, to put everything together, the rules are as follows.
+ *	It is OK to call from the USB context or the Taskqueue context into
+ * the Netgraph context (i.e. call NG_SEND_xxx functions). In other words
+ * it is allowed to call into the Netgraph context with locks held.
+ *	Is it *NOT* OK to call from the Netgraph context into the USB context,
+ * because USB2 requires us to grab interface locks and we can not do that.
+ * To avoid this, we set task flags to indicate which actions we want to
+ * perform and schedule ubt_task which would run in Taskqueue context.
+ *	Is is OK to call from the Taskqueue context into the USB context,
+ * and, ubt_task does just that (i.e. grabs appropriate interface locks
+ * before calling into USB2).
+ *	Access to outgoing queues and task flags is controlled by sc_mbufq_mtx
+ * lock. It is unavoidable evil. Again, sc_mbufq_mtx should really be a spin
+ * lock.
+ *	All USB callbacks accept Netgraph node pointer as private data. To
+ * ensure that Netgraph node point remains valid for the duration of the
+ * transfer, we grab a referrence to the node. In other words, if transfer is
+ * pending, then we should have a referrence on the node. NG_NODE_[NOT_]VALID
+ * macro is used to check if node is still present.
+ */
+
 #include <dev/usb2/include/usb2_devid.h>
 #include <dev/usb2/include/usb2_standard.h>
 #include <dev/usb2/include/usb2_mfunc.h>
@@ -46,6 +109,7 @@
 #include <dev/usb2/core/usb2_busdma.h>
 
 #include <sys/mbuf.h>
+#include <sys/taskqueue.h>
 
 #include <netgraph/ng_message.h>
 #include <netgraph/netgraph.h>
@@ -57,71 +121,56 @@
 #include <dev/usb2/bluetooth/usb2_bluetooth.h>
 #include <dev/usb2/bluetooth/ng_ubt2_var.h>
 
-/*
- * USB methods
- */
+static int		ubt_modevent(module_t, int, void *);
+static device_probe_t	ubt_probe;
+static device_attach_t	ubt_attach;
+static device_detach_t	ubt_detach;
 
-static device_probe_t ubt_probe;
-static device_attach_t ubt_attach;
-static device_detach_t ubt_detach;
+static int		ubt_task_schedule(ubt_softc_p, uint32_t);
+static task_fn_t	ubt_task;
 
-static devclass_t ubt_devclass;
+/* Netgraph methods */
+static ng_constructor_t	ng_ubt_constructor;
+static ng_shutdown_t	ng_ubt_shutdown;
+static ng_newhook_t	ng_ubt_newhook;
+static ng_connect_t	ng_ubt_connect;
+static ng_disconnect_t	ng_ubt_disconnect;
+static ng_rcvmsg_t	ng_ubt_rcvmsg;
+static ng_rcvdata_t	ng_ubt_rcvdata;
 
-static device_method_t ubt_methods[] = {
-	DEVMETHOD(device_probe, ubt_probe),
-	DEVMETHOD(device_attach, ubt_attach),
-	DEVMETHOD(device_detach, ubt_detach),
-	{0, 0}
-};
-
-static driver_t ubt_driver = {
-	.name = "ubt",
-	.methods = ubt_methods,
-	.size = sizeof(struct ubt_softc),
-};
-
-/*
- * Netgraph methods
- */
-
-static ng_constructor_t ng_ubt_constructor;
-static ng_shutdown_t ng_ubt_shutdown;
-static ng_newhook_t ng_ubt_newhook;
-static ng_connect_t ng_ubt_connect;
-static ng_disconnect_t ng_ubt_disconnect;
-static ng_rcvmsg_t ng_ubt_rcvmsg;
-static ng_rcvdata_t ng_ubt_rcvdata;
-
 /* Queue length */
-static const struct ng_parse_struct_field ng_ubt_node_qlen_type_fields[] =
+static const struct ng_parse_struct_field	ng_ubt_node_qlen_type_fields[] =
 {
-	{"queue", &ng_parse_int32_type,},
-	{"qlen", &ng_parse_int32_type,},
-	{NULL,}
+	{ "queue", &ng_parse_int32_type, },
+	{ "qlen",  &ng_parse_int32_type, },
+	{ NULL, }
 };
-static const struct ng_parse_type ng_ubt_node_qlen_type = {
+static const struct ng_parse_type		ng_ubt_node_qlen_type =
+{
 	&ng_parse_struct_type,
 	&ng_ubt_node_qlen_type_fields
 };
 
 /* Stat info */
-static const struct ng_parse_struct_field ng_ubt_node_stat_type_fields[] =
+static const struct ng_parse_struct_field	ng_ubt_node_stat_type_fields[] =
 {
-	{"pckts_recv", &ng_parse_uint32_type,},
-	{"bytes_recv", &ng_parse_uint32_type,},
-	{"pckts_sent", &ng_parse_uint32_type,},
-	{"bytes_sent", &ng_parse_uint32_type,},
-	{"oerrors", &ng_parse_uint32_type,},
-	{"ierrors", &ng_parse_uint32_type,},
-	{NULL,}
+	{ "pckts_recv", &ng_parse_uint32_type, },
+	{ "bytes_recv", &ng_parse_uint32_type, },
+	{ "pckts_sent", &ng_parse_uint32_type, },
+	{ "bytes_sent", &ng_parse_uint32_type, },
+	{ "oerrors",    &ng_parse_uint32_type, },
+	{ "ierrors",    &ng_parse_uint32_type, },
+	{ NULL, }
 };
-static const struct ng_parse_type ng_ubt_node_stat_type = {
+static const struct ng_parse_type		ng_ubt_node_stat_type =
+{
 	&ng_parse_struct_type,
 	&ng_ubt_node_stat_type_fields
 };
 
 /* Netgraph node command list */
-static const struct ng_cmdlist ng_ubt_cmdlist[] = {
+static const struct ng_cmdlist			ng_ubt_cmdlist[] =
+{
 	{
 		NGM_UBT_COOKIE,
 		NGM_UBT_NODE_SET_DEBUG,
@@ -164,316 +213,280 @@
 		NULL,
 		NULL
 	},
-	{0,}
+	{ 0, }
 };
 
 /* Netgraph node type */
-static struct ng_type typestruct = {
-	.version = NG_ABI_VERSION,
-	.name = NG_UBT_NODE_TYPE,
-	.constructor = ng_ubt_constructor,
-	.rcvmsg = ng_ubt_rcvmsg,
-	.shutdown = ng_ubt_shutdown,
-	.newhook = ng_ubt_newhook,
-	.connect = ng_ubt_connect,
-	.rcvdata = ng_ubt_rcvdata,
-	.disconnect = ng_ubt_disconnect,
-	.cmdlist = ng_ubt_cmdlist
+static struct ng_type	typestruct =
+{
+	.version = 	NG_ABI_VERSION,
+	.name =		NG_UBT_NODE_TYPE,
+	.constructor =	ng_ubt_constructor,
+	.rcvmsg =	ng_ubt_rcvmsg,
+	.shutdown =	ng_ubt_shutdown,
+	.newhook =	ng_ubt_newhook,
+	.connect =	ng_ubt_connect,
+	.rcvdata =	ng_ubt_rcvdata,
+	.disconnect =	ng_ubt_disconnect,
+	.cmdlist =	ng_ubt_cmdlist
 };
 
+/****************************************************************************
+ ****************************************************************************
+ **                              USB specific
+ ****************************************************************************
+ ****************************************************************************/
+
 /* USB methods */
+static usb2_callback_t	ubt_ctrl_write_callback;
+static usb2_callback_t	ubt_intr_read_callback;
+static usb2_callback_t	ubt_intr_read_clear_stall_callback;
+static usb2_callback_t	ubt_bulk_read_callback;
+static usb2_callback_t	ubt_bulk_read_clear_stall_callback;
+static usb2_callback_t	ubt_bulk_write_callback;
+static usb2_callback_t	ubt_bulk_write_clear_stall_callback;
+static usb2_callback_t	ubt_isoc_read_callback;
+static usb2_callback_t	ubt_isoc_write_callback;
 
-static usb2_callback_t ubt_ctrl_write_callback;
-static usb2_callback_t ubt_intr_read_callback;
-static usb2_callback_t ubt_intr_read_clear_stall_callback;
-static usb2_callback_t ubt_bulk_read_callback;
-static usb2_callback_t ubt_bulk_read_clear_stall_callback;
-static usb2_callback_t ubt_bulk_write_callback;
-static usb2_callback_t ubt_bulk_write_clear_stall_callback;
-static usb2_callback_t ubt_isoc_read_callback;
-static usb2_callback_t ubt_isoc_write_callback;
+static int	ubt_isoc_read_one_frame(struct usb2_xfer *, uint32_t);
 
-static int	ubt_modevent(module_t, int, void *);
-static void	ubt_intr_read_complete(node_p, hook_p, void *, int);
-static void	ubt_bulk_read_complete(node_p, hook_p, void *, int);
-static void	ubt_isoc_read_complete(node_p, hook_p, void *, int);
+/*
+ * USB config
+ * 
+ * The following desribes usb transfers that could be submitted
+ * on USB device's interface 0
+ *
+ * Interface 0 on the USB device must present the following endpoints
+ *	1) Interrupt endpoint to receive HCI events
+ *	2) Bulk IN endpoint to receive ACL data
+ *	3) Bulk OUT endpoint to send ACL data
+ */
 
-/* USB config */
-static const struct usb2_config ubt_config_if_0[UBT_IF_0_N_TRANSFER] = {
-
+static const struct usb2_config		ubt_config_if_0[UBT_IF_0_N_TRANSFER] =
+{
+	/* Outgoing bulk transfer - ACL packets */
 	[0] = {
-		.type = UE_BULK,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_OUT,
-		.mh.bufsize = UBT_BULK_WRITE_BUFFER_SIZE,
-		.mh.flags = {.pipe_bof = 1,},
-		.mh.callback = &ubt_bulk_write_callback,
+		.type =		UE_BULK,
+		.endpoint =	UE_ADDR_ANY,
+		.direction =	UE_DIR_OUT,
+		.mh.bufsize =	UBT_BULK_WRITE_BUFFER_SIZE,
+		.mh.flags =	{ .pipe_bof = 1, },
+		.mh.callback =	&ubt_bulk_write_callback,
 	},
-
+	/* Incoming bulk transfer - ACL packets */
 	[1] = {
-		.type = UE_BULK,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_IN,
-		.mh.bufsize = UBT_BULK_READ_BUFFER_SIZE,
-		.mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
-		.mh.callback = &ubt_bulk_read_callback,
+		.type =		UE_BULK,
+		.endpoint =	UE_ADDR_ANY,
+		.direction =	UE_DIR_IN,
+		.mh.bufsize =	UBT_BULK_READ_BUFFER_SIZE,
+		.mh.flags =	{ .pipe_bof = 1, .short_xfer_ok = 1, },
+		.mh.callback =	&ubt_bulk_read_callback,
 	},
-
+	/* Incoming interrupt transfer - HCI events */
 	[2] = {
-		.type = UE_INTERRUPT,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_IN,
-		.mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
-		.mh.bufsize = 0x110,	/* bytes */
-		.mh.callback = &ubt_intr_read_callback,
+		.type =		UE_INTERRUPT,
+		.endpoint =	UE_ADDR_ANY,
+		.direction =	UE_DIR_IN,
+		.mh.flags =	{ .pipe_bof = 1, .short_xfer_ok = 1, },
+		.mh.bufsize =	UBT_INTR_BUFFER_SIZE,
+		.mh.callback =	&ubt_intr_read_callback,
 	},
-
+	/* Outgoing control transfer - HCI commands */
 	[3] = {
-		.type = UE_CONTROL,
-		.endpoint = 0x00,	/* Control pipe */
-		.direction = UE_DIR_ANY,
-		.mh.bufsize = (sizeof(struct usb2_device_request) + UBT_CTRL_BUFFER_SIZE),
-		.mh.callback = &ubt_ctrl_write_callback,
-		.mh.timeout = 5000,	/* 5 seconds */
+		.type =		UE_CONTROL,
+		.endpoint =	0x00,	/* control pipe */
+		.direction =	UE_DIR_ANY,
+		.mh.bufsize =	(sizeof(struct usb2_device_request) +
+				 UBT_CTRL_BUFFER_SIZE), /* bytes */
+		.mh.callback =	&ubt_ctrl_write_callback,
+		.mh.timeout =	5000,	/* 5 seconds */
 	},
-
+	/* Outgoing control transfer to clear stall on outgoing bulk transfer */
 	[4] = {
-		.type = UE_CONTROL,
-		.endpoint = 0x00,	/* Control pipe */
-		.direction = UE_DIR_ANY,
-		.mh.bufsize = sizeof(struct usb2_device_request),
-		.mh.callback = &ubt_bulk_write_clear_stall_callback,
-		.mh.timeout = 1000,	/* 1 second */
-		.mh.interval = 50,	/* 50ms */
+		.type =		UE_CONTROL,
+		.endpoint =	0x00,	/* control pipe */
+		.direction =	UE_DIR_ANY,
+		.mh.bufsize =	sizeof(struct usb2_device_request),
+		.mh.callback =	&ubt_bulk_write_clear_stall_callback,
+		.mh.timeout =	1000,	/* 1 second */
+		.mh.interval =	50,	/* 50ms */
 	},
-
+	/* Outgoing control transfer to clear stall on incoming bulk transfer */
 	[5] = {
-		.type = UE_CONTROL,
-		.endpoint = 0x00,	/* Control pipe */
-		.direction = UE_DIR_ANY,
-		.mh.bufsize = sizeof(struct usb2_device_request),
-		.mh.callback = &ubt_bulk_read_clear_stall_callback,
-		.mh.timeout = 1000,	/* 1 second */
-		.mh.interval = 50,	/* 50ms */
+		.type =		UE_CONTROL,
+		.endpoint =	0x00,	/* control pipe */
+		.direction =	UE_DIR_ANY,
+		.mh.bufsize =	sizeof(struct usb2_device_request),
+		.mh.callback =	&ubt_bulk_read_clear_stall_callback,
+		.mh.timeout =	1000,	/* 1 second */
+		.mh.interval =	50,	/* 50ms */
 	},
-
+	/*
+	 * Outgoing control transfer to clear stall on incoming
+	 * interrupt transfer
+	 */
 	[6] = {
-		.type = UE_CONTROL,
-		.endpoint = 0x00,	/* Control pipe */
-		.direction = UE_DIR_ANY,
-		.mh.bufsize = sizeof(struct usb2_device_request),
-		.mh.callback = &ubt_intr_read_clear_stall_callback,
-		.mh.timeout = 1000,	/* 1 second */
-		.mh.interval = 50,	/* 50ms */
+		.type =		UE_CONTROL,
+		.endpoint =	0x00,	/* control pipe */
+		.direction =	UE_DIR_ANY,
+		.mh.bufsize =	sizeof(struct usb2_device_request),
+		.mh.callback =	&ubt_intr_read_clear_stall_callback,
+		.mh.timeout =	1000,	/* 1 second */
+		.mh.interval =	50,	/* 50ms */
 	},
 };
 
-/* USB config */
-static const struct usb2_config
-	ubt_config_if_1_full_speed[UBT_IF_1_N_TRANSFER] = {
+/*
+ * USB config
+ *
+ * The following desribes usb transfers that could be submitted
+ * on USB device's interface 1
+ *
+ * Interface 1 on the UBS device must present must 2 endpoints
+ *	1) Isochronous IN endpoint to receive SCO data
+ *	2) Isochronous OUT endpoint to send SCO data
+ *
+ * Interface 1 (with isochronous endpoints) has several alternate
+ * configurations with different packet size.
+ *
+ * Device is expected to be a high speed device.
+ */
 
+static const struct usb2_config		ubt_config_if_1[UBT_IF_1_N_TRANSFER] =
+{
+	/* Incoming isochronous transfer #1 - SCO packets */
 	[0] = {
-		.type = UE_ISOCHRONOUS,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_IN,
-		.mh.bufsize = 0,	/* use "wMaxPacketSize * frames" */
-		.mh.frames = UBT_ISOC_NFRAMES,
-		.mh.flags = {.short_xfer_ok = 1,},
-		.mh.callback = &ubt_isoc_read_callback,
+		.type =		UE_ISOCHRONOUS,
+		.endpoint =	UE_ADDR_ANY,
+		.direction =	UE_DIR_IN,
+		.mh.bufsize =	0,	/* use "wMaxPacketSize * frames" */
+		.mh.frames =	UBT_ISOC_NFRAMES,
+		.mh.flags =	{ .short_xfer_ok = 1, },
+		.mh.callback =	&ubt_isoc_read_callback,
 	},
-
+	/* Incoming isochronous transfer #2 - SCO packets */
 	[1] = {
-		.type = UE_ISOCHRONOUS,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_IN,
-		.mh.bufsize = 0,	/* use "wMaxPacketSize * frames" */
-		.mh.frames = UBT_ISOC_NFRAMES,
-		.mh.flags = {.short_xfer_ok = 1,},
-		.mh.callback = &ubt_isoc_read_callback,
+		.type =		UE_ISOCHRONOUS,
+		.endpoint =	UE_ADDR_ANY,
+		.direction =	UE_DIR_IN,
+		.mh.bufsize =	0,	/* use "wMaxPacketSize * frames" */
+		.mh.frames =	UBT_ISOC_NFRAMES,
+		.mh.flags =	{ .short_xfer_ok = 1, },
+		.mh.callback =	&ubt_isoc_read_callback,
 	},
-
+	/* Outgoing isochronous transfer #1 - SCO packets */
 	[2] = {
-		.type = UE_ISOCHRONOUS,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_OUT,
-		.mh.bufsize = 0,	/* use "wMaxPacketSize * frames" */
-		.mh.frames = UBT_ISOC_NFRAMES,
-		.mh.flags = {.short_xfer_ok = 1,},
-		.mh.callback = &ubt_isoc_write_callback,
+		.type =		UE_ISOCHRONOUS,
+		.endpoint =	UE_ADDR_ANY,
+		.direction =	UE_DIR_OUT,
+		.mh.bufsize =	0,	/* use "wMaxPacketSize * frames" */
+		.mh.frames =	UBT_ISOC_NFRAMES,
+		.mh.flags =	{ .short_xfer_ok = 1, },
+		.mh.callback =	&ubt_isoc_write_callback,
 	},
-
+	/* Outgoing isochronous transfer #2 - SCO packets */
 	[3] = {
-		.type = UE_ISOCHRONOUS,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_OUT,
-		.mh.bufsize = 0,	/* use "wMaxPacketSize * frames" */
-		.mh.frames = UBT_ISOC_NFRAMES,
-		.mh.flags = {.short_xfer_ok = 1,},
-		.mh.callback = &ubt_isoc_write_callback,
+		.type =		UE_ISOCHRONOUS,
+		.endpoint =	UE_ADDR_ANY,
+		.direction =	UE_DIR_OUT,
+		.mh.bufsize =	0,	/* use "wMaxPacketSize * frames" */
+		.mh.frames =	UBT_ISOC_NFRAMES,
+		.mh.flags =	{ .short_xfer_ok = 1, },
+		.mh.callback =	&ubt_isoc_write_callback,
 	},
 };
 
-/* USB config */
-static const struct usb2_config
-	ubt_config_if_1_high_speed[UBT_IF_1_N_TRANSFER] = {
-
-	[0] = {
-		.type = UE_ISOCHRONOUS,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_IN,
-		.mh.bufsize = 0,	/* use "wMaxPacketSize * frames" */
-		.mh.frames = UBT_ISOC_NFRAMES * 8,
-		.mh.flags = {.short_xfer_ok = 1,},
-		.mh.callback = &ubt_isoc_read_callback,
-	},
-
-	[1] = {
-		.type = UE_ISOCHRONOUS,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_IN,
-		.mh.bufsize = 0,	/* use "wMaxPacketSize * frames" */
-		.mh.frames = UBT_ISOC_NFRAMES * 8,
-		.mh.flags = {.short_xfer_ok = 1,},
-		.mh.callback = &ubt_isoc_read_callback,
-	},
-
-	[2] = {
-		.type = UE_ISOCHRONOUS,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_OUT,
-		.mh.bufsize = 0,	/* use "wMaxPacketSize * frames" */
-		.mh.frames = UBT_ISOC_NFRAMES * 8,
-		.mh.flags = {.short_xfer_ok = 1,},
-		.mh.callback = &ubt_isoc_write_callback,
-	},
-
-	[3] = {
-		.type = UE_ISOCHRONOUS,
-		.endpoint = UE_ADDR_ANY,
-		.direction = UE_DIR_OUT,
-		.mh.bufsize = 0,	/* use "wMaxPacketSize * frames" */
-		.mh.frames = UBT_ISOC_NFRAMES * 8,
-		.mh.flags = {.short_xfer_ok = 1,},
-		.mh.callback = &ubt_isoc_write_callback,
-	},
-};
-
 /*
- * Module
- */
-
-DRIVER_MODULE(ng_ubt, ushub, ubt_driver, ubt_devclass, ubt_modevent, 0);
-MODULE_VERSION(ng_ubt, NG_BLUETOOTH_VERSION);
-MODULE_DEPEND(ng_ubt, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION);
-MODULE_DEPEND(ng_ubt, ng_hci, NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION);
-MODULE_DEPEND(ng_ubt, usb2_bluetooth, 1, 1, 1);
-MODULE_DEPEND(ng_ubt, usb2_core, 1, 1, 1);
-
-/****************************************************************************
- ****************************************************************************
- **                              USB specific
- ****************************************************************************
- ****************************************************************************/
-
-/*
- * Load/Unload the driver module
- */
-
-static int
-ubt_modevent(module_t mod, int event, void *data)
-{
-	int error;
-
-	switch (event) {
-	case MOD_LOAD:
-		error = ng_newtype(&typestruct);
-		if (error != 0) {
-			printf("%s: Could not register "
-			    "Netgraph node type, error=%d\n",
-			    NG_UBT_NODE_TYPE, error);
-		}
-		break;
-
-	case MOD_UNLOAD:
-		error = ng_rmtype(&typestruct);
-		break;
-
-	default:
-		error = EOPNOTSUPP;
-		break;
-	}
-	return (error);
-}					/* ubt_modevent */
-
-/*
  * If for some reason device should not be attached then put
  * VendorID/ProductID pair into the list below. The format is
  * as follows:
  *
- *	{ VENDOR_ID, PRODUCT_ID },
+ *	{ USB_VPI(VENDOR_ID, PRODUCT_ID, 0) },
  *
  * where VENDOR_ID and PRODUCT_ID are hex numbers.
  */
 static const struct usb2_device_id ubt_ignore_devs[] = {
 	/* AVM USB Bluetooth-Adapter BlueFritz! v1.0 */
-	{USB_VPI(USB_VENDOR_AVM, 0x2200, 0)},
+	{ USB_VPI(USB_VENDOR_AVM, 0x2200, 0) },
 };
 
 /* List of supported bluetooth devices */
 static const struct usb2_device_id ubt_devs[] = {
-	/* Generic Bluetooth class devices. */
-	{USB_IFACE_CLASS(UDCLASS_WIRELESS),
-		USB_IFACE_SUBCLASS(UDSUBCLASS_RF),
-	USB_IFACE_PROTOCOL(UDPROTO_BLUETOOTH)},
+	/* Generic Bluetooth class devices */
+	{ USB_IFACE_CLASS(UDCLASS_WIRELESS),
+	  USB_IFACE_SUBCLASS(UDSUBCLASS_RF),
+	  USB_IFACE_PROTOCOL(UDPROTO_BLUETOOTH) },
 
 	/* AVM USB Bluetooth-Adapter BlueFritz! v2.0 */
-	{USB_VPI(USB_VENDOR_AVM, 0x3800, 0)},
+	{ USB_VPI(USB_VENDOR_AVM, 0x3800, 0) },
 };
 
 /*
- * Probe for a USB Bluetooth device
+ * Probe for a USB Bluetooth device.
+ * USB context.
  */
 
 static int
 ubt_probe(device_t dev)
 {
-	struct usb2_attach_arg *uaa = device_get_ivars(dev);
+	struct usb2_attach_arg	*uaa = device_get_ivars(dev);
 
-	if (uaa->usb2_mode != USB_MODE_HOST) {
+	if (uaa->usb2_mode != USB_MODE_HOST)
 		return (ENXIO);
-	}
-	if (uaa->info.bIfaceIndex != 0) {
+
+	if (uaa->info.bIfaceIndex != 0)
 		return (ENXIO);
-	}
+
 	if (usb2_lookup_id_by_uaa(ubt_ignore_devs,
-	    sizeof(ubt_ignore_devs), uaa) == 0) {
+			sizeof(ubt_ignore_devs), uaa) == 0)
 		return (ENXIO);
-	}
+
 	return (usb2_lookup_id_by_uaa(ubt_devs, sizeof(ubt_devs), uaa));
-}
+} /* ubt_probe */
 
 /*
- * Attach the device
+ * Attach the device.
+ * USB context.
  */
 
 static int
 ubt_attach(device_t dev)
 {
-	struct usb2_attach_arg *uaa = device_get_ivars(dev);
-	struct ubt_softc *sc = device_get_softc(dev);
-	const struct usb2_config *isoc_setup;
-	struct usb2_endpoint_descriptor *ed;
-	uint16_t wMaxPacketSize;
-	uint8_t alt_index;
-	uint8_t iface_index;
-	uint8_t i;
-	uint8_t j;
+	struct usb2_attach_arg		*uaa = device_get_ivars(dev);
+	struct ubt_softc		*sc = device_get_softc(dev);
+	struct usb2_endpoint_descriptor	*ed;
+	uint16_t			wMaxPacketSize;
+	uint8_t				alt_index, iface_index, i, j;
 
 	device_set_usb2_desc(dev);
 
 	snprintf(sc->sc_name, sizeof(sc->sc_name),
-	    "%s", device_get_nameunit(dev));
+		"%s", device_get_nameunit(dev));
 
+	/* Expect high-speed device */
+	if (usb2_get_speed(uaa->device) != USB_SPEED_HIGH)
+		device_printf(dev, "device is not a high-speed device!\n");
+
+	/* 
+	 * Create Netgraph node
+	 */
+
+	sc->sc_hook = NULL;
+
+	if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) {
+		device_printf(dev, "could not create Netgraph node\n");
+		return (ENXIO);
+	}
+
+	/* Name Netgraph node */
+	if (ng_name_node(sc->sc_node, sc->sc_name) != 0) {
+		device_printf(dev, "could not name Netgraph node\n");
+		NG_NODE_UNREF(sc->sc_node);
+		return (ENXIO);
+	}
+	NG_NODE_SET_PRIVATE(sc->sc_node, sc);
+	NG_NODE_FORCE_WRITER(sc->sc_node);
+
 	/*
 	 * Initialize device softc structure
 	 */
@@ -481,34 +494,28 @@
 	/* state */
 	sc->sc_debug = NG_UBT_WARN_LEVEL;
 	sc->sc_flags = 0;
-	NG_UBT_STAT_RESET(sc->sc_stat);
+	UBT_STAT_RESET(sc);
 
-	/* control pipe */
+	/* initialize locks */
+	mtx_init(&sc->sc_mbufq_mtx, "ubt mbufq", NULL, MTX_DEF);
+	mtx_init(&sc->sc_if_0_mtx, "ubt if0", NULL, MTX_DEF | MTX_RECURSE);
+	mtx_init(&sc->sc_if_1_mtx, "ubt if1", NULL, MTX_DEF | MTX_RECURSE);
+
+	/* initialize packet queues */
 	NG_BT_MBUFQ_INIT(&sc->sc_cmdq, UBT_DEFAULT_QLEN);
-
-	/* bulk-out pipe */
 	NG_BT_MBUFQ_INIT(&sc->sc_aclq, UBT_DEFAULT_QLEN);
+	NG_BT_MBUFQ_INIT(&sc->sc_scoq, UBT_DEFAULT_QLEN);
 
-	/* isoc-out pipe */
-	NG_BT_MBUFQ_INIT(&sc->sc_scoq,
-	    (usb2_get_speed(uaa->device) == USB_SPEED_HIGH) ?
-	    (2 * UBT_ISOC_NFRAMES * 8) :
-	    (2 * UBT_ISOC_NFRAMES));
+	/* initialize glue task */
+	sc->sc_task_flags = 0;
+	TASK_INIT(&sc->sc_task, 0, ubt_task, sc->sc_node);
 
-	/* isoc-in pipe */
-	NG_BT_MBUFQ_INIT(&sc->sc_sciq,
-	    (usb2_get_speed(uaa->device) == USB_SPEED_HIGH) ?
-	    (2 * UBT_ISOC_NFRAMES * 8) :
-	    (2 * UBT_ISOC_NFRAMES));
-
-	/* netgraph part */
-	sc->sc_node = NULL;
-	sc->sc_hook = NULL;
-
 	/*
 	 * Configure Bluetooth USB device. Discover all required USB
 	 * interfaces and endpoints.
 	 *
+	 * Device is expected to be a high-speed device.
+	 *
 	 * USB device must present two interfaces:
 	 * 1) Interface 0 that has 3 endpoints
 	 *	1) Interrupt endpoint to receive HCI events
@@ -527,791 +534,995 @@
 	 * Interface 0
 	 */
 
-	mtx_init(&sc->sc_mtx, "ubt lock", NULL, MTX_DEF | MTX_RECURSE);
-
 	iface_index = 0;
-	if (usb2_transfer_setup
-	    (uaa->device, &iface_index, sc->sc_xfer_if_0, ubt_config_if_0,
-	    UBT_IF_0_N_TRANSFER, sc, &sc->sc_mtx)) {
-		device_printf(dev, "Could not allocate transfers "
-		    "for interface 0!\n");
+	if (usb2_transfer_setup(uaa->device, &iface_index, sc->sc_xfer_if_0,
+			ubt_config_if_0, UBT_IF_0_N_TRANSFER,
+			sc->sc_node, &sc->sc_if_0_mtx)) {
+		device_printf(dev, "could not allocate transfers for " \
+			"interface 0!\n");
 		goto detach;
 	}
+
 	/*
 	 * Interface 1
-	 * (search alternate settings, and find
-	 *  the descriptor with the largest
+	 * (search alternate settings, and find the descriptor with the largest
 	 *  wMaxPacketSize)
 	 */
-	isoc_setup =
-	    ((usb2_get_speed(uaa->device) == USB_SPEED_HIGH) ?
-	    ubt_config_if_1_high_speed :
-	    ubt_config_if_1_full_speed);
 
 	wMaxPacketSize = 0;
-
-	/* search through all the descriptors looking for bidir mode */
-
-	alt_index = 0 - 1;
+	alt_index = 0;
 	i = 0;
 	j = 0;
+
+	/* Search through all the descriptors looking for bidir mode */
 	while (1) {
 		uint16_t temp;
 
-		ed = usb2_find_edesc(
-		    usb2_get_config_descriptor(uaa->device), 1, i, j);
+		ed = usb2_find_edesc(usb2_get_config_descriptor(uaa->device),
+				1, i, j);
 		if (ed == NULL) {
-			if (j == 0) {
-				/* end of interfaces */
-				break;
-			} else {
+			if (j != 0) {
 				/* next interface */
 				j = 0;
-				i++;
+				i ++;
 				continue;
 			}
+
+			break;	/* end of interfaces */
 		}
+
 		temp = UGETW(ed->wMaxPacketSize);
 		if (temp > wMaxPacketSize) {
 			wMaxPacketSize = temp;
 			alt_index = i;
 		}
-		j++;
-	}
 
-	if (usb2_set_alt_interface_index(uaa->device, 1, alt_index)) {
-		device_printf(dev, "Could not set alternate "
-		    "setting %d for interface 1!\n", alt_index);
-		goto detach;
+		j ++;
 	}
-	iface_index = 1;
-	if (usb2_transfer_setup
-	    (uaa->device, &iface_index, sc->sc_xfer_if_1,
-	    isoc_setup, UBT_IF_1_N_TRANSFER, sc, &sc->sc_mtx)) {
-		device_printf(dev, "Could not allocate transfers "
-		    "for interface 1!\n");
-		goto detach;
-	}
-	/* create Netgraph node */
 
-	if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) {
-		printf("%s: Could not create Netgraph node\n",
-		    sc->sc_name);
-		sc->sc_node = NULL;
+	/* Set alt configuration only if we found it */
+	if (wMaxPacketSize > 0 &&
+	    usb2_set_alt_interface_index(uaa->device, 1, alt_index)) {
+		device_printf(dev, "could not set alternate setting %d " \
+			"for interface 1!\n", alt_index);
 		goto detach;
 	}
-	/* name node */
 
-	if (ng_name_node(sc->sc_node, sc->sc_name) != 0) {
-		printf("%s: Could not name Netgraph node\n",
-		    sc->sc_name);
-		NG_NODE_UNREF(sc->sc_node);
-		sc->sc_node = NULL;
+	iface_index = 1;
+	if (usb2_transfer_setup(uaa->device, &iface_index, sc->sc_xfer_if_1,
+			ubt_config_if_1, UBT_IF_1_N_TRANSFER,
+			sc->sc_node, &sc->sc_if_1_mtx)) {
+		device_printf(dev, "could not allocate transfers for " \
+			"interface 1!\n");
 		goto detach;
 	}
-	NG_NODE_SET_PRIVATE(sc->sc_node, sc);
-	NG_NODE_FORCE_WRITER(sc->sc_node);
 
-	/* claim all interfaces on the device */
-
-	for (i = 1;; i++) {
-
-		if (usb2_get_iface(uaa->device, i) == NULL) {
-			break;
-		}
+	/* Claim all interfaces on the device */
+	for (i = 1; usb2_get_iface(uaa->device, i) != NULL; i ++)
 		usb2_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
-	}
 
-	return (0);			/* success */
+	return (0); /* success */
 
 detach:
 	ubt_detach(dev);
 
 	return (ENXIO);
-}
+} /* ubt_attach */
 
 /*
- * Detach the device
+ * Detach the device.
+ * USB context.
  */
 
 int
 ubt_detach(device_t dev)
 {
-	struct ubt_softc *sc = device_get_softc(dev);
+	struct ubt_softc	*sc = device_get_softc(dev);
 
-	/* destroy Netgraph node */
-
+	/* Destroy Netgraph node */
 	if (sc->sc_node != NULL) {
 		NG_NODE_SET_PRIVATE(sc->sc_node, NULL);
 		ng_rmnode_self(sc->sc_node);
 		sc->sc_node = NULL;
 	}
-	/* free USB transfers, if any */
 
+	/* Free USB transfers, if any */
 	usb2_transfer_unsetup(sc->sc_xfer_if_0, UBT_IF_0_N_TRANSFER);
-
 	usb2_transfer_unsetup(sc->sc_xfer_if_1, UBT_IF_1_N_TRANSFER);
 
-	mtx_destroy(&sc->sc_mtx);
-
-	/* destroy queues */
-
+	/* Destroy queues */
+	UBT_MBUFQ_LOCK(sc);
 	NG_BT_MBUFQ_DESTROY(&sc->sc_cmdq);
 	NG_BT_MBUFQ_DESTROY(&sc->sc_aclq);
 	NG_BT_MBUFQ_DESTROY(&sc->sc_scoq);
-	NG_BT_MBUFQ_DESTROY(&sc->sc_sciq);
+	UBT_MBUFQ_UNLOCK(sc);
 
+	mtx_destroy(&sc->sc_if_0_mtx);
+	mtx_destroy(&sc->sc_if_1_mtx);
+	mtx_destroy(&sc->sc_mbufq_mtx);
+
 	return (0);
-}
+} /* ubt_detach */
 
+/* 
+ * Called when outgoing control request (HCI command) has completed, i.e.
+ * HCI command was sent to the device.
+ * USB context.
+ */
+
 static void
 ubt_ctrl_write_callback(struct usb2_xfer *xfer)
 {
-	struct ubt_softc *sc = xfer->priv_sc;
-	struct usb2_device_request req;
-	struct mbuf *m;
+	node_p				node = xfer->priv_sc;
+	struct ubt_softc		*sc;
+	struct usb2_device_request	req;
+	struct mbuf			*m;
 
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
+
+	sc = NG_NODE_PRIVATE(node);
+
 	switch (USB_GET_STATE(xfer)) {
 	case USB_ST_TRANSFERRED:
-tr_transferred:
+		if (xfer->error != 0)
+			UBT_STAT_OERROR(sc);
+		else {
+			UBT_INFO(sc, "sent %d bytes to control pipe\n",
+				xfer->actlen);
 
-		if (xfer->error) {
-			NG_UBT_STAT_OERROR(sc->sc_stat);
-		} else {
-			NG_UBT_STAT_BYTES_SENT(sc->sc_stat, xfer->actlen);
-			NG_UBT_STAT_PCKTS_SENT(sc->sc_stat);
+			UBT_STAT_BYTES_SENT(sc, xfer->actlen);
+			UBT_STAT_PCKTS_SENT(sc);
 		}
+		/* FALLTHROUGH */
 
 	case USB_ST_SETUP:
-
-		/* get next mbuf, if any */
-
+send_next:
+		/* Get next command mbuf, if any */
+		UBT_MBUFQ_LOCK(sc);
 		NG_BT_MBUFQ_DEQUEUE(&sc->sc_cmdq, m);
+		UBT_MBUFQ_UNLOCK(sc);
 
 		if (m == NULL) {
-			NG_UBT_INFO(sc, "HCI command queue is empty\n");
+			UBT_INFO(sc, "HCI command queue is empty\n");
+			NG_NODE_UNREF(node); /* transfer completed */
 			return;
 		}
-		/*
-		 * check HCI command frame size and
-		 * copy it to USB transfer buffer:
-		 */
 
-		if (m->m_pkthdr.len > UBT_CTRL_BUFFER_SIZE) {
-			panic("HCI command frame too big, size=%zd, len=%d\n",
-			    UBT_CTRL_BUFFER_SIZE, m->m_pkthdr.len);
-		}
-		/* initialize a USB control request and then schedule it */
-
+		/* Initialize a USB control request and then schedule it */
 		bzero(&req, sizeof(req));
-
 		req.bmRequestType = UBT_HCI_REQUEST;
 		USETW(req.wLength, m->m_pkthdr.len);
 
-		NG_UBT_INFO(sc, "Sending control request, bmRequestType=0x%02x, "
-		    "wLength=%d\n", req.bmRequestType, UGETW(req.wLength));
+		UBT_INFO(sc, "Sending control request, " \
+			"bmRequestType=0x%02x, wLength=%d\n",
+			req.bmRequestType, UGETW(req.wLength));
 
 		usb2_copy_in(xfer->frbuffers, 0, &req, sizeof(req));
 		usb2_m_copy_in(xfer->frbuffers + 1, 0, m, 0, m->m_pkthdr.len);
 
 		xfer->frlengths[0] = sizeof(req);
 		xfer->frlengths[1] = m->m_pkthdr.len;
-		xfer->nframes = xfer->frlengths[1] ? 2 : 1;
+		xfer->nframes = 2;
 
 		NG_FREE_M(m);
 
 		usb2_start_hardware(xfer);
-		return;
+		break;
 
-	default:			/* Error */
-		if (xfer->error == USB_ERR_CANCELLED) {
-			/* ignore */
-			return;
+	default: /* Error */
+		if (xfer->error != USB_ERR_CANCELLED) {
+			UBT_WARN(sc, "control transfer failed: %s\n",
+				usb2_errstr(xfer->error));
+
+			UBT_STAT_OERROR(sc);
+			goto send_next;
 		}
-		goto tr_transferred;
+
+		NG_NODE_UNREF(node); /* transfer cancelled */
+		break;
 	}
-}
+} /* ubt_ctrl_write_callback */
 
+/* 
+ * Called when incoming interrupt transfer (HCI event) has completed, i.e.
+ * HCI event was received from the device.
+ * USB context.
+ */
+
 static void
 ubt_intr_read_callback(struct usb2_xfer *xfer)
 {
-	struct ubt_softc *sc = xfer->priv_sc;
-	struct mbuf *m;
-	uint32_t max_len;
-	uint8_t *ptr;
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	struct mbuf		*m;
+	ng_hci_event_pkt_t	*hdr;
+	int			error;
 
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
+
+	sc = NG_NODE_PRIVATE(node);
+
+	if ((sc->sc_hook == NULL) || NG_HOOK_NOT_VALID(sc->sc_hook)) {
+		UBT_INFO(sc, "no upstream hook\n");
+		NG_NODE_UNREF(node);
+		return; /* upstream hook is gone */
+	}
+
+	m = NULL;
+
 	switch (USB_GET_STATE(xfer)) {
 	case USB_ST_TRANSFERRED:
-
-		/* allocate a new mbuf */
-
+		/* Allocate a new mbuf */
 		MGETHDR(m, M_DONTWAIT, MT_DATA);
-
 		if (m == NULL) {
-			goto tr_setup;
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
+
 		MCLGET(m, M_DONTWAIT);
-
 		if (!(m->m_flags & M_EXT)) {
-			NG_FREE_M(m);
-			goto tr_setup;
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
-		if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) {
-			*mtod(m, uint8_t *)= NG_HCI_EVENT_PKT;
-			m->m_pkthdr.len = m->m_len = 1;
-		} else {
-			m->m_pkthdr.len = m->m_len = 0;
+
+		/* Add HCI packet type */
+		*mtod(m, uint8_t *)= NG_HCI_EVENT_PKT;
+		m->m_pkthdr.len = m->m_len = 1;
+
+		if (xfer->actlen > MCLBYTES - 1)
+			xfer->actlen = MCLBYTES - 1;
+
+		usb2_copy_out(xfer->frbuffers, 0, mtod(m, uint8_t *) + 1,
+			xfer->actlen);
+		m->m_pkthdr.len += xfer->actlen;
+		m->m_len += xfer->actlen;
+
+		UBT_INFO(sc, "got %d bytes from interrupt pipe\n",
+			xfer->actlen);
+
+		/* Validate packet and send it up the stack */
+		if (m->m_pkthdr.len < sizeof(*hdr)) {
+			UBT_INFO(sc, "HCI event packet is too short\n");
+
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
 
-		max_len = (MCLBYTES - m->m_len);
+		hdr = mtod(m, ng_hci_event_pkt_t *);
+		if (hdr->length != (m->m_pkthdr.len - sizeof(*hdr))) {
+			UBT_ERR(sc, "Invalid HCI event packet size, " \
+				"length=%d, pktlen=%d\n",
+				hdr->length, m->m_pkthdr.len);
 
-		if (xfer->actlen > max_len) {
-			xfer->actlen = max_len;
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
-		ptr = ((uint8_t *)(m->m_data)) + m->m_len;
 
-		usb2_copy_out(xfer->frbuffers, 0, ptr, xfer->actlen);
+		UBT_INFO(sc, "got complete HCI event frame, pktlen=%d, " \
+			"length=%d\n", m->m_pkthdr.len, hdr->length);
 
-		m->m_pkthdr.len += xfer->actlen;
-		m->m_len += xfer->actlen;
+		UBT_STAT_PCKTS_RECV(sc);
+		UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len);
 
-		NG_UBT_INFO(sc, "got %d bytes from interrupt "
-		    "pipe\n", xfer->actlen);
+		NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+		if (error != 0)
+			UBT_STAT_IERROR(sc);
 
-		sc->sc_intr_buffer = m;
+		/* m == NULL at this point */
+		/* FALLTHROUGH */
 
 	case USB_ST_SETUP:
-tr_setup:
+submit_next:
+		NG_FREE_M(m); /* checks for m != NULL */
 
-		if (sc->sc_intr_buffer) {
-			ng_send_fn(sc->sc_node, NULL, ubt_intr_read_complete, NULL, 0);
-			return;
-		}
 		if (sc->sc_flags & UBT_FLAG_INTR_STALL) {
 			usb2_transfer_start(sc->sc_xfer_if_0[6]);
-			return;
+		} else {
+			xfer->frlengths[0] = xfer->max_data_length;
+			usb2_start_hardware(xfer);
 		}
-		xfer->frlengths[0] = xfer->max_data_length;
+		break;
 
-		usb2_start_hardware(xfer);
-		return;
-
-	default:			/* Error */
+	default: /* Error */
 		if (xfer->error != USB_ERR_CANCELLED) {
-			/* try to clear stall first */
+			/* Try to clear stall first */
 			sc->sc_flags |= UBT_FLAG_INTR_STALL;
 			usb2_transfer_start(sc->sc_xfer_if_0[6]);
-		}
-		return;
-
+		} else
+			NG_NODE_UNREF(node); /* transfer cancelled */
+		break;
 	}
-}
+} /* ubt_intr_read_callback */
 
+/*
+ * Called when outgoing control transfer initiated to clear stall on
+ * interrupt pipe has completed.
+ * USB context.
+ */
+
 static void
 ubt_intr_read_clear_stall_callback(struct usb2_xfer *xfer)
 {
-	struct ubt_softc *sc = xfer->priv_sc;
-	struct usb2_xfer *xfer_other = sc->sc_xfer_if_0[2];
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	struct usb2_xfer	*xfer_other;
 
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
+
+	sc = NG_NODE_PRIVATE(node);
+	xfer_other = sc->sc_xfer_if_0[2];
+
 	if (usb2_clear_stall_callback(xfer, xfer_other)) {
 		DPRINTF("stall cleared\n");
 		sc->sc_flags &= ~UBT_FLAG_INTR_STALL;
 		usb2_transfer_start(xfer_other);
-	}
-}
+	} else
+		NG_NODE_UNREF(node); /* XXX cant clear stall */
+} /* ubt_intr_read_clear_stall_callback */
 
+/*
+ * Called when incoming bulk transfer (ACL packet) has completed, i.e.
+ * ACL packet was received from the device.
+ * USB context.
+ */
+
 static void
-ubt_intr_read_complete(node_p node, hook_p hook, void *arg1, int arg2)
+ubt_bulk_read_callback(struct usb2_xfer *xfer)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(node);
-	struct mbuf *m;
-	ng_hci_event_pkt_t *hdr;
-	int error;
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	struct mbuf		*m;
+	ng_hci_acldata_pkt_t	*hdr;
+	uint16_t		len;
+	int			error;
 
-	if (sc == NULL) {
-		return;
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
 	}
-	mtx_lock(&sc->sc_mtx);
 
-	m = sc->sc_intr_buffer;
+	sc = NG_NODE_PRIVATE(node);
 
-	if (m) {
+	if ((sc->sc_hook == NULL) || NG_HOOK_NOT_VALID(sc->sc_hook)) {
+		UBT_INFO(sc, "no upstream hook\n");
+		NG_NODE_UNREF(node);
+		return; /* upstream hook is gone */
+	}
 
-		sc->sc_intr_buffer = NULL;
+	m = NULL;
 
-		hdr = mtod(m, ng_hci_event_pkt_t *);
-
-		if ((sc->sc_hook == NULL) || NG_HOOK_NOT_VALID(sc->sc_hook)) {
-			NG_UBT_INFO(sc, "No upstream hook\n");
-			goto done;
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+		/* Allocate new mbuf */
+		MGETHDR(m, M_DONTWAIT, MT_DATA);
+		if (m == NULL) {
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
-		NG_UBT_STAT_BYTES_RECV(sc->sc_stat, m->m_pkthdr.len);
 
-		if (m->m_pkthdr.len < sizeof(*hdr)) {
-			NG_UBT_INFO(sc, "Packet too short\n");
-			goto done;
+		MCLGET(m, M_DONTWAIT);
+		if (!(m->m_flags & M_EXT)) {
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
-		if (hdr->length == (m->m_pkthdr.len - sizeof(*hdr))) {
-			NG_UBT_INFO(sc, "Got complete HCI event frame, "
-			    "pktlen=%d, length=%d\n",
-			    m->m_pkthdr.len, hdr->length);
 
-			NG_UBT_STAT_PCKTS_RECV(sc->sc_stat);
+		/* Add HCI packet type */
+		*mtod(m, uint8_t *)= NG_HCI_ACL_DATA_PKT;
+		m->m_pkthdr.len = m->m_len = 1;
 
-			NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+		if (xfer->actlen > MCLBYTES - 1)
+			xfer->actlen = MCLBYTES - 1;
 
-			m = NULL;
+		usb2_copy_out(xfer->frbuffers, 0, mtod(m, uint8_t *) + 1,
+			xfer->actlen);
+		m->m_pkthdr.len += xfer->actlen;
+		m->m_len += xfer->actlen;
 
-			if (error != 0) {
-				NG_UBT_STAT_IERROR(sc->sc_stat);
-			}
-		} else {
-			NG_UBT_ERR(sc, "Invalid HCI event frame size, "
-			    "length=%d, pktlen=%d\n",
-			    hdr->length, m->m_pkthdr.len);
+		UBT_INFO(sc, "got %d bytes from bulk-in pipe\n",
+			xfer->actlen);
 
-			NG_UBT_STAT_IERROR(sc->sc_stat);
-		}
-	}
-done:
-	if (m) {
-		NG_FREE_M(m);
-	}
-	/* start USB transfer if not already started */
+		/* Validate packet and send it up the stack */
+		if (m->m_pkthdr.len < sizeof(*hdr)) {
+			UBT_INFO(sc, "HCI ACL packet is too short\n");
 
-	usb2_transfer_start(sc->sc_xfer_if_0[2]);
-
-	mtx_unlock(&sc->sc_mtx);
-}
-
-static void
-ubt_bulk_read_callback(struct usb2_xfer *xfer)
-{
-	struct ubt_softc *sc = xfer->priv_sc;
-	struct mbuf *m;
-	uint32_t max_len;
-	uint8_t *ptr;
-
-	switch (USB_GET_STATE(xfer)) {
-	case USB_ST_TRANSFERRED:
-
-		/* allocate new mbuf */
-
-		MGETHDR(m, M_DONTWAIT, MT_DATA);
-
-		if (m == NULL) {
-			goto tr_setup;
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
-		MCLGET(m, M_DONTWAIT);
 
-		if (!(m->m_flags & M_EXT)) {
-			NG_FREE_M(m);
-			goto tr_setup;
-		}
-		if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) {
-			*mtod(m, uint8_t *)= NG_HCI_ACL_DATA_PKT;
-			m->m_pkthdr.len = m->m_len = 1;
-		} else {
-			m->m_pkthdr.len = m->m_len = 0;
-		}
+		hdr = mtod(m, ng_hci_acldata_pkt_t *);
+		len = le16toh(hdr->length);
+		if (len != (m->m_pkthdr.len - sizeof(*hdr))) {
+			UBT_ERR(sc, "Invalid ACL packet size, length=%d, " \
+				"pktlen=%d\n", len, m->m_pkthdr.len);
 
-		max_len = (MCLBYTES - m->m_len);
-
-		if (xfer->actlen > max_len) {
-			xfer->actlen = max_len;
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
-		ptr = ((uint8_t *)(m->m_data)) + m->m_len;
 
-		usb2_copy_out(xfer->frbuffers, 0, ptr, xfer->actlen);
+		UBT_INFO(sc, "got complete ACL data packet, pktlen=%d, " \
+			"length=%d\n", m->m_pkthdr.len, len);
 
-		m->m_pkthdr.len += xfer->actlen;
-		m->m_len += xfer->actlen;
+		UBT_STAT_PCKTS_RECV(sc);
+		UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len);
 
-		NG_UBT_INFO(sc, "got %d bytes from bulk-in "
-		    "pipe\n", xfer->actlen);
+		NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+		if (error != 0)
+			UBT_STAT_IERROR(sc);
 
-		sc->sc_bulk_in_buffer = m;
+		/* m == NULL at this point */
+		/* FALLTHOUGH */
 
 	case USB_ST_SETUP:
-tr_setup:
-		if (sc->sc_bulk_in_buffer) {
-			ng_send_fn(sc->sc_node, NULL, ubt_bulk_read_complete, NULL, 0);
-			return;
-		}
-		if (sc->sc_flags & UBT_FLAG_READ_STALL) {
+submit_next:
+		NG_FREE_M(m); /* checks for m != NULL */
+
+		if (sc->sc_flags & UBT_FLAG_READ_STALL)
 			usb2_transfer_start(sc->sc_xfer_if_0[5]);
-			return;
+		else {
+			xfer->frlengths[0] = xfer->max_data_length;
+			usb2_start_hardware(xfer);
 		}
-		xfer->frlengths[0] = xfer->max_data_length;
+		break;
 
-		usb2_start_hardware(xfer);
-		return;
-
-	default:			/* Error */
+	default: /* Error */
 		if (xfer->error != USB_ERR_CANCELLED) {
-			/* try to clear stall first */
+			/* Try to clear stall first */
 			sc->sc_flags |= UBT_FLAG_READ_STALL;
 			usb2_transfer_start(sc->sc_xfer_if_0[5]);
-		}
-		return;
-
+		} else
+			NG_NODE_UNREF(node); /* transfer cancelled */
+		break;
 	}
-}
+} /* ubt_bulk_read_callback */
 
+/*
+ * Called when outgoing control transfer initiated to clear stall on
+ * incoming bulk pipe has completed.
+ * USB context.
+ */
+
 static void
 ubt_bulk_read_clear_stall_callback(struct usb2_xfer *xfer)
 {
-	struct ubt_softc *sc = xfer->priv_sc;
-	struct usb2_xfer *xfer_other = sc->sc_xfer_if_0[1];
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	struct usb2_xfer	*xfer_other;
 
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
+
+	sc = NG_NODE_PRIVATE(node);
+	xfer_other = sc->sc_xfer_if_0[1];
+
 	if (usb2_clear_stall_callback(xfer, xfer_other)) {
 		DPRINTF("stall cleared\n");
 		sc->sc_flags &= ~UBT_FLAG_READ_STALL;
 		usb2_transfer_start(xfer_other);
-	}
-}
+	} else
+		NG_NODE_UNREF(node); /* XXX cant clear stall */
+} /* ubt_bulk_read_clear_stall_callback */
 
+/*
+ * Called when outgoing bulk transfer (ACL packet) has completed, i.e.
+ * ACL packet was sent to the device.
+ * USB context.
+ */
+
 static void
-ubt_bulk_read_complete(node_p node, hook_p hook, void *arg1, int arg2)
+ubt_bulk_write_callback(struct usb2_xfer *xfer)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(node);
-	struct mbuf *m;
-	ng_hci_acldata_pkt_t *hdr;
-	uint16_t len;
-	int error;
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	struct mbuf		*m;
 
-	if (sc == NULL) {
-		return;
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
 	}
-	mtx_lock(&sc->sc_mtx);
 
-	m = sc->sc_bulk_in_buffer;
+	sc = NG_NODE_PRIVATE(node);
 
-	if (m) {
-
-		sc->sc_bulk_in_buffer = NULL;
-
-		hdr = mtod(m, ng_hci_acldata_pkt_t *);
-
-		if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) {
-			NG_UBT_INFO(sc, "No upstream hook\n");
-			goto done;
-		}
-		NG_UBT_STAT_BYTES_RECV(sc->sc_stat, m->m_pkthdr.len);
-
-		if (m->m_pkthdr.len < sizeof(*hdr)) {
-			NG_UBT_INFO(sc, "Packet too short\n");
-			goto done;
-		}
-		len = le16toh(hdr->length);
-
-		if (len == (m->m_pkthdr.len - sizeof(*hdr))) {
-			NG_UBT_INFO(sc, "Got complete ACL data frame, "
-			    "pktlen=%d, length=%d\n",
-			    m->m_pkthdr.len, len);
-
-			NG_UBT_STAT_PCKTS_RECV(sc->sc_stat);
-
-			NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
-
-			m = NULL;
-
-			if (error != 0) {
-				NG_UBT_STAT_IERROR(sc->sc_stat);
-			}
-		} else {
-			NG_UBT_ERR(sc, "Invalid ACL frame size, "
-			    "length=%d, pktlen=%d\n",
-			    len, m->m_pkthdr.len);
-
-			NG_UBT_STAT_IERROR(sc->sc_stat);
-		}
-	}
-done:
-	if (m) {
-		NG_FREE_M(m);
-	}
-	/* start USB transfer if not already started */
-
-	usb2_transfer_start(sc->sc_xfer_if_0[1]);
-
-	mtx_unlock(&sc->sc_mtx);
-}
-
-static void
-ubt_bulk_write_callback(struct usb2_xfer *xfer)
-{
-	struct ubt_softc *sc = xfer->priv_sc;
-	struct mbuf *m;
-
 	switch (USB_GET_STATE(xfer)) {
 	case USB_ST_TRANSFERRED:
-		NG_UBT_INFO(sc, "sent %d bytes to bulk-out "
-		    "pipe\n", xfer->actlen);
-		NG_UBT_STAT_BYTES_SENT(sc->sc_stat, xfer->actlen);
-		NG_UBT_STAT_PCKTS_SENT(sc->sc_stat);
+		if (xfer->error != 0)
+			UBT_STAT_OERROR(sc);
+		else {
+			UBT_INFO(sc, "sent %d bytes to bulk-out pipe\n",
+				xfer->actlen);
 
-	case USB_ST_SETUP:
-		if (sc->sc_flags & UBT_FLAG_WRITE_STALL) {
-			usb2_transfer_start(sc->sc_xfer_if_0[4]);
-			return;
+			UBT_STAT_BYTES_SENT(sc, xfer->actlen);
+			UBT_STAT_PCKTS_SENT(sc);
 		}
-		/* get next mbuf, if any */
+		/* FALLTHROUGH */
 
+	case USB_ST_SETUP:
+		/* Get next mbuf, if any */
+		UBT_MBUFQ_LOCK(sc);
 		NG_BT_MBUFQ_DEQUEUE(&sc->sc_aclq, m);
+		UBT_MBUFQ_UNLOCK(sc);
 
 		if (m == NULL) {
-			NG_UBT_INFO(sc, "ACL data queue is empty\n");
-			return;
+			UBT_INFO(sc, "ACL data queue is empty\n");
+			NG_NODE_UNREF(node);
+			return; /* transfer completed */
 		}
+
 		/*
-		 * check ACL data frame size and
-		 * copy it back to a linear USB
-		 * transfer buffer:
+		 * Copy ACL data frame back to a linear USB transfer buffer
+		 * and schedule transfer
 		 */
 
-		if (m->m_pkthdr.len > UBT_BULK_WRITE_BUFFER_SIZE) {
-			panic("ACL data frame too big, size=%d, len=%d\n",
-			    UBT_BULK_WRITE_BUFFER_SIZE,
-			    m->m_pkthdr.len);
-		}
 		usb2_m_copy_in(xfer->frbuffers, 0, m, 0, m->m_pkthdr.len);
+		xfer->frlengths[0] = m->m_pkthdr.len;
 
-		NG_UBT_INFO(sc, "bulk-out transfer has been started, "
-		    "len=%d\n", m->m_pkthdr.len);
+		UBT_INFO(sc, "bulk-out transfer has been started, len=%d\n",
+			m->m_pkthdr.len);
 
-		xfer->frlengths[0] = m->m_pkthdr.len;
-
 		NG_FREE_M(m);
 
 		usb2_start_hardware(xfer);
-		return;
+		break;
 
-	default:			/* Error */
+	default: /* Error */
 		if (xfer->error != USB_ERR_CANCELLED) {
+			UBT_WARN(sc, "bulk-out transfer failed: %s\n",
+				usb2_errstr(xfer->error));
 
-			NG_UBT_WARN(sc, "bulk-out transfer failed: %s\n",
-			    usb2_errstr(xfer->error));
+			UBT_STAT_OERROR(sc);
 
-			NG_UBT_STAT_OERROR(sc->sc_stat);
-
 			/* try to clear stall first */
 			sc->sc_flags |= UBT_FLAG_WRITE_STALL;
 			usb2_transfer_start(sc->sc_xfer_if_0[4]);
-		}
-		return;
-
+		} else
+			NG_NODE_UNREF(node); /* transfer cancelled */
+		break;
 	}
-}
+} /* ubt_bulk_write_callback */
 
+/*
+ * Called when outgoing control transfer initiated to clear stall on
+ * outgoing bulk pipe has completed.
+ * USB context.
+ */
+
 static void
 ubt_bulk_write_clear_stall_callback(struct usb2_xfer *xfer)
 {
-	struct ubt_softc *sc = xfer->priv_sc;
-	struct usb2_xfer *xfer_other = sc->sc_xfer_if_0[0];
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	struct usb2_xfer	*xfer_other;
 
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
+
+	sc = NG_NODE_PRIVATE(node);
+	xfer_other = sc->sc_xfer_if_0[0];
+
 	if (usb2_clear_stall_callback(xfer, xfer_other)) {
 		DPRINTF("stall cleared\n");
 		sc->sc_flags &= ~UBT_FLAG_WRITE_STALL;
 		usb2_transfer_start(xfer_other);
-	}
-}
+	} else
+		NG_NODE_UNREF(node); /* XXX cant clear stall */
+} /* ubt_bulk_write_clear_stall_callback */
 
+/*
+ * Called when incoming isoc transfer (SCO packet) has completed, i.e.
+ * SCO packet was received from the device.
+ * USB context.
+ */
+
 static void
 ubt_isoc_read_callback(struct usb2_xfer *xfer)
 {
-	struct ubt_softc *sc = xfer->priv_sc;
-	ng_hci_scodata_pkt_t hdr;
-	struct mbuf *m;
-	uint8_t *ptr;
-	uint32_t max_len;
-	uint32_t n;
-	uint32_t offset;
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	int			n;
 
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
+
+	sc = NG_NODE_PRIVATE(node);
+
+	if ((sc->sc_hook == NULL) || NG_HOOK_NOT_VALID(sc->sc_hook)) {
+		UBT_INFO(sc, "no upstream hook\n");
+		NG_NODE_UNREF(node);
+		return; /* upstream hook is gone */
+	}
+
 	switch (USB_GET_STATE(xfer)) {
 	case USB_ST_TRANSFERRED:
-tr_transferred:
-		offset = 0;
+		for (n = 0; n < xfer->nframes; n ++)
+			if (ubt_isoc_read_one_frame(xfer, n) < 0)
+				break;
+		/* FALLTHROUGH */
 
-		for (n = 0; n < xfer->nframes; n++) {
+	case USB_ST_SETUP:
+read_next:
+		for (n = 0; n < xfer->nframes; n ++)
+			xfer->frlengths[n] = xfer->max_frame_size;
 
-			if (xfer->frlengths[n] >= sizeof(hdr)) {
+		usb2_start_hardware(xfer);
+		break;
 
-				usb2_copy_out(xfer->frbuffers, offset,
-				    &hdr, sizeof(hdr));
+	default: /* Error */
+                if (xfer->error != USB_ERR_CANCELLED) {
+                        UBT_STAT_IERROR(sc);
+                        goto read_next;
+			/* NOT REACHED */
+                }
 
-				if (hdr.length == (xfer->frlengths[n] - sizeof(hdr))) {
+		NG_NODE_UNREF(node); /* transfer is cancelled */
+		break;
+	}
+} /* ubt_isoc_read_callback */
 
-					NG_UBT_INFO(sc, "got complete SCO data "
-					    "frame, length=%d\n", hdr.length);
+/*
+ * Helper function. Called from ubt_isoc_read_callback() to read
+ * SCO data from one frame.
+ * USB context.
+ */
 
-					if (!NG_BT_MBUFQ_FULL(&sc->sc_sciq)) {
+static int
+ubt_isoc_read_one_frame(struct usb2_xfer *xfer, uint32_t frame_no)
+{
+	struct ubt_softc	*sc = xfer->priv_sc;
+	struct mbuf		*m;
+	int			len, want, got, error;
 
-						/* allocate a new mbuf */
+	/* Get existing SCO reassembly buffer */
+	m = sc->sc_isoc_in_buffer;
+	sc->sc_isoc_in_buffer = NULL;
 
-						MGETHDR(m, M_DONTWAIT, MT_DATA);
+	/* While we have data in the frame */
+	while ((len = xfer->frlengths[frame_no]) > 0) {
+		if (m == NULL) {
+			/* Start new reassembly buffer */
+			MGETHDR(m, M_DONTWAIT, MT_DATA);
+			if (m == NULL) {
+				UBT_STAT_IERROR(sc);
+				return (-1);	/* XXX out of sync! */
+			}
 
-						if (m == NULL) {
-							goto tr_setup;
-						}
-						MCLGET(m, M_DONTWAIT);
+			MCLGET(m, M_DONTWAIT);
+			if (!(m->m_flags & M_EXT)) {
+				UBT_STAT_IERROR(sc);
+				NG_FREE_M(m);
+				return (-1);	/* XXX out of sync! */
+			}
 
-						if (!(m->m_flags & M_EXT)) {
-							NG_FREE_M(m);
-							goto tr_setup;
-						}
-						/*
-						 * fix SCO data frame header
-						 * if required
-						 */
+			/* Expect SCO header */
+			*mtod(m, uint8_t *) = NG_HCI_SCO_DATA_PKT;
+			m->m_pkthdr.len = m->m_len = got = 1;
+			want = sizeof(ng_hci_scodata_pkt_t);
+		} else {
+			/*
+			 * Check if we have SCO header and if so 
+			 * adjust amount of data we want
+			 */
+			got = m->m_pkthdr.len;
+			want = sizeof(ng_hci_scodata_pkt_t);
 
-						if (!(sc->sc_flags & UBT_HAVE_FRAME_TYPE)) {
-							*mtod(m, uint8_t *)= NG_HCI_SCO_DATA_PKT;
-							m->m_pkthdr.len = m->m_len = 1;
-						} else {
-							m->m_pkthdr.len = m->m_len = 0;
-						}
+			if (got >= want)
+				want += mtod(m, ng_hci_scodata_pkt_t *)->length;
+		}
 
-						max_len = (MCLBYTES - m->m_len);
+		/* Append frame data to the SCO reassembly buffer */
+		if (got + len > want)
+			len = want - got;
 
-						if (xfer->frlengths[n] > max_len) {
-							xfer->frlengths[n] = max_len;
-						}
-						ptr = ((uint8_t *)(m->m_data)) + m->m_len;
+		usb2_copy_out(xfer->frbuffers, frame_no * xfer->max_frame_size,
+			mtod(m, uint8_t *) + m->m_pkthdr.len, len);
 
-						usb2_copy_out
-						    (xfer->frbuffers, offset,
-						    ptr, xfer->frlengths[n]);
+		m->m_pkthdr.len += len;
+		m->m_len += len;
+		xfer->frlengths[frame_no] -= len;
 
-						m->m_pkthdr.len += xfer->frlengths[n];
-						m->m_len += xfer->frlengths[n];
+		/* Check if we got everything we wanted, if not - continue */
+		if (got != want)
+			continue;
 
-						NG_BT_MBUFQ_ENQUEUE(&sc->sc_sciq, m);
-					}
-				}
-			}
-			offset += xfer->max_frame_size;
+		/* If we got here then we got complete SCO frame */
+		UBT_INFO(sc, "got complete SCO data frame, pktlen=%d, " \
+			"length=%d\n", m->m_pkthdr.len,
+			mtod(m, ng_hci_scodata_pkt_t *)->length);
+
+		UBT_STAT_PCKTS_RECV(sc);
+		UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len);
+
+		NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+		if (error != 0)
+			UBT_STAT_IERROR(sc);
+
+		/* m == NULL at this point */
+	}
+
+	/* Put SCO reassembly buffer back */
+	sc->sc_isoc_in_buffer = m;
+
+	return (0);
+} /* ubt_isoc_read_one_frame */
+
+/*
+ * Called when outgoing isoc transfer (SCO packet) has completed, i.e.
+ * SCO packet was sent to the device.
+ * USB context.
+ */
+
+static void
+ubt_isoc_write_callback(struct usb2_xfer *xfer)
+{
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	struct mbuf		*m;
+	uint32_t		n, space, offset;
+
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
+
+	sc = NG_NODE_PRIVATE(node);
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+		if (xfer->error)
+			UBT_STAT_OERROR(sc);
+		else {
+			UBT_INFO(sc, "sent %d bytes to isoc-out pipe\n",
+				xfer->actlen);
+
+			UBT_STAT_BYTES_SENT(sc, xfer->actlen);
+			UBT_STAT_PCKTS_SENT(sc);
 		}
+		/* FALLTHROUGH */
 
 	case USB_ST_SETUP:
-tr_setup:
+send_next:
+		offset = 0;
+		space = xfer->max_frame_size * xfer->nframes;
+		m = NULL;
 
-		if (NG_BT_MBUFQ_LEN(&sc->sc_sciq) > 0) {
-			ng_send_fn(sc->sc_node, NULL, ubt_isoc_read_complete, NULL, 0);
+		while (space > 0) {
+			if (m == NULL) {
+				UBT_MBUFQ_LOCK(sc);
+				NG_BT_MBUFQ_DEQUEUE(&sc->sc_scoq, m);
+				UBT_MBUFQ_UNLOCK(sc);
+
+				if (m == NULL)
+					break;
+			}
+
+			n = min(space, m->m_pkthdr.len);
+			if (n > 0) {
+				usb2_m_copy_in(xfer->frbuffers,offset, m,0, n);
+				m_adj(m, n);
+
+				offset += n;
+				space -= n;
+			}
+
+			if (m->m_pkthdr.len == 0)
+				NG_FREE_M(m);
 		}
-		for (n = 0; n < xfer->nframes; n++) {
-			xfer->frlengths[n] = xfer->max_frame_size;
+
+		if (offset == 0) {
+			NG_NODE_UNREF(node);
+			return; /* nothing to send */
 		}
 
+		/* Put whatever is left from mbuf back on queue */
+		if (m != NULL) {
+			UBT_MBUFQ_LOCK(sc);
+			NG_BT_MBUFQ_PREPEND(&sc->sc_scoq, m);
+			UBT_MBUFQ_UNLOCK(sc);
+		}
+
+		/* Calculate sized for isoc frames */
+		for (n = 0; offset > 0; n ++) {
+			xfer->frlengths[n] = min(offset, xfer->max_frame_size);
+			offset -= xfer->max_frame_size;
+		}
+
 		usb2_start_hardware(xfer);
-		return;
+		break;
 
-	default:			/* Error */
-		if (xfer->error == USB_ERR_CANCELLED) {
-			/* ignore */
-			return;
+	default: /* Error */
+		if (xfer->error != USB_ERR_CANCELLED) {
+			UBT_STAT_OERROR(sc);
+			goto send_next;
+			/* NOT REACHED */
 		}
-		goto tr_transferred;
+
+		NG_NODE_UNREF(node); /* transfer cancelled */
+		break;
 	}
 }
 
+/****************************************************************************
+ ****************************************************************************
+ **                                 Glue 
+ ****************************************************************************
+ ****************************************************************************/
+
+/*
+ * Schedule glue task. Should be called with sc_mbufq_mtx held.
+ * Netgraph context.
+ */
+
+static int
+ubt_task_schedule(ubt_softc_p sc, uint32_t action)
+{
+	mtx_assert(&sc->sc_mbufq_mtx, MA_OWNED);
+
+	if ((sc->sc_task_flags & action) == 0) {
+		/*
+		 * Try to handle corner case when "start all" and "stop all"
+		 * actions can both be set before task is executed.
+		 *
+		 * Assume the following:
+		 * 1) "stop all" after "start all" cancels "start all", and,
+		 *    keeps "stop all"
+		 *
+		 * 2) "start all" after "stop all" is fine because task is
+		 * executing "stop all" first
+		 */
+
+		if (action == UBT_FLAG_T_STOP_ALL &&
+		    (sc->sc_task_flags & UBT_FLAG_T_START_ALL) != 0)
+			sc->sc_task_flags &= ~UBT_FLAG_T_START_ALL;
+
+		sc->sc_task_flags |= action;
+	}
+
+	if (sc->sc_task_flags & UBT_FLAG_T_PENDING)
+		return (1);
+
+	if (taskqueue_enqueue(taskqueue_swi, &sc->sc_task) == 0) {
+		NG_NODE_REF(sc->sc_node);
+		sc->sc_task_flags |= UBT_FLAG_T_PENDING;
+		return (1);
+	}
+
+	/* XXX: i think this should never happen */
+
+	return (0);
+} /* ubt_task_schedule */
+
+/*
+ * Glue task. Examines sc_task_flags and does things depending on it.
+ * Taskqueue context.
+ */
+
 static void
-ubt_isoc_read_complete(node_p node, hook_p hook, void *arg1, int arg2)
+ubt_task(void *context, int pending)
 {
-	ubt_softc_p sc = NG_NODE_PRIVATE(node);
-	struct mbuf *m;
-	int error;
+	node_p		node = context;
+	ubt_softc_p	sc;
+	uint32_t	task_flags;
 
-	if (sc == NULL) {
-		return;
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
 	}
-	mtx_lock(&sc->sc_mtx);
 
-	while (1) {
+	sc = NG_NODE_PRIVATE(node);
 
-		NG_BT_MBUFQ_DEQUEUE(&sc->sc_sciq, m);
+	UBT_MBUFQ_LOCK(sc);
+	task_flags = sc->sc_task_flags;
+	sc->sc_task_flags = 0;
+	UBT_MBUFQ_UNLOCK(sc);
 
-		if (m == NULL) {
-			break;
-		}
-		if (sc->sc_hook == NULL || NG_HOOK_NOT_VALID(sc->sc_hook)) {
-			NG_UBT_INFO(sc, "No upstream hook\n");
-			goto done;
-		}
-		NG_UBT_INFO(sc, "Got complete SCO data frame, "
-		    "pktlen=%d bytes\n", m->m_pkthdr.len);
+	/* Stop all USB transfers */
+	if (task_flags & UBT_FLAG_T_STOP_ALL) {
+		/*
+		 * Interface #0
+		 */
 
-		NG_UBT_STAT_PCKTS_RECV(sc->sc_stat);
-		NG_UBT_STAT_BYTES_RECV(sc->sc_stat, m->m_pkthdr.len);
+		mtx_lock(&sc->sc_if_0_mtx);
 
-		NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+		usb2_transfer_stop(sc->sc_xfer_if_0[0]); /* bulk out */
+		usb2_transfer_stop(sc->sc_xfer_if_0[1]); /* bulk in */
+		usb2_transfer_stop(sc->sc_xfer_if_0[2]); /* intr in */
+		usb2_transfer_stop(sc->sc_xfer_if_0[3]); /* ctrl out */
+		usb2_transfer_stop(sc->sc_xfer_if_0[4]);
+		usb2_transfer_stop(sc->sc_xfer_if_0[5]);
+		usb2_transfer_stop(sc->sc_xfer_if_0[6]);
 
-		m = NULL;
+		mtx_unlock(&sc->sc_if_0_mtx);
 
-		if (error) {
-			NG_UBT_STAT_IERROR(sc->sc_stat);
-		}
-done:
-		if (m) {
-			NG_FREE_M(m);
-		}
+		/*
+		 * Interface #1
+		 */
+
+		mtx_lock(&sc->sc_if_1_mtx);
+
+		usb2_transfer_stop(sc->sc_xfer_if_1[0]); /* isoc in #1 */
+		usb2_transfer_stop(sc->sc_xfer_if_1[1]); /* isoc in #2 */
+		usb2_transfer_stop(sc->sc_xfer_if_1[2]); /* isoc out #1 */
+		usb2_transfer_stop(sc->sc_xfer_if_1[3]); /* isoc out #2 */
+
+		mtx_unlock(&sc->sc_if_1_mtx);
 	}
 
-	mtx_unlock(&sc->sc_mtx);
-}
+	/* Start all incoming USB transfers */
+	if (task_flags & UBT_FLAG_T_START_ALL) {
+		/*
+		 * Interface #0
+		 */
 
-static void
-ubt_isoc_write_callback(struct usb2_xfer *xfer)
-{
-	struct ubt_softc *sc = xfer->priv_sc;
-	struct mbuf *m;
-	uint32_t n;
-	uint32_t len;
-	uint32_t offset;
+		mtx_lock(&sc->sc_if_0_mtx);
 
-	switch (USB_GET_STATE(xfer)) {
-	case USB_ST_TRANSFERRED:
-tr_transferred:
-		if (xfer->error) {
-			NG_UBT_STAT_OERROR(sc->sc_stat);
-		} else {
-			NG_UBT_STAT_BYTES_SENT(sc->sc_stat, xfer->actlen);
-			NG_UBT_STAT_PCKTS_SENT(sc->sc_stat);
-		}
+		NG_NODE_REF(node);
+		usb2_transfer_start(sc->sc_xfer_if_0[1]); /* bulk in */
 
-	case USB_ST_SETUP:
-		offset = 0;
+		NG_NODE_REF(node);
+		usb2_transfer_start(sc->sc_xfer_if_0[2]); /* intr in */
 
-		for (n = 0; n < xfer->nframes; n++) {
+		mtx_unlock(&sc->sc_if_0_mtx);
 
-			NG_BT_MBUFQ_DEQUEUE(&sc->sc_scoq, m);
+		/*
+		 * Interface #1
+		 */
 
-			if (m) {
-				len = min(xfer->max_frame_size, m->m_pkthdr.len);
+		mtx_lock(&sc->sc_if_1_mtx);
 
-				usb2_m_copy_in(xfer->frbuffers, offset, m, 0, len);
+		NG_NODE_REF(node);
+		usb2_transfer_start(sc->sc_xfer_if_1[0]); /* isoc in #1 */
 
-				NG_FREE_M(m);
+		NG_NODE_REF(node);
+		usb2_transfer_start(sc->sc_xfer_if_1[1]); /* isoc in #2 */
 
-				xfer->frlengths[n] = len;
-				offset += len;
-			} else {
-				xfer->frlengths[n] = 0;
-			}
-		}
+		mtx_unlock(&sc->sc_if_1_mtx);
+	}
 
-		usb2_start_hardware(xfer);
-		return;
+ 	/* Start outgoing control transfer */
+	if (task_flags & UBT_FLAG_T_START_CTRL) {
+		mtx_lock(&sc->sc_if_0_mtx);
+		NG_NODE_REF(node);
+		usb2_transfer_start(sc->sc_xfer_if_0[3]);
+		mtx_unlock(&sc->sc_if_0_mtx);
+	}
 
-	default:			/* Error */
-		if (xfer->error == USB_ERR_CANCELLED) {
-			/* ignore */
-			return;
-		}
-		goto tr_transferred;
+	/* Start outgoing bulk transfer */
+	if (task_flags & UBT_FLAG_T_START_BULK) {
+		mtx_lock(&sc->sc_if_0_mtx);
+		NG_NODE_REF(node);
+		usb2_transfer_start(sc->sc_xfer_if_0[0]);
+		mtx_unlock(&sc->sc_if_0_mtx);
 	}
-}
 
+	/* Start outgoing isoc transfers */
+	if (task_flags & UBT_FLAG_T_START_ISOC) {
+                mtx_lock(&sc->sc_if_1_mtx);
+
+                NG_NODE_REF(node);
+                usb2_transfer_start(sc->sc_xfer_if_1[2]); /* isoc out #1 */
+
+                NG_NODE_REF(node);
+                usb2_transfer_start(sc->sc_xfer_if_1[3]); /* isoc out #2 */
+
+                mtx_unlock(&sc->sc_if_1_mtx);
+	}
+
+	NG_NODE_UNREF(node);
+} /* ubt_task */
+
 /****************************************************************************
  ****************************************************************************
  **                        Netgraph specific
@@ -1319,206 +1530,148 @@
  ****************************************************************************/
 
 /*
- * Netgraph node constructor.
- * Do not allow to create node of this type:
+ * Netgraph node constructor. Do not allow to create node of this type.
+ * Netgraph context.
  */
 
 static int
 ng_ubt_constructor(node_p node)
 {
 	return (EINVAL);
-}
+} /* ng_ubt_constructor */
 
 /*
- * Netgraph node destructor.
- * Destroy node only when device has been detached:
+ * Netgraph node destructor. Destroy node only when device has been detached.
+ * Netgraph context.
  */
 
 static int
 ng_ubt_shutdown(node_p node)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(node);
+	struct ubt_softc	*sc = NG_NODE_PRIVATE(node);
 
 	/* Let old node go */
 	NG_NODE_SET_PRIVATE(node, NULL);
 	NG_NODE_UNREF(node);
 
-	if (sc == NULL) {
+	if (sc == NULL)
 		goto done;
-	}
-	mtx_lock(&sc->sc_mtx);
-
+	
 	/* Create Netgraph node */
 	if (ng_make_node_common(&typestruct, &sc->sc_node) != 0) {
-		printf("%s: Could not create Netgraph node\n",
-		    sc->sc_name);
+		printf("%s: Could not create Netgraph node\n", sc->sc_name);
 		sc->sc_node = NULL;
 		goto done;
 	}
+
 	/* Name node */
 	if (ng_name_node(sc->sc_node, sc->sc_name) != 0) {
-		printf("%s: Could not name Netgraph node\n",
-		    sc->sc_name);
+		printf("%s: Could not name Netgraph node\n", sc->sc_name);
 		NG_NODE_UNREF(sc->sc_node);
 		sc->sc_node = NULL;
 		goto done;
 	}
+
 	NG_NODE_SET_PRIVATE(sc->sc_node, sc);
 	NG_NODE_FORCE_WRITER(sc->sc_node);
 
+	UBT_MBUFQ_LOCK(sc);
+	sc->sc_task_flags = 0;
+	UBT_MBUFQ_UNLOCK(sc);
 done:
-	if (sc) {
-		mtx_unlock(&sc->sc_mtx);
-	}
 	return (0);
-}
+} /* ng_ubt_shutdown */
 
 /*
- * Create new hook.
- * There can only be one.
+ * Create new hook. There can only be one.
+ * Netgraph context.
  */
 
 static int
 ng_ubt_newhook(node_p node, hook_p hook, char const *name)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(node);
-	int error = 0;
+	struct ubt_softc	*sc = NG_NODE_PRIVATE(node);
 
-	if (strcmp(name, NG_UBT_HOOK) != 0) {
+	if (strcmp(name, NG_UBT_HOOK) != 0)
 		return (EINVAL);
-	}
-	mtx_lock(&sc->sc_mtx);
 
-	if (sc->sc_hook != NULL) {
-		error = EISCONN;
-	} else {
-		sc->sc_hook = hook;
-	}
+	if (sc->sc_hook != NULL)
+		return (EISCONN);
 
-	mtx_unlock(&sc->sc_mtx);
+	sc->sc_hook = hook;
 
-	return (error);
-}
+	return (0);
+} /* ng_ubt_newhook */
 
 /*
- * Connect hook.
- * Start incoming USB transfers
+ * Connect hook. Start incoming USB transfers.
+ * Netgraph context.
  */
 
 static int
 ng_ubt_connect(hook_p hook)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+	struct ubt_softc	*sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
 
 	NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
 
-	mtx_lock(&sc->sc_mtx);
+	UBT_MBUFQ_LOCK(sc);
+	ubt_task_schedule(sc, UBT_FLAG_T_START_ALL);
+	UBT_MBUFQ_UNLOCK(sc);
 
-	sc->sc_flags |= (UBT_FLAG_READ_STALL |
-	    UBT_FLAG_WRITE_STALL |
-	    UBT_FLAG_INTR_STALL);
-
-	/* start intr transfer */
-	usb2_transfer_start(sc->sc_xfer_if_0[2]);
-
-	/* start bulk-in transfer */
-	usb2_transfer_start(sc->sc_xfer_if_0[1]);
-
-	/* start bulk-out transfer */
-	usb2_transfer_start(sc->sc_xfer_if_0[0]);
-
-	/* start control-out transfer */
-	usb2_transfer_start(sc->sc_xfer_if_0[3]);
-#if 0
-	XXX can enable this XXX
-
-	/* start isoc-in transfer */
-	     usb2_transfer_start(sc->sc_xfer_if_1[0]);
-
-	usb2_transfer_start(sc->sc_xfer_if_1[1]);
-
-	/* start isoc-out transfer */
-	usb2_transfer_start(sc->sc_xfer_if_1[2]);
-	usb2_transfer_start(sc->sc_xfer_if_1[3]);
-#endif
-
-	mtx_unlock(&sc->sc_mtx);
-
 	return (0);
-}
+} /* ng_ubt_connect */
 
 /*
- * Disconnect hook
+ * Disconnect hook.
+ * Netgraph context.
  */
 
 static int
 ng_ubt_disconnect(hook_p hook)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
-	int error = 0;
+	struct ubt_softc	*sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
 
-	if (sc != NULL) {
+	if (sc == NULL)
+		return (0);
 
-		mtx_lock(&sc->sc_mtx);
+	if (hook != sc->sc_hook)
+		return (EINVAL);
 
-		if (hook != sc->sc_hook) {
-			error = EINVAL;
-		} else {
+	sc->sc_hook = NULL;
 
-			/* stop intr transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_0[2]);
-			usb2_transfer_stop(sc->sc_xfer_if_0[6]);
+	UBT_MBUFQ_LOCK(sc);
 
-			/* stop bulk-in transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_0[1]);
-			usb2_transfer_stop(sc->sc_xfer_if_0[5]);
+	/* Drain queues */
+	NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq);
+	NG_BT_MBUFQ_DRAIN(&sc->sc_aclq);
+	NG_BT_MBUFQ_DRAIN(&sc->sc_scoq);
 
-			/* stop bulk-out transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_0[0]);
-			usb2_transfer_stop(sc->sc_xfer_if_0[4]);
+	/* Kick off task to stop all USB xfers */
+	ubt_task_schedule(sc, UBT_FLAG_T_STOP_ALL);
 
-			/* stop control transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_0[3]);
+	UBT_MBUFQ_UNLOCK(sc);
 
-			/* stop isoc-in transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_1[0]);
-			usb2_transfer_stop(sc->sc_xfer_if_1[1]);
-
-			/* stop isoc-out transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_1[2]);
-			usb2_transfer_stop(sc->sc_xfer_if_1[3]);
-
-			/* cleanup queues */
-			NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq);
-			NG_BT_MBUFQ_DRAIN(&sc->sc_aclq);
-			NG_BT_MBUFQ_DRAIN(&sc->sc_scoq);
-			NG_BT_MBUFQ_DRAIN(&sc->sc_sciq);
-
-			sc->sc_hook = NULL;
-		}
-
-		mtx_unlock(&sc->sc_mtx);
-	}
-	return (error);
-}
-
+	return (0);
+} /* ng_ubt_disconnect */
+	
 /*
- * Process control message
+ * Process control message.
+ * Netgraph context.
  */
 
 static int
 ng_ubt_rcvmsg(node_p node, item_p item, hook_p lasthook)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(node);
-	struct ng_mesg *msg = NULL, *rsp = NULL;
-	struct ng_bt_mbufq *q = NULL;
-	int error = 0, queue, qlen;
+	struct ubt_softc	*sc = NG_NODE_PRIVATE(node);
+	struct ng_mesg		*msg, *rsp = NULL;
+	struct ng_bt_mbufq	*q;
+	int			error = 0, queue, qlen;
 
 	if (sc == NULL) {
 		NG_FREE_ITEM(item);
 		return (EHOSTDOWN);
 	}
-	mtx_lock(&sc->sc_mtx);
 
 	NGI_GET_MSG(item, msg);
 
@@ -1527,25 +1680,29 @@
 		switch (msg->header.cmd) {
 		case NGM_TEXT_STATUS:
 			NG_MKRESPONSE(rsp, msg, NG_TEXTRESPONSE, M_NOWAIT);
-			if (rsp == NULL)
+			if (rsp == NULL) {
 				error = ENOMEM;
-			else
-				snprintf(rsp->data, NG_TEXTRESPONSE,
-				    "Hook: %s\n" \
-				    "Flags: %#x\n" \
-				    "Debug: %d\n" \
-				    "CMD queue: [have:%d,max:%d]\n" \
-				    "ACL queue: [have:%d,max:%d]\n" \
-				    "SCO queue: [have:%d,max:%d]",
-				    (sc->sc_hook != NULL) ? NG_UBT_HOOK : "",
-				    sc->sc_flags,
-				    sc->sc_debug,
-				    NG_BT_MBUFQ_LEN(&sc->sc_cmdq),
-				    sc->sc_cmdq.maxlen,
-				    NG_BT_MBUFQ_LEN(&sc->sc_aclq),
-				    sc->sc_aclq.maxlen,
-				    NG_BT_MBUFQ_LEN(&sc->sc_scoq),
-				    sc->sc_scoq.maxlen);
+				break;
+			}
+
+			snprintf(rsp->data, NG_TEXTRESPONSE,
+				"Hook: %s\n" \
+				"Flags: %#x\n" \
+				"Task flags: %#x\n" \
+				"Debug: %d\n" \
+				"CMD queue: [have:%d,max:%d]\n" \
+				"ACL queue: [have:%d,max:%d]\n" \
+				"SCO queue: [have:%d,max:%d]",
+				(sc->sc_hook != NULL) ? NG_UBT_HOOK:"",
+				sc->sc_flags,
+				sc->sc_task_flags,
+				sc->sc_debug,
+				sc->sc_cmdq.len,
+				sc->sc_cmdq.maxlen,
+				sc->sc_aclq.len,
+				sc->sc_aclq.maxlen,
+				sc->sc_scoq.len,
+				sc->sc_scoq.maxlen);
 			break;
 
 		default:
@@ -1557,59 +1714,54 @@
 	case NGM_UBT_COOKIE:
 		switch (msg->header.cmd) {
 		case NGM_UBT_NODE_SET_DEBUG:
-			if (msg->header.arglen != sizeof(ng_ubt_node_debug_ep))
+			if (msg->header.arglen != sizeof(ng_ubt_node_debug_ep)){
 				error = EMSGSIZE;
-			else
-				sc->sc_debug =
-				    *((ng_ubt_node_debug_ep *) (msg->data));
+				break;
+			}
+
+			sc->sc_debug = *((ng_ubt_node_debug_ep *) (msg->data));
 			break;
 
 		case NGM_UBT_NODE_GET_DEBUG:
 			NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_debug_ep),
 			    M_NOWAIT);
-			if (rsp == NULL)
+			if (rsp == NULL) {
 				error = ENOMEM;
-			else
-				*((ng_ubt_node_debug_ep *) (rsp->data)) =
-				    sc->sc_debug;
+				break;
+			}
+
+			*((ng_ubt_node_debug_ep *) (rsp->data)) = sc->sc_debug;
 			break;
 
 		case NGM_UBT_NODE_SET_QLEN:
-			if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep))
+			if (msg->header.arglen != sizeof(ng_ubt_node_qlen_ep)) {
 				error = EMSGSIZE;
-			else {
-				queue = ((ng_ubt_node_qlen_ep *)
-				    (msg->data))->queue;
-				qlen = ((ng_ubt_node_qlen_ep *)
-				    (msg->data))->qlen;
+				break;
+			}
 
-				if (qlen <= 0) {
-					error = EINVAL;
-					break;
-				}
-				switch (queue) {
-				case NGM_UBT_NODE_QUEUE_CMD:
-					q = &sc->sc_cmdq;
-					break;
+			queue = ((ng_ubt_node_qlen_ep *) (msg->data))->queue;
+			qlen = ((ng_ubt_node_qlen_ep *) (msg->data))->qlen;
 
-				case NGM_UBT_NODE_QUEUE_ACL:
-					q = &sc->sc_aclq;
-					break;
+			switch (queue) {
+			case NGM_UBT_NODE_QUEUE_CMD:
+				q = &sc->sc_cmdq;
+				break;
 
-				case NGM_UBT_NODE_QUEUE_SCO:
-					q = &sc->sc_scoq;
-					break;
+			case NGM_UBT_NODE_QUEUE_ACL:
+				q = &sc->sc_aclq;
+				break;
 
-				default:
-					q = NULL;
-					error = EINVAL;
-					break;
-				}
+			case NGM_UBT_NODE_QUEUE_SCO:
+				q = &sc->sc_scoq;
+				break;
 
-				if (q != NULL) {
-					q->maxlen = qlen;
-				}
+			default:
+				error = EINVAL;
+				goto done;
+				/* NOT REACHED */
 			}
+
+			q->maxlen = qlen;
 			break;
 
 		case NGM_UBT_NODE_GET_QLEN:
@@ -1617,7 +1769,9 @@
 				error = EMSGSIZE;
 				break;
 			}
+
 			queue = ((ng_ubt_node_qlen_ep *) (msg->data))->queue;
+
 			switch (queue) {
 			case NGM_UBT_NODE_QUEUE_CMD:
 				q = &sc->sc_cmdq;
@@ -1632,39 +1786,36 @@
 				break;
 
 			default:
-				q = NULL;
 				error = EINVAL;
+				goto done;
+				/* NOT REACHED */
+			}
+
+			NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_qlen_ep),
+				M_NOWAIT);
+			if (rsp == NULL) {
+				error = ENOMEM;
 				break;
 			}
 
-			if (q != NULL) {
-				NG_MKRESPONSE(rsp, msg,
-				    sizeof(ng_ubt_node_qlen_ep), M_NOWAIT);
-				if (rsp == NULL) {
-					error = ENOMEM;
-					break;
-				}
-				((ng_ubt_node_qlen_ep *) (rsp->data))->queue =
-				    queue;
-				((ng_ubt_node_qlen_ep *) (rsp->data))->qlen =
-				    q->maxlen;
-			}
+			((ng_ubt_node_qlen_ep *) (rsp->data))->queue = queue;
+			((ng_ubt_node_qlen_ep *) (rsp->data))->qlen = q->maxlen;
 			break;
 
 		case NGM_UBT_NODE_GET_STAT:
 			NG_MKRESPONSE(rsp, msg, sizeof(ng_ubt_node_stat_ep),
 			    M_NOWAIT);
-			if (rsp == NULL)
+			if (rsp == NULL) {
 				error = ENOMEM;
-			else {
-				bcopy(&sc->sc_stat, rsp->data,
-				    sizeof(ng_ubt_node_stat_ep));
+				break;
 			}
+
+			bcopy(&sc->sc_stat, rsp->data,
+				sizeof(ng_ubt_node_stat_ep));
 			break;
 
 		case NGM_UBT_NODE_RESET_STAT:
-
-			NG_UBT_STAT_RESET(sc->sc_stat);
+			UBT_STAT_RESET(sc);
 			break;
 
 		default:
@@ -1677,90 +1828,172 @@
 		error = EINVAL;
 		break;
 	}
-
+done:
 	NG_RESPOND_MSG(error, node, item, rsp);
 	NG_FREE_MSG(msg);
 
-	mtx_unlock(&sc->sc_mtx);
-
 	return (error);
-}
+} /* ng_ubt_rcvmsg */
 
 /*
- * Process data
+ * Process data.
+ * Netgraph context.
  */
 
 static int
 ng_ubt_rcvdata(hook_p hook, item_p item)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
-	struct mbuf *m;
-	struct ng_bt_mbufq *q;
-	struct usb2_xfer *xfer;
-	int error = 0;
+	struct ubt_softc	*sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+	struct mbuf		*m;
+	struct ng_bt_mbufq	*q;
+	uint32_t		action;
+	int			error = 0;
 
 	if (sc == NULL) {
 		error = EHOSTDOWN;
 		goto done;
 	}
-	mtx_lock(&sc->sc_mtx);
 
 	if (hook != sc->sc_hook) {
 		error = EINVAL;
 		goto done;
 	}
-	/* deatch mbuf and get HCI frame type */
+
+	/* Deatch mbuf and get HCI frame type */
 	NGI_GET_M(item, m);
 
-	/* process HCI frame */
+	/*
+	 * Minimal size of the HCI frame is 4 bytes: 1 byte frame type,
+	 * 2 bytes connection handle and at least 1 byte of length.
+	 * Panic on data frame that has size smaller than 4 bytes (it
+	 * should not happen)
+	 */
+
+	if (m->m_pkthdr.len < 4)
+		panic("HCI frame size is too small! pktlen=%d\n",
+			m->m_pkthdr.len);
+
+	/* Process HCI frame */
 	switch (*mtod(m, uint8_t *)) {	/* XXX call m_pullup ? */
 	case NG_HCI_CMD_PKT:
-		xfer = sc->sc_xfer_if_0[3];
+		if (m->m_pkthdr.len - 1 > UBT_CTRL_BUFFER_SIZE)
+			panic("HCI command frame size is too big! " \
+				"buffer size=%zd, packet len=%d\n",
+				UBT_CTRL_BUFFER_SIZE, m->m_pkthdr.len);
+
 		q = &sc->sc_cmdq;
+		action = UBT_FLAG_T_START_CTRL;
 		break;
 
 	case NG_HCI_ACL_DATA_PKT:
-		xfer = sc->sc_xfer_if_0[0];
+		if (m->m_pkthdr.len - 1 > UBT_BULK_WRITE_BUFFER_SIZE)
+			panic("ACL data frame size is too big! " \
+				"buffer size=%d, packet len=%d\n",
+				UBT_BULK_WRITE_BUFFER_SIZE, m->m_pkthdr.len);
+
 		q = &sc->sc_aclq;
+		action = UBT_FLAG_T_START_BULK;
 		break;
 
 	case NG_HCI_SCO_DATA_PKT:
-		xfer = NULL;
+#if 0		/* XXX FIXME - not yet */
 		q = &sc->sc_scoq;
+		action = UBT_FLAG_T_START_ISOC;
 		break;
+#else
+		NG_FREE_M(m);
+		goto done;
+#endif
 
 	default:
-		NG_UBT_ERR(sc, "Dropping unsupported HCI frame, "
-		    "type=0x%02x, pktlen=%d\n",
-		    *mtod(m, uint8_t *),
-		    m->m_pkthdr.len);
+		UBT_ERR(sc, "Dropping unsupported HCI frame, type=0x%02x, " \
+			"pktlen=%d\n", *mtod(m, uint8_t *), m->m_pkthdr.len);
 
 		NG_FREE_M(m);
 		error = EINVAL;
 		goto done;
+		/* NOT REACHED */
 	}
 
-	/* loose frame type, if required */
-	if (!(sc->sc_flags & UBT_NEED_FRAME_TYPE)) {
-		m_adj(m, sizeof(uint8_t));
-	}
+	UBT_MBUFQ_LOCK(sc);
 	if (NG_BT_MBUFQ_FULL(q)) {
-		NG_UBT_ERR(sc, "Dropping HCI frame 0x%02x, len=%d. "
-		    "Queue full\n", *mtod(m, uint8_t *),
-		    m->m_pkthdr.len);
+		NG_BT_MBUFQ_DROP(q);
+		UBT_MBUFQ_UNLOCK(sc);
+		
+		UBT_ERR(sc, "Dropping HCI frame 0x%02x, len=%d. Queue full\n",
+			*mtod(m, uint8_t *), m->m_pkthdr.len);
+
 		NG_FREE_M(m);
 	} else {
+		/* Loose HCI packet type, enqueue mbuf and kick off task */
+		m_adj(m, sizeof(uint8_t));
 		NG_BT_MBUFQ_ENQUEUE(q, m);
-	}
+		ubt_task_schedule(sc, action);
 
-	if (xfer) {
-		usb2_transfer_start(xfer);
+		UBT_MBUFQ_UNLOCK(sc);
 	}
 done:
 	NG_FREE_ITEM(item);
 
-	if (sc) {
-		mtx_unlock(&sc->sc_mtx);
+	return (error);
+} /* ng_ubt_rcvdata */
+
+/****************************************************************************
+ ****************************************************************************
+ **                              Module
+ ****************************************************************************
+ ****************************************************************************/
+
+/*
+ * Load/Unload the driver module
+ */
+
+static int
+ubt_modevent(module_t mod, int event, void *data)
+{
+	int error;
+
+	switch (event) {
+	case MOD_LOAD:
+		error = ng_newtype(&typestruct);
+		if (error != 0)
+			printf("%s: Could not register Netgraph node type, " \
+				"error=%d\n", NG_UBT_NODE_TYPE, error);
+		break;
+
+	case MOD_UNLOAD:
+		error = ng_rmtype(&typestruct);
+		break;
+
+	default:
+		error = EOPNOTSUPP;
+		break;
 	}
+
 	return (error);
-}
+} /* ubt_modevent */
+
+static devclass_t	ubt_devclass;
+
+static device_method_t	ubt_methods[] =
+{
+	DEVMETHOD(device_probe,	ubt_probe),
+	DEVMETHOD(device_attach, ubt_attach),
+	DEVMETHOD(device_detach, ubt_detach),
+	{ 0, 0 }
+};
+
+static driver_t		ubt_driver =
+{
+	.name =	   "ubt",
+	.methods = ubt_methods,
+	.size =	   sizeof(struct ubt_softc),
+};
+
+DRIVER_MODULE(ng_ubt, ushub, ubt_driver, ubt_devclass, ubt_modevent, 0);
+MODULE_VERSION(ng_ubt, NG_BLUETOOTH_VERSION);
+MODULE_DEPEND(ng_ubt, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION);
+MODULE_DEPEND(ng_ubt, ng_hci, NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION, NG_BLUETOOTH_VERSION);
+MODULE_DEPEND(ng_ubt, usb2_bluetooth, 1, 1, 1);
+MODULE_DEPEND(ng_ubt, usb2_core, 1, 1, 1);
+
Index: ng_ubt2_var.h
===================================================================
--- ng_ubt2_var.h	(revision 186800)
+++ ng_ubt2_var.h	(working copy)
@@ -3,7 +3,7 @@
  */
 
 /*-
- * Copyright (c) 2001-2002 Maksim Yevmenkin <m_evmenkin at yahoo.com>
+ * Copyright (c) 2001-2009 Maksim Yevmenkin <m_evmenkin at yahoo.com>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -32,95 +32,94 @@
  */
 
 #ifndef _NG_UBT_VAR_H_
-#define	_NG_UBT_VAR_H_
+#define	_NG_UBT_VAR_H_	1
 
-/* pullup wrapper */
-#define	NG_UBT_M_PULLUP(m, s) \
-	do { \
-		if ((m)->m_len < (s)) \
-			(m) = m_pullup((m), (s)); \
-		if ((m) == NULL) { \
-			NG_UBT_ALERT("%s: %s - m_pullup(%d) failed\n", \
-				__func__, sc->sc_name, (s)); \
-		} \
-	} while (0)
-
 /* Debug printf's */
-#define	NG_UBT_DEBUG(level, sc, fmt, ...) do { \
-    if ((sc)->sc_debug >= (level)) { \
-        printf("%s:%s:%d: " fmt, (sc)->sc_name, \
-	       __FUNCTION__, __LINE__,## __VA_ARGS__); \
-    } \
+#define	UBT_DEBUG(level, sc, fmt, ...)				\
+do {								\
+	if ((sc)->sc_debug >= (level))				\
+		printf("%s:%s:%d: " fmt, (sc)->sc_name,		\
+			__FUNCTION__, __LINE__,## __VA_ARGS__);	\
 } while (0)
 
-#define	NG_UBT_ALERT(...) NG_UBT_DEBUG(NG_UBT_ALERT_LEVEL, __VA_ARGS__)
-#define	NG_UBT_ERR(...)   NG_UBT_DEBUG(NG_UBT_ERR_LEVEL, __VA_ARGS__)
-#define	NG_UBT_WARN(...)  NG_UBT_DEBUG(NG_UBT_WARN_LEVEL, __VA_ARGS__)
-#define	NG_UBT_INFO(...)  NG_UBT_DEBUG(NG_UBT_INFO_LEVEL, __VA_ARGS__)
+#define	UBT_ALERT(...)		UBT_DEBUG(NG_UBT_ALERT_LEVEL, __VA_ARGS__)
+#define	UBT_ERR(...)		UBT_DEBUG(NG_UBT_ERR_LEVEL, __VA_ARGS__)
+#define	UBT_WARN(...)		UBT_DEBUG(NG_UBT_WARN_LEVEL, __VA_ARGS__)
+#define	UBT_INFO(...)		UBT_DEBUG(NG_UBT_INFO_LEVEL, __VA_ARGS__)
 
+#define UBT_MBUFQ_LOCK(sc)	mtx_lock(&(sc)->sc_mbufq_mtx)
+#define UBT_MBUFQ_UNLOCK(sc)	mtx_unlock(&(sc)->sc_mbufq_mtx)
+
 /* Bluetooth USB control request type */
 #define	UBT_HCI_REQUEST		0x20
 #define	UBT_DEFAULT_QLEN	12
 
 /* Bluetooth USB defines */
-#define	UBT_IF_0_N_TRANSFER  7		/* units */
-#define	UBT_IF_1_N_TRANSFER  4		/* units */
-#define	UBT_ISOC_NFRAMES    25		/* units */
+#define	UBT_IF_0_N_TRANSFER	7	/* units */
+#define	UBT_IF_1_N_TRANSFER	4	/* units */
+#define	UBT_ISOC_NFRAMES	25	/* units */
 
 /* USB device softc structure */
 struct ubt_softc {
+	uint8_t			sc_name[16];
+
 	/* State */
-	ng_ubt_node_debug_ep sc_debug;	/* debug level */
-	uint32_t sc_flags;		/* device flags */
-#define	UBT_NEED_FRAME_TYPE	(1 << 0)/* device required frame type */
-#define	UBT_HAVE_FRAME_TYPE UBT_NEED_FRAME_TYPE
-#define	UBT_FLAG_READ_STALL     (1 << 1)/* read transfer has stalled */
-#define	UBT_FLAG_WRITE_STALL    (1 << 2)/* write transfer has stalled */
-#define	UBT_FLAG_INTR_STALL     (1 << 3)/* interrupt transfer has stalled */
+	ng_ubt_node_debug_ep	sc_debug;	/* debug level */
 
-	ng_ubt_node_stat_ep sc_stat;	/* statistic */
-#define	NG_UBT_STAT_PCKTS_SENT(s)	(s).pckts_sent ++
-#define	NG_UBT_STAT_BYTES_SENT(s, n)	(s).bytes_sent += (n)
-#define	NG_UBT_STAT_PCKTS_RECV(s)	(s).pckts_recv ++
-#define	NG_UBT_STAT_BYTES_RECV(s, n)	(s).bytes_recv += (n)
-#define	NG_UBT_STAT_OERROR(s)		(s).oerrors ++
-#define	NG_UBT_STAT_IERROR(s)		(s).ierrors ++
-#define	NG_UBT_STAT_RESET(s)		bzero(&(s), sizeof((s)))
+	uint32_t		sc_flags;	/* device flags */
+#define	UBT_FLAG_READ_STALL	(1 << 0)	/* read transfer has stalled */
+#define	UBT_FLAG_WRITE_STALL	(1 << 1)	/* write transfer has stalled */
+#define	UBT_FLAG_INTR_STALL	(1 << 2)	/* inter transfer has stalled */
 
-	uint8_t	sc_name[16];
+	ng_ubt_node_stat_ep	sc_stat;	/* statistic */
+#define	UBT_STAT_PCKTS_SENT(sc)		(sc)->sc_stat.pckts_sent ++
+#define	UBT_STAT_BYTES_SENT(sc, n)	(sc)->sc_stat.bytes_sent += (n)
+#define	UBT_STAT_PCKTS_RECV(sc)		(sc)->sc_stat.pckts_recv ++
+#define	UBT_STAT_BYTES_RECV(sc, n)	(sc)->sc_stat.bytes_recv += (n)
+#define	UBT_STAT_OERROR(sc)		(sc)->sc_stat.oerrors ++
+#define	UBT_STAT_IERROR(sc)		(sc)->sc_stat.ierrors ++
+#define	UBT_STAT_RESET(sc)	bzero(&(sc)->sc_stat, sizeof((sc)->sc_stat))
 
-	struct mtx sc_mtx;
-
 	/* USB device specific */
-	struct usb2_xfer *sc_xfer_if_0[UBT_IF_0_N_TRANSFER];
-	struct usb2_xfer *sc_xfer_if_1[UBT_IF_1_N_TRANSFER];
+	struct mtx		sc_if_0_mtx;	/* if #0 lock */
+	struct usb2_xfer	*sc_xfer_if_0[UBT_IF_0_N_TRANSFER];
 
-	/* Interrupt pipe (HCI events) */
-	struct mbuf *sc_intr_buffer;	/* interrupt buffer */
+	struct mtx		sc_if_1_mtx;	/* if #1 lock */
+	struct usb2_xfer	*sc_xfer_if_1[UBT_IF_1_N_TRANSFER];
 
-	/* Control pipe (HCI commands) */
-	struct ng_bt_mbufq sc_cmdq;	/* HCI command queue */
-#define	UBT_CTRL_BUFFER_SIZE (sizeof(ng_hci_cmd_pkt_t) + NG_HCI_CMD_PKT_SIZE)
+	struct mtx		sc_mbufq_mtx;	/* lock for all queues */
 
-	/* Bulk in pipe (ACL data) */
-	struct mbuf *sc_bulk_in_buffer;	/* bulk-in buffer */
+	/* HCI commands */
+	struct ng_bt_mbufq	sc_cmdq;	/* HCI command queue */
+#define	UBT_CTRL_BUFFER_SIZE	(sizeof(ng_hci_cmd_pkt_t) + NG_HCI_CMD_PKT_SIZE)
+#define	UBT_INTR_BUFFER_SIZE	(MCLBYTES-1)	/* reserve 1 byte for ID-tag */
 
-	/* Bulk out pipe (ACL data) */
-	struct ng_bt_mbufq sc_aclq;	/* ACL data queue */
-#define	UBT_BULK_READ_BUFFER_SIZE (MCLBYTES-1)	/* reserve one byte for ID-tag */
+	/* ACL data */
+	struct ng_bt_mbufq	sc_aclq;	/* ACL data queue */
+#define	UBT_BULK_READ_BUFFER_SIZE (MCLBYTES-1)	/* reserve 1 byte for ID-tag */
 #define	UBT_BULK_WRITE_BUFFER_SIZE (MCLBYTES)
 
-	/* Isoc. out pipe (ACL data) */
-	struct ng_bt_mbufq sc_scoq;	/* SCO data queue */
+	/* SCO data */
+	struct ng_bt_mbufq	sc_scoq;	/* SCO data queue */
+	struct mbuf		*sc_isoc_in_buffer; /* SCO reassembly buffer */
 
-	/* Isoc. in pipe (ACL data) */
-	struct ng_bt_mbufq sc_sciq;	/* SCO data queue */
-
 	/* Netgraph specific */
-	node_p	sc_node;		/* pointer back to node */
-	hook_p	sc_hook;		/* upstream hook */
+	node_p			sc_node;	/* pointer back to node */
+	hook_p			sc_hook;	/* upstream hook */
+
+	/* Glue */
+	uint32_t		sc_task_flags;	/* task flags */
+#define UBT_FLAG_T_PENDING	(1 << 0)	/* task pending */
+#define UBT_FLAG_T_STOP_ALL	(1 << 1)	/* stop all xfers */
+#define UBT_FLAG_T_START_ALL	(1 << 2)	/* start all read xfers */
+#define UBT_FLAG_T_START_CTRL	(1 << 3)	/* start control xfer (write) */
+#define UBT_FLAG_T_START_BULK	(1 << 4)	/* start bulk xfer (write) */
+#define UBT_FLAG_T_START_ISOC	(1 << 5)	/* start isoc xfer (write) */
+
+	struct task		sc_task;
 };
 typedef struct ubt_softc ubt_softc_t;
 typedef struct ubt_softc *ubt_softc_p;
 
-#endif					/* ndef _NG_UBT_VAR_H_ */
+#endif /* ndef _NG_UBT_VAR_H_ */
+


More information about the freebsd-bluetooth mailing list