diff options
author | Chris Dickens <christopher.a.dickens@gmail.com> | 2020-08-10 19:19:21 -0700 |
---|---|---|
committer | Chris Dickens <christopher.a.dickens@gmail.com> | 2020-08-10 19:19:21 -0700 |
commit | ba6b8bcb7ea204e65a3deec3be81aacc9f4b6d5a (patch) | |
tree | 5f9973529ab12e853b36172846c8f26b49ced300 /libusb/os | |
parent | 9576ad4b8f94698aeba5218caf9e9e1f28a6f44d (diff) | |
download | libusb-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/os')
-rw-r--r-- | libusb/os/poll_windows.c | 62 | ||||
-rw-r--r-- | libusb/os/poll_windows.h | 11 | ||||
-rw-r--r-- | libusb/os/windows_common.c | 291 | ||||
-rw-r--r-- | libusb/os/windows_common.h | 8 | ||||
-rw-r--r-- | libusb/os/windows_usbdk.c | 45 | ||||
-rw-r--r-- | libusb/os/windows_winusb.c | 42 |
6 files changed, 218 insertions, 241 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; } |