]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
xhci: dbc: detect and recover hung DbC during enumeraton
authorMathias Nyman <mathias.nyman@linux.intel.com>
Wed, 3 Jun 2026 09:11:28 +0000 (12:11 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 3 Jun 2026 17:23:29 +0000 (19:23 +0200)
Add a timeout between the detection of the debug host connection and
the DbC Run transition to ‘1’. Toggle the DCE bit to re-enable DbC in
order to retry the debug device enumeration process if the DbC run
transition takes too long.

Set the timeout to 2 seconds

See xhci specification section 7.6.4.1 "Debug Capability Initialization"

Also detect cable disconnect during enable and connected state.

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://patch.msgid.link/20260603091132.1110849-12-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci-dbgcap.c
drivers/usb/host/xhci-dbgcap.h

index 34441ffa5e157015546cac6b59a51587eb87973b..b1cabf5582fa1e6e7d55d0d0056946839e4da1cf 100644 (file)
@@ -901,23 +901,49 @@ static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc)
                return EVT_ERR;
        case DS_ENABLED:
                portsc = readl(&dbc->regs->portsc);
+               ctrl = readl(&dbc->regs->control);
+
                if (portsc & DBC_PORTSC_CONN_STATUS) {
                        xhci_dbc_set_state(dbc, DS_CONNECTED);
                        dev_info(dbc->dev, "DbC connected\n");
+               } else if (!(ctrl & DBC_CTRL_DBC_ENABLE)) {
+                       dev_err(dbc->dev, "unexpected DbC disable, xHC reset?\n");
                }
 
                return EVT_DONE;
        case DS_CONNECTED:
                ctrl = readl(&dbc->regs->control);
+               portsc = readl(&dbc->regs->portsc);
                if (ctrl & DBC_CTRL_DBC_RUN) {
                        xhci_dbc_set_state(dbc, DS_CONFIGURED);
                        dev_info(dbc->dev, "DbC configured\n");
-                       portsc = readl(&dbc->regs->portsc);
                        writel(portsc, &dbc->regs->portsc);
                        ret = EVT_GSER;
                        break;
                }
 
+               /* Connection lost */
+               if (!(portsc & DBC_PORTSC_CONN_STATUS)) {
+                       /* covers DCE == 0 as it also sets CONN_STATUS to 0 */
+                       dev_warn(dbc->dev, "DbC connection lost mid enumeration\n");
+                       xhci_dbc_set_state(dbc, DS_ENABLED);
+
+                       return EVT_DONE;
+               }
+
+               /* Enumeration timeout */
+               if (time_is_before_jiffies(dbc->state_timestamp +
+                               msecs_to_jiffies(DBC_ENUMERATION_TIMEOUT))) {
+                       dev_err(dbc->dev, "DbC enumeration timeout, re-enabling DbC\n");
+                       dev_dbg(dbc->dev, "dcctrl %x, dcportsc %x\n", ctrl, portsc);
+
+                       /* Toggle DCE to retry enumeration */
+                       ret = xhci_dbc_enable_dce(dbc, false);
+                       udelay(100);
+                       ret = xhci_dbc_enable_dce(dbc, true);
+                       xhci_dbc_set_state(dbc, DS_ENABLED);
+               }
+
                return EVT_DONE;
        case DS_CONFIGURED:
                /* Handle cable unplug event: */
index b3efea43a6bee38a66f7cd79b6d5c8e68bf69ced..df7aca8bfe992c003616250c7d83c65173c02153 100644 (file)
@@ -113,6 +113,7 @@ struct dbc_ep {
 #define DBC_POLL_INTERVAL_DEFAULT      64      /* milliseconds */
 #define DBC_POLL_INTERVAL_MAX          5000    /* milliseconds */
 #define DBC_XFER_INACTIVITY_TIMEOUT    10      /* milliseconds */
+#define DBC_ENUMERATION_TIMEOUT                2000    /* milliseconds */
 /*
  * Private structure for DbC hardware state:
  */