diff options
-rw-r--r-- | libusb/os/linux_usbfs.c | 52 | ||||
-rw-r--r-- | 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 <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> +#include <sys/utsname.h> #include <unistd.h> #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, |