summaryrefslogtreecommitdiff
path: root/libusb
diff options
context:
space:
mode:
authorChris Dickens <christopher.a.dickens@gmail.com>2020-08-10 19:19:21 -0700
committerChris Dickens <christopher.a.dickens@gmail.com>2020-08-10 19:19:21 -0700
commitba6b8bcb7ea204e65a3deec3be81aacc9f4b6d5a (patch)
tree5f9973529ab12e853b36172846c8f26b49ced300 /libusb
parent9576ad4b8f94698aeba5218caf9e9e1f28a6f44d (diff)
downloadlibusb-ba6b8bcb7ea204e65a3deec3be81aacc9f4b6d5a.tar.gz
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>
Diffstat (limited to 'libusb')
-rw-r--r--libusb/os/poll_windows.c62
-rw-r--r--libusb/os/poll_windows.h11
-rw-r--r--libusb/os/windows_common.c291
-rw-r--r--libusb/os/windows_common.h8
-rw-r--r--libusb/os/windows_usbdk.c45
-rw-r--r--libusb/os/windows_winusb.c42
-rw-r--r--libusb/version_nano.h2
7 files changed, 219 insertions, 242 deletions
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 @@ 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);
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 @@ 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);
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 @@ 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),
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_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);
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_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;
}
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 @@ 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;
}
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