diff options
Diffstat (limited to 'hardware/src/libusb/os/events_posix.c')
-rw-r--r-- | hardware/src/libusb/os/events_posix.c | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/hardware/src/libusb/os/events_posix.c b/hardware/src/libusb/os/events_posix.c new file mode 100644 index 0000000..715a2d5 --- /dev/null +++ b/hardware/src/libusb/os/events_posix.c @@ -0,0 +1,300 @@ +/* + * libusb event abstraction on POSIX platforms + * + * Copyright © 2020 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 + */ + +#include "libusbi.h" + +#include <errno.h> +#include <fcntl.h> +#ifdef HAVE_EVENTFD +#include <sys/eventfd.h> +#endif +#ifdef HAVE_TIMERFD +#include <sys/timerfd.h> +#endif +#include <unistd.h> + +#ifdef HAVE_EVENTFD +#define EVENT_READ_FD(e) ((e)->eventfd) +#define EVENT_WRITE_FD(e) ((e)->eventfd) +#else +#define EVENT_READ_FD(e) ((e)->pipefd[0]) +#define EVENT_WRITE_FD(e) ((e)->pipefd[1]) +#endif + +#ifdef HAVE_NFDS_T +typedef nfds_t usbi_nfds_t; +#else +typedef unsigned int usbi_nfds_t; +#endif + +int usbi_create_event(usbi_event_t *event) +{ +#ifdef HAVE_EVENTFD + event->eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + if (event->eventfd == -1) { + usbi_err(NULL, "failed to create eventfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +#else +#if defined(HAVE_PIPE2) + int ret = pipe2(event->pipefd, O_CLOEXEC); +#else + int ret = pipe(event->pipefd); +#endif + + if (ret != 0) { + usbi_err(NULL, "failed to create pipe, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + +#if !defined(HAVE_PIPE2) && defined(FD_CLOEXEC) + ret = fcntl(event->pipefd[0], F_GETFD); + if (ret == -1) { + usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } + ret = fcntl(event->pipefd[0], F_SETFD, ret | FD_CLOEXEC); + if (ret == -1) { + usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } + + ret = fcntl(event->pipefd[1], F_GETFD); + if (ret == -1) { + usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } + ret = fcntl(event->pipefd[1], F_SETFD, ret | FD_CLOEXEC); + if (ret == -1) { + usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno); + goto err_close_pipe; + } +#endif + + ret = fcntl(event->pipefd[1], F_GETFL); + if (ret == -1) { + usbi_err(NULL, "failed to get pipe fd status flags, errno=%d", errno); + goto err_close_pipe; + } + ret = fcntl(event->pipefd[1], F_SETFL, ret | O_NONBLOCK); + if (ret == -1) { + usbi_err(NULL, "failed to set pipe fd status flags, errno=%d", errno); + goto err_close_pipe; + } + + return 0; + +err_close_pipe: + close(event->pipefd[1]); + close(event->pipefd[0]); + return LIBUSB_ERROR_OTHER; +#endif +} + +void usbi_destroy_event(usbi_event_t *event) +{ +#ifdef HAVE_EVENTFD + if (close(event->eventfd) == -1) + usbi_warn(NULL, "failed to close eventfd, errno=%d", errno); +#else + if (close(event->pipefd[1]) == -1) + usbi_warn(NULL, "failed to close pipe write end, errno=%d", errno); + if (close(event->pipefd[0]) == -1) + usbi_warn(NULL, "failed to close pipe read end, errno=%d", errno); +#endif +} + +void usbi_signal_event(usbi_event_t *event) +{ + uint64_t dummy = 1; + ssize_t r; + + r = write(EVENT_WRITE_FD(event), &dummy, sizeof(dummy)); + if (r != sizeof(dummy)) + usbi_warn(NULL, "event write failed"); +} + +void usbi_clear_event(usbi_event_t *event) +{ + uint64_t dummy; + ssize_t r; + + r = read(EVENT_READ_FD(event), &dummy, sizeof(dummy)); + if (r != sizeof(dummy)) + usbi_warn(NULL, "event read failed"); +} + +#ifdef HAVE_TIMERFD +int usbi_create_timer(usbi_timer_t *timer) +{ + timer->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if (timer->timerfd == -1) { + usbi_warn(NULL, "failed to create timerfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +void usbi_destroy_timer(usbi_timer_t *timer) +{ + if (close(timer->timerfd) == -1) + usbi_warn(NULL, "failed to close timerfd, errno=%d", errno); +} + +int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout) +{ + const struct itimerspec it = { { 0, 0 }, { timeout->tv_sec, timeout->tv_nsec } }; + + if (timerfd_settime(timer->timerfd, TFD_TIMER_ABSTIME, &it, NULL) == -1) { + usbi_warn(NULL, "failed to arm timerfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +int usbi_disarm_timer(usbi_timer_t *timer) +{ + const struct itimerspec it = { { 0, 0 }, { 0, 0 } }; + + if (timerfd_settime(timer->timerfd, 0, &it, NULL) == -1) { + usbi_warn(NULL, "failed to disarm timerfd, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} +#endif + +int usbi_alloc_event_data(struct libusb_context *ctx) +{ + struct usbi_event_source *ievent_source; + struct pollfd *fds; + size_t i = 0; + + if (ctx->event_data) { + free(ctx->event_data); + ctx->event_data = NULL; + } + + ctx->event_data_cnt = 0; + for_each_event_source(ctx, ievent_source) + ctx->event_data_cnt++; + + fds = calloc(ctx->event_data_cnt, sizeof(*fds)); + if (!fds) + return LIBUSB_ERROR_NO_MEM; + + for_each_event_source(ctx, ievent_source) { + fds[i].fd = ievent_source->data.os_handle; + fds[i].events = ievent_source->data.poll_events; + i++; + } + + ctx->event_data = fds; + return 0; +} + +int usbi_wait_for_events(struct libusb_context *ctx, + struct usbi_reported_events *reported_events, int timeout_ms) +{ + struct pollfd *fds = ctx->event_data; + usbi_nfds_t nfds = (usbi_nfds_t)ctx->event_data_cnt; + int internal_fds, num_ready; + + usbi_dbg(ctx, "poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms); + num_ready = poll(fds, nfds, timeout_ms); + usbi_dbg(ctx, "poll() returned %d", num_ready); + if (num_ready == 0) { + if (usbi_using_timer(ctx)) + goto done; + return LIBUSB_ERROR_TIMEOUT; + } else if (num_ready == -1) { + if (errno == EINTR) + return LIBUSB_ERROR_INTERRUPTED; + usbi_err(ctx, "poll() failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + /* fds[0] is always the internal signalling event */ + if (fds[0].revents) { + reported_events->event_triggered = 1; + num_ready--; + } else { + reported_events->event_triggered = 0; + } + +#ifdef HAVE_OS_TIMER + /* on timer configurations, fds[1] is the timer */ + if (usbi_using_timer(ctx) && fds[1].revents) { + reported_events->timer_triggered = 1; + num_ready--; + } else { + reported_events->timer_triggered = 0; + } +#endif + + if (!num_ready) + goto done; + + /* the backend will never need to attempt to handle events on the + * library's internal file descriptors, so we determine how many are + * in use internally for this context and skip these when passing any + * remaining pollfds to the backend. */ + internal_fds = usbi_using_timer(ctx) ? 2 : 1; + fds += internal_fds; + nfds -= internal_fds; + + usbi_mutex_lock(&ctx->event_data_lock); + if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) { + struct usbi_event_source *ievent_source; + + for_each_removed_event_source(ctx, ievent_source) { + usbi_nfds_t n; + + for (n = 0; n < nfds; n++) { + if (ievent_source->data.os_handle != fds[n].fd) + continue; + if (!fds[n].revents) + continue; + /* pollfd was removed between the creation of the fds array and + * here. remove triggered revent as it is no longer relevant. */ + usbi_dbg(ctx, "fd %d was removed, ignoring raised events", fds[n].fd); + fds[n].revents = 0; + num_ready--; + break; + } + } + } + usbi_mutex_unlock(&ctx->event_data_lock); + + if (num_ready) { + assert(num_ready > 0); + reported_events->event_data = fds; + reported_events->event_data_count = (unsigned int)nfds; + } + +done: + reported_events->num_ready = num_ready; + return LIBUSB_SUCCESS; +} |