blob: 9db3cfedd29c7b55a65dbbfdd6a7ef1ae77a446a [file] [edit]
// 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 = 0;
if (!usb_get_dev(udev))
return -ENODEV;
if (pm_runtime_get_if_active(&udev->dev) != 1) {
ret = -EBUSY;
goto err_rpm;
}
spin_lock(&udev->offload_lock);
if (udev->offload_pm_locked) {
ret = -EAGAIN;
goto err;
}
udev->offload_usage++;
err:
spin_unlock(&udev->offload_lock);
pm_runtime_put_autosuspend(&udev->dev);
err_rpm:
usb_put_dev(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 = 0;
if (!usb_get_dev(udev))
return -ENODEV;
if (pm_runtime_get_if_active(&udev->dev) != 1) {
ret = -EBUSY;
goto err_rpm;
}
spin_lock(&udev->offload_lock);
if (udev->offload_pm_locked) {
ret = -EAGAIN;
goto err;
}
/* Drop the count when it wasn't 0, ignore the operation otherwise. */
if (udev->offload_usage)
udev->offload_usage--;
err:
spin_unlock(&udev->offload_lock);
pm_runtime_put_autosuspend(&udev->dev);
err_rpm:
usb_put_dev(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 the device itself and the downstream usb devices are all marked as
* "offload_pm_locked" 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 = false;
int port1;
if (udev->offload_usage)
return true;
usb_hub_for_each_child(udev, port1, child) {
usb_lock_device(child);
active = usb_offload_check(child);
usb_unlock_device(child);
if (active)
break;
}
return active;
}
EXPORT_SYMBOL_GPL(usb_offload_check);
/**
* usb_offload_set_pm_locked - set the PM lock state of a USB device
* @udev: the USB device to modify
* @locked: the new lock state
*
* Setting @locked to true prevents offload_usage from being modified. This
* ensures that offload activities cannot be started or stopped during critical
* power management transitions, maintaining a stable state for the duration
* of the transition.
*/
void usb_offload_set_pm_locked(struct usb_device *udev, bool locked)
{
spin_lock(&udev->offload_lock);
udev->offload_pm_locked = locked;
spin_unlock(&udev->offload_lock);
}
EXPORT_SYMBOL_GPL(usb_offload_set_pm_locked);