Add support for isochronous transfers with WinUSB.
authorphilemonf <philemonf@gmail.com>
Fri, 5 Apr 2019 07:27:39 +0000 (00:27 -0700)
committerNathan Hjelm <hjelmn@me.com>
Fri, 5 Apr 2019 15:56:22 +0000 (09:56 -0600)
Closes #284

Signed-off-by: Nathan Hjelm <hjelmn@me.com>
libusb/core.c
libusb/os/windows_nt_shared_types.h
libusb/os/windows_winusb.c
libusb/os/windows_winusb.h
libusb/version_nano.h

index ddba089..6206b85 100644 (file)
@@ -1079,7 +1079,9 @@ out:
  * If acting on an isochronous or interrupt endpoint, this function will
  * multiply the value found in bits 0:10 by the number of transactions per
  * microframe (determined by bits 11:12). Otherwise, this function just
- * returns the numeric value found in bits 0:10.
+ * returns the numeric value found in bits 0:10. For USB 3.0 device, it
+ * will attempts to retrieve the Endpoint Companion Descriptor to return
+ * wBytesPerInterval.
  *
  * This function is useful for setting up isochronous transfers, for example
  * you might pass the return value from this function to
@@ -1099,9 +1101,11 @@ int API_EXPORTED libusb_get_max_iso_packet_size(libusb_device *dev,
 {
        struct libusb_config_descriptor *config;
        const struct libusb_endpoint_descriptor *ep;
+       struct libusb_ss_endpoint_companion_descriptor *ss_ep_cmp;
        enum libusb_transfer_type ep_type;
        uint16_t val;
        int r;
+       int speed;
 
        r = libusb_get_active_config_descriptor(dev, &config);
        if (r < 0) {
@@ -1116,13 +1120,25 @@ int API_EXPORTED libusb_get_max_iso_packet_size(libusb_device *dev,
                goto out;
        }
 
-       val = ep->wMaxPacketSize;
-       ep_type = (enum libusb_transfer_type) (ep->bmAttributes & 0x3);
+       speed = libusb_get_device_speed( dev );
+       if (speed == LIBUSB_SPEED_SUPER) {
+               r = libusb_get_ss_endpoint_companion_descriptor(dev->ctx, ep, &ss_ep_cmp);
+               if (r == LIBUSB_SUCCESS) {
+                       r = ss_ep_cmp->wBytesPerInterval;
+                       libusb_free_ss_endpoint_companion_descriptor(ss_ep_cmp);
+               }
+       }
 
-       r = val & 0x07ff;
-       if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS
-                       || ep_type == LIBUSB_TRANSFER_TYPE_INTERRUPT)
-               r *= (1 + ((val >> 11) & 3));
+       /* If the device isn't a SuperSpeed device or retrieving the SS endpoint didn't worked. */
+       if (speed != LIBUSB_SPEED_SUPER || r < 0) {
+               val = ep->wMaxPacketSize;
+               ep_type = (enum libusb_transfer_type) (ep->bmAttributes & 0x3);
+
+               r = val & 0x07ff;
+               if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS
+                   || ep_type == LIBUSB_TRANSFER_TYPE_INTERRUPT)
+                       r *= (1 + ((val >> 11) & 3));
+       }
 
 out:
        libusb_free_config_descriptor(config);
index 68bf261..d809bfd 100644 (file)
@@ -97,6 +97,7 @@ struct winusb_device_priv {
                int sub_api;
                int8_t nb_endpoints; // and a set of endpoint addresses (USB_MAXENDPOINTS)
                uint8_t *endpoint;
+               int current_altsetting;
                bool restricted_functionality;  // indicates if the interface functionality is restricted
                                                // by Windows (eg. HID keyboards or mice cannot do R/W)
        } usb_interface[USB_MAXINTERFACES];
@@ -134,5 +135,13 @@ struct winusb_transfer_priv {
        uint8_t *hid_buffer; // 1 byte extended data buffer, required for HID
        uint8_t *hid_dest;   // transfer buffer destination, required for HID
        size_t hid_expected_size;
+
+       // For isochronous transfers with LibUSBk driver:
        void *iso_context;
+
+       // For isochronous transfers with Microsoft WinUSB driver:
+       void *isoch_buffer_handle; // The isoch_buffer_handle to free at the end of the transfer
+       BOOL iso_break_stream;  // Whether the isoch. stream was to be continued in the last call of libusb_submit_transfer.
+                                                       // As we this structure is zeroed out upon initialization, we need to use inverse logic here.
+       libusb_transfer_cb_fn iso_user_callback; // Original transfer callback of the user. Might be used for isochronous transfers.
 };
index 3292e33..423fb3d 100644 (file)
@@ -511,6 +511,7 @@ static int windows_assign_endpoints(struct libusb_device_handle *dev_handle, int
        if (if_desc->bNumEndpoints == 0) {
                usbi_dbg("no endpoints found for interface %d", iface);
                libusb_free_config_descriptor(conf_desc);
+               priv->usb_interface[iface].current_altsetting = altsetting;
                return LIBUSB_SUCCESS;
        }
 
@@ -531,6 +532,9 @@ static int windows_assign_endpoints(struct libusb_device_handle *dev_handle, int
        if (priv->apib->configure_endpoints)
                r = priv->apib->configure_endpoints(SUB_API_NOTSET, dev_handle, iface);
 
+       if (r == LIBUSB_SUCCESS)
+               priv->usb_interface[iface].current_altsetting = altsetting;
+
        return r;
 }
 
@@ -1734,11 +1738,25 @@ static void winusb_destroy_device(struct libusb_device *dev)
 static void winusb_clear_transfer_priv(struct usbi_transfer *itransfer)
 {
        struct winusb_transfer_priv *transfer_priv = usbi_transfer_get_os_priv(itransfer);
+       struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+       struct winusb_device_priv *priv = _device_priv(transfer->dev_handle->dev);
+       int sub_api = priv->sub_api;
 
        usbi_close(transfer_priv->pollable_fd.fd);
        transfer_priv->pollable_fd = INVALID_WINFD;
        transfer_priv->handle = NULL;
        safe_free(transfer_priv->hid_buffer);
+
+       if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS && sub_api == SUB_API_WINUSB) {
+               if (transfer_priv->isoch_buffer_handle != NULL) {
+                       if (WinUSBX[sub_api].UnregisterIsochBuffer(transfer_priv->isoch_buffer_handle)) {
+                               transfer_priv->isoch_buffer_handle = NULL;
+                       } else {
+                               usbi_dbg("Couldn't unregister isoch buffer!");
+                       }
+               }
+       }
+
        safe_free(transfer_priv->iso_context);
 
        // When auto claim is in use, attempt to release the auto-claimed interface
@@ -2011,6 +2029,14 @@ const struct windows_usb_api_backend usb_api_backend[USB_API_MAX] = {
                        pLibK_GetProcAddress((PVOID *)&WinUSBX[i].fn, i, KUSB_FNID_##fn);       \
        } while (0)
 
+#define NativeWinUSBOnly_Set(fn)                                                                       \
+       do {                                                                                    \
+               if (native_winusb)                                                              \
+                       WinUSBX[i].fn = (WinUsb_##fn##_t)GetProcAddress(h, "WinUsb_" #fn);      \
+               else                                                                            \
+                       WinUSBX[i].fn = NULL;                                                   \
+       } while (0)
+
 static int winusbx_init(struct libusb_context *ctx)
 {
        HMODULE h;
@@ -2064,6 +2090,11 @@ static int winusbx_init(struct libusb_context *ctx)
                WinUSBX_Set(WritePipe);
                WinUSBX_Set(IsoReadPipe);
                WinUSBX_Set(IsoWritePipe);
+               NativeWinUSBOnly_Set(RegisterIsochBuffer);
+               NativeWinUSBOnly_Set(UnregisterIsochBuffer);
+               NativeWinUSBOnly_Set(WriteIsochPipeAsap);
+               NativeWinUSBOnly_Set(ReadIsochPipeAsap);
+               NativeWinUSBOnly_Set(QueryPipeEx);
 
                if (WinUSBX[i].Initialize != NULL) {
                        WinUSBX[i].initialized = true;
@@ -2507,6 +2538,66 @@ static int winusbx_set_interface_altsetting(int sub_api, struct libusb_device_ha
        return LIBUSB_SUCCESS;
 }
 
+static enum libusb_transfer_status usbd_status_to_libusb_transfer_status(USBD_STATUS status)
+{
+       /* Based on https://msdn.microsoft.com/en-us/library/windows/hardware/ff539136(v=vs.85).aspx :
+       * USBD_STATUS have the most significant 4 bits indicating overall status and the rest gives the details. */
+       switch (status >> 28) {
+       case 0x00: /* USBD_STATUS_SUCCESS */
+               return LIBUSB_TRANSFER_COMPLETED;
+       case 0x01: /* USBD_STATUS_PENDING */
+               return LIBUSB_TRANSFER_COMPLETED;
+       default: /* USBD_STATUS_ERROR */
+               switch (status & 0x0fffffff) {
+               case 0xC0006000: /* USBD_STATUS_TIMEOUT */
+                       return LIBUSB_TRANSFER_TIMED_OUT;
+               case 0xC0010000: /* USBD_STATUS_CANCELED */
+                       return LIBUSB_TRANSFER_CANCELLED;
+               case 0xC0000030: /* USBD_STATUS_ENDPOINT_HALTED */
+                       return LIBUSB_TRANSFER_STALL;
+               case 0xC0007000: /* USBD_STATUS_DEVICE_GONE */
+                       return LIBUSB_TRANSFER_NO_DEVICE;
+               default:
+                       usbi_dbg("USBD_STATUS 0x%08x translated to LIBUSB_TRANSFER_ERROR", status);
+                       return LIBUSB_TRANSFER_ERROR;
+               }
+       }
+}
+
+static void WINAPI winusbx_native_iso_transfer_continue_stream_callback(struct libusb_transfer *transfer)
+{
+       // If this callback is invoked, this means that we attempted to set ContinueStream
+       // to TRUE when calling Read/WriteIsochPipeAsap in winusbx_do_iso_transfer.
+       // The role of this callback is to fallback to ContinueStream = FALSE if the transfer
+       // did not succeed.
+
+       struct winusb_transfer_priv *transfer_priv = (struct winusb_transfer_priv *)
+               usbi_transfer_get_os_priv(LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer));
+       BOOL fallback = (transfer->status != LIBUSB_TRANSFER_COMPLETED);
+       int idx;
+
+       // Restore the user callback
+       transfer->callback = transfer_priv->iso_user_callback;
+
+       for (idx = 0; idx < transfer->num_iso_packets && !fallback; ++idx) {
+               if (transfer->iso_packet_desc[idx].status != LIBUSB_TRANSFER_COMPLETED) {
+                       fallback = TRUE;
+               }
+       }
+
+       if (!fallback) {
+               // If the transfer was successful, we restore the user callback and call it.
+               if (transfer->callback) {
+                       transfer->callback(transfer);
+               }
+       }
+       else {
+               // If the transfer wasn't successful we reschedule the transfer while forcing it
+               // not to continue the stream. This might results in a 5-ms delay.
+               transfer_priv->iso_break_stream = TRUE;
+               libusb_submit_transfer(transfer);
+       }
+}
 static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itransfer)
 {
        struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
@@ -2518,66 +2609,208 @@ static int winusbx_submit_iso_transfer(int sub_api, struct usbi_transfer *itrans
        OVERLAPPED *overlapped;
        bool ret;
        int current_interface;
-       int i;
-       UINT offset;
-       PKISO_CONTEXT iso_context;
-       size_t iso_ctx_size;
 
        CHECK_WINUSBX_AVAILABLE(sub_api);
 
-       if ((sub_api != SUB_API_LIBUSBK) && (sub_api != SUB_API_LIBUSB0)) {
-               // iso only supported on libusbk-based backends
-               PRINT_UNSUPPORTED_API(submit_iso_transfer);
-               return LIBUSB_ERROR_NOT_SUPPORTED;
-       };
-
        current_interface = interface_by_endpoint(priv, handle_priv, transfer->endpoint);
        if (current_interface < 0) {
                usbi_err(ctx, "unable to match endpoint to an open interface - cancelling transfer");
                return LIBUSB_ERROR_NOT_FOUND;
+       } else {
+               usbi_dbg("matched endpoint %02X with interface %d", transfer->endpoint, current_interface);
        }
 
-       usbi_dbg("matched endpoint %02X with interface %d", transfer->endpoint, current_interface);
-
        transfer_priv->handle = winusb_handle = handle_priv->interface_handle[current_interface].api_handle;
        overlapped = transfer_priv->pollable_fd.overlapped;
 
-       iso_ctx_size = sizeof(KISO_CONTEXT) + (transfer->num_iso_packets * sizeof(KISO_PACKET));
-       transfer_priv->iso_context = iso_context = calloc(1, iso_ctx_size);
-       if (transfer_priv->iso_context == NULL)
-               return LIBUSB_ERROR_NO_MEM;
+       if ((sub_api == SUB_API_LIBUSBK) || (sub_api == SUB_API_LIBUSB0)) {
+               int i;
+               UINT offset;
+               size_t iso_ctx_size;
+               PKISO_CONTEXT iso_context;
 
-       // start ASAP
-       iso_context->StartFrame = 0;
-       iso_context->NumberOfPackets = (SHORT)transfer->num_iso_packets;
+               iso_ctx_size = sizeof(KISO_CONTEXT) + (transfer->num_iso_packets * sizeof(KISO_PACKET));
+               transfer_priv->iso_context = iso_context = calloc(1, iso_ctx_size);
+               if (transfer_priv->iso_context == NULL)
+                       return LIBUSB_ERROR_NO_MEM;
 
-       // convert the transfer packet lengths to iso_packet offsets
-       offset = 0;
-       for (i = 0; i < transfer->num_iso_packets; i++) {
-               iso_context->IsoPackets[i].offset = offset;
-               offset += transfer->iso_packet_desc[i].length;
-       }
+               // start ASAP
+               iso_context->StartFrame = 0;
+               iso_context->NumberOfPackets = (SHORT)transfer->num_iso_packets;
 
-       if (IS_XFERIN(transfer)) {
-               usbi_dbg("reading %d iso packets", transfer->num_iso_packets);
-               ret = WinUSBX[sub_api].IsoReadPipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, overlapped, iso_context);
-       } else {
-               usbi_dbg("writing %d iso packets", transfer->num_iso_packets);
-               ret = WinUSBX[sub_api].IsoWritePipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, overlapped, iso_context);
-       }
+               // convert the transfer packet lengths to iso_packet offsets
+               offset = 0;
+               for (i = 0; i < transfer->num_iso_packets; i++) {
+                       iso_context->IsoPackets[i].offset = offset;
+                       offset += transfer->iso_packet_desc[i].length;
+               }
 
-       if (!ret) {
-               if (GetLastError() != ERROR_IO_PENDING) {
-                       usbi_err(ctx, "IsoReadPipe/IsoWritePipe failed: %s", windows_error_str(0));
-                       return LIBUSB_ERROR_IO;
+               if (IS_XFERIN(transfer)) {
+                       usbi_dbg("reading %d iso packets", transfer->num_iso_packets);
+                       ret = WinUSBX[sub_api].IsoReadPipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, overlapped, iso_context);
+               } else {
+                       usbi_dbg("writing %d iso packets", transfer->num_iso_packets);
+                       ret = WinUSBX[sub_api].IsoWritePipe(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, overlapped, iso_context);
                }
-       } else {
-               windows_force_sync_completion(overlapped, (ULONG)transfer->length);
+
+               if (!ret) {
+                       if (GetLastError() != ERROR_IO_PENDING) {
+                               usbi_err(ctx, "IsoReadPipe/IsoWritePipe failed: %s", windows_error_str(0));
+                               return LIBUSB_ERROR_IO;
+                       }
+               } else {
+                       windows_force_sync_completion(overlapped, (ULONG)transfer->length);
+               }
+
+               transfer_priv->interface_number = (uint8_t)current_interface;
+
+               return LIBUSB_SUCCESS;
        }
+       else if (sub_api == SUB_API_WINUSB) {
+               WINUSB_PIPE_INFORMATION_EX pipe_info_ex = { 0 };
+               WINUSB_ISOCH_BUFFER_HANDLE buffer_handle;
+               ULONG iso_transfer_size_multiple;
+               int out_transfer_length = 0;
+               int idx;
+
+#              define WINUSBX_CHECK_API_SUPPORTED(API)       \
+               if (WinUSBX[sub_api].API == NULL)             \
+               {                                             \
+                       usbi_dbg(#API " isn't available");        \
+                       return LIBUSB_ERROR_NOT_SUPPORTED;        \
+               }
 
-       transfer_priv->interface_number = (uint8_t)current_interface;
+               // Depending on the version of Microsoft WinUSB, isochronous transfers may not be supported.
+               WINUSBX_CHECK_API_SUPPORTED(RegisterIsochBuffer);
+               WINUSBX_CHECK_API_SUPPORTED(ReadIsochPipeAsap);
+               WINUSBX_CHECK_API_SUPPORTED(WriteIsochPipeAsap);
+               WINUSBX_CHECK_API_SUPPORTED(UnregisterIsochBuffer);
+               WINUSBX_CHECK_API_SUPPORTED(QueryPipeEx);
 
-       return LIBUSB_SUCCESS;
+               if (sizeof(struct libusb_iso_packet_descriptor) != sizeof(USBD_ISO_PACKET_DESCRIPTOR)) {
+                       usbi_dbg("The size of Microsoft WinUsb and libusb isochronous packet descriptor doesn't match.");
+                       return LIBUSB_ERROR_NOT_SUPPORTED;
+               }
+
+               // Query the pipe extended information to find the pipe index corresponding to the endpoint.
+               for (idx = 0; idx < priv->usb_interface[current_interface].nb_endpoints; ++idx) {
+                       ret = WinUSBX[sub_api].QueryPipeEx(winusb_handle, (UINT8)priv->usb_interface[current_interface].current_altsetting, (UCHAR)idx, &pipe_info_ex);
+                       if (!ret) {
+                               usbi_dbg("Couldn't query interface settings for USB pipe with index %d. Error: %s", idx, windows_error_str(0));
+                               return LIBUSB_ERROR_NOT_FOUND;
+                       }
+
+                       if (pipe_info_ex.PipeId == transfer->endpoint && pipe_info_ex.PipeType == UsbdPipeTypeIsochronous) {
+                               break;
+                       }
+               }
+
+               // Make sure we found the index.
+               if (idx >= priv->usb_interface[current_interface].nb_endpoints) {
+                       usbi_dbg("Couldn't find the isochronous endpoint %02x.", transfer->endpoint);
+                       return LIBUSB_ERROR_NOT_FOUND;
+               }
+
+               if (IS_XFERIN(transfer)) {
+                       int interval = pipe_info_ex.Interval;
+
+                       // For high-speed and SuperSpeed device, the interval is 2**(bInterval-1).
+                       if (libusb_get_device_speed(libusb_get_device(transfer->dev_handle)) >= LIBUSB_SPEED_HIGH) {
+                               interval = (1 << (pipe_info_ex.Interval - 1));
+                       }
+
+                       // WinUSB only supports isochronous transfers spanning a full USB frames. Later, we might be smarter about this
+                       // and allocate a temporary buffer. However, this is harder than it seems as its destruction would depend on overlapped
+                       // IO...
+                       iso_transfer_size_multiple = (pipe_info_ex.MaximumBytesPerInterval * 8) / interval;
+                       if (transfer->length % iso_transfer_size_multiple != 0) {
+                               usbi_dbg("The length of isochronous buffer must be a multiple of the MaximumBytesPerInterval * 8 / Interval");
+                               return LIBUSB_ERROR_INVALID_PARAM;
+                       }
+               }
+               else {
+                       // If this is an OUT transfer, we make sure the isochronous packets are contiguous as this isn't supported otherwise.
+                       BOOL size_should_be_zero = FALSE;
+                       out_transfer_length = 0;
+                       for (idx = 0; idx < transfer->num_iso_packets; ++idx) {
+                               if ((size_should_be_zero && transfer->iso_packet_desc[idx].length != 0) ||
+                                       (transfer->iso_packet_desc[idx].length != pipe_info_ex.MaximumBytesPerInterval && idx + 1 < transfer->num_iso_packets && transfer->iso_packet_desc[idx + 1].length > 0)) {
+                                       usbi_dbg("Isochronous packets for OUT transfer with Microsoft WinUSB must be contiguous in memory.");
+                                       return LIBUSB_ERROR_INVALID_PARAM;
+                               }
+
+                               size_should_be_zero = (transfer->iso_packet_desc[idx].length == 0);
+                               out_transfer_length += transfer->iso_packet_desc[idx].length;
+                       }
+               }
+
+               if (transfer_priv->isoch_buffer_handle != NULL) {
+                       if (WinUSBX[sub_api].UnregisterIsochBuffer(transfer_priv->isoch_buffer_handle)) {
+                               transfer_priv->isoch_buffer_handle = NULL;
+                       } else {
+                               usbi_dbg("Couldn't unregister the Microsoft WinUSB isochronous buffer: %s", windows_error_str(0));
+                               return LIBUSB_ERROR_OTHER;
+                       }
+               }
+
+               // Register the isochronous buffer to the operating system.
+               ret = WinUSBX[sub_api].RegisterIsochBuffer(winusb_handle, transfer->endpoint, transfer->buffer, transfer->length, &buffer_handle);
+               if (!ret) {
+                       usbi_dbg("Microsoft WinUSB refused to allocate an isochronous buffer.");
+                       return LIBUSB_ERROR_NO_MEM;
+               }
+
+               // Important note: the WinUSB_Read/WriteIsochPipeAsap API requires a ContinueStream parameter that tells whether the isochronous
+               // stream must be continued or if the WinUSB driver can schedule the transfer at its conveniance. Profiling subsequent transfers
+               // with ContinueStream = FALSE showed that 5 frames, i.e. about 5 milliseconds, were left empty between each transfer. This
+               // is critical as this greatly diminish the achievable isochronous bandwidth. We solved the problem using the following strategy:
+               // - Transfers are first scheduled with ContinueStream = TRUE and with winusbx_iso_transfer_continue_stream_callback as user callback.
+               // - If the transfer succeeds, winusbx_iso_transfer_continue_stream_callback restore the user callback and calls its.
+               // - If the transfer fails, winusbx_iso_transfer_continue_stream_callback reschedule the transfer and force ContinueStream = FALSE.
+               if (!transfer_priv->iso_break_stream) {
+                       transfer_priv->iso_user_callback = transfer->callback;
+                       transfer->callback = winusbx_native_iso_transfer_continue_stream_callback;
+               }
+
+               // Initiate the transfers.
+               if (IS_XFERIN(transfer)) {
+                       ret = WinUSBX[sub_api].ReadIsochPipeAsap(buffer_handle, 0, transfer->length, !transfer_priv->iso_break_stream, transfer->num_iso_packets, (PUSBD_ISO_PACKET_DESCRIPTOR)transfer->iso_packet_desc, overlapped);
+               }
+               else {
+                       ret = WinUSBX[sub_api].WriteIsochPipeAsap(buffer_handle, 0, out_transfer_length, !transfer_priv->iso_break_stream, overlapped);
+               }
+
+               // Restore the ContinueStream parameter to TRUE.
+               transfer_priv->iso_break_stream = FALSE;
+
+               if (!ret) {
+                       if (GetLastError() == ERROR_IO_PENDING) {
+                               transfer_priv->isoch_buffer_handle = buffer_handle;
+                       } else {
+                               usbi_err(ctx, "ReadIsochPipeAsap/WriteIsochPipeAsap failed: %s", windows_error_str(0));
+                               if (WinUSBX[sub_api].UnregisterIsochBuffer(buffer_handle)) {
+                                       transfer_priv->isoch_buffer_handle = NULL;
+                                       return LIBUSB_ERROR_IO;
+                               } else {
+                                       usbi_dbg("Couldn't unregister the Microsoft WinUSB isochronous buffer: %s", windows_error_str(0));
+                                       return LIBUSB_ERROR_OTHER;
+                               }
+                       }
+               } else {
+                       windows_force_sync_completion(overlapped, (ULONG)transfer->length);
+                       if (!WinUSBX[sub_api].UnregisterIsochBuffer(buffer_handle)) {
+                               usbi_dbg("Couldn't unregister the Microsoft WinUSB isochronous buffer: %s", windows_error_str(0));
+                               return LIBUSB_ERROR_OTHER;
+                       }
+               }
+
+               transfer_priv->interface_number = (uint8_t)current_interface;
+
+               return LIBUSB_SUCCESS;
+       } else {
+               PRINT_UNSUPPORTED_API(winusbx_submit_iso_transfer);
+               return LIBUSB_ERROR_NOT_SUPPORTED;
+       }
 }
 
 static int winusbx_submit_bulk_transfer(int sub_api, struct usbi_transfer *itransfer)
@@ -2779,6 +3012,28 @@ static int winusbx_copy_transfer_data(int sub_api, struct usbi_transfer *itransf
                                // TODO translate USDB_STATUS codes http://msdn.microsoft.com/en-us/library/ff539136(VS.85).aspx to libusb_transfer_status
                                //transfer->iso_packet_desc[i].status = transfer_priv->iso_context->IsoPackets[i].status;
                        }
+               } else if (sub_api == SUB_API_WINUSB) {
+                       if (IS_XFERIN(transfer)) {
+                               /* Convert isochronous packet descriptor between Windows and libusb representation.
+                                * Both representation are guaranteed to have the same length in bytes.*/
+                               PUSBD_ISO_PACKET_DESCRIPTOR usbd_iso_packet_desc = (PUSBD_ISO_PACKET_DESCRIPTOR)transfer->iso_packet_desc;
+                               for (i = 0; i < transfer->num_iso_packets; ++i)
+                               {
+                                       int length = (i < transfer->num_iso_packets - 1) ? (usbd_iso_packet_desc[i + 1].Offset - usbd_iso_packet_desc[i].Offset) : usbd_iso_packet_desc[i].Length;
+                                       int actual_length = usbd_iso_packet_desc[i].Length;
+                                       USBD_STATUS status = usbd_iso_packet_desc[i].Status;
+
+                                       transfer->iso_packet_desc[i].length = length;
+                                       transfer->iso_packet_desc[i].actual_length = actual_length;
+                                       transfer->iso_packet_desc[i].status = usbd_status_to_libusb_transfer_status(status);
+                               }
+                       }
+                       else {
+                               for (i = 0; i < transfer->num_iso_packets; ++i)
+                               {
+                                       transfer->iso_packet_desc[i].status = LIBUSB_TRANSFER_COMPLETED;
+                               }
+                       }
                } else {
                        // This should only occur if backend is not set correctly or other backend isoc is partially implemented
                        PRINT_UNSUPPORTED_API(copy_transfer_data);
index 597384a..651d7c4 100644 (file)
@@ -505,6 +505,60 @@ typedef BOOL (WINAPI *WinUsb_WritePipe_t)(
        LPOVERLAPPED Overlapped
 );
 
+typedef PVOID WINUSB_ISOCH_BUFFER_HANDLE, *PWINUSB_ISOCH_BUFFER_HANDLE;
+
+typedef BOOL (WINAPI *WinUsb_RegisterIsochBuffer_t)(
+       WINUSB_INTERFACE_HANDLE InterfaceHandle,
+       UCHAR PipeID,
+       PVOID Buffer,
+       ULONG BufferLength,
+       PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle
+);
+
+typedef BOOL (WINAPI *WinUsb_UnregisterIsochBuffer_t)(
+       WINUSB_ISOCH_BUFFER_HANDLE BufferHandle
+);
+
+typedef BOOL (WINAPI *WinUsb_WriteIsochPipeAsap_t)(
+       WINUSB_ISOCH_BUFFER_HANDLE BufferHandle,
+       ULONG Offset,
+       ULONG Length,
+       BOOL ContinueStream,
+       LPOVERLAPPED Overlapped
+);
+
+typedef LONG USBD_STATUS;
+typedef struct {
+       ULONG Offset;
+       ULONG Length;
+       USBD_STATUS Status;
+} USBD_ISO_PACKET_DESCRIPTOR, *PUSBD_ISO_PACKET_DESCRIPTOR;
+
+typedef BOOL (WINAPI *WinUsb_ReadIsochPipeAsap_t)(
+       PWINUSB_ISOCH_BUFFER_HANDLE BufferHandle,
+       ULONG Offset,
+       ULONG Length,
+       BOOL ContinueStream,
+       ULONG NumberOfPackets,
+       PUSBD_ISO_PACKET_DESCRIPTOR IsoPacketDescriptors,
+       LPOVERLAPPED Overlapped
+);
+
+typedef struct {
+       USBD_PIPE_TYPE PipeType;
+       UCHAR PipeId;
+       USHORT MaximumPacketSize;
+       UCHAR Interval;
+       ULONG MaximumBytesPerInterval;
+} WINUSB_PIPE_INFORMATION_EX, *PWINUSB_PIPE_INFORMATION_EX;
+
+typedef BOOL (WINAPI *WinUsb_QueryPipeEx_t)(
+       WINUSB_INTERFACE_HANDLE InterfaceHandle,
+       UCHAR AlternateInterfaceHandle,
+       UCHAR PipeIndex,
+       PWINUSB_PIPE_INFORMATION_EX PipeInformationEx
+);
+
 /* /!\ These must match the ones from the official libusbk.h */
 typedef enum _KUSB_FNID {
        KUSB_FNID_Init,
@@ -616,8 +670,17 @@ struct winusb_interface {
        WinUsb_SetCurrentAlternateSetting_t SetCurrentAlternateSetting;
        WinUsb_SetPipePolicy_t SetPipePolicy;
        WinUsb_WritePipe_t WritePipe;
+
+       // Isochoronous functions for LibUSBk sub api:
        WinUsb_IsoReadPipe_t IsoReadPipe;
        WinUsb_IsoWritePipe_t IsoWritePipe;
+
+       // Isochronous functions for Microsoft WinUSB sub api (native WinUSB):
+       WinUsb_RegisterIsochBuffer_t RegisterIsochBuffer;
+       WinUsb_UnregisterIsochBuffer_t UnregisterIsochBuffer;
+       WinUsb_WriteIsochPipeAsap_t WriteIsochPipeAsap;
+       WinUsb_ReadIsochPipeAsap_t ReadIsochPipeAsap;
+       WinUsb_QueryPipeEx_t QueryPipeEx;
 };
 
 /* hid.dll interface */
index 8af14bf..d242308 100644 (file)
@@ -1 +1 @@
-#define LIBUSB_NANO 11363
+#define LIBUSB_NANO 11364