| From 0e5f231bc16ff9910882fa5b9d64d80e7691cfab Mon Sep 17 00:00:00 2001 |
| From: Alan Stern <stern@rowland.harvard.edu> |
| Date: Thu, 8 Apr 2010 16:56:37 -0400 |
| Subject: USB: EHCI: defer reclamation of siTDs |
| |
| From: Alan Stern <stern@rowland.harvard.edu> |
| |
| commit 0e5f231bc16ff9910882fa5b9d64d80e7691cfab upstream. |
| |
| 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: Alan Stern <stern@rowland.harvard.edu> |
| Tested-by: Nate Case <ncase@xes-inc.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| |
| --- |
| drivers/usb/host/ehci-hcd.c | 1 + |
| drivers/usb/host/ehci-mem.c | 2 +- |
| drivers/usb/host/ehci-sched.c | 38 ++++++++++++++++++++++++++++++-------- |
| drivers/usb/host/ehci.h | 5 +++-- |
| 4 files changed, 35 insertions(+), 11 deletions(-) |
| |
| --- a/drivers/usb/host/ehci-hcd.c |
| +++ b/drivers/usb/host/ehci-hcd.c |
| @@ -543,6 +543,7 @@ static int ehci_init(struct usb_hcd *hcd |
| */ |
| ehci->periodic_size = DEFAULT_I_TDPS; |
| INIT_LIST_HEAD(&ehci->cached_itd_list); |
| + INIT_LIST_HEAD(&ehci->cached_sitd_list); |
| if ((retval = ehci_mem_init(ehci, GFP_KERNEL)) < 0) |
| return retval; |
| |
| --- a/drivers/usb/host/ehci-mem.c |
| +++ b/drivers/usb/host/ehci-mem.c |
| @@ -136,7 +136,7 @@ static inline void qh_put (struct ehci_q |
| |
| static void ehci_mem_cleanup (struct ehci_hcd *ehci) |
| { |
| - free_cached_itd_list(ehci); |
| + free_cached_lists(ehci); |
| if (ehci->async) |
| qh_put (ehci->async); |
| ehci->async = NULL; |
| --- a/drivers/usb/host/ehci-sched.c |
| +++ b/drivers/usb/host/ehci-sched.c |
| @@ -2137,13 +2137,27 @@ sitd_complete ( |
| (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out"); |
| } |
| iso_stream_put (ehci, stream); |
| - /* OK to recycle this SITD now that its completion callback ran. */ |
| + |
| done: |
| sitd->urb = NULL; |
| - sitd->stream = NULL; |
| - list_move(&sitd->sitd_list, &stream->free_list); |
| - iso_stream_put(ehci, stream); |
| - |
| + if (ehci->clock_frame != sitd->frame) { |
| + /* OK to recycle this SITD now. */ |
| + 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; |
| } |
| |
| @@ -2209,9 +2223,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_sitd *sitd, *sn; |
| |
| list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) { |
| struct ehci_iso_stream *stream = itd->stream; |
| @@ -2219,6 +2234,13 @@ static void free_cached_itd_list(struct |
| list_move(&itd->itd_list, &stream->free_list); |
| 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); |
| + } |
| } |
| |
| /*-------------------------------------------------------------------------*/ |
| @@ -2245,7 +2267,7 @@ scan_periodic (struct ehci_hcd *ehci) |
| clock_frame = -1; |
| } |
| if (ehci->clock_frame != clock_frame) { |
| - free_cached_itd_list(ehci); |
| + free_cached_lists(ehci); |
| ehci->clock_frame = clock_frame; |
| } |
| clock %= mod; |
| @@ -2408,7 +2430,7 @@ restart: |
| clock = now; |
| clock_frame = clock >> 3; |
| if (ehci->clock_frame != clock_frame) { |
| - free_cached_itd_list(ehci); |
| + free_cached_lists(ehci); |
| ehci->clock_frame = clock_frame; |
| } |
| } else { |
| --- a/drivers/usb/host/ehci.h |
| +++ b/drivers/usb/host/ehci.h |
| @@ -87,8 +87,9 @@ struct ehci_hcd { /* one per controlle |
| int next_uframe; /* scan periodic, start here */ |
| 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_sitd_list; |
| unsigned clock_frame; |
| |
| /* per root hub port */ |
| @@ -195,7 +196,7 @@ timer_action_done (struct ehci_hcd *ehci |
| clear_bit (action, &ehci->actions); |
| } |
| |
| -static void free_cached_itd_list(struct ehci_hcd *ehci); |
| +static void free_cached_lists(struct ehci_hcd *ehci); |
| |
| /*-------------------------------------------------------------------------*/ |
| |