Module Name: src
Committed By: simonb
Date: Wed Aug 8 07:20:44 UTC 2018
Modified Files:
src/sys/external/bsd/dwc2/dist: dwc2_core.h dwc2_hcd.c dwc2_hcd.h
dwc2_hcdintr.c dwc2_hcdqueue.c
Log Message:
Merge
https://github.com/torvalds/linux/commit/38d2b5fb75c15923fb89c32134516a623515bce4
to mitigate USB NAK interrupt storms, with an extra change from
skrll@ to also mitigate interrupt storms on the non-split case with
older DWC2 cores.
Fixes woeful USB disk performance on an ERLITE.
Much thanks to skrll@ for pointer to the above patch, handling the
non-split case and testing.
To generate a diff of this commit:
cvs rdiff -u -r1.8 -r1.9 src/sys/external/bsd/dwc2/dist/dwc2_core.h
cvs rdiff -u -r1.20 -r1.21 src/sys/external/bsd/dwc2/dist/dwc2_hcd.c
cvs rdiff -u -r1.14 -r1.15 src/sys/external/bsd/dwc2/dist/dwc2_hcd.h \
src/sys/external/bsd/dwc2/dist/dwc2_hcdqueue.c
cvs rdiff -u -r1.13 -r1.14 src/sys/external/bsd/dwc2/dist/dwc2_hcdintr.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/external/bsd/dwc2/dist/dwc2_core.h
diff -u src/sys/external/bsd/dwc2/dist/dwc2_core.h:1.8 src/sys/external/bsd/dwc2/dist/dwc2_core.h:1.9
--- src/sys/external/bsd/dwc2/dist/dwc2_core.h:1.8 Wed Feb 24 22:17:54 2016
+++ src/sys/external/bsd/dwc2/dist/dwc2_core.h Wed Aug 8 07:20:44 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: dwc2_core.h,v 1.8 2016/02/24 22:17:54 skrll Exp $ */
+/* $NetBSD: dwc2_core.h,v 1.9 2018/08/08 07:20:44 simonb Exp $ */
/*
* core.h - DesignWare HS OTG Controller common declarations
@@ -756,6 +756,7 @@ struct dwc2_hsotg {
} flags;
struct list_head non_periodic_sched_inactive;
+ struct list_head non_periodic_sched_waiting;
struct list_head non_periodic_sched_active;
struct list_head *non_periodic_qh_ptr;
struct list_head periodic_sched_inactive;
Index: src/sys/external/bsd/dwc2/dist/dwc2_hcd.c
diff -u src/sys/external/bsd/dwc2/dist/dwc2_hcd.c:1.20 src/sys/external/bsd/dwc2/dist/dwc2_hcd.c:1.21
--- src/sys/external/bsd/dwc2/dist/dwc2_hcd.c:1.20 Wed Aug 1 16:44:14 2018
+++ src/sys/external/bsd/dwc2/dist/dwc2_hcd.c Wed Aug 8 07:20:44 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: dwc2_hcd.c,v 1.20 2018/08/01 16:44:14 skrll Exp $ */
+/* $NetBSD: dwc2_hcd.c,v 1.21 2018/08/08 07:20:44 simonb Exp $ */
/*
* hcd.c - DesignWare HS OTG Controller host-mode routines
@@ -42,7 +42,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: dwc2_hcd.c,v 1.20 2018/08/01 16:44:14 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: dwc2_hcd.c,v 1.21 2018/08/08 07:20:44 simonb Exp $");
#include <sys/types.h>
#include <sys/kmem.h>
@@ -117,6 +117,10 @@ static void dwc2_dump_channel_info(struc
list_for_each_entry(qh, &hsotg->non_periodic_sched_inactive,
qh_list_entry)
dev_dbg(hsotg->dev, " %p\n", qh);
+ dev_dbg(hsotg->dev, " NP waiting sched:\n");
+ list_for_each_entry(qh, &hsotg->non_periodic_sched_waiting,
+ qh_list_entry)
+ dev_dbg(hsotg->dev, " %p\n", qh);
dev_dbg(hsotg->dev, " NP active sched:\n");
list_for_each_entry(qh, &hsotg->non_periodic_sched_active,
qh_list_entry)
@@ -194,6 +198,7 @@ static void dwc2_qh_list_free(struct dwc
static void dwc2_kill_all_urbs(struct dwc2_hsotg *hsotg)
{
dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_inactive);
+ dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_waiting);
dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_active);
dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_inactive);
dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_ready);
@@ -2215,6 +2220,7 @@ static void dwc2_hcd_free(struct dwc2_hs
/* Free memory for QH/QTD lists */
dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_inactive);
+ dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_waiting);
dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_active);
dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_inactive);
dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_ready);
@@ -2337,6 +2343,7 @@ int dwc2_hcd_init(struct dwc2_hsotg *hso
/* Initialize the non-periodic schedule */
INIT_LIST_HEAD(&hsotg->non_periodic_sched_inactive);
+ INIT_LIST_HEAD(&hsotg->non_periodic_sched_waiting);
INIT_LIST_HEAD(&hsotg->non_periodic_sched_active);
/* Initialize the periodic schedule */
Index: src/sys/external/bsd/dwc2/dist/dwc2_hcd.h
diff -u src/sys/external/bsd/dwc2/dist/dwc2_hcd.h:1.14 src/sys/external/bsd/dwc2/dist/dwc2_hcd.h:1.15
--- src/sys/external/bsd/dwc2/dist/dwc2_hcd.h:1.14 Sat Apr 23 10:15:30 2016
+++ src/sys/external/bsd/dwc2/dist/dwc2_hcd.h Wed Aug 8 07:20:44 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: dwc2_hcd.h,v 1.14 2016/04/23 10:15:30 skrll Exp $ */
+/* $NetBSD: dwc2_hcd.h,v 1.15 2018/08/08 07:20:44 simonb Exp $ */
/*
* hcd.h - DesignWare HS OTG Controller host-mode declarations
@@ -222,6 +222,7 @@ enum dwc2_transaction_type {
/**
* struct dwc2_qh - Software queue head structure
*
+ * @hsotg: The HCD state structure for the DWC OTG controller
* @ep_type: Endpoint type. One of the following values:
* - USB_ENDPOINT_XFER_CONTROL
* - USB_ENDPOINT_XFER_BULK
@@ -264,13 +265,18 @@ enum dwc2_transaction_type {
* @n_bytes: Xfer Bytes array. Each element corresponds to a transfer
* descriptor and indicates original XferSize value for the
* descriptor
+ * @wait_timer: Timer used to wait before re-queuing.
* @tt_buffer_dirty True if clear_tt_buffer_complete is pending
+ * @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
* be entered in either the non-periodic or periodic schedule.
*/
struct dwc2_qh {
+ struct dwc2_hsotg *hsotg;
u8 ep_type;
u8 ep_is_in;
u16 maxp;
@@ -299,7 +305,11 @@ struct dwc2_qh {
dma_addr_t desc_list_dma;
u32 desc_list_sz;
u32 *n_bytes;
+ /* XXX struct timer_list wait_timer; */
+ callout_t wait_timer;
unsigned tt_buffer_dirty:1;
+ unsigned want_wait:1;
+ unsigned wait_timer_cancel:1;
};
/**
@@ -330,6 +340,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
@@ -360,6 +371,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;
Index: src/sys/external/bsd/dwc2/dist/dwc2_hcdqueue.c
diff -u src/sys/external/bsd/dwc2/dist/dwc2_hcdqueue.c:1.14 src/sys/external/bsd/dwc2/dist/dwc2_hcdqueue.c:1.15
--- src/sys/external/bsd/dwc2/dist/dwc2_hcdqueue.c:1.14 Sun Feb 14 10:53:30 2016
+++ src/sys/external/bsd/dwc2/dist/dwc2_hcdqueue.c Wed Aug 8 07:20:44 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: dwc2_hcdqueue.c,v 1.14 2016/02/14 10:53:30 skrll Exp $ */
+/* $NetBSD: dwc2_hcdqueue.c,v 1.15 2018/08/08 07:20:44 simonb Exp $ */
/*
* hcd_queue.c - DesignWare HS OTG Controller host queuing routines
@@ -42,7 +42,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: dwc2_hcdqueue.c,v 1.14 2016/02/14 10:53:30 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: dwc2_hcdqueue.c,v 1.15 2018/08/08 07:20:44 simonb Exp $");
#include <sys/types.h>
#include <sys/kmem.h>
@@ -64,6 +64,10 @@ __KERNEL_RCSID(0, "$NetBSD: dwc2_hcdqueu
#include "dwc2_hcd.h"
static u32 dwc2_calc_bus_time(struct dwc2_hsotg *, int, int, int, int);
+static void dwc2_wait_timer_fn(void *);
+
+/* If we get a NAK, wait this long before retrying */
+#define DWC2_RETRY_WAIT_DELAY 1 /* msec */
/**
* dwc2_qh_init() - Initializes a QH structure
@@ -82,6 +86,10 @@ static void dwc2_qh_init(struct dwc2_hso
dev_vdbg(hsotg->dev, "%s()\n", __func__);
/* Initialize QH */
+ qh->hsotg = hsotg;
+ /* XXX timer_setup(&qh->wait_timer, dwc2_wait_timer_fn, 0); */
+ callout_init(&qh->wait_timer, 0);
+ callout_setfunc(&qh->wait_timer, dwc2_wait_timer_fn, qh);
qh->ep_type = dwc2_hcd_get_pipe_type(&urb->pipe_info);
qh->ep_is_in = dwc2_hcd_is_pipe_in(&urb->pipe_info) ? 1 : 0;
@@ -250,6 +258,17 @@ struct dwc2_qh *dwc2_hcd_qh_create(struc
void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
{
struct dwc2_softc *sc = hsotg->hsotg_sc;
+
+ /*
+ * We don't have the lock so we can safely wait until the wait timer
+ * finishes. Of course, at this point in time we'd better have set
+ * wait_timer_active to false so if this timer was still pending it
+ * won't do anything anyway, but we want it to finish before we free
+ * memory.
+ */
+ /* XXX del_timer_sync(&qh->wait_timer); */
+ callout_destroy(&qh->wait_timer); /* XXX need to callout_halt() first? */
+
if (qh->desc_list) {
dwc2_hcd_qh_free_ddma(hsotg, qh);
} else if (qh->dw_align_buf) {
@@ -583,6 +602,55 @@ static void dwc2_deschedule_periodic(str
}
/**
+ * dwc2_wait_timer_fn() - Timer function to re-queue after waiting
+ *
+ * As per the spec, a NAK indicates that "a function is temporarily unable to
+ * transmit or receive data, but will eventually be able to do so without need
+ * of host intervention".
+ *
+ * That means that when we encounter a NAK we're supposed to retry.
+ *
+ * ...but if we retry right away (from the interrupt handler that saw the NAK)
+ * then we can end up with an interrupt storm (if the other side keeps NAKing
+ * us) because on slow enough CPUs it could take us longer to get out of the
+ * interrupt routine than it takes for the device to send another NAK. That
+ * leads to a constant stream of NAK interrupts and the CPU locks.
+ *
+ * ...so instead of retrying right away in the case of a NAK we'll set a timer
+ * to retry some time later. This function handles that timer and moves the
+ * qh back to the "inactive" list, then queues transactions.
+ *
+ * @t: Pointer to wait_timer in a qh.
+ */
+static void dwc2_wait_timer_fn(void *arg)
+{
+ struct dwc2_qh *qh = arg;
+ struct dwc2_hsotg *hsotg = qh->hsotg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hsotg->lock, flags);
+
+ /*
+ * We'll set wait_timer_cancel to true if we want to cancel this
+ * operation in dwc2_hcd_qh_unlink().
+ */
+ if (!qh->wait_timer_cancel) {
+ enum dwc2_transaction_type tr_type;
+
+ qh->want_wait = false;
+
+ list_move(&qh->qh_list_entry,
+ &hsotg->non_periodic_sched_inactive);
+
+ tr_type = dwc2_hcd_select_transactions(hsotg);
+ if (tr_type != DWC2_TRANSACTION_NONE)
+ dwc2_hcd_queue_transactions(hsotg, tr_type);
+ }
+
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+}
+
+/**
* dwc2_hcd_qh_add() - Adds a QH to either the non periodic or periodic
* schedule if it is not already in the schedule. If the QH is already in
* the schedule, no action is taken.
@@ -614,9 +682,18 @@ int dwc2_hcd_qh_add(struct dwc2_hsotg *h
/* Add the new QH to the appropriate schedule */
if (dwc2_qh_is_non_per(qh)) {
- /* Always start in inactive schedule */
- list_add_tail(&qh->qh_list_entry,
- &hsotg->non_periodic_sched_inactive);
+ if (qh->want_wait) {
+ list_add_tail(&qh->qh_list_entry,
+ &hsotg->non_periodic_sched_waiting);
+ qh->wait_timer_cancel = false;
+ /* XXX mod_timer(&qh->wait_timer,
+ jiffies + DWC2_RETRY_WAIT_DELAY + 1); */
+ callout_schedule(&qh->wait_timer,
+ mstohz(DWC2_RETRY_WAIT_DELAY));
+ } else {
+ list_add_tail(&qh->qh_list_entry,
+ &hsotg->non_periodic_sched_inactive);
+ }
return 0;
}
@@ -646,6 +723,9 @@ void dwc2_hcd_qh_unlink(struct dwc2_hsot
dev_vdbg(hsotg->dev, "%s()\n", __func__);
+ /* If the wait_timer is pending, this will stop it from acting */
+ qh->wait_timer_cancel = true;
+
if (list_empty(&qh->qh_list_entry))
/* QH is not in a schedule */
return;
@@ -726,7 +806,7 @@ void dwc2_hcd_qh_deactivate(struct dwc2_
if (dwc2_qh_is_non_per(qh)) {
dwc2_hcd_qh_unlink(hsotg, qh);
if (!list_empty(&qh->qtd_list))
- /* Add back to inactive non-periodic schedule */
+ /* Add back to inactive/waiting non-periodic schedule */
dwc2_hcd_qh_add(hsotg, qh);
return;
}
Index: src/sys/external/bsd/dwc2/dist/dwc2_hcdintr.c
diff -u src/sys/external/bsd/dwc2/dist/dwc2_hcdintr.c:1.13 src/sys/external/bsd/dwc2/dist/dwc2_hcdintr.c:1.14
--- src/sys/external/bsd/dwc2/dist/dwc2_hcdintr.c:1.13 Sun Feb 14 10:53:30 2016
+++ src/sys/external/bsd/dwc2/dist/dwc2_hcdintr.c Wed Aug 8 07:20:44 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: dwc2_hcdintr.c,v 1.13 2016/02/14 10:53:30 skrll Exp $ */
+/* $NetBSD: dwc2_hcdintr.c,v 1.14 2018/08/08 07:20:44 simonb Exp $ */
/*
* hcd_intr.c - DesignWare HS OTG Controller host-mode interrupt handling
@@ -40,7 +40,7 @@
* This file contains the interrupt handlers for Host mode
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: dwc2_hcdintr.c,v 1.13 2016/02/14 10:53:30 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: dwc2_hcdintr.c,v 1.14 2018/08/08 07:20:44 simonb Exp $");
#include <sys/types.h>
#include <sys/pool.h>
@@ -60,6 +60,13 @@ __KERNEL_RCSID(0, "$NetBSD: dwc2_hcdintr
#include "dwc2_core.h"
#include "dwc2_hcd.h"
+/*
+ * If we get this many NAKs on a split transaction we'll slow down
+ * retransmission. A 1 here means delay after the first NAK.
+ */
+#define DWC2_NAKS_BEFORE_DELAY 3
+int dwc2_naks_before_delay = DWC2_NAKS_BEFORE_DELAY;
+
/* This function is for debug only */
static void dwc2_track_missed_sofs(struct dwc2_hsotg *hsotg)
{
@@ -1256,6 +1263,16 @@ static void dwc2_hc_nak_intr(struct dwc2
/*
* Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and
* interrupt. Re-start the SSPLIT transfer.
+ *
+ * Normally for non-periodic transfers we'll retry right away, but to
+ * avoid interrupt storms we'll wait before retrying if we've got
+ * several NAKs. If we didn't do this we'd retry directly from the
+ * interrupt handler and could end up quickly getting another
+ * interrupt (another NAK), which we'd retry.
+ *
+ * Note that in DMA mode software only gets involved to re-send NAKed
+ * transfers for split transactions unless the core is missing OUT NAK
+ * enhancement.
*/
if (chan->do_split) {
/*
@@ -1272,6 +1289,8 @@ static void dwc2_hc_nak_intr(struct dwc2
if (chan->complete_split)
qtd->error_count = 0;
qtd->complete_split = 0;
+ qtd->num_naks++;
+ qtd->qh->want_wait = qtd->num_naks >= dwc2_naks_before_delay;
dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NAK);
goto handle_nak_done;
}
@@ -1297,7 +1316,12 @@ static void dwc2_hc_nak_intr(struct dwc2
*/
qtd->error_count = 0;
- if (!chan->qh->ping_state) {
+ if (hsotg->core_params->dma_enable > 0 && !chan->ep_is_in) {
+ /*
+ * Avoid interrupt storms.
+ */
+ qtd->qh->want_wait = 1;
+ } else if (!chan->qh->ping_state) {
dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb,
qtd, DWC2_HC_XFER_NAK);
dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd);