xhci: free xhci virtual devices with leaf nodes first
[pandora-kernel.git] / drivers / usb / host / ohci-q.c
index 15dc51d..6c43152 100644 (file)
@@ -294,8 +294,7 @@ static void periodic_unlink (struct ohci_hcd *ohci, struct ed *ed)
  *  - ED_OPER: when there's any request queued, the ED gets rescheduled
  *    immediately.  HC should be working on them.
  *
- *  - ED_IDLE:  when there's no TD queue. there's no reason for the HC
- *    to care about this ED; safe to disable the endpoint.
+ *  - ED_IDLE: when there's no TD queue or the HC isn't running.
  *
  * When finish_unlinks() runs later, after SOF interrupt, it will often
  * complete one or more URB unlinks before making that state change.
@@ -909,6 +908,10 @@ rescan_all:
                int                     completed, modified;
                __hc32                  *prev;
 
+               /* Is this ED already invisible to the hardware? */
+               if (ed->state == ED_IDLE)
+                       goto ed_idle;
+
                /* only take off EDs that the HC isn't using, accounting for
                 * frame counter wraps and EDs with partially retired TDs
                 */
@@ -938,12 +941,20 @@ skip_ed:
                        }
                }
 
+               /* ED's now officially unlinked, hc doesn't see */
+               ed->state = ED_IDLE;
+               if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
+                       ohci->eds_scheduled--;
+               ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H);
+               ed->hwNextED = 0;
+               wmb();
+               ed->hwINFO &= ~cpu_to_hc32(ohci, ED_SKIP | ED_DEQUEUE);
+ed_idle:
+
                /* reentrancy:  if we drop the schedule lock, someone might
                 * have modified this list.  normally it's just prepending
                 * entries (which we'd ignore), but paranoia won't hurt.
                 */
-               *last = ed->ed_next;
-               ed->ed_next = NULL;
                modified = 0;
 
                /* unlink urbs as requested, but rescan the list after
@@ -1001,19 +1012,20 @@ rescan_this:
                if (completed && !list_empty (&ed->td_list))
                        goto rescan_this;
 
-               /* ED's now officially unlinked, hc doesn't see */
-               ed->state = ED_IDLE;
-               if (quirk_zfmicro(ohci) && ed->type == PIPE_INTERRUPT)
-                       ohci->eds_scheduled--;
-               ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_H);
-               ed->hwNextED = 0;
-               wmb ();
-               ed->hwINFO &= ~cpu_to_hc32 (ohci, ED_SKIP | ED_DEQUEUE);
-
-               /* but if there's work queued, reschedule */
-               if (!list_empty (&ed->td_list)) {
-                       if (HC_IS_RUNNING(ohci_to_hcd(ohci)->state))
-                               ed_schedule (ohci, ed);
+               /*
+                * If no TDs are queued, take ED off the ed_rm_list.
+                * Otherwise, if the HC is running, reschedule.
+                * If not, leave it on the list for further dequeues.
+                */
+               if (list_empty(&ed->td_list)) {
+                       *last = ed->ed_next;
+                       ed->ed_next = NULL;
+               } else if (HC_IS_RUNNING(ohci_to_hcd(ohci)->state)) {
+                       *last = ed->ed_next;
+                       ed->ed_next = NULL;
+                       ed_schedule(ohci, ed);
+               } else {
+                       last = &ed->ed_next;
                }
 
                if (modified)
@@ -1130,6 +1142,25 @@ dl_done_list (struct ohci_hcd *ohci)
 
        while (td) {
                struct td       *td_next = td->next_dl_td;
+               struct ed       *ed = td->ed;
+
+               /*
+                * Some OHCI controllers (NVIDIA for sure, maybe others)
+                * occasionally forget to add TDs to the done queue.  Since
+                * TDs for a given endpoint are always processed in order,
+                * if we find a TD on the donelist then all of its
+                * predecessors must be finished as well.
+                */
+               for (;;) {
+                       struct td       *td2;
+
+                       td2 = list_first_entry(&ed->td_list, struct td,
+                                       td_list);
+                       if (td2 == td)
+                               break;
+                       takeback_td(ohci, td2);
+               }
+
                takeback_td(ohci, td);
                td = td_next;
        }