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

Merge patch series "Support system sleep with offloaded usb transfers" into usb-next

Guan-Yu Lin <guanyulin@google.com> says:

Wesley Cheng and Mathias Nyman's USB offload design enables a
co-processor to handle some USB transfers, potentially allowing the
system to sleep (suspend-to-RAM) and save power. However, Linux's System
Sleep model halts the USB host controller when the main system isn't
managing any USB transfers. To address this, the proposal modifies the
system to recognize offloaded USB transfers and manage power
accordingly. This way, offloaded USB transfers could still happen during
system sleep (Suspend-to-RAM).

This involves two key steps:
1. Transfer Status Tracking: Propose offload_usage and corresponding
apis drivers could track USB transfers on the co-processor, ensuring
the system is aware of any ongoing activity.
2. Power Management Adjustment: Modifications to the USB driver stack
(xhci host controller driver, and USB device drivers) allow the
system to sleep (Suspend-to-RAM) without disrupting co-processor
managed USB transfers. This involves adding conditional checks to
bypass some power management operations in the System Sleep model.

Link: https://lore.kernel.org/r/20250911142051.90822-1-guanyulin@google.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

+284 -10
+1
drivers/usb/core/Makefile
··· 9 9 usbcore-y += phy.o port.o 10 10 11 11 usbcore-$(CONFIG_OF) += of.o 12 + usbcore-$(CONFIG_USB_XHCI_SIDEBAND) += offload.o 12 13 usbcore-$(CONFIG_USB_PCI) += hcd-pci.o 13 14 usbcore-$(CONFIG_ACPI) += usb-acpi.o 14 15
+44 -6
drivers/usb/core/driver.c
··· 1420 1420 udev->state == USB_STATE_SUSPENDED) 1421 1421 goto done; 1422 1422 1423 + if (msg.event == PM_EVENT_SUSPEND && usb_offload_check(udev)) { 1424 + dev_dbg(&udev->dev, "device offloaded, skip suspend.\n"); 1425 + udev->offload_at_suspend = 1; 1426 + } 1427 + 1423 1428 /* Suspend all the interfaces and then udev itself */ 1424 1429 if (udev->actconfig) { 1425 1430 n = udev->actconfig->desc.bNumInterfaces; 1426 1431 for (i = n - 1; i >= 0; --i) { 1427 1432 intf = udev->actconfig->interface[i]; 1433 + /* 1434 + * Don't suspend interfaces with remote wakeup while 1435 + * the controller is active. This preserves pending 1436 + * interrupt urbs, allowing interrupt events to be 1437 + * handled during system suspend. 1438 + */ 1439 + if (udev->offload_at_suspend && 1440 + intf->needs_remote_wakeup) { 1441 + dev_dbg(&intf->dev, 1442 + "device offloaded, skip suspend.\n"); 1443 + continue; 1444 + } 1428 1445 status = usb_suspend_interface(udev, intf, msg); 1429 1446 1430 1447 /* Ignore errors during system sleep transitions */ ··· 1452 1435 } 1453 1436 } 1454 1437 if (status == 0) { 1455 - status = usb_suspend_device(udev, msg); 1438 + if (!udev->offload_at_suspend) 1439 + status = usb_suspend_device(udev, msg); 1456 1440 1457 1441 /* 1458 1442 * Ignore errors from non-root-hub devices during ··· 1498 1480 */ 1499 1481 } else { 1500 1482 udev->can_submit = 0; 1501 - for (i = 0; i < 16; ++i) { 1502 - usb_hcd_flush_endpoint(udev, udev->ep_out[i]); 1503 - usb_hcd_flush_endpoint(udev, udev->ep_in[i]); 1483 + if (!udev->offload_at_suspend) { 1484 + for (i = 0; i < 16; ++i) { 1485 + usb_hcd_flush_endpoint(udev, udev->ep_out[i]); 1486 + usb_hcd_flush_endpoint(udev, udev->ep_in[i]); 1487 + } 1504 1488 } 1505 1489 } 1506 1490 ··· 1544 1524 udev->can_submit = 1; 1545 1525 1546 1526 /* Resume the device */ 1547 - if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) 1548 - status = usb_resume_device(udev, msg); 1527 + if (udev->state == USB_STATE_SUSPENDED || udev->reset_resume) { 1528 + if (!udev->offload_at_suspend) 1529 + status = usb_resume_device(udev, msg); 1530 + else 1531 + dev_dbg(&udev->dev, 1532 + "device offloaded, skip resume.\n"); 1533 + } 1549 1534 1550 1535 /* Resume the interfaces */ 1551 1536 if (status == 0 && udev->actconfig) { 1552 1537 for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { 1553 1538 intf = udev->actconfig->interface[i]; 1539 + /* 1540 + * Interfaces with remote wakeup aren't suspended 1541 + * while the controller is active. This preserves 1542 + * pending interrupt urbs, allowing interrupt events 1543 + * to be handled during system suspend. 1544 + */ 1545 + if (udev->offload_at_suspend && 1546 + intf->needs_remote_wakeup) { 1547 + dev_dbg(&intf->dev, 1548 + "device offloaded, skip resume.\n"); 1549 + continue; 1550 + } 1554 1551 usb_resume_interface(udev, intf, msg, 1555 1552 udev->reset_resume); 1556 1553 } 1557 1554 } 1555 + udev->offload_at_suspend = 0; 1558 1556 usb_mark_last_busy(udev); 1559 1557 1560 1558 done:
+136
drivers/usb/core/offload.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + /* 4 + * offload.c - USB offload related functions 5 + * 6 + * Copyright (c) 2025, Google LLC. 7 + * 8 + * Author: Guan-Yu Lin 9 + */ 10 + 11 + #include <linux/usb.h> 12 + 13 + #include "usb.h" 14 + 15 + /** 16 + * usb_offload_get - increment the offload_usage of a USB device 17 + * @udev: the USB device to increment its offload_usage 18 + * 19 + * Incrementing the offload_usage of a usb_device indicates that offload is 20 + * enabled on this usb_device; that is, another entity is actively handling USB 21 + * transfers. This information allows the USB driver to adjust its power 22 + * management policy based on offload activity. 23 + * 24 + * Return: 0 on success. A negative error code otherwise. 25 + */ 26 + int usb_offload_get(struct usb_device *udev) 27 + { 28 + int ret; 29 + 30 + usb_lock_device(udev); 31 + if (udev->state == USB_STATE_NOTATTACHED) { 32 + usb_unlock_device(udev); 33 + return -ENODEV; 34 + } 35 + 36 + if (udev->state == USB_STATE_SUSPENDED || 37 + udev->offload_at_suspend) { 38 + usb_unlock_device(udev); 39 + return -EBUSY; 40 + } 41 + 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; 50 + } 51 + 52 + udev->offload_usage++; 53 + usb_autosuspend_device(udev); 54 + usb_unlock_device(udev); 55 + 56 + return ret; 57 + } 58 + EXPORT_SYMBOL_GPL(usb_offload_get); 59 + 60 + /** 61 + * usb_offload_put - drop the offload_usage of a USB device 62 + * @udev: the USB device to drop its offload_usage 63 + * 64 + * The inverse operation of usb_offload_get, which drops the offload_usage of 65 + * a USB device. This information allows the USB driver to adjust its power 66 + * management policy based on offload activity. 67 + * 68 + * Return: 0 on success. A negative error code otherwise. 69 + */ 70 + int usb_offload_put(struct usb_device *udev) 71 + { 72 + int ret; 73 + 74 + usb_lock_device(udev); 75 + if (udev->state == USB_STATE_NOTATTACHED) { 76 + usb_unlock_device(udev); 77 + return -ENODEV; 78 + } 79 + 80 + if (udev->state == USB_STATE_SUSPENDED || 81 + udev->offload_at_suspend) { 82 + usb_unlock_device(udev); 83 + return -EBUSY; 84 + } 85 + 86 + /* 87 + * offload_usage could only be modified when the device is active, since 88 + * it will alter the suspend flow of the device. 89 + */ 90 + ret = usb_autoresume_device(udev); 91 + if (ret < 0) { 92 + usb_unlock_device(udev); 93 + return ret; 94 + } 95 + 96 + /* Drop the count when it wasn't 0, ignore the operation otherwise. */ 97 + if (udev->offload_usage) 98 + udev->offload_usage--; 99 + usb_autosuspend_device(udev); 100 + usb_unlock_device(udev); 101 + 102 + return ret; 103 + } 104 + EXPORT_SYMBOL_GPL(usb_offload_put); 105 + 106 + /** 107 + * usb_offload_check - check offload activities on a USB device 108 + * @udev: the USB device to check its offload activity. 109 + * 110 + * Check if there are any offload activity on the USB device right now. This 111 + * information could be used for power management or other forms of resource 112 + * management. 113 + * 114 + * The caller must hold @udev's device lock. In addition, the caller should 115 + * ensure downstream usb devices are all either suspended or marked as 116 + * "offload_at_suspend" to ensure the correctness of the return value. 117 + * 118 + * Returns true on any offload activity, false otherwise. 119 + */ 120 + bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex) 121 + { 122 + struct usb_device *child; 123 + bool active; 124 + int port1; 125 + 126 + usb_hub_for_each_child(udev, port1, child) { 127 + usb_lock_device(child); 128 + active = usb_offload_check(child); 129 + usb_unlock_device(child); 130 + if (active) 131 + return true; 132 + } 133 + 134 + return !!udev->offload_usage; 135 + } 136 + EXPORT_SYMBOL_GPL(usb_offload_check);
+1
drivers/usb/core/usb.c
··· 670 670 set_dev_node(&dev->dev, dev_to_node(bus->sysdev)); 671 671 dev->state = USB_STATE_ATTACHED; 672 672 dev->lpm_disable_count = 1; 673 + dev->offload_usage = 0; 673 674 atomic_set(&dev->urbnum, 0); 674 675 675 676 INIT_LIST_HEAD(&dev->ep0.urb_list);
+38 -4
drivers/usb/host/xhci-plat.c
··· 20 20 #include <linux/acpi.h> 21 21 #include <linux/usb/of.h> 22 22 #include <linux/reset.h> 23 + #include <linux/usb/xhci-sideband.h> 23 24 24 25 #include "xhci.h" 25 26 #include "xhci-plat.h" ··· 455 454 } 456 455 EXPORT_SYMBOL_GPL(xhci_plat_remove); 457 456 458 - static int xhci_plat_suspend(struct device *dev) 457 + static int xhci_plat_suspend_common(struct device *dev) 459 458 { 460 459 struct usb_hcd *hcd = dev_get_drvdata(dev); 461 460 struct xhci_hcd *xhci = hcd_to_xhci(hcd); ··· 481 480 } 482 481 483 482 return 0; 483 + } 484 + 485 + static int xhci_plat_suspend(struct device *dev) 486 + { 487 + struct usb_hcd *hcd = dev_get_drvdata(dev); 488 + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); 489 + 490 + if (xhci_sideband_check(hcd)) { 491 + priv->sideband_at_suspend = 1; 492 + dev_dbg(dev, "sideband instance active, skip suspend.\n"); 493 + return 0; 494 + } 495 + 496 + return xhci_plat_suspend_common(dev); 497 + } 498 + 499 + static int xhci_plat_freeze(struct device *dev) 500 + { 501 + return xhci_plat_suspend_common(dev); 484 502 } 485 503 486 504 static int xhci_plat_resume_common(struct device *dev, bool power_lost) ··· 546 526 547 527 static int xhci_plat_resume(struct device *dev) 548 528 { 529 + struct usb_hcd *hcd = dev_get_drvdata(dev); 530 + struct xhci_plat_priv *priv = hcd_to_xhci_priv(hcd); 531 + 532 + if (priv->sideband_at_suspend) { 533 + priv->sideband_at_suspend = 0; 534 + dev_dbg(dev, "sideband instance active, skip resume.\n"); 535 + return 0; 536 + } 537 + 538 + return xhci_plat_resume_common(dev, false); 539 + } 540 + 541 + static int xhci_plat_thaw(struct device *dev) 542 + { 549 543 return xhci_plat_resume_common(dev, false); 550 544 } 551 545 ··· 592 558 const struct dev_pm_ops xhci_plat_pm_ops = { 593 559 .suspend = pm_sleep_ptr(xhci_plat_suspend), 594 560 .resume = pm_sleep_ptr(xhci_plat_resume), 595 - .freeze = pm_sleep_ptr(xhci_plat_suspend), 596 - .thaw = pm_sleep_ptr(xhci_plat_resume), 597 - .poweroff = pm_sleep_ptr(xhci_plat_suspend), 561 + .freeze = pm_sleep_ptr(xhci_plat_freeze), 562 + .thaw = pm_sleep_ptr(xhci_plat_thaw), 563 + .poweroff = pm_sleep_ptr(xhci_plat_freeze), 598 564 .restore = pm_sleep_ptr(xhci_plat_restore), 599 565 600 566 SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend,
+1
drivers/usb/host/xhci-plat.h
··· 16 16 const char *firmware_name; 17 17 unsigned long long quirks; 18 18 bool power_lost; 19 + unsigned sideband_at_suspend:1; 19 20 void (*plat_start)(struct usb_hcd *); 20 21 int (*init_quirk)(struct usb_hcd *); 21 22 int (*suspend_quirk)(struct usb_hcd *);
+36
drivers/usb/host/xhci-sideband.c
··· 267 267 EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); 268 268 269 269 /** 270 + * xhci_sideband_check - check the existence of active sidebands 271 + * @hcd: the host controller driver associated with the target host controller 272 + * 273 + * Allow other drivers, such as usb controller driver, to check if there are 274 + * any sideband activity on the host controller. This information could be used 275 + * for power management or other forms of resource management. The caller should 276 + * ensure downstream usb devices are all either suspended or marked as 277 + * "offload_at_suspend" to ensure the correctness of the return value. 278 + * 279 + * Returns true on any active sideband existence, false otherwise. 280 + */ 281 + bool xhci_sideband_check(struct usb_hcd *hcd) 282 + { 283 + struct usb_device *udev = hcd->self.root_hub; 284 + bool active; 285 + 286 + usb_lock_device(udev); 287 + active = usb_offload_check(udev); 288 + usb_unlock_device(udev); 289 + 290 + return active; 291 + } 292 + EXPORT_SYMBOL_GPL(xhci_sideband_check); 293 + 294 + /** 270 295 * xhci_sideband_create_interrupter - creates a new interrupter for this sideband 271 296 * @sb: sideband instance for this usb device 272 297 * @num_seg: number of event ring segments to allocate ··· 311 286 bool ip_autoclear, u32 imod_interval, int intr_num) 312 287 { 313 288 int ret = 0; 289 + struct usb_device *udev; 314 290 315 291 if (!sb || !sb->xhci) 316 292 return -ENODEV; ··· 329 303 ret = -ENOMEM; 330 304 goto out; 331 305 } 306 + 307 + udev = sb->vdev->udev; 308 + ret = usb_offload_get(udev); 332 309 333 310 sb->ir->ip_autoclear = ip_autoclear; 334 311 ··· 352 323 void 353 324 xhci_sideband_remove_interrupter(struct xhci_sideband *sb) 354 325 { 326 + struct usb_device *udev; 327 + 355 328 if (!sb || !sb->ir) 356 329 return; 357 330 ··· 361 330 xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir); 362 331 363 332 sb->ir = NULL; 333 + udev = sb->vdev->udev; 334 + 335 + if (udev->state != USB_STATE_NOTATTACHED) 336 + usb_offload_put(udev); 337 + 364 338 mutex_unlock(&sb->mutex); 365 339 } 366 340 EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter);
+18
include/linux/usb.h
··· 636 636 * @do_remote_wakeup: remote wakeup should be enabled 637 637 * @reset_resume: needs reset instead of resume 638 638 * @port_is_suspended: the upstream port is suspended (L2 or U3) 639 + * @offload_at_suspend: offload activities during suspend is enabled. 640 + * @offload_usage: number of offload activities happening on this usb device. 639 641 * @slot_id: Slot ID assigned by xHCI 640 642 * @l1_params: best effor service latency for USB2 L1 LPM state, and L1 timeout. 641 643 * @u1_params: exit latencies for USB3 U1 LPM state, and hub-initiated timeout. ··· 726 724 unsigned do_remote_wakeup:1; 727 725 unsigned reset_resume:1; 728 726 unsigned port_is_suspended:1; 727 + unsigned offload_at_suspend:1; 728 + int offload_usage; 729 729 enum usb_link_tunnel_mode tunnel_mode; 730 730 struct device_link *usb4_link; 731 731 ··· 843 839 { } 844 840 static inline void usb_mark_last_busy(struct usb_device *udev) 845 841 { } 842 + #endif 843 + 844 + #if IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) 845 + int usb_offload_get(struct usb_device *udev); 846 + int usb_offload_put(struct usb_device *udev); 847 + bool usb_offload_check(struct usb_device *udev); 848 + #else 849 + 850 + static inline int usb_offload_get(struct usb_device *udev) 851 + { return 0; } 852 + static inline int usb_offload_put(struct usb_device *udev) 853 + { return 0; } 854 + static inline bool usb_offload_check(struct usb_device *udev) 855 + { return false; } 846 856 #endif 847 857 848 858 extern int usb_disable_lpm(struct usb_device *udev);
+9
include/linux/usb/xhci-sideband.h
··· 11 11 12 12 #include <linux/scatterlist.h> 13 13 #include <linux/usb.h> 14 + #include <linux/usb/hcd.h> 14 15 15 16 #define EP_CTX_PER_DEV 31 /* FIXME defined twice, from xhci.h */ 16 17 ··· 84 83 struct usb_host_endpoint *host_ep); 85 84 struct sg_table * 86 85 xhci_sideband_get_event_buffer(struct xhci_sideband *sb); 86 + 87 + #if IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) 88 + bool xhci_sideband_check(struct usb_hcd *hcd); 89 + #else 90 + static inline bool xhci_sideband_check(struct usb_hcd *hcd) 91 + { return false; } 92 + #endif /* IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) */ 93 + 87 94 int 88 95 xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, 89 96 bool ip_autoclear, u32 imod_interval, int intr_num);