| From 2a609f9fa4c30be3bf31fe594f509995c6ee20af Mon Sep 17 00:00:00 2001 |
| From: Marc Zyngier <marc.zyngier@arm.com> |
| Date: Wed, 23 May 2018 18:41:37 +0100 |
| Subject: [PATCH 1453/1795] xhci: Add quirk to zero 64bit registers on Renesas |
| PCIe controllers |
| |
| Some Renesas controllers get into a weird state if they are reset while |
| programmed with 64bit addresses (they will preserve the top half of the |
| address in internal, non visible registers). |
| |
| You end up with half the address coming from the kernel, and the other |
| half coming from the firmware. |
| |
| Also, changing the programming leads to extra accesses even if the |
| controller is supposed to be halted. The controller ends up with a fatal |
| fault, and is then ripe for being properly reset. On the flip side, |
| this is completely unsafe if the defvice isn't behind an IOMMU, so |
| we have to make sure that this is the case. Can you say "broken"? |
| |
| This is an alternative method to the one introduced in 8466489ef5ba |
| ("xhci: Reset Renesas uPD72020x USB controller for 32-bit DMA issue"), |
| which will subsequently be removed. |
| |
| Tested-by: Domenico Andreoli <domenico.andreoli@linux.com> |
| Signed-off-by: Marc Zyngier <marc.zyngier@arm.com> |
| Tested-by: Faiz Abbas <faiz_abbas@ti.com> |
| Tested-by: Domenico Andreoli <domenico.andreoli@linux.com> |
| Acked-by: Mathias Nyman <mathias.nyman@linux.intel.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| (cherry picked from commit 12de0a35c996c3a75d050bff748815db3432849c) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be> |
| --- |
| drivers/usb/host/xhci-pci.c | 8 +++-- |
| drivers/usb/host/xhci.c | 65 +++++++++++++++++++++++++++++++++++++ |
| drivers/usb/host/xhci.h | 1 + |
| 3 files changed, 72 insertions(+), 2 deletions(-) |
| |
| diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c |
| index 85ffda85f8ab..e0a0a12871e2 100644 |
| --- a/drivers/usb/host/xhci-pci.c |
| +++ b/drivers/usb/host/xhci-pci.c |
| @@ -196,11 +196,15 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) |
| xhci->quirks |= XHCI_BROKEN_STREAMS; |
| } |
| if (pdev->vendor == PCI_VENDOR_ID_RENESAS && |
| - pdev->device == 0x0014) |
| + pdev->device == 0x0014) { |
| xhci->quirks |= XHCI_TRUST_TX_LENGTH; |
| + xhci->quirks |= XHCI_ZERO_64B_REGS; |
| + } |
| if (pdev->vendor == PCI_VENDOR_ID_RENESAS && |
| - pdev->device == 0x0015) |
| + pdev->device == 0x0015) { |
| xhci->quirks |= XHCI_RESET_ON_RESUME; |
| + xhci->quirks |= XHCI_ZERO_64B_REGS; |
| + } |
| if (pdev->vendor == PCI_VENDOR_ID_VIA) |
| xhci->quirks |= XHCI_RESET_ON_RESUME; |
| |
| diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c |
| index eb16066906a6..0b41acd84137 100644 |
| --- a/drivers/usb/host/xhci.c |
| +++ b/drivers/usb/host/xhci.c |
| @@ -209,6 +209,68 @@ int xhci_reset(struct xhci_hcd *xhci) |
| return ret; |
| } |
| |
| +static void xhci_zero_64b_regs(struct xhci_hcd *xhci) |
| +{ |
| + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; |
| + int err, i; |
| + u64 val; |
| + |
| + /* |
| + * Some Renesas controllers get into a weird state if they are |
| + * reset while programmed with 64bit addresses (they will preserve |
| + * the top half of the address in internal, non visible |
| + * registers). You end up with half the address coming from the |
| + * kernel, and the other half coming from the firmware. Also, |
| + * changing the programming leads to extra accesses even if the |
| + * controller is supposed to be halted. The controller ends up with |
| + * a fatal fault, and is then ripe for being properly reset. |
| + * |
| + * Special care is taken to only apply this if the device is behind |
| + * an iommu. Doing anything when there is no iommu is definitely |
| + * unsafe... |
| + */ |
| + if (!(xhci->quirks & XHCI_ZERO_64B_REGS) || !dev->iommu_group) |
| + return; |
| + |
| + xhci_info(xhci, "Zeroing 64bit base registers, expecting fault\n"); |
| + |
| + /* Clear HSEIE so that faults do not get signaled */ |
| + val = readl(&xhci->op_regs->command); |
| + val &= ~CMD_HSEIE; |
| + writel(val, &xhci->op_regs->command); |
| + |
| + /* Clear HSE (aka FATAL) */ |
| + val = readl(&xhci->op_regs->status); |
| + val |= STS_FATAL; |
| + writel(val, &xhci->op_regs->status); |
| + |
| + /* Now zero the registers, and brace for impact */ |
| + val = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); |
| + if (upper_32_bits(val)) |
| + xhci_write_64(xhci, 0, &xhci->op_regs->dcbaa_ptr); |
| + val = xhci_read_64(xhci, &xhci->op_regs->cmd_ring); |
| + if (upper_32_bits(val)) |
| + xhci_write_64(xhci, 0, &xhci->op_regs->cmd_ring); |
| + |
| + for (i = 0; i < HCS_MAX_INTRS(xhci->hcs_params1); i++) { |
| + struct xhci_intr_reg __iomem *ir; |
| + |
| + ir = &xhci->run_regs->ir_set[i]; |
| + val = xhci_read_64(xhci, &ir->erst_base); |
| + if (upper_32_bits(val)) |
| + xhci_write_64(xhci, 0, &ir->erst_base); |
| + val= xhci_read_64(xhci, &ir->erst_dequeue); |
| + if (upper_32_bits(val)) |
| + xhci_write_64(xhci, 0, &ir->erst_dequeue); |
| + } |
| + |
| + /* Wait for the fault to appear. It will be cleared on reset */ |
| + err = xhci_handshake(&xhci->op_regs->status, |
| + STS_FATAL, STS_FATAL, |
| + XHCI_MAX_HALT_USEC); |
| + if (!err) |
| + xhci_info(xhci, "Fault detected\n"); |
| +} |
| |
| #ifdef CONFIG_USB_PCI |
| /* |
| @@ -1046,6 +1108,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) |
| |
| xhci_dbg(xhci, "Stop HCD\n"); |
| xhci_halt(xhci); |
| + xhci_zero_64b_regs(xhci); |
| xhci_reset(xhci); |
| spin_unlock_irq(&xhci->lock); |
| xhci_cleanup_msix(xhci); |
| @@ -4957,6 +5020,8 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) |
| if (retval) |
| return retval; |
| |
| + xhci_zero_64b_regs(xhci); |
| + |
| xhci_dbg(xhci, "Resetting HCD\n"); |
| /* Reset the internal HC memory state and registers. */ |
| retval = xhci_reset(xhci); |
| diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h |
| index d985210dda2d..ccf372e58521 100644 |
| --- a/drivers/usb/host/xhci.h |
| +++ b/drivers/usb/host/xhci.h |
| @@ -1845,6 +1845,7 @@ struct xhci_hcd { |
| #define XHCI_HW_LPM_DISABLE BIT_ULL(29) |
| #define XHCI_SUSPEND_DELAY BIT_ULL(30) |
| #define XHCI_INTEL_USB_ROLE_SW BIT_ULL(31) |
| +#define XHCI_ZERO_64B_REGS BIT_ULL(32) |
| |
| unsigned int num_active_eps; |
| unsigned int limit_active_eps; |
| -- |
| 2.19.0 |
| |