tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: asynchronous ugen(4) bulk transfers



	Hello.  Below are two difs.  The first contains the changes to the
libusb-1.0.19 library in the pkgsrc/devel/libusb1-1 package.   I only
needed to change the NetBSD specific backend code to get the behavior I
desired.

	The second dif contains the changes I made to ugen.c in
src/sys/dev/usb/ugen.c, relative to NetBSD-5.  The default NetBSD-5 ugen
driver doesn't have conditional variables in it.  In the course of
developing this code, I ran into all sorts  of race conditions using the
tsleep/wakep code.  I found it easier just to convert to using conditional
variables and mutexes, especially since That's what -current is doing.
I've been running this code for a couple of months on my workstations and
exercising it pretty regularly.  

-thanks
-Brian


--- netbsd_usb.c.fcs	2014-04-22 05:31:42.000000000 -0700
+++ netbsd_usb.c	2015-11-06 18:08:54.000000000 -0800
@@ -47,6 +47,7 @@
 /*
  * Backend functions
  */
+static int netbsd_init(struct libusb_context *);
 static int netbsd_get_device_list(struct libusb_context *,
     struct discovered_devs **);
 static int netbsd_open(struct libusb_device_handle *);
@@ -86,11 +87,14 @@
 static int _sync_control_transfer(struct usbi_transfer *);
 static int _sync_gen_transfer(struct usbi_transfer *);
 static int _access_endpoint(struct libusb_transfer *);
+static void netbsd_bulk_read_poll(struct usbi_transfer *itransfer);
+
+#define POLL_TIMEOUT 200 /* 200 ms between checks for bulkreads */
 
 const struct usbi_os_backend netbsd_backend = {
 	"Synchronous NetBSD backend",
 	0,
-	NULL,				/* init() */
+	netbsd_init,				/* init() */
 	NULL,				/* exit() */
 	netbsd_get_device_list,
 	NULL,				/* hotplug_poll */
@@ -134,6 +138,22 @@
 	0,				/* add_iso_packet_size */
 };
 
+
+static pthread_attr_t ta; /* For read thread attributes */
+static pthread_mutex_t read_poll_lock;
+
+/*
+ * Initialize the NetBSD back end
+ */
+int
+netbsd_init(struct libusb_context *ctx)
+{
+	
+	pthread_attr_init(&ta);
+	pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED);
+	return(0);
+}
+
 int
 netbsd_get_device_list(struct libusb_context * ctx,
 	struct discovered_devs **discdevs)
@@ -469,8 +489,10 @@
 	if (err)
 		return (err);
 
-	if (write(hpriv->pipe[1], &itransfer, sizeof(itransfer)) < 0)
-		return _errno_to_libusb(errno);
+	if (IS_XFEROUT(transfer)) {
+		if (write(hpriv->pipe[1], &itransfer, sizeof(itransfer)) < 0)
+			return _errno_to_libusb(errno);
+		}
 
 	return (LIBUSB_SUCCESS);
 }
@@ -577,6 +599,8 @@
 		return (LIBUSB_ERROR_NO_DEVICE);
 	case ENOMEM:
 		return (LIBUSB_ERROR_NO_MEM);
+	case EAGAIN:
+return (LIBUSB_ERROR_BUSY);
 	}
 
 	usbi_dbg("error: %s", strerror(err));
@@ -706,6 +730,8 @@
 {
 	struct libusb_transfer *transfer;
 	int fd, nr = 1;
+	int res;
+	pthread_t tid;
 
 	transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
 
@@ -719,20 +745,100 @@
 	if ((ioctl(fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0)
 		return _errno_to_libusb(errno);
 
+	/*
+	* Since the NetBSD ugen(4) driver doesn't honor timeouts, use
+	* non-blocking i/o via the fcntl(2) system call.
+	*/
+	if (IS_XFERIN(transfer)) {
+		res = fcntl(fd, F_GETFL, 0);
+		if (res < 0) {
+			return _errno_to_libusb(errno);
+		}
+	
+		res |= O_NONBLOCK;
+		fcntl(fd, F_SETFL, res);
+	}
+
+
+	/*
+	* In order for non-blocking reads/writes to work, USB_SET_BULK_RA
+	* and USB_SET_BULK_WB must be enabled.
+	*/
+	res = 1;
+	/* If not an end point or already enabled, nothing happens */
+	ioctl(fd, USB_SET_BULK_WB, &res);
+
 	if (IS_XFERIN(transfer)) {
 		if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0)
 			if ((ioctl(fd, USB_SET_SHORT_XFER, &nr)) < 0)
 				return _errno_to_libusb(errno);
 
-		nr = read(fd, transfer->buffer, transfer->length);
+		/* Must come after answering the short read question */
+		res = 1;
+		ioctl(fd, USB_SET_BULK_RA, &res);
+		/* Start the reading thread */
+		nr = pthread_create(&tid, &ta, netbsd_bulk_read_poll, itransfer);
+		if (nr == 0) return(nr);
 	} else {
 		nr = write(fd, transfer->buffer, transfer->length);
 	}
 
-	if (nr < 0)
+	if (nr < 0) {
 		return _errno_to_libusb(errno);
+	}
 
 	itransfer->transferred = nr;
 
 	return (0);
 }
+
+
+/*
+ * netbsd_bulk_read_poll(struct usbi_transfer *itransfer)
+ * Wait for a bulk endpoint to become ready for reading and, once ready, 
+ * perform the read and notify the upper layers.
+ */
+
+static void 
+netbsd_bulk_read_poll(struct usbi_transfer *itransfer)
+{
+	struct libusb_transfer *transfer;
+	struct handle_priv *hpriv;
+	int nfd,  fd, nr = 1;
+	int res;
+	struct pollfd readpolls;
+
+	pthread_mutex_lock(&read_poll_lock);
+	transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+	hpriv = (struct handle_priv *)transfer->dev_handle->os_priv;
+
+	/*
+	 * Bulk, Interrupt or Isochronous transfer depends on the
+	 * endpoint and thus the node to open.
+	 */
+	if ((fd = _access_endpoint(transfer)) < 0) {
+		pthread_mutex_unlock(&read_poll_lock);
+		return;
+	}
+
+	res = 0;
+	while (!res) {
+		memset(&readpolls, 0, sizeof(struct pollfd));
+
+		readpolls.fd = fd;
+		readpolls.events = POLLIN|POLLRDNORM|POLLPRI;
+		nfd = 1;
+		res = poll(&readpolls, nfd, POLL_TIMEOUT);
+	}
+
+	if (res < 0) {
+		pthread_mutex_unlock(&read_poll_lock);
+		return; /* An error, abort */
+	}
+
+	nr = read(fd, transfer->buffer, transfer->length);
+	itransfer->transferred = nr;
+	write(hpriv->pipe[1], &itransfer, sizeof(itransfer));
+	pthread_mutex_unlock(&read_poll_lock);
+	return;
+}

<ugen.c diffs below this line.>

Index: ugen.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ugen.c,v
retrieving revision 1.99.8.4
diff -u -r1.99.8.4 ugen.c
--- ugen.c	25 Jan 2012 17:33:17 -0000	1.99.8.4
+++ ugen.c	4 Apr 2016 22:50:04 -0000
@@ -1,4 +1,4 @@
-/*	$NetBSD$	*/
+/*	$NetBSD: ugen.c,v 1.99.8.4 2012/01/25 17:33:17 riz Exp $	*/
 
 /*
  * Copyright (c) 1998, 2004 The NetBSD Foundation, Inc.
@@ -37,7 +37,7 @@
 
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD$");
+__KERNEL_RCSID(0, "$NetBSD: ugen.c,v 1.99.8.4 2012/01/25 17:33:17 riz Exp $");
 
 #include "opt_ugen_bulk_ra_wb.h"
 #include "opt_compat_netbsd.h"
@@ -49,6 +49,8 @@
 #if defined(__NetBSD__) || defined(__OpenBSD__)
 #include <sys/device.h>
 #include <sys/ioctl.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
 #elif defined(__FreeBSD__)
 #include <sys/module.h>
 #include <sys/bus.h>
@@ -81,6 +83,7 @@
 #define	UGEN_CHUNK	128	/* chunk size for read */
 #define	UGEN_IBSIZE	1020	/* buffer size */
 #define	UGEN_BBSIZE	1024
+#define BUFFCOUNT 4 /* How many read ahead buffers do we use */
 
 #define	UGEN_NISOFRAMES	500	/* 0.5 seconds worth */
 #define UGEN_NISOREQS	6	/* number of outstanding xfer requests */
@@ -99,15 +102,21 @@
 #define UGEN_BULK_RA	0x08	/* in bulk read-ahead mode */
 #define UGEN_BULK_WB	0x10	/* in bulk write-behind mode */
 #define UGEN_RA_WB_STOP	0x20	/* RA/WB xfer is stopped (buffer full/empty) */
+#define UGEN_RA_WRAP	0x40	/* RA overrun is in effect */
+#define UGEN_RA_COMPLETE	0x80	/* RA Read ahead has read an entire block */
+#define UGEN_RA_ZPENDING 0x100 /* A Zero Length Packet is pending */
 	usbd_pipe_handle pipeh;
 	struct clist q;
-	struct selinfo rsel;
 	u_char *ibuf;		/* start of buffer (circular for isoc) */
 	u_char *fill;		/* location for input (isoc) */
 	u_char *limit;		/* end of circular buffer (isoc) */
 	u_char *cur;		/* current read location (isoc) */
 	u_int32_t timeout;
 #ifdef UGEN_BULK_RA_WB
+	u_char *ra_buflist[BUFFCOUNT]; 
+	int ra_buflen[BUFFCOUNT];
+	u_int32_t ra_load; /* The buffer we're loading from a USB device */
+	u_int32_t ra_read; /* The buffer ready to go to user space */
 	u_int32_t ra_wb_bufsize; /* requested size for RA/WB buffer */
 	u_int32_t ra_wb_reqsize; /* requested xfer length for RA/WB */
 	u_int32_t ra_wb_used;	 /* how much is in buffer */
@@ -120,12 +129,17 @@
 		void *dmabuf;
 		u_int16_t sizes[UGEN_NISORFRMS];
 	} isoreqs[UGEN_NISOREQS];
+#define UGEN_ENDPOINT_NONZERO_CRUFT	offsetof(struct ugen_endpoint, rsel)
+	struct selinfo rsel;
+	kcondvar_t cv;
 };
 
 struct ugen_softc {
 	USBBASEDEVICE sc_dev;		/* base device */
 	usbd_device_handle sc_udev;
 
+	kmutex_t		sc_lock;
+
 	char sc_is_open[USB_MAX_ENDPOINTS];
 	struct ugen_endpoint sc_endpoints[USB_MAX_ENDPOINTS][2];
 #define OUT 0
@@ -230,9 +244,13 @@
 	aprint_normal_dev(self, "%s\n", devinfop);
 	usbd_devinfo_free(devinfop);
 
+	//mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTUSB);
+	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
+
 	sc->sc_dev = self;
 	sc->sc_udev = udev = uaa->device;
 
+
 	/* First set configuration index 0, the default one for ugen. */
 	err = usbd_set_config_index(udev, 0, 0);
 	if (err) {
@@ -243,6 +261,17 @@
 	}
 	conf = usbd_get_config_descriptor(udev)->bConfigurationValue;
 
+	for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
+		for (dir = OUT; dir <= IN; dir++) {
+			struct ugen_endpoint *sce;
+
+			sce = &sc->sc_endpoints[i][dir];
+			memset(sce, 0, UGEN_ENDPOINT_NONZERO_CRUFT);
+			selinit(&sce->rsel);
+			cv_init(&sce->cv, "ugensce");
+		}
+	}
+
 	/* Set up all the local state for this configuration. */
 	err = ugen_set_config(sc, conf);
 	if (err) {
@@ -261,14 +290,7 @@
 		}
 	}
 #endif
-	for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
-		for (dir = OUT; dir <= IN; dir++) {
-			struct ugen_endpoint *sce;
 
-			sce = &sc->sc_endpoints[i][dir];
-			selinit(&sce->rsel);
-		}
-	}
 
 	usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev,
 			   USBDEV(sc->sc_dev));
@@ -290,7 +312,7 @@
 	u_int8_t niface, nendpt;
 	int ifaceno, endptno, endpt;
 	usbd_status err;
-	int dir;
+	int i, dir;
 
 	DPRINTFN(1,("ugen_set_config: %s to configno %d, sc=%p\n",
 		    USBDEVNAME(sc->sc_dev), configno, sc));
@@ -318,7 +340,14 @@
 	err = usbd_interface_count(dev, &niface);
 	if (err)
 		return (err);
-	memset(sc->sc_endpoints, 0, sizeof sc->sc_endpoints);
+	/* Clear out the old info, but leave the selinfo and cv initialised. */
+	for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
+		for (dir = OUT; dir <= IN; dir++) {
+			sce = &sc->sc_endpoints[i][dir];
+			memset(sce, 0, UGEN_ENDPOINT_NONZERO_CRUFT);
+		}
+	}
+
 	for (ifaceno = 0; ifaceno < niface; ifaceno++) {
 		DPRINTFN(1,("ugen_set_config: ifaceno %d\n", ifaceno));
 		err = usbd_device2interface_handle(dev, ifaceno, &iface);
@@ -530,9 +559,16 @@
 		DPRINTFN(5, ("ugenclose: endpt=%d dir=%d sce=%p\n",
 			     endpt, dir, sce));
 
-		usbd_abort_pipe(sce->pipeh);
-		usbd_close_pipe(sce->pipeh);
-		sce->pipeh = NULL;
+		/* If we're dying, the abort is handled by ugen_detach. */
+		mutex_enter(&sc->sc_lock);
+		if (!sc->sc_dying) { 
+			mutex_exit(&sc->sc_lock);
+			usbd_abort_pipe(sce->pipeh);
+			usbd_close_pipe(sce->pipeh);
+			sce->pipeh = NULL;
+		} else {
+			mutex_exit(&sc->sc_lock);
+		}
 
 		switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
 		case UE_INTERRUPT:
@@ -547,6 +583,13 @@
 		case UE_BULK:
 			if (sce->state & (UGEN_BULK_RA | UGEN_BULK_WB))
 				/* ibuf freed below */
+				for (i = 0; i < BUFFCOUNT;i ++) {
+					if (sce->ra_buflist[i] != NULL) {
+						free(sce->ra_buflist[i], M_USBDEV);
+						sce->ra_buflist[i] = NULL;
+					}
+					sce->ra_buflen[i] = 0;
+				}
 				usbd_free_xfer(sce->ra_wb_xfer);
 			break;
 #endif
@@ -572,8 +615,10 @@
 	u_int32_t n, tn;
 	usbd_xfer_handle xfer;
 	usbd_status err;
-	int s;
+	uint16_t xfer_flags;
 	int error = 0;
+	int have_data;
+	u_char *readptr;
 
 	DPRINTFN(5, ("%s: ugenread: %d\n", USBDEVNAME(sc->sc_dev), endpt));
 
@@ -597,15 +642,17 @@
 	switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
 	case UE_INTERRUPT:
 		/* Block until activity occurred. */
-		s = splusb();
+		mutex_enter(&sc->sc_lock);
 		while (sce->q.c_cc == 0) {
 			if (flag & IO_NDELAY) {
-				splx(s);
+				mutex_exit(&sc->sc_lock);
 				return (EWOULDBLOCK);
 			}
 			sce->state |= UGEN_ASLP;
 			DPRINTFN(5, ("ugenread: sleep on %p\n", sce));
-			error = tsleep(sce, PZERO | PCATCH, "ugenri", mstohz(sce->timeout));
+			/* "ugenri" */
+			error = cv_timedwait_sig(&sce->cv, &sc->sc_lock,
+			    mstohz(sce->timeout));
 			DPRINTFN(5, ("ugenread: woke, error=%d\n", error));
 			if (sc->sc_dying)
 				error = EIO;
@@ -614,7 +661,7 @@
 				break;
 			}
 		}
-		splx(s);
+		mutex_exit(&sc->sc_lock);
 
 		/* Transfer as many chunks as possible. */
 		while (sce->q.c_cc > 0 && uio->uio_resid > 0 && !error) {
@@ -635,23 +682,27 @@
 	case UE_BULK:
 #ifdef UGEN_BULK_RA_WB
 		if (sce->state & UGEN_BULK_RA) {
-			DPRINTFN(5, ("ugenread: BULK_RA req: %zd used: %d\n",
-				     uio->uio_resid, sce->ra_wb_used));
+			DPRINTFN(5, ("ugenread: BULK_RA req: %zd used: %d(%d)\n",
+				     uio->uio_resid, sce->ra_buflen[sce->ra_read],
+					sce->ra_read));
 			xfer = sce->ra_wb_xfer;
 
-			s = splusb();
-			if (sce->ra_wb_used == 0 && flag & IO_NDELAY) {
-				splx(s);
+			have_data = 0;
+			mutex_enter(&sc->sc_lock);
+			if (((sce->state & UGEN_RA_COMPLETE) == 0) && 
+				(flag & IO_NDELAY)) {
+				mutex_exit(&sc->sc_lock);
 				return (EWOULDBLOCK);
 			}
 			while (uio->uio_resid > 0 && !error) {
-				while (sce->ra_wb_used == 0) {
+				while (((sce->state & UGEN_RA_COMPLETE) == 0) && (sce->ra_read == sce->ra_load)) {
 					sce->state |= UGEN_ASLP;
 					DPRINTFN(5,
 						 ("ugenread: sleep on %p\n",
 						  sce));
-					error = tsleep(sce, PZERO | PCATCH,
-						       "ugenrb", mstohz(sce->timeout));
+					/* "ugenrb" */
+					error = cv_timedwait_sig(&sce->cv,
+					    &sc->sc_lock, mstohz(sce->timeout));
 					DPRINTFN(5,
 						 ("ugenread: woke, error=%d\n",
 						  error));
@@ -664,46 +715,77 @@
 				}
 
 				/* Copy data to the process. */
+				if (uio->uio_resid > 0) {
+					readptr = sce->ra_buflist[sce->ra_read];
+				}
 				while (uio->uio_resid > 0
-				       && sce->ra_wb_used > 0) {
+				       && sce->ra_buflen[sce->ra_read] > 0) {
+					have_data = 1;
 					n = min(uio->uio_resid,
-						sce->ra_wb_used);
-					n = min(n, sce->limit - sce->cur);
-					error = uiomove(sce->cur, n, uio);
+						sce->ra_buflen[sce->ra_read]);
+					error = uiomove(readptr, n, uio);
 					if (error)
 						break;
-					sce->cur += n;
-					sce->ra_wb_used -= n;
-					if (sce->cur == sce->limit)
-						sce->cur = sce->ibuf;
+					readptr += n;
+					sce->ra_buflen[sce->ra_read] -= n;
+				}
+				DPRINTFN(5, ("ugenread: BULK_RA_POST_UIO req: %zd used: %d(%d)\n",
+				     uio->uio_resid, sce->ra_buflen[sce->ra_read],
+					sce->ra_read));
+
+				if (sce->ra_buflen[sce->ra_read] == 0) {
+					sce->ra_read ++;
+					if (sce->ra_read == BUFFCOUNT) {
+						sce->ra_read = 0;
+					}
+					sce->state &= ~UGEN_RA_WRAP;
+				}
+
+				if (sce->ra_read == sce->ra_load) {
+					sce->state &= ~UGEN_RA_COMPLETE;
 				}
 
+
 				/* 
-				 * If the transfers stopped because the
-				 * buffer was full, restart them.
+				 * If not in non-blocking mode or not in a read
+				 * overrun, set up for a new read transfer.
 				 */
-				if (sce->state & UGEN_RA_WB_STOP &&
-				    sce->ra_wb_used < sce->limit - sce->ibuf) {
-					n = (sce->limit - sce->ibuf)
-					    - sce->ra_wb_used;
-					usbd_setup_xfer(xfer,
-					    sce->pipeh, sce, NULL,
-					    min(n, sce->ra_wb_xferlen),
-					    USBD_NO_COPY, USBD_NO_TIMEOUT,
-					    ugen_bulkra_intr);
-					sce->state &= ~UGEN_RA_WB_STOP;
-					err = usbd_transfer(xfer);
-					if (err != USBD_IN_PROGRESS)
-						/*
-						 * The transfer has not been
-						 * queued.  Setting STOP
-						 * will make us try
-						 * again at the next read.
-						 */
-						sce->state |= UGEN_RA_WB_STOP;
+				if (!flag & IO_NDELAY) {
+					xfer_flags = USBD_NO_COPY | 
+					(sce->state &UGEN_SHORT_OK ? USBD_SHORT_XFER_OK : 0);
+					n = min(sce->ra_wb_reqsize, 
+						(sce->ra_wb_bufsize - sce->ra_buflen[sce->ra_load]));
+					DPRINTFN(5, ("ugenread: calling bulkra_intr, req = %d(%d)\n",
+				     	n, sce->ra_load));
+					if (n > 0) {
+						usbd_setup_xfer(xfer,
+						    sce->pipeh, sce, NULL, n,
+						    xfer_flags, USBD_NO_TIMEOUT,
+						    ugen_bulkra_intr);
+						sce->state &= ~UGEN_RA_WB_STOP;
+						mutex_exit(&sc->sc_lock);
+						err = usbd_transfer(xfer);
+						mutex_enter(&sc->sc_lock);
+						if (err != USBD_IN_PROGRESS)
+							/*
+							 * The transfer has not been
+							 * queued.  Setting STOP
+							 * will make us try
+							 * again at the next read.
+							 */
+							sce->state |= UGEN_RA_WB_STOP;
+						} else {
+							sce->state |= UGEN_RA_WB_STOP;
+						}
+				}
+
+				if ((have_data) && (flag & IO_NDELAY)) {
+					/* Data already copied to user, now return */
+					error = 0;
+					break;
 				}
 			}
-			splx(s);
+			mutex_exit(&sc->sc_lock);
 			break;
 		}
 #endif
@@ -735,15 +817,17 @@
 		usbd_free_xfer(xfer);
 		break;
 	case UE_ISOCHRONOUS:
-		s = splusb();
+		mutex_enter(&sc->sc_lock);
 		while (sce->cur == sce->fill) {
 			if (flag & IO_NDELAY) {
-				splx(s);
+				mutex_exit(&sc->sc_lock);
 				return (EWOULDBLOCK);
 			}
 			sce->state |= UGEN_ASLP;
 			DPRINTFN(5, ("ugenread: sleep on %p\n", sce));
-			error = tsleep(sce, PZERO | PCATCH, "ugenri", mstohz(sce->timeout));
+			/* "ugenri" */
+			error = cv_timedwait_sig(&sce->cv,
+			    &sc->sc_lock, mstohz(sce->timeout));
 			DPRINTFN(5, ("ugenread: woke, error=%d\n", error));
 			if (sc->sc_dying)
 				error = EIO;
@@ -769,7 +853,7 @@
 			if(sce->cur >= sce->limit)
 				sce->cur = sce->ibuf;
 		}
-		splx(s);
+		mutex_exit(&sc->sc_lock);
 		break;
 
 
@@ -784,14 +868,28 @@
 {
 	int endpt = UGENENDPOINT(dev);
 	struct ugen_softc *sc;
-	int error;
+	int error, s;
 
 	USB_GET_SC_OPEN(ugen, UGENUNIT(dev), sc);
 
+	if (sc->sc_dying)
+		return (EIO);
+
+	mutex_enter(&sc->sc_lock);
 	sc->sc_refcnt++;
+	mutex_exit(&sc->sc_lock);
 	error = ugen_do_read(sc, endpt, uio, flag);
-	if (--sc->sc_refcnt < 0)
+
+	mutex_enter(&sc->sc_lock);
+	if (--sc->sc_refcnt < 0) {
+		mutex_exit(&sc->sc_lock);
+		s = splusb();
 		usb_detach_wakeup(USBDEV(sc->sc_dev));
+		splx(s);
+	} else {
+		mutex_exit(&sc->sc_lock);
+	}
+
 	return (error);
 }
 
@@ -803,7 +901,7 @@
 	u_int32_t n;
 	int error = 0;
 #ifdef UGEN_BULK_RA_WB
-	int s;
+	int send_zero;
 	u_int32_t tn;
 	char *dbuf;
 #endif
@@ -837,21 +935,61 @@
 				     uio->uio_resid, sce->ra_wb_used));
 			xfer = sce->ra_wb_xfer;
 
-			s = splusb();
-			if (sce->ra_wb_used == sce->limit - sce->ibuf &&
-			    flag & IO_NDELAY) {
-				splx(s);
+			mutex_enter(&sc->sc_lock);
+			if ((sce->ra_wb_used == sce->limit - sce->ibuf) &&
+			    (flag & IO_NDELAY)) {
+				DPRINTFN(5, ("ugenwrite: BULK_WB: Would block!\n"));
+				mutex_exit(&sc->sc_lock);
 				return (EWOULDBLOCK);
 			}
-			while (uio->uio_resid > 0 && !error) {
-				while (sce->ra_wb_used == 
-				       sce->limit - sce->ibuf) {
+
+			/* Allow zero length packets to generate a USB transaction */
+			if (uio->uio_resid == 0) {
+				send_zero = 1;
+				sce->state |= UGEN_RA_ZPENDING;
+			} else {
+				send_zero = 0;
+			}
+
+			DPRINTFN(2, ("ugenwrite: BULK_WB: before write state = 0x%x\n",
+				sce->state));
+
+			while (!(sce->state & UGEN_RA_COMPLETE)) {
+				if ((sce->state & UGEN_RA_WB_STOP) &&
+					(sce->ra_wb_used == 0)) {
+					break;
+				}
+				sce->state |= UGEN_ASLP;
+				DPRINTFN(5,
+					 ("ugenwrite: (complete) sleep on %p\n",
+					  sce));
+				/* "ugenwb" */
+				error = cv_timedwait_sig(&sce->cv,
+				    &sc->sc_lock, mstohz(sce->timeout));
+				DPRINTFN(5,
+					 ("ugenwrite: woke, error=%d\n",
+					  error));
+				if (sc->sc_dying)
+					error = EIO;
+				if (error) {
+					sce->state &= ~UGEN_ASLP;
+					mutex_exit(&sc->sc_lock);
+					return(error);
+				}
+			}
+
+			while ((uio->uio_resid > 0 && !error) 
+				|| (send_zero == 1)) {
+				DPRINTFN(2, ("ugenwrite: BULK_WB: state = 0x%x\n",
+					sce->state));
+				while (!(sce->state & UGEN_RA_WB_STOP)) {
 					sce->state |= UGEN_ASLP;
 					DPRINTFN(5,
 						 ("ugenwrite: sleep on %p\n",
 						  sce));
-					error = tsleep(sce, PZERO | PCATCH,
-						       "ugenwb", mstohz(sce->timeout));
+					/* "ugenwb" */
+					error = cv_timedwait_sig(&sce->cv,
+					    &sc->sc_lock, mstohz(sce->timeout));
 					DPRINTFN(5,
 						 ("ugenwrite: woke, error=%d\n",
 						  error));
@@ -862,7 +1000,31 @@
 						break;
 					}
 				}
+	
+				if (sce->state  & UGEN_RA_ZPENDING) {
+					usbd_setup_xfer(xfer,
+					    sce->pipeh, sce, NULL, 0,
+					    USBD_FORCE_SHORT_XFER | USBD_NO_COPY, USBD_NO_TIMEOUT,
+					    ugen_bulkwb_intr);
+					sce->state &= ~(UGEN_RA_WB_STOP | UGEN_RA_COMPLETE
+						| UGEN_RA_ZPENDING);
 
+					mutex_exit(&sc->sc_lock);
+					err = usbd_transfer(xfer);
+					mutex_enter(&sc->sc_lock);
+					if (err != USBD_IN_PROGRESS) {
+						/*
+						 * The transfer has not been
+						 * queued.  Setting STOP
+						 * will make us try again
+						 * at the next read.
+						 */
+						sce->state |= UGEN_RA_WB_STOP;
+						printf("ugenwrite: ZLP error: %d\n",err);
+					}
+					send_zero = 0;
+				}
+	
 				/* Copy data from the process. */
 				while (uio->uio_resid > 0 &&
 				    sce->ra_wb_used < sce->limit - sce->ibuf) {
@@ -877,6 +1039,8 @@
 					sce->ra_wb_used += n;
 					if (sce->fill == sce->limit)
 						sce->fill = sce->ibuf;
+					DPRINTFN(5, ("ugenwrite: BULK_WB: after uiomove:  req: %zd used: %d\n",
+		     			uio->uio_resid, sce->ra_wb_used));
 				}
 
 				/*
@@ -885,6 +1049,7 @@
 				 */
 				if (sce->state & UGEN_RA_WB_STOP &&
 				    sce->ra_wb_used > 0) {
+					DPRINTFN(5, ("ugenwrite: BULK_WB: starting transfer\n"));
 					dbuf = (char *)usbd_get_buffer(xfer);
 					n = min(sce->ra_wb_used,
 						sce->ra_wb_xferlen);
@@ -894,12 +1059,15 @@
 					if (n - tn > 0)
 						memcpy(dbuf, sce->ibuf,
 						       n - tn);
+					DPRINTFN(5, ("ugenwrite: BULK_WB: Transfer length: %d\n",n));
 					usbd_setup_xfer(xfer,
 					    sce->pipeh, sce, NULL, n,
 					    USBD_NO_COPY, USBD_NO_TIMEOUT,
 					    ugen_bulkwb_intr);
-					sce->state &= ~UGEN_RA_WB_STOP;
+					sce->state &= ~(UGEN_RA_WB_STOP | UGEN_RA_COMPLETE);
+					mutex_exit(&sc->sc_lock);
 					err = usbd_transfer(xfer);
+					mutex_enter(&sc->sc_lock);
 					if (err != USBD_IN_PROGRESS)
 						/*
 						 * The transfer has not been
@@ -910,7 +1078,7 @@
 						sce->state |= UGEN_RA_WB_STOP;
 				}
 			}
-			splx(s);
+			mutex_exit(&sc->sc_lock);
 			break;
 		}
 #endif
@@ -971,14 +1139,27 @@
 {
 	int endpt = UGENENDPOINT(dev);
 	struct ugen_softc *sc;
-	int error;
+	int error, s;
 
 	USB_GET_SC_OPEN(ugen, UGENUNIT(dev), sc);
 
+	if (sc->sc_dying)
+		return (EIO);
+
+	mutex_enter(&sc->sc_lock);
 	sc->sc_refcnt++;
+	mutex_exit(&sc->sc_lock);
+
 	error = ugen_do_write(sc, endpt, uio, flag);
-	if (--sc->sc_refcnt < 0)
+	mutex_enter(&sc->sc_lock);
+	if (--sc->sc_refcnt < 0) {
+		mutex_exit(&sc->sc_lock);
+		s = splusb();
 		usb_detach_wakeup(USBDEV(sc->sc_dev));
+		splx(s);
+	} else {
+		mutex_exit(&sc->sc_lock);
+	}
 	return (error);
 }
 
@@ -1004,8 +1185,7 @@
 {
 	USB_DETACH_START(ugen, sc);
 	struct ugen_endpoint *sce;
-	int i, dir;
-	int s;
+	int i, dir, s;
 #if defined(__NetBSD__) || defined(__OpenBSD__)
 	int maj, mn;
 
@@ -1017,23 +1197,34 @@
 	sc->sc_dying = 1;
 	pmf_device_deregister(self);
 	/* Abort all pipes.  Causes processes waiting for transfer to wake. */
+	mutex_enter(&sc->sc_lock);
 	for (i = 0; i < USB_MAX_ENDPOINTS; i++) {
 		for (dir = OUT; dir <= IN; dir++) {
 			sce = &sc->sc_endpoints[i][dir];
-			if (sce && sce->pipeh)
+			if (sce && sce->pipeh) {
+				mutex_exit(&sc->sc_lock);
 				usbd_abort_pipe(sce->pipeh);
+				usbd_close_pipe(sce->pipeh);
+				mutex_enter(&sc->sc_lock);
+				sce->pipeh = NULL;
+			}
 		}
 	}
+	mutex_exit(&sc->sc_lock);
 
-	s = splusb();
+	mutex_enter(&sc->sc_lock);
 	if (--sc->sc_refcnt >= 0) {
+		mutex_exit(&sc->sc_lock);
+		s = splusb();
 		/* Wake everyone */
 		for (i = 0; i < USB_MAX_ENDPOINTS; i++)
 			wakeup(&sc->sc_endpoints[i][IN]);
 		/* Wait for processes to go away. */
 		usb_detach_wait(USBDEV(sc->sc_dev));
+		splx(s);
+	} else {
+		mutex_exit(&sc->sc_lock);
 	}
-	splx(s);
 
 #if defined(__NetBSD__) || defined(__OpenBSD__)
 	/* locate the major number */
@@ -1059,9 +1250,12 @@
 		for (dir = OUT; dir <= IN; dir++) {
 			sce = &sc->sc_endpoints[i][dir];
 			seldestroy(&sce->rsel);
+			cv_destroy(&sce->cv);
 		}
 	}
 
+	mutex_destroy(&sc->sc_lock);
+
 	return (0);
 }
 
@@ -1069,7 +1263,7 @@
 ugenintr(usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status)
 {
 	struct ugen_endpoint *sce = addr;
-	/*struct ugen_softc *sc = sce->sc;*/
+	struct ugen_softc *sc = sce->sc;
 	u_int32_t count;
 	u_char *ibuf;
 
@@ -1093,11 +1287,13 @@
 
 	(void)b_to_q(ibuf, count, &sce->q);
 
+	mutex_enter(&sc->sc_lock);
 	if (sce->state & UGEN_ASLP) {
 		sce->state &= ~UGEN_ASLP;
 		DPRINTFN(5, ("ugen_intr: waking %p\n", sce));
-		wakeup(sce);
+		cv_signal(&sce->cv);
 	}
+	mutex_exit(&sc->sc_lock);
 	selnotify(&sce->rsel, 0, 0);
 }
 
@@ -1107,6 +1303,7 @@
 {
 	struct isoreq *req = addr;
 	struct ugen_endpoint *sce = req->sce;
+	struct ugen_softc *sc = sce->sc;
 	u_int32_t count, n;
 	int i, isize;
 
@@ -1152,11 +1349,13 @@
 			     USBD_NO_COPY, ugen_isoc_rintr);
 	(void)usbd_transfer(xfer);
 
+	mutex_enter(&sc->sc_lock);
 	if (sce->state & UGEN_ASLP) {
 		sce->state &= ~UGEN_ASLP;
 		DPRINTFN(5, ("ugen_isoc_rintr: waking %p\n", sce));
-		wakeup(sce);
+		cv_signal(&sce->cv);
 	}
+	mutex_exit(&sc->sc_lock);
 	selnotify(&sce->rsel, 0, 0);
 }
 
@@ -1166,65 +1365,135 @@
 		 usbd_status status)
 {
 	struct ugen_endpoint *sce = addr;
+	struct ugen_softc *sc = sce->sc;
 	u_int32_t count, n;
 	char const *tbuf;
 	usbd_status err;
+	uint16_t xfer_flags;
+	u_char *loadptr;
+	int xfer_done; /* Is the USB transaction complete ? */
 
 	/* Return if we are aborting. */
-	if (status == USBD_CANCELLED)
+	if (sc->sc_dying) {
 		return;
+	}
+	if (status == USBD_CANCELLED) {
+		mutex_enter(&sc->sc_lock);
+		sce->state |= UGEN_RA_WB_STOP;
+		mutex_exit(&sc->sc_lock);
+		return;
+	}
 
 	if (status != USBD_NORMAL_COMPLETION) {
 		DPRINTF(("ugen_bulkra_intr: status=%d\n", status));
+		mutex_enter(&sc->sc_lock);
 		sce->state |= UGEN_RA_WB_STOP;
+		mutex_exit(&sc->sc_lock);
 		if (status == USBD_STALLED)
 		    usbd_clear_endpoint_stall_async(sce->pipeh);
 		return;
 	}
 
+	if (sce->state & UGEN_RA_WRAP) {
+		printf ("ugen_bulkra_intr: USB read overrun!\n");
+		return;
+	}
+
 	usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);
 
-	/* Keep track of how much is in the buffer. */
-	sce->ra_wb_used += count;
+		DPRINTFN(5, ("ugen_bulkra_intr: count = %d(%d)\n", count,
+			sce->ra_load));
+	if ((count > 0) && (count < UGEN_BBSIZE)) {
+		xfer_done = 1;
+	} else {
+		if (count == 0) {
+			xfer_done = 1;
+		} else {
+			xfer_done = 0;
+		}
+	}
+
+	loadptr = sce->ra_buflist[sce->ra_load] +
+		sce->ra_buflen[sce->ra_load];
 
 	/* Copy data to buffer. */
 	tbuf = (char const *)usbd_get_buffer(sce->ra_wb_xfer);
-	n = min(count, sce->limit - sce->fill);
-	memcpy(sce->fill, tbuf, n);
+	n = min(count, 
+		(sce->ra_wb_bufsize - sce->ra_buflen[sce->ra_load]));
+
+	/* Keep track of how much is in the buffer. */
+	sce->ra_buflen[sce->ra_load]  += count;
+	if (sce->ra_buflen[sce->ra_load] >= sce->ra_wb_bufsize) {
+		xfer_done = 1;
+	}
+	memcpy(loadptr, tbuf, n);
 	tbuf += n;
 	count -= n;
-	sce->fill += n;
-	if (sce->fill == sce->limit)
-		sce->fill = sce->ibuf;
-	if (count > 0) {
-		memcpy(sce->fill, tbuf, count);
-		sce->fill += count;
+	loadptr += n;
+
+	if (count != 0) {
+		DPRINTFN(5, ("ugen_bulkra_intr: count = %d, should be 0\n", count));
+	}
+
+	/* If we're done with this USB transaction, move to the next buffer */
+	if (xfer_done) {
+		sce->ra_load ++;
+		if (sce->ra_load == BUFFCOUNT) {
+			sce->ra_load = 0;
+		}
+		if (sce->ra_load != sce->ra_read) {
+			mutex_enter(&sc->sc_lock);
+			sce->state |= UGEN_RA_COMPLETE;
+			mutex_exit(&sc->sc_lock);
+		}  else  {
+			mutex_enter(&sc->sc_lock);
+			sce->state |= UGEN_RA_WRAP;
+			mutex_exit(&sc->sc_lock);
+		}
 	}
 
 	/* Set up the next request if necessary. */
-	n = (sce->limit - sce->ibuf) - sce->ra_wb_used;
-	if (n > 0) {
-		usbd_setup_xfer(xfer, sce->pipeh, sce, NULL,
-		    min(n, sce->ra_wb_xferlen), USBD_NO_COPY,
-		    USBD_NO_TIMEOUT, ugen_bulkra_intr);
-		err = usbd_transfer(xfer);
-		if (err != USBD_IN_PROGRESS) {
-			printf("usbd_bulkra_intr: error=%d\n", err);
-			/*
-			 * The transfer has not been queued.  Setting STOP
-			 * will make us try again at the next read.
-			 */
+	if ((xfer_done == 0) && ((sce->state &UGEN_RA_WRAP) == 0)) {
+		xfer_flags = USBD_NO_COPY | 
+			(sce->state &UGEN_SHORT_OK ? USBD_SHORT_XFER_OK : 0);
+		n = min(sce->ra_wb_reqsize, 
+			(sce->ra_wb_bufsize - sce->ra_buflen[sce->ra_load]));
+		DPRINTFN(5, ("bulkra_intr: calling bulkra_intr, req = %d(%d)\n",
+     		n, sce->ra_load));
+		if (n > 0) {
+			usbd_setup_xfer(xfer, sce->pipeh, sce, NULL, n, xfer_flags,
+			    USBD_NO_TIMEOUT, ugen_bulkra_intr);
+			err = usbd_transfer(xfer);
+			if (err != USBD_IN_PROGRESS) {
+				printf("usbd_bulkra_intr: error=%d\n", err);
+				/*
+				 * The transfer has not been queued.  Setting STOP
+				 * will make us try again at the next read.
+				 */
+			mutex_enter(&sc->sc_lock);
+				sce->state |= UGEN_RA_WB_STOP;
+			mutex_exit(&sc->sc_lock);
+			}
+		} else {
+			mutex_enter(&sc->sc_lock);
 			sce->state |= UGEN_RA_WB_STOP;
+			mutex_exit(&sc->sc_lock);
 		}
 	}
 	else
+	{
+		mutex_enter(&sc->sc_lock);
 		sce->state |= UGEN_RA_WB_STOP;
+		mutex_exit(&sc->sc_lock);
+	}
 
+	mutex_enter(&sc->sc_lock);
 	if (sce->state & UGEN_ASLP) {
 		sce->state &= ~UGEN_ASLP;
 		DPRINTFN(5, ("ugen_bulkra_intr: waking %p\n", sce));
-		wakeup(sce);
+		cv_signal(&sce->cv);
 	}
+	mutex_exit(&sc->sc_lock);
 	selnotify(&sce->rsel, 0, 0);
 }
 
@@ -1233,17 +1502,28 @@
 		 usbd_status status)
 {
 	struct ugen_endpoint *sce = addr;
+	struct ugen_softc *sc = sce->sc;
 	u_int32_t count, n;
 	char *tbuf;
 	usbd_status err;
 
 	/* Return if we are aborting. */
-	if (status == USBD_CANCELLED)
+	if (sc->sc_dying) {
+		return;
+	}
+	if (status == USBD_CANCELLED) {
+		mutex_enter(&sc->sc_lock);
+		sce->state |= UGEN_RA_WB_STOP;
+		mutex_exit(&sc->sc_lock);
 		return;
+	}
 
-	if (status != USBD_NORMAL_COMPLETION) {
+	if ((status != USBD_NORMAL_COMPLETION) &&
+		(status != USBD_SHORT_XFER)) {
 		DPRINTF(("ugen_bulkwb_intr: status=%d\n", status));
+		mutex_enter(&sc->sc_lock);
 		sce->state |= UGEN_RA_WB_STOP;
+		mutex_exit(&sc->sc_lock);
 		if (status == USBD_STALLED)
 		    usbd_clear_endpoint_stall_async(sce->pipeh);
 		return;
@@ -1270,6 +1550,7 @@
 		if (count - n > 0)
 			memcpy(tbuf, sce->ibuf, count - n);
 
+		DPRINTF(("ugen_bulkwb_intr: transfer length: %d\n", count));
 		usbd_setup_xfer(xfer, sce->pipeh, sce, NULL,
 		    count, USBD_NO_COPY, USBD_NO_TIMEOUT, ugen_bulkwb_intr);
 		err = usbd_transfer(xfer);
@@ -1279,17 +1560,25 @@
 			 * The transfer has not been queued.  Setting STOP
 			 * will make us try again at the next write.
 			 */
+			mutex_enter(&sc->sc_lock);
 			sce->state |= UGEN_RA_WB_STOP;
+			mutex_exit(&sc->sc_lock);
 		}
 	}
 	else
-		sce->state |= UGEN_RA_WB_STOP;
+	{
+		mutex_enter(&sc->sc_lock);
+		sce->state |= UGEN_RA_WB_STOP | UGEN_RA_COMPLETE;
+		mutex_exit(&sc->sc_lock);
+	}
 
+	mutex_enter(&sc->sc_lock);
 	if (sce->state & UGEN_ASLP) {
 		sce->state &= ~UGEN_ASLP;
 		DPRINTFN(5, ("ugen_bulkwb_intr: waking %p\n", sce));
-		wakeup(sce);
+		cv_signal(&sce->cv);
 	}
+	mutex_exit(&sc->sc_lock);
 	selnotify(&sce->rsel, 0, 0);
 }
 #endif
@@ -1412,6 +1701,8 @@
 	struct usb_alt_interface *ai;
 	struct usb_string_desc *si;
 	u_int8_t conf, alt;
+	uint16_t xfer_flags;
+	int i, xfersize;
 
 	DPRINTFN(5, ("ugenioctl: cmd=%08lx\n", cmd));
 	if (sc->sc_dying)
@@ -1475,23 +1766,38 @@
 				usbd_free_xfer(sce->ra_wb_xfer);
 				return (ENOMEM);
 			}
-			sce->ibuf = malloc(sce->ra_wb_bufsize,
-					   M_USBDEV, M_WAITOK);
-			sce->fill = sce->cur = sce->ibuf;
-			sce->limit = sce->ibuf + sce->ra_wb_bufsize;
-			sce->ra_wb_used = 0;
+			for (i = 0; i < BUFFCOUNT; i ++) {
+				sce->ra_buflen[i] = 0;
+				sce->ra_buflist[i] = malloc(sce->ra_wb_bufsize,
+					   	M_USBDEV, M_WAITOK);
+			}
+			sce->ra_load = sce->ra_read = 0;
+			mutex_enter(&sc->sc_lock);
 			sce->state |= UGEN_BULK_RA;
-			sce->state &= ~UGEN_RA_WB_STOP;
-			/* Now start reading. */
+			sce->state &= ~(UGEN_RA_COMPLETE | UGEN_RA_WRAP);
+			mutex_exit(&sc->sc_lock);
+			xfer_flags = USBD_NO_COPY | 
+				(sce->state &UGEN_SHORT_OK ? USBD_SHORT_XFER_OK : 0);
+			xfersize = min(sce->ra_wb_reqsize, 
+				(sce->ra_wb_bufsize - sce->ra_buflen[sce->ra_load]));
+				DPRINTFN(5, ("ugenioctl: calling bulkra_intr, req = %d(%d)\n",
+     					xfersize, sce->ra_load));
 			usbd_setup_xfer(sce->ra_wb_xfer, sce->pipeh, sce,
-			    NULL,
-			    min(sce->ra_wb_xferlen, sce->ra_wb_bufsize),
-			    USBD_NO_COPY, USBD_NO_TIMEOUT,
+			    NULL, xfersize,
+			    xfer_flags, USBD_NO_TIMEOUT,
 			    ugen_bulkra_intr);
 			err = usbd_transfer(sce->ra_wb_xfer);
 			if (err != USBD_IN_PROGRESS) {
+				mutex_enter(&sc->sc_lock);
 				sce->state &= ~UGEN_BULK_RA;
-				free(sce->ibuf, M_USBDEV);
+				mutex_exit(&sc->sc_lock);
+				for (i = 0; i < BUFFCOUNT;i ++) {
+					if (sce->ra_buflist[i] != NULL) {
+						free(sce->ra_buflist[i], M_USBDEV);
+						sce->ra_buflist[i] = NULL;
+					}
+					sce->ra_buflen[i] = 0;
+				}
 				sce->ibuf = NULL;
 				usbd_free_xfer(sce->ra_wb_xfer);
 				return (EIO);
@@ -1501,15 +1807,25 @@
 			if (!(sce->state & UGEN_BULK_RA))
 				return (0);
 
+			mutex_enter(&sc->sc_lock);
 			sce->state &= ~UGEN_BULK_RA;
+			mutex_exit(&sc->sc_lock);
 			usbd_abort_pipe(sce->pipeh);
+			usbd_close_pipe(sce->pipeh);
+			sce->pipeh = NULL;
 			usbd_free_xfer(sce->ra_wb_xfer);
 			/*
 			 * XXX Discard whatever's in the buffer, but we
 			 * should keep it around and drain the buffer
 			 * instead.
 			 */
-			free(sce->ibuf, M_USBDEV);
+			for (i = 0; i < BUFFCOUNT;i ++) {
+				if (sce->ra_buflist[i] != NULL) {
+					free(sce->ra_buflist[i], M_USBDEV);
+					sce->ra_buflist[i] = NULL;
+				}
+				sce->ra_buflen[i] = 0;
+			}
 			sce->ibuf = NULL;
 		}
 		return (0);
@@ -1553,19 +1869,25 @@
 			sce->fill = sce->cur = sce->ibuf;
 			sce->limit = sce->ibuf + sce->ra_wb_bufsize;
 			sce->ra_wb_used = 0;
-			sce->state |= UGEN_BULK_WB | UGEN_RA_WB_STOP;
+			mutex_enter(&sc->sc_lock);
+			sce->state |= UGEN_BULK_WB | UGEN_RA_WB_STOP | UGEN_RA_COMPLETE;
+			mutex_exit(&sc->sc_lock);
 		} else {
 			/* Only turn WB off if it's currently on. */
 			if (!(sce->state & UGEN_BULK_WB))
 				return (0);
 
+			mutex_enter(&sc->sc_lock);
 			sce->state &= ~UGEN_BULK_WB;
+			mutex_exit(&sc->sc_lock);
 			/*
 			 * XXX Discard whatever's in the buffer, but we
 			 * should keep it around and keep writing to 
 			 * drain the buffer instead.
 			 */
 			usbd_abort_pipe(sce->pipeh);
+			usbd_close_pipe(sce->pipeh);
+			sce->pipeh = NULL;
 			usbd_free_xfer(sce->ra_wb_xfer);
 			free(sce->ibuf, M_USBDEV);
 			sce->ibuf = NULL;
@@ -1841,14 +2163,29 @@
 {
 	int endpt = UGENENDPOINT(dev);
 	struct ugen_softc *sc;
-	int error;
+	int error, s;
 
 	USB_GET_SC_OPEN(ugen, UGENUNIT(dev), sc);
 
+	if (sc->sc_dying)
+		return (EIO);
+
+	mutex_enter(&sc->sc_lock);
 	sc->sc_refcnt++;
+	mutex_exit(&sc->sc_lock);
+
 	error = ugen_do_ioctl(sc, endpt, cmd, addr, flag, l);
-	if (--sc->sc_refcnt < 0)
+
+	mutex_enter(&sc->sc_lock);
+	if (--sc->sc_refcnt < 0) {
+		mutex_exit(&sc->sc_lock);
+		s = splusb();
 		usb_detach_wakeup(USBDEV(sc->sc_dev));
+		splx(s);
+	} else {
+		mutex_exit(&sc->sc_lock);
+	}
+
 	return (error);
 }
 
@@ -1858,10 +2195,15 @@
 	struct ugen_softc *sc;
 	struct ugen_endpoint *sce_in, *sce_out;
 	int revents = 0;
-	int s;
+	int xfersize, err;
+	uint16_t xfer_flags;
 
 	USB_GET_SC_OPEN(ugen, UGENUNIT(dev), sc);
 
+	DPRINTFN(5,
+	("%s: ugenpoll: unit: %d, endpoint: %d\n", USBDEVNAME(sc->sc_dev), 
+	UGENUNIT(dev), UGENENDPOINT(dev)));
+
 	if (sc->sc_dying)
 		return (POLLHUP);
 
@@ -1880,7 +2222,7 @@
 		return (POLLERR);
 	}
 #endif
-	s = splusb();
+	mutex_enter(&sc->sc_lock);
 	if (sce_in && sce_in->pipeh && (events & (POLLIN | POLLRDNORM)))
 		switch (sce_in->edesc->bmAttributes & UE_XFERTYPE) {
 		case UE_INTERRUPT:
@@ -1898,11 +2240,41 @@
 		case UE_BULK:
 #ifdef UGEN_BULK_RA_WB
 			if (sce_in->state & UGEN_BULK_RA) {
-				if (sce_in->ra_wb_used > 0)
+				DPRINTFN(5,
+				("%s: ra_wb_used: %d(%d)\n",USBDEVNAME(sc->sc_dev),
+				sce_in->ra_buflen[sce_in->ra_read], sce_in->ra_read));
+				DPRINTFN(5,
+				("%s: ra_wb_state: 0x%x\n",USBDEVNAME(sc->sc_dev),
+				sce_in->state));
+				if (sce_in->state & UGEN_RA_COMPLETE) {
 					revents |= events &
 					    (POLLIN | POLLRDNORM);
+				}
 				else
+				{
 					selrecord(l, &sce_in->rsel);
+				xfer_flags = USBD_NO_COPY | 
+					(sce_in->state &UGEN_SHORT_OK ? USBD_SHORT_XFER_OK : 0);
+				xfersize = min(sce_in->ra_wb_reqsize, 
+					(sce_in->ra_wb_bufsize - sce_in->ra_buflen[sce_in->ra_load]));
+				if ((xfersize) &&
+					((sce_in->state & UGEN_RA_WRAP) == 0) && 
+					(sce_in->state & UGEN_RA_WB_STOP)) {
+					DPRINTFN(5, ("ugenpoll: calling bulkra_intr, req = %d(%d)\n",
+     					xfersize, sce_in->ra_load));
+					usbd_setup_xfer(sce_in->ra_wb_xfer, sce_in->pipeh, sce_in,
+				    		NULL, xfersize,
+				    		xfer_flags, USBD_NO_TIMEOUT,
+				    		ugen_bulkra_intr);
+					sce_in->state &= ~UGEN_RA_WB_STOP;
+					mutex_exit(&sc->sc_lock);
+					err = usbd_transfer(sce_in->ra_wb_xfer);
+					mutex_enter(&sc->sc_lock);
+					if (err != USBD_IN_PROGRESS) {
+						sce_in->state |= UGEN_RA_WB_STOP; /* Try again */
+					}
+				}
+				}
 				break;
 			}
 #endif
@@ -1946,7 +2318,7 @@
 		}
 
 
-	splx(s);
+	mutex_exit(&sc->sc_lock);
 	return (revents);
 }
 
@@ -1954,11 +2326,11 @@
 filt_ugenrdetach(struct knote *kn)
 {
 	struct ugen_endpoint *sce = kn->kn_hook;
-	int s;
+	struct ugen_softc *sc = sce->sc;
 
-	s = splusb();
+	mutex_enter(&sc->sc_lock);
 	SLIST_REMOVE(&sce->rsel.sel_klist, kn, knote, kn_selnext);
-	splx(s);
+	mutex_exit(&sc->sc_lock);
 }
 
 static int
@@ -2054,7 +2426,6 @@
 	struct ugen_softc *sc;
 	struct ugen_endpoint *sce;
 	struct klist *klist;
-	int s;
 
 	USB_GET_SC_OPEN(ugen, UGENUNIT(dev), sc);
 
@@ -2128,9 +2499,9 @@
 
 	kn->kn_hook = sce;
 
-	s = splusb();
+	mutex_enter(&sc->sc_lock);
 	SLIST_INSERT_HEAD(klist, kn, kn_selnext);
-	splx(s);
+	mutex_exit(&sc->sc_lock);
 
 	return (0);
 }



Home | Main Index | Thread Index | Old Index