diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2007-02-19 15:52:45 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-02-23 18:03:45 -0500 |
commit | 17230acdc71137622ca7dfd789b3944c75d39404 (patch) | |
tree | 67eb75c5e8d254b2d5490ea9982efe73952f90d5 /drivers/usb/host/uhci-q.c | |
parent | 28b9325e6ae45ffb5e99fedcafe00f25fcaacf06 (diff) |
UHCI: Eliminate asynchronous skeleton Queue Headers
This patch (as856) attempts to improve the performance of uhci-hcd by
removing the asynchronous skeleton Queue Headers. They don't contain
any useful information but the controller has to read through them at
least once every millisecond, incurring a non-zero DMA overhead.
Now all the asynchronous queues are combined, along with the period-1
interrupt queue, into a single list with a single skeleton QH. The
start of the low-speed control, full-speed control, and bulk sublists
is determined by linear search. Since there should rarely be more
than a couple of QHs in the list, the searches should incur a much
smaller total load than keeping the skeleton QHs.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/uhci-q.c')
-rw-r--r-- | drivers/usb/host/uhci-q.c | 193 |
1 files changed, 171 insertions, 22 deletions
diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c index a0c6bf6128a3..f4ebdb3e488f 100644 --- a/drivers/usb/host/uhci-q.c +++ b/drivers/usb/host/uhci-q.c | |||
@@ -13,7 +13,7 @@ | |||
13 | * (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface | 13 | * (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface |
14 | * support from usb-ohci.c by Adam Richter, adam@yggdrasil.com). | 14 | * support from usb-ohci.c by Adam Richter, adam@yggdrasil.com). |
15 | * (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c) | 15 | * (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c) |
16 | * (C) Copyright 2004-2006 Alan Stern, stern@rowland.harvard.edu | 16 | * (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu |
17 | */ | 17 | */ |
18 | 18 | ||
19 | 19 | ||
@@ -45,14 +45,43 @@ static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci) | |||
45 | */ | 45 | */ |
46 | static void uhci_fsbr_on(struct uhci_hcd *uhci) | 46 | static void uhci_fsbr_on(struct uhci_hcd *uhci) |
47 | { | 47 | { |
48 | struct uhci_qh *fsbr_qh, *lqh, *tqh; | ||
49 | |||
48 | uhci->fsbr_is_on = 1; | 50 | uhci->fsbr_is_on = 1; |
49 | uhci->skel_term_qh->link = LINK_TO_QH(uhci->skel_fs_control_qh); | 51 | lqh = list_entry(uhci->skel_async_qh->node.prev, |
52 | struct uhci_qh, node); | ||
53 | |||
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); | ||
50 | } | 73 | } |
51 | 74 | ||
52 | static void uhci_fsbr_off(struct uhci_hcd *uhci) | 75 | static void uhci_fsbr_off(struct uhci_hcd *uhci) |
53 | { | 76 | { |
77 | struct uhci_qh *lqh; | ||
78 | |||
54 | uhci->fsbr_is_on = 0; | 79 | uhci->fsbr_is_on = 0; |
55 | uhci->skel_term_qh->link = UHCI_PTR_TERM; | 80 | lqh = list_entry(uhci->skel_async_qh->node.prev, |
81 | struct uhci_qh, node); | ||
82 | |||
83 | /* End the async list normally and unlink the terminating QH */ | ||
84 | lqh->link = uhci->skel_term_qh->link = UHCI_PTR_TERM; | ||
56 | } | 85 | } |
57 | 86 | ||
58 | static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb) | 87 | static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb) |
@@ -404,12 +433,81 @@ static void uhci_fixup_toggles(struct uhci_qh *qh, int skip_first) | |||
404 | } | 433 | } |
405 | 434 | ||
406 | /* | 435 | /* |
407 | * Put a QH on the schedule in both hardware and software | 436 | * Link an Isochronous QH into its skeleton's list |
408 | */ | 437 | */ |
409 | static void uhci_activate_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) | 438 | static inline void link_iso(struct uhci_hcd *uhci, struct uhci_qh *qh) |
439 | { | ||
440 | list_add_tail(&qh->node, &uhci->skel_iso_qh->node); | ||
441 | |||
442 | /* Isochronous QHs aren't linked by the hardware */ | ||
443 | } | ||
444 | |||
445 | /* | ||
446 | * Link a high-period interrupt QH into the schedule at the end of its | ||
447 | * skeleton's list | ||
448 | */ | ||
449 | static void link_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh) | ||
410 | { | 450 | { |
411 | struct uhci_qh *pqh; | 451 | struct uhci_qh *pqh; |
412 | 452 | ||
453 | list_add_tail(&qh->node, &uhci->skelqh[qh->skel]->node); | ||
454 | |||
455 | pqh = list_entry(qh->node.prev, struct uhci_qh, node); | ||
456 | qh->link = pqh->link; | ||
457 | wmb(); | ||
458 | pqh->link = LINK_TO_QH(qh); | ||
459 | } | ||
460 | |||
461 | /* | ||
462 | * Link a period-1 interrupt or async QH into the schedule at the | ||
463 | * correct spot in the async skeleton's list, and update the FSBR link | ||
464 | */ | ||
465 | static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh) | ||
466 | { | ||
467 | struct uhci_qh *pqh, *lqh; | ||
468 | __le32 link_to_new_qh; | ||
469 | __le32 *extra_link = &link_to_new_qh; | ||
470 | |||
471 | /* 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 | ||
473 | * take too long. */ | ||
474 | list_for_each_entry_reverse(pqh, &uhci->skel_async_qh->node, node) { | ||
475 | if (pqh->skel <= qh->skel) | ||
476 | break; | ||
477 | } | ||
478 | 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 | |||
501 | /* Link it into the schedule */ | ||
502 | wmb(); | ||
503 | *extra_link = pqh->link = link_to_new_qh; | ||
504 | } | ||
505 | |||
506 | /* | ||
507 | * Put a QH on the schedule in both hardware and software | ||
508 | */ | ||
509 | static void uhci_activate_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) | ||
510 | { | ||
413 | WARN_ON(list_empty(&qh->queue)); | 511 | WARN_ON(list_empty(&qh->queue)); |
414 | 512 | ||
415 | /* Set the element pointer if it isn't set already. | 513 | /* Set the element pointer if it isn't set already. |
@@ -431,18 +529,64 @@ static void uhci_activate_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) | |||
431 | return; | 529 | return; |
432 | qh->state = QH_STATE_ACTIVE; | 530 | qh->state = QH_STATE_ACTIVE; |
433 | 531 | ||
434 | /* Move the QH from its old list to the end of the appropriate | 532 | /* Move the QH from its old list to the correct spot in the appropriate |
435 | * skeleton's list */ | 533 | * skeleton's list */ |
436 | if (qh == uhci->next_qh) | 534 | if (qh == uhci->next_qh) |
437 | uhci->next_qh = list_entry(qh->node.next, struct uhci_qh, | 535 | uhci->next_qh = list_entry(qh->node.next, struct uhci_qh, |
438 | node); | 536 | node); |
439 | list_move_tail(&qh->node, &qh->skel->node); | 537 | list_del(&qh->node); |
538 | |||
539 | if (qh->skel == SKEL_ISO) | ||
540 | link_iso(uhci, qh); | ||
541 | else if (qh->skel < SKEL_ASYNC) | ||
542 | link_interrupt(uhci, qh); | ||
543 | else | ||
544 | link_async(uhci, qh); | ||
545 | } | ||
546 | |||
547 | /* | ||
548 | * Unlink a high-period interrupt QH from the schedule | ||
549 | */ | ||
550 | static void unlink_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh) | ||
551 | { | ||
552 | struct uhci_qh *pqh; | ||
440 | 553 | ||
441 | /* Link it into the schedule */ | ||
442 | pqh = list_entry(qh->node.prev, struct uhci_qh, node); | 554 | pqh = list_entry(qh->node.prev, struct uhci_qh, node); |
443 | qh->link = pqh->link; | 555 | pqh->link = qh->link; |
444 | wmb(); | 556 | mb(); |
445 | pqh->link = LINK_TO_QH(qh); | 557 | } |
558 | |||
559 | /* | ||
560 | * Unlink a period-1 interrupt or async QH from the schedule | ||
561 | */ | ||
562 | static void unlink_async(struct uhci_hcd *uhci, struct uhci_qh *qh) | ||
563 | { | ||
564 | struct uhci_qh *pqh, *lqh; | ||
565 | __le32 link_to_next_qh = qh->link; | ||
566 | |||
567 | 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; | ||
589 | mb(); | ||
446 | } | 590 | } |
447 | 591 | ||
448 | /* | 592 | /* |
@@ -450,17 +594,18 @@ static void uhci_activate_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) | |||
450 | */ | 594 | */ |
451 | static void uhci_unlink_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) | 595 | static void uhci_unlink_qh(struct uhci_hcd *uhci, struct uhci_qh *qh) |
452 | { | 596 | { |
453 | struct uhci_qh *pqh; | ||
454 | |||
455 | if (qh->state == QH_STATE_UNLINKING) | 597 | if (qh->state == QH_STATE_UNLINKING) |
456 | return; | 598 | return; |
457 | WARN_ON(qh->state != QH_STATE_ACTIVE || !qh->udev); | 599 | WARN_ON(qh->state != QH_STATE_ACTIVE || !qh->udev); |
458 | qh->state = QH_STATE_UNLINKING; | 600 | qh->state = QH_STATE_UNLINKING; |
459 | 601 | ||
460 | /* Unlink the QH from the schedule and record when we did it */ | 602 | /* Unlink the QH from the schedule and record when we did it */ |
461 | pqh = list_entry(qh->node.prev, struct uhci_qh, node); | 603 | if (qh->skel == SKEL_ISO) |
462 | pqh->link = qh->link; | 604 | ; |
463 | mb(); | 605 | else if (qh->skel < SKEL_ASYNC) |
606 | unlink_interrupt(uhci, qh); | ||
607 | else | ||
608 | unlink_async(uhci, qh); | ||
464 | 609 | ||
465 | uhci_get_current_frame_number(uhci); | 610 | uhci_get_current_frame_number(uhci); |
466 | qh->unlink_frame = uhci->frame_number; | 611 | qh->unlink_frame = uhci->frame_number; |
@@ -696,6 +841,7 @@ static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb, | |||
696 | dma_addr_t data = urb->transfer_dma; | 841 | dma_addr_t data = urb->transfer_dma; |
697 | __le32 *plink; | 842 | __le32 *plink; |
698 | struct urb_priv *urbp = urb->hcpriv; | 843 | struct urb_priv *urbp = urb->hcpriv; |
844 | int skel; | ||
699 | 845 | ||
700 | /* The "pipe" thing contains the destination in bits 8--18 */ | 846 | /* The "pipe" thing contains the destination in bits 8--18 */ |
701 | destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP; | 847 | destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP; |
@@ -796,11 +942,13 @@ static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb, | |||
796 | * isn't in the CONFIGURED state. */ | 942 | * isn't in the CONFIGURED state. */ |
797 | if (urb->dev->speed == USB_SPEED_LOW || | 943 | if (urb->dev->speed == USB_SPEED_LOW || |
798 | urb->dev->state != USB_STATE_CONFIGURED) | 944 | urb->dev->state != USB_STATE_CONFIGURED) |
799 | qh->skel = uhci->skel_ls_control_qh; | 945 | skel = SKEL_LS_CONTROL; |
800 | else { | 946 | else { |
801 | qh->skel = uhci->skel_fs_control_qh; | 947 | skel = SKEL_FS_CONTROL; |
802 | uhci_add_fsbr(uhci, urb); | 948 | uhci_add_fsbr(uhci, urb); |
803 | } | 949 | } |
950 | if (qh->state != QH_STATE_ACTIVE) | ||
951 | qh->skel = skel; | ||
804 | 952 | ||
805 | urb->actual_length = -8; /* Account for the SETUP packet */ | 953 | urb->actual_length = -8; /* Account for the SETUP packet */ |
806 | return 0; | 954 | return 0; |
@@ -930,7 +1078,7 @@ nomem: | |||
930 | return -ENOMEM; | 1078 | return -ENOMEM; |
931 | } | 1079 | } |
932 | 1080 | ||
933 | static inline int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb, | 1081 | static int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb, |
934 | struct uhci_qh *qh) | 1082 | struct uhci_qh *qh) |
935 | { | 1083 | { |
936 | int ret; | 1084 | int ret; |
@@ -939,7 +1087,8 @@ static inline int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb, | |||
939 | if (urb->dev->speed == USB_SPEED_LOW) | 1087 | if (urb->dev->speed == USB_SPEED_LOW) |
940 | return -EINVAL; | 1088 | return -EINVAL; |
941 | 1089 | ||
942 | qh->skel = uhci->skel_bulk_qh; | 1090 | if (qh->state != QH_STATE_ACTIVE) |
1091 | qh->skel = SKEL_BULK; | ||
943 | ret = uhci_submit_common(uhci, urb, qh); | 1092 | ret = uhci_submit_common(uhci, urb, qh); |
944 | if (ret == 0) | 1093 | if (ret == 0) |
945 | uhci_add_fsbr(uhci, urb); | 1094 | uhci_add_fsbr(uhci, urb); |
@@ -967,7 +1116,7 @@ static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb, | |||
967 | if (exponent < 0) | 1116 | if (exponent < 0) |
968 | return -EINVAL; | 1117 | return -EINVAL; |
969 | qh->period = 1 << exponent; | 1118 | qh->period = 1 << exponent; |
970 | qh->skel = uhci->skelqh[UHCI_SKEL_INDEX(exponent)]; | 1119 | qh->skel = SKEL_INDEX(exponent); |
971 | 1120 | ||
972 | /* For now, interrupt phase is fixed by the layout | 1121 | /* For now, interrupt phase is fixed by the layout |
973 | * of the QH lists. */ | 1122 | * of the QH lists. */ |
@@ -1215,7 +1364,7 @@ static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb, | |||
1215 | qh->iso_status = 0; | 1364 | qh->iso_status = 0; |
1216 | } | 1365 | } |
1217 | 1366 | ||
1218 | qh->skel = uhci->skel_iso_qh; | 1367 | qh->skel = SKEL_ISO; |
1219 | if (!qh->bandwidth_reserved) | 1368 | if (!qh->bandwidth_reserved) |
1220 | uhci_reserve_bandwidth(uhci, qh); | 1369 | uhci_reserve_bandwidth(uhci, qh); |
1221 | return 0; | 1370 | return 0; |