xref: /linux/drivers/usb/core/offload.c (revision 6093a688a07da07808f0122f9aa2a3eed250d853)
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);
137