[PATCH] libusb: Add support for the new USBDEVFS_URB_BULK_CONTINUATION flag

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux