]> www.infradead.org Git - users/hch/misc.git/commitdiff
usb: offload: add apis for offload usage tracking
authorGuan-Yu Lin <guanyulin@google.com>
Thu, 11 Sep 2025 14:20:14 +0000 (14:20 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 12 Sep 2025 12:08:02 +0000 (14:08 +0200)
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@google.com>
Link: https://lore.kernel.org/r/20250911142051.90822-3-guanyulin@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Link: https://lore.kernel.org/r/20250911142051.90822-3-guanyulin@google.com
drivers/usb/core/Makefile
drivers/usb/core/offload.c [new file with mode: 0644]
drivers/usb/core/usb.c
include/linux/usb.h

index ac006abd13b3ad8362dc7baa115124c11eaafc84..766000b4939ef937a04848aa9cc45d8bb8860fe5 100644 (file)
@@ -9,6 +9,7 @@ usbcore-y += devio.o notify.o generic.o quirks.o devices.o
 usbcore-y += phy.o port.o
 
 usbcore-$(CONFIG_OF)           += of.o
+usbcore-$(CONFIG_USB_XHCI_SIDEBAND)    += offload.o
 usbcore-$(CONFIG_USB_PCI)              += hcd-pci.o
 usbcore-$(CONFIG_ACPI)         += usb-acpi.o
 
diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c
new file mode 100644 (file)
index 0000000..7c699f1
--- /dev/null
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * offload.c - USB offload related functions
+ *
+ * Copyright (c) 2025, Google LLC.
+ *
+ * Author: Guan-Yu Lin
+ */
+
+#include <linux/usb.h>
+
+#include "usb.h"
+
+/**
+ * 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.
+ *
+ * Return: 0 on success. A negative error code otherwise.
+ */
+int usb_offload_get(struct usb_device *udev)
+{
+       int ret;
+
+       usb_lock_device(udev);
+       if (udev->state == USB_STATE_NOTATTACHED) {
+               usb_unlock_device(udev);
+               return -ENODEV;
+       }
+
+       if (udev->state == USB_STATE_SUSPENDED ||
+                  udev->offload_at_suspend) {
+               usb_unlock_device(udev);
+               return -EBUSY;
+       }
+
+       /*
+        * 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) {
+               usb_unlock_device(udev);
+               return ret;
+       }
+
+       udev->offload_usage++;
+       usb_autosuspend_device(udev);
+       usb_unlock_device(udev);
+
+       return ret;
+}
+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.
+ *
+ * Return: 0 on success. A negative error code otherwise.
+ */
+int usb_offload_put(struct usb_device *udev)
+{
+       int ret;
+
+       usb_lock_device(udev);
+       if (udev->state == USB_STATE_NOTATTACHED) {
+               usb_unlock_device(udev);
+               return -ENODEV;
+       }
+
+       if (udev->state == USB_STATE_SUSPENDED ||
+                  udev->offload_at_suspend) {
+               usb_unlock_device(udev);
+               return -EBUSY;
+       }
+
+       /*
+        * 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) {
+               usb_unlock_device(udev);
+               return ret;
+       }
+
+       /* Drop the count when it wasn't 0, ignore the operation otherwise. */
+       if (udev->offload_usage)
+               udev->offload_usage--;
+       usb_autosuspend_device(udev);
+       usb_unlock_device(udev);
+
+       return ret;
+}
+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. In addition, the caller should
+ * ensure downstream usb devices are all either suspended or marked as
+ * "offload_at_suspend" to ensure the correctness of the return value.
+ *
+ * Returns true on any offload activity, false otherwise.
+ */
+bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex)
+{
+       struct usb_device *child;
+       bool active;
+       int port1;
+
+       usb_hub_for_each_child(udev, port1, child) {
+               usb_lock_device(child);
+               active = usb_offload_check(child);
+               usb_unlock_device(child);
+               if (active)
+                       return true;
+       }
+
+       return !!udev->offload_usage;
+}
+EXPORT_SYMBOL_GPL(usb_offload_check);
index b88b6271cb301896b1f6464f327b826a40183a2c..b6b0b84895237e2c49b5e8015627ad2c24ee31c2 100644 (file)
@@ -670,6 +670,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
        set_dev_node(&dev->dev, dev_to_node(bus->sysdev));
        dev->state = USB_STATE_ATTACHED;
        dev->lpm_disable_count = 1;
+       dev->offload_usage = 0;
        atomic_set(&dev->urbnum, 0);
 
        INIT_LIST_HEAD(&dev->ep0.urb_list);
index 70ef00c42d22caf8f0efd8c5c494ad2df95d9ad9..e85105939af8ee126461023c3bb8e3aabe251be4 100644 (file)
@@ -636,6 +636,8 @@ struct usb3_lpm_parameters {
  * @do_remote_wakeup:  remote wakeup should be enabled
  * @reset_resume: needs reset instead of resume
  * @port_is_suspended: the upstream port is suspended (L2 or U3)
+ * @offload_at_suspend: offload activities during suspend is enabled.
+ * @offload_usage: number of offload activities happening on this usb device.
  * @slot_id: Slot ID assigned by xHCI
  * @l1_params: best effor service latency for USB2 L1 LPM state, and L1 timeout.
  * @u1_params: exit latencies for USB3 U1 LPM state, and hub-initiated timeout.
@@ -724,6 +726,8 @@ struct usb_device {
        unsigned do_remote_wakeup:1;
        unsigned reset_resume:1;
        unsigned port_is_suspended:1;
+       unsigned offload_at_suspend:1;
+       int offload_usage;
        enum usb_link_tunnel_mode tunnel_mode;
        struct device_link *usb4_link;
 
@@ -841,6 +845,20 @@ static inline void usb_mark_last_busy(struct usb_device *udev)
 { }
 #endif
 
+#if IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND)
+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. */