linux/drivers/usb/core/offload.c
Guan-Yu Lin 7f70b89b2b 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: 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
2025-09-12 14:08:02 +02:00

136 lines
3.4 KiB
C

// 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);