Update usb_offload_get() and usb_offload_put() to require that the
caller holds the USB device lock. Remove the internal call to
usb_lock_device() and add device_lock_assert() to ensure synchronization
is handled by the caller. These functions continue to manage the
device's power state via autoresume/autosuspend and update the
offload_usage counter.
Additionally, decouple the xHCI sideband interrupter lifecycle from the
offload usage counter by removing the calls to usb_offload_get() and
usb_offload_put() from the interrupter creation and removal paths. This
allows interrupters to be managed independently of the device's offload
activity status.
Cc: stable@vger.kernel.org
Fixes: ef82a4803aab ("xhci: sideband: add api to trace sideband usage")
Signed-off-by: Guan-Yu Lin <guanyulin@google.com>
Tested-by: Hailong Liu <hailong.liu@oppo.com>
---
drivers/usb/core/offload.c | 34 +++++++++++---------------------
drivers/usb/host/xhci-sideband.c | 14 +------------
2 files changed, 13 insertions(+), 35 deletions(-)
diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c
index 7c699f1b8d2b..e13a4c21d61b 100644
--- a/drivers/usb/core/offload.c
+++ b/drivers/usb/core/offload.c
@@ -20,6 +20,7 @@
* enabled on this usb_device; that is, another entity is actively handling USB
* transfers. This information allows the USB driver to adjust its power
* management policy based on offload activity.
+ * The caller must hold @udev's device lock.
*
* Return: 0 on success. A negative error code otherwise.
*/
@@ -27,31 +28,25 @@ int usb_offload_get(struct usb_device *udev)
{
int ret;
- usb_lock_device(udev);
- if (udev->state == USB_STATE_NOTATTACHED) {
- usb_unlock_device(udev);
+ device_lock_assert(&udev->dev);
+
+ if (udev->state == USB_STATE_NOTATTACHED)
return -ENODEV;
- }
if (udev->state == USB_STATE_SUSPENDED ||
- udev->offload_at_suspend) {
- usb_unlock_device(udev);
+ udev->offload_at_suspend)
return -EBUSY;
- }
/*
* offload_usage could only be modified when the device is active, since
* it will alter the suspend flow of the device.
*/
ret = usb_autoresume_device(udev);
- if (ret < 0) {
- usb_unlock_device(udev);
+ if (ret < 0)
return ret;
- }
udev->offload_usage++;
usb_autosuspend_device(udev);
- usb_unlock_device(udev);
return ret;
}
@@ -64,6 +59,7 @@ EXPORT_SYMBOL_GPL(usb_offload_get);
* The inverse operation of usb_offload_get, which drops the offload_usage of
* a USB device. This information allows the USB driver to adjust its power
* management policy based on offload activity.
+ * The caller must hold @udev's device lock.
*
* Return: 0 on success. A negative error code otherwise.
*/
@@ -71,33 +67,27 @@ int usb_offload_put(struct usb_device *udev)
{
int ret;
- usb_lock_device(udev);
- if (udev->state == USB_STATE_NOTATTACHED) {
- usb_unlock_device(udev);
+ device_lock_assert(&udev->dev);
+
+ if (udev->state == USB_STATE_NOTATTACHED)
return -ENODEV;
- }
if (udev->state == USB_STATE_SUSPENDED ||
- udev->offload_at_suspend) {
- usb_unlock_device(udev);
+ udev->offload_at_suspend)
return -EBUSY;
- }
/*
* offload_usage could only be modified when the device is active, since
* it will alter the suspend flow of the device.
*/
ret = usb_autoresume_device(udev);
- if (ret < 0) {
- usb_unlock_device(udev);
+ if (ret < 0)
return ret;
- }
/* Drop the count when it wasn't 0, ignore the operation otherwise. */
if (udev->offload_usage)
udev->offload_usage--;
usb_autosuspend_device(udev);
- usb_unlock_device(udev);
return ret;
}
diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c
index 2bd77255032b..6fc0ad658d66 100644
--- a/drivers/usb/host/xhci-sideband.c
+++ b/drivers/usb/host/xhci-sideband.c
@@ -93,8 +93,6 @@ __xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *e
static void
__xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
{
- struct usb_device *udev;
-
lockdep_assert_held(&sb->mutex);
if (!sb->ir)
@@ -102,10 +100,6 @@ __xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
sb->ir = NULL;
- udev = sb->vdev->udev;
-
- if (udev->state != USB_STATE_NOTATTACHED)
- usb_offload_put(udev);
}
/* sideband api functions */
@@ -328,9 +322,6 @@ int
xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
bool ip_autoclear, u32 imod_interval, int intr_num)
{
- int ret = 0;
- struct usb_device *udev;
-
if (!sb || !sb->xhci)
return -ENODEV;
@@ -348,12 +339,9 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
if (!sb->ir)
return -ENOMEM;
- udev = sb->vdev->udev;
- ret = usb_offload_get(udev);
-
sb->ir->ip_autoclear = ip_autoclear;
- return ret;
+ return 0;
}
EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);
--
2.53.0.473.g4a7958ca14-goog
On 3/8/2026 7:22 PM, Guan-Yu Lin wrote:
> Update usb_offload_get() and usb_offload_put() to require that the
> caller holds the USB device lock. Remove the internal call to
> usb_lock_device() and add device_lock_assert() to ensure synchronization
> is handled by the caller. These functions continue to manage the
> device's power state via autoresume/autosuspend and update the
> offload_usage counter.
>
> Additionally, decouple the xHCI sideband interrupter lifecycle from the
> offload usage counter by removing the calls to usb_offload_get() and
> usb_offload_put() from the interrupter creation and removal paths. This
> allows interrupters to be managed independently of the device's offload
> activity status.
>
> Cc: stable@vger.kernel.org
> Fixes: ef82a4803aab ("xhci: sideband: add api to trace sideband usage")
> Signed-off-by: Guan-Yu Lin <guanyulin@google.com>
> Tested-by: Hailong Liu <hailong.liu@oppo.com>
> ---
> drivers/usb/core/offload.c | 34 +++++++++++---------------------
> drivers/usb/host/xhci-sideband.c | 14 +------------
> 2 files changed, 13 insertions(+), 35 deletions(-)
>
> diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c
> index 7c699f1b8d2b..e13a4c21d61b 100644
> --- a/drivers/usb/core/offload.c
> +++ b/drivers/usb/core/offload.c
> @@ -20,6 +20,7 @@
> * enabled on this usb_device; that is, another entity is actively handling USB
> * transfers. This information allows the USB driver to adjust its power
> * management policy based on offload activity.
> + * The caller must hold @udev's device lock.
> *
> * Return: 0 on success. A negative error code otherwise.
> */
> @@ -27,31 +28,25 @@ int usb_offload_get(struct usb_device *udev)
> {
> int ret;
>
> - usb_lock_device(udev);
> - if (udev->state == USB_STATE_NOTATTACHED) {
> - usb_unlock_device(udev);
> + device_lock_assert(&udev->dev);
> +
> + if (udev->state == USB_STATE_NOTATTACHED)
> return -ENODEV;
> - }
>
> if (udev->state == USB_STATE_SUSPENDED ||
> - udev->offload_at_suspend) {
> - usb_unlock_device(udev);
> + udev->offload_at_suspend)
> return -EBUSY;
> - }
>
Do we really need to be explicitly checking for the usb device state before
we touch the offload_usage count? In the end, its a reference count that
determines how many consumers are active for a specific interrupter, so my
question revolves around if we need to have such strict checks.
> /*
> * offload_usage could only be modified when the device is active, since
> * it will alter the suspend flow of the device.
> */
> ret = usb_autoresume_device(udev);
> - if (ret < 0) {
> - usb_unlock_device(udev);
> + if (ret < 0)
> return ret;
> - }
>
IMO this should be handled already by the class driver, and if not, what is
the harm?
> udev->offload_usage++;
> usb_autosuspend_device(udev);
> - usb_unlock_device(udev);
>
> return ret;
> }
> @@ -64,6 +59,7 @@ EXPORT_SYMBOL_GPL(usb_offload_get);
> * The inverse operation of usb_offload_get, which drops the offload_usage of
> * a USB device. This information allows the USB driver to adjust its power
> * management policy based on offload activity.
> + * The caller must hold @udev's device lock.
> *
> * Return: 0 on success. A negative error code otherwise.
> */
> @@ -71,33 +67,27 @@ int usb_offload_put(struct usb_device *udev)
> {
> int ret;
>
> - usb_lock_device(udev);
> - if (udev->state == USB_STATE_NOTATTACHED) {
> - usb_unlock_device(udev);
> + device_lock_assert(&udev->dev);
> +
> + if (udev->state == USB_STATE_NOTATTACHED)
> return -ENODEV;
> - }
>
> if (udev->state == USB_STATE_SUSPENDED ||
> - udev->offload_at_suspend) {
> - usb_unlock_device(udev);
> + udev->offload_at_suspend)
> return -EBUSY;
> - }
>
During your testing, did you ever run into any unbalanced counter issues
due to the above early exit conditions?
I guess these are all just questions to see if we can remove the need to
lock the udev mutex, and move to a local mutex for the offload framework.
That would address the locking concerns being brought up by Greg, etc...
Thanks
Wesley Cheng
> /*
> * offload_usage could only be modified when the device is active, since
> * it will alter the suspend flow of the device.
> */
> ret = usb_autoresume_device(udev);
> - if (ret < 0) {
> - usb_unlock_device(udev);
> + if (ret < 0)
> return ret;
> - }
>
> /* Drop the count when it wasn't 0, ignore the operation otherwise. */
> if (udev->offload_usage)
> udev->offload_usage--;
> usb_autosuspend_device(udev);
> - usb_unlock_device(udev);
>
> return ret;
> }
> diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c
> index 2bd77255032b..6fc0ad658d66 100644
> --- a/drivers/usb/host/xhci-sideband.c
> +++ b/drivers/usb/host/xhci-sideband.c
> @@ -93,8 +93,6 @@ __xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *e
> static void
> __xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
> {
> - struct usb_device *udev;
> -
> lockdep_assert_held(&sb->mutex);
>
> if (!sb->ir)
> @@ -102,10 +100,6 @@ __xhci_sideband_remove_interrupter(struct xhci_sideband *sb)
>
> xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir);
> sb->ir = NULL;
> - udev = sb->vdev->udev;
> -
> - if (udev->state != USB_STATE_NOTATTACHED)
> - usb_offload_put(udev);
> }
>
> /* sideband api functions */
> @@ -328,9 +322,6 @@ int
> xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
> bool ip_autoclear, u32 imod_interval, int intr_num)
> {
> - int ret = 0;
> - struct usb_device *udev;
> -
> if (!sb || !sb->xhci)
> return -ENODEV;
>
> @@ -348,12 +339,9 @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg,
> if (!sb->ir)
> return -ENOMEM;
>
> - udev = sb->vdev->udev;
> - ret = usb_offload_get(udev);
> -
> sb->ir->ip_autoclear = ip_autoclear;
>
> - return ret;
> + return 0;
> }
> EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter);
>
On Tue, Mar 17, 2026 at 4:17 PM Wesley Cheng
<wesley.cheng@oss.qualcomm.com> wrote:
>
> On 3/8/2026 7:22 PM, Guan-Yu Lin wrote:
> >
> > @@ -27,31 +28,25 @@ int usb_offload_get(struct usb_device *udev)
> > {
> > int ret;
> >
> > - usb_lock_device(udev);
> > - if (udev->state == USB_STATE_NOTATTACHED) {
> > - usb_unlock_device(udev);
> > + device_lock_assert(&udev->dev);
> > +
> > + if (udev->state == USB_STATE_NOTATTACHED)
> > return -ENODEV;
> > - }
Could be removed. Since the udev is in USB_STATE_NOTATTACHED. I expect
the data structure being cleaned afterwards, so actually counter value
might not be important at this moment.
> >
> > if (udev->state == USB_STATE_SUSPENDED ||
> > - udev->offload_at_suspend) {
> > - usb_unlock_device(udev);
> > + udev->offload_at_suspend)
> > return -EBUSY;
> > - }
> >
This check is still required. Because the suspend/resume process
depends on the counter value, we can't modify the counter value while
the device is suspended. If we do so, we will have an unbalanced
suspend resume operation.
However, we might only need to check for udev->offload_at_suspend (if
we ensure the device is active when we want to incremant the counter):
1. If the offload_usage_count is 0, we won't decrement counts at this moment.
2. If the offload_usage_count is not 0, the offload_at_suspend flag
will be true anyway.
>
> Do we really need to be explicitly checking for the usb device state before
> we touch the offload_usage count? In the end, its a reference count that
> determines how many consumers are active for a specific interrupter, so my
> question revolves around if we need to have such strict checks.
>
Please find the explanation for each check above.
> > /*
> > * offload_usage could only be modified when the device is active, since
> > * it will alter the suspend flow of the device.
> > */
> > ret = usb_autoresume_device(udev);
> > - if (ret < 0) {
> > - usb_unlock_device(udev);
> > + if (ret < 0)
> > return ret;
> > - }
> >
>
> IMO this should be handled already by the class driver, and if not, what is
> the harm?
>
We can only increment the usage count when the device is active. For
counter decrement, the device could be in any state.
My initial design is to resume the device and then modify the usage
count. Another option is to check only whether the USB device is
active via pm_runtime_get_if_active, and leave the device-resuming
effort to the class driver. Do you think this is the better approach?
> > udev->offload_usage++;
> > usb_autosuspend_device(udev);
> > - usb_unlock_device(udev);
> >
> > return ret;
> > }
> > @@ -64,6 +59,7 @@ EXPORT_SYMBOL_GPL(usb_offload_get);
> > * The inverse operation of usb_offload_get, which drops the offload_usage of
> > * a USB device. This information allows the USB driver to adjust its power
> > * management policy based on offload activity.
> > + * The caller must hold @udev's device lock.
> > *
> > * Return: 0 on success. A negative error code otherwise.
> > */
> > @@ -71,33 +67,27 @@ int usb_offload_put(struct usb_device *udev)
> > {
> > int ret;
> >
> > - usb_lock_device(udev);
> > - if (udev->state == USB_STATE_NOTATTACHED) {
> > - usb_unlock_device(udev);
> > + device_lock_assert(&udev->dev);
> > +
> > + if (udev->state == USB_STATE_NOTATTACHED)
> > return -ENODEV;
> > - }
> >
> > if (udev->state == USB_STATE_SUSPENDED ||
> > - udev->offload_at_suspend) {
> > - usb_unlock_device(udev);
> > + udev->offload_at_suspend)
> > return -EBUSY;
> > - }
> >
>
> During your testing, did you ever run into any unbalanced counter issues
> due to the above early exit conditions?
>
> I guess these are all just questions to see if we can remove the need to
> lock the udev mutex, and move to a local mutex for the offload framework.
> That would address the locking concerns being brought up by Greg, etc...
>
> Thanks
> Wesley Cheng
>
While developing the initial patch set, I did encounter the counter imbalance.
Following the discussion, we could move the device resume effort to
the class driver. This way we only need to check if the device is
active before manipulating the offload usage counter, which doesn't
require a device lock. Is there any concern with this approach?
Regards,
Guan-Yu
On 3/18/2026 4:21 PM, Guan-Yu Lin wrote:
> On Tue, Mar 17, 2026 at 4:17 PM Wesley Cheng
> <wesley.cheng@oss.qualcomm.com> wrote:
>>
>> On 3/8/2026 7:22 PM, Guan-Yu Lin wrote:
>>>
>>> @@ -27,31 +28,25 @@ int usb_offload_get(struct usb_device *udev)
>>> {
>>> int ret;
>>>
>>> - usb_lock_device(udev);
>>> - if (udev->state == USB_STATE_NOTATTACHED) {
>>> - usb_unlock_device(udev);
>>> + device_lock_assert(&udev->dev);
>>> +
>>> + if (udev->state == USB_STATE_NOTATTACHED)
>>> return -ENODEV;
>>> - }
>
> Could be removed. Since the udev is in USB_STATE_NOTATTACHED. I expect
> the data structure being cleaned afterwards, so actually counter value
> might not be important at this moment.
>
>>>
>>> if (udev->state == USB_STATE_SUSPENDED ||
>>> - udev->offload_at_suspend) {
>>> - usb_unlock_device(udev);
>>> + udev->offload_at_suspend)
>>> return -EBUSY;
>>> - }
>>>
>
> This check is still required. Because the suspend/resume process
> depends on the counter value, we can't modify the counter value while
> the device is suspended. If we do so, we will have an unbalanced
> suspend resume operation.
>
> However, we might only need to check for udev->offload_at_suspend (if
> we ensure the device is active when we want to incremant the counter):
> 1. If the offload_usage_count is 0, we won't decrement counts at this moment.
> 2. If the offload_usage_count is not 0, the offload_at_suspend flag
> will be true anyway.
>
>>
>> Do we really need to be explicitly checking for the usb device state before
>> we touch the offload_usage count? In the end, its a reference count that
>> determines how many consumers are active for a specific interrupter, so my
>> question revolves around if we need to have such strict checks.
>>
>
> Please find the explanation for each check above.
>
>>> /*
>>> * offload_usage could only be modified when the device is active, since
>>> * it will alter the suspend flow of the device.
>>> */
>>> ret = usb_autoresume_device(udev);
>>> - if (ret < 0) {
>>> - usb_unlock_device(udev);
>>> + if (ret < 0)
>>> return ret;
>>> - }
>>>
>>
>> IMO this should be handled already by the class driver, and if not, what is
>> the harm?
>>
>
> We can only increment the usage count when the device is active. For
> counter decrement, the device could be in any state.
>
> My initial design is to resume the device and then modify the usage
> count. Another option is to check only whether the USB device is
> active via pm_runtime_get_if_active, and leave the device-resuming
> effort to the class driver. Do you think this is the better approach?
>
I think I prefer the active check over RPM versus forcing a device resume.
>>> udev->offload_usage++;
>>> usb_autosuspend_device(udev);
>>> - usb_unlock_device(udev);
>>>
>>> return ret;
>>> }
>>> @@ -64,6 +59,7 @@ EXPORT_SYMBOL_GPL(usb_offload_get);
>>> * The inverse operation of usb_offload_get, which drops the offload_usage of
>>> * a USB device. This information allows the USB driver to adjust its power
>>> * management policy based on offload activity.
>>> + * The caller must hold @udev's device lock.
>>> *
>>> * Return: 0 on success. A negative error code otherwise.
>>> */
>>> @@ -71,33 +67,27 @@ int usb_offload_put(struct usb_device *udev)
>>> {
>>> int ret;
>>>
>>> - usb_lock_device(udev);
>>> - if (udev->state == USB_STATE_NOTATTACHED) {
>>> - usb_unlock_device(udev);
>>> + device_lock_assert(&udev->dev);
>>> +
>>> + if (udev->state == USB_STATE_NOTATTACHED)
>>> return -ENODEV;
>>> - }
>>>
>>> if (udev->state == USB_STATE_SUSPENDED ||
>>> - udev->offload_at_suspend) {
>>> - usb_unlock_device(udev);
>>> + udev->offload_at_suspend)
>>> return -EBUSY;
>>> - }
>>>
>>
>> During your testing, did you ever run into any unbalanced counter issues
>> due to the above early exit conditions?
>>
>> I guess these are all just questions to see if we can remove the need to
>> lock the udev mutex, and move to a local mutex for the offload framework.
>> That would address the locking concerns being brought up by Greg, etc...
>>
>> Thanks
>> Wesley Cheng
>>
>
> While developing the initial patch set, I did encounter the counter imbalance.
>
> Following the discussion, we could move the device resume effort to
> the class driver. This way we only need to check if the device is
> active before manipulating the offload usage counter, which doesn't
> require a device lock. Is there any concern with this approach?
>
I think that is what I was getting to. Now, instead of having to rely on
the udev lock, you can protect the counter using a local mutex, which
should avoid the deadlock mentioned by Oppo. You can avoid also having the
class driver worry about locking requirements, etc..
Thanks
Wesley Cheng
On Mon, Mar 09, 2026 at 02:22:04AM +0000, Guan-Yu Lin wrote:
> Update usb_offload_get() and usb_offload_put() to require that the
> caller holds the USB device lock. Remove the internal call to
> usb_lock_device() and add device_lock_assert() to ensure synchronization
> is handled by the caller. These functions continue to manage the
> device's power state via autoresume/autosuspend and update the
> offload_usage counter.
>
> Additionally, decouple the xHCI sideband interrupter lifecycle from the
> offload usage counter by removing the calls to usb_offload_get() and
> usb_offload_put() from the interrupter creation and removal paths. This
> allows interrupters to be managed independently of the device's offload
> activity status.
>
> Cc: stable@vger.kernel.org
> Fixes: ef82a4803aab ("xhci: sideband: add api to trace sideband usage")
> Signed-off-by: Guan-Yu Lin <guanyulin@google.com>
> Tested-by: Hailong Liu <hailong.liu@oppo.com>
> ---
> drivers/usb/core/offload.c | 34 +++++++++++---------------------
> drivers/usb/host/xhci-sideband.c | 14 +------------
> 2 files changed, 13 insertions(+), 35 deletions(-)
>
> diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c
> index 7c699f1b8d2b..e13a4c21d61b 100644
> --- a/drivers/usb/core/offload.c
> +++ b/drivers/usb/core/offload.c
> @@ -20,6 +20,7 @@
> * enabled on this usb_device; that is, another entity is actively handling USB
> * transfers. This information allows the USB driver to adjust its power
> * management policy based on offload activity.
> + * The caller must hold @udev's device lock.
Ok, but:
> *
> * Return: 0 on success. A negative error code otherwise.
> */
> @@ -27,31 +28,25 @@ int usb_offload_get(struct usb_device *udev)
Why are you not using the __must_hold() definition here?
> {
> int ret;
>
> - usb_lock_device(udev);
> - if (udev->state == USB_STATE_NOTATTACHED) {
> - usb_unlock_device(udev);
> + device_lock_assert(&udev->dev);
That's going to splat at runtime, not compile time, which is when you
really want to check for this, right?
And I thought all of the locking was messy before, and you cleaned it up
to be nicer here, why go back to the "old" way? Having a caller be
forced to have a lock held is ripe for problems...
You also are not changing any callers to usb_offload_get() in this
patch, so does this leave the kernel tree in a broken state? If not,
why not? If so, that's not ok :(
> +
> + if (udev->state == USB_STATE_NOTATTACHED)
> return -ENODEV;
> - }
>
> if (udev->state == USB_STATE_SUSPENDED ||
> - udev->offload_at_suspend) {
> - usb_unlock_device(udev);
> + udev->offload_at_suspend)
Can't that really all be on one line?
> return -EBUSY;
> - }
>
> /*
> * offload_usage could only be modified when the device is active, since
> * it will alter the suspend flow of the device.
> */
> ret = usb_autoresume_device(udev);
> - if (ret < 0) {
> - usb_unlock_device(udev);
> + if (ret < 0)
> return ret;
> - }
>
> udev->offload_usage++;
> usb_autosuspend_device(udev);
> - usb_unlock_device(udev);
>
> return ret;
> }
> @@ -64,6 +59,7 @@ EXPORT_SYMBOL_GPL(usb_offload_get);
> * The inverse operation of usb_offload_get, which drops the offload_usage of
> * a USB device. This information allows the USB driver to adjust its power
> * management policy based on offload activity.
> + * The caller must hold @udev's device lock.
> *
> * Return: 0 on success. A negative error code otherwise.
> */
> @@ -71,33 +67,27 @@ int usb_offload_put(struct usb_device *udev)
Again, use __must_hold() here, to catch build time issues.
And again, I don't see any code changes to reflect this new requirement
:(
thanks,
greg k-h
On Wed, Mar 11, 2026 at 5:26 AM Greg KH <gregkh@linuxfoundation.org> wrote:
>
> On Mon, Mar 09, 2026 at 02:22:04AM +0000, Guan-Yu Lin wrote:
> > Update usb_offload_get() and usb_offload_put() to require that the
> > caller holds the USB device lock. Remove the internal call to
> > usb_lock_device() and add device_lock_assert() to ensure synchronization
> > is handled by the caller. These functions continue to manage the
> > device's power state via autoresume/autosuspend and update the
> > offload_usage counter.
> >
> > Additionally, decouple the xHCI sideband interrupter lifecycle from the
> > offload usage counter by removing the calls to usb_offload_get() and
> > usb_offload_put() from the interrupter creation and removal paths. This
> > allows interrupters to be managed independently of the device's offload
> > activity status.
> >
> > Cc: stable@vger.kernel.org
> > Fixes: ef82a4803aab ("xhci: sideband: add api to trace sideband usage")
> > Signed-off-by: Guan-Yu Lin <guanyulin@google.com>
> > Tested-by: Hailong Liu <hailong.liu@oppo.com>
> > ---
> > drivers/usb/core/offload.c | 34 +++++++++++---------------------
> > drivers/usb/host/xhci-sideband.c | 14 +------------
> > 2 files changed, 13 insertions(+), 35 deletions(-)
> >
> > diff --git a/drivers/usb/core/offload.c b/drivers/usb/core/offload.c
> > index 7c699f1b8d2b..e13a4c21d61b 100644
> > --- a/drivers/usb/core/offload.c
> > +++ b/drivers/usb/core/offload.c
> > @@ -20,6 +20,7 @@
> > * enabled on this usb_device; that is, another entity is actively handling USB
> > * transfers. This information allows the USB driver to adjust its power
> > * management policy based on offload activity.
> > + * The caller must hold @udev's device lock.
>
> Ok, but:
>
> > *
> > * Return: 0 on success. A negative error code otherwise.
> > */
> > @@ -27,31 +28,25 @@ int usb_offload_get(struct usb_device *udev)
>
> Why are you not using the __must_hold() definition here?
>
Thanks for the suggestion, __must_hold() will be added in the next version.
> > {
> > int ret;
> >
> > - usb_lock_device(udev);
> > - if (udev->state == USB_STATE_NOTATTACHED) {
> > - usb_unlock_device(udev);
> > + device_lock_assert(&udev->dev);
>
> That's going to splat at runtime, not compile time, which is when you
> really want to check for this, right?
>
> And I thought all of the locking was messy before, and you cleaned it up
> to be nicer here, why go back to the "old" way? Having a caller be
> forced to have a lock held is ripe for problems...
>
The challenge is that the USB stack automatically holds the lock
during the hardware/software USB connection change. But USB locks are
not held when we create/remove xhci sideband interrupters. Hence, we
need to manipulate the locks by ourselves to distinguish between these
2 usecases. What's your suggestion on this sceneario? Do you have
other options in mind?
> You also are not changing any callers to usb_offload_get() in this
> patch, so does this leave the kernel tree in a broken state? If not,
> why not? If so, that's not ok :(
>
The current upstream implementation triggers deadlocks in some cases.
This patch simply disassociates the offload counter manipulation from
sideband interrupt creation to address the deadlock. After applying
the patch, the offload counter won't update until the next patch in
this series is applied. Is this considered a broken state? Should I
squash the two commits into one, or keep them as they were?
>
> > +
> > + if (udev->state == USB_STATE_NOTATTACHED)
> > return -ENODEV;
> > - }
> >
> > if (udev->state == USB_STATE_SUSPENDED ||
> > - udev->offload_at_suspend) {
> > - usb_unlock_device(udev);
> > + udev->offload_at_suspend)
>
> Can't that really all be on one line?
>
Sure, Let me change it to one line.
> > return -EBUSY;
> > - }
> >
> > /*
> > * offload_usage could only be modified when the device is active, since
> > * it will alter the suspend flow of the device.
> > */
> > ret = usb_autoresume_device(udev);
> > - if (ret < 0) {
> > - usb_unlock_device(udev);
> > + if (ret < 0)
> > return ret;
> > - }
> >
> > udev->offload_usage++;
> > usb_autosuspend_device(udev);
> > - usb_unlock_device(udev);
> >
> > return ret;
> > }
> > @@ -64,6 +59,7 @@ EXPORT_SYMBOL_GPL(usb_offload_get);
> > * The inverse operation of usb_offload_get, which drops the offload_usage of
> > * a USB device. This information allows the USB driver to adjust its power
> > * management policy based on offload activity.
> > + * The caller must hold @udev's device lock.
> > *
> > * Return: 0 on success. A negative error code otherwise.
> > */
> > @@ -71,33 +67,27 @@ int usb_offload_put(struct usb_device *udev)
>
> Again, use __must_hold() here, to catch build time issues.
>
> And again, I don't see any code changes to reflect this new requirement
> :(
>
> thanks,
>
> greg k-h
Thanks for the suggestion, The __must_hold() macro will be adaped in
the next version.
Regards,
Guan-Yu
© 2016 - 2026 Red Hat, Inc.