summaryrefslogtreecommitdiff
path: root/libusb/os/windows_nt_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'libusb/os/windows_nt_common.c')
-rw-r--r--libusb/os/windows_nt_common.c616
1 files changed, 616 insertions, 0 deletions
diff --git a/libusb/os/windows_nt_common.c b/libusb/os/windows_nt_common.c
new file mode 100644
index 0000000..d99e3b8
--- /dev/null
+++ b/libusb/os/windows_nt_common.c
@@ -0,0 +1,616 @@
+/*
+ * windows backend for libusb 1.0
+ * Copyright © 2009-2012 Pete Batard <pete@akeo.ie>
+ * 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 <config.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <process.h>
+
+
+#include "libusbi.h"
+#include "windows_common.h"
+#include "windows_nt_common.h"
+
+// Global variables
+const uint64_t epoch_time = UINT64_C(116444736000000000); // 1970.01.01 00:00:000 in MS Filetime
+
+// Global variables for clock_gettime mechanism
+uint64_t hires_ticks_to_ps;
+uint64_t hires_frequency;
+
+#define WM_TIMER_REQUEST (WM_USER + 1)
+#define WM_TIMER_EXIT (WM_USER + 2)
+
+// used for monotonic clock_gettime()
+struct timer_request {
+ struct timespec *tp;
+ HANDLE event;
+};
+
+// Timer thread
+HANDLE timer_thread = NULL;
+DWORD timer_thread_id = 0;
+
+/* User32 dependencies */
+DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, GetMessageA, (LPMSG, HWND, UINT, UINT));
+DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, PeekMessageA, (LPMSG, HWND, UINT, UINT, UINT));
+DLL_DECLARE_PREFIXED(WINAPI, BOOL, p, PostThreadMessageA, (DWORD, UINT, WPARAM, LPARAM));
+
+static unsigned __stdcall windows_clock_gettime_threaded(void* param);
+
+/*
+* Converts a windows error to human readable string
+* uses retval as errorcode, or, if 0, use GetLastError()
+*/
+#if defined(ENABLE_LOGGING)
+char *windows_error_str(uint32_t retval)
+{
+ static char err_string[ERR_BUFFER_SIZE];
+
+ DWORD size;
+ ssize_t i;
+ uint32_t error_code, format_error;
+
+ error_code = retval ? retval : GetLastError();
+
+ safe_sprintf(err_string, ERR_BUFFER_SIZE, "[%u] ", 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, NULL, error_code,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &err_string[safe_strlen(err_string)],
+ ERR_BUFFER_SIZE - (DWORD)safe_strlen(err_string), NULL);
+ if (size == 0) {
+ format_error = GetLastError();
+ if (format_error)
+ safe_sprintf(err_string, ERR_BUFFER_SIZE,
+ "Windows error code %u (FormatMessage error code %u)", error_code, format_error);
+ else
+ safe_sprintf(err_string, ERR_BUFFER_SIZE, "Unknown error code %u", error_code);
+ }
+ else {
+ // Remove CR/LF terminators
+ for (i = safe_strlen(err_string) - 1; (i >= 0) && ((err_string[i] == 0x0A) || (err_string[i] == 0x0D)); i--) {
+ err_string[i] = 0;
+ }
+ }
+ return err_string;
+}
+#endif
+
+/* 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) */
+typedef struct htab_entry {
+ unsigned long used;
+ char* str;
+} htab_entry;
+htab_entry* htab_table = NULL;
+usbi_mutex_t htab_write_mutex = NULL;
+unsigned long htab_size, htab_filled;
+
+/* For the used double hash method the table size has to be a prime. To
+ correct the user given table size we need a prime test. This trivial
+ algorithm is adequate because the code is called only during init and
+ the number is likely to be small */
+static int isprime(unsigned long number)
+{
+ // no even number will be passed
+ unsigned int divider = 3;
+
+ while((divider * divider < number) && (number % divider != 0))
+ divider += 2;
+
+ return (number % divider != 0);
+}
+
+/* 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. */
+int htab_create(struct libusb_context *ctx, unsigned long nel)
+{
+ if (htab_table != NULL) {
+ usbi_err(ctx, "hash table already allocated");
+ }
+
+ // Create a mutex
+ usbi_mutex_init(&htab_write_mutex, NULL);
+
+ // Change nel to the first prime number not smaller as nel.
+ nel |= 1;
+ while(!isprime(nel))
+ nel += 2;
+
+ htab_size = nel;
+ usbi_dbg("using %lu entries hash table", nel);
+ htab_filled = 0;
+
+ // allocate memory and zero out.
+ htab_table = (htab_entry*) calloc(htab_size + 1, sizeof(htab_entry));
+ if (htab_table == NULL) {
+ usbi_err(ctx, "could not allocate space for hash table");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* After using the hash table it has to be destroyed. */
+void htab_destroy(void)
+{
+ size_t i;
+ if (htab_table == NULL) {
+ return;
+ }
+
+ for (i=0; i<htab_size; i++) {
+ if (htab_table[i].used) {
+ safe_free(htab_table[i].str);
+ }
+ }
+ usbi_mutex_destroy(&htab_write_mutex);
+ safe_free(htab_table);
+}
+
+/* This is the search function. It uses double hashing with open addressing.
+ We use an 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(char* str)
+{
+ unsigned long hval, hval2;
+ unsigned long idx;
+ unsigned long r = 5381;
+ int c;
+ 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;
+
+ if (htab_table[idx].used) {
+ if ( (htab_table[idx].used == hval)
+ && (safe_strcmp(str, htab_table[idx].str) == 0) ) {
+ // existing hash
+ return idx;
+ }
+ usbi_dbg("hash collision ('%s' vs '%s')", str, htab_table[idx].str);
+
+ // Second hash function, as suggested in [Knuth]
+ hval2 = 1 + 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)
+ && (safe_strcmp(str, htab_table[idx].str) == 0) ) {
+ return idx;
+ }
+ }
+ 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 (%d entries)", htab_size);
+ return 0;
+ }
+
+ // Concurrent threads might be storing the same entry at the same time
+ // (eg. "simultaneous" enums from different threads) => use a mutex
+ usbi_mutex_lock(&htab_write_mutex);
+ // Just free any previously allocated string (which should be the same as
+ // new one). The possibility of concurrent threads storing a collision
+ // string (same hash, different string) at the same time is extremely low
+ safe_free(htab_table[idx].str);
+ htab_table[idx].used = hval;
+ htab_table[idx].str = (char*) malloc(safe_strlen(str)+1);
+ if (htab_table[idx].str == NULL) {
+ usbi_err(NULL, "could not duplicate string for hash table");
+ usbi_mutex_unlock(&htab_write_mutex);
+ return 0;
+ }
+ memcpy(htab_table[idx].str, str, safe_strlen(str)+1);
+ ++htab_filled;
+ usbi_mutex_unlock(&htab_write_mutex);
+
+ return idx;
+}
+
+static int windows_init_dlls(void)
+{
+ DLL_LOAD_PREFIXED(User32.dll, p, GetMessageA, TRUE);
+ DLL_LOAD_PREFIXED(User32.dll, p, PeekMessageA, TRUE);
+ DLL_LOAD_PREFIXED(User32.dll, p, PostThreadMessageA, TRUE);
+ return LIBUSB_SUCCESS;
+}
+
+bool windows_init_clock(struct libusb_context *ctx)
+{
+ DWORD_PTR affinity, dummy;
+ HANDLE event = NULL;
+ LARGE_INTEGER li_frequency;
+ int i;
+
+ if (QueryPerformanceFrequency(&li_frequency)) {
+ // Load DLL imports
+ if (windows_init_dlls() != LIBUSB_SUCCESS) {
+ usbi_err(ctx, "could not resolve DLL functions");
+ return false;
+ }
+
+ // The hires frequency can go as high as 4 GHz, so we'll use a conversion
+ // to picoseconds to compute the tv_nsecs part in clock_gettime
+ hires_frequency = li_frequency.QuadPart;
+ hires_ticks_to_ps = UINT64_C(1000000000000) / hires_frequency;
+ usbi_dbg("hires timer available (Frequency: %"PRIu64" Hz)", hires_frequency);
+
+ // Because QueryPerformanceCounter might report different values when
+ // running on different cores, we create a separate thread for the timer
+ // calls, which we glue to the first available core always to prevent timing discrepancies.
+ if (!GetProcessAffinityMask(GetCurrentProcess(), &affinity, &dummy) || (affinity == 0)) {
+ usbi_err(ctx, "could not get process affinity: %s", windows_error_str(0));
+ return false;
+ }
+ // The process affinity mask is a bitmask where each set bit represents a core on
+ // which this process is allowed to run, so we find the first set bit
+ for (i = 0; !(affinity & (DWORD_PTR)(1 << i)); i++);
+ affinity = (DWORD_PTR)(1 << i);
+
+ usbi_dbg("timer thread will run on core #%d", i);
+
+ event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (event == NULL) {
+ usbi_err(ctx, "could not create event: %s", windows_error_str(0));
+ return false;
+ }
+ timer_thread = (HANDLE)_beginthreadex(NULL, 0, windows_clock_gettime_threaded, (void *)event,
+ 0, (unsigned int *)&timer_thread_id);
+ if (timer_thread == NULL) {
+ usbi_err(ctx, "unable to create timer thread - aborting");
+ CloseHandle(event);
+ return false;
+ }
+ if (!SetThreadAffinityMask(timer_thread, affinity)) {
+ usbi_warn(ctx, "unable to set timer thread affinity, timer discrepancies may arise");
+ }
+
+ // Wait for timer thread to init before continuing.
+ if (WaitForSingleObject(event, INFINITE) != WAIT_OBJECT_0) {
+ usbi_err(ctx, "failed to wait for timer thread to become ready - aborting");
+ CloseHandle(event);
+ return false;
+ }
+
+ CloseHandle(event);
+ } else {
+ usbi_dbg("no hires timer available on this platform");
+ hires_frequency = 0;
+ hires_ticks_to_ps = UINT64_C(0);
+ }
+
+ return true;
+}
+
+void windows_destroy_clock(void)
+{
+ if (timer_thread) {
+ // actually the signal to quit the thread.
+ if (!pPostThreadMessageA(timer_thread_id, WM_TIMER_EXIT, 0, 0) ||
+ (WaitForSingleObject(timer_thread, INFINITE) != WAIT_OBJECT_0)) {
+ usbi_dbg("could not wait for timer thread to quit");
+ TerminateThread(timer_thread, 1);
+ // shouldn't happen, but we're destroying
+ // all objects it might have held anyway.
+ }
+ CloseHandle(timer_thread);
+ timer_thread = NULL;
+ timer_thread_id = 0;
+ }
+}
+
+/*
+* Monotonic and real time functions
+*/
+static unsigned __stdcall windows_clock_gettime_threaded(void* param)
+{
+ struct timer_request *request;
+ LARGE_INTEGER hires_counter;
+ MSG msg;
+
+ // The following call will create this thread's message queue
+ // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms644946.aspx
+ pPeekMessageA(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
+
+ // Signal windows_init() that we're ready to service requests
+ if (!SetEvent((HANDLE)param)) {
+ usbi_dbg("SetEvent failed for timer init event: %s", windows_error_str(0));
+ }
+ param = NULL;
+
+ // Main loop - wait for requests
+ while (1) {
+
+ if (pGetMessageA(&msg, NULL, WM_TIMER_REQUEST, WM_TIMER_EXIT) == -1) {
+ usbi_err(NULL, "GetMessage failed for timer thread: %s", windows_error_str(0));
+ return 1;
+ }
+
+ switch (msg.message) {
+ case WM_TIMER_REQUEST:
+ // Requests to this thread are for hires always
+ // Microsoft says that this function always succeeds on XP and later
+ // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms644904.aspx
+ request = (struct timer_request *)msg.lParam;
+ QueryPerformanceCounter(&hires_counter);
+ request->tp->tv_sec = (long)(hires_counter.QuadPart / hires_frequency);
+ request->tp->tv_nsec = (long)(((hires_counter.QuadPart % hires_frequency) / 1000) * hires_ticks_to_ps);
+ if (!SetEvent(request->event)) {
+ usbi_err(NULL, "SetEvent failed for timer request: %s", windows_error_str(0));
+ }
+ break;
+
+ case WM_TIMER_EXIT:
+ usbi_dbg("timer thread quitting");
+ return 0;
+ }
+
+ }
+}
+
+int windows_clock_gettime(int clk_id, struct timespec *tp)
+{
+ struct timer_request request;
+ FILETIME filetime;
+ ULARGE_INTEGER rtime;
+ DWORD r;
+ switch (clk_id) {
+ case USBI_CLOCK_MONOTONIC:
+ if (timer_thread) {
+ request.tp = tp;
+ request.event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (request.event == NULL) {
+ return LIBUSB_ERROR_NO_MEM;
+ }
+
+ if (!pPostThreadMessageA(timer_thread_id, WM_TIMER_REQUEST, 0, (LPARAM)&request)) {
+ usbi_err(NULL, "PostThreadMessage failed for timer thread: %s", windows_error_str(0));
+ CloseHandle(request.event);
+ return LIBUSB_ERROR_OTHER;
+ }
+
+ do {
+ r = WaitForSingleObject(request.event, TIMER_REQUEST_RETRY_MS);
+ if (r == WAIT_TIMEOUT) {
+ usbi_dbg("could not obtain a timer value within reasonable timeframe - too much load?");
+ }
+ else if (r == WAIT_FAILED) {
+ usbi_err(NULL, "WaitForSingleObject failed: %s", windows_error_str(0));
+ }
+ } while (r == WAIT_TIMEOUT);
+ CloseHandle(request.event);
+
+ if (r == WAIT_OBJECT_0) {
+ return LIBUSB_SUCCESS;
+ } else {
+ return LIBUSB_ERROR_OTHER;
+ }
+ }
+ // Fall through and return real-time if monotonic was not detected @ timer init
+ case USBI_CLOCK_REALTIME:
+ // We follow http://msdn.microsoft.com/en-us/library/ms724928%28VS.85%29.aspx
+ // with a predef epoch_time to have an epoch that starts at 1970.01.01 00:00
+ // Note however that our resolution is bounded by the Windows system time
+ // functions and is at best of the order of 1 ms (or, usually, worse)
+ GetSystemTimeAsFileTime(&filetime);
+ rtime.LowPart = filetime.dwLowDateTime;
+ rtime.HighPart = filetime.dwHighDateTime;
+ rtime.QuadPart -= epoch_time;
+ tp->tv_sec = (long)(rtime.QuadPart / 10000000);
+ tp->tv_nsec = (long)((rtime.QuadPart % 10000000) * 100);
+ return LIBUSB_SUCCESS;
+ default:
+ return LIBUSB_ERROR_INVALID_PARAM;
+ }
+}
+
+static void windows_transfer_callback(struct usbi_transfer *itransfer, uint32_t io_result, uint32_t io_size)
+{
+ int status, istatus;
+
+ usbi_dbg("handling I/O completion with errcode %u, size %u", io_result, io_size);
+
+ switch (io_result) {
+ case NO_ERROR:
+ status = windows_copy_transfer_data(itransfer, io_size);
+ 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 = windows_copy_transfer_data(itransfer, io_size);
+ if (istatus != LIBUSB_TRANSFER_COMPLETED) {
+ usbi_dbg("Failed to copy partial data in aborted operation: %d", istatus);
+ }
+ if (itransfer->flags & USBI_TRANSFER_TIMED_OUT) {
+ usbi_dbg("detected timeout");
+ status = LIBUSB_TRANSFER_TIMED_OUT;
+ }
+ else {
+ usbi_dbg("detected operation aborted");
+ status = LIBUSB_TRANSFER_CANCELLED;
+ }
+ break;
+ default:
+ usbi_err(ITRANSFER_CTX(itransfer), "detected I/O error %u: %s", io_result, windows_error_str(io_result));
+ status = LIBUSB_TRANSFER_ERROR;
+ break;
+ }
+ windows_clear_transfer_priv(itransfer); // Cancel polling
+ usbi_handle_transfer_completion(itransfer, (enum libusb_transfer_status)status);
+}
+
+void windows_handle_callback(struct usbi_transfer *itransfer, uint32_t io_result, uint32_t io_size)
+{
+ struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+
+ switch (transfer->type) {
+ case LIBUSB_TRANSFER_TYPE_CONTROL:
+ case LIBUSB_TRANSFER_TYPE_BULK:
+ case LIBUSB_TRANSFER_TYPE_INTERRUPT:
+ case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
+ windows_transfer_callback(itransfer, io_result, io_size);
+ break;
+ case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
+ usbi_warn(ITRANSFER_CTX(itransfer), "bulk stream transfers are not yet supported on this platform");
+ break;
+ default:
+ usbi_err(ITRANSFER_CTX(itransfer), "unknown endpoint type %d", transfer->type);
+ }
+}
+
+int windows_handle_events(struct libusb_context *ctx, struct pollfd *fds, POLL_NFDS_TYPE nfds, int num_ready)
+{
+ POLL_NFDS_TYPE i = 0;
+ bool found = false;
+ struct usbi_transfer *transfer;
+ struct winfd *pollable_fd = NULL;
+ DWORD io_size, io_result;
+
+ 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--;
+
+ // Because a Windows OVERLAPPED is used for poll emulation,
+ // a pollable fd is created and stored with each transfer
+ usbi_mutex_lock(&ctx->flying_transfers_lock);
+ found = false;
+ list_for_each_entry(transfer, &ctx->flying_transfers, list, struct usbi_transfer) {
+ pollable_fd = windows_get_fd(transfer);
+ if (pollable_fd->fd == fds[i].fd) {
+ found = true;
+ break;
+ }
+ }
+ usbi_mutex_unlock(&ctx->flying_transfers_lock);
+
+ if (found) {
+ windows_get_overlapped_result(transfer, pollable_fd, &io_result, &io_size);
+
+ usbi_remove_pollfd(ctx, pollable_fd->fd);
+ // let handle_callback free the event using the transfer wfd
+ // If you don't use the transfer wfd, you run a risk of trying to free a
+ // newly allocated wfd that took the place of the one from the transfer.
+ windows_handle_callback(transfer, io_result, io_size);
+ } else {
+ usbi_mutex_unlock(&ctx->open_devs_lock);
+ usbi_err(ctx, "could not find a matching transfer for fd %d", fds[i]);
+ return LIBUSB_ERROR_NOT_FOUND;
+ }
+ }
+
+ usbi_mutex_unlock(&ctx->open_devs_lock);
+ return LIBUSB_SUCCESS;
+}
+
+int windows_common_init(struct libusb_context *ctx)
+{
+ static const unsigned long HTAB_SIZE = 1021;
+
+ if (!windows_init_clock(ctx)){
+ goto error_roll_back;
+ }
+
+ if (!htab_create(ctx, HTAB_SIZE)) {
+ goto error_roll_back;
+ }
+
+ return LIBUSB_SUCCESS;
+
+error_roll_back:
+ windows_common_exit();
+
+ return LIBUSB_ERROR_NO_MEM;
+}
+
+void windows_common_exit(void)
+{
+ htab_destroy();
+ windows_destroy_clock();
+}