diff options
author | Douglas Anderson <dianders@chromium.org> | 2017-12-12 10:30:31 -0800 |
---|---|---|
committer | Felipe Balbi <felipe.balbi@linux.intel.com> | 2017-12-13 11:27:53 +0200 |
commit | 38d2b5fb75c15923fb89c32134516a623515bce4 (patch) | |
tree | 42186c421271b50e0431e9fab9e5141417512905 /drivers/usb/dwc2/hcd.h | |
parent | f2830ad455ec0fdc386baeb9d654f7095bf849da (diff) |
usb: dwc2: host: Don't retry NAKed transactions right away
On rk3288-veyron devices on Chrome OS it was found that plugging in an
Arduino-based USB device could cause the system to lockup, especially
if the CPU Frequency was at one of the slower operating points (like
100 MHz / 200 MHz).
Upon tracing, I found that the following was happening:
* The USB device (full speed) was connected to a high speed hub and
then to the rk3288. Thus, we were dealing with split transactions,
which is all handled in software on dwc2.
* Userspace was initiating a BULK IN transfer
* When we sent the SSPLIT (to start the split transaction), we got an
ACK. Good. Then we issued the CSPLIT.
* When we sent the CSPLIT, we got back a NAK. We immediately (from
the interrupt handler) started to retry and sent another SSPLIT.
* The device kept NAKing our CSPLIT, so we kept ping-ponging between
sending a SSPLIT and a CSPLIT, each time sending from the interrupt
handler.
* The handling of the interrupts was (because of the low CPU speed and
the inefficiency of the dwc2 interrupt handler) was actually taking
_longer_ than it took the other side to send the ACK/NAK. Thus we
were _always_ in the USB interrupt routine.
* The fact that USB interrupts were always going off was preventing
other things from happening in the system. This included preventing
the system from being able to transition to a higher CPU frequency.
As I understand it, there is no requirement to retry super quickly
after a NAK, we just have to retry sometime in the future. Thus one
solution to the above is to just add a delay between getting a NAK and
retrying the transmission. If this delay is sufficiently long to get
out of the interrupt routine then the rest of the system will be able
to make forward progress. Even a 25 us delay would probably be
enough, but we'll be extra conservative and try to delay 1 ms (the
exact amount depends on HZ and the accuracy of the jiffy and how close
the current jiffy is to ticking, but could be as much as 20 ms or as
little as 1 ms).
Presumably adding a delay like this could impact the USB throughput,
so we only add the delay with repeated NAKs.
NOTE: Upon further testing of a pl2303 serial adapter, I found that
this fix may help with problems there. Specifically I found that the
pl2303 serial adapters tend to respond with a NAK when they have
nothing to say and thus we end with this same sequence.
Signed-off-by: Douglas Anderson <dianders@chromium.org>
Reviewed-by: Julius Werner <jwerner@chromium.org>
Tested-by: Stefan Wahren <stefan.wahren@i2se.com>
Acked-by: John Youn <johnyoun@synopsys.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
Diffstat (limited to 'drivers/usb/dwc2/hcd.h')
-rw-r--r-- | drivers/usb/dwc2/hcd.h | 9 |
1 files changed, 9 insertions, 0 deletions
diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h index 78e9e01051b5..ad60e46e66e1 100644 --- a/drivers/usb/dwc2/hcd.h +++ b/drivers/usb/dwc2/hcd.h @@ -314,12 +314,16 @@ struct dwc2_hs_transfer_time { * descriptor and indicates original XferSize value for the * descriptor * @unreserve_timer: Timer for releasing periodic reservation. + * @wait_timer: Timer used to wait before re-queuing. * @dwc2_tt: Pointer to our tt info (or NULL if no tt). * @ttport: Port number within our tt. * @tt_buffer_dirty True if clear_tt_buffer_complete is pending * @unreserve_pending: True if we planned to unreserve but haven't yet. * @schedule_low_speed: True if we have a low/full speed component (either the * host is in low/full speed mode or do_split). + * @want_wait: We should wait before re-queuing; only matters for non- + * periodic transfers and is ignored for periodic ones. + * @wait_timer_cancel: Set to true to cancel the wait_timer. * * A Queue Head (QH) holds the static characteristics of an endpoint and * maintains a list of transfers (QTDs) for that endpoint. A QH structure may @@ -354,11 +358,14 @@ struct dwc2_qh { u32 desc_list_sz; u32 *n_bytes; struct timer_list unreserve_timer; + struct timer_list wait_timer; struct dwc2_tt *dwc_tt; int ttport; unsigned tt_buffer_dirty:1; unsigned unreserve_pending:1; unsigned schedule_low_speed:1; + unsigned want_wait:1; + unsigned wait_timer_cancel:1; }; /** @@ -389,6 +396,7 @@ struct dwc2_qh { * @n_desc: Number of DMA descriptors for this QTD * @isoc_frame_index_last: Last activated frame (packet) index, used in * descriptor DMA mode only + * @num_naks: Number of NAKs received on this QTD. * @urb: URB for this transfer * @qh: Queue head for this QTD * @qtd_list_entry: For linking to the QH's list of QTDs @@ -419,6 +427,7 @@ struct dwc2_qtd { u8 error_count; u8 n_desc; u16 isoc_frame_index_last; + u16 num_naks; struct dwc2_hcd_urb *urb; struct dwc2_qh *qh; struct list_head qtd_list_entry; |