Commit 7ca9229c authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

USB: OHCI: accept very late isochronous URBs

commit a8693424 upstream.

Commit 24f53137 (USB: EHCI: accept very late isochronous URBs)
changed the isochronous API provided by ehci-hcd.  URBs submitted too
late, so that the time slots for all their packets have already
expired, are no longer rejected outright.  Instead the submission is
accepted, and the URB completes normally with a -EXDEV error for each
packet.  This is what client drivers expect.

This patch implements the same policy in ohci-hcd.  The change is more
complicated than it was in ehci-hcd, because ohci-hcd doesn't scan for
isochronous completions in the same way as ehci-hcd does.  Rather, it
depends on the hardware adding completed TDs to a "done queue".  Some
OHCI controller don't handle this properly when a TD's time slot has
already expired, so we have to avoid adding such TDs to the schedule
in the first place.  As a result, if the URB was submitted too late
then none of its TDs will get put on the schedule, so none of them
will end up on the done queue, so the driver will never realize that
the URB should be completed.

To solve this problem, the patch adds one to urb_priv->td_cnt for such
URBs, making it larger than urb_priv->length (td_cnt already gets set
to the number of TD's that had to be skipped because their slots have
expired).  Each time an URB is given back, the finish_urb() routine
looks to see if urb_priv->td_cnt for the next URB on the same endpoint
is marked in this way.  If so, it gives back the next URB right away.

This should be applied to all kernels containing commit 815fa7b9
(USB: OHCI: fix logic for scheduling isochronous URBs).
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 27e08a98
...@@ -231,31 +231,26 @@ static int ohci_urb_enqueue ( ...@@ -231,31 +231,26 @@ static int ohci_urb_enqueue (
frame &= ~(ed->interval - 1); frame &= ~(ed->interval - 1);
frame |= ed->branch; frame |= ed->branch;
urb->start_frame = frame; urb->start_frame = frame;
ed->last_iso = frame + ed->interval * (size - 1);
} }
} else if (ed->type == PIPE_ISOCHRONOUS) { } else if (ed->type == PIPE_ISOCHRONOUS) {
u16 next = ohci_frame_no(ohci) + 1; u16 next = ohci_frame_no(ohci) + 1;
u16 frame = ed->last_iso + ed->interval; u16 frame = ed->last_iso + ed->interval;
u16 length = ed->interval * (size - 1);
/* Behind the scheduling threshold? */ /* Behind the scheduling threshold? */
if (unlikely(tick_before(frame, next))) { if (unlikely(tick_before(frame, next))) {
/* USB_ISO_ASAP: Round up to the first available slot */ /* URB_ISO_ASAP: Round up to the first available slot */
if (urb->transfer_flags & URB_ISO_ASAP) { if (urb->transfer_flags & URB_ISO_ASAP) {
frame += (next - frame + ed->interval - 1) & frame += (next - frame + ed->interval - 1) &
-ed->interval; -ed->interval;
/* /*
* Not ASAP: Use the next slot in the stream. If * Not ASAP: Use the next slot in the stream,
* the entire URB falls before the threshold, fail. * no matter what.
*/ */
} else { } else {
if (tick_before(frame + ed->interval *
(urb->number_of_packets - 1), next)) {
retval = -EXDEV;
usb_hcd_unlink_urb_from_ep(hcd, urb);
goto fail;
}
/* /*
* Some OHCI hardware doesn't handle late TDs * Some OHCI hardware doesn't handle late TDs
* correctly. After retiring them it proceeds * correctly. After retiring them it proceeds
...@@ -266,9 +261,16 @@ static int ohci_urb_enqueue ( ...@@ -266,9 +261,16 @@ static int ohci_urb_enqueue (
urb_priv->td_cnt = DIV_ROUND_UP( urb_priv->td_cnt = DIV_ROUND_UP(
(u16) (next - frame), (u16) (next - frame),
ed->interval); ed->interval);
if (urb_priv->td_cnt >= urb_priv->length) {
++urb_priv->td_cnt; /* Mark it */
ohci_dbg(ohci, "iso underrun %p (%u+%u < %u)\n",
urb, frame, length,
next);
}
} }
} }
urb->start_frame = frame; urb->start_frame = frame;
ed->last_iso = frame + length;
} }
/* fill the TDs and link them to the ed; and /* fill the TDs and link them to the ed; and
......
...@@ -41,8 +41,12 @@ finish_urb(struct ohci_hcd *ohci, struct urb *urb, int status) ...@@ -41,8 +41,12 @@ finish_urb(struct ohci_hcd *ohci, struct urb *urb, int status)
__releases(ohci->lock) __releases(ohci->lock)
__acquires(ohci->lock) __acquires(ohci->lock)
{ {
struct usb_host_endpoint *ep = urb->ep;
struct urb_priv *urb_priv;
// ASSERT (urb->hcpriv != 0); // ASSERT (urb->hcpriv != 0);
restart:
urb_free_priv (ohci, urb->hcpriv); urb_free_priv (ohci, urb->hcpriv);
urb->hcpriv = NULL; urb->hcpriv = NULL;
if (likely(status == -EINPROGRESS)) if (likely(status == -EINPROGRESS))
...@@ -79,6 +83,21 @@ __acquires(ohci->lock) ...@@ -79,6 +83,21 @@ __acquires(ohci->lock)
ohci->hc_control &= ~(OHCI_CTRL_PLE|OHCI_CTRL_IE); ohci->hc_control &= ~(OHCI_CTRL_PLE|OHCI_CTRL_IE);
ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
} }
/*
* An isochronous URB that is sumitted too late won't have any TDs
* (marked by the fact that the td_cnt value is larger than the
* actual number of TDs). If the next URB on this endpoint is like
* that, give it back now.
*/
if (!list_empty(&ep->urb_list)) {
urb = list_first_entry(&ep->urb_list, struct urb, urb_list);
urb_priv = urb->hcpriv;
if (urb_priv->td_cnt > urb_priv->length) {
status = 0;
goto restart;
}
}
} }
...@@ -545,7 +564,6 @@ td_fill (struct ohci_hcd *ohci, u32 info, ...@@ -545,7 +564,6 @@ td_fill (struct ohci_hcd *ohci, u32 info,
td->hwCBP = cpu_to_hc32 (ohci, data & 0xFFFFF000); td->hwCBP = cpu_to_hc32 (ohci, data & 0xFFFFF000);
*ohci_hwPSWp(ohci, td, 0) = cpu_to_hc16 (ohci, *ohci_hwPSWp(ohci, td, 0) = cpu_to_hc16 (ohci,
(data & 0x0FFF) | 0xE000); (data & 0x0FFF) | 0xE000);
td->ed->last_iso = info & 0xffff;
} else { } else {
td->hwCBP = cpu_to_hc32 (ohci, data); td->hwCBP = cpu_to_hc32 (ohci, data);
} }
...@@ -994,7 +1012,7 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick) ...@@ -994,7 +1012,7 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
urb_priv->td_cnt++; urb_priv->td_cnt++;
/* if URB is done, clean up */ /* if URB is done, clean up */
if (urb_priv->td_cnt == urb_priv->length) { if (urb_priv->td_cnt >= urb_priv->length) {
modified = completed = 1; modified = completed = 1;
finish_urb(ohci, urb, 0); finish_urb(ohci, urb, 0);
} }
...@@ -1084,7 +1102,7 @@ static void takeback_td(struct ohci_hcd *ohci, struct td *td) ...@@ -1084,7 +1102,7 @@ static void takeback_td(struct ohci_hcd *ohci, struct td *td)
urb_priv->td_cnt++; urb_priv->td_cnt++;
/* If all this urb's TDs are done, call complete() */ /* If all this urb's TDs are done, call complete() */
if (urb_priv->td_cnt == urb_priv->length) if (urb_priv->td_cnt >= urb_priv->length)
finish_urb(ohci, urb, status); finish_urb(ohci, urb, status);
/* clean schedule: unlink EDs that are no longer busy */ /* clean schedule: unlink EDs that are no longer busy */
......
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