/* * poll_windows: poll compatibility wrapper for Windows * Copyright © 2017 Chris Dickens * * 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 #include #include #include #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() { 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) { 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 >= 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; }