forked from Minki/linux
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: Alan Stern <stern@rowland.harvard.edu> Tested-by: Nate Case <ncase@xes-inc.com> CC: <stable@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
5f677f1d45
commit
0e5f231bc1
@ -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)
|
|||||||
|
|
||||||
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)
|
|||||||
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 (
|
|||||||
(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;
|
||||||
|
if (ehci->clock_frame != sitd->frame) {
|
||||||
|
/* OK to recycle this SITD now. */
|
||||||
sitd->stream = NULL;
|
sitd->stream = NULL;
|
||||||
list_move(&sitd->sitd_list, &stream->free_list);
|
list_move(&sitd->sitd_list, &stream->free_list);
|
||||||
iso_stream_put(ehci, stream);
|
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:
|
|||||||
|
|
||||||
/*-------------------------------------------------------------------------*/
|
/*-------------------------------------------------------------------------*/
|
||||||
|
|
||||||
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)
|
|||||||
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)
|
|||||||
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:
|
|||||||
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 */
|
|||||||
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)
|
|||||||
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);
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------*/
|
/*-------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user