Skip to content

Commit

Permalink
USB: ohci - record data toggle after unlink
Browse files Browse the repository at this point in the history
This patch fixes a problem with OHCI where canceling bulk or
interrupt URBs may lose track of the right data toggle.  This
seems to be a longstanding bug, possibly dating back to the
Linux 2.4 kernel, which stayed hidden because

 (a) about half the time the data toggle bit was correct;
 (b) canceling such URBs is unusual; and
 (c) the few drivers which cancel these URBs either
      [1] do it only as part of shutting down, or
      [2] have fault recovery logic, which recovers.

For those transfer types, the toggle is normally written back
into the ED when each TD is retired.  But canceling bypasses
the mechanism used to retire TDs ... so on average, half the
time the toggle bit will be invalid after cancelation.

The fix is simple:  the toggle state of any canceled TDs are
propagated back to the ED in the finish_unlinks function.

(Issue found by leonidv11@gmail.com ...)

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Cc: Leonid <leonidv11@gmail.com>
Cc: stable <stable@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
David Brownell authored and Greg Kroah-Hartman committed Jul 4, 2008
1 parent 056761e commit 29c8f6a
Showing 1 changed file with 12 additions and 0 deletions.
12 changes: 12 additions & 0 deletions drivers/usb/host/ohci-q.c
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,7 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
struct urb *urb;
urb_priv_t *urb_priv;
__hc32 savebits;
u32 tdINFO;

td = list_entry (entry, struct td, td_list);
urb = td->urb;
Expand All @@ -966,6 +967,17 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick)
savebits = *prev & ~cpu_to_hc32 (ohci, TD_MASK);
*prev = td->hwNextTD | savebits;

/* If this was unlinked, the TD may not have been
* retired ... so manually save the data toggle.
* The controller ignores the value we save for
* control and ISO endpoints.
*/
tdINFO = hc32_to_cpup(ohci, &td->hwINFO);
if ((tdINFO & TD_T) == TD_T_DATA0)
ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_C);
else if ((tdINFO & TD_T) == TD_T_DATA1)
ed->hwHeadP |= cpu_to_hc32(ohci, ED_C);

/* HC may have partly processed this TD */
td_done (ohci, urb, td);
urb_priv->td_cnt++;
Expand Down

0 comments on commit 29c8f6a

Please sign in to comment.