fiq_fsm: Push error recovery into the FIQ when fiq_fsm is used
If the transfer associated with a QTD failed due to a bus error, the HCD
would retry the transfer up to 3 times (implementing the USB2.0
three-strikes retry in software).
Due to the masking mechanism used by fiq_fsm, it is only possible to pass
a single interrupt through to the HCD per-transfer.
In this instance host channels would fall off the radar because the error
reset would function, but the subsequent channel halt would be lost.
Push the error count reset into the FIQ handler.
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 e286d57..c3d64cf 100644
--- a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c
@@ -612,6 +612,7 @@
{
hcint_data_t hcint;
hcintmsk_data_t hcintmsk;
+ hcint_data_t hcint_probe;
hcchar_data_t hcchar;
int handled = 0;
int restart = 0;
@@ -622,7 +623,8 @@
hcint.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINT);
hcintmsk.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK);
-
+ hcint_probe.d32 = hcint.d32 & hcintmsk.d32;
+
if (st->fsm != FIQ_PASSTHROUGH) {
fiq_print(FIQDBG_INT, state, "HC%01d ST%02d", n, st->fsm);
fiq_print(FIQDBG_INT, state, "%08x", hcint.d32);
@@ -635,6 +637,30 @@
/* doesn't belong to us, kick it upstairs */
break;
+ case FIQ_PASSTHROUGH_ERRORSTATE:
+ /* We are here to emulate the error recovery mechanism of the dwc HCD.
+ * Several interrupts are unmasked if a previous transaction failed - it's
+ * death for the FIQ to attempt to handle them as the channel isn't halted.
+ * Emulate what the HCD does in this situation: mask and continue.
+ * The FSM has no other state setup so this has to be handled out-of-band.
+ */
+ fiq_print(FIQDBG_ERR, state, "ERRST %02d", n);
+ if (hcint_probe.b.nak || hcint_probe.b.ack || hcint_probe.b.datatglerr) {
+ fiq_print(FIQDBG_ERR, state, "RESET %02d", n);
+ st->nr_errors = 0;
+ hcintmsk.b.nak = 0;
+ hcintmsk.b.ack = 0;
+ hcintmsk.b.datatglerr = 0;
+ FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK, hcintmsk.d32);
+ return 1;
+ }
+ if (hcint_probe.b.chhltd) {
+ fiq_print(FIQDBG_ERR, state, "CHHLT %02d", n);
+ fiq_print(FIQDBG_ERR, state, "%08x", hcint.d32);
+ return 0;
+ }
+ break;
+
/* Non-periodic state groups */
case FIQ_NP_SSPLIT_STARTED:
case FIQ_NP_SSPLIT_RETRY:
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 716e921..bc808a0 100644
--- a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h
+++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h
@@ -155,7 +155,9 @@
enum fiq_fsm_state {
/* FIQ isn't enabled for this host channel */
FIQ_PASSTHROUGH = 0,
-
+ /* For the first interrupt received for this channel,
+ * the FIQ has to ack any interrupts indicating success. */
+ FIQ_PASSTHROUGH_ERRORSTATE = 31,
/* Nonperiodic state groups */
FIQ_NP_SSPLIT_STARTED = 1,
FIQ_NP_SSPLIT_RETRY = 2,
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
index 0f5ed30..c6d4df4 100644
--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
@@ -2073,6 +2073,12 @@
hc->qh->ping_state = 0;
}
} else if (!hc->xfer_started) {
+ if (fiq_fsm_enable && hc->error_state) {
+ hcd->fiq_state->channel[hc->hc_num].nr_errors =
+ DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list)->error_count;
+ hcd->fiq_state->channel[hc->hc_num].fsm =
+ FIQ_PASSTHROUGH_ERRORSTATE;
+ }
dwc_otg_hc_start_transfer(hcd->core_if, hc);
hc->qh->ping_state = 0;
}
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c
index 981334f..1f8b81e 100644
--- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c
@@ -2600,21 +2600,30 @@
release_channel(dwc_otg_hcd, hc, NULL, hc->halt_status);
return 1;
}
+ qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list);
/*
* FSM mode: Check to see if this is a HC interrupt from a channel handled by the FIQ.
* Execution path is fundamentally different for the channels after a FIQ has completed
* a split transaction.
*/
-
-
if (fiq_fsm_enable) {
- if (*(volatile uint32_t *)&dwc_otg_hcd->fiq_state->channel[num].fsm != FIQ_PASSTHROUGH) {
- dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd, num);
- return 1;
+ switch (dwc_otg_hcd->fiq_state->channel[num].fsm) {
+ case FIQ_PASSTHROUGH:
+ break;
+ case FIQ_PASSTHROUGH_ERRORSTATE:
+ /* Hook into the error count */
+ fiq_print(FIQDBG_ERR, dwc_otg_hcd->fiq_state, "HCDERR%02d", num);
+ if (dwc_otg_hcd->fiq_state->channel[num].nr_errors) {
+ qtd->error_count = 0;
+ fiq_print(FIQDBG_ERR, dwc_otg_hcd->fiq_state, "RESET ");
+ }
+ break;
+ default:
+ dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd, num);
+ return 1;
}
}
- qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list);
hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk);