summaryrefslogtreecommitdiff
path: root/libusb/os/poll_windows.c
diff options
context:
space:
mode:
authorChris Dickens <christopher.a.dickens@gmail.com>2020-08-12 16:06:38 -0700
committerChris Dickens <christopher.a.dickens@gmail.com>2020-08-12 16:06:38 -0700
commitd67eb5beaa44c17c09f089a522ff483f4978a978 (patch)
tree8a5dffe3e6779dc95fa1adb32a1dc6d7be69c7af /libusb/os/poll_windows.c
parentba6b8bcb7ea204e65a3deec3be81aacc9f4b6d5a (diff)
downloadlibusb-d67eb5beaa44c17c09f089a522ff483f4978a978.tar.gz
core: Introduce platform events abstraction
The way in which system handles or resources are represented differs greatly between Unix-like operating systems and Windows. Ever since Windows support was added to libusb, Windows been emulating principles of Unix-like operating systems such as file descriptors and poll(). This commit introduces an abstraction layer that completely removes the need to perform any emulation. Fundamentally there are three things that each platform provides to libusb: 1) A signallable event 2) A timer (not required, but useful) 3) A means to wait for event sources such as the above to be triggered The POSIX abstraction for Unix-like operating systems uses file descriptors as the "handles" to the underlying system resources. The signallable event is implemented using a pipe, the timer as a timerfd (where supported) and the poll() system call is used to wait for events. The Windows abstraction uses native HANDLEs as the "handles" to the underlying system resources. The signallable event is implemented using a manual-reset event, the timer as a manual-reset waitable timer, and the WaitForMultipleObjects() system call is used to wait for events. Closes #252 Signed-off-by: Chris Dickens <christopher.a.dickens@gmail.com>
Diffstat (limited to 'libusb/os/poll_windows.c')
-rw-r--r--libusb/os/poll_windows.c502
1 files changed, 0 insertions, 502 deletions
diff --git a/libusb/os/poll_windows.c b/libusb/os/poll_windows.c
deleted file mode 100644
index c2bc105..0000000
--- a/libusb/os/poll_windows.c
+++ /dev/null
@@ -1,502 +0,0 @@
-/*
- * poll_windows: poll compatibility wrapper for Windows
- * Copyright © 2017 Chris Dickens <christopher.a.dickens@gmail.com>
- *
- * 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
- *
- */
-
-/*
- * poll() and pipe() Windows compatibility layer for libusb 1.0
- *
- * 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.
- */
-
-#include "libusbi.h"
-#include "windows_common.h"
-
-#include <errno.h>
-#include <intrin.h>
-#include <malloc.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-
-// private data
-struct file_descriptor {
- LONG refcount;
- OVERLAPPED overlapped;
-};
-
-static usbi_mutex_static_t fd_table_lock = USBI_MUTEX_INITIALIZER;
-
-#define BITS_PER_BYTE 8
-#define BITMAP_BITS_PER_WORD (sizeof(unsigned long) * BITS_PER_BYTE)
-#define FD_TABLE_INCR_SIZE 64
-
-static struct file_descriptor **fd_table;
-static unsigned long *fd_table_bitmap;
-static unsigned int fd_table_size;
-static unsigned int fd_count;
-
-#define return_with_errno(err) \
- do { \
- errno = (err); \
- return -1; \
- } while (0)
-
-static struct file_descriptor *alloc_fd(LONG refcount)
-{
- struct file_descriptor *fd = calloc(1, sizeof(*fd));
-
- if (fd == NULL)
- return NULL;
- fd->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
- if (fd->overlapped.hEvent == NULL) {
- free(fd);
- return NULL;
- }
- fd->refcount = refcount;
- return fd;
-}
-
-static struct file_descriptor *get_fd(unsigned int _fd, bool ref)
-{
- struct file_descriptor *fd = NULL;
-
- if (_fd < fd_table_size)
- fd = fd_table[_fd];
- if (fd != NULL && ref)
- InterlockedIncrement(&fd->refcount);
-
- return fd;
-}
-
-static void put_fd(struct file_descriptor *fd)
-{
- if (InterlockedDecrement(&fd->refcount) == 0L) {
- CloseHandle(fd->overlapped.hEvent);
- free(fd);
- }
-}
-
-static int install_fd(struct file_descriptor *fd)
-{
- unsigned int n;
-
- if (fd_count == fd_table_size) {
- struct file_descriptor **new_table;
- unsigned long *new_bitmap;
-
- // Need to expand the fd table and bitmap
- new_table = realloc(fd_table, (fd_table_size + FD_TABLE_INCR_SIZE) * sizeof(*new_table));
- if (new_table == NULL)
- return -ENOMEM;
- memset(new_table + fd_table_size, 0, FD_TABLE_INCR_SIZE * sizeof(*new_table));
- fd_table = new_table;
-
- new_bitmap = realloc(fd_table_bitmap, (fd_table_size + FD_TABLE_INCR_SIZE) / BITS_PER_BYTE);
- if (new_bitmap == NULL)
- return -ENOMEM;
- memset(new_bitmap + (fd_table_size / BITMAP_BITS_PER_WORD), 0, FD_TABLE_INCR_SIZE / BITS_PER_BYTE);
- fd_table_bitmap = new_bitmap;
-
- fd_table_size += FD_TABLE_INCR_SIZE;
- assert(fd_table_size < (unsigned int)INT_MAX);
- }
-
- for (n = 0; n < fd_table_size; n += BITMAP_BITS_PER_WORD) {
- unsigned int idx = n / BITMAP_BITS_PER_WORD;
- ULONG mask, pos = 0U;
- unsigned char nonzero;
-
- mask = ~fd_table_bitmap[idx];
- if (mask == 0U)
- continue;
-
- nonzero = _BitScanForward(&pos, mask);
- assert(nonzero);
- fd_table_bitmap[idx] |= 1U << pos;
- n += pos;
- break;
- }
-
- assert(n < fd_table_size);
- assert(fd_table[n] == NULL);
- fd_table[n] = fd;
- fd_count++;
-
- return n;
-}
-
-static void remove_fd(unsigned int pos)
-{
- assert(fd_table[pos] != NULL);
- fd_table[pos] = NULL;
- fd_table_bitmap[pos / BITMAP_BITS_PER_WORD] &= ~(1U << (pos % BITMAP_BITS_PER_WORD));
- fd_count--;
- if (fd_count == 0) {
- free(fd_table);
- free(fd_table_bitmap);
- fd_table = NULL;
- fd_table_bitmap = NULL;
- fd_table_size = 0;
- }
-}
-
-struct wait_thread_data {
- HANDLE thread;
- HANDLE handles[MAXIMUM_WAIT_OBJECTS];
- DWORD num_handles;
- DWORD error;
-};
-
-static DWORD WINAPI WaitThread(LPVOID lpParam)
-{
- struct wait_thread_data *thread_data = lpParam;
- HANDLE notify_event = thread_data->handles[0];
- DWORD status;
-
- status = WaitForMultipleObjects(thread_data->num_handles, thread_data->handles, FALSE, INFINITE);
- if (status < (WAIT_OBJECT_0 + thread_data->num_handles)) {
- if (status > WAIT_OBJECT_0) {
- // This will wake up all the other waiting threads
- SetEvent(notify_event);
- }
- thread_data->error = 0;
- } else {
- assert(status == WAIT_FAILED);
- thread_data->error = (status == WAIT_FAILED) ? GetLastError() : ERROR_CAN_NOT_COMPLETE;
- }
-
- return 0;
-}
-
-static DWORD poll_wait(const HANDLE *wait_handles, DWORD num_wait_handles, DWORD timeout)
-{
- struct wait_thread_data *thread_data;
- HANDLE notify_event;
- HANDLE *handles;
- int n, num_threads;
- DWORD error, status;
-
- if (num_wait_handles <= MAXIMUM_WAIT_OBJECTS)
- return WaitForMultipleObjects(num_wait_handles, wait_handles, FALSE, timeout);
-
- // To wait on more than MAXIMUM_WAIT_OBJECTS, each thread (including the
- // current thread) will wait on an event and (MAXIMUM_WAIT_OBJECTS - 1)
- // HANDLEs. The event is shared amongst all threads so that any thread
- // that returns from a WaitForMultipleObjects() call will set the event
- // and wake up all the other threads.
- notify_event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (notify_event == NULL)
- return WAIT_FAILED;
-
- num_threads = 1 + (num_wait_handles - MAXIMUM_WAIT_OBJECTS - 1) / (MAXIMUM_WAIT_OBJECTS - 1);
- thread_data = malloc(num_threads * sizeof(*thread_data));
- if (thread_data == NULL) {
- CloseHandle(notify_event);
- SetLastError(ERROR_OUTOFMEMORY);
- return WAIT_FAILED;
- }
-
- handles = _alloca(MAXIMUM_WAIT_OBJECTS * sizeof(HANDLE));
- handles[0] = notify_event;
- memcpy(handles + 1, wait_handles, (MAXIMUM_WAIT_OBJECTS - 1) * sizeof(HANDLE));
- wait_handles += MAXIMUM_WAIT_OBJECTS - 1;
- num_wait_handles -= MAXIMUM_WAIT_OBJECTS - 1;
-
- for (n = 0; n < num_threads; n++) {
- DWORD copy_size = MIN(num_wait_handles, MAXIMUM_WAIT_OBJECTS - 1);
-
- thread_data[n].handles[0] = notify_event;
- memcpy(thread_data[n].handles + 1, wait_handles, copy_size * sizeof(HANDLE));
- thread_data[n].num_handles = copy_size + 1;
-
- // Create the thread that will wait on these HANDLEs
- thread_data[n].thread = CreateThread(NULL, 0, WaitThread, &thread_data[n], 0, NULL);
- if (thread_data[n].thread == NULL) {
- thread_data[n].error = GetLastError();
- SetEvent(notify_event);
- num_threads = n + 1;
- break;
- }
-
- wait_handles += copy_size;
- num_wait_handles -= copy_size;
- }
-
- status = WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, handles, FALSE, timeout);
- if (status < (WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS)) {
- if (status > WAIT_OBJECT_0) {
- // Wake up all the waiting threads
- SetEvent(notify_event);
- status = WAIT_OBJECT_0;
- }
- error = 0;
- } else if (status == WAIT_TIMEOUT) {
- // Wake up all the waiting threads
- SetEvent(notify_event);
- error = 0;
- } else {
- assert(status == WAIT_FAILED);
- error = (status == WAIT_FAILED) ? GetLastError() : ERROR_CAN_NOT_COMPLETE;
- }
-
- for (n = 0; n < num_threads; n++) {
- if (thread_data[n].thread != NULL) {
- if (WaitForSingleObject(thread_data[n].thread, INFINITE) != WAIT_OBJECT_0)
- usbi_err(NULL, "WaitForSingleObject() failed: %lu", ULONG_CAST(GetLastError()));
- CloseHandle(thread_data[n].thread);
- }
- if (thread_data[n].error) {
- usbi_err(NULL, "wait thread %d had error %lu\n", n, ULONG_CAST(thread_data[n].error));
- error = thread_data[n].error;
- status = WAIT_FAILED;
- }
- }
-
- free(thread_data);
-
- CloseHandle(notify_event);
-
- if (status == WAIT_FAILED)
- SetLastError(error);
-
- return status;
-}
-
-/*
- * POSIX poll equivalent, using Windows OVERLAPPED
- * Currently, this function only accepts one of POLLIN or POLLOUT per fd
- * (but you can create multiple fds from the same handle for read and write)
- */
-int usbi_poll(struct pollfd *fds, usbi_nfds_t nfds, int timeout)
-{
- struct file_descriptor **fds_array;
- HANDLE *handles_array;
- struct file_descriptor *fd;
- usbi_nfds_t n;
- int nready;
-
- if (nfds <= MAXIMUM_WAIT_OBJECTS) {
- fds_array = _alloca(nfds * sizeof(*fds_array));
- handles_array = _alloca(nfds * sizeof(*handles_array));
- } else {
- fds_array = malloc(nfds * sizeof(*fds_array));
- if (fds_array == NULL)
- return_with_errno(ENOMEM);
- handles_array = malloc(nfds * sizeof(*handles_array));
- if (handles_array == NULL) {
- free(fds_array);
- return_with_errno(ENOMEM);
- }
- }
-
- usbi_mutex_static_lock(&fd_table_lock);
- for (n = 0; n < nfds; n++) {
- struct pollfd *pfd = &fds[n];
-
- // Keep it simple - only allow either POLLIN *or* POLLOUT
- assert((pfd->events == POLLIN) || (pfd->events == POLLOUT));
- if ((pfd->events != POLLIN) && (pfd->events != POLLOUT)) {
- fds_array[n] = NULL;
- continue;
- }
-
- // All file descriptors must be valid
- fd = get_fd(pfd->fd, true);
- assert(fd != NULL);
- if (fd == NULL) {
- fds_array[n] = NULL;
- continue;
- }
-
- // We hold a reference to fd for the duration of usbi_poll()
- fds_array[n] = fd;
- handles_array[n] = fd->overlapped.hEvent;
- }
- usbi_mutex_static_unlock(&fd_table_lock);
-
- nready = 0;
- while (nready == 0) {
- DWORD ret;
-
- // Check all fds for events
- for (n = 0; n < nfds; n++) {
- fd = fds_array[n];
- if (fd == NULL) {
- fds[n].revents = POLLNVAL;
- nready++;
- } else if (HasOverlappedIoCompleted(&fd->overlapped)) {
- fds[n].revents = fds[n].events;
- nready++;
- } else {
- fds[n].revents = 0;
- }
- }
-
- if ((nready != 0) || (timeout == 0))
- break;
-
- // Wait for any of the events to trigger
- ret = poll_wait(handles_array, nfds, (timeout < 0) ? INFINITE : (DWORD)timeout);
- if (ret == WAIT_TIMEOUT) {
- assert(timeout > 0);
- timeout = 0;
- } else if (ret == WAIT_FAILED) {
- usbi_err(NULL, "WaitForMultipleObjects failed: %lu", ULONG_CAST(GetLastError()));
- errno = EIO;
- nready = -1;
- }
- }
-
- for (n = 0; n < nfds; n++) {
- if (fds_array[n] != NULL)
- put_fd(fds_array[n]);
- }
-
- if (nfds > MAXIMUM_WAIT_OBJECTS) {
- free(handles_array);
- free(fds_array);
- }
-
- return nready;
-}
-
-/*
- * close a fake file descriptor
- */
-int usbi_close(int _fd)
-{
- struct file_descriptor *fd;
-
- usbi_mutex_static_lock(&fd_table_lock);
- fd = get_fd(_fd, false);
- if (fd != NULL)
- remove_fd(_fd);
- usbi_mutex_static_unlock(&fd_table_lock);
-
- if (fd == NULL)
- return_with_errno(EBADF);
-
- put_fd(fd);
-
- return 0;
-}
-
-/*
-* Create a fake pipe.
-* As libusb only uses pipes for signaling, all we need from a pipe is an
-* event. To that extent, we create a single wfd and overlapped as a means
-* to access that event.
-*/
-int usbi_pipe(int filedes[2])
-{
- struct file_descriptor *fd;
- int r_fd, w_fd;
- int error = 0;
-
- fd = alloc_fd(2);
- if (fd == NULL)
- return_with_errno(ENOMEM);
-
- fd->overlapped.Internal = STATUS_PENDING;
-
- usbi_mutex_static_lock(&fd_table_lock);
- r_fd = install_fd(fd);
- if (r_fd >= 0) {
- w_fd = install_fd(fd);
- if (w_fd < 0) {
- remove_fd(r_fd);
- error = w_fd;
- }
- } else {
- error = r_fd;
- w_fd = -1; // Keep compiler happy
- }
- usbi_mutex_static_unlock(&fd_table_lock);
-
- if (error) {
- CloseHandle(fd->overlapped.hEvent);
- free(fd);
- return_with_errno(error);
- }
-
- filedes[0] = r_fd;
- filedes[1] = w_fd;
-
- return 0;
-}
-
-/*
- * synchronous write for fake "pipe" signaling
- */
-ssize_t usbi_write(int _fd, const void *buf, size_t count)
-{
- struct file_descriptor *fd;
-
- UNUSED(buf);
-
- if (count != sizeof(unsigned char)) {
- usbi_err(NULL, "this function should only used for signaling");
- return_with_errno(EINVAL);
- }
-
- usbi_mutex_static_lock(&fd_table_lock);
- fd = get_fd(_fd, false);
- if (fd != NULL) {
- assert(fd->overlapped.Internal == STATUS_PENDING);
- fd->overlapped.Internal = STATUS_WAIT_0;
- SetEvent(fd->overlapped.hEvent);
- }
- usbi_mutex_static_unlock(&fd_table_lock);
-
- if (fd == NULL)
- return_with_errno(EBADF);
-
- return sizeof(unsigned char);
-}
-
-/*
- * synchronous read for fake "pipe" signaling
- */
-ssize_t usbi_read(int _fd, void *buf, size_t count)
-{
- struct file_descriptor *fd;
-
- UNUSED(buf);
-
- if (count != sizeof(unsigned char)) {
- usbi_err(NULL, "this function should only used for signaling");
- return_with_errno(EINVAL);
- }
-
- usbi_mutex_static_lock(&fd_table_lock);
- fd = get_fd(_fd, false);
- if (fd != NULL) {
- assert(fd->overlapped.Internal == STATUS_WAIT_0);
- fd->overlapped.Internal = STATUS_PENDING;
- ResetEvent(fd->overlapped.hEvent);
- }
- usbi_mutex_static_unlock(&fd_table_lock);
-
- if (fd == NULL)
- return_with_errno(EBADF);
-
- return sizeof(unsigned char);
-}