summaryrefslogtreecommitdiff
path: root/libusb/os/windows_common.c
diff options
context:
space:
mode:
Diffstat (limited to 'libusb/os/windows_common.c')
-rw-r--r--libusb/os/windows_common.c268
1 files changed, 174 insertions, 94 deletions
diff --git a/libusb/os/windows_common.c b/libusb/os/windows_common.c
index a2c9beb..3a11c56 100644
--- a/libusb/os/windows_common.c
+++ b/libusb/os/windows_common.c
@@ -33,12 +33,14 @@
#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 = 0;
-static bool usbdk_available = false;
+static unsigned int init_count;
+static bool usbdk_available;
// Global variables for clock_gettime mechanism
static uint64_t hires_ticks_to_ps;
@@ -114,7 +116,7 @@ typedef struct htab_entry {
char *str;
} htab_entry;
-static htab_entry *htab_table = NULL;
+static htab_entry *htab_table;
static usbi_mutex_t htab_mutex;
static unsigned long htab_filled;
@@ -125,7 +127,7 @@ static unsigned long htab_filled;
static bool htab_create(struct libusb_context *ctx)
{
if (htab_table != NULL) {
- usbi_err(ctx, "hash table already allocated");
+ usbi_err(ctx, "program assertion falied - hash table already allocated");
return true;
}
@@ -173,7 +175,7 @@ unsigned long htab_hash(const char *str)
{
unsigned long hval, hval2;
unsigned long idx;
- unsigned long r = 5381;
+ unsigned long r = 5381UL;
int c;
const char *sz = str;
@@ -204,7 +206,7 @@ unsigned long htab_hash(const char *str)
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);
+ hval2 = 1UL + hval % (HTAB_SIZE - 2);
do {
// Because size is prime this guarantees to step through all available indexes
@@ -228,14 +230,14 @@ unsigned long htab_hash(const char *str)
// 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 = 0;
+ 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 = 0;
+ idx = 0UL;
goto out_unlock;
}
@@ -248,13 +250,33 @@ out_unlock:
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("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(OVERLAPPED *overlapped, ULONG size)
{
- overlapped->Internal = STATUS_COMPLETED_SYNCHRONOUSLY;
- overlapped->InternalHigh = size;
+ overlapped->Internal = (ULONG_PTR)STATUS_SUCCESS;
+ overlapped->InternalHigh = (ULONG_PTR)size;
SetEvent(overlapped->hEvent);
}
@@ -294,7 +316,7 @@ static void get_windows_version(void)
const char *arch, *w = NULL;
unsigned major, minor, version;
ULONGLONG major_equal, minor_equal;
- BOOL ws;
+ bool ws;
windows_version = WINDOWS_UNDEFINED;
@@ -378,16 +400,17 @@ static void get_windows_version(void)
}
static void windows_transfer_callback(const struct windows_backend *backend,
- struct usbi_transfer *itransfer, DWORD io_result, DWORD io_size)
+ struct usbi_transfer *itransfer, DWORD error, DWORD bytes_transferred)
{
- int status, istatus;
+ struct windows_transfer_priv *transfer_priv = get_transfer_priv(itransfer);
+ enum libusb_transfer_status status, istatus;
- usbi_dbg("handling I/O completion with errcode %lu, size %lu",
- ULONG_CAST(io_result), ULONG_CAST(io_size));
+ usbi_dbg("handling I/O completion with errcode %lu, length %lu",
+ ULONG_CAST(error), ULONG_CAST(bytes_transferred));
- switch (io_result) {
+ switch (error) {
case NO_ERROR:
- status = backend->copy_transfer_data(itransfer, (uint32_t)io_size);
+ status = backend->copy_transfer_data(itransfer, bytes_transferred);
break;
case ERROR_GEN_FAILURE:
usbi_dbg("detected endpoint stall");
@@ -398,9 +421,9 @@ static void windows_transfer_callback(const struct windows_backend *backend,
status = LIBUSB_TRANSFER_TIMED_OUT;
break;
case ERROR_OPERATION_ABORTED:
- istatus = backend->copy_transfer_data(itransfer, (uint32_t)io_size);
+ istatus = backend->copy_transfer_data(itransfer, bytes_transferred);
if (istatus != LIBUSB_TRANSFER_COMPLETED)
- usbi_dbg("Failed to copy partial data in aborted operation: %d", istatus);
+ usbi_dbg("failed to copy partial data in aborted operation: %d", (int)istatus);
usbi_dbg("detected operation aborted");
status = LIBUSB_TRANSFER_CANCELLED;
@@ -412,57 +435,45 @@ static void windows_transfer_callback(const struct windows_backend *backend,
break;
default:
usbi_err(ITRANSFER_CTX(itransfer), "detected I/O error %lu: %s",
- ULONG_CAST(io_result), windows_error_str(io_result));
+ ULONG_CAST(error), windows_error_str(error));
status = LIBUSB_TRANSFER_ERROR;
break;
}
- backend->clear_transfer_priv(itransfer); // Cancel polling
+
+ // Cancel polling
+ usbi_close(transfer_priv->pollable_fd.fd);
+ transfer_priv->pollable_fd = INVALID_WINFD;
+ transfer_priv->handle = NULL;
+
+ // Backend-specific cleanup
+ backend->clear_transfer_priv(itransfer);
+
if (status == LIBUSB_TRANSFER_CANCELLED)
usbi_handle_transfer_cancellation(itransfer);
else
- usbi_handle_transfer_completion(itransfer, (enum libusb_transfer_status)status);
-}
-
-static void windows_handle_callback(const struct windows_backend *backend,
- struct usbi_transfer *itransfer, DWORD io_result, DWORD 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(backend, 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);
- }
+ usbi_handle_transfer_completion(itransfer, status);
}
static int windows_init(struct libusb_context *ctx)
{
struct windows_context_priv *priv = _context_priv(ctx);
- HANDLE semaphore;
- char sem_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
+ 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;
- sprintf(sem_name, "libusb_init%08X", (unsigned int)(GetCurrentProcessId() & 0xFFFFFFFF));
- semaphore = CreateSemaphoreA(NULL, 1, 1, sem_name);
- if (semaphore == NULL) {
- usbi_err(ctx, "could not create semaphore: %s", windows_error_str(0));
+ sprintf(mutex_name, "libusb_init%08X", (unsigned int)(GetCurrentProcessId() & 0xFFFFFFFFU));
+ mutex = CreateMutexA(NULL, FALSE, mutex_name);
+ if (mutex == NULL) {
+ usbi_err(ctx, "could not create mutex: %s", windows_error_str(0));
return LIBUSB_ERROR_NO_MEM;
}
- // A successful wait brings our semaphore count to 0 (unsignaled)
- // => any concurent wait stalls until the semaphore's release
- if (WaitForSingleObject(semaphore, INFINITE) != WAIT_OBJECT_0) {
- usbi_err(ctx, "failure to access semaphore: %s", windows_error_str(0));
- CloseHandle(semaphore);
+ // A successful wait gives this thread ownership of the mutex
+ // => any concurent wait stalls until the mutex is released
+ if (WaitForSingleObject(mutex, INFINITE) != WAIT_OBJECT_0) {
+ usbi_err(ctx, "failure to access mutex: %s", windows_error_str(0));
+ CloseHandle(mutex);
return LIBUSB_ERROR_NO_MEM;
}
@@ -511,26 +522,26 @@ init_exit: // Holds semaphore here
--init_count;
}
- ReleaseSemaphore(semaphore, 1, NULL); // increase count back to 1
- CloseHandle(semaphore);
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
return r;
}
static void windows_exit(struct libusb_context *ctx)
{
- HANDLE semaphore;
- char sem_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
- UNUSED(ctx);
+ char mutex_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
+ HANDLE mutex;
- sprintf(sem_name, "libusb_init%08lX", (GetCurrentProcessId() & 0xFFFFFFFFUL));
- semaphore = CreateSemaphoreA(NULL, 1, 1, sem_name);
- if (semaphore == NULL)
+ sprintf(mutex_name, "libusb_init%08lX", (GetCurrentProcessId() & 0xFFFFFFFFU));
+ mutex = CreateMutexA(NULL, FALSE, mutex_name);
+ if (mutex == NULL)
return;
- // A successful wait brings our semaphore count to 0 (unsignaled)
- // => any concurent wait stalls until the semaphore release
- if (WaitForSingleObject(semaphore, INFINITE) != WAIT_OBJECT_0) {
- CloseHandle(semaphore);
+ // A successful wait gives this thread ownership of the mutex
+ // => any concurent wait stalls until the mutex is released
+ if (WaitForSingleObject(mutex, INFINITE) != WAIT_OBJECT_0) {
+ usbi_err(ctx, "failed to access mutex: %s", windows_error_str(0));
+ CloseHandle(mutex);
return;
}
@@ -544,8 +555,8 @@ static void windows_exit(struct libusb_context *ctx)
htab_destroy();
}
- ReleaseSemaphore(semaphore, 1, NULL); // increase count back to 1
- CloseHandle(semaphore);
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
}
static int windows_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap)
@@ -567,7 +578,6 @@ static int windows_set_option(struct libusb_context *ctx, enum libusb_option opt
default:
return LIBUSB_ERROR_NOT_SUPPORTED;
}
-
}
static int windows_get_device_list(struct libusb_context *ctx, struct discovered_devs **discdevs)
@@ -671,29 +681,103 @@ static void windows_destroy_device(struct libusb_device *dev)
static int windows_submit_transfer(struct usbi_transfer *itransfer)
{
- struct windows_context_priv *priv = _context_priv(ITRANSFER_CTX(itransfer));
- return priv->backend->submit_transfer(itransfer);
+ struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
+ struct libusb_context *ctx = TRANSFER_CTX(transfer);
+ struct windows_context_priv *priv = _context_priv(ctx);
+ struct windows_transfer_priv *transfer_priv = 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");
+ return LIBUSB_ERROR_NOT_SUPPORTED;
+ default:
+ usbi_err(ctx, "unknown endpoint type %d", transfer->type);
+ 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;
+ }
+
+ r = priv->backend->submit_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;
+ }
+
+ // 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");
+
+ // 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;
}
static int windows_cancel_transfer(struct usbi_transfer *itransfer)
{
struct windows_context_priv *priv = _context_priv(ITRANSFER_CTX(itransfer));
- return priv->backend->cancel_transfer(itransfer);
+ struct windows_transfer_priv *transfer_priv = 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->pollable_fd.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_events(struct libusb_context *ctx, struct pollfd *fds, usbi_nfds_t nfds, int num_ready)
{
struct windows_context_priv *priv = _context_priv(ctx);
struct usbi_transfer *itransfer;
- DWORD io_size, io_result;
+ struct windows_transfer_priv *transfer_priv;
+ DWORD result, bytes_transferred;
usbi_nfds_t i;
- bool found;
- int transfer_fd;
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)
@@ -701,34 +785,30 @@ static int windows_handle_events(struct libusb_context *ctx, struct pollfd *fds,
num_ready--;
- // Because a Windows OVERLAPPED is used for poll emulation,
- // a pollable fd is created and stored with each transfer
- found = false;
- transfer_fd = -1;
+ transfer_priv = NULL;
usbi_mutex_lock(&ctx->flying_transfers_lock);
list_for_each_entry(itransfer, &ctx->flying_transfers, list, struct usbi_transfer) {
- transfer_fd = priv->backend->get_transfer_fd(itransfer);
- if (transfer_fd == fds[i].fd) {
- found = true;
+ transfer_priv = get_transfer_priv(itransfer);
+ if (transfer_priv->pollable_fd.fd == fds[i].fd)
break;
- }
+ transfer_priv = NULL;
}
usbi_mutex_unlock(&ctx->flying_transfers_lock);
- if (found) {
- priv->backend->get_overlapped_result(itransfer, &io_result, &io_size);
-
- usbi_remove_pollfd(ctx, transfer_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(priv->backend, itransfer, io_result, io_size);
- } else {
+ if (transfer_priv == NULL) {
usbi_err(ctx, "could not find a matching transfer for fd %d", fds[i].fd);
r = LIBUSB_ERROR_NOT_FOUND;
break;
}
+
+ usbi_remove_pollfd(ctx, transfer_priv->pollable_fd.fd);
+
+ if (GetOverlappedResult(transfer_priv->handle, transfer_priv->pollable_fd.overlapped, &bytes_transferred, FALSE))
+ result = NO_ERROR;
+ else
+ result = GetLastError();
+
+ windows_transfer_callback(priv->backend, itransfer, result, bytes_transferred);
}
usbi_mutex_unlock(&ctx->open_devs_lock);
@@ -814,5 +894,5 @@ const struct usbi_os_backend usbi_backend = {
sizeof(struct windows_context_priv),
sizeof(union windows_device_priv),
sizeof(union windows_device_handle_priv),
- sizeof(union windows_transfer_priv),
+ sizeof(struct windows_transfer_priv),
};