void
usbd_abort_xfer(struct usbd_xfer *xfer, usbd_status status)
{
	struct usbd_pipe *pipe = xfer->ux_pipe;
	struct usbd_device *dev = pipe->up_dev;
	struct usbd_bus *bus = dev->ud_bus;

	KASSERTMSG((status == USBD_CANCELLED || status == USBD_TIMEOUT),
	    "invalid status for abort: %d", (int)status);

	KASSERT(mutex_owned(bus->ub_lock));
	ASSERT_SLEEPABLE();

	if (status == USBD_CANCELLED) {
		/*
		 * If we are synchronously aborting, wait for the
		 * callout and the task to complete.  While we are
		 * wating, they may beat us to aborting the xfer.
		 */
		callout_halt(&xfer->ux_callout, bus->ub_lock);
		usb_rem_task_wait(dev, &xfer->ux_aborttask, USB_TASKQ_HC,
		    bus->ub_lock);

		/*
		 * The xfer cannot have been cancelled already.  It is
		 * the caller's responsibility of usbd_abort_pipe not
		 * to try to abort a pipe multiple times concurrently.
		 */
		KASSERT(xfer->ux_status != USBD_CANCELLED);

		/* If the timeout beat us, we're done.  */
		if (xfer->ux_status == USBD_TIMEOUT)
			return;
	} else {
		/* Otherwise, we are timing out.  */
		KASSERT(status == USBD_TIMEOUT);

		/* Nobody else can time it out.  */
		KASSERT(xfer->ux_status != USBD_TIMEOUT);

		/*
		 * If it was synchronously aborted before we timed out,
		 * we're done.
		 */
		if (xfer->ux_status == USBD_CANCELLED)
			return;
	}

	/*
	 * We are the first to abort, whether due to a timeout or
	 * otherwise.  Set the status.
	 */
	KASSERT(xfer->ux_status != USBD_CANCELLED);
	KASSERT(xfer->ux_status != USBD_TIMEOUT);
	xfer->ux_status = status;

	/*
	 * If we're dying, skip the hardware action and just notify the
	 * software that we're done.
	 */
	if (sc->sc_dying)
		goto out;

	/*
	 * Take whatever HCI-specific actions are necessary to notify
	 * the hardware that we're not interested any more.
	 */
	(*pipe->up_methods->upm_abort)(xfer);

out:
	/*
	 * We are done -- the transfer is complete, cancelled or timed
	 * out, so notify any waiters, call any callbacks, and move the
	 * pipe on to the next transfer.
	 */
	usb_transfer_complete(xfer);
	KASSERT(mutex_owned(bus->ub_lock));
}