]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
USB: EHCI: slow down ITD reuse
authorKarsten Wiese <fzu@wemgehoertderstaat.de>
Mon, 9 Feb 2009 00:07:58 +0000 (16:07 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 17 Mar 2009 00:32:07 +0000 (17:32 -0700)
commit 9aa09d2f8f4bc440d6db1c3414d4009642875240 upstream.

Currently ITDs are immediately recycled whenever their URB completes.
However, EHCI hardware can sometimes remember some ITD state.  This
means that when the ITD is reused before end-of-frame it may sometimes
cause the hardware to reference bogus state.

This patch defers reusing such ITDs by moving them into a new ehci member
cached_itd_list. ITDs resting in cached_itd_list are moved back into their
stream's free_list once scan_periodic() detects that the active frame has
elapsed.

This makes the snd_usb_us122l driver (in kernel since .28) work right
when it's hooked up through EHCI.

[ dbrownell@users.sourceforge.net: comment fixups ]

Signed-off-by: Karsten Wiese <fzu@wemgehoertderstaat.de>
Tested-by: Philippe Carriere <philippe-f.carriere@wanadoo.fr>
Tested-by: Federico Briata <federicobriata@gmail.com>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci-mem.c
drivers/usb/host/ehci-sched.c
drivers/usb/host/ehci.h

index 4725d15d096f559fc833d0e0f6323bcff4aa7631..e551bb38852be35eefeb532a23c260ecff6109ff 100644 (file)
@@ -485,6 +485,7 @@ static int ehci_init(struct usb_hcd *hcd)
         * periodic_size can shrink by USBCMD update if hcc_params allows.
         */
        ehci->periodic_size = DEFAULT_I_TDPS;
+       INIT_LIST_HEAD(&ehci->cached_itd_list);
        if ((retval = ehci_mem_init(ehci, GFP_KERNEL)) < 0)
                return retval;
 
@@ -497,6 +498,7 @@ static int ehci_init(struct usb_hcd *hcd)
 
        ehci->reclaim = NULL;
        ehci->next_uframe = -1;
+       ehci->clock_frame = -1;
 
        /*
         * dedicate a qh for the async ring head, since we couldn't unlink
index 0431397836f65e859f1a2b1829f418fa3697f65d..10d52919abbb1513feefe95ecaa5c5aa518a7443 100644 (file)
@@ -128,6 +128,7 @@ static inline void qh_put (struct ehci_qh *qh)
 
 static void ehci_mem_cleanup (struct ehci_hcd *ehci)
 {
+       free_cached_itd_list(ehci);
        if (ehci->async)
                qh_put (ehci->async);
        ehci->async = NULL;
index a081ee65bde6d933f3f2000c67028d461ece27c7..07bcb931021b48a9b00ffeac887288448c7d696e 100644 (file)
@@ -1004,7 +1004,8 @@ iso_stream_put(struct ehci_hcd *ehci, struct ehci_iso_stream *stream)
 
                is_in = (stream->bEndpointAddress & USB_DIR_IN) ? 0x10 : 0;
                stream->bEndpointAddress &= 0x0f;
-               stream->ep->hcpriv = NULL;
+               if (stream->ep)
+                       stream->ep->hcpriv = NULL;
 
                if (stream->rescheduled) {
                        ehci_info (ehci, "ep%d%s-iso rescheduled "
@@ -1653,14 +1654,28 @@ itd_complete (
                        (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out");
        }
        iso_stream_put (ehci, stream);
-       /* OK to recycle this ITD now that its completion callback ran. */
+
 done:
        usb_put_urb(urb);
        itd->urb = NULL;
-       itd->stream = NULL;
-       list_move(&itd->itd_list, &stream->free_list);
-       iso_stream_put(ehci, stream);
-
+       if (ehci->clock_frame != itd->frame || itd->index[7] != -1) {
+               /* OK to recycle this ITD now. */
+               itd->stream = NULL;
+               list_move(&itd->itd_list, &stream->free_list);
+               iso_stream_put(ehci, stream);
+       } else {
+               /* HW might remember this ITD, so we can't recycle it yet.
+                * Move it to a safe place until a new frame starts.
+                */
+               list_move(&itd->itd_list, &ehci->cached_itd_list);
+               if (stream->refcount == 2) {
+                       /* If iso_stream_put() were called here, stream
+                        * would be freed.  Instead, just prevent reuse.
+                        */
+                       stream->ep->hcpriv = NULL;
+                       stream->ep = NULL;
+               }
+       }
        return retval;
 }
 
@@ -2101,6 +2116,20 @@ done:
 
 /*-------------------------------------------------------------------------*/
 
+static void free_cached_itd_list(struct ehci_hcd *ehci)
+{
+       struct ehci_itd *itd, *n;
+
+       list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) {
+               struct ehci_iso_stream  *stream = itd->stream;
+               itd->stream = NULL;
+               list_move(&itd->itd_list, &stream->free_list);
+               iso_stream_put(ehci, stream);
+       }
+}
+
+/*-------------------------------------------------------------------------*/
+
 static void
 scan_periodic (struct ehci_hcd *ehci)
 {
@@ -2115,10 +2144,17 @@ scan_periodic (struct ehci_hcd *ehci)
         * Touches as few pages as possible:  cache-friendly.
         */
        now_uframe = ehci->next_uframe;
-       if (HC_IS_RUNNING (ehci_to_hcd(ehci)->state))
+       if (HC_IS_RUNNING(ehci_to_hcd(ehci)->state)) {
                clock = ehci_readl(ehci, &ehci->regs->frame_index);
-       else
+               clock_frame = (clock >> 3) % ehci->periodic_size;
+       } else  {
                clock = now_uframe + mod - 1;
+               clock_frame = -1;
+       }
+       if (ehci->clock_frame != clock_frame) {
+               free_cached_itd_list(ehci);
+               ehci->clock_frame = clock_frame;
+       }
        clock %= mod;
        clock_frame = clock >> 3;
 
@@ -2277,6 +2313,10 @@ restart:
                        /* rescan the rest of this frame, then ... */
                        clock = now;
                        clock_frame = clock >> 3;
+                       if (ehci->clock_frame != clock_frame) {
+                               free_cached_itd_list(ehci);
+                               ehci->clock_frame = clock_frame;
+                       }
                } else {
                        now_uframe++;
                        now_uframe %= mod;
index c7d4b5a06bdb049e424228560a9d0b51f4baf7fd..fe976881320a58ba6e9512aa718a3d124e36953a 100644 (file)
@@ -87,6 +87,10 @@ struct ehci_hcd {                    /* one per controller */
        int                     next_uframe;    /* scan periodic, start here */
        unsigned                periodic_sched; /* periodic activity count */
 
+       /* list of itds completed while clock_frame was still active */
+       struct list_head        cached_itd_list;
+       unsigned                clock_frame;
+
        /* per root hub port */
        unsigned long           reset_done [EHCI_MAX_ROOT_PORTS];
 
@@ -210,6 +214,8 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
        }
 }
 
+static void free_cached_itd_list(struct ehci_hcd *ehci);
+
 /*-------------------------------------------------------------------------*/
 
 #include <linux/usb/ehci_def.h>