Linux: Add support for the new URB_BULK_CONTINUATION flag
authorDavid Moore <dcm@acm.org>
Fri, 6 Nov 2009 21:37:25 +0000 (21:37 +0000)
committerDaniel Drake <dsd@gentoo.org>
Fri, 6 Nov 2009 21:50:41 +0000 (21:50 +0000)
Add support for the new USBDEVFS_URB_BULK_CONTINUATION flag to libusb.

This flag, which is expected to be available in usbfs starting with
kernel 2.6.32, allows the kernel to cancel multiple URBs upon receipt
of a short packet.  This capability allows libusb to preserve data
integrity of large bulk transfers that are split into multiple URBs.
Without this support, these URBs must be canceled in userspace upon
receipt of a short packet, a race condition against future transfers
which might partially fill these canceled URBs.

This patch automatically detects whether a supported kernel is present
and enables the use of the flag when possible.

[dsd: tweaks to supported kernel detection, and some inline
 documentation of this mechanism]

libusb/os/linux_usbfs.c
libusb/os/linux_usbfs.h

index a151c8d..cf30703 100644 (file)
@@ -31,6 +31,7 @@
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/utsname.h>
 #include <unistd.h>
 
 #include "libusb.h"
 
 static const char *usbfs_path = NULL;
 
+/* Linux 2.6.32 adds support for a bulk continuation URB flag. this should
+ * be set on all URBs in the transfer except the first. also set the
+ * SHORT_NOT_OK flag on all of them, to raise error conditions on short
+ * transfers.
+ * then, on any error except a cancellation, all URBs until the next
+ * non-continuation URB will be cancelled with the endpoint disabled,
+ * meaning that no more data can creep in during the time it takes us to
+ * cancel the remaining URBs. */
+static int supports_flag_bulk_continuation = -1;
+
 /* clock ID for monotonic clock, as not all clock sources are available on all
  * systems. appropriate choice made at initialization time. */
 static clockid_t monotonic_clkid = -1;
@@ -208,6 +219,23 @@ static clockid_t find_monotonic_clock(void)
        return -1;
 }
 
+/* bulk continuation URB flag available from Linux 2.6.32 */
+static int check_flag_bulk_continuation(void)
+{
+       struct utsname uts;
+       int sublevel;
+
+       if (uname(&uts) < 0)
+               return -1;
+       if (strlen(uts.release) < 4)
+               return 0;
+       if (strncmp(uts.release, "2.6.", 4) != 0)
+               return 0;
+
+       sublevel = atoi(uts.release + 4);
+       return sublevel >= 32;
+}
+
 static int op_init(struct libusb_context *ctx)
 {
        struct stat statbuf;
@@ -227,6 +255,17 @@ static int op_init(struct libusb_context *ctx)
                }
        }
 
+       if (supports_flag_bulk_continuation == -1) {
+               supports_flag_bulk_continuation = check_flag_bulk_continuation();
+               if (supports_flag_bulk_continuation == -1) {
+                       usbi_err(ctx, "error checking for bulk continuation support");
+                       return LIBUSB_ERROR_OTHER;
+               }
+       }
+
+       if (supports_flag_bulk_continuation)
+               usbi_dbg("bulk continuation flag supported");
+
        r = stat(SYSFS_DEVICE_PATH, &statbuf);
        if (r == 0 && S_ISDIR(statbuf.st_mode)) {
                usbi_dbg("found usb devices in sysfs");
@@ -1344,6 +1383,8 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
                urb->type = urb_type;
                urb->endpoint = transfer->endpoint;
                urb->buffer = transfer->buffer + (i * MAX_BULK_BUFFER_LENGTH);
+               if (supports_flag_bulk_continuation)
+                       urb->flags = USBFS_URB_SHORT_NOT_OK;
                if (i == num_urbs - 1 && last_urb_partial)
                        urb->buffer_length = transfer->length % MAX_BULK_BUFFER_LENGTH;
                else if (transfer->length == 0)
@@ -1351,6 +1392,9 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
                else
                        urb->buffer_length = MAX_BULK_BUFFER_LENGTH;
 
+               if (i > 0 && supports_flag_bulk_continuation)
+                       urb->flags |= USBFS_URB_BULK_CONTINUATION;
+
                r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
                if (r < 0) {
                        int j;
@@ -1804,7 +1848,7 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer,
                goto out_unlock;
        }
 
-       if (urb->status == 0 ||
+       if (urb->status == 0 || urb->status == -EREMOTEIO ||
                        (urb->status == -EOVERFLOW && urb->actual_length > 0))
                itransfer->transferred += urb->actual_length;
 
@@ -1812,6 +1856,8 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer,
        switch (urb->status) {
        case 0:
                break;
+       case -EREMOTEIO: /* short transfer */
+               break;
        case -EPIPE:
                usbi_dbg("detected endpoint stall");
                status = LIBUSB_TRANSFER_STALL;
@@ -1852,6 +1898,10 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer,
                 * before reporting results */
                tpriv->reap_action = COMPLETED_EARLY;
                for (i = urb_idx + 1; i < tpriv->num_urbs; i++) {
+                       /* remaining URBs with continuation flag are automatically
+                        * cancelled by the kernel */
+                       if (tpriv->urbs[i].flags & USBFS_URB_BULK_CONTINUATION)
+                               continue;
                        int r = ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, &tpriv->urbs[i]);
                        if (r && errno != EINVAL)
                                usbi_warn(TRANSFER_CTX(transfer),
index fdf5e9b..bd02edc 100644 (file)
@@ -60,9 +60,10 @@ struct usbfs_getdriver {
        char driver[USBFS_MAXDRIVERNAME + 1];
 };
 
-#define USBFS_URB_DISABLE_SPD  1
-#define USBFS_URB_ISO_ASAP     2
-#define USBFS_URB_QUEUE_BULK   0x10
+#define USBFS_URB_SHORT_NOT_OK         0x01
+#define USBFS_URB_ISO_ASAP                     0x02
+#define USBFS_URB_BULK_CONTINUATION    0x04
+#define USBFS_URB_QUEUE_BULK           0x10
 
 enum usbfs_urb_type {
        USBFS_URB_TYPE_ISO = 0,