From 6779af1510d6f5412d444aac52c4d5badc912c01 Mon Sep 17 00:00:00 2001
From: Daniel Drake <dsd@gentoo.org>
Date: Sat, 7 Nov 2009 12:37:59 +0000
Subject: [PATCH] Add LIBUSB_TRANSFER_ZERO_PACKET (#6)

Add a transfer flag useful when working with protocols where logical
requests are divided by incomplete packets. Currently only supported on
Linux.

Based on earlier work by Nikias Bassen.
---
 libusb/core.c           |    9 +++++++++
 libusb/io.c             |    2 ++
 libusb/libusb.h         |   27 ++++++++++++++++++++++++++-
 libusb/os/darwin_usb.c  |    3 +++
 libusb/os/linux_usbfs.c |    3 +++
 libusb/os/linux_usbfs.h |    1 +
 6 files changed, 44 insertions(+), 1 deletions(-)

diff --git a/libusb/core.c b/libusb/core.c
index 7e4fd24..04dcc3e 100644
--- a/libusb/core.c
+++ b/libusb/core.c
@@ -283,6 +283,15 @@ if (cfg != desired)
  * kernel where boundaries occur between logical libusb-level transfers. When
  * a short transfer (or other error) occurs, the kernel will cancel all the
  * subtransfers until the boundary without allowing those transfers to start.
+ *
+ * \section zlp Zero length packets
+ *
+ * - libusb is designed to be able to send a packet of zero-length to an
+ * endpoint simply by submitting a transfer of zero length. On Linux, this did
+ * not work with libusb versions prior to v1.0.3 and kernel versions prior to
+ * v2.6.31.
+ * - The \ref libusb_transfer_flags::LIBUSB_TRANSFER_ZERO_PACKET
+ * "LIBUSB_TRANSFER_ZERO_PACKET" flag is unsupported on Darwin.
  */
 
 /**
diff --git a/libusb/io.c b/libusb/io.c
index 65c2cf4..361057a 100644
--- a/libusb/io.c
+++ b/libusb/io.c
@@ -1206,6 +1206,8 @@ API_EXPORTED void libusb_free_transfer(struct libusb_transfer *transfer)
  * \returns 0 on success
  * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
  * \returns LIBUSB_ERROR_BUSY if the transfer has already been submitted.
+ * \returns LIBUSB_ERROR_NOT_SUPPORTED if the transfer flags are not supported
+ * by the operating system.
  * \returns another LIBUSB_ERROR code on other failure
  */
 API_EXPORTED int libusb_submit_transfer(struct libusb_transfer *transfer)
diff --git a/libusb/libusb.h b/libusb/libusb.h
index 2dbb9a5..b682ee8 100644
--- a/libusb/libusb.h
+++ b/libusb/libusb.h
@@ -672,7 +672,32 @@ enum libusb_transfer_flags {
 	 * If this flag is set, it is illegal to call libusb_free_transfer()
 	 * from your transfer callback, as this will result in a double-free
 	 * when this flag is acted upon. */
-	LIBUSB_TRANSFER_FREE_TRANSFER = 1<<2
+	LIBUSB_TRANSFER_FREE_TRANSFER = 1<<2,
+
+	/** Terminate transfers that are a multiple of the endpoint's maximum
+	 * packet size with an extra zero-length packet. This is useful when a
+	 * device protocol mandates that each logical request is terminated by
+	 * an incomplete packet (i.e. the logical requests are not separated by
+	 * other means).
+	 *
+	 * This flag only affects host-to-device transfers to bulk and interrupt
+	 * endpoints. In other situations, it is ignored.
+	 *
+	 * This flag only affects transfers with a length that is a multiple of
+	 * the endpoint's packet size. On transfers of other lengths, this flag
+	 * has no effect. Therefore, if you are working with a device that needs
+	 * a ZLP whenever the end of the logical request falls on a packet boundary,
+	 * then it is sensible to set this flag on <em>every</em> transfer (you
+	 * do not have to worry about only setting it on transfers that end on the
+	 * boundary).
+	 *
+	 * This flag is unsupported on Darwin. On Darwin, libusb_submit_transfer()
+	 * will return LIBUSB_ERROR_NOT_SUPPORTED for every transfer where this flag
+	 * is set.
+	 *
+	 * Since v1.0.5.
+	 */
+	LIBUSB_TRANSFER_ZERO_PACKET = 1 << 3
 };
 
 /** \ingroup asyncio
diff --git a/libusb/os/darwin_usb.c b/libusb/os/darwin_usb.c
index 9c64b60..9c75ed0 100644
--- a/libusb/os/darwin_usb.c
+++ b/libusb/os/darwin_usb.c
@@ -1046,6 +1046,9 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer) {
 
   /* are we reading or writing? */
   is_read = transfer->endpoint & LIBUSB_ENDPOINT_IN;
+
+  if (!is_read & transfer->flags & LIBUSB_TRANSFER_ZERO_PACKET)
+    return LIBUSB_ERROR_NOT_SUPPORTED;
   
   if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface) != 0) {
     _usbi_log (TRANSFER_CTX (transfer), LOG_LEVEL_ERROR, "endpoint not found on any open interface");
diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c
index ffa4088..f93e0c8 100644
--- a/libusb/os/linux_usbfs.c
+++ b/libusb/os/linux_usbfs.c
@@ -1376,6 +1376,9 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
 		if (i > 0 && supports_flag_bulk_continuation)
 			urb->flags |= USBFS_URB_BULK_CONTINUATION;
 
+		if (transfer->flags & LIBUSB_TRANSFER_ZERO_PACKET && i == num_urbs - 1)
+			urb->flags |= USBFS_URB_ZERO_PACKET;
+
 		r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
 		if (r < 0) {
 			int j;
diff --git a/libusb/os/linux_usbfs.h b/libusb/os/linux_usbfs.h
index bd02edc..7b737f6 100644
--- a/libusb/os/linux_usbfs.h
+++ b/libusb/os/linux_usbfs.h
@@ -64,6 +64,7 @@ struct usbfs_getdriver {
 #define USBFS_URB_ISO_ASAP			0x02
 #define USBFS_URB_BULK_CONTINUATION	0x04
 #define USBFS_URB_QUEUE_BULK		0x10
+#define USBFS_URB_ZERO_PACKET		0x40
 
 enum usbfs_urb_type {
 	USBFS_URB_TYPE_ISO = 0,
-- 
1.6.5.2


