*/
static void uhci_fsbr_on(struct uhci_hcd *uhci)
{
- struct uhci_qh *fsbr_qh, *lqh, *tqh;
+ struct uhci_qh *lqh;
+ /* The terminating skeleton QH always points back to the first
+ * FSBR QH. Make the last async QH point to the terminating
+ * skeleton QH. */
uhci->fsbr_is_on = 1;
lqh = list_entry(uhci->skel_async_qh->node.prev,
struct uhci_qh, node);
-
- /* Find the first FSBR QH. Linear search through the list is
- * acceptable because normally FSBR gets turned on as soon as
- * one QH needs it. */
- fsbr_qh = NULL;
- list_for_each_entry_reverse(tqh, &uhci->skel_async_qh->node, node) {
- if (tqh->skel < SKEL_FSBR)
- break;
- fsbr_qh = tqh;
- }
-
- /* No FSBR QH means we must insert the terminating skeleton QH */
- if (!fsbr_qh) {
- uhci->skel_term_qh->link = LINK_TO_QH(uhci->skel_term_qh);
- wmb();
- lqh->link = uhci->skel_term_qh->link;
-
- /* Otherwise loop the last QH to the first FSBR QH */
- } else
- lqh->link = LINK_TO_QH(fsbr_qh);
+ lqh->link = LINK_TO_QH(uhci->skel_term_qh);
}
static void uhci_fsbr_off(struct uhci_hcd *uhci)
{
struct uhci_qh *lqh;
+ /* Remove the link from the last async QH to the terminating
+ * skeleton QH. */
uhci->fsbr_is_on = 0;
lqh = list_entry(uhci->skel_async_qh->node.prev,
struct uhci_qh, node);
-
- /* End the async list normally and unlink the terminating QH */
- lqh->link = uhci->skel_term_qh->link = UHCI_PTR_TERM;
+ lqh->link = UHCI_PTR_TERM;
}
static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb)
static void uhci_free_td(struct uhci_hcd *uhci, struct uhci_td *td)
{
- if (!list_empty(&td->list))
+ if (!list_empty(&td->list)) {
dev_warn(uhci_dev(uhci), "td %p still in list!\n", td);
- if (!list_empty(&td->fl_list))
+ WARN_ON(1);
+ }
+ if (!list_empty(&td->fl_list)) {
dev_warn(uhci_dev(uhci), "td %p still in fl_list!\n", td);
+ WARN_ON(1);
+ }
dma_pool_free(uhci->td_pool, td, td->dma_handle);
}
static void uhci_free_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
WARN_ON(qh->state != QH_STATE_IDLE && qh->udev);
- if (!list_empty(&qh->queue))
+ if (!list_empty(&qh->queue)) {
dev_warn(uhci_dev(uhci), "qh %p list not empty!\n", qh);
+ WARN_ON(1);
+ }
list_del(&qh->node);
if (qh->udev) {
*/
static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
- struct uhci_qh *pqh, *lqh;
+ struct uhci_qh *pqh;
__le32 link_to_new_qh;
- __le32 *extra_link = &link_to_new_qh;
/* Find the predecessor QH for our new one and insert it in the list.
* The list of QHs is expected to be short, so linear search won't
break;
}
list_add(&qh->node, &pqh->node);
- qh->link = pqh->link;
-
- link_to_new_qh = LINK_TO_QH(qh);
-
- /* If this is now the first FSBR QH, take special action */
- if (uhci->fsbr_is_on && pqh->skel < SKEL_FSBR &&
- qh->skel >= SKEL_FSBR) {
- lqh = list_entry(uhci->skel_async_qh->node.prev,
- struct uhci_qh, node);
-
- /* If the new QH is also the last one, we must unlink
- * the terminating skeleton QH and make the new QH point
- * back to itself. */
- if (qh == lqh) {
- qh->link = link_to_new_qh;
- extra_link = &uhci->skel_term_qh->link;
-
- /* Otherwise the last QH must point to the new QH */
- } else
- extra_link = &lqh->link;
- }
/* Link it into the schedule */
+ qh->link = pqh->link;
wmb();
- *extra_link = pqh->link = link_to_new_qh;
+ link_to_new_qh = LINK_TO_QH(qh);
+ pqh->link = link_to_new_qh;
+
+ /* If this is now the first FSBR QH, link the terminating skeleton
+ * QH to it. */
+ if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR)
+ uhci->skel_term_qh->link = link_to_new_qh;
}
/*
*/
static void unlink_async(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
- struct uhci_qh *pqh, *lqh;
+ struct uhci_qh *pqh;
__le32 link_to_next_qh = qh->link;
pqh = list_entry(qh->node.prev, struct uhci_qh, node);
-
- /* If this is the first FSBQ QH, take special action */
- if (uhci->fsbr_is_on && pqh->skel < SKEL_FSBR &&
- qh->skel >= SKEL_FSBR) {
- lqh = list_entry(uhci->skel_async_qh->node.prev,
- struct uhci_qh, node);
-
- /* If this QH is also the last one, we must link in
- * the terminating skeleton QH. */
- if (qh == lqh) {
- link_to_next_qh = LINK_TO_QH(uhci->skel_term_qh);
- uhci->skel_term_qh->link = link_to_next_qh;
- wmb();
- qh->link = link_to_next_qh;
-
- /* Otherwise the last QH must point to the new first FSBR QH */
- } else
- lqh->link = link_to_next_qh;
- }
-
pqh->link = link_to_next_qh;
+
+ /* If this was the old first FSBR QH, link the terminating skeleton
+ * QH to the next (new first FSBR) QH. */
+ if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR)
+ uhci->skel_term_qh->link = link_to_next_qh;
mb();
}
{
struct uhci_td *td, *tmp;
- if (!list_empty(&urbp->node))
+ if (!list_empty(&urbp->node)) {
dev_warn(uhci_dev(uhci), "urb %p still on QH's list!\n",
urbp->urb);
+ WARN_ON(1);
+ }
list_for_each_entry_safe(td, tmp, &urbp->td_list, list) {
uhci_remove_td_from_urbp(td);
uhci_free_td(uhci, td);
}
- urbp->urb->hcpriv = NULL;
kmem_cache_free(uhci_up_cachep, urbp);
}
* If direction is "send", change the packet ID from SETUP (0x2D)
* to OUT (0xE1). Else change it from SETUP to IN (0x69) and
* set Short Packet Detect (SPD) for all data packets.
+ *
+ * 0-length transfers always get treated as "send".
*/
- if (usb_pipeout(urb->pipe))
+ if (usb_pipeout(urb->pipe) || len == 0)
destination ^= (USB_PID_SETUP ^ USB_PID_OUT);
else {
destination ^= (USB_PID_SETUP ^ USB_PID_IN);
* Build the DATA TDs
*/
while (len > 0) {
- int pktsze = min(len, maxsze);
+ int pktsze = maxsze;
+
+ if (len <= pktsze) { /* The last data packet */
+ pktsze = len;
+ status &= ~TD_CTRL_SPD;
+ }
td = uhci_alloc_td(uhci);
if (!td)
goto nomem;
*plink = LINK_TO_TD(td);
- /*
- * It's IN if the pipe is an output pipe or we're not expecting
- * data back.
- */
- destination &= ~TD_TOKEN_PID_MASK;
- if (usb_pipeout(urb->pipe) || !urb->transfer_buffer_length)
- destination |= USB_PID_IN;
- else
- destination |= USB_PID_OUT;
-
+ /* Change direction for the status transaction */
+ destination ^= (USB_PID_IN ^ USB_PID_OUT);
destination |= TD_TOKEN_TOGGLE; /* End in Data1 */
- status &= ~TD_CTRL_SPD;
-
uhci_add_td_to_urbp(td, urbp);
uhci_fill_td(td, status | TD_CTRL_IOC,
destination | uhci_explen(0), 0);
if (debug > 1 && errbuf) {
/* Print the chain for debugging */
- uhci_show_qh(urbp->qh, errbuf,
+ uhci_show_qh(uhci, urbp->qh, errbuf,
ERRBUF_LEN, 0);
lprintk(errbuf);
}
}
+ /* Did we receive a short packet? */
} else if (len < uhci_expected_length(td_token(td))) {
- /* We received a short packet */
- if (urb->transfer_flags & URB_SHORT_NOT_OK)
+ /* For control transfers, go to the status TD if
+ * this isn't already the last data TD */
+ if (qh->type == USB_ENDPOINT_XFER_CONTROL) {
+ if (td->list.next != urbp->td_list.prev)
+ ret = 1;
+ }
+
+ /* For bulk and interrupt, this may be an error */
+ else if (urb->transfer_flags & URB_SHORT_NOT_OK)
ret = -EREMOTEIO;
/* Fixup needed only if this isn't the URB's last TD */
err:
if (ret < 0) {
- /* In case a control transfer gets an error
- * during the setup stage */
- urb->actual_length = max(urb->actual_length, 0);
-
/* Note that the queue has stopped and save
* the next toggle value */
qh->element = UHCI_PTR_TERM;
if (list_empty(&qh->queue)) {
qh->iso_packet_desc = &urb->iso_frame_desc[0];
qh->iso_frame = urb->start_frame;
- qh->iso_status = 0;
}
qh->skel = SKEL_ISO;
qh->iso_packet_desc->actual_length = actlength;
qh->iso_packet_desc->status = status;
}
-
- if (status) {
+ if (status)
urb->error_count++;
- qh->iso_status = status;
- }
uhci_remove_td_from_urbp(td);
uhci_free_td(uhci, td);
qh->iso_frame += qh->period;
++qh->iso_packet_desc;
}
- return qh->iso_status;
+ return 0;
}
static int uhci_urb_enqueue(struct usb_hcd *hcd,
- struct usb_host_endpoint *hep,
struct urb *urb, gfp_t mem_flags)
{
int ret;
spin_lock_irqsave(&uhci->lock, flags);
- ret = urb->status;
- if (ret != -EINPROGRESS) /* URB already unlinked! */
- goto done;
+ ret = usb_hcd_link_urb_to_ep(hcd, urb);
+ if (ret)
+ goto done_not_linked;
ret = -ENOMEM;
urbp = uhci_alloc_urb_priv(uhci, urb);
if (!urbp)
goto done;
- if (hep->hcpriv)
- qh = (struct uhci_qh *) hep->hcpriv;
+ if (urb->ep->hcpriv)
+ qh = urb->ep->hcpriv;
else {
- qh = uhci_alloc_qh(uhci, urb->dev, hep);
+ qh = uhci_alloc_qh(uhci, urb->dev, urb->ep);
if (!qh)
goto err_no_qh;
}
err_submit_failed:
if (qh->state == QH_STATE_IDLE)
uhci_make_qh_idle(uhci, qh); /* Reclaim unused QH */
-
err_no_qh:
uhci_free_urb_priv(uhci, urbp);
-
done:
+ if (ret)
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+done_not_linked:
spin_unlock_irqrestore(&uhci->lock, flags);
return ret;
}
-static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
+static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned long flags;
- struct urb_priv *urbp;
struct uhci_qh *qh;
+ int rc;
spin_lock_irqsave(&uhci->lock, flags);
- urbp = urb->hcpriv;
- if (!urbp) /* URB was never linked! */
+ rc = usb_hcd_check_unlink_urb(hcd, urb, status);
+ if (rc)
goto done;
- qh = urbp->qh;
+
+ qh = ((struct urb_priv *) urb->hcpriv)->qh;
/* Remove Isochronous TDs from the frame list ASAP */
if (qh->type == USB_ENDPOINT_XFER_ISOC) {
done:
spin_unlock_irqrestore(&uhci->lock, flags);
- return 0;
+ return rc;
}
/*
{
struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
+ if (qh->type == USB_ENDPOINT_XFER_CONTROL) {
+
+ /* urb->actual_length < 0 means the setup transaction didn't
+ * complete successfully. Either it failed or the URB was
+ * unlinked first. Regardless, don't confuse people with a
+ * negative length. */
+ urb->actual_length = max(urb->actual_length, 0);
+ }
+
/* When giving back the first URB in an Isochronous queue,
* reinitialize the QH's iso-related members for the next URB. */
- if (qh->type == USB_ENDPOINT_XFER_ISOC &&
+ else if (qh->type == USB_ENDPOINT_XFER_ISOC &&
urbp->node.prev == &qh->queue &&
urbp->node.next != &qh->queue) {
struct urb *nurb = list_entry(urbp->node.next,
qh->iso_packet_desc = &nurb->iso_frame_desc[0];
qh->iso_frame = nurb->start_frame;
- qh->iso_status = 0;
}
/* Take the URB off the QH's queue. If the queue is now empty,
}
uhci_free_urb_priv(uhci, urbp);
+ usb_hcd_unlink_urb_from_ep(uhci_to_hcd(uhci), urb);
spin_unlock(&uhci->lock);
usb_hcd_giveback_urb(uhci_to_hcd(uhci), urb);
break;
spin_lock(&urb->lock);
- if (urb->status == -EINPROGRESS) /* Not dequeued */
- urb->status = status;
- else
- status = ECONNRESET; /* Not -ECONNRESET */
+ urb->status = status;
spin_unlock(&urb->lock);
/* Dequeued but completed URBs can't be given back unless
* the QH is stopped or has finished unlinking. */
- if (status == ECONNRESET) {
+ if (urb->unlinked) {
if (QH_FINISHED_UNLINKING(qh))
qh->is_stopped = 1;
else if (!qh->is_stopped)
}
uhci_giveback_urb(uhci, qh, urb);
- if (status < 0 && qh->type != USB_ENDPOINT_XFER_ISOC)
+ if (status < 0)
break;
}
restart:
list_for_each_entry(urbp, &qh->queue, node) {
urb = urbp->urb;
- if (urb->status != -EINPROGRESS) {
+ if (urb->unlinked) {
/* Fix up the TD links and save the toggles for
* non-Isochronous queues. For Isochronous queues,