Commit 0e5f231b authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: EHCI: defer reclamation of siTDs

This patch (as1369) fixes a problem in ehci-hcd.  Some controllers
occasionally run into trouble when the driver reclaims siTDs too
quickly.  This can happen while streaming audio; it causes the
controller to crash.

The patch changes siTD reclamation to work the same way as iTD
reclamation: Completed siTDs are stored on a list and not reused until
at least one frame has passed.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Tested-by: default avatarNate Case <ncase@xes-inc.com>
CC: <stable@kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 5f677f1d
...@@ -543,6 +543,7 @@ static int ehci_init(struct usb_hcd *hcd) ...@@ -543,6 +543,7 @@ static int ehci_init(struct usb_hcd *hcd)
*/ */
ehci->periodic_size = DEFAULT_I_TDPS; ehci->periodic_size = DEFAULT_I_TDPS;
INIT_LIST_HEAD(&ehci->cached_itd_list); INIT_LIST_HEAD(&ehci->cached_itd_list);
INIT_LIST_HEAD(&ehci->cached_sitd_list);
if ((retval = ehci_mem_init(ehci, GFP_KERNEL)) < 0) if ((retval = ehci_mem_init(ehci, GFP_KERNEL)) < 0)
return retval; return retval;
......
...@@ -136,7 +136,7 @@ static inline void qh_put (struct ehci_qh *qh) ...@@ -136,7 +136,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); free_cached_lists(ehci);
if (ehci->async) if (ehci->async)
qh_put (ehci->async); qh_put (ehci->async);
ehci->async = NULL; ehci->async = NULL;
......
...@@ -510,7 +510,7 @@ static int disable_periodic (struct ehci_hcd *ehci) ...@@ -510,7 +510,7 @@ static int disable_periodic (struct ehci_hcd *ehci)
ehci_writel(ehci, cmd, &ehci->regs->command); ehci_writel(ehci, cmd, &ehci->regs->command);
/* posted write ... */ /* posted write ... */
free_cached_itd_list(ehci); free_cached_lists(ehci);
ehci->next_uframe = -1; ehci->next_uframe = -1;
return 0; return 0;
...@@ -2139,13 +2139,27 @@ sitd_complete ( ...@@ -2139,13 +2139,27 @@ sitd_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 SITD now that its completion callback ran. */
done: done:
sitd->urb = NULL; sitd->urb = NULL;
sitd->stream = NULL; if (ehci->clock_frame != sitd->frame) {
list_move(&sitd->sitd_list, &stream->free_list); /* OK to recycle this SITD now. */
iso_stream_put(ehci, stream); sitd->stream = NULL;
list_move(&sitd->sitd_list, &stream->free_list);
iso_stream_put(ehci, stream);
} else {
/* HW might remember this SITD, so we can't recycle it yet.
* Move it to a safe place until a new frame starts.
*/
list_move(&sitd->sitd_list, &ehci->cached_sitd_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;
} }
...@@ -2211,9 +2225,10 @@ done: ...@@ -2211,9 +2225,10 @@ done:
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
static void free_cached_itd_list(struct ehci_hcd *ehci) static void free_cached_lists(struct ehci_hcd *ehci)
{ {
struct ehci_itd *itd, *n; struct ehci_itd *itd, *n;
struct ehci_sitd *sitd, *sn;
list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) { list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) {
struct ehci_iso_stream *stream = itd->stream; struct ehci_iso_stream *stream = itd->stream;
...@@ -2221,6 +2236,13 @@ static void free_cached_itd_list(struct ehci_hcd *ehci) ...@@ -2221,6 +2236,13 @@ static void free_cached_itd_list(struct ehci_hcd *ehci)
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);
} }
list_for_each_entry_safe(sitd, sn, &ehci->cached_sitd_list, sitd_list) {
struct ehci_iso_stream *stream = sitd->stream;
sitd->stream = NULL;
list_move(&sitd->sitd_list, &stream->free_list);
iso_stream_put(ehci, stream);
}
} }
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
...@@ -2247,7 +2269,7 @@ scan_periodic (struct ehci_hcd *ehci) ...@@ -2247,7 +2269,7 @@ scan_periodic (struct ehci_hcd *ehci)
clock_frame = -1; clock_frame = -1;
} }
if (ehci->clock_frame != clock_frame) { if (ehci->clock_frame != clock_frame) {
free_cached_itd_list(ehci); free_cached_lists(ehci);
ehci->clock_frame = clock_frame; ehci->clock_frame = clock_frame;
} }
clock %= mod; clock %= mod;
...@@ -2414,7 +2436,7 @@ restart: ...@@ -2414,7 +2436,7 @@ restart:
clock = now; clock = now;
clock_frame = clock >> 3; clock_frame = clock >> 3;
if (ehci->clock_frame != clock_frame) { if (ehci->clock_frame != clock_frame) {
free_cached_itd_list(ehci); free_cached_lists(ehci);
ehci->clock_frame = clock_frame; ehci->clock_frame = clock_frame;
} }
} else { } else {
......
...@@ -87,8 +87,9 @@ struct ehci_hcd { /* one per controller */ ...@@ -87,8 +87,9 @@ 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 */ /* list of itds & sitds completed while clock_frame was still active */
struct list_head cached_itd_list; struct list_head cached_itd_list;
struct list_head cached_sitd_list;
unsigned clock_frame; unsigned clock_frame;
/* per root hub port */ /* per root hub port */
...@@ -195,7 +196,7 @@ timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action) ...@@ -195,7 +196,7 @@ timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action)
clear_bit (action, &ehci->actions); clear_bit (action, &ehci->actions);
} }
static void free_cached_itd_list(struct ehci_hcd *ehci); static void free_cached_lists(struct ehci_hcd *ehci);
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment