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 */
usb_offload_get(struct usb_device * udev)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 */
usb_offload_put(struct usb_device * udev)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 */
usb_offload_check(struct usb_device * udev)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);
137