Contributors: 8
Author Tokens Token Proportion Commits Commit Proportion
Guan-Yu Lin 321 89.92% 2 16.67%
Oliver Neukum 20 5.60% 1 8.33%
Greg Kroah-Hartman 7 1.96% 2 16.67%
Linus Torvalds (pre-git) 4 1.12% 3 25.00%
Eric Lescouet 2 0.56% 1 8.33%
David Brownell 1 0.28% 1 8.33%
Valentina Manea 1 0.28% 1 8.33%
Sakari Ailus 1 0.28% 1 8.33%
Total 357 12


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