Windows: Use I/O completion ports for transfers
authorChris Dickens <christopher.a.dickens@gmail.com>
Tue, 11 Aug 2020 02:19:21 +0000 (19:19 -0700)
committerChris Dickens <christopher.a.dickens@gmail.com>
Tue, 11 Aug 2020 02:19:21 +0000 (19:19 -0700)
As a first step in removing the Windows poll() emulation, switch the
transfers to use an I/O completion port. A dedicated per-context thread
will wait on the I/O completion port and report transfer completions
using usbi_signal_transfer_completion(). This enables the complete
removal of the handle_events() function for the Windows backend and
removes the notion of one "file descriptor" per transfer.

Signed-off-by: Chris Dickens <christopher.a.dickens@gmail.com>
libusb/os/poll_windows.c
libusb/os/poll_windows.h
libusb/os/windows_common.c
libusb/os/windows_common.h
libusb/os/windows_usbdk.c
libusb/os/windows_winusb.c
libusb/version_nano.h

index bf9f7b7..c2bc105 100644 (file)
 /*
  * poll() and pipe() Windows compatibility layer for libusb 1.0
  *
- * The way this layer works is by using OVERLAPPED with async I/O transfers, as
- * OVERLAPPED have an associated event which is flagged for I/O completion.
- *
- * For USB pollable async I/O, you would typically:
- * - obtain a Windows HANDLE to a file or device that has been opened in
- *   OVERLAPPED mode
- * - call usbi_create_fd with this handle to obtain a custom fd.
- * - leave the core functions call the poll routine and flag POLLIN/POLLOUT
- *
  * The pipe pollable synchronous I/O works using the overlapped event associated
  * with a fake pipe. The read/write functions are only meant to be used in that
  * context.
 #include <stdbool.h>
 #include <stdlib.h>
 
-// public fd data
-const struct winfd INVALID_WINFD = { -1, NULL };
 
 // private data
 struct file_descriptor {
-       enum fd_type { FD_TYPE_PIPE, FD_TYPE_TRANSFER } type;
        LONG refcount;
        OVERLAPPED overlapped;
 };
@@ -71,7 +59,7 @@ static unsigned int fd_count;
                return -1;              \
        } while (0)
 
-static struct file_descriptor *alloc_fd(enum fd_type type, LONG refcount)
+static struct file_descriptor *alloc_fd(LONG refcount)
 {
        struct file_descriptor *fd = calloc(1, sizeof(*fd));
 
@@ -82,7 +70,6 @@ static struct file_descriptor *alloc_fd(enum fd_type type, LONG refcount)
                free(fd);
                return NULL;
        }
-       fd->type = type;
        fd->refcount = refcount;
        return fd;
 }
@@ -171,43 +158,6 @@ static void remove_fd(unsigned int pos)
        }
 }
 
-/*
- * Create both an fd and an OVERLAPPED, so that it can be used with our
- * polling function
- * The handle MUST support overlapped transfers (usually requires CreateFile
- * with FILE_FLAG_OVERLAPPED)
- * Return a pollable file descriptor struct, or INVALID_WINFD on error
- *
- * Note that the fd returned by this function is a per-transfer fd, rather
- * than a per-session fd and cannot be used for anything else but our
- * custom functions.
- * if you plan to do R/W on the same handle, you MUST create 2 fds: one for
- * read and one for write. Using a single R/W fd is unsupported and will
- * produce unexpected results
- */
-struct winfd usbi_create_fd(void)
-{
-       struct file_descriptor *fd;
-       struct winfd wfd;
-
-       fd = alloc_fd(FD_TYPE_TRANSFER, 1);
-       if (fd == NULL)
-               return INVALID_WINFD;
-
-       usbi_mutex_static_lock(&fd_table_lock);
-       wfd.fd = install_fd(fd);
-       usbi_mutex_static_unlock(&fd_table_lock);
-
-       if (wfd.fd < 0) {
-               put_fd(fd);
-               return INVALID_WINFD;
-       }
-
-       wfd.overlapped = &fd->overlapped;
-
-       return wfd;
-}
-
 struct wait_thread_data {
        HANDLE thread;
        HANDLE handles[MAXIMUM_WAIT_OBJECTS];
@@ -461,7 +411,7 @@ int usbi_pipe(int filedes[2])
        int r_fd, w_fd;
        int error = 0;
 
-       fd = alloc_fd(FD_TYPE_PIPE, 2);
+       fd = alloc_fd(2);
        if (fd == NULL)
                return_with_errno(ENOMEM);
 
@@ -509,12 +459,10 @@ ssize_t usbi_write(int _fd, const void *buf, size_t count)
 
        usbi_mutex_static_lock(&fd_table_lock);
        fd = get_fd(_fd, false);
-       if (fd && fd->type == FD_TYPE_PIPE) {
+       if (fd != NULL) {
                assert(fd->overlapped.Internal == STATUS_PENDING);
                fd->overlapped.Internal = STATUS_WAIT_0;
                SetEvent(fd->overlapped.hEvent);
-       } else {
-               fd = NULL;
        }
        usbi_mutex_static_unlock(&fd_table_lock);
 
@@ -540,12 +488,10 @@ ssize_t usbi_read(int _fd, void *buf, size_t count)
 
        usbi_mutex_static_lock(&fd_table_lock);
        fd = get_fd(_fd, false);
-       if (fd && fd->type == FD_TYPE_PIPE) {
+       if (fd != NULL) {
                assert(fd->overlapped.Internal == STATUS_WAIT_0);
                fd->overlapped.Internal = STATUS_PENDING;
                ResetEvent(fd->overlapped.hEvent);
-       } else {
-               fd = NULL;
        }
        usbi_mutex_static_unlock(&fd_table_lock);
 
index df1781b..14a5b27 100644 (file)
@@ -25,8 +25,6 @@
 #ifndef LIBUSB_POLL_WINDOWS_H
 #define LIBUSB_POLL_WINDOWS_H
 
-#define DUMMY_HANDLE ((HANDLE)(LONG_PTR)-2)
-
 #define POLLIN         0x0001  /* There is data to read */
 #define POLLPRI                0x0002  /* There is urgent data to read */
 #define POLLOUT                0x0004  /* Writing now will not block */
@@ -42,15 +40,6 @@ struct pollfd {
        short revents;  /* returned events */
 };
 
-struct winfd {
-       int fd;                         // what's exposed to libusb core
-       OVERLAPPED *overlapped;         // what will report our I/O status
-};
-
-extern const struct winfd INVALID_WINFD;
-
-struct winfd usbi_create_fd(void);
-
 int usbi_pipe(int pipefd[2]);
 int usbi_poll(struct pollfd *fds, usbi_nfds_t nfds, int timeout);
 ssize_t usbi_write(int fd, const void *buf, size_t count);
index e2f9116..9406632 100644 (file)
@@ -39,7 +39,7 @@
 // Public
 enum windows_version windows_version = WINDOWS_UNDEFINED;
 
- // Global variables for init/exit
+// Global variables for init/exit
 static unsigned int init_count;
 static bool usbdk_available;
 
@@ -269,13 +269,19 @@ enum libusb_transfer_status usbd_status_to_libusb_transfer_status(USBD_STATUS st
 }
 
 /*
-* Make a transfer complete synchronously
-*/
-void windows_force_sync_completion(OVERLAPPED *overlapped, ULONG size)
+ * Make a transfer complete synchronously
+ */
+void windows_force_sync_completion(struct usbi_transfer *itransfer, ULONG size)
 {
+       struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
+       OVERLAPPED *overlapped = &transfer_priv->overlapped;
+
+       usbi_dbg("transfer %p, length %lu", USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), size);
+
        overlapped->Internal = (ULONG_PTR)STATUS_SUCCESS;
        overlapped->InternalHigh = (ULONG_PTR)size;
-       SetEvent(overlapped->hEvent);
+
+       usbi_signal_transfer_completion(itransfer);
 }
 
 static void windows_init_clock(void)
@@ -310,27 +316,26 @@ static BOOL is_x64(void)
        return ret;
 }
 
-static void get_windows_version(void)
+static enum windows_version get_windows_version(void)
 {
+       enum windows_version winver;
        OSVERSIONINFOEXA vi, vi2;
-       const char *arch, *w = NULL;
        unsigned major, minor, version;
        ULONGLONG major_equal, minor_equal;
+       const char *w, *arch;
        bool ws;
 
-       windows_version = WINDOWS_UNDEFINED;
-
        memset(&vi, 0, sizeof(vi));
        vi.dwOSVersionInfoSize = sizeof(vi);
        if (!GetVersionExA((OSVERSIONINFOA *)&vi)) {
                memset(&vi, 0, sizeof(vi));
                vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
                if (!GetVersionExA((OSVERSIONINFOA *)&vi))
-                       return;
+                       return WINDOWS_UNDEFINED;
        }
 
        if (vi.dwPlatformId != VER_PLATFORM_WIN32_NT)
-               return;
+               return WINDOWS_UNDEFINED;
 
        if ((vi.dwMajorVersion > 6) || ((vi.dwMajorVersion == 6) && (vi.dwMinorVersion >= 2))) {
                // Starting with Windows 8.1 Preview, GetVersionEx() does no longer report the actual OS version
@@ -366,27 +371,25 @@ static void get_windows_version(void)
        }
 
        if ((vi.dwMajorVersion > 0xf) || (vi.dwMinorVersion > 0xf))
-               return;
+               return WINDOWS_UNDEFINED;
 
        ws = (vi.wProductType <= VER_NT_WORKSTATION);
        version = vi.dwMajorVersion << 4 | vi.dwMinorVersion;
        switch (version) {
-       case 0x50: windows_version = WINDOWS_2000;  w = "2000"; break;
-       case 0x51: windows_version = WINDOWS_XP;    w = "XP";   break;
-       case 0x52: windows_version = WINDOWS_2003;  w = "2003"; break;
-       case 0x60: windows_version = WINDOWS_VISTA; w = (ws ? "Vista" : "2008");  break;
-       case 0x61: windows_version = WINDOWS_7;     w = (ws ? "7" : "2008_R2");   break;
-       case 0x62: windows_version = WINDOWS_8;     w = (ws ? "8" : "2012");      break;
-       case 0x63: windows_version = WINDOWS_8_1;   w = (ws ? "8.1" : "2012_R2"); break;
+       case 0x50: winver = WINDOWS_2000;  w = "2000"; break;
+       case 0x51: winver = WINDOWS_XP;    w = "XP";   break;
+       case 0x52: winver = WINDOWS_2003;  w = "2003"; break;
+       case 0x60: winver = WINDOWS_VISTA; w = (ws ? "Vista" : "2008");  break;
+       case 0x61: winver = WINDOWS_7;     w = (ws ? "7" : "2008_R2");   break;
+       case 0x62: winver = WINDOWS_8;     w = (ws ? "8" : "2012");      break;
+       case 0x63: winver = WINDOWS_8_1;   w = (ws ? "8.1" : "2012_R2"); break;
        case 0x64: // Early Windows 10 Insider Previews and Windows Server 2017 Technical Preview 1 used version 6.4
-       case 0xA0: windows_version = WINDOWS_10;    w = (ws ? "10" : "2016");     break;
+       case 0xA0: winver = WINDOWS_10;    w = (ws ? "10" : "2016");     break;
        default:
-               if (version < 0x50) {
-                       return;
-               } else {
-                       windows_version = WINDOWS_11_OR_LATER;
-                       w = "11 or later";
-               }
+               if (version < 0x50)
+                       return WINDOWS_UNDEFINED;
+               winver = WINDOWS_11_OR_LATER;
+               w = "11 or later";
        }
 
        arch = is_x64() ? "64-bit" : "32-bit";
@@ -397,62 +400,42 @@ static void get_windows_version(void)
                usbi_dbg("Windows %s SP%u %s", w, vi.wServicePackMajor, arch);
        else
                usbi_dbg("Windows %s %s", w, arch);
+
+       return winver;
 }
 
-static void windows_transfer_callback(const struct windows_backend *backend,
-       struct usbi_transfer *itransfer, DWORD error, DWORD bytes_transferred)
+static unsigned __stdcall windows_iocp_thread(void *arg)
 {
-       struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
-       enum libusb_transfer_status status, istatus;
+       struct libusb_context *ctx = arg;
+       struct windows_context_priv *priv = usbi_get_context_priv(ctx);
+       HANDLE iocp = priv->completion_port;
+       DWORD num_bytes;
+       ULONG_PTR completion_key;
+       OVERLAPPED *overlapped;
+       struct windows_transfer_priv *transfer_priv;
+       struct usbi_transfer *itransfer;
 
-       usbi_dbg("handling I/O completion with errcode %lu, length %lu",
-               ULONG_CAST(error), ULONG_CAST(bytes_transferred));
+       usbi_dbg("I/O completion thread started");
 
-       switch (error) {
-       case NO_ERROR:
-               status = backend->copy_transfer_data(itransfer, bytes_transferred);
-               break;
-       case ERROR_GEN_FAILURE:
-               usbi_dbg("detected endpoint stall");
-               status = LIBUSB_TRANSFER_STALL;
-               break;
-       case ERROR_SEM_TIMEOUT:
-               usbi_dbg("detected semaphore timeout");
-               status = LIBUSB_TRANSFER_TIMED_OUT;
-               break;
-       case ERROR_OPERATION_ABORTED:
-               istatus = backend->copy_transfer_data(itransfer, bytes_transferred);
-               if (istatus != LIBUSB_TRANSFER_COMPLETED)
-                       usbi_dbg("failed to copy partial data in aborted operation: %d", (int)istatus);
+       while (true) {
+               if (!GetQueuedCompletionStatus(iocp, &num_bytes, &completion_key, &overlapped, INFINITE)) {
+                       usbi_err(ctx, "GetQueuedCompletionStatus failed: %s", windows_error_str(0));
+                       break;
+               }
 
-               usbi_dbg("detected operation aborted");
-               status = LIBUSB_TRANSFER_CANCELLED;
-               break;
-       case ERROR_FILE_NOT_FOUND:
-       case ERROR_DEVICE_NOT_CONNECTED:
-       case ERROR_NO_SUCH_DEVICE:
-               usbi_dbg("detected device removed");
-               status = LIBUSB_TRANSFER_NO_DEVICE;
-               break;
-       default:
-               usbi_err(ITRANSFER_CTX(itransfer), "detected I/O error %lu: %s",
-                       ULONG_CAST(error), windows_error_str(error));
-               status = LIBUSB_TRANSFER_ERROR;
-               break;
-       }
+               if (overlapped == NULL)
+                       break; // Signal to quit
 
-       // Cancel polling
-       usbi_close(transfer_priv->pollable_fd.fd);
-       transfer_priv->pollable_fd = INVALID_WINFD;
-       transfer_priv->handle = NULL;
+               transfer_priv = container_of(overlapped, struct windows_transfer_priv, overlapped);
+               itransfer = (struct usbi_transfer *)((unsigned char *)transfer_priv + PTR_ALIGN(sizeof(*transfer_priv)));
+               usbi_dbg("transfer %p completed, length %lu",
+                        USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(num_bytes));
+               usbi_signal_transfer_completion(itransfer);
+       }
 
-       // Backend-specific cleanup
-       backend->clear_transfer_priv(itransfer);
+       usbi_dbg("I/O completion thread exiting");
 
-       if (status == LIBUSB_TRANSFER_CANCELLED)
-               usbi_handle_transfer_cancellation(itransfer);
-       else
-               usbi_handle_transfer_completion(itransfer, status);
+       return 0;
 }
 
 static int windows_init(struct libusb_context *ctx)
@@ -460,8 +443,8 @@ static int windows_init(struct libusb_context *ctx)
        struct windows_context_priv *priv = usbi_get_context_priv(ctx);
        char mutex_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
        HANDLE mutex;
-       int r = LIBUSB_ERROR_OTHER;
        bool winusb_backend_init = false;
+       int r;
 
        sprintf(mutex_name, "libusb_init%08lX", ULONG_CAST(GetCurrentProcessId() & 0xFFFFFFFFU));
        mutex = CreateMutexA(NULL, FALSE, mutex_name);
@@ -481,18 +464,23 @@ static int windows_init(struct libusb_context *ctx)
        // NB: concurrent usage supposes that init calls are equally balanced with
        // exit calls. If init is called more than exit, we will not exit properly
        if (++init_count == 1) { // First init?
-               get_windows_version();
-
+               windows_version = get_windows_version();
                if (windows_version == WINDOWS_UNDEFINED) {
                        usbi_err(ctx, "failed to detect Windows version");
                        r = LIBUSB_ERROR_NOT_SUPPORTED;
                        goto init_exit;
+               } else if (windows_version < WINDOWS_VISTA) {
+                       usbi_err(ctx, "Windows version is too old");
+                       r = LIBUSB_ERROR_NOT_SUPPORTED;
+                       goto init_exit;
                }
 
                windows_init_clock();
 
-               if (!htab_create(ctx))
+               if (!htab_create(ctx)) {
+                       r = LIBUSB_ERROR_NO_MEM;
                        goto init_exit;
+               }
 
                r = winusb_backend.init(ctx);
                if (r != LIBUSB_SUCCESS)
@@ -506,17 +494,37 @@ static int windows_init(struct libusb_context *ctx)
                } else {
                        usbi_info(ctx, "UsbDk backend is not available");
                        // Do not report this as an error
-                       r = LIBUSB_SUCCESS;
                }
        }
 
        // By default, new contexts will use the WinUSB backend
        priv->backend = &winusb_backend;
 
+       r = LIBUSB_ERROR_NO_MEM;
+
+       // Use an I/O completion port to manage all transfers for this context
+       priv->completion_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
+       if (priv->completion_port == NULL) {
+               usbi_err(ctx, "failed to create I/O completion port: %s", windows_error_str(0));
+               goto init_exit;
+       }
+
+       // And a dedicated thread to wait for I/O completions
+       priv->completion_port_thread = (HANDLE)_beginthreadex(NULL, 0, windows_iocp_thread, ctx, 0, NULL);
+       if (priv->completion_port_thread == NULL) {
+               usbi_err(ctx, "failed to create I/O completion port thread");
+               CloseHandle(priv->completion_port);
+               goto init_exit;
+       }
+
        r = LIBUSB_SUCCESS;
 
 init_exit: // Holds semaphore here
        if ((init_count == 1) && (r != LIBUSB_SUCCESS)) { // First init failed?
+               if (usbdk_available) {
+                       usbdk_backend.exit(ctx);
+                       usbdk_available = false;
+               }
                if (winusb_backend_init)
                        winusb_backend.exit(ctx);
                htab_destroy();
@@ -530,6 +538,7 @@ init_exit: // Holds semaphore here
 
 static void windows_exit(struct libusb_context *ctx)
 {
+       struct windows_context_priv *priv = usbi_get_context_priv(ctx);
        char mutex_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
        HANDLE mutex;
 
@@ -546,6 +555,16 @@ static void windows_exit(struct libusb_context *ctx)
                return;
        }
 
+       // A NULL completion status will indicate to the thread that it is time to exit
+       if (!PostQueuedCompletionStatus(priv->completion_port, 0, 0, NULL))
+               usbi_err(ctx, "failed to post I/O completion: %s", windows_error_str(0));
+
+       if (WaitForSingleObject(priv->completion_port_thread, INFINITE) == WAIT_FAILED)
+               usbi_err(ctx, "failed to wait for I/O completion port thread: %s", windows_error_str(0));
+
+       CloseHandle(priv->completion_port_thread);
+       CloseHandle(priv->completion_port);
+
        // Only works if exits and inits are balanced exactly
        if (--init_count == 0) { // Last exit
                if (usbdk_available) {
@@ -677,17 +696,13 @@ static int windows_submit_transfer(struct usbi_transfer *itransfer)
        struct libusb_context *ctx = TRANSFER_CTX(transfer);
        struct windows_context_priv *priv = usbi_get_context_priv(ctx);
        struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
-       short events;
        int r;
 
        switch (transfer->type) {
        case LIBUSB_TRANSFER_TYPE_CONTROL:
-               events = (transfer->buffer[0] & LIBUSB_ENDPOINT_IN) ? POLLIN : POLLOUT;
-               break;
        case LIBUSB_TRANSFER_TYPE_BULK:
        case LIBUSB_TRANSFER_TYPE_INTERRUPT:
        case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
-               events = IS_XFERIN(transfer) ? POLLIN : POLLOUT;
                break;
        case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
                usbi_warn(ctx, "bulk stream transfers are not yet supported on this platform");
@@ -697,14 +712,6 @@ static int windows_submit_transfer(struct usbi_transfer *itransfer)
                return LIBUSB_ERROR_INVALID_PARAM;
        }
 
-       // Because a Windows OVERLAPPED is used for poll emulation,
-       // a pollable fd is created and stored with each transfer
-       transfer_priv->pollable_fd = usbi_create_fd();
-       if (transfer_priv->pollable_fd.fd < 0) {
-               usbi_err(ctx, "failed to create pollable fd");
-               return LIBUSB_ERROR_NO_MEM;
-       }
-
        if (transfer_priv->handle != NULL) {
                usbi_err(ctx, "program assertion failed - transfer HANDLE is not NULL");
                transfer_priv->handle = NULL;
@@ -714,9 +721,6 @@ static int windows_submit_transfer(struct usbi_transfer *itransfer)
        if (r != LIBUSB_SUCCESS) {
                // Always call the backend's clear_transfer_priv() function on failure
                priv->backend->clear_transfer_priv(itransfer);
-               // Release the pollable fd since it won't be used
-               usbi_close(transfer_priv->pollable_fd.fd);
-               transfer_priv->pollable_fd = INVALID_WINFD;
                transfer_priv->handle = NULL;
                return r;
        }
@@ -726,16 +730,6 @@ static int windows_submit_transfer(struct usbi_transfer *itransfer)
        if (transfer_priv->handle == NULL)
                usbi_err(ctx, "program assertion failed - transfer HANDLE is NULL after transfer was submitted");
 
-       // We don't want to start monitoring the pollable fd before the transfer
-       // has been submitted, so start monitoring it now.  Note that if the
-       // usbi_add_pollfd() function fails, the user will never get notified
-       // that the transfer has completed.  We don't attempt any cleanup if this
-       // happens because the transfer is already in progress and could even have
-       // completed
-       if (usbi_add_pollfd(ctx, transfer_priv->pollable_fd.fd, events))
-               usbi_err(ctx, "failed to add pollable fd %d for transfer %p",
-                       transfer_priv->pollable_fd.fd, transfer);
-
        return r;
 }
 
@@ -747,7 +741,7 @@ static int windows_cancel_transfer(struct usbi_transfer *itransfer)
        // Try CancelIoEx() on the transfer
        // If that fails, fall back to the backend's cancel_transfer()
        // function if it is available
-       if (CancelIoEx(transfer_priv->handle, transfer_priv->pollable_fd.overlapped))
+       if (CancelIoEx(transfer_priv->handle, &transfer_priv->overlapped))
                return LIBUSB_SUCCESS;
        else if (GetLastError() == ERROR_NOT_FOUND)
                return LIBUSB_ERROR_NOT_FOUND;
@@ -759,52 +753,65 @@ static int windows_cancel_transfer(struct usbi_transfer *itransfer)
        return LIBUSB_ERROR_NOT_SUPPORTED;
 }
 
-static int windows_handle_events(struct libusb_context *ctx, struct pollfd *fds, usbi_nfds_t nfds, int num_ready)
+static int windows_handle_transfer_completion(struct usbi_transfer *itransfer)
 {
+       struct libusb_context *ctx = ITRANSFER_CTX(itransfer);
        struct windows_context_priv *priv = usbi_get_context_priv(ctx);
-       struct usbi_transfer *itransfer;
-       struct windows_transfer_priv *transfer_priv;
+       const struct windows_backend *backend = priv->backend;
+       struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
+       enum libusb_transfer_status status, istatus;
        DWORD result, bytes_transferred;
-       usbi_nfds_t i;
-       int r = LIBUSB_SUCCESS;
-
-       usbi_mutex_lock(&ctx->open_devs_lock);
-       for (i = 0; i < nfds && num_ready > 0; i++) {
-               usbi_dbg("checking fd %d with revents = %04x", fds[i].fd, fds[i].revents);
-
-               if (!fds[i].revents)
-                       continue;
 
-               num_ready--;
+       if (GetOverlappedResult(transfer_priv->handle, &transfer_priv->overlapped, &bytes_transferred, FALSE))
+               result = NO_ERROR;
+       else
+               result = GetLastError();
 
-               transfer_priv = NULL;
-               usbi_mutex_lock(&ctx->flying_transfers_lock);
-               for_each_transfer(ctx, itransfer) {
-                       transfer_priv = usbi_get_transfer_priv(itransfer);
-                       if (transfer_priv->pollable_fd.fd == fds[i].fd)
-                               break;
-                       transfer_priv = NULL;
-               }
-               usbi_mutex_unlock(&ctx->flying_transfers_lock);
+       usbi_dbg("handling transfer %p completion with errcode %lu, length %lu",
+                USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(result), ULONG_CAST(bytes_transferred));
 
-               if (transfer_priv == NULL) {
-                       usbi_err(ctx, "could not find a matching transfer for fd %d", fds[i].fd);
-                       r = LIBUSB_ERROR_NOT_FOUND;
-                       break;
-               }
+       switch (result) {
+       case NO_ERROR:
+               status = backend->copy_transfer_data(itransfer, bytes_transferred);
+               break;
+       case ERROR_GEN_FAILURE:
+               usbi_dbg("detected endpoint stall");
+               status = LIBUSB_TRANSFER_STALL;
+               break;
+       case ERROR_SEM_TIMEOUT:
+               usbi_dbg("detected semaphore timeout");
+               status = LIBUSB_TRANSFER_TIMED_OUT;
+               break;
+       case ERROR_OPERATION_ABORTED:
+               istatus = backend->copy_transfer_data(itransfer, bytes_transferred);
+               if (istatus != LIBUSB_TRANSFER_COMPLETED)
+                       usbi_dbg("failed to copy partial data in aborted operation: %d", (int)istatus);
 
-               usbi_remove_pollfd(ctx, transfer_priv->pollable_fd.fd);
+               usbi_dbg("detected operation aborted");
+               status = LIBUSB_TRANSFER_CANCELLED;
+               break;
+       case ERROR_FILE_NOT_FOUND:
+       case ERROR_DEVICE_NOT_CONNECTED:
+       case ERROR_NO_SUCH_DEVICE:
+               usbi_dbg("detected device removed");
+               status = LIBUSB_TRANSFER_NO_DEVICE;
+               break;
+       default:
+               usbi_err(ctx, "detected I/O error %lu: %s",
+                       ULONG_CAST(result), windows_error_str(result));
+               status = LIBUSB_TRANSFER_ERROR;
+               break;
+       }
 
-               if (GetOverlappedResult(transfer_priv->handle, transfer_priv->pollable_fd.overlapped, &bytes_transferred, FALSE))
-                       result = NO_ERROR;
-               else
-                       result = GetLastError();
+       transfer_priv->handle = NULL;
 
-               windows_transfer_callback(priv->backend, itransfer, result, bytes_transferred);
-       }
-       usbi_mutex_unlock(&ctx->open_devs_lock);
+       // Backend-specific cleanup
+       backend->clear_transfer_priv(itransfer);
 
-       return r;
+       if (status == LIBUSB_TRANSFER_CANCELLED)
+               return usbi_handle_transfer_cancellation(itransfer);
+       else
+               return usbi_handle_transfer_completion(itransfer, status);
 }
 
 #if !defined(HAVE_CLOCK_GETTIME)
@@ -885,8 +892,8 @@ const struct usbi_os_backend usbi_backend = {
        windows_submit_transfer,
        windows_cancel_transfer,
        NULL,   /* clear_transfer_priv */
-       windows_handle_events,
-       NULL,   /* handle_transfer_completion */
+       NULL,   /* handle_events */
+       windows_handle_transfer_completion,
        sizeof(struct windows_context_priv),
        sizeof(union windows_device_priv),
        sizeof(union windows_device_handle_priv),
index 00cdda3..3de4f02 100644 (file)
@@ -327,6 +327,8 @@ struct windows_backend {
 
 struct windows_context_priv {
        const struct windows_backend *backend;
+       HANDLE completion_port;
+       HANDLE completion_port_thread;
 };
 
 union windows_device_priv {
@@ -340,7 +342,7 @@ union windows_device_handle_priv {
 };
 
 struct windows_transfer_priv {
-       struct winfd pollable_fd;
+       OVERLAPPED overlapped;
        HANDLE handle;
        union {
                struct usbdk_transfer_priv usbdk_priv;
@@ -351,7 +353,7 @@ struct windows_transfer_priv {
 static inline OVERLAPPED *get_transfer_priv_overlapped(struct usbi_transfer *itransfer)
 {
        struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
-       return transfer_priv->pollable_fd.overlapped;
+       return &transfer_priv->overlapped;
 }
 
 static inline void set_transfer_priv_handle(struct usbi_transfer *itransfer, HANDLE handle)
@@ -377,7 +379,7 @@ extern const struct windows_backend winusb_backend;
 
 unsigned long htab_hash(const char *str);
 enum libusb_transfer_status usbd_status_to_libusb_transfer_status(USBD_STATUS status);
-void windows_force_sync_completion(OVERLAPPED *overlapped, ULONG size);
+void windows_force_sync_completion(struct usbi_transfer *itransfer, ULONG size);
 
 #if defined(ENABLE_LOGGING)
 const char *windows_error_str(DWORD error_code);
index d9e2a9c..cdfbb17 100644 (file)
@@ -400,15 +400,27 @@ static int usbdk_get_active_config_descriptor(struct libusb_device *dev, void *b
 
 static int usbdk_open(struct libusb_device_handle *dev_handle)
 {
-       struct usbdk_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
-
-       priv->redirector_handle = usbdk_helper.StartRedirect(&priv->ID);
-       if (priv->redirector_handle == INVALID_HANDLE_VALUE) {
-               usbi_err(HANDLE_CTX(dev_handle), "Redirector startup failed");
+       struct libusb_device *dev = dev_handle->dev;
+       struct libusb_context *ctx = DEVICE_CTX(dev);
+       struct windows_context_priv *priv = usbi_get_context_priv(ctx);
+       struct usbdk_device_priv *device_priv = usbi_get_device_priv(dev);
+
+       device_priv->redirector_handle = usbdk_helper.StartRedirect(&device_priv->ID);
+       if (device_priv->redirector_handle == INVALID_HANDLE_VALUE) {
+               usbi_err(ctx, "Redirector startup failed");
+               device_priv->redirector_handle = NULL;
                return LIBUSB_ERROR_OTHER;
        }
 
-       priv->system_handle = usbdk_helper.GetRedirectorSystemHandle(priv->redirector_handle);
+       device_priv->system_handle = usbdk_helper.GetRedirectorSystemHandle(device_priv->redirector_handle);
+
+       if (CreateIoCompletionPort(device_priv->system_handle, priv->completion_port, 0, 0) == NULL) {
+               usbi_err(ctx, "failed to associate handle to I/O completion port: %s", windows_error_str(0));
+               usbdk_helper.StopRedirect(device_priv->redirector_handle);
+               device_priv->system_handle = NULL;
+               device_priv->redirector_handle = NULL;
+               return LIBUSB_ERROR_OTHER;
+       }
 
        return LIBUSB_SUCCESS;
 }
@@ -419,6 +431,9 @@ static void usbdk_close(struct libusb_device_handle *dev_handle)
 
        if (!usbdk_helper.StopRedirect(priv->redirector_handle))
                usbi_err(HANDLE_CTX(dev_handle), "Redirector shutdown failed");
+
+       priv->system_handle = NULL;
+       priv->redirector_handle = NULL;
 }
 
 static int usbdk_get_configuration(struct libusb_device_handle *dev_handle, uint8_t *config)
@@ -518,6 +533,8 @@ static int usbdk_do_control_transfer(struct usbi_transfer *itransfer)
        transfer_priv->request.BufferLength = transfer->length;
        transfer_priv->request.TransferType = ControlTransferType;
 
+       set_transfer_priv_handle(itransfer, priv->system_handle);
+
        if (transfer->buffer[0] & LIBUSB_ENDPOINT_IN)
                transResult = usbdk_helper.ReadPipe(priv->redirector_handle, &transfer_priv->request, overlapped);
        else
@@ -525,7 +542,7 @@ static int usbdk_do_control_transfer(struct usbi_transfer *itransfer)
 
        switch (transResult) {
        case TransferSuccess:
-               windows_force_sync_completion(overlapped, (ULONG)transfer_priv->request.Result.GenResult.BytesTransferred);
+               windows_force_sync_completion(itransfer, (ULONG)transfer_priv->request.Result.GenResult.BytesTransferred);
                break;
        case TransferSuccessAsync:
                break;
@@ -534,8 +551,6 @@ static int usbdk_do_control_transfer(struct usbi_transfer *itransfer)
                return LIBUSB_ERROR_IO;
        }
 
-       set_transfer_priv_handle(itransfer, priv->system_handle);
-
        return LIBUSB_SUCCESS;
 }
 
@@ -560,6 +575,8 @@ static int usbdk_do_bulk_transfer(struct usbi_transfer *itransfer)
                break;
        }
 
+       set_transfer_priv_handle(itransfer, priv->system_handle);
+
        if (IS_XFERIN(transfer))
                transferRes = usbdk_helper.ReadPipe(priv->redirector_handle, &transfer_priv->request, overlapped);
        else
@@ -567,7 +584,7 @@ static int usbdk_do_bulk_transfer(struct usbi_transfer *itransfer)
 
        switch (transferRes) {
        case TransferSuccess:
-               windows_force_sync_completion(overlapped, (ULONG)transfer_priv->request.Result.GenResult.BytesTransferred);
+               windows_force_sync_completion(itransfer, (ULONG)transfer_priv->request.Result.GenResult.BytesTransferred);
                break;
        case TransferSuccessAsync:
                break;
@@ -576,8 +593,6 @@ static int usbdk_do_bulk_transfer(struct usbi_transfer *itransfer)
                return LIBUSB_ERROR_IO;
        }
 
-       set_transfer_priv_handle(itransfer, priv->system_handle);
-
        return LIBUSB_SUCCESS;
 }
 
@@ -612,6 +627,8 @@ static int usbdk_do_iso_transfer(struct usbi_transfer *itransfer)
        for (i = 0; i < transfer->num_iso_packets; i++)
                transfer_priv->IsochronousPacketsArray[i] = transfer->iso_packet_desc[i].length;
 
+       set_transfer_priv_handle(itransfer, priv->system_handle);
+
        if (IS_XFERIN(transfer))
                transferRes = usbdk_helper.ReadPipe(priv->redirector_handle, &transfer_priv->request, overlapped);
        else
@@ -619,7 +636,7 @@ static int usbdk_do_iso_transfer(struct usbi_transfer *itransfer)
 
        switch (transferRes) {
        case TransferSuccess:
-               windows_force_sync_completion(overlapped, (ULONG)transfer_priv->request.Result.GenResult.BytesTransferred);
+               windows_force_sync_completion(itransfer, (ULONG)transfer_priv->request.Result.GenResult.BytesTransferred);
                break;
        case TransferSuccessAsync:
                break;
@@ -627,8 +644,6 @@ static int usbdk_do_iso_transfer(struct usbi_transfer *itransfer)
                return LIBUSB_ERROR_IO;
        }
 
-       set_transfer_priv_handle(itransfer, priv->system_handle);
-
        return LIBUSB_SUCCESS;
 }
 
index 7ed9de5..04dda4a 100644 (file)
@@ -465,6 +465,28 @@ static int get_interface_by_endpoint(struct libusb_config_descriptor *conf_desc,
 }
 
 /*
+ * Open a device and associate the HANDLE with the context's I/O completion port
+ */
+HANDLE windows_open(struct libusb_device *dev, const char *path, DWORD access)
+{
+       struct libusb_context *ctx = DEVICE_CTX(dev);
+       struct windows_context_priv *priv = usbi_get_context_priv(ctx);
+       HANDLE handle;
+
+       handle = CreateFileA(path, access, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+       if (handle == INVALID_HANDLE_VALUE)
+               return handle;
+
+       if (CreateIoCompletionPort(handle, priv->completion_port, 0, 0) == NULL) {
+               usbi_err(ctx, "failed to associate handle to I/O completion port: %s", windows_error_str(0));
+               CloseHandle(handle);
+               return INVALID_HANDLE_VALUE;
+       }
+
+       return handle;
+}
+
+/*
  * Populate the endpoints addresses of the device_priv interface helper structs
  */
 static int windows_assign_endpoints(struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting)
@@ -822,8 +844,7 @@ static int init_device(struct libusb_device *dev, struct libusb_device *parent_d
                dev->parent_dev = parent_dev;
                priv->depth = depth;
 
-               hub_handle = CreateFileA(parent_priv->path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
-                                    0, NULL);
+               hub_handle = CreateFileA(parent_priv->path, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
                if (hub_handle == INVALID_HANDLE_VALUE) {
                        usbi_warn(ctx, "could not open hub %s: %s", parent_priv->path, windows_error_str(0));
                        return LIBUSB_ERROR_ACCESS;
@@ -2090,8 +2111,7 @@ static int winusbx_open(int sub_api, struct libusb_device_handle *dev_handle)
        for (i = 0; i < USB_MAXINTERFACES; i++) {
                if ((priv->usb_interface[i].path != NULL)
                                && (priv->usb_interface[i].apib->id == USB_API_WINUSBX)) {
-                       file_handle = CreateFileA(priv->usb_interface[i].path, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ,
-                               NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+                       file_handle = windows_open(dev_handle->dev, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE);
                        if (file_handle == INVALID_HANDLE_VALUE) {
                                usbi_err(HANDLE_CTX(dev_handle), "could not open device %s (interface %d): %s", priv->usb_interface[i].path, i, windows_error_str(0));
                                switch (GetLastError()) {
@@ -2103,6 +2123,7 @@ static int winusbx_open(int sub_api, struct libusb_device_handle *dev_handle)
                                        return LIBUSB_ERROR_IO;
                                }
                        }
+
                        handle_priv->interface_handle[i].dev_handle = file_handle;
                }
        }
@@ -2262,8 +2283,7 @@ static int winusbx_claim_interface(int sub_api, struct libusb_device_handle *dev
                                        *dev_interface_path_guid_start = '\0';
 
                                        if (strncmp(dev_interface_path, priv->usb_interface[iface].path, strlen(dev_interface_path)) == 0) {
-                                               file_handle = CreateFileA(filter_path, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ,
-                                                       NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+                                               file_handle = windows_open(dev_handle->dev, filter_path, GENERIC_READ | GENERIC_WRITE);
                                                if (file_handle != INVALID_HANDLE_VALUE) {
                                                        if (WinUSBX[sub_api].Initialize(file_handle, &winusb_handle)) {
                                                                // Replace the existing file handle with the working one
@@ -2459,7 +2479,7 @@ static int winusbx_submit_control_transfer(int sub_api, struct usbi_transfer *it
                        usbi_warn(TRANSFER_CTX(transfer), "cannot set configuration other than the default one");
                        return LIBUSB_ERROR_NOT_SUPPORTED;
                }
-               windows_force_sync_completion(overlapped, 0);
+               windows_force_sync_completion(itransfer, 0);
        } else {
                if (!WinUSBX[sub_api].ControlTransfer(winusb_handle, *setup, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, size, NULL, overlapped)) {
                        if (GetLastError() != ERROR_IO_PENDING) {
@@ -3400,8 +3420,7 @@ static int hid_open(int sub_api, struct libusb_device_handle *dev_handle)
        for (i = 0; i < USB_MAXINTERFACES; i++) {
                if ((priv->usb_interface[i].path != NULL)
                                && (priv->usb_interface[i].apib->id == USB_API_HID)) {
-                       hid_handle = CreateFileA(priv->usb_interface[i].path, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ,
-                               NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+                       hid_handle = windows_open(dev, priv->usb_interface[i].path, GENERIC_READ | GENERIC_WRITE);
                        /*
                         * http://www.lvr.com/hidfaq.htm: Why do I receive "Access denied" when attempting to access my HID?
                         * "Windows 2000 and later have exclusive read/write access to HIDs that are configured as a system
@@ -3411,8 +3430,7 @@ static int hid_open(int sub_api, struct libusb_device_handle *dev_handle)
                         */
                        if (hid_handle == INVALID_HANDLE_VALUE) {
                                usbi_warn(HANDLE_CTX(dev_handle), "could not open HID device in R/W mode (keyboard or mouse?) - trying without");
-                               hid_handle = CreateFileA(priv->usb_interface[i].path, 0, FILE_SHARE_WRITE | FILE_SHARE_READ,
-                                       NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+                               hid_handle = windows_open(dev, priv->usb_interface[i].path, 0);
                                if (hid_handle == INVALID_HANDLE_VALUE) {
                                        usbi_err(HANDLE_CTX(dev_handle), "could not open device %s (interface %d): %s", priv->path, i, windows_error_str(0));
                                        switch (GetLastError()) {
@@ -3692,7 +3710,7 @@ static int hid_submit_control_transfer(int sub_api, struct usbi_transfer *itrans
 
        if (r == LIBUSB_COMPLETED) {
                // Force request to be completed synchronously. Transferred size has been set by previous call
-               windows_force_sync_completion(overlapped, (ULONG)size);
+               windows_force_sync_completion(itransfer, (ULONG)size);
                r = LIBUSB_SUCCESS;
        }
 
index d21e1b9..e7b7f08 100644 (file)
@@ -1 +1 @@
-#define LIBUSB_NANO 11530
+#define LIBUSB_NANO 11531