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

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)