[PATCH v11 2/4] usb: add apis for offload usage tracking

From: Guan-Yu Lin
Date: Tue Apr 08 2025 - 00:01:45 EST


Introduce offload_usage and corresponding apis to track offload usage
on each USB device. Offload denotes that there is another co-processor
accessing the USB device via the same USB host controller. To optimize
power usage, it's essential to monitor whether the USB device is
actively used by other co-processor. This information is vital when
determining if a USB device can be safely suspended during system power
state transitions.

Signed-off-by: Guan-Yu Lin <guanyulin@xxxxxxxxxx>
---
drivers/usb/core/driver.c | 108 ++++++++++++++++++++++++++++++++++++++
drivers/usb/core/usb.c | 1 +
drivers/usb/host/Kconfig | 11 ++++
include/linux/usb.h | 17 ++++++
4 files changed, 137 insertions(+)

diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 460d4dde5994..316526a05250 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -2036,6 +2036,114 @@ int usb_disable_usb2_hardware_lpm(struct usb_device *udev)

#endif /* CONFIG_PM */

+#if IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND_SUSPEND)
+
+/**
+ * usb_offload_get - increment the offload_usage of a USB device
+ * @udev: the USB device to increment its offload_usage
+ *
+ * Incrementing the offload_usage of a usb_device indicates that offload is
+ * enabled on this usb_device; that is, another entity is actively handling USB
+ * transfers. This information allows the USB driver to adjust its power
+ * management policy based on offload activity.
+ *
+ * The caller must hold @udev's device lock.
+ *
+ * Return: 0 on success. A negative error code otherwise.
+ */
+int usb_offload_get(struct usb_device *udev)
+{
+ int ret;
+
+ if (udev->state == USB_STATE_NOTATTACHED ||
+ udev->state == USB_STATE_SUSPENDED)
+ return -EAGAIN;
+
+ /*
+ * offload_usage could only be modified when the device is active, since
+ * it will alter the suspend flow of the device.
+ */
+ ret = usb_autoresume_device(udev);
+
+ if (ret < 0)
+ return ret;
+
+ refcount_inc(&udev->offload_usage);
+ usb_autosuspend_device(udev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_offload_get);
+
+/**
+ * usb_offload_put - drop the offload_usage of a USB device
+ * @udev: the USB device to drop its offload_usage
+ *
+ * The inverse operation of usb_offload_get, which drops the offload_usage of
+ * a USB device. This information allows the USB driver to adjust its power
+ * management policy based on offload activity.
+ *
+ * The caller must hold @udev's device lock.
+ *
+ * Return: 0 on success. A negative error code otherwise.
+ */
+int usb_offload_put(struct usb_device *udev)
+{
+ int ret;
+
+ if (udev->state == USB_STATE_NOTATTACHED ||
+ udev->state == USB_STATE_SUSPENDED)
+ return -EAGAIN;
+
+ /*
+ * offload_usage could only be modified when the device is active, since
+ * it will alter the suspend flow of the device.
+ */
+ ret = usb_autoresume_device(udev);
+
+ if (ret < 0)
+ return ret;
+
+ /* Drop the count when it wasn't 0, ignore the operation otherwise. */
+ ret = refcount_add_not_zero(-1, &udev->offload_usage);
+ usb_autosuspend_device(udev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_offload_put);
+
+/**
+ * usb_offload_check - check offload activities on a USB device
+ * @udev: the USB device to check its offload activity.
+ *
+ * Check if there are any offload activity on the USB device right now. This
+ * information could be used for power management or other forms of resource
+ * management.
+ *
+ * The caller must hold @udev's device lock.
+ *
+ * Returns true on any offload activity, false otherwise.
+ */
+bool usb_offload_check(struct usb_device *udev)
+{
+ struct usb_device *child;
+ bool active;
+ int port1;
+
+ usb_hub_for_each_child(udev, port1, child) {
+ device_lock(&child->dev);
+ active = usb_offload_check(child);
+ device_unlock(&child->dev);
+ if (active)
+ return true;
+ }
+
+ return !!refcount_read(&udev->offload_usage);
+}
+EXPORT_SYMBOL_GPL(usb_offload_check);
+
+#endif /* IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND_SUSPEND) */
+
const struct bus_type usb_bus_type = {
.name = "usb",
.match = usb_device_match,
diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c
index 0b4685aad2d5..765987813630 100644
--- a/drivers/usb/core/usb.c
+++ b/drivers/usb/core/usb.c
@@ -671,6 +671,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
dev->state = USB_STATE_ATTACHED;
dev->lpm_disable_count = 1;
atomic_set(&dev->urbnum, 0);
+ refcount_set(&dev->offload_usage, 0);

INIT_LIST_HEAD(&dev->ep0.urb_list);
dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE;
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 033a9a4b51fe..b564f46f1c87 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -113,6 +113,17 @@ config USB_XHCI_SIDEBAND
xHCI USB endpoints directly, allowing CPU to sleep while playing
audio.

+config USB_XHCI_SIDEBAND_SUSPEND
+ bool "xHCI support for sideband during system suspend"
+ depends on USB_XHCI_SIDEBAND
+ depends on USB_XHCI_PLATFORM
+ depends on SUSPEND
+ help
+ Say 'Y' to enable the support for the xHCI sideband capability
+ after system suspended. In addition to USB_XHCI_SIDEBAND, this
+ config allows endpoints and interrupters associated with the
+ sideband function when system is suspended.
+
config USB_XHCI_TEGRA
tristate "xHCI support for NVIDIA Tegra SoCs"
depends on PHY_TEGRA_XUSB
diff --git a/include/linux/usb.h b/include/linux/usb.h
index b46738701f8d..f3a4064c729c 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -647,6 +647,7 @@ struct usb3_lpm_parameters {
* parent->hub_delay + wHubDelay + tTPTransmissionDelay (40ns)
* Will be used as wValue for SetIsochDelay requests.
* @use_generic_driver: ask driver core to reprobe using the generic driver.
+ * @offload_usage: number of offload activities happening on this usb device.
*
* Notes:
* Usbcore drivers should not set usbdev->state directly. Instead use
@@ -733,6 +734,8 @@ struct usb_device {

u16 hub_delay;
unsigned use_generic_driver:1;
+
+ refcount_t offload_usage;
};

#define to_usb_device(__dev) container_of_const(__dev, struct usb_device, dev)
@@ -839,6 +842,20 @@ static inline void usb_mark_last_busy(struct usb_device *udev)
{ }
#endif

+#if IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND_SUSPEND)
+int usb_offload_get(struct usb_device *udev);
+int usb_offload_put(struct usb_device *udev);
+bool usb_offload_check(struct usb_device *udev);
+#else
+
+static inline int usb_offload_get(struct usb_device *udev)
+{ return 0; }
+static inline int usb_offload_put(struct usb_device *udev)
+{ return 0; }
+static inline bool usb_offload_check(struct usb_device *udev)
+{ return false; }
+#endif
+
extern int usb_disable_lpm(struct usb_device *udev);
extern void usb_enable_lpm(struct usb_device *udev);
/* Same as above, but these functions lock/unlock the bandwidth_mutex. */
--
2.49.0.504.g3bcea36a83-goog