Commit dccd574c authored by Sarah Sharp's avatar Sarah Sharp Committed by Greg Kroah-Hartman

USB: ehci: Respect IST when scheduling new split iTDs.

The EHCI specification says that an EHCI host controller may cache part of
the isochronous schedule.  The EHCI controller must advertise how much it
caches in the schedule through the HCCPARAMS register isochronous
scheduling threshold (IST) bits.

In theory, adding new iTDs within the IST should be harmless.  The HW will
follow the old cached linked list and miss the new iTD.  SW will notice HW
missed the iTD and return 0 for the transfer length.

However, Intel ICH9 chipsets (and some later chipsets) have issues when SW
attempts to schedule a split transaction within the IST.  All transfers
will cease being sent out that port, and the drivers will see isochronous
packets complete with a length of zero.  Start of frames may or may not
also disappear, causing the device to go into auto-suspend.  This "bus
stall" will continue until a control or bulk transfer is queued to a
device under that roothub.

Most drivers will never cause this behavior, because they use multiple
URBs with multiple packets to keep the bus busy.  If you limit the number
of URBs to one, you may be able to hit this bug.

Make sure the EHCI driver does not schedule full-speed transfers within
the IST under an Intel chipset.  Make sure that when we fall behind the
current microframe plus IST, we schedule the new transfer at the next
periodic interval after the IST.

Don't change the scheduling for new transfers, since the schedule slop will
always be greater than the IST.  Allow high speed isochronous transfers to
be scheduled within the IST, since this doesn't trigger the Intel chipset
bug.

Make sure that if the host caches the full frame, the EHCI driver's
internal isochronous threshold (ehci->i_thresh) is set to
8 microframes + 2 microframes wiggle room.  This is similar to what is done in
the case where the host caches less than the full frame.
Signed-off-by: default avatarSarah Sharp <sarah.a.sharp@linux.intel.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Cc: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent d7e055f1
...@@ -549,7 +549,7 @@ static int ehci_init(struct usb_hcd *hcd) ...@@ -549,7 +549,7 @@ static int ehci_init(struct usb_hcd *hcd)
/* controllers may cache some of the periodic schedule ... */ /* controllers may cache some of the periodic schedule ... */
hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params); hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params);
if (HCC_ISOC_CACHE(hcc_params)) // full frame cache if (HCC_ISOC_CACHE(hcc_params)) // full frame cache
ehci->i_thresh = 8; ehci->i_thresh = 2 + 8;
else // N microframes cached else // N microframes cached
ehci->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); ehci->i_thresh = 2 + HCC_ISOC_THRES(hcc_params);
......
...@@ -1394,10 +1394,11 @@ iso_stream_schedule ( ...@@ -1394,10 +1394,11 @@ iso_stream_schedule (
struct ehci_iso_stream *stream struct ehci_iso_stream *stream
) )
{ {
u32 now, start, max, period; u32 now, next, start, period;
int status; int status;
unsigned mod = ehci->periodic_size << 3; unsigned mod = ehci->periodic_size << 3;
struct ehci_iso_sched *sched = urb->hcpriv; struct ehci_iso_sched *sched = urb->hcpriv;
struct pci_dev *pdev;
if (sched->span > (mod - SCHEDULE_SLOP)) { if (sched->span > (mod - SCHEDULE_SLOP)) {
ehci_dbg (ehci, "iso request %p too long\n", urb); ehci_dbg (ehci, "iso request %p too long\n", urb);
...@@ -1418,26 +1419,35 @@ iso_stream_schedule ( ...@@ -1418,26 +1419,35 @@ iso_stream_schedule (
now = ehci_readl(ehci, &ehci->regs->frame_index) % mod; now = ehci_readl(ehci, &ehci->regs->frame_index) % mod;
/* when's the last uframe this urb could start? */
max = now + mod;
/* Typical case: reuse current schedule, stream is still active. /* Typical case: reuse current schedule, stream is still active.
* Hopefully there are no gaps from the host falling behind * Hopefully there are no gaps from the host falling behind
* (irq delays etc), but if there are we'll take the next * (irq delays etc), but if there are we'll take the next
* slot in the schedule, implicitly assuming URB_ISO_ASAP. * slot in the schedule, implicitly assuming URB_ISO_ASAP.
*/ */
if (likely (!list_empty (&stream->td_list))) { if (likely (!list_empty (&stream->td_list))) {
pdev = to_pci_dev(ehci_to_hcd(ehci)->self.controller);
start = stream->next_uframe; start = stream->next_uframe;
if (start < now)
start += mod; /* For high speed devices, allow scheduling within the
* isochronous scheduling threshold. For full speed devices,
* don't. (Work around for Intel ICH9 bug.)
*/
if (!stream->highspeed &&
pdev->vendor == PCI_VENDOR_ID_INTEL)
next = now + ehci->i_thresh;
else
next = now;
/* Fell behind (by up to twice the slop amount)? */ /* Fell behind (by up to twice the slop amount)? */
if (start >= max - 2 * SCHEDULE_SLOP) if (((start - next) & (mod - 1)) >=
mod - 2 * SCHEDULE_SLOP)
start += period * DIV_ROUND_UP( start += period * DIV_ROUND_UP(
max - start, period) - mod; (next - start) & (mod - 1),
period);
/* Tried to schedule too far into the future? */ /* Tried to schedule too far into the future? */
if (unlikely((start + sched->span) >= max)) { if (unlikely(((start - now) & (mod - 1)) + sched->span
>= mod - 2 * SCHEDULE_SLOP)) {
status = -EFBIG; status = -EFBIG;
goto fail; goto fail;
} }
...@@ -1482,7 +1492,7 @@ iso_stream_schedule ( ...@@ -1482,7 +1492,7 @@ iso_stream_schedule (
/* no room in the schedule */ /* no room in the schedule */
ehci_dbg (ehci, "iso %ssched full %p (now %d max %d)\n", ehci_dbg (ehci, "iso %ssched full %p (now %d max %d)\n",
list_empty (&stream->td_list) ? "" : "re", list_empty (&stream->td_list) ? "" : "re",
urb, now, max); urb, now, now + mod);
status = -ENOSPC; status = -ENOSPC;
fail: fail:
......
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