Subject: ugen: isochronous transfer mode
To: None <tech-kern@netbsd.org, port-i386@netbsd.org>
From: Berndt Josef Wulf <wulf@ping.net.au>
List: tech-kern
Date: 09/08/2000 08:37:23
G'day,

I've been succesful in adding code that I found on a FreeBSD site to
the ugen device driver that extents its capability to include
isochronous tranfer mode. It was tested using a Creative Webcam3
digital camera and sofar no problems have been detected.

It would be nice if someone more knowledgeable than myself could have
a look over the source code and perhaps initiate its
implementation into NetBSD's kernel. 

Find below a diff against /sys/dev/usb/ugen.c. The diff is cut against
the sources from NetBSD-1.5Alpha2, but should be fine with
NetBSD-current.

Whilst on the subject of usb device driver..., I've attempted to write
a driver for the QuikCam Express USB digital camera, but sofar had no
luck in deciphering the propriaty protocol used by Logitech. Is there
anyone out there who has access to a USB Protocol Analyzer such as a
CATC Chief (www.catc.com) or similar. This could be used to obtain a
dump from the device initialization phase allowing reverse engineering of
the protocol used by Logitech. Alternatively, are there any software
tools that will do this job? 

I enterain your comments.

cheerio Berndt
-- 
Name    : Berndt Josef Wulf            | +++ With BSD on Packet Radio +++
E-Mail  : wulf@ping.net.au             |    tfkiss, tnt, dpbox, wampes
ICQ     : 18196098                     |  VK5ABN, Nairne, South Australia 
URL     : http://www.ping.net.au/~wulf | MBOX : vk5abn@vk5abn.#lmr.#sa.au.oc
Sysinfo : DEC AXPpci33+, NetBSD-1.4.2  | BBS  : vk5abn.#lmr.#sa.aus.oc 

------- snip --------
--- ugen.c.orig	Fri Jun  2 19:57:35 2000
+++ ugen.c	Sat Sep  2 20:40:42 2000
@@ -75,6 +75,14 @@
 #define DPRINTFN(n,x)
 #endif
 
+#define	UGEN_CHUNK	128	/* chunk size for read */
+#define	UGEN_IBSIZE	1020	/* buffer size */
+#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 */
+
 struct ugen_endpoint {
 	struct ugen_softc *sc;
 	usb_endpoint_descriptor_t *edesc;
@@ -85,14 +93,19 @@
 	usbd_pipe_handle pipeh;
 	struct clist q;
 	struct selinfo rsel;
-	void *ibuf;
+	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;
+	struct isoreq {
+		struct ugen_endpoint *sce;
+		usbd_xfer_handle xfer;
+		void *dmabuf;
+		u_int16_t sizes[UGEN_NISORFRMS];
+	} isoreqs[UGEN_NISOREQS];
 };
 
-#define	UGEN_CHUNK	128	/* chunk size for read */
-#define	UGEN_IBSIZE	1020	/* buffer size */
-#define	UGEN_BBSIZE	1024
-
 struct ugen_softc {
 	USBBASEDEVICE sc_dev;		/* base device */
 	usbd_device_handle sc_udev;
@@ -138,7 +151,9 @@
 
 Static void ugenintr(usbd_xfer_handle xfer, usbd_private_handle addr, 
 		     usbd_status status);
-
+Static void ugen_isoc_rintr __P((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, 
@@ -274,6 +289,9 @@
 	struct ugen_endpoint *sce;
 	int dir, isize;
 	usbd_status err;
+	usbd_xfer_handle xfer;
+	void *buf;
+	int i, j;
 
 	USB_GET_SC_OPEN(ugen, unit, sc);
 
@@ -339,8 +357,53 @@
 			if (err)
 				return (EIO);
 			break;
-		case UE_CONTROL:
 		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));
+			err = usbd_open_pipe(sce->iface,
+				  edesc->bEndpointAddress, 0, &sce->pipeh);
+			if (err) {
+				free(sce->ibuf, M_USBDEV);
+				return (EIO);
+			}
+			for(i = 0; i < UGEN_NISOREQS; ++i) {
+				sce->isoreqs[i].sce = sce;
+				xfer = usbd_alloc_xfer(sc->sc_udev);
+				if (xfer == 0)
+					goto bad;
+				sce->isoreqs[i].xfer = xfer;
+				buf = usbd_alloc_buffer
+					(xfer, isize * UGEN_NISORFRMS);
+				if (buf == 0) {
+					i++;
+					goto bad;
+				}
+				sce->isoreqs[i].dmabuf = buf;
+				for(j = 0; j < UGEN_NISORFRMS; ++j)
+					sce->isoreqs[i].sizes[j] = isize;
+				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);
+			}
+			DPRINTFN(5, ("ugenopen: isoc open done\n"));
+			break;
+		bad:
+			while (--i >= 0) /* implicit buffer free */
+				usbd_free_xfer(sce->isoreqs[i].xfer);
+			return (ENOMEM);
+		case UE_CONTROL:
 			return (EINVAL);
 		}
 	}
@@ -355,6 +418,7 @@
 	struct ugen_softc *sc;
 	struct ugen_endpoint *sce;
 	int dir;
+	int i;
 
 	USB_GET_SC(ugen, UGENUNIT(dev), sc);
 
@@ -386,7 +450,20 @@
 		usbd_abort_pipe(sce->pipeh);
 		usbd_close_pipe(sce->pipeh);
 		sce->pipeh = NULL;
-		
+
+                switch (sce->edesc->bmAttributes & UE_XFERTYPE) {
+                case UE_INTERRUPT:
+                        ndflush(&sce->q, sce->q.c_cc);
+                        clfree(&sce->q);
+                        break;
+                case UE_ISOCHRONOUS:
+                        for (i = 0; i < UGEN_NISOREQS; ++i)
+                                usbd_free_xfer(sce->isoreqs[i].xfer);
+
+                default:
+                        break;
+		}
+
 		if (sce->ibuf != NULL) {
 			free(sce->ibuf, M_USBDEV);
 			sce->ibuf = NULL;
@@ -498,6 +575,45 @@
 		}
 		usbd_free_xfer(xfer);
 		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", sc));
+			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 (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);
 	}
@@ -702,6 +818,56 @@
 	selwakeup(&sce->rsel);
 }
 
+Static void
+ugen_isoc_rintr(xfer, addr, status)
+	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;
+
+	/* Return if we are aborting. */
+	if (status == USBD_CANCELLED)
+		return;
+
+	usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL);
+	DPRINTFN(5,("ugen_isoc_rintr: xfer %d, count=%d\n", req - sce->isoreqs,
+		    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));
+	}
+
+	/* copy data to buffer */
+	while(count > 0) {
+		n = min(count, sce->limit - sce->fill);
+		memcpy(sce->fill, req->dmabuf, n);
+
+		count -= n;
+		sce->fill += n;
+		if(sce->fill == sce->limit)
+			sce->fill = sce->ibuf;
+	}
+
+	usbd_setup_isoc_xfer(xfer, sce->pipeh, req, req->sizes, UGEN_NISORFRMS,
+			     USBD_NO_COPY, ugen_isoc_rintr);
+	(void)usbd_transfer(xfer);
+
+	if (sce->state & UGEN_ASLP) {
+		sce->state &= ~UGEN_ASLP;
+		DPRINTFN(5, ("ugen_isoc_rintr: waking %p\n", sce));
+		wakeup(sce);
+	}
+	selwakeup(&sce->rsel);
+}
+
 Static usbd_status
 ugen_set_interface(struct ugen_softc *sc, int ifaceidx, int altno)
 {
@@ -1118,6 +1284,14 @@
 	case UE_INTERRUPT:
 		if (events & (POLLIN | POLLRDNORM)) {
 			if (sce->q.c_cc > 0)
+				revents |= events & (POLLIN | POLLRDNORM);
+			else
+				selrecord(p, &sce->rsel);
+		}
+		break;
+	case UE_ISOCHRONOUS:
+		if (events & (POLLIN | POLLRDNORM)) {
+			if (sce->cur != sce->fill)
 				revents |= events & (POLLIN | POLLRDNORM);
 			else
 				selrecord(p, &sce->rsel);