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

UHCI: Fix problem caused by lack of terminating QH

This patch (as871) fixes a problem introduced by an earlier change.
It turns out that some systems really do need to have a terminating
skeleton QH present whenever FSBR is on.  I don't know any way to tell
which systems do need it and which don't; the easiest answer is to
have it there always.

This fixes the NumLock-hang bug reported by Jiri Slaby.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent e0f2e3a0
...@@ -145,7 +145,8 @@ static int uhci_show_urbp(struct urb_priv *urbp, char *buf, int len, int space) ...@@ -145,7 +145,8 @@ static int uhci_show_urbp(struct urb_priv *urbp, char *buf, int len, int space)
return out - buf; return out - buf;
} }
static int uhci_show_qh(struct uhci_qh *qh, char *buf, int len, int space) static int uhci_show_qh(struct uhci_hcd *uhci,
struct uhci_qh *qh, char *buf, int len, int space)
{ {
char *out = buf; char *out = buf;
int i, nurbs; int i, nurbs;
...@@ -190,6 +191,9 @@ static int uhci_show_qh(struct uhci_qh *qh, char *buf, int len, int space) ...@@ -190,6 +191,9 @@ static int uhci_show_qh(struct uhci_qh *qh, char *buf, int len, int space)
if (list_empty(&qh->queue)) { if (list_empty(&qh->queue)) {
out += sprintf(out, "%*s queue is empty\n", space, ""); out += sprintf(out, "%*s queue is empty\n", space, "");
if (qh == uhci->skel_async_qh)
out += uhci_show_td(uhci->term_td, out,
len - (out - buf), 0);
} else { } else {
struct urb_priv *urbp = list_entry(qh->queue.next, struct urb_priv *urbp = list_entry(qh->queue.next,
struct urb_priv, node); struct urb_priv, node);
...@@ -343,6 +347,7 @@ static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len) ...@@ -343,6 +347,7 @@ static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len)
struct list_head *tmp, *head; struct list_head *tmp, *head;
int nframes, nerrs; int nframes, nerrs;
__le32 link; __le32 link;
__le32 fsbr_link;
static const char * const qh_names[] = { static const char * const qh_names[] = {
"unlink", "iso", "int128", "int64", "int32", "int16", "unlink", "iso", "int128", "int64", "int32", "int16",
...@@ -424,21 +429,22 @@ static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len) ...@@ -424,21 +429,22 @@ static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len)
out += sprintf(out, "Skeleton QHs\n"); out += sprintf(out, "Skeleton QHs\n");
fsbr_link = 0;
for (i = 0; i < UHCI_NUM_SKELQH; ++i) { for (i = 0; i < UHCI_NUM_SKELQH; ++i) {
int cnt = 0; int cnt = 0;
__le32 fsbr_link = 0;
qh = uhci->skelqh[i]; qh = uhci->skelqh[i];
out += sprintf(out, "- skel_%s_qh\n", qh_names[i]); \ out += sprintf(out, "- skel_%s_qh\n", qh_names[i]); \
out += uhci_show_qh(qh, out, len - (out - buf), 4); out += uhci_show_qh(uhci, qh, out, len - (out - buf), 4);
/* Last QH is the Terminating QH, it's different */ /* Last QH is the Terminating QH, it's different */
if (i == SKEL_TERM) { if (i == SKEL_TERM) {
if (qh_element(qh) != LINK_TO_TD(uhci->term_td)) if (qh_element(qh) != LINK_TO_TD(uhci->term_td))
out += sprintf(out, " skel_term_qh element is not set to term_td!\n"); out += sprintf(out, " skel_term_qh element is not set to term_td!\n");
if (link == LINK_TO_QH(uhci->skel_term_qh)) link = fsbr_link;
if (!link)
link = LINK_TO_QH(uhci->skel_term_qh);
goto check_qh_link; goto check_qh_link;
continue;
} }
head = &qh->node; head = &qh->node;
...@@ -448,7 +454,7 @@ static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len) ...@@ -448,7 +454,7 @@ static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len)
qh = list_entry(tmp, struct uhci_qh, node); qh = list_entry(tmp, struct uhci_qh, node);
tmp = tmp->next; tmp = tmp->next;
if (++cnt <= 10) if (++cnt <= 10)
out += uhci_show_qh(qh, out, out += uhci_show_qh(uhci, qh, out,
len - (out - buf), 4); len - (out - buf), 4);
if (!fsbr_link && qh->skel >= SKEL_FSBR) if (!fsbr_link && qh->skel >= SKEL_FSBR)
fsbr_link = LINK_TO_QH(qh); fsbr_link = LINK_TO_QH(qh);
...@@ -463,8 +469,6 @@ static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len) ...@@ -463,8 +469,6 @@ static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len)
link = LINK_TO_QH(uhci->skel_async_qh); link = LINK_TO_QH(uhci->skel_async_qh);
else if (!uhci->fsbr_is_on) else if (!uhci->fsbr_is_on)
; ;
else if (fsbr_link)
link = fsbr_link;
else else
link = LINK_TO_QH(uhci->skel_term_qh); link = LINK_TO_QH(uhci->skel_term_qh);
check_qh_link: check_qh_link:
...@@ -573,8 +577,8 @@ static const struct file_operations uhci_debug_operations = { ...@@ -573,8 +577,8 @@ static const struct file_operations uhci_debug_operations = {
static inline void lprintk(char *buf) static inline void lprintk(char *buf)
{} {}
static inline int uhci_show_qh(struct uhci_qh *qh, char *buf, static inline int uhci_show_qh(struct uhci_hcd *uhci,
int len, int space) struct uhci_qh *qh, char *buf, int len, int space)
{ {
return 0; return 0;
} }
......
...@@ -632,7 +632,8 @@ static int uhci_start(struct usb_hcd *hcd) ...@@ -632,7 +632,8 @@ static int uhci_start(struct usb_hcd *hcd)
*/ */
for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i) for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i)
uhci->skelqh[i]->link = LINK_TO_QH(uhci->skel_async_qh); uhci->skelqh[i]->link = LINK_TO_QH(uhci->skel_async_qh);
uhci->skel_async_qh->link = uhci->skel_term_qh->link = UHCI_PTR_TERM; uhci->skel_async_qh->link = UHCI_PTR_TERM;
uhci->skel_term_qh->link = LINK_TO_QH(uhci->skel_term_qh);
/* This dummy TD is to work around a bug in Intel PIIX controllers */ /* This dummy TD is to work around a bug in Intel PIIX controllers */
uhci_fill_td(uhci->term_td, 0, uhci_explen(0) | uhci_fill_td(uhci->term_td, 0, uhci_explen(0) |
......
...@@ -45,43 +45,27 @@ static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci) ...@@ -45,43 +45,27 @@ static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci)
*/ */
static void uhci_fsbr_on(struct uhci_hcd *uhci) 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; uhci->fsbr_is_on = 1;
lqh = list_entry(uhci->skel_async_qh->node.prev, lqh = list_entry(uhci->skel_async_qh->node.prev,
struct uhci_qh, node); struct uhci_qh, node);
lqh->link = LINK_TO_QH(uhci->skel_term_qh);
/* 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);
} }
static void uhci_fsbr_off(struct uhci_hcd *uhci) static void uhci_fsbr_off(struct uhci_hcd *uhci)
{ {
struct uhci_qh *lqh; struct uhci_qh *lqh;
/* Remove the link from the last async QH to the terminating
* skeleton QH. */
uhci->fsbr_is_on = 0; uhci->fsbr_is_on = 0;
lqh = list_entry(uhci->skel_async_qh->node.prev, lqh = list_entry(uhci->skel_async_qh->node.prev,
struct uhci_qh, node); struct uhci_qh, node);
lqh->link = UHCI_PTR_TERM;
/* End the async list normally and unlink the terminating QH */
lqh->link = uhci->skel_term_qh->link = UHCI_PTR_TERM;
} }
static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb) static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb)
...@@ -464,9 +448,8 @@ static void link_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh) ...@@ -464,9 +448,8 @@ static void link_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh)
*/ */
static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh) 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 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. /* 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 * The list of QHs is expected to be short, so linear search won't
...@@ -476,31 +459,17 @@ static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh) ...@@ -476,31 +459,17 @@ static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh)
break; break;
} }
list_add(&qh->node, &pqh->node); 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 */ /* Link it into the schedule */
qh->link = pqh->link;
wmb(); 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;
} }
/* /*
...@@ -561,31 +530,16 @@ static void unlink_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh) ...@@ -561,31 +530,16 @@ static void unlink_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh)
*/ */
static void unlink_async(struct uhci_hcd *uhci, struct uhci_qh *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; __le32 link_to_next_qh = qh->link;
pqh = list_entry(qh->node.prev, struct uhci_qh, node); pqh = list_entry(qh->node.prev, struct uhci_qh, node);
pqh->link = link_to_next_qh;
/* If this is the first FSBQ QH, take special action */ /* If this was the old first FSBR QH, link the terminating skeleton
if (uhci->fsbr_is_on && pqh->skel < SKEL_FSBR && * QH to the next (new first FSBR) QH. */
qh->skel >= SKEL_FSBR) { if (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; 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;
mb(); mb();
} }
...@@ -1217,7 +1171,7 @@ static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb) ...@@ -1217,7 +1171,7 @@ static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb)
if (debug > 1 && errbuf) { if (debug > 1 && errbuf) {
/* Print the chain for debugging */ /* Print the chain for debugging */
uhci_show_qh(urbp->qh, errbuf, uhci_show_qh(uhci, urbp->qh, errbuf,
ERRBUF_LEN, 0); ERRBUF_LEN, 0);
lprintk(errbuf); lprintk(errbuf);
} }
......
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