diff options
-rw-r--r-- | AUTHORS | 2 | ||||
-rw-r--r-- | configure.ac | 24 | ||||
-rw-r--r-- | libusb/io.c | 339 | ||||
-rw-r--r-- | libusb/libusb.h | 1 | ||||
-rw-r--r-- | libusb/libusbi.h | 23 | ||||
-rw-r--r-- | libusb/os/linux_usbfs.c | 81 |
6 files changed, 392 insertions, 78 deletions
@@ -1,4 +1,4 @@ -Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org> +Copyright (C) 2007-2009 Daniel Drake <dsd@gentoo.org> Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> Copyright (C) 2008-2009 Nathan Hjelm <hjelmn@users.sourceforge.net> diff --git a/configure.ac b/configure.ac index d287e01..ba6014e 100644 --- a/configure.ac +++ b/configure.ac @@ -45,6 +45,30 @@ AC_SUBST(lt_major) AC_SUBST(lt_revision) AC_SUBST(lt_age) +# timerfd +AC_CHECK_HEADER([sys/timerfd.h], [timerfd_h=1], [timerfd_h=0]) +AC_ARG_ENABLE([timerfd], + [AS_HELP_STRING([--enable-timerfd], + [use timerfd for timing (default auto)])], + [use_timerfd=$enableval], [use_timerfd='auto']) + +if test "x$use_timerfd" = "xyes" -a "x$timerfd_h" = "x0"; then + AC_MSG_ERROR([timerfd header not available; glibc 2.8+ required]) +error +fi + +AC_MSG_CHECKING([whether to use timerfd for timing]) +if test "x$use_timerfd" = "xno"; then + AC_MSG_RESULT([no (disabled by user)]) +else + if test "x$timerfd_h" = "x1"; then + AC_MSG_RESULT([yes]) + AC_DEFINE(USBI_TIMERFD_AVAILABLE, [], [timerfd headers available]) + else + AC_MSG_RESULT([no (header not available)]) + fi +fi + # Message logging AC_ARG_ENABLE([log], [AS_HELP_STRING([--disable-log], [disable all logging])], [log_enabled=$enableval], diff --git a/libusb/io.c b/libusb/io.c index ef286dd..b58eafa 100644 --- a/libusb/io.c +++ b/libusb/io.c @@ -1,6 +1,6 @@ /* * I/O functions for libusb - * Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org> + * Copyright (C) 2007-2009 Daniel Drake <dsd@gentoo.org> * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> * * This library is free software; you can redistribute it and/or @@ -30,6 +30,10 @@ #include <time.h> #include <unistd.h> +#ifdef USBI_TIMERFD_AVAILABLE +#include <sys/timerfd.h> +#endif + #include "libusbi.h" /** @@ -584,11 +588,13 @@ while (user_has_not_requested_exit) * detect activity on libusb's file descriptors, you call * libusb_handle_events_timeout() in non-blocking mode. * - * You must also consider the fact that libusb sometimes has to handle events - * at certain known times which do not generate activity on file descriptors. - * Your main loop must also consider these times, modify it's poll()/select() - * timeout accordingly, and track time so that libusb_handle_events_timeout() - * is called in non-blocking mode when timeouts expire. + * What's more, libusb may also need to handle events at specific moments in + * time. No file descriptor activity is generated at these times, so your + * own application needs to be continually aware of when the next one of these + * moments occurs (through calling libusb_get_next_timeout()), and then it + * needs to call libusb_handle_events_timeout() in non-blocking mode when + * these moments occur. This means that you need to adjust your + * poll()/select() timeout accordingly. * * In pseudo-code, you want something that looks like: \code @@ -597,23 +603,72 @@ while (user_has_not_requested_exit) libusb_get_pollfds(ctx) while (user has not requested application exit) { libusb_get_next_timeout(ctx); - select(on libusb file descriptors plus any other event sources of interest, + poll(on libusb file descriptors plus any other event sources of interest, using a timeout no larger than the value libusb just suggested) - if (select() indicated activity on libusb file descriptors) + if (poll() indicated activity on libusb file descriptors) libusb_handle_events_timeout(ctx, 0); if (time has elapsed to or beyond the libusb timeout) libusb_handle_events_timeout(ctx, 0); + // handle events from other sources here } // clean up and exit \endcode * + * \subsection polltime Notes on time-based events + * + * The above complication with having to track time and call into libusb at + * specific moments is a bit of a headache. For maximum compatibility, you do + * need to write your main loop as above, but you may decide that you can + * restrict the supported platforms of your application and get away with + * a more simplistic scheme. + * + * These time-based event complications are \b not required on the following + * platforms: + * - Darwin + * - Linux, provided that the following version requirements are satisfied: + * - Linux v2.6.27 or newer, compiled with timerfd support + * - glibc v2.8 or newer + * - libusb v1.0.5 or newer + * + * Under these configurations, libusb_get_next_timeout() will \em always return + * 0, so your main loop can be simplified to: +\code +// initialise libusb + +libusb_get_pollfds(ctx) +while (user has not requested application exit) { + poll(on libusb file descriptors plus any other event sources of interest, + using any timeout that you like) + if (poll() indicated activity on libusb file descriptors) + libusb_handle_events_timeout(ctx, 0); + // handle events from other sources here +} + +// clean up and exit +\endcode + * + * Do remember that if you simplify your main loop to the above, you will + * lose compatibility with some platforms (including legacy Linux platforms, + * and <em>any future platforms supported by libusb which may have time-based + * event requirements</em>). The resultant problems will likely appear as + * strange bugs in your application. + * + * You can use the libusb_pollfds_handle_timeouts() function to do a runtime + * check to see if it is safe to ignore the time-based event complications. + * If your application has taken the shortcut of ignoring libusb's next timeout + * in your main loop, then you are advised to check the return value of + * libusb_pollfds_handle_timeouts() during application startup, and to abort + * if the platform does suffer from these timing complications. + * + * \subsection fdsetchange Changes in the file descriptor set + * * The set of file descriptors that libusb uses as event sources may change * during the life of your application. Rather than having to repeatedly * call libusb_get_pollfds(), you can set up notification functions for when * the file descriptor set changes using libusb_set_pollfd_notifiers(). * - * \section mtissues Multi-threaded considerations + * \subsection mtissues Multi-threaded considerations * * Unfortunately, the situation is complicated further when multiple threads * come into play. If two threads are monitoring the same file descriptors, @@ -957,6 +1012,22 @@ int usbi_io_init(struct libusb_context *ctx) if (r < 0) return r; +#ifdef USBI_TIMERFD_AVAILABLE + ctx->timerfd = timerfd_create(usbi_backend->get_timerfd_clockid(), + TFD_NONBLOCK); + if (ctx->timerfd >= 0) { + usbi_dbg("using timerfd for timeouts"); + r = usbi_add_pollfd(ctx, ctx->timerfd, POLLIN); + if (r < 0) { + close(ctx->timerfd); + return r; + } + } else { + usbi_dbg("timerfd not available (code %d error %d)", ctx->timerfd, errno); + ctx->timerfd = -1; + } +#endif + return 0; } @@ -965,6 +1036,12 @@ void usbi_io_exit(struct libusb_context *ctx) usbi_remove_pollfd(ctx, ctx->ctrl_pipe[0]); close(ctx->ctrl_pipe[0]); close(ctx->ctrl_pipe[1]); +#ifdef USBI_TIMERFD_AVAILABLE + if (usbi_using_timerfd(ctx)) { + usbi_remove_pollfd(ctx, ctx->timerfd); + close(ctx->timerfd); + } +#endif } static int calculate_timeout(struct usbi_transfer *transfer) @@ -996,17 +1073,24 @@ static int calculate_timeout(struct usbi_transfer *transfer) return 0; } -static void add_to_flying_list(struct usbi_transfer *transfer) +/* add a transfer to the (timeout-sorted) active transfers list. + * returns 1 if the transfer has a timeout and it is the timeout next to + * expire */ +static int add_to_flying_list(struct usbi_transfer *transfer) { struct usbi_transfer *cur; struct timeval *timeout = &transfer->timeout; struct libusb_context *ctx = ITRANSFER_CTX(transfer); - + int r = 0; + int first = 1; + pthread_mutex_lock(&ctx->flying_transfers_lock); /* if we have no other flying transfers, start the list with this one */ if (list_empty(&ctx->flying_transfers)) { list_add(&transfer->list, &ctx->flying_transfers); + if (timerisset(timeout)) + r = 1; goto out; } @@ -1025,14 +1109,17 @@ static void add_to_flying_list(struct usbi_transfer *transfer) (cur_tv->tv_sec == timeout->tv_sec && cur_tv->tv_usec > timeout->tv_usec)) { list_add_tail(&transfer->list, &cur->list); + r = first; goto out; } + first = 0; } /* otherwise we need to be inserted at the end */ list_add_tail(&transfer->list, &ctx->flying_transfers); out: pthread_mutex_unlock(&ctx->flying_transfers_lock); + return r; } /** \ingroup asyncio @@ -1119,9 +1206,11 @@ API_EXPORTED void libusb_free_transfer(struct libusb_transfer *transfer) */ API_EXPORTED int libusb_submit_transfer(struct libusb_transfer *transfer) { + struct libusb_context *ctx = TRANSFER_CTX(transfer); struct usbi_transfer *itransfer = __LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer); int r; + int first; pthread_mutex_lock(&itransfer->lock); itransfer->transferred = 0; @@ -1132,13 +1221,25 @@ API_EXPORTED int libusb_submit_transfer(struct libusb_transfer *transfer) goto out; } - add_to_flying_list(itransfer); + first = add_to_flying_list(itransfer); r = usbi_backend->submit_transfer(itransfer); if (r) { - pthread_mutex_lock(&TRANSFER_CTX(transfer)->flying_transfers_lock); + pthread_mutex_lock(&ctx->flying_transfers_lock); list_del(&itransfer->list); - pthread_mutex_unlock(&TRANSFER_CTX(transfer)->flying_transfers_lock); + pthread_mutex_unlock(&ctx->flying_transfers_lock); + } +#ifdef USBI_TIMERFD_AVAILABLE + else if (first && usbi_using_timerfd(ctx)) { + /* if this transfer has the lowest timeout of all active transfers, + * rearm the timerfd with this transfer's timeout */ + const struct itimerspec it = { {0, 0}, + { itransfer->timeout.tv_sec, itransfer->timeout.tv_usec * 1000 } }; + usbi_dbg("arm timerfd for timeout in %dms (first in line)", transfer->timeout); + r = timerfd_settime(ctx->timerfd, TFD_TIMER_ABSTIME, &it, NULL); + if (r < 0) + r = LIBUSB_ERROR_OTHER; } +#endif out: pthread_mutex_unlock(&itransfer->lock); @@ -1175,6 +1276,64 @@ API_EXPORTED int libusb_cancel_transfer(struct libusb_transfer *transfer) return r; } +#ifdef USBI_TIMERFD_AVAILABLE +static int disarm_timerfd(struct libusb_context *ctx) +{ + const struct itimerspec disarm_timer = { { 0, 0 }, { 0, 0 } }; + int r; + + usbi_dbg(""); + r = timerfd_settime(ctx->timerfd, 0, &disarm_timer, NULL); + if (r < 0) + return LIBUSB_ERROR_OTHER; + else + return 0; +} + +/* iterates through the flying transfers, and rearms the timerfd based on the + * next upcoming timeout. + * must be called with flying_list locked. + * returns 0 if there was no timeout to arm, 1 if the next timeout was armed, + * or a LIBUSB_ERROR code on failure. + */ +static int arm_timerfd_for_next_timeout(struct libusb_context *ctx) +{ + struct usbi_transfer *transfer; + + list_for_each_entry(transfer, &ctx->flying_transfers, list) { + struct timeval *cur_tv = &transfer->timeout; + + /* if we've reached transfers of infinite timeout, then we have no + * arming to do */ + if (!timerisset(cur_tv)) + return 0; + + /* act on first transfer that is not already cancelled */ + if (!(transfer->flags & USBI_TRANSFER_TIMED_OUT)) { + int r; + const struct itimerspec it = { {0, 0}, + { cur_tv->tv_sec, cur_tv->tv_usec * 1000 } }; + usbi_dbg("next timeout originally %dms", __USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer)->timeout); + r = timerfd_settime(ctx->timerfd, TFD_TIMER_ABSTIME, &it, NULL); + if (r < 0) + return LIBUSB_ERROR_OTHER; + return 1; + } + } + + return 0; +} +#else +static int disarm_timerfd(struct libusb_context *ctx) +{ + return 0; +} +static int arm_timerfd_for_next_timeout(struct libusb_context *ctx) +{ + return 0; +} +#endif + /* Handle completion of a transfer (completion might be an error condition). * This will invoke the user-supplied callback function, which may end up * freeing the transfer. Therefore you cannot use the transfer structure @@ -1183,18 +1342,33 @@ API_EXPORTED int libusb_cancel_transfer(struct libusb_transfer *transfer) * Do not call this function with the usbi_transfer lock held. User-specified * callback functions may attempt to directly resubmit the transfer, which * will attempt to take the lock. */ -void usbi_handle_transfer_completion(struct usbi_transfer *itransfer, +int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, enum libusb_transfer_status status) { struct libusb_transfer *transfer = __USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); struct libusb_context *ctx = TRANSFER_CTX(transfer); uint8_t flags; + int r; + + /* FIXME: could be more intelligent with the timerfd here. we don't need + * to disarm the timerfd if there was no timer running, and we only need + * to rearm the timerfd if the transfer that expired was the one with + * the shortest timeout. */ pthread_mutex_lock(&ctx->flying_transfers_lock); list_del(&itransfer->list); + r = arm_timerfd_for_next_timeout(ctx); pthread_mutex_unlock(&ctx->flying_transfers_lock); + if (r < 0) { + return r; + } else if (r == 0) { + r = disarm_timerfd(ctx); + if (r < 0) + return r; + } + if (status == LIBUSB_TRANSFER_COMPLETED && transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) { int rqlen = transfer->length; @@ -1218,6 +1392,7 @@ void usbi_handle_transfer_completion(struct usbi_transfer *itransfer, pthread_mutex_lock(&ctx->event_waiters_lock); pthread_cond_broadcast(&ctx->event_waiters_cond); pthread_mutex_unlock(&ctx->event_waiters_lock); + return 0; } /* Similar to usbi_handle_transfer_completion() but exclusively for transfers @@ -1226,17 +1401,16 @@ void usbi_handle_transfer_completion(struct usbi_transfer *itransfer, * Do not call this function with the usbi_transfer lock held. User-specified * callback functions may attempt to directly resubmit the transfer, which * will attempt to take the lock. */ -void usbi_handle_transfer_cancellation(struct usbi_transfer *transfer) +int usbi_handle_transfer_cancellation(struct usbi_transfer *transfer) { /* if the URB was cancelled due to timeout, report timeout to the user */ if (transfer->flags & USBI_TRANSFER_TIMED_OUT) { usbi_dbg("detected timeout cancellation"); - usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_TIMED_OUT); - return; + return usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_TIMED_OUT); } /* otherwise its a normal async cancel */ - usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_CANCELLED); + return usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_CANCELLED); } /** \ingroup poll @@ -1498,23 +1672,30 @@ static void handle_timeout(struct usbi_transfer *itransfer) "async cancel failed %d errno=%d", r, errno); } +#ifdef USBI_OS_HANDLES_TIMEOUT +static int handle_timeouts_locked(struct libusb_context *ctx) +{ + return 0; +} static int handle_timeouts(struct libusb_context *ctx) { - int r = 0; -#ifndef USBI_OS_HANDLES_TIMEOUT + return 0; +} +#else +static int handle_timeouts_locked(struct libusb_context *ctx) +{ + int r; struct timespec systime_ts; struct timeval systime; struct usbi_transfer *transfer; - USBI_GET_CONTEXT(ctx); - pthread_mutex_lock(&ctx->flying_transfers_lock); if (list_empty(&ctx->flying_transfers)) - goto out; + return 0; /* get current time */ r = usbi_backend->clock_gettime(USBI_CLOCK_MONOTONIC, &systime_ts); if (r < 0) - goto out; + return r; TIMESPEC_TO_TIMEVAL(&systime, &systime_ts); @@ -1525,7 +1706,7 @@ static int handle_timeouts(struct libusb_context *ctx) /* if we've reached transfers of infinite timeout, we're all done */ if (!timerisset(cur_tv)) - goto out; + return 0; /* ignore timeouts we've already handled */ if (transfer->flags & USBI_TRANSFER_TIMED_OUT) @@ -1535,18 +1716,49 @@ static int handle_timeouts(struct libusb_context *ctx) if ((cur_tv->tv_sec > systime.tv_sec) || (cur_tv->tv_sec == systime.tv_sec && cur_tv->tv_usec > systime.tv_usec)) - goto out; + return 0; /* otherwise, we've got an expired timeout to handle */ handle_timeout(transfer); } + return 0; +} -out: +static int handle_timeouts(struct libusb_context *ctx) +{ + int r; + USBI_GET_CONTEXT(ctx); + pthread_mutex_lock(&ctx->flying_transfers_lock); + r = handle_timeouts_locked(ctx); pthread_mutex_unlock(&ctx->flying_transfers_lock); + return r; +} #endif +#ifdef USBI_TIMERFD_AVAILABLE +static int handle_timerfd_trigger(struct libusb_context *ctx) +{ + int r; + + r = disarm_timerfd(ctx); + if (r < 0) + return r; + + pthread_mutex_lock(&ctx->flying_transfers_lock); + + /* process the timeout that just happened */ + r = handle_timeouts_locked(ctx); + if (r < 0) + goto out; + + /* arm for next timeout*/ + r = arm_timerfd_for_next_timeout(ctx); + +out: + pthread_mutex_unlock(&ctx->flying_transfers_lock); return r; } +#endif /* do the actual event handling. assumes that no other thread is concurrently * doing the same thing. */ @@ -1616,6 +1828,31 @@ static int handle_events(struct libusb_context *ctx, struct timeval *tv) } } +#ifdef USBI_TIMERFD_AVAILABLE + /* on timerfd configurations, fds[1] is the timerfd */ + if (usbi_using_timerfd(ctx) && fds[1].revents) { + /* timerfd indicates that a timeout has expired */ + int ret; + usbi_dbg("timerfd triggered"); + + ret = handle_timerfd_trigger(ctx); + if (ret < 0) { + /* return error code */ + r = ret; + goto handled; + } else if (r == 1) { + /* no more active file descriptors, nothing more to do */ + r = 0; + goto handled; + } else { + /* more events pending... + * prevent OS backend from trying to handle events on timerfd */ + fds[1].revents = 0; + r--; + } + } +#endif + r = usbi_backend->handle_events(ctx, fds, nfds, r); if (r) usbi_err(ctx, "backend handle_events failed with error %d", r); @@ -1768,6 +2005,46 @@ API_EXPORTED int libusb_handle_events_locked(libusb_context *ctx, } /** \ingroup poll + * Determines whether your application must apply special timing considerations + * when monitoring libusb's file descriptors. + * + * This function is only useful for applications which retrieve and poll + * libusb's file descriptors in their own main loop (\ref pollmain). + * + * Ordinarily, libusb's event handler needs to be called into at specific + * moments in time (in addition to times when there is activity on the file + * descriptor set). The usual approach is to use libusb_get_next_timeout() + * to learn about when the next timeout occurs, and to adjust your + * poll()/select() timeout accordingly so that you can make a call into the + * library at that time. + * + * Some platforms supported by libusb do not come with this baggage - any + * events relevant to timing will be represented by activity on the file + * descriptor set, and libusb_get_next_timeout() will always return 0. + * This function allows you to detect whether you are running on such a + * platform. + * + * Since v1.0.5. + * + * \param ctx the context to operate on, or NULL for the default context + * \returns 0 if you must call into libusb at times determined by + * libusb_get_next_timeout(), or 1 if all timeout events are handled internally + * or through regular activity on the file descriptors. + * \see \ref pollmain "Polling libusb file descriptors for event handling" + */ +API_EXPORTED int libusb_pollfds_handle_timeouts(libusb_context *ctx) +{ +#if defined(USBI_OS_HANDLES_TIMEOUT) + return 1; +#elif defined(USBI_TIMERFD_AVAILABLE) + USBI_GET_CONTEXT(ctx); + return usbi_using_timerfd(ctx); +#else + return 0; +#endif +} + +/** \ingroup poll * Determine the next internal timeout that libusb needs to handle. You only * need to use this function if you are calling poll() or select() or similar * on libusb's file descriptors yourself - you do not need to use it if you @@ -1786,6 +2063,9 @@ API_EXPORTED int libusb_handle_events_locked(libusb_context *ctx, * so you should call libusb_handle_events_timeout() or similar immediately. * A return code of 0 indicates that there are no pending timeouts. * + * On some platforms, this function will always returns 0 (no pending + * timeouts). See \ref polltime. + * * \param ctx the context to operate on, or NULL for the default context * \param tv output location for a relative time against the current * clock in which libusb must be called into in order to process timeout events @@ -1804,6 +2084,9 @@ API_EXPORTED int libusb_get_next_timeout(libusb_context *ctx, int found = 0; USBI_GET_CONTEXT(ctx); + if (usbi_using_timerfd(ctx)) + return 0; + pthread_mutex_lock(&ctx->flying_transfers_lock); if (list_empty(&ctx->flying_transfers)) { pthread_mutex_unlock(&ctx->flying_transfers_lock); diff --git a/libusb/libusb.h b/libusb/libusb.h index 1126380..67f248c 100644 --- a/libusb/libusb.h +++ b/libusb/libusb.h @@ -1182,6 +1182,7 @@ int libusb_wait_for_event(libusb_context *ctx, struct timeval *tv); int libusb_handle_events_timeout(libusb_context *ctx, struct timeval *tv); int libusb_handle_events(libusb_context *ctx); int libusb_handle_events_locked(libusb_context *ctx, struct timeval *tv); +int libusb_pollfds_handle_timeouts(libusb_context *ctx); int libusb_get_next_timeout(libusb_context *ctx, struct timeval *tv); /** \ingroup poll diff --git a/libusb/libusbi.h b/libusb/libusbi.h index 6e9e2e0..10a4994 100644 --- a/libusb/libusbi.h +++ b/libusb/libusbi.h @@ -1,6 +1,6 @@ /* * Internal header for libusb - * Copyright (C) 2007-2008 Daniel Drake <dsd@gentoo.org> + * Copyright (C) 2007-2009 Daniel Drake <dsd@gentoo.org> * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> * * This library is free software; you can redistribute it and/or @@ -187,8 +187,20 @@ struct libusb_context { * event handling */ pthread_mutex_t event_waiters_lock; pthread_cond_t event_waiters_cond; + +#ifdef USBI_TIMERFD_AVAILABLE + /* used for timeout handling, if supported by OS. + * this timerfd is maintained to trigger on the next pending timeout */ + int timerfd; +#endif }; +#ifdef USBI_TIMERFD_AVAILABLE +#define usbi_using_timerfd(ctx) ((ctx)->timerfd >= 0) +#else +#define usbi_using_timerfd(ctx) (0) +#endif + struct libusb_device { /* lock protects refcnt, everything else is finalized at initialization * time */ @@ -288,9 +300,9 @@ struct libusb_device *usbi_get_device_by_session_id(struct libusb_context *ctx, int usbi_sanitize_device(struct libusb_device *dev); void usbi_handle_disconnect(struct libusb_device_handle *handle); -void usbi_handle_transfer_completion(struct usbi_transfer *itransfer, +int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, enum libusb_transfer_status status); -void usbi_handle_transfer_cancellation(struct usbi_transfer *transfer); +int usbi_handle_transfer_cancellation(struct usbi_transfer *transfer); int usbi_parse_descriptor(unsigned char *source, char *descriptor, void *dest, int host_endian); @@ -761,6 +773,11 @@ struct usbi_os_backend { */ int (*clock_gettime)(int clkid, struct timespec *tp); +#ifdef USBI_TIMERFD_AVAILABLE + /* clock ID of the clock that should be used for timerfd */ + clockid_t (*get_timerfd_clockid)(void); +#endif + /* Number of bytes to reserve for per-device private backend data. * This private data area is accessible through the "os_priv" field of * struct libusb_device. */ diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c index cf30703..ffa4088 100644 --- a/libusb/os/linux_usbfs.c +++ b/libusb/os/linux_usbfs.c @@ -187,36 +187,22 @@ static const char *find_usbfs_path(void) return ret; } +/* the monotonic clock is not usable on all systems (e.g. embedded ones often + * seem to lack it). fall back to REALTIME if we have to. */ static clockid_t find_monotonic_clock(void) { struct timespec ts; - int i; - const clockid_t clktypes[] = { - /* the most monotonic clock I know about, but only available since - * Linux 2.6.28, and not even available in glibc-2.10 */ -#ifndef CLOCK_MONOTONIC_RAW -#define CLOCK_MONOTONIC_RAW 4 -#endif - CLOCK_MONOTONIC_RAW, - - /* a monotonic clock, but it's not available on all architectures, - * and is susceptible to ntp adjustments */ - CLOCK_MONOTONIC, - - /* the fallback option */ - CLOCK_REALTIME, - }; + int r; - for (i = 0; i < (sizeof(clktypes) / sizeof(*clktypes)); i++) { - int r = clock_gettime(clktypes[i], &ts); - if (r == 0) { - usbi_dbg("clock %d selected", clktypes[i]); - return r; - } - usbi_dbg("clock %d doesn't work", clktypes[i]); + /* Linux 2.6.28 adds CLOCK_MONOTONIC_RAW but we don't use it + * because it's not available through timerfd */ + r = clock_gettime(CLOCK_MONOTONIC, &ts); + if (r == 0) { + return CLOCK_MONOTONIC; + } else { + usbi_dbg("monotonic clock doesn't work, errno %d", errno); + return CLOCK_REALTIME; } - - return -1; } /* bulk continuation URB flag available from Linux 2.6.32 */ @@ -247,13 +233,8 @@ static int op_init(struct libusb_context *ctx) return LIBUSB_ERROR_OTHER; } - if (monotonic_clkid == -1) { + if (monotonic_clkid == -1) monotonic_clkid = find_monotonic_clock(); - if (monotonic_clkid == -1) { - usbi_err(ctx, "could not find working monotonic clock"); - return LIBUSB_ERROR_OTHER; - } - } if (supports_flag_bulk_continuation == -1) { supports_flag_bulk_continuation = check_flag_bulk_continuation(); @@ -1792,6 +1773,7 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, int num_urbs = tpriv->num_urbs; int urb_idx = urb - tpriv->urbs; enum libusb_transfer_status status = LIBUSB_TRANSFER_COMPLETED; + int r = 0; pthread_mutex_lock(&itransfer->lock); usbi_dbg("handling completion status %d of bulk urb %d/%d", urb->status, @@ -1838,7 +1820,7 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, free(tpriv->urbs); tpriv->urbs = NULL; pthread_mutex_unlock(&itransfer->lock); - usbi_handle_transfer_cancellation(itransfer); + r = usbi_handle_transfer_cancellation(itransfer); goto out_unlock; } if (tpriv->reap_action != COMPLETED_EARLY) @@ -1902,8 +1884,8 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer, * cancelled by the kernel */ if (tpriv->urbs[i].flags & USBFS_URB_BULK_CONTINUATION) continue; - int r = ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, &tpriv->urbs[i]); - if (r && errno != EINVAL) + int tmp = ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, &tpriv->urbs[i]); + if (tmp && errno != EINVAL) usbi_warn(TRANSFER_CTX(transfer), "unrecognised discard errno %d", errno); } @@ -1916,11 +1898,10 @@ completed: free(tpriv->urbs); tpriv->urbs = NULL; pthread_mutex_unlock(&itransfer->lock); - usbi_handle_transfer_completion(itransfer, status); - return 0; + return usbi_handle_transfer_completion(itransfer, status); out_unlock: pthread_mutex_unlock(&itransfer->lock); - return 0; + return r; } static int handle_iso_completion(struct usbi_transfer *itransfer, @@ -1971,13 +1952,12 @@ static int handle_iso_completion(struct usbi_transfer *itransfer, free_iso_urbs(tpriv); if (tpriv->reap_action == CANCELLED) { pthread_mutex_unlock(&itransfer->lock); - usbi_handle_transfer_cancellation(itransfer); + return usbi_handle_transfer_cancellation(itransfer); } else { pthread_mutex_unlock(&itransfer->lock); - usbi_handle_transfer_completion(itransfer, + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_ERROR); } - return 0; } goto out; } @@ -2002,8 +1982,7 @@ static int handle_iso_completion(struct usbi_transfer *itransfer, usbi_dbg("last URB in transfer --> complete!"); free_iso_urbs(tpriv); pthread_mutex_unlock(&itransfer->lock); - usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED); - return 0; + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED); } out: @@ -2030,8 +2009,7 @@ static int handle_control_completion(struct usbi_transfer *itransfer, free(tpriv->urbs); tpriv->urbs = NULL; pthread_mutex_unlock(&itransfer->lock); - usbi_handle_transfer_cancellation(itransfer); - return 0; + return usbi_handle_transfer_cancellation(itransfer); } switch (urb->status) { @@ -2059,8 +2037,7 @@ static int handle_control_completion(struct usbi_transfer *itransfer, free(tpriv->urbs); tpriv->urbs = NULL; pthread_mutex_unlock(&itransfer->lock); - usbi_handle_transfer_completion(itransfer, status); - return 0; + return usbi_handle_transfer_completion(itransfer, status); } static int reap_for_handle(struct libusb_device_handle *handle) @@ -2157,6 +2134,14 @@ static int op_clock_gettime(int clk_id, struct timespec *tp) } } +#ifdef USBI_TIMERFD_AVAILABLE +static clockid_t op_get_timerfd_clockid(void) +{ + return monotonic_clkid; + +} +#endif + const struct usbi_os_backend linux_usbfs_backend = { .name = "Linux usbfs", .init = op_init, @@ -2191,6 +2176,10 @@ const struct usbi_os_backend linux_usbfs_backend = { .clock_gettime = op_clock_gettime, +#ifdef USBI_TIMERFD_AVAILABLE + .get_timerfd_clockid = op_get_timerfd_clockid, +#endif + .device_priv_size = sizeof(struct linux_device_priv), .device_handle_priv_size = sizeof(struct linux_device_handle_priv), .transfer_priv_size = sizeof(struct linux_transfer_priv), |