patch-2.3.15 linux/drivers/usb/ezusb.c
Next file: linux/drivers/usb/ezusb.h
Previous file: linux/drivers/usb/cpia.h
Back to the patch index
Back to the overall index
- Lines: 499
- Date:
Wed Aug 25 15:03:54 1999
- Orig file:
v2.3.14/linux/drivers/usb/ezusb.c
- Orig date:
Wed Jul 28 10:45:39 1999
diff -u --recursive --new-file v2.3.14/linux/drivers/usb/ezusb.c linux/drivers/usb/ezusb.c
@@ -22,6 +22,10 @@
*
* History:
* 0.1 26.05.99 Created
+ * 0.2 23.07.99 Removed EZUSB_INTERRUPT. Interrupt pipes may be polled with
+ * bulk reads.
+ * Implemented EZUSB_SETINTERFACE, more sanity checks for EZUSB_BULK.
+ * Preliminary ISO support
*
*/
@@ -31,6 +35,9 @@
#include <linux/socket.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
+#include <linux/list.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
#include "usb.h"
#include "ezusb.h"
@@ -42,23 +49,27 @@
static struct ezusb {
struct semaphore mutex;
struct usb_device *usbdev;
- unsigned int irqep;
- unsigned int intlen;
- unsigned char intdata[64];
+ struct list_head iso;
} ezusb[NREZUSB];
-/* --------------------------------------------------------------------- */
+struct isodesc {
+ struct list_head isolist;
+ spinlock_t lock;
+ struct usb_device *usbdev;
+ unsigned int ep;
+ unsigned int pipe;
+ unsigned int pktsz;
+ unsigned int framesperint;
+ unsigned int rd, wr, buflen;
+ unsigned int flags;
+ unsigned int schedcnt, unschedcnt;
+
+ void *hcbuf[2];
+ void *hcisodesc[2];
+ unsigned char *buf;
+};
-static int ezusb_irq(int state, void *__buffer, int len, void *dev_id)
-{
- struct ezusb *ez = (struct ezusb *)dev_id;
-
- if (len > sizeof(ez->intdata))
- len = sizeof(ez->intdata);
- ez->intlen = len;
- memcpy(ez->intdata, __buffer, len);
- return 1;
-}
+#define ISOFLG_ACTIVE (1<<0)
/* --------------------------------------------------------------------- */
@@ -214,16 +225,132 @@
return 0;
}
+static void iso_schedrcv(struct isodesc *isodesc)
+{
+ unsigned diff;
+
+ if (!(isodesc->flags & ISOFLG_ACTIVE))
+ return;
+ diff = (isodesc->buflen - 1 + isodesc->rd - isodesc->wr) % isodesc->buflen;
+ if (diff < isodesc->framesperint * isodesc->pktsz)
+ return;
+ for (;;) {
+ diff = (isodesc->schedcnt - isodesc->unschedcnt) & 3;
+ if (diff >= 2)
+ return;
+ usb_schedule_isochronous(isodesc->usbdev, isodesc->hcisodesc[isodesc->schedcnt & 1],
+ diff ? isodesc->hcisodesc[(isodesc->schedcnt - 1) & 1] : NULL);
+ isodesc->schedcnt++;
+ }
+}
+
+static void iso_schedsnd(struct isodesc *isodesc)
+{
+ unsigned diff, bcnt, x;
+ unsigned char *p1, *p2;
+
+ if (!(isodesc->flags & ISOFLG_ACTIVE))
+ return;
+ for (;;) {
+ diff = (isodesc->schedcnt - isodesc->unschedcnt) & 3;
+ if (diff >= 2)
+ return;
+ bcnt = (isodesc->buflen - isodesc->rd + isodesc->wr) % isodesc->buflen;
+ if (bcnt < isodesc->framesperint * isodesc->pktsz)
+ return;
+ p2 = isodesc->hcbuf[isodesc->schedcnt & 1];
+ for (bcnt = 0; bcnt < isodesc->framesperint; bcnt++) {
+ p1 = isodesc->buf + isodesc->rd;
+ if (isodesc->rd + isodesc->pktsz > isodesc->buflen) {
+ x = isodesc->buflen - isodesc->rd;
+ memcpy(p2, p1, x);
+ memcpy(p2+x, isodesc->buf, isodesc->pktsz - x);
+ } else
+ memcpy(p2, p1, isodesc->pktsz);
+ isodesc->rd = (isodesc->rd + isodesc->pktsz) % isodesc->buflen;
+ p2 += isodesc->pktsz;
+ if (((unsigned long)p2 ^ ((unsigned long)p2 + isodesc->pktsz - 1)) & (~(PAGE_SIZE - 1)))
+ p2 = (void *)(((unsigned long)p2 + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1)));
+ }
+ usb_schedule_isochronous(isodesc->usbdev, isodesc->hcisodesc[isodesc->schedcnt & 1],
+ diff ? isodesc->hcisodesc[(isodesc->schedcnt - 1) & 1] : NULL);
+ isodesc->schedcnt++;
+ }
+}
+
+static int ezusb_isorcv_irq(int status, void *__buffer, int __len, void *dev_id)
+{
+ struct isodesc *isodesc = (struct isodesc *)dev_id;
+ unsigned int len, len2;
+ unsigned char *p1;
+
+ spin_lock(&isodesc->lock);
+ usb_unschedule_isochronous(isodesc->usbdev, isodesc->hcisodesc[isodesc->unschedcnt & 1]);
+ len = usb_compress_isochronous(isodesc->usbdev, isodesc->hcisodesc[isodesc->unschedcnt & 1]);
+ printk(KERN_DEBUG "ezusb_isorcv_irq: %u bytes recvd\n", len);
+ p1 = isodesc->hcbuf[isodesc->unschedcnt & 1];
+ while (len > 0) {
+ len2 = (isodesc->buflen - 1 + isodesc->rd - isodesc->wr) % isodesc->buflen;
+ if (!len2)
+ break;
+ if (isodesc->wr + len2 > isodesc->buflen)
+ len2 = isodesc->buflen - isodesc->wr;
+ if (len2 > len)
+ len2 = len;
+ memcpy(isodesc->buf + isodesc->wr, p1, len2);
+ isodesc->wr = (isodesc->wr + len2) % isodesc->buflen;
+ p1 += len2;
+ len -= len2;
+ }
+ isodesc->unschedcnt++;
+ iso_schedrcv(isodesc);
+ spin_unlock(&isodesc->lock);
+ return 1;
+}
+
+static int ezusb_isosnd_irq(int status, void *__buffer, int len, void *dev_id)
+{
+ struct isodesc *isodesc = (struct isodesc *)dev_id;
+
+ spin_lock(&isodesc->lock);
+ usb_unschedule_isochronous(isodesc->usbdev, isodesc->hcisodesc[isodesc->unschedcnt & 1]);
+ isodesc->unschedcnt++;
+ iso_schedsnd(isodesc);
+ spin_unlock(&isodesc->lock);
+ return 1;
+}
+
+static struct isodesc *findiso(struct ezusb *ez, unsigned int ep)
+{
+ struct list_head *head = &ez->iso;
+ struct list_head *tmp = head->next;
+ struct isodesc *id;
+
+ while (tmp != head) {
+ id = list_entry(tmp, struct isodesc, isolist);
+ if (id->ep == ep)
+ return id;
+ tmp = tmp->next;
+ }
+ return NULL;
+}
+
static int ezusb_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
struct ezusb *ez = (struct ezusb *)file->private_data;
struct ezusb_ctrltransfer ctrl;
struct ezusb_bulktransfer bulk;
- unsigned int len1, ep;
+ struct ezusb_setinterface setintf;
+ unsigned int len1, ep, pipe, cnt;
unsigned long len2;
- unsigned int irqep;
unsigned char tbuf[1024];
int i;
+ struct ezusb_isotransfer isot;
+ struct ezusb_isodata isod;
+ struct isodesc *isodesc;
+ usb_device_irq isocompl;
+ unsigned long flags;
+ unsigned char *p1, *p2;
switch (cmd) {
case EZUSB_CONTROL:
@@ -264,23 +391,14 @@
}
return 0;
- case EZUSB_INTERRUPT:
- get_user_ret(irqep, (unsigned int *)arg, -EFAULT);
- if (irqep != ez->irqep) {
- if (ez->irqep)
- return -EIO;
- ez->irqep = irqep;
- usb_request_irq(ez->usbdev, usb_rcvctrlpipe(ez->usbdev, ez->irqep),
- ezusb_irq, 2 /* interval */, ez);
- ez->intlen = 0;
- return -EAGAIN;
- }
- copy_to_user_ret((&((struct ezusb_interrupttransfer *)0)->data) + arg,
- ez->intdata, 64, -EFAULT);
- return ez->intlen;
-
case EZUSB_BULK:
copy_from_user_ret(&bulk, (void *)arg, sizeof(bulk), -EFAULT);
+ if (bulk.ep & 0x80)
+ pipe = usb_rcvbulkpipe(ez->usbdev, bulk.ep & 0x7f);
+ else
+ pipe = usb_sndbulkpipe(ez->usbdev, bulk.ep & 0x7f);
+ if (!usb_maxpacket(ez->usbdev, pipe, !(bulk.ep & 0x80)))
+ return -EINVAL;
len1 = bulk.len;
if (len1 > sizeof(tbuf))
len1 = sizeof(tbuf);
@@ -292,8 +410,7 @@
up(&ez->mutex);
return -EIO;
}
- i = ez->usbdev->bus->op->bulk_msg(ez->usbdev, usb_rcvbulkpipe(ez->usbdev, bulk.ep & 0x7f),
- tbuf, len1, &len2);
+ i = ez->usbdev->bus->op->bulk_msg(ez->usbdev, pipe, tbuf, len1, &len2);
up(&ez->mutex);
if (!i && len2) {
copy_to_user_ret(bulk.data, tbuf, len2, -EFAULT);
@@ -307,8 +424,7 @@
up(&ez->mutex);
return -EIO;
}
- i = ez->usbdev->bus->op->bulk_msg(ez->usbdev, usb_sndbulkpipe(ez->usbdev, bulk.ep & 0x7f),
- tbuf, len1, &len2);
+ i = ez->usbdev->bus->op->bulk_msg(ez->usbdev, pipe, tbuf, len1, &len2);
up(&ez->mutex);
}
if (i) {
@@ -324,6 +440,213 @@
return -EINVAL;
usb_settoggle(ez->usbdev, ep & 0xf, !(ep & 0x80), 0);
return 0;
+
+ case EZUSB_SETINTERFACE:
+ copy_from_user_ret(&setintf, (void *)arg, sizeof(setintf), -EFAULT);
+ if (usb_set_interface(ez->usbdev, setintf.interface, setintf.altsetting))
+ return -EINVAL;
+ return 0;
+
+ case EZUSB_STARTISO:
+ copy_from_user_ret(&isot, (void *)arg, sizeof(isot), -EFAULT);
+ len1 = isot.framesperint * isot.pktsz;
+ if (len1 > PAGE_SIZE) {
+ len1 = PAGE_SIZE / isot.pktsz;
+ len1 = PAGE_SIZE * ((isot.framesperint + len1 - 1) / len1);
+ }
+ len2 = (isot.pktsz * 1000 + PAGE_SIZE - 1) & (PAGE_SIZE-1);
+ if (len2 > 32*PAGE_SIZE)
+ len2 = PAGE_SIZE;
+ if ((isot.ep & ~0x80) >= 16 || isot.pktsz < 1 || isot.pktsz > 1023 ||
+ isot.framesperint < 1 || isot.framesperint > 1000 ||
+ len1 > 4*PAGE_SIZE)
+ return -EINVAL;
+ down(&ez->mutex);
+ if (!ez->usbdev) {
+ up(&ez->mutex);
+ return -EIO;
+ }
+ if (findiso(ez, isod.ep)) {
+ up(&ez->mutex);
+ return -EBUSY;
+ }
+ if (isot.ep & 0x80) {
+ pipe = usb_rcvisocpipe(ez->usbdev, isot.ep & 15);
+ isocompl = ezusb_isorcv_irq;
+ } else {
+ pipe = usb_sndisocpipe(ez->usbdev, isot.ep & 15);
+ isocompl = ezusb_isosnd_irq;
+ }
+ if (!(isodesc = kmalloc(sizeof(struct isodesc), GFP_KERNEL))) {
+ up(&ez->mutex);
+ return -ENOMEM;
+ }
+ memset(isodesc, 0, sizeof(struct isodesc));
+ INIT_LIST_HEAD(&isodesc->isolist);
+ spin_lock_init(&isodesc->lock);
+ isodesc->usbdev = ez->usbdev;
+ isodesc->ep = isot.ep;
+ isodesc->pktsz = isot.pktsz;
+ isodesc->framesperint = isot.framesperint;
+ isodesc->buflen = len2;
+ if (!(isodesc->hcbuf[0] = kmalloc(len1, GFP_KERNEL)) ||
+ !(isodesc->hcbuf[1] = kmalloc(len1, GFP_KERNEL)))
+ goto startisomemerr;
+ if (!(isodesc->hcisodesc[0] = usb_allocate_isochronous(ez->usbdev, pipe, isodesc->hcbuf[0],
+ len1, isodesc->pktsz, isocompl, isodesc)) ||
+ !(isodesc->hcisodesc[1] = usb_allocate_isochronous(ez->usbdev, pipe, isodesc->hcbuf[1],
+ len1, isodesc->pktsz, isocompl, isodesc)))
+ goto startisomemerr;
+ if (!(isodesc->buf = vmalloc(isodesc->buflen)))
+ goto startisomemerr;
+ up(&ez->mutex);
+ return 0;
+
+ startisomemerr:
+ if (isodesc->hcisodesc[0])
+ usb_delete_isochronous(ez->usbdev, isodesc->hcisodesc[0]);
+ if (isodesc->hcisodesc[1])
+ usb_delete_isochronous(ez->usbdev, isodesc->hcisodesc[1]);
+ if (isodesc->hcbuf[0])
+ kfree(isodesc->hcbuf[0]);
+ if (isodesc->hcbuf[1])
+ kfree(isodesc->hcbuf[1]);
+ if (isodesc->buf)
+ vfree(isodesc->buf);
+ up(&ez->mutex);
+ return -ENOMEM;
+
+ case EZUSB_STOPISO:
+ get_user_ret(ep, (unsigned int *)arg, -EFAULT);
+ if ((ep & ~0x80) >= 16)
+ return -EINVAL;
+ down(&ez->mutex);
+ if (!ez->usbdev) {
+ up(&ez->mutex);
+ return -EIO;
+ }
+ if (!(isodesc = findiso(ez, ep))) {
+ up(&ez->mutex);
+ return -EINVAL;
+ }
+ list_del(&isodesc->isolist);
+ usb_delete_isochronous(ez->usbdev, isodesc->hcisodesc[0]);
+ usb_delete_isochronous(ez->usbdev, isodesc->hcisodesc[1]);
+ kfree(isodesc->hcbuf[0]);
+ kfree(isodesc->hcbuf[1]);
+ vfree(isodesc->buf);
+ up(&ez->mutex);
+ return 0;
+
+ case EZUSB_ISODATA:
+ copy_from_user_ret(&isod, (void *)arg, sizeof(isod), -EFAULT);
+ if ((isod.ep & ~0x80) >= 16)
+ return -EINVAL;
+ if (isod.size)
+ if (!access_ok((isod.ep & 0x80) ? VERIFY_WRITE : VERIFY_READ, isod.data, isod.size))
+ return -EFAULT;
+ down(&ez->mutex);
+ if (!ez->usbdev) {
+ up(&ez->mutex);
+ return -EIO;
+ }
+ if (!(isodesc = findiso(ez, ep))) {
+ up(&ez->mutex);
+ return -EINVAL;
+ }
+ if (isod.ep & 0x80) {
+ cnt = 0;
+ p1 = isod.data;
+ while (cnt < isod.size) {
+ spin_lock_irqsave(&isodesc->lock, flags);
+ p2 = isodesc->buf + isodesc->rd;
+ len2 = (isodesc->rd >= isodesc->wr) ? isodesc->buflen : isodesc->wr;
+ len2 -= isodesc->rd;
+ spin_unlock_irqrestore(&isodesc->lock, flags);
+ if (len2 <= 0)
+ break;
+ if (len2 >= isod.size - cnt)
+ len2 = isod.size - cnt;
+ if (__copy_to_user(p1, p2, len2)) {
+ up(&ez->mutex);
+ return -EFAULT;
+ }
+ p1 += len2;
+ cnt += len2;
+ spin_lock_irqsave(&isodesc->lock, flags);
+ isodesc->rd = (isodesc->rd + len2) % isodesc->buflen;
+ spin_unlock_irqrestore(&isodesc->lock, flags);
+ }
+ isod.size = cnt;
+ iso_schedrcv(isodesc);
+ } else {
+ cnt = 0;
+ p1 = isod.data;
+ while (cnt < isod.size) {
+ spin_lock_irqsave(&isodesc->lock, flags);
+ p2 = isodesc->buf + isodesc->wr;
+ len2 = (isodesc->buflen - 1 + isodesc->rd - isodesc->wr) % isodesc->buflen;
+ if (isodesc->wr + len2 > isodesc->buflen)
+ len2 = isodesc->buflen - isodesc->wr;
+ spin_unlock_irqrestore(&isodesc->lock, flags);
+ if (len2 <= 0)
+ break;
+ if (len2 >= isod.size - cnt)
+ len2 = isod.size - cnt;
+ if (__copy_from_user(p2, p1, len2)) {
+ up(&ez->mutex);
+ return -EFAULT;
+ }
+ p1 += len2;
+ cnt += len2;
+ spin_lock_irqsave(&isodesc->lock, flags);
+ isodesc->wr = (isodesc->wr + len2) % isodesc->buflen;
+ spin_unlock_irqrestore(&isodesc->lock, flags);
+ }
+ isod.size = cnt;
+ iso_schedsnd(isodesc);
+ }
+ spin_lock_irqsave(&isodesc->lock, flags);
+ isod.bufqueued = (isodesc->buflen + isodesc->wr - isodesc->rd) % isodesc->buflen;
+ isod.buffree = (isodesc->buflen - 1 + isodesc->rd - isodesc->wr) % isodesc->buflen;
+ spin_unlock_irqrestore(&isodesc->lock, flags);
+ up(&ez->mutex);
+ copy_to_user_ret((void *)arg, &isod, sizeof(isod), -EFAULT);
+ return 0;
+
+ case EZUSB_PAUSEISO:
+ get_user_ret(ep, (unsigned int *)arg, -EFAULT);
+ if ((ep & ~0x80) >= 16)
+ return -EINVAL;
+ if (!(isodesc = findiso(ez, ep)))
+ return -EINVAL;
+ spin_lock_irqsave(&isodesc->lock, flags);
+ isodesc->flags &= ~ISOFLG_ACTIVE;
+ spin_unlock_irqrestore(&isodesc->lock, flags);
+ return 0;
+
+ case EZUSB_RESUMEISO:
+ get_user_ret(ep, (unsigned int *)arg, -EFAULT);
+ if ((ep & ~0x80) >= 16)
+ return -EINVAL;
+ down(&ez->mutex);
+ if (!ez->usbdev) {
+ up(&ez->mutex);
+ return -EIO;
+ }
+ if (!(isodesc = findiso(ez, ep))) {
+ up(&ez->mutex);
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&isodesc->lock, flags);
+ isodesc->flags |= ISOFLG_ACTIVE;
+ if (isot.ep & 0x80)
+ iso_schedrcv(isodesc);
+ else
+ iso_schedsnd(isodesc);
+ spin_unlock_irqrestore(&isodesc->lock, flags);
+ up(&ez->mutex);
+ return 0;
}
return -ENOIOCTLCMD;
}
@@ -388,7 +711,7 @@
printk(KERN_ERR "ezusb: set_configuration failed\n");
goto err;
}
- interface = &usbdev->config[0].altsetting[1].interface[0];
+ interface = &usbdev->config[0].interface[0].altsetting[1];
if (usb_set_interface(usbdev, 0, 1)) {
printk(KERN_ERR "ezusb: set_interface failed\n");
goto err;
@@ -407,8 +730,18 @@
static void ezusb_disconnect(struct usb_device *usbdev)
{
struct ezusb *ez = (struct ezusb *)usbdev->private;
+ struct isodesc *isodesc;
down(&ez->mutex);
+ while (!list_empty(&ez->iso)) {
+ isodesc = list_entry(ez->iso.next, struct isodesc, isolist);
+ list_del(ez->iso.next);
+ usb_delete_isochronous(ez->usbdev, isodesc->hcisodesc[0]);
+ usb_delete_isochronous(ez->usbdev, isodesc->hcisodesc[1]);
+ kfree(isodesc->hcbuf[0]);
+ kfree(isodesc->hcbuf[1]);
+ vfree(isodesc->buf);
+ }
ez->usbdev = NULL;
up(&ez->mutex);
usbdev->private = NULL;
@@ -432,7 +765,7 @@
for (u = 0; u < NREZUSB; u++) {
init_MUTEX(&ezusb[u].mutex);
ezusb[u].usbdev = NULL;
- ezusb[u].irqep = 0;
+ INIT_LIST_HEAD(&ezusb[u].iso);
}
/* register misc device */
if (misc_register(&ezusb_misc)) {
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)