Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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: ef82a4803aab ("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>

authored by

Guan-Yu Lin and committed by
Greg Kroah-Hartman
bd3d245b c32f8748

+84 -56
+14 -9
drivers/usb/core/driver.c
··· 1415 1415 int status = 0; 1416 1416 int i = 0, n = 0; 1417 1417 struct usb_interface *intf; 1418 + bool offload_active = false; 1418 1419 1419 1420 if (udev->state == USB_STATE_NOTATTACHED || 1420 1421 udev->state == USB_STATE_SUSPENDED) 1421 1422 goto done; 1422 1423 1424 + usb_offload_set_pm_locked(udev, true); 1423 1425 if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) { 1424 1426 dev_dbg(&udev->dev, "device offloaded, skip suspend.\n"); 1425 - udev->offload_at_suspend = 1; 1427 + offload_active = true; 1426 1428 } 1427 1429 1428 1430 /* Suspend all the interfaces and then udev itself */ ··· 1438 1436 * interrupt urbs, allowing interrupt events to be 1439 1437 * handled during system suspend. 1440 1438 */ 1441 - if (udev->offload_at_suspend && 1442 - intf->needs_remote_wakeup) { 1439 + if (offload_active && intf->needs_remote_wakeup) { 1443 1440 dev_dbg(&intf->dev, 1444 1441 "device offloaded, skip suspend.\n"); 1445 1442 continue; ··· 1453 1452 } 1454 1453 } 1455 1454 if (status == 0) { 1456 - if (!udev->offload_at_suspend) 1455 + if (!offload_active) 1457 1456 status = usb_suspend_device(udev, msg); 1458 1457 1459 1458 /* ··· 1499 1498 */ 1500 1499 } else { 1501 1500 udev->can_submit = 0; 1502 - if (!udev->offload_at_suspend) { 1501 + if (!offload_active) { 1503 1502 for (i = 0; i < 16; ++i) { 1504 1503 usb_hcd_flush_endpoint(udev, udev->ep_out[i]); 1505 1504 usb_hcd_flush_endpoint(udev, udev->ep_in[i]); ··· 1508 1507 } 1509 1508 1510 1509 done: 1510 + if (status != 0) 1511 + usb_offload_set_pm_locked(udev, false); 1511 1512 dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status); 1512 1513 return status; 1513 1514 } ··· 1539 1536 int status = 0; 1540 1537 int i; 1541 1538 struct usb_interface *intf; 1539 + bool offload_active = false; 1542 1540 1543 1541 if (udev->state == USB_STATE_NOTATTACHED) { 1544 1542 status = -ENODEV; 1545 1543 goto done; 1546 1544 } 1547 1545 udev->can_submit = 1; 1546 + if (msg.event == PM_EVENT_RESUME) 1547 + offload_active = usb_offload_check(udev); 1548 1548 1549 1549 /* Resume the device */ 1550 1550 if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) { 1551 - if (!udev->offload_at_suspend) 1551 + if (!offload_active) 1552 1552 status = usb_resume_device(udev, msg); 1553 1553 else 1554 1554 dev_dbg(&udev->dev, ··· 1568 1562 * pending interrupt urbs, allowing interrupt events 1569 1563 * to be handled during system suspend. 1570 1564 */ 1571 - if (udev->offload_at_suspend && 1572 - intf->needs_remote_wakeup) { 1565 + if (offload_active && intf->needs_remote_wakeup) { 1573 1566 dev_dbg(&intf->dev, 1574 1567 "device offloaded, skip resume.\n"); 1575 1568 continue; ··· 1577 1572 udev->reset_resume); 1578 1573 } 1579 1574 } 1580 - udev->offload_at_suspend = 0; 1581 1575 usb_mark_last_busy(udev); 1582 1576 1583 1577 done: 1584 1578 dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status); 1579 + usb_offload_set_pm_locked(udev, false); 1585 1580 if (!status) 1586 1581 udev->reset_resume = 0; 1587 1582 return status;
+59 -43
drivers/usb/core/offload.c
··· 25 25 */ 26 26 int usb_offload_get(struct usb_device *udev) 27 27 { 28 - int ret; 28 + int ret = 0; 29 29 30 - usb_lock_device(udev); 31 - if (udev->state == USB_STATE_NOTATTACHED) { 32 - usb_unlock_device(udev); 30 + if (!usb_get_dev(udev)) 33 31 return -ENODEV; 32 + 33 + if (pm_runtime_get_if_active(&udev->dev) != 1) { 34 + ret = -EBUSY; 35 + goto err_rpm; 34 36 } 35 37 36 - if (udev->state == USB_STATE_SUSPENDED || 37 - udev->offload_at_suspend) { 38 - usb_unlock_device(udev); 39 - return -EBUSY; 40 - } 38 + spin_lock(&udev->offload_lock); 41 39 42 - /* 43 - * offload_usage could only be modified when the device is active, since 44 - * it will alter the suspend flow of the device. 45 - */ 46 - ret = usb_autoresume_device(udev); 47 - if (ret < 0) { 48 - usb_unlock_device(udev); 49 - return ret; 40 + if (udev->offload_pm_locked) { 41 + ret = -EAGAIN; 42 + goto err; 50 43 } 51 44 52 45 udev->offload_usage++; 53 - usb_autosuspend_device(udev); 54 - usb_unlock_device(udev); 46 + 47 + err: 48 + spin_unlock(&udev->offload_lock); 49 + pm_runtime_put_autosuspend(&udev->dev); 50 + err_rpm: 51 + usb_put_dev(udev); 55 52 56 53 return ret; 57 54 } ··· 66 69 */ 67 70 int usb_offload_put(struct usb_device *udev) 68 71 { 69 - int ret; 72 + int ret = 0; 70 73 71 - usb_lock_device(udev); 72 - if (udev->state == USB_STATE_NOTATTACHED) { 73 - usb_unlock_device(udev); 74 + if (!usb_get_dev(udev)) 74 75 return -ENODEV; 76 + 77 + if (pm_runtime_get_if_active(&udev->dev) != 1) { 78 + ret = -EBUSY; 79 + goto err_rpm; 75 80 } 76 81 77 - if (udev->state == USB_STATE_SUSPENDED || 78 - udev->offload_at_suspend) { 79 - usb_unlock_device(udev); 80 - return -EBUSY; 81 - } 82 + spin_lock(&udev->offload_lock); 82 83 83 - /* 84 - * offload_usage could only be modified when the device is active, since 85 - * it will alter the suspend flow of the device. 86 - */ 87 - ret = usb_autoresume_device(udev); 88 - if (ret < 0) { 89 - usb_unlock_device(udev); 90 - return ret; 84 + if (udev->offload_pm_locked) { 85 + ret = -EAGAIN; 86 + goto err; 91 87 } 92 88 93 89 /* Drop the count when it wasn't 0, ignore the operation otherwise. */ 94 90 if (udev->offload_usage) 95 91 udev->offload_usage--; 96 - usb_autosuspend_device(udev); 97 - usb_unlock_device(udev); 92 + 93 + err: 94 + spin_unlock(&udev->offload_lock); 95 + pm_runtime_put_autosuspend(&udev->dev); 96 + err_rpm: 97 + usb_put_dev(udev); 98 98 99 99 return ret; 100 100 } ··· 106 112 * management. 107 113 * 108 114 * The caller must hold @udev's device lock. In addition, the caller should 109 - * ensure downstream usb devices are all either suspended or marked as 110 - * "offload_at_suspend" to ensure the correctness of the return value. 115 + * ensure the device itself and the downstream usb devices are all marked as 116 + * "offload_pm_locked" to ensure the correctness of the return value. 111 117 * 112 118 * Returns true on any offload activity, false otherwise. 113 119 */ 114 120 bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex) 115 121 { 116 122 struct usb_device *child; 117 - bool active; 123 + bool active = false; 118 124 int port1; 125 + 126 + if (udev->offload_usage) 127 + return true; 119 128 120 129 usb_hub_for_each_child(udev, port1, child) { 121 130 usb_lock_device(child); 122 131 active = usb_offload_check(child); 123 132 usb_unlock_device(child); 133 + 124 134 if (active) 125 - return true; 135 + break; 126 136 } 127 137 128 - return !!udev->offload_usage; 138 + return active; 129 139 } 130 140 EXPORT_SYMBOL_GPL(usb_offload_check); 141 + 142 + /** 143 + * usb_offload_set_pm_locked - set the PM lock state of a USB device 144 + * @udev: the USB device to modify 145 + * @locked: the new lock state 146 + * 147 + * Setting @locked to true prevents offload_usage from being modified. This 148 + * ensures that offload activities cannot be started or stopped during critical 149 + * power management transitions, maintaining a stable state for the duration 150 + * of the transition. 151 + */ 152 + void usb_offload_set_pm_locked(struct usb_device *udev, bool locked) 153 + { 154 + spin_lock(&udev->offload_lock); 155 + udev->offload_pm_locked = locked; 156 + spin_unlock(&udev->offload_lock); 157 + } 158 + EXPORT_SYMBOL_GPL(usb_offload_set_pm_locked);
+1
drivers/usb/core/usb.c
··· 671 671 set_dev_node(&dev->dev, dev_to_node(bus->sysdev)); 672 672 dev->state = USB_STATE_ATTACHED; 673 673 dev->lpm_disable_count = 1; 674 + spin_lock_init(&dev->offload_lock); 674 675 dev->offload_usage = 0; 675 676 atomic_set(&dev->urbnum, 0); 676 677
+2 -2
drivers/usb/host/xhci-sideband.c
··· 291 291 * Allow other drivers, such as usb controller driver, to check if there are 292 292 * any sideband activity on the host controller. This information could be used 293 293 * for power management or other forms of resource management. The caller should 294 - * ensure downstream usb devices are all either suspended or marked as 295 - * "offload_at_suspend" to ensure the correctness of the return value. 294 + * ensure downstream usb devices are all marked as "offload_pm_locked" to ensure 295 + * the correctness of the return value. 296 296 * 297 297 * Returns true on any active sideband existence, false otherwise. 298 298 */
+8 -2
include/linux/usb.h
··· 21 21 #include <linux/completion.h> /* for struct completion */ 22 22 #include <linux/sched.h> /* for current && schedule_timeout */ 23 23 #include <linux/mutex.h> /* for struct mutex */ 24 + #include <linux/spinlock.h> /* for spinlock_t */ 24 25 #include <linux/pm_runtime.h> /* for runtime PM */ 25 26 26 27 struct usb_device; ··· 637 636 * @do_remote_wakeup: remote wakeup should be enabled 638 637 * @reset_resume: needs reset instead of resume 639 638 * @port_is_suspended: the upstream port is suspended (L2 or U3) 640 - * @offload_at_suspend: offload activities during suspend is enabled. 639 + * @offload_pm_locked: prevents offload_usage changes during PM transitions. 641 640 * @offload_usage: number of offload activities happening on this usb device. 641 + * @offload_lock: protects offload_usage and offload_pm_locked 642 642 * @slot_id: Slot ID assigned by xHCI 643 643 * @l1_params: best effor service latency for USB2 L1 LPM state, and L1 timeout. 644 644 * @u1_params: exit latencies for USB3 U1 LPM state, and hub-initiated timeout. ··· 728 726 unsigned do_remote_wakeup:1; 729 727 unsigned reset_resume:1; 730 728 unsigned port_is_suspended:1; 731 - unsigned offload_at_suspend:1; 729 + unsigned offload_pm_locked:1; 732 730 int offload_usage; 731 + spinlock_t offload_lock; 733 732 enum usb_link_tunnel_mode tunnel_mode; 734 733 struct device_link *usb4_link; 735 734 ··· 852 849 int usb_offload_get(struct usb_device *udev); 853 850 int usb_offload_put(struct usb_device *udev); 854 851 bool usb_offload_check(struct usb_device *udev); 852 + void usb_offload_set_pm_locked(struct usb_device *udev, bool locked); 855 853 #else 856 854 857 855 static inline int usb_offload_get(struct usb_device *udev) ··· 861 857 { return 0; } 862 858 static inline bool usb_offload_check(struct usb_device *udev) 863 859 { return false; } 860 + static inline void usb_offload_set_pm_locked(struct usb_device *udev, bool locked) 861 + { } 864 862 #endif 865 863 866 864 extern int usb_disable_lpm(struct usb_device *udev);