]> bbs.cooldavid.org Git - net-next-2.6.git/commitdiff
USB: usbfs: add USBDEVFS_URB_BULK_CONTINUATION flag
authorAlan Stern <stern@rowland.harvard.edu>
Tue, 1 Sep 2009 15:09:56 +0000 (11:09 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 23 Sep 2009 13:46:39 +0000 (06:46 -0700)
This patch (as1283) adds a new flag, USBDEVFS_URB_BULK_CONTINUATION,
to usbfs.  It is intended for userspace libraries such as libusb and
openusb.  When they have to break up a single usbfs bulk transfer into
multiple URBs, they will set the flag on all but the first URB of the
series.

If an error other than an unlink occurs, the kernel will automatically
cancel all the following URBs for the same endpoint and refuse to
accept new submissions, until an URB is encountered that is not marked
as a BULK_CONTINUATION.  Such an URB would indicate the start of a new
transfer or the presence of an older library, so the kernel returns to
normal operation.

This enables libraries to delimit bulk transfers correctly, even in
the presence of early termination as indicated by short packets.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/core/devio.c
include/linux/usbdevice_fs.h

index 71514be8b71531a1596269bd6156809bd4133559..181f78c84105c090df4689b44845638472ccb8ef 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))
@@ -1194,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,
index 00ceebeb9e5c272818f3ce44cb664c7907e71f00..b2a7d8ba6ee3d6c7ee7435bd43eedd6640243a26 100644 (file)
@@ -77,6 +77,7 @@ struct usbdevfs_connectinfo {
 
 #define USBDEVFS_URB_SHORT_NOT_OK      0x01
 #define USBDEVFS_URB_ISO_ASAP          0x02
+#define USBDEVFS_URB_BULK_CONTINUATION 0x04
 #define USBDEVFS_URB_NO_FSBR           0x20
 #define USBDEVFS_URB_ZERO_PACKET       0x40
 #define USBDEVFS_URB_NO_INTERRUPT      0x80