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 <housel@acm.org>
		http://members.home.com/housel/
Approved by:	Nick Hibma
This commit is contained in:
Roger Hardiman 2000-12-07 10:28:25 +00:00
parent 5f41c13187
commit 2c097d3332
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=69699

View file

@ -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