USB2: ng_ubt2 patch

Maksim Yevmenkin maksim.yevmenkin at gmail.com
Mon Jan 19 14:21:59 PST 2009


hello,

[...]

>> >> > 1) Maybe you just want to merge together all the USB config structures
>> >> > into one?
>> >>
>> >> i guess, i could. since isoc transfers are going over different (from
>> >> control, interrupt and bulk transfers) interface, i kept original code
>> >> that had two separate usb2_config structures (i.e. one per each
>> >> interface). for the same reason, i added separate lock for interface 1
>> >> (where isoc transfers are). i figured, due to the nature of isoc
>> >> transfers, this lock will be grabbed a lot more often. it seemed like
>> >> a good idea to use different lock so there would be less contention
>> >> with interface 0 lock. but then again, may be i just full of it :) and
>> >> it really does not matter.
>> >>
>> >> could you please tell me what is the reason for merging usb2_config
>> >> structures? is it better for usb2?
>> >
>> > It will save some memory allocations.
>>
>> i will see what i can do.

ok, as per your request, i have merged two usb2_config structures into
one  (see attached patch)

[...]

> If isochronous is being used, then I recommend you have two transfers running
> at any time in both directions. If there is nothing to send you send zero
> length frames. Else you end up with glitches in the isochronous stream when
> you change from one transfer to the other! Both isochronous transfers should
> be queued all the time!

ok, i've made this change as well. newer code queues and keeps both
read and both write isoc. transfers running all the time.

the attached patch seems to work (there was a reference counting
problem in the previous version of the patch). i also tortured it a
bit, i.e. stop the stack and/or yank the device while stack it active
and sending lots of data (i used l2ping -f to create lots of
activity).

i think, it can be committed to -head now. it should be stable enough
for early adopters of usb2 and bluetooth :) so, please let me know how
should i proceed at this point?

do i

1) commit it to head myself

or

2) wait for you commit it into your cvs/scv/p4/etc. branch and then
merge it into -head

thanks,
max
-------------- next part --------------
Index: ng_ubt2.c
===================================================================
--- ng_ubt2.c	(revision 187313)
+++ 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_mtx[0] - 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_mtx[1] - 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 a 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 stuff 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 the 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 the 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 the outgoing queues and task flags is controlled by the
+ * sc_mbufq_mtx lock. It is an 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 pointer 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 and pointer is valid.
+ */
+
 #include <dev/usb2/include/usb2_devid.h>
 #include <dev/usb2/include/usb2_standard.h>
 #include <dev/usb2/include/usb2_mfunc.h>
@@ -44,8 +107,11 @@
 #include <dev/usb2/core/usb2_lookup.h>
 #include <dev/usb2/core/usb2_util.h>
 #include <dev/usb2/core/usb2_busdma.h>
+#include <dev/usb2/core/usb2_process.h>
+#include <dev/usb2/core/usb2_transfer.h>
 
 #include <sys/mbuf.h>
+#include <sys/taskqueue.h>
 
 #include <netgraph/ng_message.h>
 #include <netgraph/netgraph.h>
@@ -57,71 +123,57 @@
 #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, int);
+static task_fn_t	ubt_task;
+static void		ubt_xfer_start(ubt_softc_p, int);
 
-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 +216,267 @@
 		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 *, int);
 
-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.
+ *
+ * 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
+ *
+ * Interface 1 on the USB device must present the following endpoints
+ *	1) Isochronous IN endpoint to receive SCO data
+ *	2) Isochronous OUT endpoint to send SCO data
+ */
 
-/* USB config */
-static const struct usb2_config ubt_config_if_0[UBT_IF_0_N_TRANSFER] = {
+static const struct usb2_config		ubt_config[UBT_N_TRANSFER] =
+{
+	/*
+	 * Interface #0
+ 	 */
 
-	[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,
+	/* Outgoing bulk transfer - ACL packets */
+	[UBT_IF_0_BULK_DT_WR] = {
+		.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,
 	},
-
-	[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,
+	/* Incoming bulk transfer - ACL packets */
+	[UBT_IF_0_BULK_DT_RD] = {
+		.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,
 	},
-
-	[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,
+	/* Incoming interrupt transfer - HCI events */
+	[UBT_IF_0_INTR_DT_RD] = {
+		.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,
 	},
-
-	[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 */
+	/* Outgoing control transfer - HCI commands */
+	[UBT_IF_0_CTRL_DT_WR] = {
+		.type =		UE_CONTROL,
+		.endpoint =	0x00,	/* control pipe */
+		.direction =	UE_DIR_ANY,
+		.mh.bufsize =	UBT_CTRL_BUFFER_SIZE,
+		.mh.callback =	&ubt_ctrl_write_callback,
+		.mh.timeout =	5000,	/* 5 seconds */
 	},
-
-	[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 */
+	/* Outgoing control transfer to clear stall on outgoing bulk transfer */
+	[UBT_IF_0_BULK_CS_WR] = {
+		.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 */
 	},
-
-	[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 */
+	/* Outgoing control transfer to clear stall on incoming bulk transfer */
+	[UBT_IF_0_BULK_CS_RD] = {
+		.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 */
 	},
-
-	[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 */
+	/*
+	 * Outgoing control transfer to clear stall on incoming
+	 * interrupt transfer
+	 */
+	[UBT_IF_0_INTR_CS_RD] = {
+		.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] = {
+	/*
+	 * Interface #1
+ 	 */
 
-	[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,
+	/* Incoming isochronous transfer #1 - SCO packets */
+	[UBT_IF_1_ISOC_DT_RD1] = {
+		.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,
 	},
-
-	[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,
+	/* Incoming isochronous transfer #2 - SCO packets */
+	[UBT_IF_1_ISOC_DT_RD2] = {
+		.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,
 	},
-
-	[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,
+	/* Outgoing isochronous transfer #1 - SCO packets */
+	[UBT_IF_1_ISOC_DT_WR1] = {
+		.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,
 	},
-
-	[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,
+	/* Outgoing isochronous transfer #2 - SCO packets */
+	[UBT_IF_1_ISOC_DT_WR2] = {
+		.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));
 
+	/* 
+	 * 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 +484,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_mtx[0], "ubt if0", NULL, MTX_DEF | MTX_RECURSE);
+	mtx_init(&sc->sc_if_mtx[1], "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
@@ -523,1003 +520,1134 @@
 	 * configurations with different packet size.
 	 */
 
+	bzero(&sc->sc_xfer, sizeof(sc->sc_xfer));
+
 	/*
 	 * 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,
+			ubt_config, UBT_IF_0_N_TRANSFER,
+			sc->sc_node, &sc->sc_if_mtx[0])) {
+		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[UBT_IF_0_N_TRANSFER],
+			&ubt_config[UBT_IF_0_N_TRANSFER], UBT_IF_1_N_TRANSFER,
+			sc->sc_node, &sc->sc_if_mtx[1])) {
+		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);
+	node_p			node = sc->sc_node;
 
-	/* destroy Netgraph node */
+	/* Destroy Netgraph node */
+	if (node != NULL) {
+		sc->sc_node = NULL;
 
-	if (sc->sc_node != NULL) {
-		NG_NODE_SET_PRIVATE(sc->sc_node, NULL);
-		ng_rmnode_self(sc->sc_node);
-		sc->sc_node = NULL;
+		NG_NODE_SET_PRIVATE(node, NULL);
+		NG_NODE_REALLY_DIE(node);
+		NG_NODE_REF(node);
+		ng_rmnode_self(node);
 	}
-	/* free USB transfers, if any */
 
-	usb2_transfer_unsetup(sc->sc_xfer_if_0, UBT_IF_0_N_TRANSFER);
+	/* Free USB transfers, if any */
+	usb2_transfer_unsetup(sc->sc_xfer, UBT_N_TRANSFER);
 
-	usb2_transfer_unsetup(sc->sc_xfer_if_1, UBT_IF_1_N_TRANSFER);
+	if (node != NULL)
+		NG_NODE_UNREF(node);
 
-	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_mtx[0]);
+	mtx_destroy(&sc->sc_if_mtx[1]);
+	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);
 			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); /* 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[UBT_IF_0_INTR_CS_RD]);
+		else {
+			xfer->frlengths[0] = xfer->max_data_length;
+			usb2_start_hardware(xfer);
 		}
-		if (sc->sc_flags & UBT_FLAG_INTR_STALL) {
-			usb2_transfer_start(sc->sc_xfer_if_0[6]);
-			return;
-		}
-		xfer->frlengths[0] = xfer->max_data_length;
+		break;
 
-		usb2_start_hardware(xfer);
-		return;
+	default: /* Error */
+		if (xfer->error != USB_ERR_CANCELLED) {
+			UBT_WARN(sc, "interrupt transfer failed: %s\n",
+				usb2_errstr(xfer->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;
-
+			usb2_transfer_start(sc->sc_xfer[UBT_IF_0_INTR_CS_RD]);
+		} else
+			NG_NODE_UNREF(node); /* 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[UBT_IF_0_INTR_DT_RD];
+
 	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); /* 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) {
-
-		sc->sc_intr_buffer = 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;
-		}
-		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;
-		}
-		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);
-
-			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 HCI event frame size, "
-			    "length=%d, pktlen=%d\n",
-			    hdr->length, m->m_pkthdr.len);
-
-			NG_UBT_STAT_IERROR(sc->sc_stat);
-		}
+	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 */
 	}
-done:
-	if (m) {
-		NG_FREE_M(m);
-	}
-	/* start USB transfer if not already started */
 
-	usb2_transfer_start(sc->sc_xfer_if_0[2]);
+	m = NULL;
 
-	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 */
-
+		/* 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;
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
-		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;
-		}
 
-		max_len = (MCLBYTES - m->m_len);
+		/* Add HCI packet type */
+		*mtod(m, uint8_t *)= NG_HCI_ACL_DATA_PKT;
+		m->m_pkthdr.len = m->m_len = 1;
 
-		if (xfer->actlen > max_len) {
-			xfer->actlen = max_len;
-		}
-		ptr = ((uint8_t *)(m->m_data)) + m->m_len;
+		if (xfer->actlen > MCLBYTES - 1)
+			xfer->actlen = MCLBYTES - 1;
 
-		usb2_copy_out(xfer->frbuffers, 0, ptr, xfer->actlen);
-
+		usb2_copy_out(xfer->frbuffers, 0, mtod(m, uint8_t *) + 1,
+			xfer->actlen);
 		m->m_pkthdr.len += xfer->actlen;
 		m->m_len += xfer->actlen;
 
-		NG_UBT_INFO(sc, "got %d bytes from bulk-in "
-		    "pipe\n", xfer->actlen);
+		UBT_INFO(sc, "got %d bytes from bulk-in pipe\n",
+			xfer->actlen);
 
-		sc->sc_bulk_in_buffer = m;
+		/* 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");
 
-	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;
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
-		if (sc->sc_flags & UBT_FLAG_READ_STALL) {
-			usb2_transfer_start(sc->sc_xfer_if_0[5]);
-			return;
-		}
-		xfer->frlengths[0] = xfer->max_data_length;
 
-		usb2_start_hardware(xfer);
-		return;
+		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);
 
-	default:			/* Error */
-		if (xfer->error != USB_ERR_CANCELLED) {
-			/* try to clear stall first */
-			sc->sc_flags |= UBT_FLAG_READ_STALL;
-			usb2_transfer_start(sc->sc_xfer_if_0[5]);
+			UBT_STAT_IERROR(sc);
+			goto submit_next;
 		}
-		return;
 
-	}
-}
+		UBT_INFO(sc, "got complete ACL data packet, pktlen=%d, " \
+			"length=%d\n", m->m_pkthdr.len, len);
 
-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];
+		UBT_STAT_PCKTS_RECV(sc);
+		UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len);
 
-	if (usb2_clear_stall_callback(xfer, xfer_other)) {
-		DPRINTF("stall cleared\n");
-		sc->sc_flags &= ~UBT_FLAG_READ_STALL;
-		usb2_transfer_start(xfer_other);
-	}
-}
+		NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+		if (error != 0)
+			UBT_STAT_IERROR(sc);
 
-static void
-ubt_bulk_read_complete(node_p node, hook_p hook, void *arg1, int arg2)
-{
-	struct ubt_softc *sc = NG_NODE_PRIVATE(node);
-	struct mbuf *m;
-	ng_hci_acldata_pkt_t *hdr;
-	uint16_t len;
-	int error;
+		/* m == NULL at this point */
+		/* FALLTHOUGH */
 
-	if (sc == NULL) {
-		return;
-	}
-	mtx_lock(&sc->sc_mtx);
+	case USB_ST_SETUP:
+submit_next:
+		NG_FREE_M(m); /* checks for m != NULL */
 
-	m = sc->sc_bulk_in_buffer;
-
-	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;
+		if (sc->sc_flags & UBT_FLAG_READ_STALL)
+			usb2_transfer_start(sc->sc_xfer[UBT_IF_0_BULK_CS_RD]);
+		else {
+			xfer->frlengths[0] = xfer->max_data_length;
+			usb2_start_hardware(xfer);
 		}
-		NG_UBT_STAT_BYTES_RECV(sc->sc_stat, m->m_pkthdr.len);
+		break;
 
-		if (m->m_pkthdr.len < sizeof(*hdr)) {
-			NG_UBT_INFO(sc, "Packet too short\n");
-			goto done;
-		}
-		len = le16toh(hdr->length);
+	default: /* Error */
+		if (xfer->error != USB_ERR_CANCELLED) {
+			UBT_WARN(sc, "bulk-in transfer failed: %s\n",
+				usb2_errstr(xfer->error));
 
-		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);
+			/* Try to clear stall first */
+			sc->sc_flags |= UBT_FLAG_READ_STALL;
+			usb2_transfer_start(sc->sc_xfer[UBT_IF_0_BULK_CS_RD]);
+		} else
+			NG_NODE_UNREF(node); /* cancelled */
+		break;
+	}
+} /* ubt_bulk_read_callback */
 
-			NG_UBT_STAT_PCKTS_RECV(sc->sc_stat);
+/*
+ * Called when outgoing control transfer initiated to clear stall on
+ * incoming bulk pipe has completed.
+ * USB context.
+ */
 
-			NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+static void
+ubt_bulk_read_clear_stall_callback(struct usb2_xfer *xfer)
+{
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	struct usb2_xfer	*xfer_other;
 
-			m = NULL;
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
 
-			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);
+	sc = NG_NODE_PRIVATE(node);
+	xfer_other = sc->sc_xfer[UBT_IF_0_BULK_DT_RD];
 
-			NG_UBT_STAT_IERROR(sc->sc_stat);
-		}
-	}
-done:
-	if (m) {
-		NG_FREE_M(m);
-	}
-	/* start USB transfer if not already started */
+	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); /* cant clear stall */
+} /* ubt_bulk_read_clear_stall_callback */
 
-	usb2_transfer_start(sc->sc_xfer_if_0[1]);
+/*
+ * Called when outgoing bulk transfer (ACL packet) has completed, i.e.
+ * ACL packet was sent to the device.
+ * USB context.
+ */
 
-	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;
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	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:
-		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;
-
+			usb2_transfer_start(sc->sc_xfer[UBT_IF_0_BULK_CS_WR]);
+		} else
+			NG_NODE_UNREF(node); /* 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[UBT_IF_0_BULK_DT_WR];
+
 	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); /* 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;
 
-	switch (USB_GET_STATE(xfer)) {
-	case USB_ST_TRANSFERRED:
-tr_transferred:
-		offset = 0;
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
 
-		for (n = 0; n < xfer->nframes; n++) {
+	sc = NG_NODE_PRIVATE(node);
 
-			if (xfer->frlengths[n] >= sizeof(hdr)) {
+	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 */
+	}
 
-				usb2_copy_out(xfer->frbuffers, offset,
-				    &hdr, sizeof(hdr));
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+		for (n = 0; n < xfer->nframes; n ++)
+			if (ubt_isoc_read_one_frame(xfer, n) < 0)
+				break;
+		/* FALLTHROUGH */
 
-				if (hdr.length == (xfer->frlengths[n] - sizeof(hdr))) {
+	case USB_ST_SETUP:
+read_next:
+		for (n = 0; n < xfer->nframes; n ++)
+			xfer->frlengths[n] = xfer->max_frame_size;
 
-					NG_UBT_INFO(sc, "got complete SCO data "
-					    "frame, length=%d\n", hdr.length);
+		usb2_start_hardware(xfer);
+		break;
 
-					if (!NG_BT_MBUFQ_FULL(&sc->sc_sciq)) {
+	default: /* Error */
+                if (xfer->error != USB_ERR_CANCELLED) {
+                        UBT_STAT_IERROR(sc);
+                        goto read_next;
+			/* NOT REACHED */
+                }
 
-						/* allocate a new mbuf */
+		NG_NODE_UNREF(node); /* cancelled */
+		break;
+	}
+} /* ubt_isoc_read_callback */
 
-						MGETHDR(m, M_DONTWAIT, MT_DATA);
+/*
+ * Helper function. Called from ubt_isoc_read_callback() to read
+ * SCO data from one frame.
+ * USB context.
+ */
 
-						if (m == NULL) {
-							goto tr_setup;
-						}
-						MCLGET(m, M_DONTWAIT);
+static int
+ubt_isoc_read_one_frame(struct usb2_xfer *xfer, int frame_no)
+{
+	struct ubt_softc	*sc = xfer->priv_sc;
+	struct mbuf		*m;
+	int			len, want, got, error;
 
-						if (!(m->m_flags & M_EXT)) {
-							NG_FREE_M(m);
-							goto tr_setup;
-						}
-						/*
-						 * fix SCO data frame header
-						 * if required
-						 */
+	/* Get existing SCO reassembly buffer */
+	m = sc->sc_isoc_in_buffer;
+	sc->sc_isoc_in_buffer = NULL;
 
-						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;
-						}
+	/* 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! */
+			}
 
-						max_len = (MCLBYTES - m->m_len);
-
-						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, offset,
-						    ptr, xfer->frlengths[n]);
-
-						m->m_pkthdr.len += xfer->frlengths[n];
-						m->m_len += xfer->frlengths[n];
-
-						NG_BT_MBUFQ_ENQUEUE(&sc->sc_sciq, m);
-					}
-				}
+			MCLGET(m, M_DONTWAIT);
+			if (!(m->m_flags & M_EXT)) {
+				UBT_STAT_IERROR(sc);
+				NG_FREE_M(m);
+				return (-1);	/* XXX out of sync! */
 			}
-			offset += xfer->max_frame_size;
-		}
 
-	case USB_ST_SETUP:
-tr_setup:
+			/* 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 (NG_BT_MBUFQ_LEN(&sc->sc_sciq) > 0) {
-			ng_send_fn(sc->sc_node, NULL, ubt_isoc_read_complete, NULL, 0);
+			if (got >= want)
+				want += mtod(m, ng_hci_scodata_pkt_t *)->length;
 		}
-		for (n = 0; n < xfer->nframes; n++) {
-			xfer->frlengths[n] = xfer->max_frame_size;
-		}
 
-		usb2_start_hardware(xfer);
-		return;
+		/* Append frame data to the SCO reassembly buffer */
+		if (got + len > want)
+			len = want - got;
 
-	default:			/* Error */
-		if (xfer->error == USB_ERR_CANCELLED) {
-			/* ignore */
-			return;
-		}
-		goto tr_transferred;
-	}
-}
+		usb2_copy_out(xfer->frbuffers, frame_no * xfer->max_frame_size,
+			mtod(m, uint8_t *) + m->m_pkthdr.len, len);
 
-static void
-ubt_isoc_read_complete(node_p node, hook_p hook, void *arg1, int arg2)
-{
-	ubt_softc_p sc = NG_NODE_PRIVATE(node);
-	struct mbuf *m;
-	int error;
+		m->m_pkthdr.len += len;
+		m->m_len += len;
+		xfer->frlengths[frame_no] -= len;
 
-	if (sc == NULL) {
-		return;
-	}
-	mtx_lock(&sc->sc_mtx);
+		/* Check if we got everything we wanted, if not - continue */
+		if (got != want)
+			continue;
 
-	while (1) {
+		/* 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);
 
-		NG_BT_MBUFQ_DEQUEUE(&sc->sc_sciq, m);
+		UBT_STAT_PCKTS_RECV(sc);
+		UBT_STAT_BYTES_RECV(sc, m->m_pkthdr.len);
 
-		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);
+		NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+		if (error != 0)
+			UBT_STAT_IERROR(sc);
 
-		NG_UBT_STAT_PCKTS_RECV(sc->sc_stat);
-		NG_UBT_STAT_BYTES_RECV(sc->sc_stat, m->m_pkthdr.len);
+		/* m == NULL at this point */
+	}
 
-		NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+	/* Put SCO reassembly buffer back */
+	sc->sc_isoc_in_buffer = m;
 
-		m = NULL;
+	return (0);
+} /* ubt_isoc_read_one_frame */
 
-		if (error) {
-			NG_UBT_STAT_IERROR(sc->sc_stat);
-		}
-done:
-		if (m) {
-			NG_FREE_M(m);
-		}
-	}
+/*
+ * Called when outgoing isoc transfer (SCO packet) has completed, i.e.
+ * SCO packet was sent to the device.
+ * USB context.
+ */
 
-	mtx_unlock(&sc->sc_mtx);
-}
-
 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;
+	node_p			node = xfer->priv_sc;
+	struct ubt_softc	*sc;
+	struct mbuf		*m;
+	int			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:
-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);
+		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:
+send_next:
 		offset = 0;
+		space = xfer->max_frame_size * xfer->nframes;
+		m = NULL;
 
-		for (n = 0; n < xfer->nframes; n++) {
+		while (space > 0) {
+			if (m == NULL) {
+				UBT_MBUFQ_LOCK(sc);
+				NG_BT_MBUFQ_DEQUEUE(&sc->sc_scoq, m);
+				UBT_MBUFQ_UNLOCK(sc);
 
-			NG_BT_MBUFQ_DEQUEUE(&sc->sc_scoq, m);
+				if (m == NULL)
+					break;
+			}
 
-			if (m) {
-				len = min(xfer->max_frame_size, m->m_pkthdr.len);
+			n = min(space, m->m_pkthdr.len);
+			if (n > 0) {
+				usb2_m_copy_in(xfer->frbuffers, offset, m,0, n);
+				m_adj(m, n);
 
-				usb2_m_copy_in(xfer->frbuffers, offset, m, 0, len);
+				offset += n;
+				space -= n;
+			}
 
-				NG_FREE_M(m);
+			if (m->m_pkthdr.len == 0)
+				NG_FREE_M(m); /* sets m = NULL */
+		}
 
-				xfer->frlengths[n] = len;
-				offset += len;
-			} else {
-				xfer->frlengths[n] = 0;
-			}
+		/* 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 sizes for isoc frames.
+		 * Note that offset could be 0 at this point (i.e. we have
+		 * nothing to send). That is fine, as we have isoc. transfers
+		 * going in both directions all the time. In this case it
+		 * would be just empty isoc. transfer.
+		 */
+
+		for (n = 0; n < xfer->nframes; n ++) {
+			xfer->frlengths[n] = min(offset, xfer->max_frame_size);
+			offset -= xfer->frlengths[n];
+		}
+
 		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); /* cancelled */
+		break;
 	}
 }
 
 /****************************************************************************
  ****************************************************************************
- **                        Netgraph specific
+ **                                 Glue 
  ****************************************************************************
  ****************************************************************************/
 
 /*
- * Netgraph node constructor.
- * Do not allow to create node of this type:
+ * Schedule glue task. Should be called with sc_mbufq_mtx held.
+ * Netgraph context.
  */
 
 static int
-ng_ubt_constructor(node_p node)
+ubt_task_schedule(ubt_softc_p sc, int action)
 {
-	return (EINVAL);
-}
+	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 */
+
 /*
- * Netgraph node destructor.
- * Destroy node only when device has been detached:
+ * Glue task. Examines sc_task_flags and does things depending on it.
+ * Taskqueue context.
  */
 
-static int
-ng_ubt_shutdown(node_p node)
+static void
+ubt_task(void *context, int pending)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(node);
+	node_p		node = context;
+	ubt_softc_p	sc;
+	int		task_flags;
 
-	/* Let old node go */
-	NG_NODE_SET_PRIVATE(node, NULL);
-	NG_NODE_UNREF(node);
+	if (NG_NODE_NOT_VALID(node)) {
+		NG_NODE_UNREF(node);
+		return; /* netgraph node is gone */
+	}
 
-	if (sc == NULL) {
-		goto done;
+	sc = NG_NODE_PRIVATE(node);
+
+	UBT_MBUFQ_LOCK(sc);
+	task_flags = sc->sc_task_flags;
+	sc->sc_task_flags = 0;
+	UBT_MBUFQ_UNLOCK(sc);
+
+	/* Stop all USB transfers */
+	if (task_flags & UBT_FLAG_T_STOP_ALL) {
+		int	i;
+
+		/*
+		 * Interface #0
+		 */
+
+		mtx_lock(&sc->sc_if_mtx[0]);
+
+		for (i = UBT_IF_0_BULK_DT_WR; i < UBT_IF_0_N_TRANSFER; i ++)
+			usb2_transfer_stop(sc->sc_xfer[i]);
+
+		mtx_unlock(&sc->sc_if_mtx[0]);
+
+		/*
+		 * Interface #1
+		 */
+
+		mtx_lock(&sc->sc_if_mtx[1]);
+
+		for (i = UBT_IF_1_ISOC_DT_RD1; i < UBT_N_TRANSFER; i ++) 
+			usb2_transfer_stop(sc->sc_xfer[i]);
+
+		mtx_unlock(&sc->sc_if_mtx[1]);
 	}
-	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);
-		sc->sc_node = NULL;
-		goto done;
+	/* Start all incoming USB transfers */
+	if (task_flags & UBT_FLAG_T_START_ALL) {
+		/*
+		 * Interface #0
+		 */
+
+		mtx_lock(&sc->sc_if_mtx[0]);
+		ubt_xfer_start(sc, UBT_IF_0_INTR_DT_RD);
+		ubt_xfer_start(sc, UBT_IF_0_BULK_DT_RD);
+		mtx_unlock(&sc->sc_if_mtx[0]);
+
+		/*
+		 * Interface #1
+		 * Start both read and write isoc. transfers by default.
+		 * Get them going all the time even if we have nothing
+		 * to send to avoid any delays.
+		 */
+
+		mtx_lock(&sc->sc_if_mtx[1]);
+		ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_RD1);
+		ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_RD2);
+		ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_WR1);
+		ubt_xfer_start(sc, UBT_IF_1_ISOC_DT_WR2);
+		mtx_unlock(&sc->sc_if_mtx[1]);
 	}
-	/* 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;
-		goto done;
+
+ 	/* Start outgoing control transfer */
+	if (task_flags & UBT_FLAG_T_START_CTRL) {
+		mtx_lock(&sc->sc_if_mtx[0]);
+		ubt_xfer_start(sc, UBT_IF_0_CTRL_DT_WR);
+		mtx_unlock(&sc->sc_if_mtx[0]);
 	}
-	NG_NODE_SET_PRIVATE(sc->sc_node, sc);
-	NG_NODE_FORCE_WRITER(sc->sc_node);
 
-done:
-	if (sc) {
-		mtx_unlock(&sc->sc_mtx);
+	/* Start outgoing bulk transfer */
+	if (task_flags & UBT_FLAG_T_START_BULK) {
+		mtx_lock(&sc->sc_if_mtx[0]);
+		ubt_xfer_start(sc, UBT_IF_0_BULK_DT_WR);
+		mtx_unlock(&sc->sc_if_mtx[0]);
 	}
-	return (0);
-}
 
+	NG_NODE_UNREF(node);
+} /* ubt_task */
+
 /*
- * Create new hook.
- * There can only be one.
+ * Start USB transfer.
+ * Helper function called from ubt_task. Must be called with appropriate
+ * interface lock held.
+ * Taskqueue context.
  */
 
-static int
-ng_ubt_newhook(node_p node, hook_p hook, char const *name)
+static void
+ubt_xfer_start(ubt_softc_p sc, int transfer)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(node);
-	int error = 0;
-
-	if (strcmp(name, NG_UBT_HOOK) != 0) {
-		return (EINVAL);
+	if (!usb2_transfer_pending(sc->sc_xfer[transfer])) {
+		NG_NODE_REF(sc->sc_node);
+		usb2_transfer_start(sc->sc_xfer[transfer]);
 	}
-	mtx_lock(&sc->sc_mtx);
+} /* ubt_xfer_start */
 
-	if (sc->sc_hook != NULL) {
-		error = EISCONN;
-	} else {
-		sc->sc_hook = hook;
-	}
+/****************************************************************************
+ ****************************************************************************
+ **                        Netgraph specific
+ ****************************************************************************
+ ****************************************************************************/
 
-	mtx_unlock(&sc->sc_mtx);
+/*
+ * Netgraph node constructor. Do not allow to create node of this type.
+ * Netgraph context.
+ */
 
-	return (error);
-}
+static int
+ng_ubt_constructor(node_p node)
+{
+	return (EINVAL);
+} /* ng_ubt_constructor */
 
 /*
- * Connect hook.
- * Start incoming USB transfers
+ * Netgraph node destructor. Destroy node only when device has been detached.
+ * Netgraph context.
  */
 
 static int
-ng_ubt_connect(hook_p hook)
+ng_ubt_shutdown(node_p node)
 {
-	struct ubt_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+	if (node->nd_flags & NGF_REALLY_DIE) {
+		/*
+                 * We came here because the USB device is being
+		 * detached, so stop being persistant.
+                 */
+		NG_NODE_SET_PRIVATE(node, NULL);
+		NG_NODE_UNREF(node);
+	} else
+		NG_NODE_REVIVE(node); /* tell ng_rmnode we are persisant */
 
-	NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
+	return (0);
+} /* ng_ubt_shutdown */
 
-	mtx_lock(&sc->sc_mtx);
+/*
+ * Create new hook. There can only be one.
+ * Netgraph context.
+ */
 
-	sc->sc_flags |= (UBT_FLAG_READ_STALL |
-	    UBT_FLAG_WRITE_STALL |
-	    UBT_FLAG_INTR_STALL);
+static int
+ng_ubt_newhook(node_p node, hook_p hook, char const *name)
+{
+	struct ubt_softc	*sc = NG_NODE_PRIVATE(node);
 
-	/* start intr transfer */
-	usb2_transfer_start(sc->sc_xfer_if_0[2]);
+	if (strcmp(name, NG_UBT_HOOK) != 0)
+		return (EINVAL);
 
-	/* start bulk-in transfer */
-	usb2_transfer_start(sc->sc_xfer_if_0[1]);
+	if (sc->sc_hook != NULL)
+		return (EISCONN);
 
-	/* start bulk-out transfer */
-	usb2_transfer_start(sc->sc_xfer_if_0[0]);
+	sc->sc_hook = hook;
 
-	/* start control-out transfer */
-	usb2_transfer_start(sc->sc_xfer_if_0[3]);
-#if 0
-	XXX can enable this XXX
+	return (0);
+} /* ng_ubt_newhook */
 
-	/* start isoc-in transfer */
-	     usb2_transfer_start(sc->sc_xfer_if_1[0]);
+/*
+ * Connect hook. Start incoming USB transfers.
+ * Netgraph context.
+ */
 
-	usb2_transfer_start(sc->sc_xfer_if_1[1]);
+static int
+ng_ubt_connect(hook_p hook)
+{
+	struct ubt_softc	*sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
 
-	/* start isoc-out transfer */
-	usb2_transfer_start(sc->sc_xfer_if_1[2]);
-	usb2_transfer_start(sc->sc_xfer_if_1[3]);
-#endif
+	NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
 
-	mtx_unlock(&sc->sc_mtx);
+	UBT_MBUFQ_LOCK(sc);
+	ubt_task_schedule(sc, UBT_FLAG_T_START_ALL);
+	UBT_MBUFQ_UNLOCK(sc);
 
 	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;
+	node_p			node = NG_HOOK_NODE(hook);
+	struct ubt_softc	*sc;
 
-	if (sc != NULL) {
+	if (NG_NODE_NOT_VALID(node))
+		return (0);
 
-		mtx_lock(&sc->sc_mtx);
+	sc = NG_NODE_PRIVATE(node);
 
-		if (hook != sc->sc_hook) {
-			error = EINVAL;
-		} else {
+	if (hook != sc->sc_hook)
+		return (EINVAL);
 
-			/* stop intr transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_0[2]);
-			usb2_transfer_stop(sc->sc_xfer_if_0[6]);
+	sc->sc_hook = NULL;
 
-			/* stop bulk-in transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_0[1]);
-			usb2_transfer_stop(sc->sc_xfer_if_0[5]);
+	UBT_MBUFQ_LOCK(sc);
 
-			/* stop bulk-out transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_0[0]);
-			usb2_transfer_stop(sc->sc_xfer_if_0[4]);
+	/* Drain queues */
+	NG_BT_MBUFQ_DRAIN(&sc->sc_cmdq);
+	NG_BT_MBUFQ_DRAIN(&sc->sc_aclq);
+	NG_BT_MBUFQ_DRAIN(&sc->sc_scoq);
 
-			/* stop control transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_0[3]);
+	/* Kick off task to stop all USB xfers */
+	ubt_task_schedule(sc, UBT_FLAG_T_STOP_ALL);
 
-			/* stop isoc-in transfer */
-			usb2_transfer_stop(sc->sc_xfer_if_1[0]);
-			usb2_transfer_stop(sc->sc_xfer_if_1[1]);
+	UBT_MBUFQ_UNLOCK(sc);
 
-			/* 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);
 
 	switch (msg->header.typecookie) {
@@ -1527,25 +1655,31 @@
 		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,
+				"Refs: %d\n" \
+				"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]",
+				node->nd_refs,
+				(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 +1691,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 +1746,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 +1763,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 +1805,161 @@
 		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;
+	int			action, 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;
 		q = &sc->sc_scoq;
+		action = 0;
 		break;
 
 	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 187313)
+++ 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,111 @@
  */
 
 #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
+#define	UBT_DEFAULT_QLEN	64
+#define	UBT_ISOC_NFRAMES	32	/* should be factor of 8 */
 
 /* 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 */
+enum {
+	/* Interface #0 transfers */
+	UBT_IF_0_BULK_DT_WR = 0,
+	UBT_IF_0_BULK_DT_RD,
+	UBT_IF_0_INTR_DT_RD,
+	UBT_IF_0_CTRL_DT_WR,
+	UBT_IF_0_BULK_CS_WR,
+	UBT_IF_0_BULK_CS_RD,
+	UBT_IF_0_INTR_CS_RD,
+	UBT_IF_0_N_TRANSFER,	/* number of interface 0's transfers */
+	
+	/* Interface #1 transfers */
+	UBT_IF_1_ISOC_DT_RD1 = UBT_IF_0_N_TRANSFER,
+	UBT_IF_1_ISOC_DT_RD2,
+	UBT_IF_1_ISOC_DT_WR1,
+	UBT_IF_1_ISOC_DT_WR2,
 
+	UBT_N_TRANSFER,		/* total number of transfers */
+
+	UBT_IF_1_N_TRANSFER = UBT_N_TRANSFER - UBT_IF_1_ISOC_DT_RD1,
+};
+
 /* 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)))
+	int			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_mtx[2];	/* interface locks */
+	struct usb2_xfer	*sc_xfer[UBT_N_TRANSFER];
 
-	/* Interrupt pipe (HCI events) */
-	struct mbuf *sc_intr_buffer;	/* interrupt buffer */
+	struct mtx		sc_mbufq_mtx;	/* lock for all queues */
 
-	/* 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)
+	/* HCI commands */
+	struct ng_bt_mbufq	sc_cmdq;	/* HCI command queue */
+#define	UBT_CTRL_BUFFER_SIZE	(sizeof(struct usb2_device_request) +	\
+				 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 in pipe (ACL data) */
-	struct mbuf *sc_bulk_in_buffer;	/* bulk-in buffer */
-
-	/* 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 */
 
-	/* Netgraph specific */
-	node_p	sc_node;		/* pointer back to node */
-	hook_p	sc_hook;		/* upstream hook */
+	/* Glue */
+	int			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 and isoc
+						   write 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) */
+
+	struct task		sc_task;
 };
-typedef struct ubt_softc ubt_softc_t;
-typedef struct ubt_softc *ubt_softc_p;
+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