Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153// 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);