itransfer->num_iso_packets = iso_packets;
usbi_mutex_init(&itransfer->lock);
- usbi_mutex_init(&itransfer->flags_lock);
transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
usbi_dbg("transfer %p", transfer);
return transfer;
itransfer = LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
usbi_mutex_destroy(&itransfer->lock);
- usbi_mutex_destroy(&itransfer->flags_lock);
free(itransfer);
}
if (!timerisset(cur_tv))
goto disarm;
- /* act on first transfer that is not already cancelled */
- if (!(transfer->flags & USBI_TRANSFER_TIMEOUT_HANDLED)) {
+ /* act on first transfer that has not already been handled */
+ if (!(transfer->timeout_flags & USBI_TRANSFER_TIMEOUT_HANDLED)) {
int r;
const struct itimerspec it = { {0, 0},
{ cur_tv->tv_sec, cur_tv->tv_usec * 1000 } };
int r = 0;
int first = 1;
- usbi_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 (r)
list_del(&transfer->list);
- usbi_mutex_unlock(&ctx->flying_transfers_lock);
return r;
}
{
struct usbi_transfer *itransfer =
LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
- int remove = 0;
+ struct libusb_context *ctx = TRANSFER_CTX(transfer);
int r;
usbi_dbg("transfer %p", transfer);
+
+ /*
+ * Important note on locking, this function takes / releases locks
+ * in the following order:
+ * take flying_transfers_lock
+ * take itransfer->lock
+ * clear transfer
+ * add to flying_transfers list
+ * release flying_transfers_lock
+ * submit transfer
+ * release itransfer->lock
+ * if submit failed:
+ * take flying_transfers_lock
+ * remove from flying_transfers list
+ * release flying_transfers_lock
+ *
+ * Note that it takes locks in the order a-b and then releases them
+ * in the same order a-b. This is somewhat unusual but not wrong,
+ * release order is not important as long as *all* locks are released
+ * before re-acquiring any locks.
+ *
+ * This means that the ordering of first releasing itransfer->lock
+ * and then re-acquiring the flying_transfers_list on error is
+ * important and must not be changed!
+ *
+ * This is done this way because when we take both locks we must always
+ * take flying_transfers_lock first to avoid ab-ba style deadlocks with
+ * the timeout handling and usbi_handle_disconnect paths.
+ *
+ * And we cannot release itransfer->lock before the submission is
+ * complete otherwise timeout handling for transfers with short
+ * timeouts may run before submission.
+ */
+ usbi_mutex_lock(&ctx->flying_transfers_lock);
usbi_mutex_lock(&itransfer->lock);
- usbi_mutex_lock(&itransfer->flags_lock);
- if (itransfer->flags & USBI_TRANSFER_IN_FLIGHT) {
- r = LIBUSB_ERROR_BUSY;
- goto out;
+ if (itransfer->state_flags & USBI_TRANSFER_IN_FLIGHT) {
+ usbi_mutex_unlock(&ctx->flying_transfers_lock);
+ usbi_mutex_unlock(&itransfer->lock);
+ return LIBUSB_ERROR_BUSY;
}
itransfer->transferred = 0;
- itransfer->flags = 0;
+ itransfer->state_flags = 0;
+ itransfer->timeout_flags = 0;
r = calculate_timeout(itransfer);
if (r < 0) {
- r = LIBUSB_ERROR_OTHER;
- goto out;
+ usbi_mutex_unlock(&ctx->flying_transfers_lock);
+ usbi_mutex_unlock(&itransfer->lock);
+ return LIBUSB_ERROR_OTHER;
}
- itransfer->flags |= USBI_TRANSFER_SUBMITTING;
- usbi_mutex_unlock(&itransfer->flags_lock);
r = add_to_flying_list(itransfer);
if (r) {
- usbi_mutex_lock(&itransfer->flags_lock);
- itransfer->flags = 0;
- goto out;
+ usbi_mutex_unlock(&ctx->flying_transfers_lock);
+ usbi_mutex_unlock(&itransfer->lock);
+ return r;
}
+ usbi_mutex_unlock(&ctx->flying_transfers_lock);
- /* keep a reference to this device */
- libusb_ref_device(transfer->dev_handle->dev);
r = usbi_backend->submit_transfer(itransfer);
-
- usbi_mutex_lock(&itransfer->flags_lock);
- itransfer->flags &= ~USBI_TRANSFER_SUBMITTING;
if (r == LIBUSB_SUCCESS) {
- /* check for two possible special conditions:
- * 1) device disconnect occurred immediately after submission
- * 2) transfer completed before we got here to update the flags
- */
- if (itransfer->flags & USBI_TRANSFER_DEVICE_DISAPPEARED) {
- usbi_backend->clear_transfer_priv(itransfer);
- remove = 1;
- r = LIBUSB_ERROR_NO_DEVICE;
- }
- else if (!(itransfer->flags & USBI_TRANSFER_COMPLETED)) {
- itransfer->flags |= USBI_TRANSFER_IN_FLIGHT;
- }
- } else {
- remove = 1;
- }
-out:
- usbi_mutex_unlock(&itransfer->flags_lock);
- if (remove) {
- libusb_unref_device(transfer->dev_handle->dev);
- remove_from_flying_list(itransfer);
+ itransfer->state_flags |= USBI_TRANSFER_IN_FLIGHT;
+ /* keep a reference to this device */
+ libusb_ref_device(transfer->dev_handle->dev);
}
usbi_mutex_unlock(&itransfer->lock);
+
+ if (r != LIBUSB_SUCCESS)
+ remove_from_flying_list(itransfer);
+
return r;
}
usbi_dbg("transfer %p", transfer );
usbi_mutex_lock(&itransfer->lock);
- usbi_mutex_lock(&itransfer->flags_lock);
- if (!(itransfer->flags & USBI_TRANSFER_IN_FLIGHT)
- || (itransfer->flags & USBI_TRANSFER_CANCELLING)) {
+ if (!(itransfer->state_flags & USBI_TRANSFER_IN_FLIGHT)
+ || (itransfer->state_flags & USBI_TRANSFER_CANCELLING)) {
r = LIBUSB_ERROR_NOT_FOUND;
goto out;
}
usbi_dbg("cancel transfer failed error %d", r);
if (r == LIBUSB_ERROR_NO_DEVICE)
- itransfer->flags |= USBI_TRANSFER_DEVICE_DISAPPEARED;
+ itransfer->state_flags |= USBI_TRANSFER_DEVICE_DISAPPEARED;
}
- itransfer->flags |= USBI_TRANSFER_CANCELLING;
+ itransfer->state_flags |= USBI_TRANSFER_CANCELLING;
out:
- usbi_mutex_unlock(&itransfer->flags_lock);
usbi_mutex_unlock(&itransfer->lock);
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;
- itransfer->flags |= USBI_TRANSFER_COMPLETED;
- usbi_mutex_unlock(&itransfer->flags_lock);
+ usbi_mutex_lock(&itransfer->lock);
+ itransfer->state_flags &= ~USBI_TRANSFER_IN_FLIGHT;
+ usbi_mutex_unlock(&itransfer->lock);
if (status == LIBUSB_TRANSFER_COMPLETED
&& transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) {
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) {
+ if (transfer->timeout_flags & USBI_TRANSFER_TIMED_OUT) {
usbi_dbg("detected timeout cancellation");
return usbi_handle_transfer_completion(transfer, LIBUSB_TRANSFER_TIMED_OUT);
}
USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
int r;
- itransfer->flags |= USBI_TRANSFER_TIMEOUT_HANDLED;
+ itransfer->timeout_flags |= USBI_TRANSFER_TIMEOUT_HANDLED;
r = libusb_cancel_transfer(transfer);
if (r == 0)
- itransfer->flags |= USBI_TRANSFER_TIMED_OUT;
+ itransfer->timeout_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_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))
+ if (transfer->timeout_flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))
continue;
/* if transfer has non-expired timeout, nothing more to do */
/* 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_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))
+ if (transfer->timeout_flags & (USBI_TRANSFER_TIMEOUT_HANDLED | USBI_TRANSFER_OS_HANDLES_TIMEOUT))
continue;
/* if we've reached transfers of infinte timeout, we're done looking */
* possible scenarios:
* 1. the transfer is currently in-flight, in which case we terminate the
* transfer here
- * 2. the transfer is not in-flight (or is but hasn't been marked as such),
- * in which case we record that the device disappeared and this will be
- * handled by libusb_submit_transfer()
+ * 2. the transfer has been added to the flying transfer list by
+ * libusb_submit_transfer, has failed to submit and
+ * libusb_submit_transfer is waiting for us to release the
+ * flying_transfers_lock to remove it, so we ignore it
*/
while (1) {
usbi_mutex_lock(&HANDLE_CTX(dev_handle)->flying_transfers_lock);
list_for_each_entry(cur, &HANDLE_CTX(dev_handle)->flying_transfers, list, struct usbi_transfer)
if (USBI_TRANSFER_TO_LIBUSB_TRANSFER(cur)->dev_handle == dev_handle) {
- usbi_mutex_lock(&cur->flags_lock);
- if (cur->flags & USBI_TRANSFER_IN_FLIGHT)
+ usbi_mutex_lock(&cur->lock);
+ if (cur->state_flags & USBI_TRANSFER_IN_FLIGHT)
to_cancel = cur;
- else
- cur->flags |= USBI_TRANSFER_DEVICE_DISAPPEARED;
- usbi_mutex_unlock(&cur->flags_lock);
+ usbi_mutex_unlock(&cur->lock);
if (to_cancel)
break;
* the list, URBs that will time out later are placed after, and urbs with
* infinite timeout are always placed at the very end. */
struct list_head flying_transfers;
+ /* Note paths taking both this and usbi_transfer->lock must always
+ * take this lock first */
usbi_mutex_t flying_transfers_lock;
/* user callbacks for pollfd changes */
struct timeval timeout;
int transferred;
uint32_t stream_id;
- uint8_t flags;
+ uint8_t state_flags; /* Protected by usbi_transfer->lock */
+ uint8_t timeout_flags; /* Protected by the flying_stransfers_lock */
/* this lock is held during libusb_submit_transfer() and
* libusb_cancel_transfer() (allowing the OS backend to prevent duplicate
* should also take this lock in the handle_events path, to prevent the user
* cancelling the transfer from another thread while you are processing
* its completion (presumably there would be races within your OS backend
- * if this were possible). */
+ * if this were possible).
+ * Note paths taking both this and the flying_transfers_lock must
+ * always take the flying_transfers_lock first */
usbi_mutex_t lock;
-
- /* this lock should be held whenever viewing or modifying flags
- * relating to the transfer state */
- usbi_mutex_t flags_lock;
};
-enum usbi_transfer_flags {
- /* The transfer has timed out */
- USBI_TRANSFER_TIMED_OUT = 1 << 0,
-
- /* Set by backend submit_transfer() if the OS handles timeout */
- USBI_TRANSFER_OS_HANDLES_TIMEOUT = 1 << 1,
+enum usbi_transfer_state_flags {
+ /* Transfer successfully submitted by backend */
+ USBI_TRANSFER_IN_FLIGHT = 1 << 0,
/* Cancellation was requested via libusb_cancel_transfer() */
- USBI_TRANSFER_CANCELLING = 1 << 2,
+ USBI_TRANSFER_CANCELLING = 1 << 1,
/* Operation on the transfer failed because the device disappeared */
- USBI_TRANSFER_DEVICE_DISAPPEARED = 1 << 3,
-
- /* Transfer is currently being submitted */
- USBI_TRANSFER_SUBMITTING = 1 << 4,
-
- /* Transfer successfully submitted by backend */
- USBI_TRANSFER_IN_FLIGHT = 1 << 5,
+ USBI_TRANSFER_DEVICE_DISAPPEARED = 1 << 2,
+};
- /* Completion handler has run */
- USBI_TRANSFER_COMPLETED = 1 << 6,
+enum usbi_transfer_timeout_flags {
+ /* Set by backend submit_transfer() if the OS handles timeout */
+ USBI_TRANSFER_OS_HANDLES_TIMEOUT = 1 << 0,
/* The transfer timeout has been handled */
- USBI_TRANSFER_TIMEOUT_HANDLED = 1 << 7,
+ USBI_TRANSFER_TIMEOUT_HANDLED = 1 << 1,
+
+ /* The transfer timeout was successfully processed */
+ USBI_TRANSFER_TIMED_OUT = 1 << 2,
};
#define USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer) \