From 590aeab8c4050e61fd412cc972a0a027e7964110 Mon Sep 17 00:00:00 2001 From: P33M Date: Tue, 20 Jun 2017 13:44:01 +0100 Subject: [PATCH] dwc_otg: fiq_fsm: Add non-periodic TT exclusivity constraints 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 | 32 +++++++++++++ drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h | 2 + drivers/usb/host/dwc_otg/dwc_otg_hcd.c | 53 +++++++++++++++++++++- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c index 208252645c09..0163e9cf620b 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c @@ -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) */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h index 0a1ddf3f89f4..ed088f34f210 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h +++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h @@ -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, diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c index 71834cf365e6..7710370b3036 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c @@ -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 { -- 2.30.2