summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2013-09-03 21:59:03 +0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-09-17 20:49:24 +0400
commit46c73d1d3ebc38feed1d97c6980252a0a01f6a5b (patch)
tree4cecc10c38316a435ef5e968515eac0a82c025e6 /drivers/usb
parente4e18cbd52c8efb0d73a66d0598dc887c0feefb7 (diff)
downloadlinux-46c73d1d3ebc38feed1d97c6980252a0a01f6a5b.tar.xz
USB: EHCI: handle isochronous underruns with tasklets
This patch updates the iso_stream_schedule() routine in ehci-sched.c to handle cases where an underrun causes an isochronous endpoint's queue to empty out, but the client driver wants to maintain synchronization with the device (i.e., the URB_ISO_ASAP flag is not set). This could not happen until recently, when ehci-hcd switched over to completing URBs in a tasklet. (This may seem like an unlikely case to worry about, but underruns are all too common with the snd-usb-audio driver, which doesn't use URB_ISO_ASAP.) As part of the fix, some URBs may need to be given back when they are submitted. This is necessary when the URB's scheduled slots all fall before the current value of ehci->last_iso_frame, and as an optimization we do it also when the slots all fall before the current frame number. As a second part of the fix, we may need to skip some but not all of an URB's packets. This is necessary when some of the URB's scheduled slots fall before the current value of ehci->last_iso_frame and some of them fall after the current frame number. A new field (first_packet) is added to struct ehci_iso_sched, to indicate how many packets should be skipped. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> CC: Ming Lei <tom.leiming@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/host/ehci-sched.c150
-rw-r--r--drivers/usb/host/ehci.h1
2 files changed, 96 insertions, 55 deletions
diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
index 208518bc6e36..dcbaad94d607 100644
--- a/drivers/usb/host/ehci-sched.c
+++ b/drivers/usb/host/ehci-sched.c
@@ -1370,10 +1370,12 @@ iso_stream_schedule (
struct ehci_iso_stream *stream
)
{
- u32 now, base, next, start, period, span;
- int status;
+ u32 now, base, next, start, period, span, now2;
+ u32 wrap = 0, skip = 0;
+ int status = 0;
unsigned mod = ehci->periodic_size << 3;
struct ehci_iso_sched *sched = urb->hcpriv;
+ bool empty = list_empty(&stream->td_list);
period = urb->interval;
span = sched->span;
@@ -1384,6 +1386,19 @@ iso_stream_schedule (
now = ehci_read_frame_index(ehci) & (mod - 1);
+ /* Take the isochronous scheduling threshold into account */
+ if (ehci->i_thresh)
+ next = now + ehci->i_thresh; /* uframe cache */
+ else
+ next = (now + 2 + 7) & ~0x07; /* full frame cache */
+
+ /*
+ * Use ehci->last_iso_frame as the base. There can't be any
+ * TDs scheduled for earlier than that.
+ */
+ base = ehci->last_iso_frame << 3;
+ next = (next - base) & (mod - 1);
+
/*
* Need to schedule; when's the next (u)frame we could start?
* This is bigger than ehci->i_thresh allows; scheduling itself
@@ -1391,11 +1406,11 @@ iso_stream_schedule (
* can also help high bandwidth if the dma and irq loads don't
* jump until after the queue is primed.
*/
- if (unlikely(list_empty(&stream->td_list))) {
+ if (unlikely(empty && !hcd_periodic_completion_in_progress(
+ ehci_to_hcd(ehci), urb->ep))) {
int done = 0;
- base = now & ~0x07;
- start = base + SCHEDULING_DELAY;
+ start = (now & ~0x07) + SCHEDULING_DELAY;
/* find a uframe slot with enough bandwidth.
* Early uframes are more precious because full-speed
@@ -1426,6 +1441,9 @@ iso_stream_schedule (
status = -ENOSPC;
goto fail;
}
+
+ start = (start - base) & (mod - 1);
+ goto use_start;
}
/*
@@ -1434,72 +1452,85 @@ iso_stream_schedule (
* (irq delays etc). If there are, the behavior depends on
* whether URB_ISO_ASAP is set.
*/
- else {
+ start = (stream->next_uframe - base) & (mod - 1);
+ now2 = (now - base) & (mod - 1);
- /* Take the isochronous scheduling threshold into account */
- if (ehci->i_thresh)
- next = now + ehci->i_thresh; /* uframe cache */
- else
- next = (now + 2 + 7) & ~0x07; /* full frame cache */
+ /* Is the schedule already full? */
+ if (unlikely(!empty && start < period)) {
+ ehci_dbg(ehci, "iso sched full %p (%u-%u < %u mod %u)\n",
+ urb, stream->next_uframe, base, period, mod);
+ status = -ENOSPC;
+ goto fail;
+ }
- /*
- * Use ehci->last_iso_frame as the base. There can't be any
- * TDs scheduled for earlier than that.
- */
- base = ehci->last_iso_frame << 3;
- next = (next - base) & (mod - 1);
- start = (stream->next_uframe - base) & (mod - 1);
-
- /* Is the schedule already full? */
- if (unlikely(start < period)) {
- ehci_dbg(ehci, "iso sched full %p (%u-%u < %u mod %u)\n",
- urb, stream->next_uframe, base,
- period, mod);
- status = -ENOSPC;
- goto fail;
- }
+ /* Is the next packet scheduled after the base time? */
+ if (likely(!empty || start <= now2 + period)) {
- /* Behind the scheduling threshold? */
- if (unlikely(start < next)) {
- unsigned now2 = (now - base) & (mod - 1);
+ /* URB_ISO_ASAP: make sure that start >= next */
+ if (unlikely(start < next &&
+ (urb->transfer_flags & URB_ISO_ASAP)))
+ goto do_ASAP;
- /* USB_ISO_ASAP: Round up to the first available slot */
- if (urb->transfer_flags & URB_ISO_ASAP)
- start += (next - start + period - 1) & -period;
+ /* Otherwise use start, if it's not in the past */
+ if (likely(start >= now2))
+ goto use_start;
- /*
- * Not ASAP: Use the next slot in the stream,
- * no matter what.
- */
- else if (start + span - period < now2) {
- ehci_dbg(ehci, "iso underrun %p (%u+%u < %u)\n",
- urb, start + base,
- span - period, now2 + base);
- }
- }
+ /* Otherwise we got an underrun while the queue was empty */
+ } else {
+ if (urb->transfer_flags & URB_ISO_ASAP)
+ goto do_ASAP;
+ wrap = mod;
+ now2 += mod;
+ }
- start += base;
+ /* How many uframes and packets do we need to skip? */
+ skip = (now2 - start + period - 1) & -period;
+ if (skip >= span) { /* Entirely in the past? */
+ ehci_dbg(ehci, "iso underrun %p (%u+%u < %u) [%u]\n",
+ urb, start + base, span - period, now2 + base,
+ base);
+
+ /* Try to keep the last TD intact for scanning later */
+ skip = span - period;
+
+ /* Will it come before the current scan position? */
+ if (empty) {
+ skip = span; /* Skip the entire URB */
+ status = 1; /* and give it back immediately */
+ iso_sched_free(stream, sched);
+ sched = NULL;
+ }
}
+ urb->error_count = skip / period;
+ if (sched)
+ sched->first_packet = urb->error_count;
+ goto use_start;
+ do_ASAP:
+ /* Use the first slot after "next" */
+ start = next + ((start - next) & (period - 1));
+
+ use_start:
/* Tried to schedule too far into the future? */
- if (unlikely(start - base + span - period >= mod)) {
+ if (unlikely(start + span - period >= mod + wrap)) {
ehci_dbg(ehci, "request %p would overflow (%u+%u >= %u)\n",
- urb, start - base, span - period, mod);
+ urb, start, span - period, mod + wrap);
status = -EFBIG;
goto fail;
}
- stream->next_uframe = start & (mod - 1);
+ start += base;
+ stream->next_uframe = (start + skip) & (mod - 1);
/* report high speed start in uframes; full speed, in frames */
- urb->start_frame = stream->next_uframe;
+ urb->start_frame = start & (mod - 1);
if (!stream->highspeed)
urb->start_frame >>= 3;
/* Make sure scan_isoc() sees these */
if (ehci->isoc_count == 0)
ehci->last_iso_frame = now >> 3;
- return 0;
+ return status;
fail:
iso_sched_free(stream, sched);
@@ -1612,7 +1643,8 @@ static void itd_link_urb(
ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++;
/* fill iTDs uframe by uframe */
- for (packet = 0, itd = NULL; packet < urb->number_of_packets; ) {
+ for (packet = iso_sched->first_packet, itd = NULL;
+ packet < urb->number_of_packets;) {
if (itd == NULL) {
/* ASSERT: we have all necessary itds */
// BUG_ON (list_empty (&iso_sched->td_list));
@@ -1806,10 +1838,14 @@ static int itd_submit (struct ehci_hcd *ehci, struct urb *urb,
if (unlikely(status))
goto done_not_linked;
status = iso_stream_schedule(ehci, urb, stream);
- if (likely (status == 0))
+ if (likely(status == 0)) {
itd_link_urb (ehci, urb, ehci->periodic_size << 3, stream);
- else
+ } else if (status > 0) {
+ status = 0;
+ ehci_urb_done(ehci, urb, 0);
+ } else {
usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb);
+ }
done_not_linked:
spin_unlock_irqrestore (&ehci->lock, flags);
done:
@@ -2010,7 +2046,7 @@ static void sitd_link_urb(
ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++;
/* fill sITDs frame by frame */
- for (packet = 0, sitd = NULL;
+ for (packet = sched->first_packet, sitd = NULL;
packet < urb->number_of_packets;
packet++) {
@@ -2180,10 +2216,14 @@ static int sitd_submit (struct ehci_hcd *ehci, struct urb *urb,
if (unlikely(status))
goto done_not_linked;
status = iso_stream_schedule(ehci, urb, stream);
- if (status == 0)
+ if (likely(status == 0)) {
sitd_link_urb (ehci, urb, ehci->periodic_size << 3, stream);
- else
+ } else if (status > 0) {
+ status = 0;
+ ehci_urb_done(ehci, urb, 0);
+ } else {
usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb);
+ }
done_not_linked:
spin_unlock_irqrestore (&ehci->lock, flags);
done:
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 291db7d09f22..2d401927e143 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -434,6 +434,7 @@ struct ehci_iso_packet {
struct ehci_iso_sched {
struct list_head td_list;
unsigned span;
+ unsigned first_packet;
struct ehci_iso_packet packet [0];
};