usbdevfs: move compat_ioctl handling to devio.c
[safe/jmp/linux-2.6] / drivers / usb / core / devio.c
index a1add77..6e8bcdf 100644 (file)
@@ -74,6 +74,7 @@ struct dev_state {
        void __user *disccontext;
        unsigned long ifclaimed;
        u32 secid;
+       u32 disabled_bulk_eps;
 };
 
 struct async {
@@ -88,6 +89,8 @@ struct async {
        struct urb *urb;
        int status;
        u32 secid;
+       u8 bulk_addr;
+       u8 bulk_status;
 };
 
 static int usbfs_snoop;
@@ -343,6 +346,43 @@ static void snoop_urb(struct usb_device *udev,
        }
 }
 
+#define AS_CONTINUATION        1
+#define AS_UNLINK      2
+
+static void cancel_bulk_urbs(struct dev_state *ps, unsigned bulk_addr)
+__releases(ps->lock)
+__acquires(ps->lock)
+{
+       struct async *as;
+
+       /* Mark all the pending URBs that match bulk_addr, up to but not
+        * including the first one without AS_CONTINUATION.  If such an
+        * URB is encountered then a new transfer has already started so
+        * the endpoint doesn't need to be disabled; otherwise it does.
+        */
+       list_for_each_entry(as, &ps->async_pending, asynclist) {
+               if (as->bulk_addr == bulk_addr) {
+                       if (as->bulk_status != AS_CONTINUATION)
+                               goto rescan;
+                       as->bulk_status = AS_UNLINK;
+                       as->bulk_addr = 0;
+               }
+       }
+       ps->disabled_bulk_eps |= (1 << bulk_addr);
+
+       /* Now carefully unlink all the marked pending URBs */
+ rescan:
+       list_for_each_entry(as, &ps->async_pending, asynclist) {
+               if (as->bulk_status == AS_UNLINK) {
+                       as->bulk_status = 0;            /* Only once */
+                       spin_unlock(&ps->lock);         /* Allow completions */
+                       usb_unlink_urb(as->urb);
+                       spin_lock(&ps->lock);
+                       goto rescan;
+               }
+       }
+}
+
 static void async_completed(struct urb *urb)
 {
        struct async *as = urb->context;
@@ -371,6 +411,9 @@ static void async_completed(struct urb *urb)
        snoop(&urb->dev->dev, "urb complete\n");
        snoop_urb(urb->dev, as->userurb, urb->pipe, urb->actual_length,
                        as->status, COMPLETE);
+       if (as->status < 0 && as->bulk_addr && as->status != -ECONNRESET &&
+                       as->status != -ENOENT)
+               cancel_bulk_urbs(ps, as->bulk_addr);
        spin_unlock(&ps->lock);
 
        if (signr)
@@ -993,6 +1036,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,
 
        if (uurb->flags & ~(USBDEVFS_URB_ISO_ASAP |
                                USBDEVFS_URB_SHORT_NOT_OK |
+                               USBDEVFS_URB_BULK_CONTINUATION |
                                USBDEVFS_URB_NO_FSBR |
                                USBDEVFS_URB_ZERO_PACKET |
                                USBDEVFS_URB_NO_INTERRUPT))
@@ -1091,7 +1135,8 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,
                        }
                        totlen += isopkt[u].length;
                }
-               if (totlen > 32768) {
+               /* 3072 * 64 microframes */
+               if (totlen > 196608) {
                        kfree(isopkt);
                        return -EINVAL;
                }
@@ -1193,7 +1238,39 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,
        snoop_urb(ps->dev, as->userurb, as->urb->pipe,
                        as->urb->transfer_buffer_length, 0, SUBMIT);
        async_newpending(as);
-       if ((ret = usb_submit_urb(as->urb, GFP_KERNEL))) {
+
+       if (usb_endpoint_xfer_bulk(&ep->desc)) {
+               spin_lock_irq(&ps->lock);
+
+               /* Not exactly the endpoint address; the direction bit is
+                * shifted to the 0x10 position so that the value will be
+                * between 0 and 31.
+                */
+               as->bulk_addr = usb_endpoint_num(&ep->desc) |
+                       ((ep->desc.bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+                               >> 3);
+
+               /* If this bulk URB is the start of a new transfer, re-enable
+                * the endpoint.  Otherwise mark it as a continuation URB.
+                */
+               if (uurb->flags & USBDEVFS_URB_BULK_CONTINUATION)
+                       as->bulk_status = AS_CONTINUATION;
+               else
+                       ps->disabled_bulk_eps &= ~(1 << as->bulk_addr);
+
+               /* Don't accept continuation URBs if the endpoint is
+                * disabled because of an earlier error.
+                */
+               if (ps->disabled_bulk_eps & (1 << as->bulk_addr))
+                       ret = -EREMOTEIO;
+               else
+                       ret = usb_submit_urb(as->urb, GFP_ATOMIC);
+               spin_unlock_irq(&ps->lock);
+       } else {
+               ret = usb_submit_urb(as->urb, GFP_KERNEL);
+       }
+
+       if (ret) {
                dev_printk(KERN_DEBUG, &ps->dev->dev,
                           "usbfs: usb_submit_urb returned %d\n", ret);
                snoop_urb(ps->dev, as->userurb, as->urb->pipe,
@@ -1311,6 +1388,46 @@ static int proc_reapurbnonblock(struct dev_state *ps, void __user *arg)
 }
 
 #ifdef CONFIG_COMPAT
+static int proc_control_compat(struct dev_state *ps,
+                               struct usbdevfs_ctrltransfer32 __user *p32)
+{
+        struct usbdevfs_ctrltransfer __user *p;
+        __u32 udata;
+        p = compat_alloc_user_space(sizeof(*p));
+        if (copy_in_user(p, p32, (sizeof(*p32) - sizeof(compat_caddr_t))) ||
+            get_user(udata, &p32->data) ||
+           put_user(compat_ptr(udata), &p->data))
+               return -EFAULT;
+        return proc_control(ps, p);
+}
+
+static int proc_bulk_compat(struct dev_state *ps,
+                       struct usbdevfs_bulktransfer32 __user *p32)
+{
+        struct usbdevfs_bulktransfer __user *p;
+        compat_uint_t n;
+        compat_caddr_t addr;
+
+        p = compat_alloc_user_space(sizeof(*p));
+
+        if (get_user(n, &p32->ep) || put_user(n, &p->ep) ||
+            get_user(n, &p32->len) || put_user(n, &p->len) ||
+            get_user(n, &p32->timeout) || put_user(n, &p->timeout) ||
+            get_user(addr, &p32->data) || put_user(compat_ptr(addr), &p->data))
+                return -EFAULT;
+
+        return proc_bulk(ps, p);
+}
+static int proc_disconnectsignal_compat(struct dev_state *ps, void __user *arg)
+{
+       struct usbdevfs_disconnectsignal32 ds;
+
+       if (copy_from_user(&ds, arg, sizeof(ds)))
+               return -EFAULT;
+       ps->discsignr = ds.signr;
+       ps->disccontext = compat_ptr(ds.context);
+       return 0;
+}
 
 static int get_urb32(struct usbdevfs_urb *kurb,
                     struct usbdevfs_urb32 __user *uurb)
@@ -1405,6 +1522,7 @@ static int proc_reapurbnonblock_compat(struct dev_state *ps, void __user *arg)
        return processcompl_compat(as, (void __user * __user *)arg);
 }
 
+
 #endif
 
 static int proc_disconnectsignal(struct dev_state *ps, void __user *arg)
@@ -1571,12 +1689,12 @@ static int proc_release_port(struct dev_state *ps, void __user *arg)
  * are assuming that somehow the configuration has been prevented from
  * changing.  But there's no mechanism to ensure that...
  */
-static int usbdev_ioctl(struct inode *inode, struct file *file,
-                       unsigned int cmd, unsigned long arg)
+static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
+                               void __user *p)
 {
        struct dev_state *ps = file->private_data;
+       struct inode *inode = file->f_path.dentry->d_inode;
        struct usb_device *dev = ps->dev;
-       void __user *p = (void __user *)arg;
        int ret = -ENOTTY;
 
        if (!(file->f_mode & FMODE_WRITE))
@@ -1649,6 +1767,24 @@ static int usbdev_ioctl(struct inode *inode, struct file *file,
                break;
 
 #ifdef CONFIG_COMPAT
+       case USBDEVFS_CONTROL32:
+               snoop(&dev->dev, "%s: CONTROL32\n", __func__);
+               ret = proc_control_compat(ps, p);
+               if (ret >= 0)
+                       inode->i_mtime = CURRENT_TIME;
+               break;
+
+       case USBDEVFS_BULK32:
+               snoop(&dev->dev, "%s: BULK32\n", __func__);
+               ret = proc_bulk_compat(ps, p);
+               if (ret >= 0)
+                       inode->i_mtime = CURRENT_TIME;
+               break;
+
+       case USBDEVFS_DISCSIGNAL32:
+               snoop(&dev->dev, "%s: DISCSIGNAL32\n", __func__);
+               ret = proc_disconnectsignal_compat(ps, p);
+               break;
 
        case USBDEVFS_SUBMITURB32:
                snoop(&dev->dev, "%s: SUBMITURB32\n", __func__);
@@ -1668,7 +1804,7 @@ static int usbdev_ioctl(struct inode *inode, struct file *file,
                break;
 
        case USBDEVFS_IOCTL32:
-               snoop(&dev->dev, "%s: IOCTL\n", __func__);
+               snoop(&dev->dev, "%s: IOCTL32\n", __func__);
                ret = proc_ioctl_compat(ps, ptr_to_compat(p));
                break;
 #endif
@@ -1724,6 +1860,32 @@ static int usbdev_ioctl(struct inode *inode, struct file *file,
        return ret;
 }
 
+static long usbdev_ioctl(struct file *file, unsigned int cmd,
+                       unsigned long arg)
+{
+       int ret;
+
+       lock_kernel();
+       ret = usbdev_do_ioctl(file, cmd, (void __user *)arg);
+       unlock_kernel();
+
+       return ret;
+}
+
+#ifdef CONFIG_COMPAT
+static long usbdev_compat_ioctl(struct file *file, unsigned int cmd,
+                       unsigned long arg)
+{
+       int ret;
+
+       lock_kernel();
+       ret = usbdev_do_ioctl(file, cmd, compat_ptr(arg));
+       unlock_kernel();
+
+       return ret;
+}
+#endif
+
 /* No kernel lock - fine */
 static unsigned int usbdev_poll(struct file *file,
                                struct poll_table_struct *wait)
@@ -1740,13 +1902,16 @@ static unsigned int usbdev_poll(struct file *file,
 }
 
 const struct file_operations usbdev_file_operations = {
-       .owner =        THIS_MODULE,
-       .llseek =       usbdev_lseek,
-       .read =         usbdev_read,
-       .poll =         usbdev_poll,
-       .ioctl =        usbdev_ioctl,
-       .open =         usbdev_open,
-       .release =      usbdev_release,
+       .owner =          THIS_MODULE,
+       .llseek =         usbdev_lseek,
+       .read =           usbdev_read,
+       .poll =           usbdev_poll,
+       .unlocked_ioctl = usbdev_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl =   usbdev_compat_ioctl,
+#endif
+       .open =           usbdev_open,
+       .release =        usbdev_release,
 };
 
 static void usbdev_remove(struct usb_device *udev)