xref: /linux/drivers/usb/core/offload.c (revision 35c2c39832e569449b9192fa1afbbc4c66227af7)
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 = 0;
29 
30 	if (!usb_get_dev(udev))
31 		return -ENODEV;
32 
33 	if (pm_runtime_get_if_active(&udev->dev) != 1) {
34 		ret = -EBUSY;
35 		goto err_rpm;
36 	}
37 
38 	spin_lock(&udev->offload_lock);
39 
40 	if (udev->offload_pm_locked) {
41 		ret = -EAGAIN;
42 		goto err;
43 	}
44 
45 	udev->offload_usage++;
46 
47 err:
48 	spin_unlock(&udev->offload_lock);
49 	pm_runtime_put_autosuspend(&udev->dev);
50 err_rpm:
51 	usb_put_dev(udev);
52 
53 	return ret;
54 }
55 EXPORT_SYMBOL_GPL(usb_offload_get);
56 
57 /**
58  * usb_offload_put - drop the offload_usage of a USB device
59  * @udev: the USB device to drop its offload_usage
60  *
61  * The inverse operation of usb_offload_get, which drops the offload_usage of
62  * a USB device. This information allows the USB driver to adjust its power
63  * management policy based on offload activity.
64  *
65  * Return: 0 on success. A negative error code otherwise.
66  */
67 int usb_offload_put(struct usb_device *udev)
68 {
69 	int ret = 0;
70 
71 	if (!usb_get_dev(udev))
72 		return -ENODEV;
73 
74 	if (pm_runtime_get_if_active(&udev->dev) != 1) {
75 		ret = -EBUSY;
76 		goto err_rpm;
77 	}
78 
79 	spin_lock(&udev->offload_lock);
80 
81 	if (udev->offload_pm_locked) {
82 		ret = -EAGAIN;
83 		goto err;
84 	}
85 
86 	/* Drop the count when it wasn't 0, ignore the operation otherwise. */
87 	if (udev->offload_usage)
88 		udev->offload_usage--;
89 
90 err:
91 	spin_unlock(&udev->offload_lock);
92 	pm_runtime_put_autosuspend(&udev->dev);
93 err_rpm:
94 	usb_put_dev(udev);
95 
96 	return ret;
97 }
98 EXPORT_SYMBOL_GPL(usb_offload_put);
99 
100 /**
101  * usb_offload_check - check offload activities on a USB device
102  * @udev: the USB device to check its offload activity.
103  *
104  * Check if there are any offload activity on the USB device right now. This
105  * information could be used for power management or other forms of resource
106  * management.
107  *
108  * The caller must hold @udev's device lock. In addition, the caller should
109  * ensure the device itself and the downstream usb devices are all marked as
110  * "offload_pm_locked" to ensure the correctness of the return value.
111  *
112  * Returns true on any offload activity, false otherwise.
113  */
114 bool usb_offload_check(struct usb_device *udev) __must_hold(&udev->dev->mutex)
115 {
116 	struct usb_device *child;
117 	bool active = false;
118 	int port1;
119 
120 	if (udev->offload_usage)
121 		return true;
122 
123 	usb_hub_for_each_child(udev, port1, child) {
124 		usb_lock_device(child);
125 		active = usb_offload_check(child);
126 		usb_unlock_device(child);
127 
128 		if (active)
129 			break;
130 	}
131 
132 	return active;
133 }
134 EXPORT_SYMBOL_GPL(usb_offload_check);
135 
136 /**
137  * usb_offload_set_pm_locked - set the PM lock state of a USB device
138  * @udev: the USB device to modify
139  * @locked: the new lock state
140  *
141  * Setting @locked to true prevents offload_usage from being modified. This
142  * ensures that offload activities cannot be started or stopped during critical
143  * power management transitions, maintaining a stable state for the duration
144  * of the transition.
145  */
146 void usb_offload_set_pm_locked(struct usb_device *udev, bool locked)
147 {
148 	spin_lock(&udev->offload_lock);
149 	udev->offload_pm_locked = locked;
150 	spin_unlock(&udev->offload_lock);
151 }
152 EXPORT_SYMBOL_GPL(usb_offload_set_pm_locked);
153