blob: b256d692a1fbc3a3cd2b77201df71e29e7f74951 [file] [log] [blame]
/*
* 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 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.
*/
#include <config.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include "libusbi.h"
#include "windows_common.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;
OVERLAPPED overlapped;
int refcount;
};
static usbi_mutex_static_t fd_table_lock = USBI_MUTEX_INITIALIZER;
static struct file_descriptor **fd_table;
static size_t fd_count;
static size_t fd_size;
#define INC_FDS_EACH 256
static void usbi_dec_fd_table(void)
{
fd_count--;
if (fd_count == 0) {
free(fd_table);
fd_size = 0;
fd_table = NULL;
}
}
static void smart_realloc_fd_table_space(int inc)
{
if (fd_table == NULL || fd_count + inc > fd_size) {
struct file_descriptor **p = (struct file_descriptor **)realloc(fd_table, (fd_size + INC_FDS_EACH) * sizeof(struct file_descriptor *));
if (p != NULL) {
memset(p + fd_size, 0, INC_FDS_EACH * sizeof(struct file_descriptor *));
fd_size += INC_FDS_EACH;
fd_table = p;
}
}
}
static struct file_descriptor *create_fd(enum fd_type type)
{
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->type = type;
fd->refcount = 1;
return fd;
}
static void free_fd(struct file_descriptor *fd)
{
CloseHandle(fd->overlapped.hEvent);
free(fd);
}
/*
* 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 = create_fd(FD_TYPE_TRANSFER);
if (fd == NULL)
return INVALID_WINFD;
usbi_mutex_static_lock(&fd_table_lock);
smart_realloc_fd_table_space(1);
for (wfd.fd = 0; wfd.fd < fd_size; wfd.fd++) {
if (fd_table[wfd.fd] != NULL)
continue;
fd_table[wfd.fd] = fd;
fd_count++;
break;
}
usbi_mutex_static_unlock(&fd_table_lock);
if (wfd.fd == fd_size) {
free_fd(fd);
return INVALID_WINFD;
}
wfd.overlapped = &fd->overlapped;
return wfd;
}
void usbi_inc_fds_ref(struct pollfd *fds, unsigned int nfds)
{
int n;
usbi_mutex_static_lock(&fd_table_lock);
for (n = 0; n < nfds; ++n) {
fd_table[fds[n].fd]->refcount++;
}
usbi_mutex_static_unlock(&fd_table_lock);
}
void usbi_dec_fds_ref(struct pollfd *fds, unsigned int nfds)
{
int n;
struct file_descriptor *fd;
usbi_mutex_static_lock(&fd_table_lock);
for (n = 0; n < nfds; ++n) {
fd = fd_table[fds[n].fd];
fd->refcount--;
//FD_TYPE_PIPE map fd to two _fd
if (fd->refcount == 0 || (fd->refcount == 1 && fd->type == FD_TYPE_PIPE))
{
if (fd->type == FD_TYPE_PIPE) {
// InternalHigh is our reference count
fd->overlapped.InternalHigh--;
if (fd->overlapped.InternalHigh == 0)
free_fd(fd);
}
else {
free_fd(fd);
}
fd_table[fds[n].fd] = NULL;
usbi_dec_fd_table();
}
}
usbi_mutex_static_unlock(&fd_table_lock);
}
static int check_pollfds(struct pollfd *fds, unsigned int nfds,
HANDLE *wait_handles, DWORD *nb_wait_handles)
{
struct file_descriptor *fd;
unsigned int n;
int nready = 0;
usbi_mutex_static_lock(&fd_table_lock);
for (n = 0; n < nfds; ++n) {
fds[n].revents = 0;
// Keep it simple - only allow either POLLIN *or* POLLOUT
assert((fds[n].events == POLLIN) || (fds[n].events == POLLOUT));
if ((fds[n].events != POLLIN) && (fds[n].events != POLLOUT)) {
fds[n].revents = POLLNVAL;
nready++;
continue;
}
if ((fds[n].fd >= 0) && (fds[n].fd < fd_size))
fd = fd_table[fds[n].fd];
else
fd = NULL;
assert(fd != NULL);
if (fd == NULL) {
fds[n].revents = POLLNVAL;
nready++;
continue;
}
if (HasOverlappedIoCompleted(&fd->overlapped)
&& (WaitForSingleObject(fd->overlapped.hEvent, 0) == WAIT_OBJECT_0)) {
fds[n].revents = fds[n].events;
nready++;
} else if (wait_handles != NULL) {
wait_handles[*nb_wait_handles] = fd->overlapped.hEvent;
(*nb_wait_handles)++;
}
}
usbi_mutex_static_unlock(&fd_table_lock);
return nready;
}
#define EXT_TIMEOUT WAIT_OBJECT_0
struct ExThreadData
{
HANDLE notifyevents;
HANDLE thread;
HANDLE wait_event[MAXIMUM_WAIT_OBJECTS];
int nEvents;
DWORD ret_wait;
volatile int bexit;
};
static DWORD __stdcall WaitThread(LPVOID lpThreadParameter)
{
struct ExThreadData *p = (struct ExThreadData *)lpThreadParameter;
int ret = WaitForMultipleObjects(p->nEvents, p->wait_event, FALSE, INFINITE);
p->ret_wait = ret;
p->bexit = true;
SetEvent(p->notifyevents);
return 0;
}
static DWORD ExtendWaitForMultipleObjects(
DWORD nCount,
const HANDLE *lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
)
{
DWORD ret;
int i = 0;
int nThreads = 0;
struct ExThreadData *pThread;
int size;
HANDLE notify_event;
if (nCount <= MAXIMUM_WAIT_OBJECTS) {
ret = WaitForMultipleObjects(nCount, lpHandles, bWaitAll, dwMilliseconds);
if (ret == WAIT_TIMEOUT)
return EXT_TIMEOUT;
if (ret < WAIT_OBJECT_0 + nCount)
return ret + 1;
return ret;
}
nThreads = (nCount + MAXIMUM_WAIT_OBJECTS - 2) / (MAXIMUM_WAIT_OBJECTS - 1);
notify_event = CreateEvent(NULL, FALSE, FALSE, NULL);
if (notify_event == NULL) {
usbi_err(NULL, "Create Event failure");
return WAIT_FAILED;
}
pThread = malloc(sizeof(struct ExThreadData) * nThreads);
if (pThread == NULL) {
usbi_err(NULL, "Out of memory");
CloseHandle(notify_event);
return WAIT_FAILED;
}
for (i = 0; i < nThreads; i++)
{
pThread[i].wait_event[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
pThread[i].notifyevents = notify_event;
size = nCount - i * (MAXIMUM_WAIT_OBJECTS - 1);
if (size >= (MAXIMUM_WAIT_OBJECTS - 1))
size = (MAXIMUM_WAIT_OBJECTS - 1);
memcpy(pThread[i].wait_event + 1, lpHandles + i * (MAXIMUM_WAIT_OBJECTS - 1), size * sizeof(HANDLE));
pThread[i].nEvents = size + 1;
pThread[i].bexit = 0;
pThread[i].thread = CreateThread(NULL, 0, WaitThread, pThread+i, 0, NULL);
}
ret = WaitForSingleObject(notify_event, INFINITE);
for (i = 0; i < nThreads; i++)
{
SetEvent(pThread[i].wait_event[0]);
while (pThread[i].bexit == 0); //wait for thread exist;
if (pThread[i].ret_wait != WAIT_OBJECT_0)
ret = pThread[i].ret_wait + i * (MAXIMUM_WAIT_OBJECTS - 1);
CloseHandle(pThread[i].wait_event[0]);
}
CloseHandle(notify_event);
free(pThread);
return ret ;
}
/*
* 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, unsigned int nfds, int timeout)
{
HANDLE *wait_handles;
DWORD nb_wait_handles = 0;
DWORD ret;
int nready;
wait_handles = malloc(nfds * sizeof(HANDLE));
if (!wait_handles)
{
usbi_err(NULL, "Out of memory");
return -1;
}
nready = check_pollfds(fds, nfds, wait_handles, &nb_wait_handles);
// If nothing was triggered, wait on all fds that require it
if ((nready == 0) && (nb_wait_handles != 0) && (timeout != 0)) {
ret = ExtendWaitForMultipleObjects(nb_wait_handles, wait_handles,
FALSE, (timeout < 0) ? INFINITE : (DWORD)timeout);
if (ret != EXT_TIMEOUT && ret <= (WAIT_OBJECT_0 + nb_wait_handles)) {
nready = check_pollfds(fds, nfds, NULL, NULL);
} else if (ret != EXT_TIMEOUT) {
if (ret == WAIT_FAILED)
usbi_err(NULL, "WaitForMultipleObjects failed: %u", (unsigned int)GetLastError());
nready = -1;
}
}
free(wait_handles);
return nready;
}
/*
* close a fake file descriptor
*/
int usbi_close(int _fd)
{
struct file_descriptor *fd;
if (_fd < 0 || _fd >= fd_size)
goto err_badfd;
usbi_mutex_static_lock(&fd_table_lock);
fd = fd_table[_fd];
fd->refcount--;
//FD_TYPE_PIPE map fd to two _fd
if(fd->refcount==0 || (fd->refcount == 1 && fd->type == FD_TYPE_PIPE))
{ fd_table[_fd] = NULL;
usbi_dec_fd_table();
if (fd->type == FD_TYPE_PIPE) {
// InternalHigh is our reference count
fd->overlapped.InternalHigh--;
if (fd->overlapped.InternalHigh == 0)
free_fd(fd);
}
else {
free_fd(fd);
}
}
usbi_mutex_static_unlock(&fd_table_lock);
if (fd == NULL)
goto err_badfd;
return 0;
err_badfd:
errno = EBADF;
return -1;
}
/*
* 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 = -1, w_fd = -1;
int i;
fd = create_fd(FD_TYPE_PIPE);
if (fd == NULL) {
errno = ENOMEM;
return -1;
}
// Use InternalHigh as a reference count
fd->overlapped.Internal = STATUS_PENDING;
fd->overlapped.InternalHigh = 2;
usbi_mutex_static_lock(&fd_table_lock);
do {
smart_realloc_fd_table_space(2);
for (i = 0; i < fd_size; i++) {
if (fd_table[i] != NULL)
continue;
if (r_fd == -1) {
r_fd = i;
} else if (w_fd == -1) {
w_fd = i;
break;
}
}
if (i == fd_size)
break;
fd_table[r_fd] = fd;
fd_table[w_fd] = fd;
fd->refcount++; //this fd reference twice for r and w.
fd_count += 2;
} while (0);
usbi_mutex_static_unlock(&fd_table_lock);
if (i == fd_size) {
free_fd(fd);
errno = EMFILE;
return -1;
}
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)
{
int error = EBADF;
UNUSED(buf);
if (fd < 0 || fd >= fd_size)
goto err_out;
if (count != sizeof(unsigned char)) {
usbi_err(NULL, "this function should only used for signaling");
error = EINVAL;
goto err_out;
}
usbi_mutex_static_lock(&fd_table_lock);
if ((fd_table[fd] != NULL) && (fd_table[fd]->type == FD_TYPE_PIPE)) {
assert(fd_table[fd]->overlapped.Internal == STATUS_PENDING);
assert(fd_table[fd]->overlapped.InternalHigh == 2);
fd_table[fd]->overlapped.Internal = STATUS_WAIT_0;
SetEvent(fd_table[fd]->overlapped.hEvent);
error = 0;
}
usbi_mutex_static_unlock(&fd_table_lock);
if (error)
goto err_out;
return sizeof(unsigned char);
err_out:
errno = error;
return -1;
}
/*
* synchronous read for fake "pipe" signaling
*/
ssize_t usbi_read(int fd, void *buf, size_t count)
{
int error = EBADF;
UNUSED(buf);
if (fd < 0 || fd >= fd_size)
goto err_out;
if (count != sizeof(unsigned char)) {
usbi_err(NULL, "this function should only used for signaling");
error = EINVAL;
goto err_out;
}
usbi_mutex_static_lock(&fd_table_lock);
if ((fd_table[fd] != NULL) && (fd_table[fd]->type == FD_TYPE_PIPE)) {
assert(fd_table[fd]->overlapped.Internal == STATUS_WAIT_0);
assert(fd_table[fd]->overlapped.InternalHigh == 2);
fd_table[fd]->overlapped.Internal = STATUS_PENDING;
ResetEvent(fd_table[fd]->overlapped.hEvent);
error = 0;
}
usbi_mutex_static_unlock(&fd_table_lock);
if (error)
goto err_out;
return sizeof(unsigned char);
err_out:
errno = error;
return -1;
}