]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
usb: xhci: Fix sleep in atomic context in xhci_free_streams()
author胡连勤 <hulianqin@vivo.com>
Fri, 3 Jul 2026 14:40:33 +0000 (17:40 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 8 Jul 2026 11:38:28 +0000 (13:38 +0200)
When a USB device with active stream endpoints is disconnected,
xhci_free_streams() is called from the hub_event workqueue to
free the stream resources.  It calls xhci_free_stream_info()
while holding xhci->lock with irqs disabled.

xhci_free_stream_info() invokes xhci_free_stream_ctx(), which
calls dma_free_coherent() for large stream context arrays.

dma_free_coherent() can sleep (e.g. via vunmap), triggering
a BUG when called from atomic context.

Call trace:
 dma_free_attrs+0x174/0x220
 xhci_free_stream_info+0xd0/0x11c
 xhci_free_streams+0x278/0x37c
 usb_free_streams+0x98/0xc0
 usb_unbind_interface+0x1b8/0x2f8
 device_release_driver_internal+0x1d4/0x2cc
 device_release_driver+0x18/0x28
 bus_remove_device+0x160/0x1a4
 device_del+0x1ec/0x350
 usb_disable_device+0x98/0x214
 usb_disconnect+0xf0/0x35c
 hub_event+0xab4/0x19ec
 process_one_work+0x278/0x63c

Fix this by saving the stream_info pointers and clearing the
ep references under the lock, then calling xhci_free_stream_info()
outside the lock where sleeping is allowed.

Fixes: 8df75f42f8e6 ("USB: xhci: Add memory allocation for USB3 bulk streams.")
Cc: stable <stable@kernel.org>
Signed-off-by: Lianqin Hu <hulianqin@vivo.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://patch.msgid.link/20260703144033.483286-3-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci.c

index 6922cc5496c18257928965808dd911cda9bdbec0..f44ccee5fa07882d87c0efd40233c39feb543c40 100644 (file)
@@ -3785,6 +3785,7 @@ static int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev,
        struct xhci_virt_device *vdev;
        struct xhci_command *command;
        struct xhci_input_control_ctx *ctrl_ctx;
+       struct xhci_stream_info *stream_info[EP_CTX_PER_DEV];
        unsigned int ep_index;
        unsigned long flags;
        u32 changed_ep_bitmask;
@@ -3845,10 +3846,15 @@ static int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev,
        if (ret < 0)
                return ret;
 
+       /*
+        * dma_free_coherent() called by xhci_free_stream_info() may sleep,
+        * so save stream_info pointers and clear references under lock,
+        * then free the memory outside lock.
+        */
        spin_lock_irqsave(&xhci->lock, flags);
        for (i = 0; i < num_eps; i++) {
                ep_index = xhci_get_endpoint_index(&eps[i]->desc);
-               xhci_free_stream_info(xhci, vdev->eps[ep_index].stream_info);
+               stream_info[i] = vdev->eps[ep_index].stream_info;
                vdev->eps[ep_index].stream_info = NULL;
                /* FIXME Unset maxPstreams in endpoint context and
                 * update deq ptr to point to normal string ring.
@@ -3858,6 +3864,9 @@ static int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev,
        }
        spin_unlock_irqrestore(&xhci->lock, flags);
 
+       for (i = 0; i < num_eps; i++)
+               xhci_free_stream_info(xhci, stream_info[i]);
+
        return 0;
 }