[PATCH 2/3] xhci: Add quirk to zero 64bit registers on Renesas PCIe controllers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.

Signed-off-by: Marc Zyngier <marc.zyngier@xxxxxxx>
---
 drivers/usb/host/xhci-pci.c |  8 +++++--
 drivers/usb/host/xhci.c     | 56 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci.h     |  1 +
 3 files changed, 63 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 8dba26d3de07..cd6b48c43007 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -4921,6 +4921,62 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
 	if (retval)
 		return retval;
 
+	/*
+	 * 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.
+	 */
+	if (xhci->quirks & XHCI_ZERO_64B_REGS) {
+		u64 val;
+		int i;
+
+		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 */
+		retval = xhci_handshake(&xhci->op_regs->status,
+					STS_FATAL, STS_FATAL,
+					XHCI_MAX_HALT_USEC);
+		if (!retval)
+			xhci_info(xhci, "Fault detected\n");
+	}
+
 	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 42848dfc3445..5ea0b52b49cc 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1831,6 +1831,7 @@ struct xhci_hcd {
 #define XHCI_HW_LPM_DISABLE	(1 << 29)
 #define XHCI_SUSPEND_DELAY	(1 << 30)
 #define XHCI_INTEL_USB_ROLE_SW	(1 << 31)
+#define XHCI_ZERO_64B_REGS	(1 << 32)
 
 	unsigned int		num_active_eps;
 	unsigned int		limit_active_eps;
-- 
2.14.2

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux