mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
usb: core: use dedicated spinlock for offload state
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>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
c32f8748d7
commit
bd3d245b0f
@@ -1415,14 +1415,16 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
|
||||
int status = 0;
|
||||
int i = 0, n = 0;
|
||||
struct usb_interface *intf;
|
||||
bool offload_active = false;
|
||||
|
||||
if (udev->state == USB_STATE_NOTATTACHED ||
|
||||
udev->state == USB_STATE_SUSPENDED)
|
||||
goto done;
|
||||
|
||||
usb_offload_set_pm_locked(udev, true);
|
||||
if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) {
|
||||
dev_dbg(&udev->dev, "device offloaded, skip suspend.\n");
|
||||
udev->offload_at_suspend = 1;
|
||||
offload_active = true;
|
||||
}
|
||||
|
||||
/* Suspend all the interfaces and then udev itself */
|
||||
@@ -1436,8 +1438,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
|
||||
* interrupt urbs, allowing interrupt events to be
|
||||
* handled during system suspend.
|
||||
*/
|
||||
if (udev->offload_at_suspend &&
|
||||
intf->needs_remote_wakeup) {
|
||||
if (offload_active && intf->needs_remote_wakeup) {
|
||||
dev_dbg(&intf->dev,
|
||||
"device offloaded, skip suspend.\n");
|
||||
continue;
|
||||
@@ -1452,7 +1453,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
|
||||
}
|
||||
}
|
||||
if (status == 0) {
|
||||
if (!udev->offload_at_suspend)
|
||||
if (!offload_active)
|
||||
status = usb_suspend_device(udev, msg);
|
||||
|
||||
/*
|
||||
@@ -1498,7 +1499,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
|
||||
*/
|
||||
} else {
|
||||
udev->can_submit = 0;
|
||||
if (!udev->offload_at_suspend) {
|
||||
if (!offload_active) {
|
||||
for (i = 0; i < 16; ++i) {
|
||||
usb_hcd_flush_endpoint(udev, udev->ep_out[i]);
|
||||
usb_hcd_flush_endpoint(udev, udev->ep_in[i]);
|
||||
@@ -1507,6 +1508,8 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
|
||||
}
|
||||
|
||||
done:
|
||||
if (status != 0)
|
||||
usb_offload_set_pm_locked(udev, false);
|
||||
dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status);
|
||||
return status;
|
||||
}
|
||||
@@ -1536,16 +1539,19 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
|
||||
int status = 0;
|
||||
int i;
|
||||
struct usb_interface *intf;
|
||||
bool offload_active = false;
|
||||
|
||||
if (udev->state == USB_STATE_NOTATTACHED) {
|
||||
status = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
udev->can_submit = 1;
|
||||
if (msg.event == PM_EVENT_RESUME)
|
||||
offload_active = usb_offload_check(udev);
|
||||
|
||||
/* Resume the device */
|
||||
if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) {
|
||||
if (!udev->offload_at_suspend)
|
||||
if (!offload_active)
|
||||
status = usb_resume_device(udev, msg);
|
||||
else
|
||||
dev_dbg(&udev->dev,
|
||||
@@ -1562,8 +1568,7 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
|
||||
* pending interrupt urbs, allowing interrupt events
|
||||
* to be handled during system suspend.
|
||||
*/
|
||||
if (udev->offload_at_suspend &&
|
||||
intf->needs_remote_wakeup) {
|
||||
if (offload_active && intf->needs_remote_wakeup) {
|
||||
dev_dbg(&intf->dev,
|
||||
"device offloaded, skip resume.\n");
|
||||
continue;
|
||||
@@ -1572,11 +1577,11 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
|
||||
udev->reset_resume);
|
||||
}
|
||||
}
|
||||
udev->offload_at_suspend = 0;
|
||||
usb_mark_last_busy(udev);
|
||||
|
||||
done:
|
||||
dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status);
|
||||
usb_offload_set_pm_locked(udev, false);
|
||||
if (!status)
|
||||
udev->reset_resume = 0;
|
||||
return status;
|
||||
|
||||
@@ -25,33 +25,30 @@
|
||||
*/
|
||||
int usb_offload_get(struct usb_device *udev)
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
usb_lock_device(udev);
|
||||
if (udev->state == USB_STATE_NOTATTACHED) {
|
||||
usb_unlock_device(udev);
|
||||
if (!usb_get_dev(udev))
|
||||
return -ENODEV;
|
||||
|
||||
if (pm_runtime_get_if_active(&udev->dev) != 1) {
|
||||
ret = -EBUSY;
|
||||
goto err_rpm;
|
||||
}
|
||||
|
||||
if (udev->state == USB_STATE_SUSPENDED ||
|
||||
udev->offload_at_suspend) {
|
||||
usb_unlock_device(udev);
|
||||
return -EBUSY;
|
||||
}
|
||||
spin_lock(&udev->offload_lock);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
if (udev->offload_pm_locked) {
|
||||
ret = -EAGAIN;
|
||||
goto err;
|
||||
}
|
||||
|
||||
udev->offload_usage++;
|
||||
usb_autosuspend_device(udev);
|
||||
usb_unlock_device(udev);
|
||||
|
||||
err:
|
||||
spin_unlock(&udev->offload_lock);
|
||||
pm_runtime_put_autosuspend(&udev->dev);
|
||||
err_rpm:
|
||||
usb_put_dev(udev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -69,35 +66,32 @@ EXPORT_SYMBOL_GPL(usb_offload_get);
|
||||
*/
|
||||
int usb_offload_put(struct usb_device *udev)
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
usb_lock_device(udev);
|
||||
if (udev->state == USB_STATE_NOTATTACHED) {
|
||||
usb_unlock_device(udev);
|
||||
if (!usb_get_dev(udev))
|
||||
return -ENODEV;
|
||||
|
||||
if (pm_runtime_get_if_active(&udev->dev) != 1) {
|
||||
ret = -EBUSY;
|
||||
goto err_rpm;
|
||||
}
|
||||
|
||||
if (udev->state == USB_STATE_SUSPENDED ||
|
||||
udev->offload_at_suspend) {
|
||||
usb_unlock_device(udev);
|
||||
return -EBUSY;
|
||||
}
|
||||
spin_lock(&udev->offload_lock);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
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--;
|
||||
usb_autosuspend_device(udev);
|
||||
usb_unlock_device(udev);
|
||||
|
||||
err:
|
||||
spin_unlock(&udev->offload_lock);
|
||||
pm_runtime_put_autosuspend(&udev->dev);
|
||||
err_rpm:
|
||||
usb_put_dev(udev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -112,25 +106,47 @@ EXPORT_SYMBOL_GPL(usb_offload_put);
|
||||
* 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.
|
||||
* 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;
|
||||
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)
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
|
||||
return !!udev->offload_usage;
|
||||
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);
|
||||
|
||||
@@ -671,6 +671,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent,
|
||||
set_dev_node(&dev->dev, dev_to_node(bus->sysdev));
|
||||
dev->state = USB_STATE_ATTACHED;
|
||||
dev->lpm_disable_count = 1;
|
||||
spin_lock_init(&dev->offload_lock);
|
||||
dev->offload_usage = 0;
|
||||
atomic_set(&dev->urbnum, 0);
|
||||
|
||||
|
||||
@@ -291,8 +291,8 @@ EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer);
|
||||
* Allow other drivers, such as usb controller driver, to check if there are
|
||||
* any sideband activity on the host controller. This information could be used
|
||||
* for power management or other forms of resource management. 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.
|
||||
* ensure downstream usb devices are all marked as "offload_pm_locked" to ensure
|
||||
* the correctness of the return value.
|
||||
*
|
||||
* Returns true on any active sideband existence, false otherwise.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user