From: David Moore <dcm@xxxxxxx> 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. Signed-off-by: David Moore <dcm@xxxxxxx> --- libusb/os/linux_usbfs.c | 39 +++++++++++++++++++++++++++++++++++++-- libusb/os/linux_usbfs.h | 3 +++ 2 files changed, 40 insertions(+), 2 deletions(-) This patch deserves extensive testing on kernels with and without support for USBDEVFS_URB_BULK_CONTINUATION. diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c index 1280188..9940b67 100644 --- a/libusb/os/linux_usbfs.c +++ b/libusb/os/linux_usbfs.c @@ -86,6 +86,8 @@ struct linux_device_priv { struct linux_device_handle_priv { int fd; + char supports_flag_short_not_ok; + char supports_flag_bulk_continuation; }; enum reap_action { @@ -989,6 +991,8 @@ static int op_open(struct libusb_device_handle *handle) return LIBUSB_ERROR_IO; } } + hpriv->supports_flag_short_not_ok = 1; + hpriv->supports_flag_bulk_continuation = 1; return usbi_add_pollfd(HANDLE_CTX(handle), hpriv->fd, POLLOUT); } @@ -1253,6 +1257,28 @@ static void free_iso_urbs(struct linux_transfer_priv *tpriv) tpriv->iso_urbs = NULL; } +static int submit_bulk_ioctl(struct linux_device_handle_priv *dpriv, + struct usbfs_urb *urb) +{ + int r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r < 0 && errno == EINVAL && + (urb->flags & USBFS_URB_BULK_CONTINUATION)) { + usbi_dbg("disabled BULK_CONTINUATION flag"); + dpriv->supports_flag_bulk_continuation = 0; + urb->flags &= ~USBFS_URB_BULK_CONTINUATION; + r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + } + if (r < 0 && errno == EINVAL && + (urb->flags & USBFS_URB_SHORT_NOT_OK)) { + usbi_dbg("disabled SHORT_NOT_OK flag"); + dpriv->supports_flag_short_not_ok = 0; + dpriv->supports_flag_bulk_continuation = 0; + urb->flags &= ~USBFS_URB_SHORT_NOT_OK; + r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + } + return r; +} + static int submit_bulk_transfer(struct usbi_transfer *itransfer, unsigned char urb_type) { @@ -1300,6 +1326,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 (dpriv->supports_flag_short_not_ok) + 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) @@ -1307,7 +1335,10 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer, else urb->buffer_length = MAX_BULK_BUFFER_LENGTH; - r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (i > 0 && dpriv->supports_flag_bulk_continuation) + urb->flags |= USBFS_URB_BULK_CONTINUATION; + + r = submit_bulk_ioctl(dpriv, urb); if (r < 0) { int j; @@ -1758,7 +1789,7 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, return 0; } - if (urb->status == 0 || + if (urb->status == 0 || urb->status == -EREMOTEIO || (urb->status == -EOVERFLOW && urb->actual_length > 0)) itransfer->transferred += urb->actual_length; @@ -1766,6 +1797,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; @@ -1806,6 +1839,8 @@ 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++) { + 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..8f0d60d 100644 --- a/libusb/os/linux_usbfs.h +++ b/libusb/os/linux_usbfs.h @@ -81,6 +81,9 @@ struct usbfs_iso_packet_desc { #define MAX_BULK_BUFFER_LENGTH 16384 #define MAX_CTRL_BUFFER_LENGTH 4096 +#define USBFS_URB_SHORT_NOT_OK 0x01 +#define USBFS_URB_BULK_CONTINUATION 0x04 + struct usbfs_urb { unsigned char type; unsigned char endpoint; -- 1.6.0.6 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html