dwc_otg: fiq_fsm: Add non-periodic TT exclusivity constraints
authorP33M <p33m@github.com>
Tue, 20 Jun 2017 12:44:01 +0000 (13:44 +0100)
committerRaspbian kernel package updater <root@raspbian.org>
Sun, 8 Oct 2017 01:08:14 +0000 (01:08 +0000)
Certain hub types do not discriminate between pipe direction (IN or OUT)
when considering non-periodic transfers. Therefore these hubs get confused
if multiple transfers are issued in different directions with the same
device address and endpoint number.

Constrain queuing non-periodic split transactions so they are performed
serially in such cases.

Related: https://github.com/raspberrypi/linux/issues/2024

drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c
drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h
drivers/usb/host/dwc_otg/dwc_otg_hcd.c

index 208252645c09d1d17bf07673989f91b7f4b3ef7a..0163e9cf620ba58df36a872b82cea92734baada6 100644 (file)
@@ -191,6 +191,32 @@ static void notrace fiq_fsm_setup_csplit(struct fiq_state *st, int n)
        mb();
 }
 
+/**
+ * fiq_fsm_restart_np_pending() - Restart a single non-periodic contended transfer
+ * @st: Pointer to the channel's state
+ * @num_channels: Total number of host channels
+ * @orig_channel: Channel index of completed transfer
+ *
+ * In the case where an IN and OUT transfer are simultaneously scheduled to the
+ * same device/EP, inadequate hub implementations will misbehave. Once the first
+ * transfer is complete, a pending non-periodic split can then be issued.
+ */
+static void notrace fiq_fsm_restart_np_pending(struct fiq_state *st, int num_channels, int orig_channel)
+{
+       int i;
+       int dev_addr = st->channel[orig_channel].hcchar_copy.b.devaddr;
+       int ep_num = st->channel[orig_channel].hcchar_copy.b.epnum;
+       for (i = 0; i < num_channels; i++) {
+               if (st->channel[i].fsm == FIQ_NP_SSPLIT_PENDING &&
+                       st->channel[i].hcchar_copy.b.devaddr == dev_addr &&
+                       st->channel[i].hcchar_copy.b.epnum == ep_num) {
+                       st->channel[i].fsm = FIQ_NP_SSPLIT_STARTED;
+                       fiq_fsm_restart_channel(st, i, 0);
+                       break;
+               }
+       }
+}
+
 static inline int notrace fiq_get_xfer_len(struct fiq_state *st, int n)
 {
        /* The xfersize register is a bit wonky. For IN transfers, it decrements by the packet size. */
@@ -870,6 +896,9 @@ static int notrace noinline fiq_fsm_do_hcintr(struct fiq_state *state, int num_c
                                st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
                        }
                }
+               if (st->fsm != FIQ_NP_IN_CSPLIT_RETRY) {
+                       fiq_fsm_restart_np_pending(state, num_channels, n);
+               }
                break;
 
        case FIQ_NP_OUT_CSPLIT_RETRY:
@@ -919,6 +948,9 @@ static int notrace noinline fiq_fsm_do_hcintr(struct fiq_state *state, int num_c
                                st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
                        }
                }
+               if (st->fsm != FIQ_NP_OUT_CSPLIT_RETRY) {
+                       fiq_fsm_restart_np_pending(state, num_channels, n);
+               }
                break;
 
        /* Periodic split states (except isoc out) */
index 0a1ddf3f89f45ca75b8880722fbc22cbdc9f4a2f..ed088f34f210e9a337ab9b80fff0cf9e9b0241ea 100644 (file)
@@ -178,6 +178,8 @@ enum fiq_fsm_state {
        /* Nonperiodic state groups */
        FIQ_NP_SSPLIT_STARTED = 1,
        FIQ_NP_SSPLIT_RETRY = 2,
+       /* TT contention - working around hub bugs */
+       FIQ_NP_SSPLIT_PENDING = 33,
        FIQ_NP_OUT_CSPLIT_RETRY = 3,
        FIQ_NP_IN_CSPLIT_RETRY = 4,
        FIQ_NP_SPLIT_DONE = 5,
index 71834cf365e67d7ad995bba7869216c4091c3a74..7710370b30363e3170bf9bf522597c5f41dfb908 100644 (file)
@@ -1572,6 +1572,45 @@ int fiq_fsm_setup_periodic_dma(dwc_otg_hcd_t *hcd, struct fiq_channel_state *st,
        }
 }
 
+/**
+ * fiq_fsm_np_tt_contended() - Avoid performing contended non-periodic transfers
+ * @hcd: Pointer to the dwc_otg_hcd struct
+ * @qh: Pointer to the endpoint's queue head
+ *
+ * Certain hub chips don't differentiate between IN and OUT non-periodic pipes
+ * with the same endpoint number. If transfers get completed out of order
+ * (disregarding the direction token) then the hub can lock up
+ * or return erroneous responses.
+ *
+ * Returns 1 if initiating the transfer would cause contention, 0 otherwise.
+ */
+int fiq_fsm_np_tt_contended(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+       int i;
+       struct fiq_channel_state *st;
+       int dev_addr = qh->channel->dev_addr;
+       int ep_num = qh->channel->ep_num;
+       for (i = 0; i < hcd->core_if->core_params->host_channels; i++) {
+               if (i == qh->channel->hc_num)
+                       continue;
+               st = &hcd->fiq_state->channel[i];
+               switch (st->fsm) {
+               case FIQ_NP_SSPLIT_STARTED:
+               case FIQ_NP_SSPLIT_RETRY:
+               case FIQ_NP_SSPLIT_PENDING:
+               case FIQ_NP_OUT_CSPLIT_RETRY:
+               case FIQ_NP_IN_CSPLIT_RETRY:
+                       if (st->hcchar_copy.b.devaddr == dev_addr &&
+                               st->hcchar_copy.b.epnum == ep_num)
+                               return 1;
+                       break;
+               default:
+                       break;
+               }
+       }
+       return 0;
+}
+
 /*
  * Pushing a periodic request into the queue near the EOF1 point
  * in a microframe causes erroneous behaviour (frmovrun) interrupt.
@@ -1894,7 +1933,12 @@ int fiq_fsm_queue_split_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
        switch (hc->ep_type) {
                case UE_CONTROL:
                case UE_BULK:
-                       st->fsm = FIQ_NP_SSPLIT_STARTED;
+                       if (fiq_fsm_np_tt_contended(hcd, qh)) {
+                               st->fsm = FIQ_NP_SSPLIT_PENDING;
+                               start_immediate = 0;
+                       } else {
+                               st->fsm = FIQ_NP_SSPLIT_STARTED;
+                       }
                        break;
                case UE_ISOCHRONOUS:
                        if (hc->ep_is_in) {
@@ -1918,7 +1962,12 @@ int fiq_fsm_queue_split_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
                        break;
                case UE_INTERRUPT:
                        if (fiq_fsm_mask & 0x8) {
-                               st->fsm = FIQ_NP_SSPLIT_STARTED;
+                               if (fiq_fsm_np_tt_contended(hcd, qh)) {
+                                       st->fsm = FIQ_NP_SSPLIT_PENDING;
+                                       start_immediate = 0;
+                               } else {
+                                       st->fsm = FIQ_NP_SSPLIT_STARTED;
+                               }
                        } else if (start_immediate) {
                                        st->fsm = FIQ_PER_SSPLIT_STARTED;
                        } else {