diff -uNrp linux-source-2.6.28/drivers/usb/host/ehci.h linux-source-2.6.28.new/drivers/usb/host/ehci.h --- linux-source-2.6.28/drivers/usb/host/ehci.h 2008-12-25 00:26:37.000000000 +0100 +++ linux-source-2.6.28.new/drivers/usb/host/ehci.h 2009-02-02 00:20:50.000000000 +0100 @@ -86,6 +86,9 @@ struct ehci_hcd { /* one per controlle union ehci_shadow *pshadow; /* mirror hw periodic table */ int next_uframe; /* scan periodic, start here */ unsigned periodic_sched; /* periodic activity count */ + struct list_head cached_itd_list; /* list of itds completed + while frame hadn't yet elapsed */ + unsigned clock_frame; /* per root hub port */ unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; @@ -210,6 +213,8 @@ timer_action (struct ehci_hcd *ehci, enu } } +static void free_cached_itd_list(struct ehci_hcd *ehci); + /*-------------------------------------------------------------------------*/ #include diff -uNrp linux-source-2.6.28/drivers/usb/host/ehci-hcd.c linux-source-2.6.28.new/drivers/usb/host/ehci-hcd.c --- linux-source-2.6.28/drivers/usb/host/ehci-hcd.c 2008-12-25 00:26:37.000000000 +0100 +++ linux-source-2.6.28.new/drivers/usb/host/ehci-hcd.c 2009-02-02 00:18:52.000000000 +0100 @@ -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 diff -uNrp linux-source-2.6.28/drivers/usb/host/ehci-mem.c linux-source-2.6.28.new/drivers/usb/host/ehci-mem.c --- linux-source-2.6.28/drivers/usb/host/ehci-mem.c 2008-12-25 00:26:37.000000000 +0100 +++ linux-source-2.6.28.new/drivers/usb/host/ehci-mem.c 2009-02-02 00:22:16.000000000 +0100 @@ -128,6 +128,7 @@ static inline void qh_put (struct ehci_q static void ehci_mem_cleanup (struct ehci_hcd *ehci) { + free_cached_itd_list(ehci); if (ehci->async) qh_put (ehci->async); ehci->async = NULL; diff -uNrp linux-source-2.6.28/drivers/usb/host/ehci-sched.c linux-source-2.6.28.new/drivers/usb/host/ehci-sched.c --- linux-source-2.6.28/drivers/usb/host/ehci-sched.c 2008-12-25 00:26:37.000000000 +0100 +++ linux-source-2.6.28.new/drivers/usb/host/ehci-sched.c 2009-02-02 00:37:23.000000000 +0100 @@ -1004,7 +1004,8 @@ iso_stream_put(struct ehci_hcd *ehci, st 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,26 @@ 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 still start transactions based on this ITD. + If its content changed that is. Move it to a safe place. */ + list_move(&itd->itd_list, &ehci->cached_itd_list); + if (stream->refcount == 2) { + /* If iso_stream_put() would be called here, stream + would be freed. Prevent stream's reusage instead. */ + stream->ep->hcpriv = NULL; + stream->ep = NULL; + } + } return retval; } @@ -2101,6 +2114,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 +2142,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 +2311,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;