summaryrefslogtreecommitdiff
path: root/drivers/usb/host
diff options
context:
space:
mode:
authorUdipto Goswami <quic_ugoswami@quicinc.com>2023-10-19 13:29:22 +0300
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2023-10-21 13:38:54 +0300
commit6ccb83d6c4972ebe6ae49de5eba051de3638362c (patch)
tree0c445a42f1c128a11fde186eb29f7cf9592f9825 /drivers/usb/host
parent47f503cf5f799ec02e5f4b7c3b9afe145eca2aef (diff)
downloadlinux-6ccb83d6c4972ebe6ae49de5eba051de3638362c.tar.xz
usb: xhci: Implement xhci_handshake_check_state() helper
In some situations where xhci removal happens parallel to xhci_handshake, we encounter a scenario where the xhci_handshake can't succeed, and it polls until timeout. If xhci_handshake runs until timeout it can on some platforms result in a long wait which might lead to a watchdog timeout. Add a helper that checks xhci status during the handshake, and exits if set state is entered. Use this helper in places where xhci_handshake is called unlocked and has a long timeout. For example xhci command timeout and xhci reset. [commit message and code comment rewording -Mathias] Signed-off-by: Udipto Goswami <quic_ugoswami@quicinc.com> Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> Link: https://lore.kernel.org/r/20231019102924.2797346-18-mathias.nyman@linux.intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/xhci-ring.c5
-rw-r--r--drivers/usb/host/xhci.c26
-rw-r--r--drivers/usb/host/xhci.h2
3 files changed, 30 insertions, 3 deletions
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 73057f01e4aa..f3b5e6345858 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -450,8 +450,9 @@ static int xhci_abort_cmd_ring(struct xhci_hcd *xhci, unsigned long flags)
* In the future we should distinguish between -ENODEV and -ETIMEDOUT
* and try to recover a -ETIMEDOUT with a host controller reset.
*/
- ret = xhci_handshake(&xhci->op_regs->cmd_ring,
- CMD_RING_RUNNING, 0, 5 * 1000 * 1000);
+ ret = xhci_handshake_check_state(xhci, &xhci->op_regs->cmd_ring,
+ CMD_RING_RUNNING, 0, 5 * 1000 * 1000,
+ XHCI_STATE_REMOVING);
if (ret < 0) {
xhci_err(xhci, "Abort failed to stop command ring: %d\n", ret);
xhci_halt(xhci);
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 68920cb96044..119cbe2a3d65 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -82,6 +82,29 @@ int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us)
}
/*
+ * xhci_handshake_check_state - same as xhci_handshake but takes an additional
+ * exit_state parameter, and bails out with an error immediately when xhc_state
+ * has exit_state flag set.
+ */
+int xhci_handshake_check_state(struct xhci_hcd *xhci, void __iomem *ptr,
+ u32 mask, u32 done, int usec, unsigned int exit_state)
+{
+ u32 result;
+ int ret;
+
+ ret = readl_poll_timeout_atomic(ptr, result,
+ (result & mask) == done ||
+ result == U32_MAX ||
+ xhci->xhc_state & exit_state,
+ 1, usec);
+
+ if (result == U32_MAX || xhci->xhc_state & exit_state)
+ return -ENODEV;
+
+ return ret;
+}
+
+/*
* Disable interrupts and begin the xHCI halting process.
*/
void xhci_quiesce(struct xhci_hcd *xhci)
@@ -201,7 +224,8 @@ int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us)
if (xhci->quirks & XHCI_INTEL_HOST)
udelay(1000);
- ret = xhci_handshake(&xhci->op_regs->command, CMD_RESET, 0, timeout_us);
+ ret = xhci_handshake_check_state(xhci, &xhci->op_regs->command,
+ CMD_RESET, 0, timeout_us, XHCI_STATE_REMOVING);
if (ret)
return ret;
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 813d55468c00..5768ce804caa 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -2084,6 +2084,8 @@ void xhci_free_container_ctx(struct xhci_hcd *xhci,
/* xHCI host controller glue */
typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *);
int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, u64 timeout_us);
+int xhci_handshake_check_state(struct xhci_hcd *xhci, void __iomem *ptr,
+ u32 mask, u32 done, int usec, unsigned int exit_state);
void xhci_quiesce(struct xhci_hcd *xhci);
int xhci_halt(struct xhci_hcd *xhci);
int xhci_start(struct xhci_hcd *xhci);