From 3d7c48a6e5c5c532cdd66b3ba5a8c5911bcf2383 Mon Sep 17 00:00:00 2001 From: ZoRo Date: Sat, 19 Feb 2022 12:47:26 +0000 Subject: Initial --- src/os/windows_common.c | 904 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 904 insertions(+) create mode 100644 src/os/windows_common.c (limited to 'src/os/windows_common.c') diff --git a/src/os/windows_common.c b/src/os/windows_common.c new file mode 100644 index 0000000..eefd01e --- /dev/null +++ b/src/os/windows_common.c @@ -0,0 +1,904 @@ +/* + * windows backend for libusb 1.0 + * Copyright © 2009-2012 Pete Batard + * With contributions from Michael Plante, Orin Eman et al. + * Parts of this code adapted from libusb-win32-v1 by Stephan Meyer + * HID Reports IOCTLs inspired from HIDAPI by Alan Ott, Signal 11 Software + * Hash table functions adapted from glibc, by Ulrich Drepper et al. + * Major code testing contribution by Xiaofan Chen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include + +#include "libusbi.h" +#include "windows_common.h" + +#define EPOCH_TIME UINT64_C(116444736000000000) // 1970.01.01 00:00:000 in MS Filetime + +#define STATUS_SUCCESS ((ULONG_PTR)0UL) + +// Public +enum windows_version windows_version = WINDOWS_UNDEFINED; + +// Global variables for init/exit +static unsigned int init_count; +static bool usbdk_available; + +/* +* Converts a windows error to human readable string +* uses retval as errorcode, or, if 0, use GetLastError() +*/ +#if defined(ENABLE_LOGGING) +const char *windows_error_str(DWORD error_code) +{ + static char err_string[256]; + + DWORD size; + int len; + + if (error_code == 0) + error_code = GetLastError(); + + len = sprintf(err_string, "[%lu] ", ULONG_CAST(error_code)); + + // Translate codes returned by SetupAPI. The ones we are dealing with are either + // in 0x0000xxxx or 0xE000xxxx and can be distinguished from standard error codes. + // See http://msdn.microsoft.com/en-us/library/windows/hardware/ff545011.aspx + switch (error_code & 0xE0000000) { + case 0: + error_code = HRESULT_FROM_WIN32(error_code); // Still leaves ERROR_SUCCESS unmodified + break; + case 0xE0000000: + error_code = 0x80000000 | (FACILITY_SETUPAPI << 16) | (error_code & 0x0000FFFF); + break; + default: + break; + } + + size = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + &err_string[len], sizeof(err_string) - len, NULL); + if (size == 0) { + DWORD format_error = GetLastError(); + if (format_error) + snprintf(err_string, sizeof(err_string), + "Windows error code %lu (FormatMessage error code %lu)", + ULONG_CAST(error_code), ULONG_CAST(format_error)); + else + snprintf(err_string, sizeof(err_string), "Unknown error code %lu", + ULONG_CAST(error_code)); + } else { + // Remove CRLF from end of message, if present + size_t pos = len + size - 2; + if (err_string[pos] == '\r') + err_string[pos] = '\0'; + } + + return err_string; +} +#endif + +/* + * Dynamically loads a DLL from the Windows system directory. Unlike the + * LoadLibraryA() function, this function will not search through any + * directories to try and find the library. + */ +HMODULE load_system_library(struct libusb_context *ctx, const char *name) +{ + char library_path[MAX_PATH]; + char *filename_start; + UINT length; + + length = GetSystemDirectoryA(library_path, sizeof(library_path)); + if ((length == 0) || (length >= (UINT)sizeof(library_path))) { + usbi_err(ctx, "program assertion failed - could not get system directory"); + return NULL; + } + + filename_start = library_path + length; + // Append '\' + name + ".dll" + NUL + length += 1 + (UINT)strlen(name) + 4 + 1; + if (length >= (UINT)sizeof(library_path)) { + usbi_err(ctx, "program assertion failed - library path buffer overflow"); + return NULL; + } + + sprintf(filename_start, "\\%s.dll", name); + return LoadLibraryA(library_path); +} + +/* Hash table functions - modified From glibc 2.3.2: + [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986 + [Knuth] The Art of Computer Programming, part 3 (6.4) */ + +#define HTAB_SIZE 1021UL // *MUST* be a prime number!! + +typedef struct htab_entry { + unsigned long used; + char *str; +} htab_entry; + +static htab_entry *htab_table; +static usbi_mutex_t htab_mutex; +static unsigned long htab_filled; + +/* Before using the hash table we must allocate memory for it. + We allocate one element more as the found prime number says. + This is done for more effective indexing as explained in the + comment for the hash function. */ +static bool htab_create(struct libusb_context *ctx) +{ + if (htab_table != NULL) { + usbi_err(ctx, "program assertion failed - hash table already allocated"); + return true; + } + + // Create a mutex + usbi_mutex_init(&htab_mutex); + + usbi_dbg(ctx, "using %lu entries hash table", HTAB_SIZE); + htab_filled = 0; + + // allocate memory and zero out. + htab_table = calloc(HTAB_SIZE + 1, sizeof(htab_entry)); + if (htab_table == NULL) { + usbi_err(ctx, "could not allocate space for hash table"); + return false; + } + + return true; +} + +/* After using the hash table it has to be destroyed. */ +static void htab_destroy(void) +{ + unsigned long i; + + if (htab_table == NULL) + return; + + for (i = 0; i < HTAB_SIZE; i++) + free(htab_table[i].str); + + safe_free(htab_table); + + usbi_mutex_destroy(&htab_mutex); +} + +/* This is the search function. It uses double hashing with open addressing. + We use a trick to speed up the lookup. The table is created with one + more element available. This enables us to use the index zero special. + This index will never be used because we store the first hash index in + the field used where zero means not used. Every other value means used. + The used field can be used as a first fast comparison for equality of + the stored and the parameter value. This helps to prevent unnecessary + expensive calls of strcmp. */ +unsigned long htab_hash(const char *str) +{ + unsigned long hval, hval2; + unsigned long idx; + unsigned long r = 5381UL; + int c; + const char *sz = str; + + if (str == NULL) + return 0; + + // Compute main hash value (algorithm suggested by Nokia) + while ((c = *sz++) != 0) + r = ((r << 5) + r) + c; + if (r == 0) + ++r; + + // compute table hash: simply take the modulus + hval = r % HTAB_SIZE; + if (hval == 0) + ++hval; + + // Try the first index + idx = hval; + + // Mutually exclusive access (R/W lock would be better) + usbi_mutex_lock(&htab_mutex); + + if (htab_table[idx].used) { + if ((htab_table[idx].used == hval) && (strcmp(str, htab_table[idx].str) == 0)) + goto out_unlock; // existing hash + + usbi_dbg(NULL, "hash collision ('%s' vs '%s')", str, htab_table[idx].str); + + // Second hash function, as suggested in [Knuth] + hval2 = 1UL + hval % (HTAB_SIZE - 2); + + do { + // Because size is prime this guarantees to step through all available indexes + if (idx <= hval2) + idx = HTAB_SIZE + idx - hval2; + else + idx -= hval2; + + // If we visited all entries leave the loop unsuccessfully + if (idx == hval) + break; + + // If entry is found use it. + if ((htab_table[idx].used == hval) && (strcmp(str, htab_table[idx].str) == 0)) + goto out_unlock; + } while (htab_table[idx].used); + } + + // Not found => New entry + + // If the table is full return an error + if (htab_filled >= HTAB_SIZE) { + usbi_err(NULL, "hash table is full (%lu entries)", HTAB_SIZE); + idx = 0UL; + goto out_unlock; + } + + htab_table[idx].str = _strdup(str); + if (htab_table[idx].str == NULL) { + usbi_err(NULL, "could not duplicate string for hash table"); + idx = 0UL; + goto out_unlock; + } + + htab_table[idx].used = hval; + ++htab_filled; + +out_unlock: + usbi_mutex_unlock(&htab_mutex); + + return idx; +} + +enum libusb_transfer_status usbd_status_to_libusb_transfer_status(USBD_STATUS status) +{ + if (USBD_SUCCESS(status)) + return LIBUSB_TRANSFER_COMPLETED; + + switch (status) { + case USBD_STATUS_TIMEOUT: + return LIBUSB_TRANSFER_TIMED_OUT; + case USBD_STATUS_CANCELED: + return LIBUSB_TRANSFER_CANCELLED; + case USBD_STATUS_ENDPOINT_HALTED: + return LIBUSB_TRANSFER_STALL; + case USBD_STATUS_DEVICE_GONE: + return LIBUSB_TRANSFER_NO_DEVICE; + default: + usbi_dbg(NULL, "USBD_STATUS 0x%08lx translated to LIBUSB_TRANSFER_ERROR", ULONG_CAST(status)); + return LIBUSB_TRANSFER_ERROR; + } +} + +/* + * Make a transfer complete synchronously + */ +void windows_force_sync_completion(struct usbi_transfer *itransfer, ULONG size) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct windows_context_priv *priv = usbi_get_context_priv(TRANSFER_CTX(transfer)); + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + OVERLAPPED *overlapped = &transfer_priv->overlapped; + + usbi_dbg(TRANSFER_CTX(transfer), "transfer %p, length %lu", transfer, ULONG_CAST(size)); + + overlapped->Internal = (ULONG_PTR)STATUS_SUCCESS; + overlapped->InternalHigh = (ULONG_PTR)size; + + if (!PostQueuedCompletionStatus(priv->completion_port, (DWORD)size, (ULONG_PTR)transfer->dev_handle, overlapped)) + usbi_err(TRANSFER_CTX(transfer), "failed to post I/O completion: %s", windows_error_str(0)); +} + +/* Windows version detection */ +static BOOL is_x64(void) +{ + BOOL ret = FALSE; + + // Detect if we're running a 32 or 64 bit system + if (sizeof(uintptr_t) < 8) { + IsWow64Process(GetCurrentProcess(), &ret); + } else { + ret = TRUE; + } + + return ret; +} + +static enum windows_version get_windows_version(void) +{ + enum windows_version winver; + OSVERSIONINFOEXA vi, vi2; + unsigned major, minor, version; + ULONGLONG major_equal, minor_equal; + const char *w, *arch; + bool ws; + + 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 WINDOWS_UNDEFINED; + } + + if (vi.dwPlatformId != VER_PLATFORM_WIN32_NT) + 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 + // See: http://msdn.microsoft.com/en-us/library/windows/desktop/dn302074.aspx + + major_equal = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL); + for (major = vi.dwMajorVersion; major <= 9; major++) { + memset(&vi2, 0, sizeof(vi2)); + vi2.dwOSVersionInfoSize = sizeof(vi2); + vi2.dwMajorVersion = major; + if (!VerifyVersionInfoA(&vi2, VER_MAJORVERSION, major_equal)) + continue; + + if (vi.dwMajorVersion < major) { + vi.dwMajorVersion = major; + vi.dwMinorVersion = 0; + } + + minor_equal = VerSetConditionMask(0, VER_MINORVERSION, VER_EQUAL); + for (minor = vi.dwMinorVersion; minor <= 9; minor++) { + memset(&vi2, 0, sizeof(vi2)); + vi2.dwOSVersionInfoSize = sizeof(vi2); + vi2.dwMinorVersion = minor; + if (!VerifyVersionInfoA(&vi2, VER_MINORVERSION, minor_equal)) + continue; + + vi.dwMinorVersion = minor; + break; + } + + break; + } + } + + if ((vi.dwMajorVersion > 0xf) || (vi.dwMinorVersion > 0xf)) + return WINDOWS_UNDEFINED; + + ws = (vi.wProductType <= VER_NT_WORKSTATION); + version = vi.dwMajorVersion << 4 | vi.dwMinorVersion; + switch (version) { + 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: winver = WINDOWS_10; w = (ws ? "10" : "2016"); break; + default: + if (version < 0x50) + return WINDOWS_UNDEFINED; + winver = WINDOWS_11_OR_LATER; + w = "11 or later"; + } + + arch = is_x64() ? "64-bit" : "32-bit"; + + if (vi.wServicePackMinor) + usbi_dbg(NULL, "Windows %s SP%u.%u %s", w, vi.wServicePackMajor, vi.wServicePackMinor, arch); + else if (vi.wServicePackMajor) + usbi_dbg(NULL, "Windows %s SP%u %s", w, vi.wServicePackMajor, arch); + else + usbi_dbg(NULL, "Windows %s %s", w, arch); + + return winver; +} + +static unsigned __stdcall windows_iocp_thread(void *arg) +{ + struct libusb_context *ctx = arg; + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + HANDLE iocp = priv->completion_port; + DWORD num_bytes; + ULONG_PTR completion_key; + OVERLAPPED *overlapped; + struct libusb_device_handle *dev_handle; + struct libusb_device_handle *opened_device_handle; + struct windows_device_handle_priv *handle_priv; + struct windows_transfer_priv *transfer_priv; + struct usbi_transfer *itransfer; + bool found; + + usbi_dbg(ctx, "I/O completion thread started"); + + while (true) { + overlapped = NULL; + if (!GetQueuedCompletionStatus(iocp, &num_bytes, &completion_key, &overlapped, INFINITE) && (overlapped == NULL)) { + usbi_err(ctx, "GetQueuedCompletionStatus failed: %s", windows_error_str(0)); + break; + } + + if (overlapped == NULL) { + // Signal to quit + if (completion_key != (ULONG_PTR)ctx) + usbi_err(ctx, "program assertion failed - overlapped is NULL"); + break; + } + + // Find the transfer associated with the OVERLAPPED that just completed. + // If we cannot find a match, the I/O operation originated from outside of libusb + // (e.g. within libusbK) and we need to ignore it. + dev_handle = (struct libusb_device_handle *)completion_key; + + found = false; + transfer_priv = NULL; + + // Issue 912: lock opened device handles in context to search the current device handle + // to avoid accessing unallocated memory after device has been closed + usbi_mutex_lock(&ctx->open_devs_lock); + for_each_open_device(ctx, opened_device_handle) { + if (dev_handle == opened_device_handle) { + handle_priv = usbi_get_device_handle_priv(dev_handle); + + usbi_mutex_lock(&dev_handle->lock); + list_for_each_entry(transfer_priv, &handle_priv->active_transfers, list, struct windows_transfer_priv) { + if (overlapped == &transfer_priv->overlapped) { + // This OVERLAPPED belongs to us, remove the transfer from the device handle's list + list_del(&transfer_priv->list); + found = true; + break; + } + } + usbi_mutex_unlock(&dev_handle->lock); + } + } + usbi_mutex_unlock(&ctx->open_devs_lock); + + if (!found) { + usbi_dbg(ctx, "ignoring overlapped %p for handle %p (device %u.%u)", + overlapped, dev_handle, dev_handle->dev->bus_number, dev_handle->dev->device_address); + continue; + } + + itransfer = (struct usbi_transfer *)((unsigned char *)transfer_priv + PTR_ALIGN(sizeof(*transfer_priv))); + usbi_dbg(ctx, "transfer %p completed, length %lu", + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(num_bytes)); + usbi_signal_transfer_completion(itransfer); + } + + usbi_dbg(ctx, "I/O completion thread exiting"); + + return 0; +} + +static int windows_init(struct libusb_context *ctx) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + bool winusb_backend_init = false; + int r; + + // NB: concurrent usage supposes that init calls are equally balanced with + // exit calls. If init is called more than exit, we will not exit properly + if (++init_count == 1) { // First init? + 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; + } + + if (!htab_create(ctx)) { + r = LIBUSB_ERROR_NO_MEM; + goto init_exit; + } + + r = winusb_backend.init(ctx); + if (r != LIBUSB_SUCCESS) + goto init_exit; + winusb_backend_init = true; + + r = usbdk_backend.init(ctx); + if (r == LIBUSB_SUCCESS) { + usbi_dbg(ctx, "UsbDk backend is available"); + usbdk_available = true; + } else { + usbi_info(ctx, "UsbDk backend is not available"); + // Do not report this as an error + } + } + + // By default, new contexts will use the WinUSB backend + priv->backend = &winusb_backend; + + r = LIBUSB_ERROR_NO_MEM; + + // Use an I/O completion port to manage all transfers for this context + priv->completion_port = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); + if (priv->completion_port == NULL) { + usbi_err(ctx, "failed to create I/O completion port: %s", windows_error_str(0)); + goto init_exit; + } + + // And a dedicated thread to wait for I/O completions + priv->completion_port_thread = (HANDLE)_beginthreadex(NULL, 0, windows_iocp_thread, ctx, 0, NULL); + if (priv->completion_port_thread == NULL) { + usbi_err(ctx, "failed to create I/O completion port thread"); + CloseHandle(priv->completion_port); + goto init_exit; + } + + r = LIBUSB_SUCCESS; + +init_exit: // Holds semaphore here + if ((init_count == 1) && (r != LIBUSB_SUCCESS)) { // First init failed? + if (usbdk_available) { + usbdk_backend.exit(ctx); + usbdk_available = false; + } + if (winusb_backend_init) + winusb_backend.exit(ctx); + htab_destroy(); + --init_count; + } + + return r; +} + +static void windows_exit(struct libusb_context *ctx) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + + // A NULL completion status will indicate to the thread that it is time to exit + if (!PostQueuedCompletionStatus(priv->completion_port, 0, (ULONG_PTR)ctx, NULL)) + usbi_err(ctx, "failed to post I/O completion: %s", windows_error_str(0)); + + 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) { + usbdk_backend.exit(ctx); + usbdk_available = false; + } + winusb_backend.exit(ctx); + htab_destroy(); + } +} + +static int windows_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + + UNUSED(ap); + + if (option == LIBUSB_OPTION_USE_USBDK) { + if (!usbdk_available) { + usbi_err(ctx, "UsbDk backend not available"); + return LIBUSB_ERROR_NOT_FOUND; + } + usbi_dbg(ctx, "switching context %p to use UsbDk backend", ctx); + priv->backend = &usbdk_backend; + return LIBUSB_SUCCESS; + } + + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int windows_get_device_list(struct libusb_context *ctx, struct discovered_devs **discdevs) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + return priv->backend->get_device_list(ctx, discdevs); +} + +static int windows_open(struct libusb_device_handle *dev_handle) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle); + + list_init(&handle_priv->active_transfers); + return priv->backend->open(dev_handle); +} + +static void windows_close(struct libusb_device_handle *dev_handle) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + priv->backend->close(dev_handle); +} + +static int windows_get_active_config_descriptor(struct libusb_device *dev, + void *buffer, size_t len) +{ + struct windows_context_priv *priv = usbi_get_context_priv(DEVICE_CTX(dev)); + return priv->backend->get_active_config_descriptor(dev, buffer, len); +} + +static int windows_get_config_descriptor(struct libusb_device *dev, + uint8_t config_index, void *buffer, size_t len) +{ + struct windows_context_priv *priv = usbi_get_context_priv(DEVICE_CTX(dev)); + return priv->backend->get_config_descriptor(dev, config_index, buffer, len); +} + +static int windows_get_config_descriptor_by_value(struct libusb_device *dev, + uint8_t bConfigurationValue, void **buffer) +{ + struct windows_context_priv *priv = usbi_get_context_priv(DEVICE_CTX(dev)); + return priv->backend->get_config_descriptor_by_value(dev, bConfigurationValue, buffer); +} + +static int windows_get_configuration(struct libusb_device_handle *dev_handle, uint8_t *config) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->get_configuration(dev_handle, config); +} + +static int windows_set_configuration(struct libusb_device_handle *dev_handle, int config) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + if (config == -1) + config = 0; + return priv->backend->set_configuration(dev_handle, (uint8_t)config); +} + +static int windows_claim_interface(struct libusb_device_handle *dev_handle, uint8_t interface_number) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->claim_interface(dev_handle, interface_number); +} + +static int windows_release_interface(struct libusb_device_handle *dev_handle, uint8_t interface_number) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->release_interface(dev_handle, interface_number); +} + +static int windows_set_interface_altsetting(struct libusb_device_handle *dev_handle, + uint8_t interface_number, uint8_t altsetting) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->set_interface_altsetting(dev_handle, interface_number, altsetting); +} + +static int windows_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->clear_halt(dev_handle, endpoint); +} + +static int windows_reset_device(struct libusb_device_handle *dev_handle) +{ + struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); + return priv->backend->reset_device(dev_handle); +} + +static void windows_destroy_device(struct libusb_device *dev) +{ + struct windows_context_priv *priv = usbi_get_context_priv(DEVICE_CTX(dev)); + priv->backend->destroy_device(dev); +} + +static int windows_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct libusb_device_handle *dev_handle = transfer->dev_handle; + struct libusb_context *ctx = HANDLE_CTX(dev_handle); + struct windows_context_priv *priv = usbi_get_context_priv(ctx); + struct windows_device_handle_priv *handle_priv = usbi_get_device_handle_priv(dev_handle); + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer); + int r; + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + break; + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + usbi_warn(ctx, "bulk stream transfers are not yet supported on this platform"); + return LIBUSB_ERROR_NOT_SUPPORTED; + default: + usbi_err(ctx, "unknown endpoint type %d", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } + + if (transfer_priv->handle != NULL) { + usbi_err(ctx, "program assertion failed - transfer HANDLE is not NULL"); + transfer_priv->handle = NULL; + } + + // Add transfer to the device handle's list + usbi_mutex_lock(&dev_handle->lock); + list_add_tail(&transfer_priv->list, &handle_priv->active_transfers); + usbi_mutex_unlock(&dev_handle->lock); + + r = priv->backend->submit_transfer(itransfer); + if (r != LIBUSB_SUCCESS) { + // Remove the unsuccessful transfer from the device handle's list + usbi_mutex_lock(&dev_handle->lock); + list_del(&transfer_priv->list); + usbi_mutex_unlock(&dev_handle->lock); + + // Always call the backend's clear_transfer_priv() function on failure + priv->backend->clear_transfer_priv(itransfer); + transfer_priv->handle = NULL; + return r; + } + + // The backend should set the HANDLE used for each submitted transfer + // by calling set_transfer_priv_handle() + if (transfer_priv->handle == NULL) + usbi_err(ctx, "program assertion failed - transfer HANDLE is NULL after transfer was submitted"); + + return r; +} + +static int windows_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct windows_context_priv *priv = usbi_get_context_priv(ITRANSFER_CTX(itransfer)); + struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(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->overlapped)) + return LIBUSB_SUCCESS; + else if (GetLastError() == ERROR_NOT_FOUND) + return LIBUSB_ERROR_NOT_FOUND; + + if (priv->backend->cancel_transfer) + return priv->backend->cancel_transfer(itransfer); + + usbi_warn(ITRANSFER_CTX(itransfer), "cancellation not supported for this transfer's driver"); + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +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); + 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; + + if (GetOverlappedResult(transfer_priv->handle, &transfer_priv->overlapped, &bytes_transferred, FALSE)) + result = NO_ERROR; + else + result = GetLastError(); + + usbi_dbg(ctx, "handling transfer %p completion with errcode %lu, length %lu", + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer), ULONG_CAST(result), ULONG_CAST(bytes_transferred)); + + switch (result) { + case NO_ERROR: + status = backend->copy_transfer_data(itransfer, bytes_transferred); + break; + case ERROR_GEN_FAILURE: + usbi_dbg(ctx, "detected endpoint stall"); + status = LIBUSB_TRANSFER_STALL; + break; + case ERROR_SEM_TIMEOUT: + usbi_dbg(ctx, "detected semaphore timeout"); + status = LIBUSB_TRANSFER_TIMED_OUT; + break; + case ERROR_OPERATION_ABORTED: + istatus = backend->copy_transfer_data(itransfer, bytes_transferred); + if (istatus != LIBUSB_TRANSFER_COMPLETED) + usbi_dbg(ctx, "failed to copy partial data in aborted operation: %d", (int)istatus); + + usbi_dbg(ctx, "detected operation aborted"); + status = LIBUSB_TRANSFER_CANCELLED; + break; + case ERROR_FILE_NOT_FOUND: + case ERROR_DEVICE_NOT_CONNECTED: + case ERROR_NO_SUCH_DEVICE: + usbi_dbg(ctx, "detected device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + default: + usbi_err(ctx, "detected I/O error %lu: %s", + ULONG_CAST(result), windows_error_str(result)); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + 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); +} + +void usbi_get_monotonic_time(struct timespec *tp) +{ + static LONG hires_counter_init; + static uint64_t hires_ticks_to_ps; + static uint64_t hires_frequency; + LARGE_INTEGER hires_counter; + + if (InterlockedExchange(&hires_counter_init, 1L) == 0L) { + LARGE_INTEGER li_frequency; + + // Microsoft says that the QueryPerformanceFrequency() and + // QueryPerformanceCounter() functions always succeed on XP and later + QueryPerformanceFrequency(&li_frequency); + + // The hires frequency can go as high as 4 GHz, so we'll use a conversion + // to picoseconds to compute the tv_nsecs part + hires_frequency = li_frequency.QuadPart; + hires_ticks_to_ps = UINT64_C(1000000000000) / hires_frequency; + } + + QueryPerformanceCounter(&hires_counter); + tp->tv_sec = (long)(hires_counter.QuadPart / hires_frequency); + tp->tv_nsec = (long)(((hires_counter.QuadPart % hires_frequency) * hires_ticks_to_ps) / UINT64_C(1000)); +} + +// NB: MSVC6 does not support named initializers. +const struct usbi_os_backend usbi_backend = { + "Windows", + USBI_CAP_HAS_HID_ACCESS, + windows_init, + windows_exit, + windows_set_option, + windows_get_device_list, + NULL, /* hotplug_poll */ + NULL, /* wrap_sys_device */ + windows_open, + windows_close, + windows_get_active_config_descriptor, + windows_get_config_descriptor, + windows_get_config_descriptor_by_value, + windows_get_configuration, + windows_set_configuration, + windows_claim_interface, + windows_release_interface, + windows_set_interface_altsetting, + windows_clear_halt, + windows_reset_device, + NULL, /* alloc_streams */ + NULL, /* free_streams */ + NULL, /* dev_mem_alloc */ + NULL, /* dev_mem_free */ + NULL, /* kernel_driver_active */ + NULL, /* detach_kernel_driver */ + NULL, /* attach_kernel_driver */ + windows_destroy_device, + windows_submit_transfer, + windows_cancel_transfer, + NULL, /* clear_transfer_priv */ + NULL, /* handle_events */ + windows_handle_transfer_completion, + sizeof(struct windows_context_priv), + sizeof(union windows_device_priv), + sizeof(struct windows_device_handle_priv), + sizeof(struct windows_transfer_priv), +}; -- cgit v1.2.3