core: update usbi_dbg to take the context as an argument
[platform/upstream/libusb.git] / libusb / os / windows_common.c
index d8f1e38..eefd01e 100644 (file)
@@ -24,9 +24,6 @@
 
 #include <config.h>
 
-#include <errno.h>
-#include <inttypes.h>
-#include <process.h>
 #include <stdio.h>
 
 #include "libusbi.h"
 // 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;
 
-#if !defined(HAVE_CLOCK_GETTIME)
-// Global variables for clock_gettime mechanism
-static uint64_t hires_ticks_to_ps;
-static uint64_t hires_frequency;
-#endif
-
 /*
 * Converts a windows error to human readable string
 * uses retval as errorcode, or, if 0, use GetLastError()
@@ -103,6 +94,35 @@ const char *windows_error_str(DWORD error_code)
 }
 #endif
 
+/*
+ * Dynamically loads a DLL from the Windows system directory.  Unlike the
+ * LoadLibraryA() function, this function will not search through any
+ * directories to try and find the library.
+ */
+HMODULE load_system_library(struct libusb_context *ctx, const char *name)
+{
+       char library_path[MAX_PATH];
+       char *filename_start;
+       UINT length;
+
+       length = GetSystemDirectoryA(library_path, sizeof(library_path));
+       if ((length == 0) || (length >= (UINT)sizeof(library_path))) {
+               usbi_err(ctx, "program assertion failed - could not get system directory");
+               return NULL;
+       }
+
+       filename_start = library_path + length;
+       // Append '\' + name + ".dll" + NUL
+       length += 1 + (UINT)strlen(name) + 4 + 1;
+       if (length >= (UINT)sizeof(library_path)) {
+               usbi_err(ctx, "program assertion failed - library path buffer overflow");
+               return NULL;
+       }
+
+       sprintf(filename_start, "\\%s.dll", name);
+       return LoadLibraryA(library_path);
+}
+
 /* Hash table functions - modified From glibc 2.3.2:
    [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986
    [Knuth]            The Art of Computer Programming, part 3 (6.4)  */
@@ -125,14 +145,14 @@ static unsigned long htab_filled;
 static bool htab_create(struct libusb_context *ctx)
 {
        if (htab_table != NULL) {
-               usbi_err(ctx, "program assertion falied - hash table already allocated");
+               usbi_err(ctx, "program assertion failed - hash table already allocated");
                return true;
        }
 
        // Create a mutex
        usbi_mutex_init(&htab_mutex);
 
-       usbi_dbg("using %lu entries hash table", HTAB_SIZE);
+       usbi_dbg(ctx, "using %lu entries hash table", HTAB_SIZE);
        htab_filled = 0;
 
        // allocate memory and zero out.
@@ -201,7 +221,7 @@ unsigned long htab_hash(const char *str)
                if ((htab_table[idx].used == hval) && (strcmp(str, htab_table[idx].str) == 0))
                        goto out_unlock; // existing hash
 
-               usbi_dbg("hash collision ('%s' vs '%s')", str, htab_table[idx].str);
+               usbi_dbg(NULL, "hash collision ('%s' vs '%s')", str, htab_table[idx].str);
 
                // Second hash function, as suggested in [Knuth]
                hval2 = 1UL + hval % (HTAB_SIZE - 2);
@@ -263,36 +283,28 @@ enum libusb_transfer_status usbd_status_to_libusb_transfer_status(USBD_STATUS st
        case USBD_STATUS_DEVICE_GONE:
                return LIBUSB_TRANSFER_NO_DEVICE;
        default:
-               usbi_dbg("USBD_STATUS 0x%08lx translated to LIBUSB_TRANSFER_ERROR", ULONG_CAST(status));
+               usbi_dbg(NULL, "USBD_STATUS 0x%08lx translated to LIBUSB_TRANSFER_ERROR", ULONG_CAST(status));
                return LIBUSB_TRANSFER_ERROR;
        }
 }
 
 /*
-* 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 libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+       struct windows_context_priv *priv = usbi_get_context_priv(TRANSFER_CTX(transfer));
+       struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
+       OVERLAPPED *overlapped = &transfer_priv->overlapped;
+
+       usbi_dbg(TRANSFER_CTX(transfer), "transfer %p, length %lu", transfer, ULONG_CAST(size));
+
        overlapped->Internal = (ULONG_PTR)STATUS_SUCCESS;
        overlapped->InternalHigh = (ULONG_PTR)size;
-       SetEvent(overlapped->hEvent);
-}
 
-static void windows_init_clock(void)
-{
-#if !defined(HAVE_CLOCK_GETTIME)
-       LARGE_INTEGER li_frequency;
-
-       // Microsoft says that the QueryPerformanceFrequency() and
-       // QueryPerformanceCounter() functions always succeed on XP and later
-       QueryPerformanceFrequency(&li_frequency);
-
-       // The hires frequency can go as high as 4 GHz, so we'll use a conversion
-       // to picoseconds to compute the tv_nsecs part in clock_gettime
-       hires_frequency = li_frequency.QuadPart;
-       hires_ticks_to_ps = UINT64_C(1000000000000) / hires_frequency;
-       usbi_dbg("hires timer frequency: %"PRIu64" Hz", hires_frequency);
-#endif
+       if (!PostQueuedCompletionStatus(priv->completion_port, (DWORD)size, (ULONG_PTR)transfer->dev_handle, overlapped))
+               usbi_err(TRANSFER_CTX(transfer), "failed to post I/O completion: %s", windows_error_str(0));
 }
 
 /* Windows version detection */
@@ -310,27 +322,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,132 +377,140 @@ 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";
 
        if (vi.wServicePackMinor)
-               usbi_dbg("Windows %s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, arch);
+               usbi_dbg(NULL, "Windows %s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, arch);
        else if (vi.wServicePackMajor)
-               usbi_dbg("Windows %s SP%u %s", w, vi.wServicePackMajor, arch);
+               usbi_dbg(NULL, "Windows %s SP%u %s", w, vi.wServicePackMajor, arch);
        else
-               usbi_dbg("Windows %s %s", w, arch);
+               usbi_dbg(NULL, "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 libusb_device_handle *dev_handle;
+       struct libusb_device_handle *opened_device_handle;
+       struct windows_device_handle_priv *handle_priv;
+       struct windows_transfer_priv *transfer_priv;
+       struct usbi_transfer *itransfer;
+       bool found;
 
-       usbi_dbg("handling I/O completion with errcode %lu, length %lu",
-               ULONG_CAST(error), ULONG_CAST(bytes_transferred));
+       usbi_dbg(ctx, "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) {
+               overlapped = NULL;
+               if (!GetQueuedCompletionStatus(iocp, &num_bytes, &completion_key, &overlapped, INFINITE) && (overlapped == NULL)) {
+                       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:
-               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) {
+                       // Signal to quit
+                       if (completion_key != (ULONG_PTR)ctx)
+                               usbi_err(ctx, "program assertion failed - overlapped is NULL");
+                       break;
+               }
 
-       // Cancel polling
-       usbi_close(transfer_priv->pollable_fd.fd);
-       transfer_priv->pollable_fd = INVALID_WINFD;
-       transfer_priv->handle = NULL;
+               // Find the transfer associated with the OVERLAPPED that just completed.
+               // If we cannot find a match, the I/O operation originated from outside of libusb
+               // (e.g. within libusbK) and we need to ignore it.
+               dev_handle = (struct libusb_device_handle *)completion_key;
 
-       // Backend-specific cleanup
-       backend->clear_transfer_priv(itransfer);
+               found = false;
+               transfer_priv = NULL;
 
-       if (status == LIBUSB_TRANSFER_CANCELLED)
-               usbi_handle_transfer_cancellation(itransfer);
-       else
-               usbi_handle_transfer_completion(itransfer, status);
+               // Issue 912: lock opened device handles in context to search the current device handle
+               // to avoid accessing unallocated memory after device has been closed
+               usbi_mutex_lock(&ctx->open_devs_lock);
+               for_each_open_device(ctx, opened_device_handle) {
+                       if (dev_handle == opened_device_handle) {
+                               handle_priv = usbi_get_device_handle_priv(dev_handle);
+
+                               usbi_mutex_lock(&dev_handle->lock);
+                               list_for_each_entry(transfer_priv, &handle_priv->active_transfers, list, struct windows_transfer_priv) {
+                                       if (overlapped == &transfer_priv->overlapped) {
+                                               // This OVERLAPPED belongs to us, remove the transfer from the device handle's list
+                                               list_del(&transfer_priv->list);
+                                               found = true;
+                                               break;
+                                       }
+                               }
+                               usbi_mutex_unlock(&dev_handle->lock);
+                       }
+               }
+               usbi_mutex_unlock(&ctx->open_devs_lock);
+
+               if (!found) {
+                       usbi_dbg(ctx, "ignoring overlapped %p for handle %p (device %u.%u)",
+                               overlapped, dev_handle, dev_handle->dev->bus_number, dev_handle->dev->device_address);
+                       continue;
+               }
+
+               itransfer = (struct usbi_transfer *)((unsigned char *)transfer_priv + PTR_ALIGN(sizeof(*transfer_priv)));
+               usbi_dbg(ctx, "transfer %p completed, length %lu",
+                        USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(num_bytes));
+               usbi_signal_transfer_completion(itransfer);
+       }
+
+       usbi_dbg(ctx, "I/O completion thread exiting");
+
+       return 0;
 }
 
 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;
-
-       sprintf(mutex_name, "libusb_init%08lX", ULONG_CAST(GetCurrentProcessId() & 0xFFFFFFFFU));
-       mutex = CreateMutexA(NULL, FALSE, mutex_name);
-       if (mutex == NULL) {
-               usbi_err(ctx, "could not create mutex: %s", windows_error_str(0));
-               return LIBUSB_ERROR_NO_MEM;
-       }
-
-       // A successful wait gives this thread ownership of the mutex
-       // => any concurent wait stalls until the mutex is released
-       if (WaitForSingleObject(mutex, INFINITE) != WAIT_OBJECT_0) {
-               usbi_err(ctx, "failure to access mutex: %s", windows_error_str(0));
-               CloseHandle(mutex);
-               return LIBUSB_ERROR_NO_MEM;
-       }
+       int r;
 
        // 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)
@@ -500,50 +519,64 @@ static int windows_init(struct libusb_context *ctx)
 
                r = usbdk_backend.init(ctx);
                if (r == LIBUSB_SUCCESS) {
-                       usbi_dbg("UsbDk backend is available");
+                       usbi_dbg(ctx, "UsbDk backend is available");
                        usbdk_available = true;
                } 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();
                --init_count;
        }
 
-       ReleaseMutex(mutex);
-       CloseHandle(mutex);
        return r;
 }
 
 static void windows_exit(struct libusb_context *ctx)
 {
-       char mutex_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
-       HANDLE mutex;
+       struct windows_context_priv *priv = usbi_get_context_priv(ctx);
 
-       sprintf(mutex_name, "libusb_init%08lX", ULONG_CAST(GetCurrentProcessId() & 0xFFFFFFFFU));
-       mutex = CreateMutexA(NULL, FALSE, mutex_name);
-       if (mutex == NULL)
-               return;
+       // A NULL completion status will indicate to the thread that it is time to exit
+       if (!PostQueuedCompletionStatus(priv->completion_port, 0, (ULONG_PTR)ctx, NULL))
+               usbi_err(ctx, "failed to post I/O completion: %s", windows_error_str(0));
 
-       // A successful wait gives this thread ownership of the mutex
-       // => any concurent wait stalls until the mutex is released
-       if (WaitForSingleObject(mutex, INFINITE) != WAIT_OBJECT_0) {
-               usbi_err(ctx, "failed to access mutex: %s", windows_error_str(0));
-               CloseHandle(mutex);
-               return;
-       }
+       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
@@ -554,9 +587,6 @@ static void windows_exit(struct libusb_context *ctx)
                winusb_backend.exit(ctx);
                htab_destroy();
        }
-
-       ReleaseMutex(mutex);
-       CloseHandle(mutex);
 }
 
 static int windows_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap)
@@ -565,19 +595,17 @@ static int windows_set_option(struct libusb_context *ctx, enum libusb_option opt
 
        UNUSED(ap);
 
-       switch ((int)option) {
-       case LIBUSB_OPTION_USE_USBDK:
-               if (usbdk_available) {
-                       usbi_dbg("switching context %p to use UsbDk backend", ctx);
-                       priv->backend = &usbdk_backend;
-               } else {
+       if (option == LIBUSB_OPTION_USE_USBDK) {
+               if (!usbdk_available) {
                        usbi_err(ctx, "UsbDk backend not available");
                        return LIBUSB_ERROR_NOT_FOUND;
                }
+               usbi_dbg(ctx, "switching context %p to use UsbDk backend", ctx);
+               priv->backend = &usbdk_backend;
                return LIBUSB_SUCCESS;
-       default:
-               return LIBUSB_ERROR_NOT_SUPPORTED;
        }
+
+       return LIBUSB_ERROR_NOT_SUPPORTED;
 }
 
 static int windows_get_device_list(struct libusb_context *ctx, struct discovered_devs **discdevs)
@@ -589,6 +617,9 @@ static int windows_get_device_list(struct libusb_context *ctx, struct discovered
 static int windows_open(struct libusb_device_handle *dev_handle)
 {
        struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle));
+       struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
+
+       list_init(&handle_priv->active_transfers);
        return priv->backend->open(dev_handle);
 }
 
@@ -673,20 +704,18 @@ static void windows_destroy_device(struct libusb_device *dev)
 static int windows_submit_transfer(struct usbi_transfer *itransfer)
 {
        struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-       struct libusb_context *ctx = TRANSFER_CTX(transfer);
+       struct libusb_device_handle *dev_handle = transfer->dev_handle;
+       struct libusb_context *ctx = HANDLE_CTX(dev_handle);
        struct windows_context_priv *priv = usbi_get_context_priv(ctx);
+       struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle);
        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");
@@ -696,26 +725,25 @@ 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;
        }
 
+       // Add transfer to the device handle's list
+       usbi_mutex_lock(&dev_handle->lock);
+       list_add_tail(&transfer_priv->list, &handle_priv->active_transfers);
+       usbi_mutex_unlock(&dev_handle->lock);
+
        r = priv->backend->submit_transfer(itransfer);
        if (r != LIBUSB_SUCCESS) {
+               // Remove the unsuccessful transfer from the device handle's list
+               usbi_mutex_lock(&dev_handle->lock);
+               list_del(&transfer_priv->list);
+               usbi_mutex_unlock(&dev_handle->lock);
+
                // 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;
        }
@@ -725,16 +753,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;
 }
 
@@ -746,7 +764,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;
@@ -758,98 +776,91 @@ 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);
-               list_for_each_entry(itransfer, &ctx->flying_transfers, list, struct usbi_transfer) {
-                       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(ctx, "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(ctx, "detected endpoint stall");
+               status = LIBUSB_TRANSFER_STALL;
+               break;
+       case ERROR_SEM_TIMEOUT:
+               usbi_dbg(ctx, "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(ctx, "failed to copy partial data in aborted operation: %d", (int)istatus);
 
-               usbi_remove_pollfd(ctx, transfer_priv->pollable_fd.fd);
+               usbi_dbg(ctx, "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(ctx, "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)
-int usbi_clock_gettime(int clk_id, struct timespec *tp)
+void usbi_get_monotonic_time(struct timespec *tp)
 {
+       static LONG hires_counter_init;
+       static uint64_t hires_ticks_to_ps;
+       static uint64_t hires_frequency;
        LARGE_INTEGER hires_counter;
-#if !defined(_MSC_VER) || (_MSC_VER < 1900)
-       FILETIME filetime;
-       ULARGE_INTEGER rtime;
-#endif
 
-       switch (clk_id) {
-       case USBI_CLOCK_MONOTONIC:
-               if (hires_frequency) {
-                       QueryPerformanceCounter(&hires_counter);
-                       tp->tv_sec = (long)(hires_counter.QuadPart / hires_frequency);
-                       tp->tv_nsec = (long)(((hires_counter.QuadPart % hires_frequency) * hires_ticks_to_ps) / UINT64_C(1000));
-                       return 0;
-               }
-               // Return real-time if monotonic was not detected @ timer init
-               // Fall through
-       case USBI_CLOCK_REALTIME:
-#if defined(_MSC_VER) && (_MSC_VER >= 1900)
-               if (!timespec_get(tp, TIME_UTC)) {
-                       errno = EIO;
-                       return -1;
-               }
-#else
-               // We follow http://msdn.microsoft.com/en-us/library/ms724928%28VS.85%29.aspx
-               // with a predef epoch time to have an epoch that starts at 1970.01.01 00:00
-               // Note however that our resolution is bounded by the Windows system time
-               // functions and is at best of the order of 1 ms (or, usually, worse)
-               GetSystemTimeAsFileTime(&filetime);
-               rtime.LowPart = filetime.dwLowDateTime;
-               rtime.HighPart = filetime.dwHighDateTime;
-               rtime.QuadPart -= EPOCH_TIME;
-               tp->tv_sec = (long)(rtime.QuadPart / 10000000);
-               tp->tv_nsec = (long)((rtime.QuadPart % 10000000) * 100);
-#endif
-               return 0;
-       default:
-               errno = EINVAL;
-               return -1;
+       if (InterlockedExchange(&hires_counter_init, 1L) == 0L) {
+               LARGE_INTEGER li_frequency;
+
+               // Microsoft says that the QueryPerformanceFrequency() and
+               // QueryPerformanceCounter() functions always succeed on XP and later
+               QueryPerformanceFrequency(&li_frequency);
+
+               // The hires frequency can go as high as 4 GHz, so we'll use a conversion
+               // to picoseconds to compute the tv_nsecs part
+               hires_frequency = li_frequency.QuadPart;
+               hires_ticks_to_ps = UINT64_C(1000000000000) / hires_frequency;
        }
+
+       QueryPerformanceCounter(&hires_counter);
+       tp->tv_sec = (long)(hires_counter.QuadPart / hires_frequency);
+       tp->tv_nsec = (long)(((hires_counter.QuadPart % hires_frequency) * hires_ticks_to_ps) / UINT64_C(1000));
 }
-#endif
 
 // NB: MSVC6 does not support named initializers.
 const struct usbi_os_backend usbi_backend = {
@@ -884,10 +895,10 @@ 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),
+       sizeof(struct windows_device_handle_priv),
        sizeof(struct windows_transfer_priv),
 };