Windows: Use I/O completion ports for transfers

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>
diff --git a/libusb/os/poll_windows.c b/libusb/os/poll_windows.c
index bf9f7b7..c2bc105 100644
--- a/libusb/os/poll_windows.c
+++ b/libusb/os/poll_windows.c
@@ -21,15 +21,6 @@
 /*
  * 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.
@@ -44,12 +35,9 @@
 #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 @@
 		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 @@
 		free(fd);
 		return NULL;
 	}
-	fd->type = type;
 	fd->refcount = refcount;
 	return fd;
 }
@@ -171,43 +158,6 @@
 	}
 }
 
-/*
- * 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 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 @@
 
 	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 @@
 
 	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);
 
diff --git a/libusb/os/poll_windows.h b/libusb/os/poll_windows.h
index df1781b..14a5b27 100644
--- a/libusb/os/poll_windows.h
+++ b/libusb/os/poll_windows.h
@@ -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 @@
 	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);
diff --git a/libusb/os/windows_common.c b/libusb/os/windows_common.c
index e2f9116..9406632 100644
--- a/libusb/os/windows_common.c
+++ b/libusb/os/windows_common.c
@@ -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 @@
 }
 
 /*
-* 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 @@
 	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 @@
 	}
 
 	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 @@
 		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
+
+		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);
 	}
 
-	// Cancel polling
-	usbi_close(transfer_priv->pollable_fd.fd);
-	transfer_priv->pollable_fd = INVALID_WINFD;
-	transfer_priv->handle = NULL;
+	usbi_dbg("I/O completion thread exiting");
 
-	// Backend-specific cleanup
-	backend->clear_transfer_priv(itransfer);
-
-	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 @@
 	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 @@
 	// 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 @@
 		} 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 @@
 
 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 @@
 		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 @@
 	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 @@
 		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 @@
 	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 @@
 	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 @@
 	// 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 @@
 	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 (GetOverlappedResult(transfer_priv->handle, &transfer_priv->overlapped, &bytes_transferred, FALSE))
+		result = NO_ERROR;
+	else
+		result = GetLastError();
 
-		if (!fds[i].revents)
-			continue;
+	usbi_dbg("handling transfer %p completion with errcode %lu, length %lu",
+		 USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(result), ULONG_CAST(bytes_transferred));
 
-		num_ready--;
+	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);
 
-		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);
-
-		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;
-		}
-
-		usbi_remove_pollfd(ctx, transfer_priv->pollable_fd.fd);
-
-		if (GetOverlappedResult(transfer_priv->handle, transfer_priv->pollable_fd.overlapped, &bytes_transferred, FALSE))
-			result = NO_ERROR;
-		else
-			result = GetLastError();
-
-		windows_transfer_callback(priv->backend, itransfer, result, bytes_transferred);
+		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;
 	}
-	usbi_mutex_unlock(&ctx->open_devs_lock);
 
-	return r;
+	transfer_priv->handle = NULL;
+
+	// Backend-specific cleanup
+	backend->clear_transfer_priv(itransfer);
+
+	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 @@
 	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),
diff --git a/libusb/os/windows_common.h b/libusb/os/windows_common.h
index 00cdda3..3de4f02 100644
--- a/libusb/os/windows_common.h
+++ b/libusb/os/windows_common.h
@@ -327,6 +327,8 @@
 
 struct windows_context_priv {
 	const struct windows_backend *backend;
+	HANDLE completion_port;
+	HANDLE completion_port_thread;
 };
 
 union windows_device_priv {
@@ -340,7 +342,7 @@
 };
 
 struct windows_transfer_priv {
-	struct winfd pollable_fd;
+	OVERLAPPED overlapped;
 	HANDLE handle;
 	union {
 		struct usbdk_transfer_priv usbdk_priv;
@@ -351,7 +353,7 @@
 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 @@
 
 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);
diff --git a/libusb/os/windows_usbdk.c b/libusb/os/windows_usbdk.c
index d9e2a9c..cdfbb17 100644
--- a/libusb/os/windows_usbdk.c
+++ b/libusb/os/windows_usbdk.c
@@ -400,15 +400,27 @@
 
 static int usbdk_open(struct libusb_device_handle *dev_handle)
 {
-	struct usbdk_device_priv *priv = usbi_get_device_priv(dev_handle->dev);
+	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);
 
-	priv->redirector_handle = usbdk_helper.StartRedirect(&priv->ID);
-	if (priv->redirector_handle == INVALID_HANDLE_VALUE) {
-		usbi_err(HANDLE_CTX(dev_handle), "Redirector startup failed");
+	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 @@
 
 	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 @@
 	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 @@
 
 	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 @@
 		return LIBUSB_ERROR_IO;
 	}
 
-	set_transfer_priv_handle(itransfer, priv->system_handle);
-
 	return LIBUSB_SUCCESS;
 }
 
@@ -560,6 +575,8 @@
 		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 @@
 
 	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 @@
 		return LIBUSB_ERROR_IO;
 	}
 
-	set_transfer_priv_handle(itransfer, priv->system_handle);
-
 	return LIBUSB_SUCCESS;
 }
 
@@ -612,6 +627,8 @@
 	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 @@
 
 	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 @@
 		return LIBUSB_ERROR_IO;
 	}
 
-	set_transfer_priv_handle(itransfer, priv->system_handle);
-
 	return LIBUSB_SUCCESS;
 }
 
diff --git a/libusb/os/windows_winusb.c b/libusb/os/windows_winusb.c
index 7ed9de5..04dda4a 100644
--- a/libusb/os/windows_winusb.c
+++ b/libusb/os/windows_winusb.c
@@ -465,6 +465,28 @@
 }
 
 /*
+ * 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 @@
 		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 @@
 	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 @@
 					return LIBUSB_ERROR_IO;
 				}
 			}
+
 			handle_priv->interface_handle[i].dev_handle = file_handle;
 		}
 	}
@@ -2262,8 +2283,7 @@
 					*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 @@
 			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 @@
 	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 @@
 			 */
 			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 @@
 
 	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;
 	}
 
diff --git a/libusb/version_nano.h b/libusb/version_nano.h
index d21e1b9..e7b7f08 100644
--- a/libusb/version_nano.h
+++ b/libusb/version_nano.h
@@ -1 +1 @@
-#define LIBUSB_NANO 11530
+#define LIBUSB_NANO 11531