| /* |
| * 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("poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms); |
| num_ready = poll(fds, nfds, timeout_ms); |
| usbi_dbg("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 |
| |
| assert(num_ready > 0); |
| 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("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; |
| } |