mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-05-05 09:57:21 +02:00
bd3d245b0f
Replace the coarse USB device lock with a dedicated offload_lock
spinlock to reduce contention during offload operations. Use
offload_pm_locked to synchronize with PM transitions and replace
the legacy offload_at_suspend flag.
Optimize usb_offload_get/put by switching from auto-resume/suspend
to pm_runtime_get_if_active(). This ensures offload state is only
modified when the device is already active, avoiding unnecessary
power transitions.
Cc: stable <stable@kernel.org>
Fixes: ef82a4803a ("xhci: sideband: add api to trace sideband usage")
Signed-off-by: Guan-Yu Lin <guanyulin@google.com>
Tested-by: Hailong Liu <hailong.liu@oppo.com>
Acked-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://patch.msgid.link/20260401123238.3790062-2-guanyulin@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
153 lines
3.6 KiB
C
153 lines
3.6 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 = 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);
|