mirror of
https://github.com/torvalds/linux.git
synced 2025-11-02 09:40:27 +02:00
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
136 lines
3.4 KiB
C
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);
|