Commit 7f70b89b authored by Guan-Yu Lin's avatar Guan-Yu Lin Committed by Greg Kroah-Hartman
Browse files

usb: offload: add apis for offload usage tracking



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: default avatarGuan-Yu Lin <guanyulin@google.com>
Link: https://lore.kernel.org/r/20250911142051.90822-3-guanyulin@google.com


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Link: https://lore.kernel.org/r/20250911142051.90822-3-guanyulin@google.com
parent ddb473a5
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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

+136 −0
Original line number Diff line number Diff line
// 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);
+1 −0
Original line number Diff line number Diff line
@@ -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);
+18 −0
Original line number Diff line number Diff line
@@ -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. */