/* 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.
+ * returns 0 on success or a LIBUSB_ERROR code on failure.
*/
static int arm_timerfd_for_next_timeout(struct libusb_context *ctx)
{
goto disarm;
/* act on first transfer that is not already cancelled */
- if (!(transfer->flags & USBI_TRANSFER_TIMED_OUT)) {
+ if (!(transfer->flags & USBI_TRANSFER_TIMEOUT_HANDLED)) {
int r;
const struct itimerspec it = { {0, 0},
{ cur_tv->tv_sec, cur_tv->tv_usec * 1000 } };
r = timerfd_settime(ctx->timerfd, TFD_TIMER_ABSTIME, &it, NULL);
if (r < 0)
return LIBUSB_ERROR_OTHER;
- return 1;
+ return 0;
}
}
/* remove a transfer from the active transfers list.
* This function will *always* remove the transfer from the
- * flying_transfers list. It will return non 0 if it fails to
- * update the timer for the next timeout. */
+ * flying_transfers list. It will return a LIBUSB_ERROR code
+ * if it fails to update the timer for the next timeout. */
static int remove_from_flying_list(struct usbi_transfer *transfer)
{
struct libusb_context *ctx = ITRANSFER_CTX(transfer);
+ int rearm_timerfd;
int r = 0;
- /* 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. */
-
usbi_mutex_lock(&ctx->flying_transfers_lock);
+ rearm_timerfd = (timerisset(&transfer->timeout) &&
+ list_first_entry(&ctx->flying_transfers, struct usbi_transfer, list) == transfer);
list_del(&transfer->list);
- if (usbi_using_timerfd(ctx))
+ if (usbi_using_timerfd(ctx) && rearm_timerfd)
r = arm_timerfd_for_next_timeout(ctx);
usbi_mutex_unlock(&ctx->flying_transfers_lock);
int r;
r = remove_from_flying_list(itransfer);
- if (r)
- return r;
+ if (r < 0)
+ usbi_err(ITRANSFER_CTX(itransfer), "failed to set timer for next timeout, errno=%d", errno);
usbi_mutex_lock(&itransfer->flags_lock);
itransfer->flags &= ~USBI_TRANSFER_IN_FLIGHT;
if (flags & LIBUSB_TRANSFER_FREE_TRANSFER)
libusb_free_transfer(transfer);
libusb_unref_device(handle->dev);
- return 0;
+ return r;
}
/* Similar to usbi_handle_transfer_completion() but exclusively for transfers
USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
int r;
- itransfer->flags |= USBI_TRANSFER_TIMED_OUT;
+ itransfer->flags |= USBI_TRANSFER_TIMEOUT_HANDLED;
r = libusb_cancel_transfer(transfer);
- if (r < 0)
+ if (r == 0)
+ itransfer->flags |= USBI_TRANSFER_TIMED_OUT;
+ else
usbi_warn(TRANSFER_CTX(transfer),
"async cancel failed %d errno=%d", r, errno);
}
return 0;
/* ignore timeouts we've already handled */
- if (transfer->flags & (USBI_TRANSFER_TIMED_OUT | USBI_TRANSFER_OS_HANDLES_TIMEOUT))
+ if (transfer->flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))
continue;
/* if transfer has non-expired timeout, nothing more to do */
struct usbi_transfer *transfer;
struct timespec cur_ts;
struct timeval cur_tv;
- struct timeval *next_timeout;
+ struct timeval next_timeout = { 0, 0 };
int r;
- int found = 0;
USBI_GET_CONTEXT(ctx);
if (usbi_using_timerfd(ctx))
/* find next transfer which hasn't already been processed as timed out */
list_for_each_entry(transfer, &ctx->flying_transfers, list, struct usbi_transfer) {
- if (transfer->flags & (USBI_TRANSFER_TIMED_OUT | USBI_TRANSFER_OS_HANDLES_TIMEOUT))
+ if (transfer->flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))
continue;
- /* no timeout for this transfer? */
+ /* if we've reached transfers of infinte timeout, we're done looking */
if (!timerisset(&transfer->timeout))
- continue;
+ break;
- found = 1;
+ next_timeout = transfer->timeout;
break;
}
usbi_mutex_unlock(&ctx->flying_transfers_lock);
- if (!found) {
+ if (!timerisset(&next_timeout)) {
usbi_dbg("no URB with timeout or all handled by OS; no timeout!");
return 0;
}
- next_timeout = &transfer->timeout;
-
r = usbi_backend->clock_gettime(USBI_CLOCK_MONOTONIC, &cur_ts);
if (r < 0) {
usbi_err(ctx, "failed to read monotonic clock, errno=%d", errno);
}
TIMESPEC_TO_TIMEVAL(&cur_tv, &cur_ts);
- if (!timercmp(&cur_tv, next_timeout, <)) {
+ if (!timercmp(&cur_tv, &next_timeout, <)) {
usbi_dbg("first timeout already expired");
timerclear(tv);
} else {
- timersub(next_timeout, &cur_tv, tv);
+ timersub(&next_timeout, &cur_tv, tv);
usbi_dbg("next timeout in %d.%06ds", tv->tv_sec, tv->tv_usec);
}
* Retrieve a list of file descriptors that should be polled by your main loop
* as libusb event sources.
*
- * The returned list is NULL-terminated and should be freed with free() when
- * done. The actual list contents must not be touched.
+ * The returned list is NULL-terminated and should be freed with libusb_free_pollfds()
+ * when done. The actual list contents must not be touched.
*
* As file descriptors are a Unix-specific concept, this function is not
* available on Windows and will always return NULL.
#endif
}
+/** \ingroup poll
+ * Free a list of libusb_pollfd structures. This should be called for all
+ * pollfd lists allocated with libusb_get_pollfds().
+ *
+ * Since version 1.0.20, \ref LIBUSB_API_VERSION >= 0x01000104
+ *
+ * It is legal to call this function with a NULL pollfd list. In this case,
+ * the function will simply return safely.
+ *
+ * \param pollfds the list of libusb_pollfd structures to free
+ */
+void API_EXPORTED libusb_free_pollfds(const struct libusb_pollfd **pollfds)
+{
+ if (!pollfds)
+ return;
+
+ free((void *)pollfds);
+}
+
/* Backends may call this from handle_events to report disconnection of a
* device. This function ensures transfers get cancelled appropriately.
* Callers of this function must hold the events_lock.