aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2007-03-19 15:31:42 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2007-03-26 17:17:48 -0400
commite009f1b202219c62ea7e277adbb953d703dac983 (patch)
treef4eddb5808258c2b1f7504b4866a65c8a0a8434e
parente0f2e3a06be513352cb4955313ed7e55909acd84 (diff)
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: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/usb/host/uhci-debug.c26
-rw-r--r--drivers/usb/host/uhci-hcd.c3
-rw-r--r--drivers/usb/host/uhci-q.c94
3 files changed, 41 insertions, 82 deletions
diff --git a/drivers/usb/host/uhci-debug.c b/drivers/usb/host/uhci-debug.c
index 8d24d3dc0a61..1497371583b9 100644
--- a/drivers/usb/host/uhci-debug.c
+++ b/drivers/usb/host/uhci-debug.c
@@ -145,7 +145,8 @@ static int uhci_show_urbp(struct urb_priv *urbp, char *buf, int len, int space)
145 return out - buf; 145 return out - buf;
146} 146}
147 147
148static int uhci_show_qh(struct uhci_qh *qh, char *buf, int len, int space) 148static int uhci_show_qh(struct uhci_hcd *uhci,
149 struct uhci_qh *qh, char *buf, int len, int space)
149{ 150{
150 char *out = buf; 151 char *out = buf;
151 int i, nurbs; 152 int i, nurbs;
@@ -190,6 +191,9 @@ static int uhci_show_qh(struct uhci_qh *qh, char *buf, int len, int space)
190 191
191 if (list_empty(&qh->queue)) { 192 if (list_empty(&qh->queue)) {
192 out += sprintf(out, "%*s queue is empty\n", space, ""); 193 out += sprintf(out, "%*s queue is empty\n", space, "");
194 if (qh == uhci->skel_async_qh)
195 out += uhci_show_td(uhci->term_td, out,
196 len - (out - buf), 0);
193 } else { 197 } else {
194 struct urb_priv *urbp = list_entry(qh->queue.next, 198 struct urb_priv *urbp = list_entry(qh->queue.next,
195 struct urb_priv, node); 199 struct urb_priv, node);
@@ -343,6 +347,7 @@ static int uhci_sprint_schedule(struct uhci_hcd *uhci, char *buf, int len)
343 struct list_head *tmp, *head; 347 struct list_head *tmp, *head;
344 int nframes, nerrs; 348 int nframes, nerrs;
345 __le32 link; 349 __le32 link;
350 __le32 fsbr_link;
346 351
347 static const char * const qh_names[] = { 352 static const char * const qh_names[] = {
348 "unlink", "iso", "int128", "int64", "int32", "int16", 353 "unlink", "iso", "int128", "int64", "int32", "int16",
@@ -424,21 +429,22 @@ check_link:
424 429
425 out += sprintf(out, "Skeleton QHs\n"); 430 out += sprintf(out, "Skeleton QHs\n");
426 431
432 fsbr_link = 0;
427 for (i = 0; i < UHCI_NUM_SKELQH; ++i) { 433 for (i = 0; i < UHCI_NUM_SKELQH; ++i) {
428 int cnt = 0; 434 int cnt = 0;
429 __le32 fsbr_link = 0;
430 435
431 qh = uhci->skelqh[i]; 436 qh = uhci->skelqh[i];
432 out += sprintf(out, "- skel_%s_qh\n", qh_names[i]); \ 437 out += sprintf(out, "- skel_%s_qh\n", qh_names[i]); \
433 out += uhci_show_qh(qh, out, len - (out - buf), 4); 438 out += uhci_show_qh(uhci, qh, out, len - (out - buf), 4);
434 439
435 /* Last QH is the Terminating QH, it's different */ 440 /* Last QH is the Terminating QH, it's different */
436 if (i == SKEL_TERM) { 441 if (i == SKEL_TERM) {
437 if (qh_element(qh) != LINK_TO_TD(uhci->term_td)) 442 if (qh_element(qh) != LINK_TO_TD(uhci->term_td))
438 out += sprintf(out, " skel_term_qh element is not set to term_td!\n"); 443 out += sprintf(out, " skel_term_qh element is not set to term_td!\n");
439 if (link == LINK_TO_QH(uhci->skel_term_qh)) 444 link = fsbr_link;
440 goto check_qh_link; 445 if (!link)
441 continue; 446 link = LINK_TO_QH(uhci->skel_term_qh);
447 goto check_qh_link;
442 } 448 }
443 449
444 head = &qh->node; 450 head = &qh->node;
@@ -448,7 +454,7 @@ check_link:
448 qh = list_entry(tmp, struct uhci_qh, node); 454 qh = list_entry(tmp, struct uhci_qh, node);
449 tmp = tmp->next; 455 tmp = tmp->next;
450 if (++cnt <= 10) 456 if (++cnt <= 10)
451 out += uhci_show_qh(qh, out, 457 out += uhci_show_qh(uhci, qh, out,
452 len - (out - buf), 4); 458 len - (out - buf), 4);
453 if (!fsbr_link && qh->skel >= SKEL_FSBR) 459 if (!fsbr_link && qh->skel >= SKEL_FSBR)
454 fsbr_link = LINK_TO_QH(qh); 460 fsbr_link = LINK_TO_QH(qh);
@@ -463,8 +469,6 @@ check_link:
463 link = LINK_TO_QH(uhci->skel_async_qh); 469 link = LINK_TO_QH(uhci->skel_async_qh);
464 else if (!uhci->fsbr_is_on) 470 else if (!uhci->fsbr_is_on)
465 ; 471 ;
466 else if (fsbr_link)
467 link = fsbr_link;
468 else 472 else
469 link = LINK_TO_QH(uhci->skel_term_qh); 473 link = LINK_TO_QH(uhci->skel_term_qh);
470check_qh_link: 474check_qh_link:
@@ -573,8 +577,8 @@ static const struct file_operations uhci_debug_operations = {
573static inline void lprintk(char *buf) 577static inline void lprintk(char *buf)
574{} 578{}
575 579
576static inline int uhci_show_qh(struct uhci_qh *qh, char *buf, 580static inline int uhci_show_qh(struct uhci_hcd *uhci,
577 int len, int space) 581 struct uhci_qh *qh, char *buf, int len, int space)
578{ 582{
579 return 0; 583 return 0;
580} 584}
diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c
index 44da4334f1d6..d22da26ff167 100644
--- a/drivers/usb/host/uhci-hcd.c
+++ b/drivers/usb/host/uhci-hcd.c
@@ -632,7 +632,8 @@ static int uhci_start(struct usb_hcd *hcd)
632 */ 632 */
633 for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i) 633 for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i)
634 uhci->skelqh[i]->link = LINK_TO_QH(uhci->skel_async_qh); 634 uhci->skelqh[i]->link = LINK_TO_QH(uhci->skel_async_qh);
635 uhci->skel_async_qh->link = uhci->skel_term_qh->link = UHCI_PTR_TERM; 635 uhci->skel_async_qh->link = UHCI_PTR_TERM;
636 uhci->skel_term_qh->link = LINK_TO_QH(uhci->skel_term_qh);
636 637
637 /* This dummy TD is to work around a bug in Intel PIIX controllers */ 638 /* This dummy TD is to work around a bug in Intel PIIX controllers */
638 uhci_fill_td(uhci->term_td, 0, uhci_explen(0) | 639 uhci_fill_td(uhci->term_td, 0, uhci_explen(0) |
diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c
index f4ebdb3e488f..19a0cc02b9a2 100644
--- a/drivers/usb/host/uhci-q.c
+++ b/drivers/usb/host/uhci-q.c
@@ -45,43 +45,27 @@ static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci)
45 */ 45 */
46static void uhci_fsbr_on(struct uhci_hcd *uhci) 46static void uhci_fsbr_on(struct uhci_hcd *uhci)
47{ 47{
48 struct uhci_qh *fsbr_qh, *lqh, *tqh; 48 struct uhci_qh *lqh;
49 49
50 /* The terminating skeleton QH always points back to the first
51 * FSBR QH. Make the last async QH point to the terminating
52 * skeleton QH. */
50 uhci->fsbr_is_on = 1; 53 uhci->fsbr_is_on = 1;
51 lqh = list_entry(uhci->skel_async_qh->node.prev, 54 lqh = list_entry(uhci->skel_async_qh->node.prev,
52 struct uhci_qh, node); 55 struct uhci_qh, node);
53 56 lqh->link = LINK_TO_QH(uhci->skel_term_qh);
54 /* Find the first FSBR QH. Linear search through the list is
55 * acceptable because normally FSBR gets turned on as soon as
56 * one QH needs it. */
57 fsbr_qh = NULL;
58 list_for_each_entry_reverse(tqh, &uhci->skel_async_qh->node, node) {
59 if (tqh->skel < SKEL_FSBR)
60 break;
61 fsbr_qh = tqh;
62 }
63
64 /* No FSBR QH means we must insert the terminating skeleton QH */
65 if (!fsbr_qh) {
66 uhci->skel_term_qh->link = LINK_TO_QH(uhci->skel_term_qh);
67 wmb();
68 lqh->link = uhci->skel_term_qh->link;
69
70 /* Otherwise loop the last QH to the first FSBR QH */
71 } else
72 lqh->link = LINK_TO_QH(fsbr_qh);
73} 57}
74 58
75static void uhci_fsbr_off(struct uhci_hcd *uhci) 59static void uhci_fsbr_off(struct uhci_hcd *uhci)
76{ 60{
77 struct uhci_qh *lqh; 61 struct uhci_qh *lqh;
78 62
63 /* Remove the link from the last async QH to the terminating
64 * skeleton QH. */
79 uhci->fsbr_is_on = 0; 65 uhci->fsbr_is_on = 0;
80 lqh = list_entry(uhci->skel_async_qh->node.prev, 66 lqh = list_entry(uhci->skel_async_qh->node.prev,
81 struct uhci_qh, node); 67 struct uhci_qh, node);
82 68 lqh->link = UHCI_PTR_TERM;
83 /* End the async list normally and unlink the terminating QH */
84 lqh->link = uhci->skel_term_qh->link = UHCI_PTR_TERM;
85} 69}
86 70
87static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb) 71static 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 */ 448 */
465static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh) 449static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh)
466{ 450{
467 struct uhci_qh *pqh, *lqh; 451 struct uhci_qh *pqh;
468 __le32 link_to_new_qh; 452 __le32 link_to_new_qh;
469 __le32 *extra_link = &link_to_new_qh;
470 453
471 /* Find the predecessor QH for our new one and insert it in the list. 454 /* Find the predecessor QH for our new one and insert it in the list.
472 * The list of QHs is expected to be short, so linear search won't 455 * 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 break; 459 break;
477 } 460 }
478 list_add(&qh->node, &pqh->node); 461 list_add(&qh->node, &pqh->node);
479 qh->link = pqh->link;
480
481 link_to_new_qh = LINK_TO_QH(qh);
482
483 /* If this is now the first FSBR QH, take special action */
484 if (uhci->fsbr_is_on && pqh->skel < SKEL_FSBR &&
485 qh->skel >= SKEL_FSBR) {
486 lqh = list_entry(uhci->skel_async_qh->node.prev,
487 struct uhci_qh, node);
488
489 /* If the new QH is also the last one, we must unlink
490 * the terminating skeleton QH and make the new QH point
491 * back to itself. */
492 if (qh == lqh) {
493 qh->link = link_to_new_qh;
494 extra_link = &uhci->skel_term_qh->link;
495
496 /* Otherwise the last QH must point to the new QH */
497 } else
498 extra_link = &lqh->link;
499 }
500 462
501 /* Link it into the schedule */ 463 /* Link it into the schedule */
464 qh->link = pqh->link;
502 wmb(); 465 wmb();
503 *extra_link = pqh->link = link_to_new_qh; 466 link_to_new_qh = LINK_TO_QH(qh);
467 pqh->link = link_to_new_qh;
468
469 /* If this is now the first FSBR QH, link the terminating skeleton
470 * QH to it. */
471 if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR)
472 uhci->skel_term_qh->link = link_to_new_qh;
504} 473}
505 474
506/* 475/*
@@ -561,31 +530,16 @@ static void unlink_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh)
561 */ 530 */
562static void unlink_async(struct uhci_hcd *uhci, struct uhci_qh *qh) 531static void unlink_async(struct uhci_hcd *uhci, struct uhci_qh *qh)
563{ 532{
564 struct uhci_qh *pqh, *lqh; 533 struct uhci_qh *pqh;
565 __le32 link_to_next_qh = qh->link; 534 __le32 link_to_next_qh = qh->link;
566 535
567 pqh = list_entry(qh->node.prev, struct uhci_qh, node); 536 pqh = list_entry(qh->node.prev, struct uhci_qh, node);
568
569 /* If this is the first FSBQ QH, take special action */
570 if (uhci->fsbr_is_on && pqh->skel < SKEL_FSBR &&
571 qh->skel >= SKEL_FSBR) {
572 lqh = list_entry(uhci->skel_async_qh->node.prev,
573 struct uhci_qh, node);
574
575 /* If this QH is also the last one, we must link in
576 * the terminating skeleton QH. */
577 if (qh == lqh) {
578 link_to_next_qh = LINK_TO_QH(uhci->skel_term_qh);
579 uhci->skel_term_qh->link = link_to_next_qh;
580 wmb();
581 qh->link = link_to_next_qh;
582
583 /* Otherwise the last QH must point to the new first FSBR QH */
584 } else
585 lqh->link = link_to_next_qh;
586 }
587
588 pqh->link = link_to_next_qh; 537 pqh->link = link_to_next_qh;
538
539 /* If this was the old first FSBR QH, link the terminating skeleton
540 * QH to the next (new first FSBR) QH. */
541 if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR)
542 uhci->skel_term_qh->link = link_to_next_qh;
589 mb(); 543 mb();
590} 544}
591 545
@@ -1217,7 +1171,7 @@ static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb)
1217 1171
1218 if (debug > 1 && errbuf) { 1172 if (debug > 1 && errbuf) {
1219 /* Print the chain for debugging */ 1173 /* Print the chain for debugging */
1220 uhci_show_qh(urbp->qh, errbuf, 1174 uhci_show_qh(uhci, urbp->qh, errbuf,
1221 ERRBUF_LEN, 0); 1175 ERRBUF_LEN, 0);
1222 lprintk(errbuf); 1176 lprintk(errbuf);
1223 } 1177 }