dwc_otg: fiq_fsm: pause when cancelling split transactions
authorJonathan Bell <jonathan@raspberrypi.org>
Wed, 8 Jan 2020 12:48:09 +0000 (12:48 +0000)
committerPhil Elwell <pelwell@users.noreply.github.com>
Mon, 13 Jan 2020 17:31:35 +0000 (17:31 +0000)
Non-periodic splits will DMA to/from the driver-provided transfer_buffer,
which may be freed immediately after the dequeue call returns. Block until
we know the transfer is complete.

A similar delay is needed when cleaning up disconnects, as the FIQ could
have started a periodic transfer in the previous microframe to the one
that triggered a disconnect.

Signed-off-by: Jonathan Bell <jonathan@raspberrypi.org>
drivers/usb/host/dwc_otg/dwc_otg_hcd.c
drivers/usb/host/dwc_otg/dwc_otg_os_dep.h

index cba2aa0..0f6645e 100644 (file)
@@ -175,6 +175,7 @@ static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list)
        dwc_list_link_t *qh_item, *qh_tmp;
        dwc_otg_qh_t *qh;
        dwc_otg_qtd_t *qtd, *qtd_tmp;
+       int quiesced = 0;
 
        DWC_LIST_FOREACH_SAFE(qh_item, qh_tmp, qh_list) {
                qh = DWC_LIST_ENTRY(qh_item, dwc_otg_qh_t, qh_list_entry);
@@ -198,8 +199,17 @@ static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list)
                                qh->channel->halt_status = DWC_OTG_HC_XFER_URB_DEQUEUE;
                                qh->channel->halt_pending = 1;
                                if (hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_TURBO ||
-                                       hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING)
+                                   hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING)
                                        hcd->fiq_state->channel[n].fsm = FIQ_HS_ISOC_ABORTED;
+                               /* We're called from disconnect callback or in the middle of freeing the HCD here,
+                                * so FIQ is disabled, top-level interrupts masked and we're holding the spinlock.
+                                * No further URBs will be submitted, but wait 1 microframe for any previously
+                                * submitted periodic DMA to finish.
+                                */
+                               if (!quiesced) {
+                                       udelay(125);
+                                       quiesced = 1;
+                               }
                        } else {
                                dwc_otg_hc_halt(hcd->core_if, qh->channel,
                                                DWC_OTG_HC_XFER_URB_DEQUEUE);
@@ -600,15 +610,34 @@ int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd,
                        /* In FIQ FSM mode, we need to shut down carefully.
                         * The FIQ may attempt to restart a disabled channel */
                        if (fiq_fsm_enable && (hcd->fiq_state->channel[n].fsm != FIQ_PASSTHROUGH)) {
+                               int retries = 3;
+                               int running = 0;
+                               enum fiq_fsm_state state;
+
                                local_fiq_disable();
                                fiq_fsm_spin_lock(&hcd->fiq_state->lock);
                                qh->channel->halt_status = DWC_OTG_HC_XFER_URB_DEQUEUE;
                                qh->channel->halt_pending = 1;
                                if (hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_TURBO ||
-                                       hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING)
+                                   hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING)
                                        hcd->fiq_state->channel[n].fsm = FIQ_HS_ISOC_ABORTED;
                                fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
                                local_fiq_enable();
+
+                               if (dwc_qh_is_non_per(qh)) {
+                                       do {
+                                               state = READ_ONCE(hcd->fiq_state->channel[n].fsm);
+                                               running = (state != FIQ_NP_SPLIT_DONE) &&
+                                                         (state != FIQ_NP_SPLIT_LS_ABORTED) &&
+                                                         (state != FIQ_NP_SPLIT_HS_ABORTED);
+                                               if (!running)
+                                                       break;
+                                               udelay(125);
+                                       } while(--retries);
+                                       if (!retries)
+                                               DWC_WARN("Timed out waiting for FSM NP transfer to complete on %d",
+                                                        qh->channel->hc_num);
+                               }
                        } else {
                                dwc_otg_hc_halt(hcd->core_if, qh->channel,
                                                DWC_OTG_HC_XFER_URB_DEQUEUE);
index 0e9a34f..7a77977 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/workqueue.h>
 #include <linux/stat.h>
 #include <linux/pci.h>
+#include <linux/compiler.h>
 
 #include <linux/version.h>