提交 9aa09d2f 编写于 作者: K Karsten Wiese 提交者: Greg Kroah-Hartman

USB: EHCI: slow down ITD reuse

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: NKarsten Wiese <fzu@wemgehoertderstaat.de>
Tested-by: NPhilippe Carriere <philippe-f.carriere@wanadoo.fr>
Tested-by: NFederico Briata <federicobriata@gmail.com>
Cc: stable <stable@kernel.org>
Signed-off-by: NDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: NGreg Kroah-Hartman <gregkh@suse.de>
上级 9a6e184c
...@@ -485,6 +485,7 @@ static int ehci_init(struct usb_hcd *hcd) ...@@ -485,6 +485,7 @@ static int ehci_init(struct usb_hcd *hcd)
* periodic_size can shrink by USBCMD update if hcc_params allows. * periodic_size can shrink by USBCMD update if hcc_params allows.
*/ */
ehci->periodic_size = DEFAULT_I_TDPS; ehci->periodic_size = DEFAULT_I_TDPS;
INIT_LIST_HEAD(&ehci->cached_itd_list);
if ((retval = ehci_mem_init(ehci, GFP_KERNEL)) < 0) if ((retval = ehci_mem_init(ehci, GFP_KERNEL)) < 0)
return retval; return retval;
...@@ -497,6 +498,7 @@ static int ehci_init(struct usb_hcd *hcd) ...@@ -497,6 +498,7 @@ static int ehci_init(struct usb_hcd *hcd)
ehci->reclaim = NULL; ehci->reclaim = NULL;
ehci->next_uframe = -1; ehci->next_uframe = -1;
ehci->clock_frame = -1;
/* /*
* dedicate a qh for the async ring head, since we couldn't unlink * dedicate a qh for the async ring head, since we couldn't unlink
......
...@@ -128,6 +128,7 @@ static inline void qh_put (struct ehci_qh *qh) ...@@ -128,6 +128,7 @@ static inline void qh_put (struct ehci_qh *qh)
static void ehci_mem_cleanup (struct ehci_hcd *ehci) static void ehci_mem_cleanup (struct ehci_hcd *ehci)
{ {
free_cached_itd_list(ehci);
if (ehci->async) if (ehci->async)
qh_put (ehci->async); qh_put (ehci->async);
ehci->async = NULL; ehci->async = NULL;
......
...@@ -1004,6 +1004,7 @@ iso_stream_put(struct ehci_hcd *ehci, struct ehci_iso_stream *stream) ...@@ -1004,6 +1004,7 @@ iso_stream_put(struct ehci_hcd *ehci, struct ehci_iso_stream *stream)
is_in = (stream->bEndpointAddress & USB_DIR_IN) ? 0x10 : 0; is_in = (stream->bEndpointAddress & USB_DIR_IN) ? 0x10 : 0;
stream->bEndpointAddress &= 0x0f; stream->bEndpointAddress &= 0x0f;
if (stream->ep)
stream->ep->hcpriv = NULL; stream->ep->hcpriv = NULL;
if (stream->rescheduled) { if (stream->rescheduled) {
...@@ -1653,14 +1654,28 @@ itd_complete ( ...@@ -1653,14 +1654,28 @@ itd_complete (
(stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out"); (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out");
} }
iso_stream_put (ehci, stream); iso_stream_put (ehci, stream);
/* OK to recycle this ITD now that its completion callback ran. */
done: done:
usb_put_urb(urb); usb_put_urb(urb);
itd->urb = NULL; itd->urb = NULL;
if (ehci->clock_frame != itd->frame || itd->index[7] != -1) {
/* OK to recycle this ITD now. */
itd->stream = NULL; itd->stream = NULL;
list_move(&itd->itd_list, &stream->free_list); list_move(&itd->itd_list, &stream->free_list);
iso_stream_put(ehci, stream); 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; return retval;
} }
...@@ -2101,6 +2116,20 @@ static int sitd_submit (struct ehci_hcd *ehci, struct urb *urb, ...@@ -2101,6 +2116,20 @@ static int sitd_submit (struct ehci_hcd *ehci, struct urb *urb,
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
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 static void
scan_periodic (struct ehci_hcd *ehci) scan_periodic (struct ehci_hcd *ehci)
{ {
...@@ -2115,10 +2144,17 @@ scan_periodic (struct ehci_hcd *ehci) ...@@ -2115,10 +2144,17 @@ scan_periodic (struct ehci_hcd *ehci)
* Touches as few pages as possible: cache-friendly. * Touches as few pages as possible: cache-friendly.
*/ */
now_uframe = ehci->next_uframe; 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); clock = ehci_readl(ehci, &ehci->regs->frame_index);
else clock_frame = (clock >> 3) % ehci->periodic_size;
} else {
clock = now_uframe + mod - 1; 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 %= mod;
clock_frame = clock >> 3; clock_frame = clock >> 3;
...@@ -2277,6 +2313,10 @@ scan_periodic (struct ehci_hcd *ehci) ...@@ -2277,6 +2313,10 @@ scan_periodic (struct ehci_hcd *ehci)
/* rescan the rest of this frame, then ... */ /* rescan the rest of this frame, then ... */
clock = now; clock = now;
clock_frame = clock >> 3; clock_frame = clock >> 3;
if (ehci->clock_frame != clock_frame) {
free_cached_itd_list(ehci);
ehci->clock_frame = clock_frame;
}
} else { } else {
now_uframe++; now_uframe++;
now_uframe %= mod; now_uframe %= mod;
......
...@@ -87,6 +87,10 @@ struct ehci_hcd { /* one per controller */ ...@@ -87,6 +87,10 @@ struct ehci_hcd { /* one per controller */
int next_uframe; /* scan periodic, start here */ int next_uframe; /* scan periodic, start here */
unsigned periodic_sched; /* periodic activity count */ 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 */ /* per root hub port */
unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; unsigned long reset_done [EHCI_MAX_ROOT_PORTS];
...@@ -220,6 +224,8 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) ...@@ -220,6 +224,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> #include <linux/usb/ehci_def.h>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册