aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2014-07-18 16:25:59 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-07-18 19:30:46 -0400
commitc6fcb85ea22889527ee44aba42c3e3b479fd2d92 (patch)
tree37ef7947b0b0aa53aa49bf6c5cbc7fca3204b1ea
parent8b3ab0edaf6acd281243bf974fac7e01c9574d08 (diff)
USB: OHCI: redesign the TD done list
This patch changes the way ohci-hcd handles the TD done list. In addition to relying on the TD pointers stored by the controller hardware, we need to handle TDs that the hardware has forgotten about. This means the list has to exist even while the dl_done_list() routine isn't running. That function essentially gets split in two: update_done_list() reads the TD pointers stored by the hardware and adds the TDs to the done list, and process_done_list() scans through the list to handle URB completions. When we detect a TD that the hardware forgot about, we will be able to add it to the done list manually and then process it normally. Since the list is really a queue, and because there can be a lot of TDs, keep the existing singly linked implementation. To insure that URBs are given back in order of submission, whenever a TD is added to the done list, all the preceding TDs for the same endpoint must be added as well (going back to the first one that isn't already on the done list). The done list manipulations must all be protected by the private lock. The scope of the lock is expanded in preparation for the watchdog routine to be added in a later patch. We have to be more careful about giving back unlinked URBs. Since TDs may be added to the done list by the watchdog routine and not in response to a controller interrupt, we have to check explicitly to make sure all the URB's TDs that were added to the done list have been processed before giving back the URB. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/usb/host/ohci-hcd.c12
-rw-r--r--drivers/usb/host/ohci-hub.c6
-rw-r--r--drivers/usb/host/ohci-q.c109
-rw-r--r--drivers/usb/host/ohci.h1
4 files changed, 70 insertions, 58 deletions
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 52829276a44e..3112799bba7f 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -780,24 +780,21 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
780 usb_hcd_resume_root_hub(hcd); 780 usb_hcd_resume_root_hub(hcd);
781 } 781 }
782 782
783 if (ints & OHCI_INTR_WDH) { 783 spin_lock(&ohci->lock);
784 spin_lock (&ohci->lock); 784 if (ints & OHCI_INTR_WDH)
785 dl_done_list (ohci); 785 update_done_list(ohci);
786 spin_unlock (&ohci->lock);
787 }
788 786
789 /* could track INTR_SO to reduce available PCI/... bandwidth */ 787 /* could track INTR_SO to reduce available PCI/... bandwidth */
790 788
791 /* handle any pending URB/ED unlinks, leaving INTR_SF enabled 789 /* handle any pending URB/ED unlinks, leaving INTR_SF enabled
792 * when there's still unlinking to be done (next frame). 790 * when there's still unlinking to be done (next frame).
793 */ 791 */
794 spin_lock (&ohci->lock); 792 process_done_list(ohci);
795 if (ohci->ed_rm_list) 793 if (ohci->ed_rm_list)
796 finish_unlinks (ohci, ohci_frame_no(ohci)); 794 finish_unlinks (ohci, ohci_frame_no(ohci));
797 if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list 795 if ((ints & OHCI_INTR_SF) != 0 && !ohci->ed_rm_list
798 && ohci->rh_state == OHCI_RH_RUNNING) 796 && ohci->rh_state == OHCI_RH_RUNNING)
799 ohci_writel (ohci, OHCI_INTR_SF, &regs->intrdisable); 797 ohci_writel (ohci, OHCI_INTR_SF, &regs->intrdisable);
800 spin_unlock (&ohci->lock);
801 798
802 if (ohci->rh_state == OHCI_RH_RUNNING) { 799 if (ohci->rh_state == OHCI_RH_RUNNING) {
803 ohci_writel (ohci, ints, &regs->intrstatus); 800 ohci_writel (ohci, ints, &regs->intrstatus);
@@ -805,6 +802,7 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd)
805 // flush those writes 802 // flush those writes
806 (void) ohci_readl (ohci, &ohci->regs->control); 803 (void) ohci_readl (ohci, &ohci->regs->control);
807 } 804 }
805 spin_unlock(&ohci->lock);
808 806
809 return IRQ_HANDLED; 807 return IRQ_HANDLED;
810} 808}
diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c
index b4940de1eba3..dccb90edd66e 100644
--- a/drivers/usb/host/ohci-hub.c
+++ b/drivers/usb/host/ohci-hub.c
@@ -39,7 +39,8 @@
39#define OHCI_SCHED_ENABLES \ 39#define OHCI_SCHED_ENABLES \
40 (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE) 40 (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE)
41 41
42static void dl_done_list (struct ohci_hcd *); 42static void update_done_list(struct ohci_hcd *);
43static void process_done_list(struct ohci_hcd *);
43static void finish_unlinks (struct ohci_hcd *, u16); 44static void finish_unlinks (struct ohci_hcd *, u16);
44 45
45#ifdef CONFIG_PM 46#ifdef CONFIG_PM
@@ -87,7 +88,8 @@ __acquires(ohci->lock)
87 msleep (8); 88 msleep (8);
88 spin_lock_irq (&ohci->lock); 89 spin_lock_irq (&ohci->lock);
89 } 90 }
90 dl_done_list (ohci); 91 update_done_list(ohci);
92 process_done_list(ohci);
91 finish_unlinks (ohci, ohci_frame_no(ohci)); 93 finish_unlinks (ohci, ohci_frame_no(ohci));
92 94
93 /* 95 /*
diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
index a9f4f04c3fad..f36b2fa0ee2f 100644
--- a/drivers/usb/host/ohci-q.c
+++ b/drivers/usb/host/ohci-q.c
@@ -892,13 +892,41 @@ static void ed_halted(struct ohci_hcd *ohci, struct td *td, int cc)
892 } 892 }
893} 893}
894 894
895/* replies to the request have to be on a FIFO basis so 895/* Add a TD to the done list */
896 * we unreverse the hc-reversed done-list 896static void add_to_done_list(struct ohci_hcd *ohci, struct td *td)
897 */ 897{
898static struct td *dl_reverse_done_list (struct ohci_hcd *ohci) 898 struct td *td2, *td_prev;
899 struct ed *ed;
900
901 if (td->next_dl_td)
902 return; /* Already on the list */
903
904 /* Add all the TDs going back until we reach one that's on the list */
905 ed = td->ed;
906 td2 = td_prev = td;
907 list_for_each_entry_continue_reverse(td2, &ed->td_list, td_list) {
908 if (td2->next_dl_td)
909 break;
910 td2->next_dl_td = td_prev;
911 td_prev = td2;
912 }
913
914 if (ohci->dl_end)
915 ohci->dl_end->next_dl_td = td_prev;
916 else
917 ohci->dl_start = td_prev;
918
919 /*
920 * Make td->next_dl_td point to td itself, to mark the fact
921 * that td is on the done list.
922 */
923 ohci->dl_end = td->next_dl_td = td;
924}
925
926/* Get the entries on the hardware done queue and put them on our list */
927static void update_done_list(struct ohci_hcd *ohci)
899{ 928{
900 u32 td_dma; 929 u32 td_dma;
901 struct td *td_rev = NULL;
902 struct td *td = NULL; 930 struct td *td = NULL;
903 931
904 td_dma = hc32_to_cpup (ohci, &ohci->hcca->done_head); 932 td_dma = hc32_to_cpup (ohci, &ohci->hcca->done_head);
@@ -906,7 +934,7 @@ static struct td *dl_reverse_done_list (struct ohci_hcd *ohci)
906 wmb(); 934 wmb();
907 935
908 /* get TD from hc's singly linked list, and 936 /* get TD from hc's singly linked list, and
909 * prepend to ours. ed->td_list changes later. 937 * add to ours. ed->td_list changes later.
910 */ 938 */
911 while (td_dma) { 939 while (td_dma) {
912 int cc; 940 int cc;
@@ -928,11 +956,9 @@ static struct td *dl_reverse_done_list (struct ohci_hcd *ohci)
928 && (td->ed->hwHeadP & cpu_to_hc32 (ohci, ED_H))) 956 && (td->ed->hwHeadP & cpu_to_hc32 (ohci, ED_H)))
929 ed_halted(ohci, td, cc); 957 ed_halted(ohci, td, cc);
930 958
931 td->next_dl_td = td_rev;
932 td_rev = td;
933 td_dma = hc32_to_cpup (ohci, &td->hwNextTD); 959 td_dma = hc32_to_cpup (ohci, &td->hwNextTD);
960 add_to_done_list(ohci, td);
934 } 961 }
935 return td_rev;
936} 962}
937 963
938/*-------------------------------------------------------------------------*/ 964/*-------------------------------------------------------------------------*/
@@ -956,26 +982,27 @@ rescan_all:
956 /* only take off EDs that the HC isn't using, accounting for 982 /* only take off EDs that the HC isn't using, accounting for
957 * frame counter wraps and EDs with partially retired TDs 983 * frame counter wraps and EDs with partially retired TDs
958 */ 984 */
959 if (likely(ohci->rh_state == OHCI_RH_RUNNING)) { 985 if (likely(ohci->rh_state == OHCI_RH_RUNNING) &&
960 if (tick_before (tick, ed->tick)) { 986 tick_before(tick, ed->tick)) {
961skip_ed: 987skip_ed:
962 last = &ed->ed_next; 988 last = &ed->ed_next;
963 continue; 989 continue;
964 } 990 }
991 if (!list_empty(&ed->td_list)) {
992 struct td *td;
993 u32 head;
965 994
966 if (!list_empty (&ed->td_list)) { 995 td = list_first_entry(&ed->td_list, struct td, td_list);
967 struct td *td;
968 u32 head;
969 996
970 td = list_entry (ed->td_list.next, struct td, 997 /* INTR_WDH may need to clean up first */
971 td_list); 998 head = hc32_to_cpu(ohci, ed->hwHeadP) & TD_MASK;
972 head = hc32_to_cpu (ohci, ed->hwHeadP) & 999 if (td->td_dma != head &&
973 TD_MASK; 1000 ohci->rh_state == OHCI_RH_RUNNING)
1001 goto skip_ed;
974 1002
975 /* INTR_WDH may need to clean up first */ 1003 /* Don't mess up anything already on the done list */
976 if (td->td_dma != head) 1004 if (td->next_dl_td)
977 goto skip_ed; 1005 goto skip_ed;
978 }
979 } 1006 }
980 1007
981 /* ED's now officially unlinked, hc doesn't see */ 1008 /* ED's now officially unlinked, hc doesn't see */
@@ -1161,33 +1188,17 @@ static void takeback_td(struct ohci_hcd *ohci, struct td *td)
1161 * normal path is finish_unlinks(), which unlinks URBs using ed_rm_list, 1188 * normal path is finish_unlinks(), which unlinks URBs using ed_rm_list,
1162 * instead of scanning the (re-reversed) donelist as this does. 1189 * instead of scanning the (re-reversed) donelist as this does.
1163 */ 1190 */
1164static void 1191static void process_done_list(struct ohci_hcd *ohci)
1165dl_done_list (struct ohci_hcd *ohci)
1166{ 1192{
1167 struct td *td = dl_reverse_done_list (ohci); 1193 struct td *td;
1168
1169 while (td) {
1170 struct td *td_next = td->next_dl_td;
1171 struct ed *ed = td->ed;
1172 1194
1173 /* 1195 while (ohci->dl_start) {
1174 * Some OHCI controllers (NVIDIA for sure, maybe others) 1196 td = ohci->dl_start;
1175 * occasionally forget to add TDs to the done queue. Since 1197 if (td == ohci->dl_end)
1176 * TDs for a given endpoint are always processed in order, 1198 ohci->dl_start = ohci->dl_end = NULL;
1177 * if we find a TD on the donelist then all of its 1199 else
1178 * predecessors must be finished as well. 1200 ohci->dl_start = td->next_dl_td;
1179 */
1180 for (;;) {
1181 struct td *td2;
1182
1183 td2 = list_first_entry(&ed->td_list, struct td,
1184 td_list);
1185 if (td2 == td)
1186 break;
1187 takeback_td(ohci, td2);
1188 }
1189 1201
1190 takeback_td(ohci, td); 1202 takeback_td(ohci, td);
1191 td = td_next;
1192 } 1203 }
1193} 1204}
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index 392932dd6318..a8259bc6fd8b 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -380,6 +380,7 @@ struct ohci_hcd {
380 struct dma_pool *td_cache; 380 struct dma_pool *td_cache;
381 struct dma_pool *ed_cache; 381 struct dma_pool *ed_cache;
382 struct td *td_hash [TD_HASH_SIZE]; 382 struct td *td_hash [TD_HASH_SIZE];
383 struct td *dl_start, *dl_end; /* the done list */
383 struct list_head pending; 384 struct list_head pending;
384 385
385 /* 386 /*