Subject: kern/25960: NetBSD-current usb ugen(4) isochronous fix/enhance
To: None <gnats-bugs@gnats.NetBSD.org>
From: None <matthew.gream@pobox.com>
List: netbsd-bugs
Date: 06/18/2004 01:00:38
>Number:         25960
>Category:       kern
>Synopsis:       ugen(4) isochronous handling correction and tx support
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Fri Jun 18 01:02:00 UTC 2004
>Closed-Date:
>Last-Modified:
>Originator:     Matthew Gream <matthew.gream@pobox.com>
>Release:        NetBSD-current (2004/06/18 CVS HEAD)
>Organization:
>Environment:
System: NetBSD usbdev 2.0F NetBSD 2.0F (USBDEV) #101: Thu Jun 17 23:30:20 UTC 2004 root@dev:/usr/src/sys/arch/i386/compile/USBDEV i386
Architecture: i386
Machine: i386
>Description:

	Problems with ugen(4) isoc handling:

	1. panic during ugenopen as a result of early isr completion in
	in ohci_device_isoc_transfer, causing 
	ohci_device_isoc_start(SIMPLEQ_FIRST(&xfer->pipe->queue)) to
	deliver null pointer resulting in panic in
	ohci_device_isoc_start(usbd_xfer_handle xfer) on deref of xfer.

	2. hcpriv = NULL in ohci_device_isoc_done(usbd_xfer_handle xfer)
	would overwrite newly set value from usbd_transfer call in 
	ugen_isoc_rintr, causing subseqently isoc transfers to fail.

	3. circular buffer broken and generally poor.

	4. supports read only, no write.

	(FreeBSD/NetBSD/OpenBSD PR's cover some of this)

>How-To-Repeat:

	Using full-duplex 256kbps USB A/D w/ isoc 80 byte endpoints; 
	150Mhz Pentium/MMX, USB 1.0 OHCI.

>Fix:
	Solutions:

	1. locked usb around usbd_transfers in ugenopen.

	2. rolled hcpriv = NULL from all _done methods up into ohci_softintr,
	   and moved post-callback xfer dereferences in usbd_transfer_complete
	   into temporaries.

	3. implemented IBUF_* macros.

	4. implemented write support using:
		- ioctl USB_SET_ISOC_RATE, USB_GET_ISOC_RATE to specify
		  outgoing frame sizes (which may be lower than maximum
		  supported by interface);
		- updated fs methods (do_write, do_write, do_poll, etc)
		  to support transfer and select handling for isoc IN and 
		  OUT cases.

	Notes:

	- isorate for endpoints is inherited from sce (device) isorate,
	both can be directly manipulated by ioctl, would be nicer to allow
	user to directly specify outgoing frame sizes.

	Limitations: 

	- first isoc tx xfers will be null filled frames.

	- if write queue empty, isoc tx xfers will be silently null filled.

	Tested:

	With above configuration, stable under moderate load, not stress
	tested (yet).

	Patched:
	: ohci.c
	: ohcivar.h
	: ugen.c
	: usb.h
	: usbdi.c

Index: ohci.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ohci.c,v
retrieving revision 1.146
diff -u -u -r1.146 ohci.c
--- ohci.c	29 Dec 2003 08:17:10 -0000	1.146
+++ ohci.c	18 Jun 2004 00:12:15 -0000
@@ -1443,6 +1443,7 @@
 			if (uedir == UE_DIR_IN &&
 			    xfer->status == USBD_NORMAL_COMPLETION)
 				xfer->actlen = actlen;
+			xfer->hcpriv = NULL;
 
 			s = splusb();
 			usb_transfer_complete(xfer);
@@ -1471,7 +1472,6 @@
 		panic("ohci_device_ctrl_done: not a request");
 	}
 #endif
-	xfer->hcpriv = NULL;
 }
 
 void
@@ -1486,8 +1486,6 @@
 	DPRINTFN(10,("ohci_device_intr_done: xfer=%p, actlen=%d\n",
 		     xfer, xfer->actlen));
 
-	xfer->hcpriv = NULL;
-
 	if (xfer->pipe->repeat) {
 		data = opipe->tail.td;
 		tail = ohci_alloc_std(sc); /* XXX should reuse TD */
@@ -1523,8 +1521,6 @@
 {
 	DPRINTFN(10,("ohci_device_bulk_done: xfer=%p, actlen=%d\n",
 		     xfer, xfer->actlen));
-
-	xfer->hcpriv = NULL;
 }
 
 void
@@ -1564,13 +1560,13 @@
 void
 ohci_root_intr_done(usbd_xfer_handle xfer)
 {
-	xfer->hcpriv = NULL;
+	DPRINTFN(10,("ohci_root_intr_done: xfer=%p\n", xfer));
 }
 
 void
 ohci_root_ctrl_done(usbd_xfer_handle xfer)
 {
-	xfer->hcpriv = NULL;
+	DPRINTFN(10,("ohci_root_ctrl_done: xfer=%p\n", xfer));
 }
 
 /*
@@ -3374,10 +3370,7 @@
 void
 ohci_device_isoc_done(usbd_xfer_handle xfer)
 {
-
-	DPRINTFN(1,("ohci_device_isoc_done: xfer=%p\n", xfer));
-
-	xfer->hcpriv = NULL;
+	DPRINTFN(10,("ohci_device_isoc_done: xfer=%p\n", xfer));
 }
 
 usbd_status
Index: ohcivar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ohcivar.h,v
retrieving revision 1.32
diff -u -u -r1.32 ohcivar.h
--- ohcivar.h	22 Feb 2003 05:24:17 -0000	1.32
+++ ohcivar.h	18 Jun 2004 00:12:15 -0000
@@ -146,7 +146,7 @@
 	struct usb_task	abort_task;
 };
 
-#define OXFER(xfer) ((struct ehci_xfer *)(xfer))
+#define OXFER(xfer) ((struct ohci_xfer *)(xfer))
 
 usbd_status	ohci_init(ohci_softc_t *);
 int		ohci_intr(void *);
Index: ugen.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/ugen.c,v
retrieving revision 1.67
diff -u -u -r1.67 ugen.c
--- ugen.c	23 Apr 2004 17:25:25 -0000	1.67
+++ ugen.c	18 Jun 2004 00:12:17 -0000
@@ -83,8 +83,8 @@
 #define	UGEN_BBSIZE	1024
 
 #define	UGEN_NISOFRAMES	500	/* 0.5 seconds worth */
-#define UGEN_NISOREQS	6	/* number of outstanding xfer requests */
-#define UGEN_NISORFRMS	4	/* number of frames (miliseconds) per req */
+#define UGEN_NISOREQS	4	/* number of outstanding xfer requests */
+#define UGEN_NISORFRMS	10	/* number of frames (milliseconds) per req */
 
 struct ugen_endpoint {
 	struct ugen_softc *sc;
@@ -101,6 +101,9 @@
 	u_char *limit;		/* end of circular buffer (isoc) */
 	u_char *cur;		/* current read location (isoc) */
 	u_int32_t timeout;
+	int isorate;
+	int isoflen;
+	int isofres;
 	struct isoreq {
 		struct ugen_endpoint *sce;
 		usbd_xfer_handle xfer;
@@ -109,6 +112,61 @@
 	} isoreqs[UGEN_NISOREQS];
 };
 
+#define ISOC_CALC_FLEN(r) \
+	((r / USB_FRAMES_PER_SECOND) & (~1))
+#define ISOC_CALC_FRES(r) \
+	(((r - USB_FRAMES_PER_SECOND * ISOC_CALC_FLEN(r)) / \
+		(USB_FRAMES_PER_SECOND / UGEN_NISORFRMS)) >> 1)
+#define ISOC_MAKE_FLEN(l,r,o) \
+	(l + ((o < r) ? 2 : 0))
+
+#define IBUF_SPACE_ALLOC(sce,s,d) { \
+	sce->ibuf = malloc(s, d, M_WAITOK); \
+	sce->cur = sce->fill = sce->ibuf; \
+	sce->limit = sce->ibuf + (s); \
+}
+#define	IBUF_SPACE_FREE(sce,d) \
+	if (sce->ibuf != NULL) { \
+		free(sce->ibuf, d); \
+		sce->ibuf = NULL; \
+	}
+#define	IBUF_SPACE_PNTR(sce) \
+	(sce->ibuf)
+#define IBUF_SPACE_WR_PNTR(sce) \
+	(sce->fill)
+#define IBUF_SPACE_WR_SIZE(sce) \
+	(sce->cur > sce->fill ? \
+		(sce->cur - sce->fill) - 1 : \
+		(sce->limit - sce->fill) + (sce->cur - sce->ibuf) - 1)
+#define	IBUF_SPACE_WR_ZERO(sce) \
+	(sce->fill + 1 == sce->cur) || \
+		(sce->fill + 1 == sce->limit && sce->cur == sce->ibuf)
+#define IBUF_SPACE_WR_CHNK(sce) \
+	(sce->cur > sce->fill ? \
+		(sce->cur - sce->fill) - 1 : \
+		(sce->limit - sce->fill) - (sce->cur == sce->ibuf ? 1 : 0))
+#define IBUF_SPACE_WR_INCR(sce, n) { \
+	sce->fill += (n); \
+	if (sce->fill >= sce->limit) \
+		sce->fill = sce->ibuf + (sce->fill - sce->limit); \
+	}
+#define IBUF_SPACE_RD_PNTR(sce) \
+	(sce->cur)
+#define IBUF_SPACE_RD_SIZE(sce) \
+	(sce->fill >= sce->cur ? \
+		sce->fill - sce->cur : \
+		(sce->limit - sce->cur) + (sce->fill - sce->ibuf))
+#define IBUF_SPACE_RD_ZERO(sce) \
+	(sce->fill == sce->cur)
+#define IBUF_SPACE_RD_CHNK(sce) \
+	(sce->fill > sce->cur ? \
+		sce->fill - sce->cur : sce->limit - sce->cur)
+#define IBUF_SPACE_RD_INCR(sce, n) { \
+	sce->cur += (n); \
+	if (sce->cur >= sce->limit) \
+		sce->cur = sce->ibuf + (sce->cur - sce->limit); \
+	}
+
 struct ugen_softc {
 	USBBASEDEVICE sc_dev;		/* base device */
 	usbd_device_handle sc_udev;
@@ -117,6 +175,7 @@
 	struct ugen_endpoint sc_endpoints[USB_MAX_ENDPOINTS][2];
 #define OUT 0
 #define IN  1
+	int sc_isorate;
 
 	int sc_refcnt;
 	u_char sc_dying;
@@ -167,12 +226,13 @@
 
 Static void ugenintr(usbd_xfer_handle xfer, usbd_private_handle addr,
 		     usbd_status status);
-Static void ugen_isoc_rintr(usbd_xfer_handle xfer, usbd_private_handle addr,
-			    usbd_status status);
+Static void ugenintr_isoc(usbd_xfer_handle xfer, usbd_private_handle addr,
+			  usbd_status status);
 Static int ugen_do_read(struct ugen_softc *, int, struct uio *, int);
 Static int ugen_do_write(struct ugen_softc *, int, struct uio *, int);
 Static int ugen_do_ioctl(struct ugen_softc *, int, u_long,
 			 caddr_t, int, usb_proc_ptr);
+Static int ugen_do_poll(struct ugen_softc *, int, int, usb_proc_ptr);
 Static int ugen_set_config(struct ugen_softc *sc, int configno);
 Static usb_config_descriptor_t *ugen_get_cdesc(struct ugen_softc *sc,
 					       int index, int *lenp);
@@ -324,6 +384,7 @@
 	usbd_xfer_handle xfer;
 	void *buf;
 	int i, j;
+	int s;
 
 	USB_GET_SC_OPEN(ugen, unit, sc);
 
@@ -366,18 +427,22 @@
 			isize = UGETW(edesc->wMaxPacketSize);
 			if (isize == 0)	/* shouldn't happen */
 				return (EINVAL);
-			sce->ibuf = malloc(isize, M_USBDEV, M_WAITOK);
 			DPRINTFN(5, ("ugenopen: intr endpt=%d,isize=%d\n",
 				     endpt, isize));
-			if (clalloc(&sce->q, UGEN_IBSIZE, 0) == -1)
+			IBUF_SPACE_ALLOC(sce, isize, M_USBDEV);
+			if (!IBUF_SPACE_PNTR(sce))
+				return (ENOMEM);
+			if (clalloc(&sce->q, UGEN_IBSIZE, 0) == -1) {
+				IBUF_SPACE_FREE(sce, M_USBDEV);
 				return (ENOMEM);
+			}
 			err = usbd_open_pipe_intr(sce->iface,
 				  edesc->bEndpointAddress,
 				  USBD_SHORT_XFER_OK, &sce->pipeh, sce,
-				  sce->ibuf, isize, ugenintr,
+				  IBUF_SPACE_PNTR(sce), isize, ugenintr,
 				  USBD_DEFAULT_INTERVAL);
 			if (err) {
-				free(sce->ibuf, M_USBDEV);
+				IBUF_SPACE_FREE(sce, M_USBDEV);
 				clfree(&sce->q);
 				return (EIO);
 			}
@@ -390,25 +455,25 @@
 				return (EIO);
 			break;
 		case UE_ISOCHRONOUS:
-			if (dir == OUT)
-				return (EINVAL);
 			isize = UGETW(edesc->wMaxPacketSize);
 			if (isize == 0)	/* shouldn't happen */
 				return (EINVAL);
-			sce->ibuf = malloc(isize * UGEN_NISOFRAMES,
-				M_USBDEV, M_WAITOK);
-			sce->cur = sce->fill = sce->ibuf;
-			sce->limit = sce->ibuf + isize * UGEN_NISOFRAMES;
 			DPRINTFN(5, ("ugenopen: isoc endpt=%d, isize=%d\n",
 				     endpt, isize));
+			IBUF_SPACE_ALLOC(sce, isize * UGEN_NISOFRAMES, 
+				M_USBDEV);
+			if (!IBUF_SPACE_PNTR(sce))
+				return (ENOMEM);
 			err = usbd_open_pipe(sce->iface,
 				  edesc->bEndpointAddress, 0, &sce->pipeh);
 			if (err) {
-				free(sce->ibuf, M_USBDEV);
+				IBUF_SPACE_FREE(sce, M_USBDEV);
 				return (EIO);
 			}
+			sce->isorate = sc->sc_isorate;
+			sce->isoflen = ISOC_CALC_FLEN(sce->isorate);
+			sce->isofres = ISOC_CALC_FRES(sce->isorate);
 			for(i = 0; i < UGEN_NISOREQS; ++i) {
-				sce->isoreqs[i].sce = sce;
 				xfer = usbd_alloc_xfer(sc->sc_udev);
 				if (xfer == 0)
 					goto bad;
@@ -420,20 +485,30 @@
 					goto bad;
 				}
 				sce->isoreqs[i].dmabuf = buf;
+				sce->isoreqs[i].sce = sce;
+				if (dir == OUT)
+					memset(buf, 0, isize * UGEN_NISORFRMS);
 				for(j = 0; j < UGEN_NISORFRMS; ++j)
-					sce->isoreqs[i].sizes[j] = isize;
+					sce->isoreqs[i].sizes[j] = 
+						(dir == IN) ? isize :
+						ISOC_MAKE_FLEN(sce->isoflen,
+							sce->isofres, j);	
 				usbd_setup_isoc_xfer
 					(xfer, sce->pipeh, &sce->isoreqs[i],
 					 sce->isoreqs[i].sizes,
 					 UGEN_NISORFRMS, USBD_NO_COPY,
-					 ugen_isoc_rintr);
-				(void)usbd_transfer(xfer);
+					 ugenintr_isoc);
 			}
+			s = splusb();
+			for(i = 0; i < UGEN_NISOREQS; ++i)
+				(void)usbd_transfer(sce->isoreqs[i].xfer);
+			splx(s);
 			DPRINTFN(5, ("ugenopen: isoc open done\n"));
 			break;
 		bad:
 			while (--i >= 0) /* implicit buffer free */
 				usbd_free_xfer(sce->isoreqs[i].xfer);
+			IBUF_SPACE_FREE(sce, M_USBDEV);
 			return (ENOMEM);
 		case UE_CONTROL:
 			sce->timeout = USBD_DEFAULT_TIMEOUT;
@@ -497,11 +572,7 @@
 			break;
 		}
 
-		if (sce->ibuf != NULL) {
-			free(sce->ibuf, M_USBDEV);
-			sce->ibuf = NULL;
-			clfree(&sce->q);
-		}
+		IBUF_SPACE_FREE(sce, M_USBDEV);
 	}
 	sc->sc_is_open[endpt] = 0;
 
@@ -607,43 +678,33 @@
 		break;
 	case UE_ISOCHRONOUS:
 		s = splusb();
-		while (sce->cur == sce->fill) {
-			if (flag & IO_NDELAY) {
-				splx(s);
-				return (EWOULDBLOCK);
-			}
-			sce->state |= UGEN_ASLP;
-			DPRINTFN(5, ("ugenread: sleep on %p\n", sce));
-			error = tsleep(sce, PZERO | PCATCH, "ugenri", 0);
-			DPRINTFN(5, ("ugenread: woke, error=%d\n", error));
-			if (sc->sc_dying)
-				error = EIO;
-			if (error) {
-				sce->state &= ~UGEN_ASLP;
-				break;
+		while (uio->uio_resid > 0 && !error) {
+			if (IBUF_SPACE_RD_ZERO(sce)) {
+				if (flag & IO_NDELAY) {
+					error = EWOULDBLOCK;
+					break;
+				}
+				sce->state |= UGEN_ASLP;
+				DPRINTFN(5, ("ugenread: sleep on %p\n", sce));
+				error = tsleep(sce, PZERO | PCATCH, "ugenri", 0);
+				DPRINTFN(5, ("ugenread: woke, error=%d\n", error));
+				if (sc->sc_dying)
+					error = EIO;
+				if (error)
+					sce->state &= ~UGEN_ASLP;
+			} else {
+				n = min(IBUF_SPACE_RD_CHNK(sce), uio->uio_resid);
+
+				DPRINTFN(5, ("ugenread: isoc read %d bytes\n", n));
+
+				error = uiomove(IBUF_SPACE_RD_PNTR(sce), n, uio);
+				if (!error)
+					IBUF_SPACE_RD_INCR(sce, n);
 			}
 		}
-
-		while (sce->cur != sce->fill && uio->uio_resid > 0 && !error) {
-			if(sce->fill > sce->cur)
-				n = min(sce->fill - sce->cur, uio->uio_resid);
-			else
-				n = min(sce->limit - sce->cur, uio->uio_resid);
-
-			DPRINTFN(5, ("ugenread: isoc got %d chars\n", n));
-
-			/* Copy the data to the user process. */
-			error = uiomove(sce->cur, n, uio);
-			if (error)
-				break;
-			sce->cur += n;
-			if(sce->cur >= sce->limit)
-				sce->cur = sce->ibuf;
-		}
 		splx(s);
 		break;
 
-
 	default:
 		return (ENXIO);
 	}
@@ -675,6 +736,7 @@
 	char buf[UGEN_BBSIZE];
 	usbd_xfer_handle xfer;
 	usbd_status err;
+	int s;
 
 	DPRINTFN(5, ("%s: ugenwrite: %d\n", USBDEVNAME(sc->sc_dev), endpt));
 
@@ -719,6 +781,35 @@
 		}
 		usbd_free_xfer(xfer);
 		break;
+	case UE_ISOCHRONOUS:
+		s = splusb();
+		while(uio->uio_resid > 0 && !error) {
+			if (IBUF_SPACE_WR_ZERO(sce)) {
+				if (flag & IO_NDELAY) {
+					error = EWOULDBLOCK;
+					break;
+				}
+				sce->state |= UGEN_ASLP;
+				DPRINTFN(5, ("ugenwrite: sleep on %p\n", sce));
+				error = tsleep(sce, PZERO | PCATCH, "ugenri", 0);
+				DPRINTFN(5, ("ugenwrite: woke, error=%d\n", error));
+				if (sc->sc_dying)
+					error = EIO;
+				if (error)
+					sce->state &= ~UGEN_ASLP;
+			} else {
+				n = min(IBUF_SPACE_WR_CHNK(sce), uio->uio_resid);
+				DPRINTFN(5, ("ugenwrite: isoc write %d bytes\n", n));
+
+				error = uiomove(IBUF_SPACE_WR_PNTR(sce), n, uio);
+
+				if (!error)
+					IBUF_SPACE_WR_INCR(sce, n);
+			}
+		}
+		splx(s);
+		break;
+
 	default:
 		return (ENXIO);
 	}
@@ -853,59 +944,73 @@
 }
 
 Static void
-ugen_isoc_rintr(usbd_xfer_handle xfer, usbd_private_handle addr,
-		usbd_status status)
+ugenintr_isoc(usbd_xfer_handle xfer, usbd_private_handle addr,
+	      usbd_status status)
 {
 	struct isoreq *req = addr;
 	struct ugen_endpoint *sce = req->sce;
-	u_int32_t count, n;
-	int i, isize;
+	u_int32_t count;
+	u_char* buf;
+	int i, dir, psize, n, len;
 
 	/* Return if we are aborting. */
 	if (status == USBD_CANCELLED)
 		return;
 
+	dir = UE_GET_DIR(sce->edesc->bEndpointAddress) == UE_DIR_IN ? IN : OUT;
+	psize = UGETW(sce->edesc->wMaxPacketSize);
+
 	usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);
-	DPRINTFN(5,("ugen_isoc_rintr: xfer %ld, count=%d\n",
-	    (long)(req - sce->isoreqs), count));
+	DPRINTFN(5,("ugenintr_isoc: xfer %ld, dir %s, count=%d\n",
+	    (long)(req - sce->isoreqs), (dir == IN) ? "IN" : "OUT", count));
 
-	/* throw away oldest input if the buffer is full */
-	if(sce->fill < sce->cur && sce->cur <= sce->fill + count) {
-		sce->cur += count;
-		if(sce->cur >= sce->limit)
-			sce->cur = sce->ibuf + (sce->limit - sce->cur);
-		DPRINTFN(5, ("ugen_isoc_rintr: throwing away %d bytes\n",
-			     count));
-	}
-
-	isize = UGETW(sce->edesc->wMaxPacketSize);
-	for (i = 0; i < UGEN_NISORFRMS; i++) {
-		u_int32_t actlen = req->sizes[i];
-		char const *buf = (char const *)req->dmabuf + isize * i;
-
-		/* copy data to buffer */
-		while (actlen > 0) {
-			n = min(actlen, sce->limit - sce->fill);
-			memcpy(sce->fill, buf, n);
-
-			buf += n;
-			actlen -= n;
-			sce->fill += n;
-			if(sce->fill == sce->limit)
-				sce->fill = sce->ibuf;
+	buf = (u_char *)req->dmabuf;
+
+	if (dir == OUT) {
+		for (i = 0; i < UGEN_NISORFRMS; i++) {
+			len = ISOC_MAKE_FLEN(sce->isoflen, sce->isofres, i);
+			req->sizes[i] = len;
+
+			for (; len > 0 && !IBUF_SPACE_RD_ZERO(sce);
+					buf += n, len -= n) {
+				n = min(IBUF_SPACE_RD_CHNK(sce), len);
+				memcpy(buf, IBUF_SPACE_RD_PNTR(sce), n);
+				IBUF_SPACE_RD_INCR(sce, n);
+			}
+
+			if (len > 0) {
+				memset(buf, 0, len);
+				buf += len;
+			}
 		}
+	} else {
+		for (i = 0; i < UGEN_NISORFRMS; i++) {
+			len = req->sizes[i];
+
+			if ((n = IBUF_SPACE_WR_SIZE(sce)) < len) {
+				IBUF_SPACE_RD_INCR(sce, len - n);
+				DPRINTFN(5, ("ugenintr_isoc: throw %d bytes\n",
+					len - n));
+			}
+
+			for (; len > 0; buf += n, len -= n) {
+				n = min(IBUF_SPACE_WR_CHNK(sce), len);
+				memcpy(IBUF_SPACE_WR_PNTR(sce), buf, n);
+				IBUF_SPACE_WR_INCR(sce, n);
+			}
 
-		/* setup size for next transfer */
-		req->sizes[i] = isize;
+			buf += (psize - req->sizes[i]);
+			req->sizes[i] = psize;
+		}
 	}
 
 	usbd_setup_isoc_xfer(xfer, sce->pipeh, req, req->sizes, UGEN_NISORFRMS,
-			     USBD_NO_COPY, ugen_isoc_rintr);
+			     USBD_NO_COPY, ugenintr_isoc);
 	(void)usbd_transfer(xfer);
 
 	if (sce->state & UGEN_ASLP) {
 		sce->state &= ~UGEN_ASLP;
-		DPRINTFN(5, ("ugen_isoc_rintr: waking %p\n", sce));
+		DPRINTFN(5, ("ugenintr_isoc: waking %p\n", sce));
 		wakeup(sce);
 	}
 	selnotify(&sce->rsel, 0);
@@ -1059,6 +1164,28 @@
 			return (EINVAL);
 		sce->timeout = *(int *)addr;
 		return (0);
+	case USB_GET_ISOC_RATE:	
+		if (endpt == USB_CONTROL_ENDPOINT)
+			*(int *)addr = sc->sc_isorate;
+		else {
+			sce = &sc->sc_endpoints[endpt][OUT];
+			if (sce == NULL || sce->edesc == NULL)
+				return (EINVAL);
+			*(int *)addr = sce->isorate;
+		}
+		return (0);
+	case USB_SET_ISOC_RATE:	
+		if (endpt == USB_CONTROL_ENDPOINT)
+			sc->sc_isorate = *(int *)addr;
+		else {
+			sce = &sc->sc_endpoints[endpt][OUT];
+			if (sce == NULL || sce->edesc == NULL)
+				return (EINVAL);
+			sce->isorate = *(int *)addr;
+			sce->isoflen = ISOC_CALC_FLEN(sce->isorate);
+			sce->isofres = ISOC_CALC_FRES(sce->isorate);
+		}
+		return (0);
 	default:
 		break;
 	}
@@ -1296,59 +1423,58 @@
 	return (error);
 }
 
-int
-ugenpoll(dev_t dev, int events, usb_proc_ptr p)
+Static int
+ugen_do_poll(struct ugen_softc *sc, int endpt, int events, usb_proc_ptr p)
 {
-	struct ugen_softc *sc;
-	struct ugen_endpoint *sce;
+	struct ugen_endpoint *sce_in, *sce_out;
+	usb_endpoint_descriptor_t *edesc;
 	int revents = 0;
 	int s;
 
-	USB_GET_SC(ugen, UGENUNIT(dev), sc);
-
 	if (sc->sc_dying)
 		return (EIO);
 
-	/* XXX always IN */
-	sce = &sc->sc_endpoints[UGENENDPOINT(dev)][IN];
-	if (sce == NULL)
+	sce_in = &sc->sc_endpoints[endpt][IN];
+	sce_out = &sc->sc_endpoints[endpt][OUT];
+	if (sce_in == NULL && sce_out == NULL)
 		return (EINVAL);
-#ifdef DIAGNOSTIC
-	if (!sce->edesc) {
-		printf("ugenpoll: no edesc\n");
-		return (EIO);
-	}
-	if (!sce->pipeh) {
-		printf("ugenpoll: no pipe\n");
+
+	if (sce_in && sce_in->edesc)
+		edesc = sce_in->edesc;
+	else if (sce_out && sce_out->edesc)
+		edesc = sce_out->edesc;
+	else /* no edesc! */
 		return (EIO);
-	}
-#endif
+
 	s = splusb();
-	switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
+	switch (edesc->bmAttributes & UE_XFERTYPE) {
 	case UE_INTERRUPT:
-		if (events & (POLLIN | POLLRDNORM)) {
-			if (sce->q.c_cc > 0)
+		if (sce_in && events & (POLLIN | POLLRDNORM)) {
+			if (sce_in->q.c_cc > 0)
 				revents |= events & (POLLIN | POLLRDNORM);
 			else
-				selrecord(p, &sce->rsel);
+				selrecord(p, &sce_in->rsel);
 		}
 		break;
 	case UE_ISOCHRONOUS:
-		if (events & (POLLIN | POLLRDNORM)) {
-			if (sce->cur != sce->fill)
+		if (sce_in && events & (POLLIN | POLLRDNORM)) {
+			if (!IBUF_SPACE_RD_ZERO(sce_in))
 				revents |= events & (POLLIN | POLLRDNORM);
 			else
-				selrecord(p, &sce->rsel);
+				selrecord(p, &sce_in->rsel);
+		}
+		if (sce_out && events & (POLLOUT | POLLWRNORM)) {
+			if (!IBUF_SPACE_WR_ZERO(sce_out))
+				revents |= events & (POLLOUT | POLLWRNORM);
+			else
+				selrecord(p, &sce_out->rsel);
 		}
 		break;
 	case UE_BULK:
-		/*
-		 * We have no easy way of determining if a read will
-		 * yield any data or a write will happen.
-		 * Pretend they will.
-		 */
-		revents |= events &
-			   (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM);
+		if (sce_in && events & (POLLIN | POLLRDNORM))
+			revents |= events & (POLLIN | POLLRDNORM);
+		if (sce_out && events & (POLLOUT | POLLWRNORM))
+			revents |= events & (POLLOUT | POLLWRNORM);
 		break;
 	default:
 		break;
@@ -1357,6 +1483,22 @@
 	return (revents);
 }
 
+int
+ugenpoll(dev_t dev, int events, usb_proc_ptr p)
+{
+	int endpt = UGENENDPOINT(dev);
+	struct ugen_softc *sc;
+	int error;
+
+	USB_GET_SC(ugen, UGENUNIT(dev), sc);
+
+	sc->sc_refcnt++;
+	error = ugen_do_poll(sc, endpt, events, p);
+	if (--sc->sc_refcnt < 0)
+		usb_detach_wakeup(USBDEV(sc->sc_dev));
+	return (error);
+}
+
 static void
 filt_ugenrdetach(struct knote *kn)
 {
@@ -1382,14 +1524,22 @@
 {
 	struct ugen_endpoint *sce = kn->kn_hook;
 
-	if (sce->cur == sce->fill)
+	if (IBUF_SPACE_RD_ZERO(sce))
 		return (0);
 
-	if (sce->cur < sce->fill)
-		kn->kn_data = sce->fill - sce->cur;
-	else
-		kn->kn_data = (sce->limit - sce->cur) +
-		    (sce->fill - sce->ibuf);
+	kn->kn_data = IBUF_SPACE_RD_SIZE(sce);
+
+	return (1);
+}
+static int
+filt_ugenwrite_isoc(struct knote *kn, long hint)
+{
+	struct ugen_endpoint *sce = kn->kn_hook;
+
+	if (IBUF_SPACE_WR_ZERO(sce))
+		return (0);
+
+	kn->kn_data = IBUF_SPACE_WR_SIZE(sce);
 
 	return (1);
 }
@@ -1399,6 +1549,8 @@
 
 static const struct filterops ugenread_isoc_filtops =
 	{ 1, NULL, filt_ugenrdetach, filt_ugenread_isoc };
+static const struct filterops ugenwrite_isoc_filtops =
+	{ 1, NULL, filt_ugenrdetach, filt_ugenwrite_isoc };
 
 static const struct filterops ugen_seltrue_filtops =
 	{ 1, NULL, filt_ugenrdetach, filt_seltrue };
@@ -1416,13 +1568,11 @@
 	if (sc->sc_dying)
 		return (1);
 
-	/* XXX always IN */
-	sce = &sc->sc_endpoints[UGENENDPOINT(dev)][IN];
-	if (sce == NULL)
-		return (1);
-
 	switch (kn->kn_filter) {
 	case EVFILT_READ:
+		sce = &sc->sc_endpoints[UGENENDPOINT(dev)][IN];
+		if (sce == NULL || sce->edesc == NULL)
+			return (1);
 		klist = &sce->rsel.sel_klist;
 		switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
 		case UE_INTERRUPT:
@@ -1445,13 +1595,19 @@
 		break;
 
 	case EVFILT_WRITE:
+		sce = &sc->sc_endpoints[UGENENDPOINT(dev)][OUT];
+		if (sce == NULL || sce->edesc == NULL)
+			return (1);
 		klist = &sce->rsel.sel_klist;
 		switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
 		case UE_INTERRUPT:
-		case UE_ISOCHRONOUS:
 			/* XXX poll doesn't support this */
 			return (1);
 
+		case UE_ISOCHRONOUS:
+			kn->kn_fop = &ugenwrite_isoc_filtops;
+			break;
+
 		case UE_BULK:
 			/*
 			 * We have no easy way of determining if a read will
Index: usb.h
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/usb.h,v
retrieving revision 1.69
diff -u -u -r1.69 usb.h
--- usb.h	22 Sep 2002 23:20:50 -0000	1.69
+++ usb.h	18 Jun 2004 00:12:17 -0000
@@ -682,6 +682,8 @@
 #define USB_GET_DEVICEINFO	_IOR ('U', 112, struct usb_device_info)
 #define USB_SET_SHORT_XFER	_IOW ('U', 113, int)
 #define USB_SET_TIMEOUT		_IOW ('U', 114, int)
+#define USB_GET_ISOC_RATE	_IOR ('U', 115, int)
+#define USB_SET_ISOC_RATE	_IOW ('U', 116, int)
 
 /* Modem device */
 #define USB_GET_CM_OVER_DATA	_IOR ('U', 130, int)
Index: usbdi.c
===================================================================
RCS file: /cvsroot/src/sys/dev/usb/usbdi.c,v
retrieving revision 1.103
diff -u -u -r1.103 usbdi.c
--- usbdi.c	27 Sep 2002 15:37:38 -0000	1.103
+++ usbdi.c	18 Jun 2004 00:12:17 -0000
@@ -758,6 +758,9 @@
 {
 	usbd_pipe_handle pipe = xfer->pipe;
 	usb_dma_t *dmap = &xfer->dmabuf;
+	int sync = xfer->flags & USBD_SYNCRONOUS;
+	int errd = xfer->status == USBD_CANCELLED || 
+		    xfer->status == USBD_TIMEOUT;
 	int repeat = pipe->repeat;
 	int polling;
 
@@ -842,14 +845,12 @@
 	pipe->methods->done(xfer);
 #endif
 
-	if ((xfer->flags & USBD_SYNCHRONOUS) && !polling)
+	if (sync && !polling)
 		wakeup(xfer);
 
 	if (!repeat) {
 		/* XXX should we stop the queue on all errors? */
-		if ((xfer->status == USBD_CANCELLED ||
-		     xfer->status == USBD_TIMEOUT) &&
-		    pipe->iface != NULL)		/* not control pipe */
+		if (errd && pipe->iface != NULL) /* not control pipe */
 			pipe->running = 0;
 		else
 			usbd_start_next(pipe);


>Release-Note:
>Audit-Trail:
>Unformatted: