mirror of
https://github.com/Retropex/dolphin.git
synced 2025-08-04 05:54:47 +02:00

Now has support for isochronous transfers in the WinUSB backend, which may or may not work better than the UsbDk backend.
408 lines
9.6 KiB
C
408 lines
9.6 KiB
C
/*
|
|
* 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[MAX_FDS];
|
|
|
|
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);
|
|
for (wfd.fd = 0; wfd.fd < MAX_FDS; wfd.fd++) {
|
|
if (fd_table[wfd.fd] != NULL)
|
|
continue;
|
|
fd_table[wfd.fd] = fd;
|
|
break;
|
|
}
|
|
usbi_mutex_static_unlock(&fd_table_lock);
|
|
|
|
if (wfd.fd == MAX_FDS) {
|
|
free_fd(fd);
|
|
return INVALID_WINFD;
|
|
}
|
|
|
|
wfd.overlapped = &fd->overlapped;
|
|
|
|
return wfd;
|
|
}
|
|
|
|
int 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);
|
|
}
|
|
|
|
int 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--;
|
|
if (fd->refcount == 0)
|
|
{
|
|
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_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 < MAX_FDS))
|
|
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) {
|
|
if (*nb_wait_handles == MAXIMUM_WAIT_OBJECTS) {
|
|
usbi_warn(NULL, "too many HANDLEs to wait on");
|
|
continue;
|
|
}
|
|
wait_handles[*nb_wait_handles] = fd->overlapped.hEvent;
|
|
(*nb_wait_handles)++;
|
|
}
|
|
}
|
|
|
|
usbi_mutex_static_unlock(&fd_table_lock);
|
|
|
|
return nready;
|
|
}
|
|
/*
|
|
* 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[MAXIMUM_WAIT_OBJECTS];
|
|
DWORD nb_wait_handles = 0;
|
|
DWORD ret;
|
|
int nready;
|
|
|
|
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 = WaitForMultipleObjects(nb_wait_handles, wait_handles,
|
|
FALSE, (timeout < 0) ? INFINITE : (DWORD)timeout);
|
|
if (ret < (WAIT_OBJECT_0 + nb_wait_handles)) {
|
|
nready = check_pollfds(fds, nfds, NULL, NULL);
|
|
} else if (ret != WAIT_TIMEOUT) {
|
|
if (ret == WAIT_FAILED)
|
|
usbi_err(NULL, "WaitForMultipleObjects failed: %u", (unsigned int)GetLastError());
|
|
nready = -1;
|
|
}
|
|
}
|
|
|
|
return nready;
|
|
}
|
|
|
|
/*
|
|
* close a fake file descriptor
|
|
*/
|
|
int usbi_close(int _fd)
|
|
{
|
|
struct file_descriptor *fd;
|
|
|
|
if (_fd < 0 || _fd >= MAX_FDS)
|
|
goto err_badfd;
|
|
|
|
usbi_mutex_static_lock(&fd_table_lock);
|
|
fd = fd_table[_fd];
|
|
fd->refcount--;
|
|
if(fd->refcount==0)
|
|
{ fd_table[_fd] = NULL;
|
|
|
|
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 {
|
|
for (i = 0; i < MAX_FDS; 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 == MAX_FDS)
|
|
break;
|
|
|
|
fd_table[r_fd] = fd;
|
|
fd_table[w_fd] = fd;
|
|
|
|
} while (0);
|
|
usbi_mutex_static_unlock(&fd_table_lock);
|
|
|
|
if (i == MAX_FDS) {
|
|
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 >= MAX_FDS)
|
|
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 >= MAX_FDS)
|
|
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;
|
|
}
|