From 217f57617e0cff0d1bd6d726b243f04c6b179773 Mon Sep 17 00:00:00 2001 From: David Moore Date: Fri, 6 Nov 2009 21:37:25 +0000 Subject: [PATCH] Linux: Add support for the new URB_BULK_CONTINUATION flag 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 | 52 ++++++++++++++++++++++++++++++++++++++++++++++++- libusb/os/linux_usbfs.h | 7 ++++--- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c index a151c8d..cf30703 100644 --- a/libusb/os/linux_usbfs.c +++ b/libusb/os/linux_usbfs.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "libusb.h" @@ -71,6 +72,16 @@ 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), diff --git a/libusb/os/linux_usbfs.h b/libusb/os/linux_usbfs.h index fdf5e9b..bd02edc 100644 --- a/libusb/os/linux_usbfs.h +++ b/libusb/os/linux_usbfs.h @@ -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, -- 2.7.4