Commit c3998736 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

[PATCH] USB: Important bugfix for UHCI list management code

A major bug in the UHCI driver turned up recently.  Thanks to a lot of
help from Simone Gotti it was identified and fixed late last week.  It
turned out to be entirely my fault -- a previous patch had introduced two
(!) errors.  (A combination of carelessness and a nasty thinko, and
somehow it passed the regression tests...)

Anyway, it's entirely possible that many of the problems people have been
seeing are caused by that bug.  This patch is the solution.
parent 8ae141b2
...@@ -382,6 +382,7 @@ static void uhci_insert_qh(struct uhci_hcd *uhci, struct uhci_qh *skelqh, struct ...@@ -382,6 +382,7 @@ static void uhci_insert_qh(struct uhci_hcd *uhci, struct uhci_qh *skelqh, struct
static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
{ {
struct uhci_qh *pqh; struct uhci_qh *pqh;
__u32 newlink;
if (!qh) if (!qh)
return; return;
...@@ -390,8 +391,24 @@ static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) ...@@ -390,8 +391,24 @@ static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
* Only go through the hoops if it's actually linked in * Only go through the hoops if it's actually linked in
*/ */
if (!list_empty(&qh->list)) { if (!list_empty(&qh->list)) {
pqh = list_entry(qh->list.prev, struct uhci_qh, list);
/* If our queue is nonempty, make the next URB the head */
if (!list_empty(&qh->urbp->queue_list)) {
struct urb_priv *nurbp;
nurbp = list_entry(qh->urbp->queue_list.next,
struct urb_priv, queue_list);
nurbp->queued = 0;
list_add(&nurbp->qh->list, &qh->list);
newlink = cpu_to_le32(nurbp->qh->dma_handle) | UHCI_PTR_QH;
} else
newlink = qh->link;
/* Fix up the previous QH's queue to link to either
* the new head of this queue or the start of the
* next endpoint's queue. */
pqh = list_entry(qh->list.prev, struct uhci_qh, list);
pqh->link = newlink;
if (pqh->urbp) { if (pqh->urbp) {
struct list_head *head, *tmp; struct list_head *head, *tmp;
...@@ -403,28 +420,19 @@ static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) ...@@ -403,28 +420,19 @@ static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
tmp = tmp->next; tmp = tmp->next;
turbp->qh->link = qh->link; turbp->qh->link = newlink;
} }
} }
pqh->link = qh->link;
mb(); mb();
/* Leave qh->link in case the HC is on the QH now, it will */ /* Leave qh->link in case the HC is on the QH now, it will */
/* continue the rest of the schedule */ /* continue the rest of the schedule */
qh->element = UHCI_PTR_TERM; qh->element = UHCI_PTR_TERM;
/* If our queue is nonempty, make the next URB the head */
if (!list_empty(&qh->urbp->queue_list)) {
struct urb_priv *nurbp;
nurbp = list_entry(qh->urbp->queue_list.next,
struct urb_priv, queue_list);
nurbp->queued = 0;
list_add_tail(&nurbp->qh->list, &qh->list);
}
list_del_init(&qh->list); list_del_init(&qh->list);
} }
list_del_init(&qh->urbp->queue_list);
qh->urbp = NULL; qh->urbp = NULL;
/* Check to see if the remove list is empty. Set the IOC bit */ /* Check to see if the remove list is empty. Set the IOC bit */
...@@ -579,7 +587,7 @@ static void uhci_delete_queued_urb(struct uhci_hcd *uhci, struct urb *urb) ...@@ -579,7 +587,7 @@ static void uhci_delete_queued_urb(struct uhci_hcd *uhci, struct urb *urb)
pltd->link = UHCI_PTR_TERM; pltd->link = UHCI_PTR_TERM;
} }
list_del_init(&urbp->queue_list); /* urbp->queue_list is handled in uhci_remove_qh() */
} }
static struct urb_priv *uhci_alloc_urb_priv(struct uhci_hcd *uhci, struct urb *urb) static struct urb_priv *uhci_alloc_urb_priv(struct uhci_hcd *uhci, struct urb *urb)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment