Add LIBUSB_TRANSFER_ADD_ZERO_PACKET flag to indicate need for ZLP
authorPeter Stuge <peter@stuge.se>
Wed, 22 Feb 2012 13:21:54 +0000 (14:21 +0100)
committerPeter Stuge <peter@stuge.se>
Wed, 22 Feb 2012 13:21:54 +0000 (14:21 +0100)
Some protocols which use USB require an extra zero length data packet
to signal end-of-transfer on bulk endpoints, if the last data packet
is exactly wMaxPacketSize bytes long.

This flag allows applications to inform libusb about this requirement,
so that libusb can handle the issue transparently.

At the moment the new flag is only supported on Linux, and submitting
a transfer with the flag set returns an error at submit time on other
systems. Hopefully implementations will soon follow for other systems.

References #6.

libusb/core.c
libusb/io.c
libusb/libusb.h
libusb/os/darwin_usb.c
libusb/os/linux_usbfs.c
libusb/os/linux_usbfs.h
libusb/os/openbsd_usb.c
libusb/os/windows_usb.c

index 8b3927a..142c6d4 100644 (file)
@@ -285,6 +285,14 @@ 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 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 1.0.3 and kernel versions prior to 2.6.31.
+ * - The \ref libusb_transfer_flags::LIBUSB_TRANSFER_ADD_ZERO_PACKET
+ * "LIBUSB_TRANSFER_ADD_ZERO_PACKET" flag is currently only supported on Linux.
  */
 
 /**
index bb6e275..bb04a8f 100644 (file)
@@ -1281,6 +1281,8 @@ void API_EXPORTED 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
  */
 int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer)
index 15e8698..4141a8e 100644 (file)
@@ -799,6 +799,31 @@ enum libusb_transfer_flags {
         * from your transfer callback, as this will result in a double-free
         * when this flag is acted upon. */
        LIBUSB_TRANSFER_FREE_TRANSFER = 1<<2,
+
+       /** Terminate transfers that are a multiple of the endpoint's
+        * wMaxPacketSize 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 wMaxPacketSize. 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 currently only supported on Linux.
+        * On other systems, libusb_submit_transfer() will return
+        * LIBUSB_ERROR_NOT_SUPPORTED for every transfer where this flag is set.
+        *
+        * Available since libusb-1.0.9.
+        */
+       LIBUSB_TRANSFER_ADD_ZERO_PACKET = 1 << 3,
 };
 
 /** \ingroup asyncio
index a7696b7..8815f26 100644 (file)
@@ -1278,6 +1278,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_ADD_ZERO_PACKET)
+    return LIBUSB_ERROR_NOT_SUPPORTED;
+
   if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface) != 0) {
     usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface");
 
index 2aa2026..ad3c37a 100644 (file)
@@ -88,6 +88,12 @@ static const char *usbfs_path = NULL;
  */
 static int supports_flag_bulk_continuation = -1;
 
+/* Linux 2.6.31 fixes support for the zero length packet URB flag. This
+ * allows us to mark URBs that should be followed by a zero length data
+ * packet, which can be required by device- or class-specific protocols.
+ */
+static int supports_flag_zero_packet = -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;
@@ -289,6 +295,18 @@ static int op_init(struct libusb_context *ctx)
        if (supports_flag_bulk_continuation)
                usbi_dbg("bulk continuation flag supported");
 
+       if (-1 == supports_flag_zero_packet) {
+               /* zero length packet URB flag fixed since Linux 2.6.31 */
+               supports_flag_zero_packet = kernel_version_ge(2,6,31);
+               if (-1 == supports_flag_zero_packet) {
+                       usbi_err(ctx, "error checking for zero length packet support");
+                       return LIBUSB_ERROR_OTHER;
+               }
+       }
+
+       if (supports_flag_zero_packet)
+               usbi_dbg("zero length packet flag supported");
+
        r = stat(SYSFS_DEVICE_PATH, &statbuf);
        if (r == 0 && S_ISDIR(statbuf.st_mode)) {
                DIR *devices = opendir(SYSFS_DEVICE_PATH);
@@ -1510,6 +1528,10 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
        if (tpriv->urbs)
                return LIBUSB_ERROR_BUSY;
 
+       if (is_out && transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET &&
+           !supports_flag_zero_packet)
+               return LIBUSB_ERROR_NOT_SUPPORTED;
+
        /* usbfs places a 16kb limit on bulk URBs. we divide up larger requests
         * into smaller units to meet such restriction, then fire off all the
         * units at once. it would be simpler if we just fired one unit at a time,
@@ -1554,6 +1576,11 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
                if (i > 0 && supports_flag_bulk_continuation)
                        urb->flags |= USBFS_URB_BULK_CONTINUATION;
 
+               /* we have already checked that the flag is supported */
+               if (is_out && i == num_urbs - 1 &&
+                   transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET)
+                       urb->flags |= USBFS_URB_ZERO_PACKET;
+
                r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
                if (r < 0) {
                        if (errno == ENODEV) {
index 93e7f31..487acb5 100644 (file)
@@ -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,
index 8306b46..9b03277 100644 (file)
@@ -441,9 +441,15 @@ obsd_submit_transfer(struct usbi_transfer *itransfer)
                        err = LIBUSB_ERROR_NOT_SUPPORTED;
                        break;
                }
-               /* PASSTHROUGH */
+               err = _sync_gen_transfer(itransfer);
+               break;
        case LIBUSB_TRANSFER_TYPE_BULK:
        case LIBUSB_TRANSFER_TYPE_INTERRUPT:
+               if (0 == transfer->endpoint & LIBUSB_ENDPOINT_IN &&
+                   transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) {
+                       err = LIBUSB_ERROR_NOT_SUPPORTED;
+                       break;
+               }
                err = _sync_gen_transfer(itransfer);
                break;
        }
index da4e61c..e7da2ee 100644 (file)
@@ -1846,6 +1846,9 @@ static int windows_submit_transfer(struct usbi_transfer *itransfer)
                return submit_control_transfer(itransfer);
        case LIBUSB_TRANSFER_TYPE_BULK:
        case LIBUSB_TRANSFER_TYPE_INTERRUPT:
+               if (0 == transfer->endpoint & LIBUSB_ENDPOINT_IN &&
+                   transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET)
+                       return LIBUSB_ERROR_NOT_SUPPORTED;
                return submit_bulk_transfer(itransfer);
        case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
                return submit_iso_transfer(itransfer);