From 2c097d333210e54e072e78bc0008825e254b85a4 Mon Sep 17 00:00:00 2001 From: Roger Hardiman Date: Thu, 7 Dec 2000 10:28:25 +0000 Subject: [PATCH] Add Isochronus transfer mode support required by USB WebCams, using a patch from Peter Housel. With this change ugen, and with Peter's 'vid' program in ports/graphics/vid, we can capture single images from USB Cameras using the OmniVision OV511 chipset (including some models of the Creative WebCam 3) NetBSD merged in Peter's patch to their ugen.c file several months ago, so this brings us back in line. Submitted by: Peter Housel http://members.home.com/housel/ Approved by: Nick Hibma --- sys/dev/usb/ugen.c | 193 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 180 insertions(+), 13 deletions(-) diff --git a/sys/dev/usb/ugen.c b/sys/dev/usb/ugen.c index d324220bfe0d..47a933b774b6 100644 --- a/sys/dev/usb/ugen.c +++ b/sys/dev/usb/ugen.c @@ -1,5 +1,5 @@ /* $NetBSD: ugen.c,v 1.27 1999/10/28 12:08:38 augustss Exp $ */ -/* $FreeBSD$ */ +/* $FreeBSD$ */ /* * Copyright (c) 1998 The NetBSD Foundation, Inc. @@ -73,6 +73,14 @@ int ugendebug = 0; #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; @@ -83,14 +91,19 @@ struct ugen_endpoint { 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; @@ -136,7 +149,8 @@ Static struct cdevsw ugen_cdevsw = { 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 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, @@ -300,6 +314,9 @@ ugenopen(dev_t dev, int flag, int mode, struct proc *p) 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); @@ -365,8 +382,53 @@ ugenopen(dev_t dev, int flag, int mode, struct proc *p) 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); } } @@ -381,6 +443,7 @@ ugenclose(dev_t dev, int flag, int mode, struct proc *p) struct ugen_softc *sc; struct ugen_endpoint *sce; int dir; + int i; USB_GET_SC(ugen, UGENUNIT(dev), sc); @@ -408,17 +471,26 @@ ugenclose(dev_t dev, int flag, int mode, struct proc *p) continue; 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; - + + 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; - ndflush(&sce->q, sce->q.c_cc); - clfree(&sce->q); - } } sc->sc_is_open[endpt] = 0; @@ -523,6 +595,45 @@ ugen_do_read(struct ugen_softc *sc, int endpt, struct uio *uio, int flag) } 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); } @@ -755,6 +866,54 @@ ugenintr(usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status) selwakeup(&sce->rsel); } +Static void +ugen_isoc_rintr(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) { @@ -1185,6 +1344,14 @@ ugenpoll(dev_t dev, int events, struct proc *p) 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); + } + break; case UE_BULK: /* * We have no easy way of determining if a read will