]> exis.tech > repos - linux.git/commitdiff
xhci: dbc: support runtime suspend while DbC is in enabled state
authorMathias Nyman <mathias.nyman@linux.intel.com>
Tue, 16 Jun 2026 10:09:16 +0000 (13:09 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 25 Jun 2026 14:10:08 +0000 (15:10 +0100)
Allow xHC to runtime suspend if DbC is in 'enabled' state for over
15 seconds without a connect.

Idea is that every time we go to 'enabled' state we make sure DbC runtime
pm usage is '1' and save a timestamp. if the event loop still finds DbC in
enabled state 15 seconds later then it decrease DbC runtime pm usage by
calling pm_runtime_put().
Enabled state is reached either when DbC is enabled by userspace or a
connected/configured DbC is disconnected.

When a connect is detected we make sure DbC usage count is 1.

If DbC has been in 'enabled' state for 15 seconds and DbC usage is
decreased to 0 by pm_runtime_put, then the whole xHC controller may
runtime suspends to PCI D3 state if no other devices are using it

DbC sysfs file will show 'suspended' when xHC is suspended and will wake up
and enable DbC at cable connect, or when user writes 'enable' to the file.

This patch was originally part of a larger DbC series, but dropped before
the series was submitted to 7.2-rc1. The series has a locking issue in
commit 520058b73ba3 ("xhci: dbc: serialize enabling and disabling dbc")
which is also resolved by this patch

Fixes: 520058b73ba3 ("xhci: dbc: serialize enabling and disabling dbc")
Reported-by: Chaitanya Kumar Borah <chaitanya.kumar.borah@intel.com>
Closes: https://lore.kernel.org/linux-usb/9ce24ff5-efab-4089-92d7-709862d68e6d@intel.com
Tested-by: Chaitanya Kumar Borah <chaitanya.kumar.borah@intel.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://patch.msgid.link/20260616100916.2234205-2-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd
drivers/usb/host/xhci-dbgcap.c
drivers/usb/host/xhci-dbgcap.h

index 98a8376a83d29cf61437f4065b87a66b3fbca017..991765d84201e1e67d1c81651a182d350c7b9179 100644 (file)
@@ -22,7 +22,7 @@ Description:
 
                Reading this attribute gives the state of the DbC. It
                can be one of the following states: disabled, enabled,
-               initialized, connected or configured.
+               initialized, connected, configured or suspended.
 
 What:          /sys/bus/pci/drivers/xhci_hcd/.../dbc_idVendor
 Date:          March 2023
index b1cabf5582fa1e6e7d55d0d0056946839e4da1cf..48ee6a4f9e1c0757ec3a15fd6e47df6d57883dd4 100644 (file)
@@ -646,6 +646,31 @@ static int xhci_dbc_enable_dce(struct xhci_dbc *dbc, bool enable)
 
 static void xhci_dbc_set_state(struct xhci_dbc *dbc, enum dbc_state new_state)
 {
+       if (dbc->state == new_state)
+               return;
+
+       switch (new_state) {
+       case DS_ENABLED:
+               /*
+                * DbC pm usage is 1 here, both when moved from disconnect or
+                * configured states, or when setting initial DbC enable state.
+                * Just enable pending put
+                */
+               dev_dbg(dbc->dev, "DbC set pending_rpm_put = 1\n");
+               dbc->pending_rpm_put = 1;
+               break;
+       case DS_CONNECTED:
+               if (dbc->pending_rpm_put)
+                       /* DbC pm usage still 1, just remove pending put */
+                       dbc->pending_rpm_put = 0;
+               else
+                       /* DbC pm usage was put to 0, call get */
+                       pm_runtime_get(dbc->dev);
+               break;
+       default:
+               break;
+       }
+
        dbc->state_timestamp = jiffies;
        dbc->state = new_state;
 }
@@ -681,7 +706,7 @@ static int xhci_dbc_start(struct xhci_dbc *dbc)
 
        WARN_ON(!dbc);
 
-       pm_runtime_get_sync(dbc->dev); /* note this was self.controller */
+       pm_runtime_get(dbc->dev);
 
        spin_lock_irqsave(&dbc->lock, flags);
        ret = xhci_do_dbc_start(dbc);
@@ -706,6 +731,7 @@ err_unlock:
 static void xhci_dbc_stop(struct xhci_dbc *dbc)
 {
        unsigned long           flags;
+       bool                    need_rpm_put = false;
 
        WARN_ON(!dbc);
 
@@ -731,12 +757,20 @@ static void xhci_dbc_stop(struct xhci_dbc *dbc)
 
        spin_lock_irqsave(&dbc->lock, flags);
        writel(0, &dbc->regs->control);
+
+       if (dbc->state == DS_CONNECTED || dbc->state == DS_CONFIGURED ||
+           dbc->pending_rpm_put)
+               need_rpm_put = true;
+
+       dbc->pending_rpm_put = 0;
+
        xhci_dbc_set_state(dbc, DS_DISABLED);
        spin_unlock_irqrestore(&dbc->lock, flags);
 
        xhci_dbc_mem_cleanup(dbc);
 
-       pm_runtime_put(dbc->dev); /* note, was self.controller */
+       if (need_rpm_put)
+               pm_runtime_put(dbc->dev);
 }
 
 static void
@@ -908,6 +942,12 @@ static enum evtreturn xhci_dbc_do_handle_events(struct xhci_dbc *dbc)
                        dev_info(dbc->dev, "DbC connected\n");
                } else if (!(ctrl & DBC_CTRL_DBC_ENABLE)) {
                        dev_err(dbc->dev, "unexpected DbC disable, xHC reset?\n");
+               } else if (dbc->pending_rpm_put &&
+                          time_is_before_jiffies(dbc->state_timestamp +
+                               msecs_to_jiffies(DBC_AUTOSUSPEND_DELAY))) {
+                       dbc->pending_rpm_put = 0;
+                       dev_dbg(dbc->dev, "DbC Enabled state for 15 seconds, allow rpm suspend\n");
+                       pm_runtime_put(dbc->dev);
                }
 
                return EVT_DONE;
@@ -1096,6 +1136,9 @@ static ssize_t dbc_show(struct device *dev,
        if (dbc->state >= ARRAY_SIZE(dbc_state_strings))
                return sysfs_emit(buf, "unknown\n");
 
+       if (dbc->resume_required)
+               return sysfs_emit(buf, "suspended\n");
+
        return sysfs_emit(buf, "%s\n", dbc_state_strings[dbc->state]);
 }
 
@@ -1110,12 +1153,25 @@ static ssize_t dbc_store(struct device *dev,
        dbc = xhci->dbc;
 
        if (sysfs_streq(buf, "enable")) {
+               pm_runtime_get_sync(dbc->dev);
+
                mutex_lock(&dbc->enable_mutex);
+               /*
+                * DbC may already be enabled here if xhci was suspended with
+                * dbc->resume_required set, and resumed by pm_runtime_get_sync()
+                * above. In this case we end up calling xhci_dbc_start() twice,
+                * second time returns an error but is harmless
+                */
                xhci_dbc_start(dbc);
+
                mutex_unlock(&dbc->enable_mutex);
+               pm_runtime_put(dbc->dev);
        } else if (sysfs_streq(buf, "disable")) {
                mutex_lock(&dbc->enable_mutex);
+
+               dbc->resume_required = 0;
                xhci_dbc_stop(dbc);
+
                mutex_unlock(&dbc->enable_mutex);
        } else {
                return -EINVAL;
index df7aca8bfe992c003616250c7d83c65173c02153..5b18efb2c1eab58cb3ce5f062ef1e45373e963d0 100644 (file)
@@ -114,6 +114,8 @@ struct dbc_ep {
 #define DBC_POLL_INTERVAL_MAX          5000    /* milliseconds */
 #define DBC_XFER_INACTIVITY_TIMEOUT    10      /* milliseconds */
 #define DBC_ENUMERATION_TIMEOUT                2000    /* milliseconds */
+#define DBC_AUTOSUSPEND_DELAY          15000   /* milliseconds */
+
 /*
  * Private structure for DbC hardware state:
  */
@@ -166,6 +168,7 @@ struct xhci_dbc {
        unsigned long                   xfer_timestamp;
        unsigned long                   state_timestamp;
        unsigned                        resume_required:1;
+       unsigned                        pending_rpm_put:1;
        struct dbc_ep                   eps[2];
 
        const struct dbc_driver         *driver;