Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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 */
26int 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
47err:
48 spin_unlock(&udev->offload_lock);
49 pm_runtime_put_autosuspend(&udev->dev);
50err_rpm:
51 usb_put_dev(udev);
52
53 return ret;
54}
55EXPORT_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 */
67int 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
90err:
91 spin_unlock(&udev->offload_lock);
92 pm_runtime_put_autosuspend(&udev->dev);
93err_rpm:
94 usb_put_dev(udev);
95
96 return ret;
97}
98EXPORT_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 */
114bool 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}
134EXPORT_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 */
146void 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}
152EXPORT_SYMBOL_GPL(usb_offload_set_pm_locked);