[RFC PATCH] usb: xhci: Skip configure EP for disabled slots during teardown

Udipto Goswami
2026-01-05T08:48:17Z

Consider a scenario when a HS headset fails resume and the hub performs
a logical disconnect, the USB core tears down endpoints and calls
hcd->check_bandwidth() on the way out, which with xHCI translates to a
drop-only Configure Endpoint command (add_flags == SLOT_FLAG, drop_flags
!= 0). If the slot is already disabled (slot_id == 0) or the virtual
device has been freed, issuing this Configure Endpoint command is
pointless and may appear stuck until event handling catches up,
causing unnecessary delays during disconnect teardown.

Fix this by adding a check in xhci_check_bandwidth(), return success
immediately if slot_id == 0 or vdev is missing, preventing the
Configure Endpoint command from being queued at all. Additionally,
in xhci_configure_endpoint() for drop-only Configure Endpoint operations,
return success early if slot_id == 0 or vdev is already freed,
avoiding spurious command waits.

Signed-off-by: Udipto Goswami <udipto.goswami@oss.qualcomm.com>
---
drivers/usb/host/xhci.c | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 02c9bfe21ae2..bc92edbad468 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -2958,6 +2958,7 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci,
struct xhci_input_control_ctx *ctrl_ctx;
struct xhci_virt_device *virt_dev;
struct xhci_slot_ctx *slot_ctx;
+ u32 add_flags, drop_flags;

if (!command)
return -EINVAL;
@@ -2979,6 +2980,19 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci,
return -ENOMEM;
}

+ /*
+ * For drop-only Configure Endpoint (add_flags == SLOT_FLAG
+ * and drop_flags != 0), if vdev is already gone, there is no hardware
+ * to configure. Return success early to avoid issuing pointless commands.
+ */
+ add_flags = le32_to_cpu(ctrl_ctx->add_flags);
+ drop_flags = le32_to_cpu(ctrl_ctx->drop_flags);
+ if (!ctx_change && add_flags == SLOT_FLAG && drop_flags != 0 && !virt_dev) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ xhci_dbg(xhci, "skip drop-only Configure EP; vdev already freed\n");
+ return 0;
+ }
+
if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK) &&
xhci_reserve_host_resources(xhci, ctrl_ctx)) {
spin_unlock_irqrestore(&xhci->lock, flags);
@@ -3082,12 +3096,27 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
if (ret <= 0)
return ret;
xhci = hcd_to_xhci(hcd);
+
+ /*
+ * If the slot is already disabled (slot_id == 0) or the vdev is gone,
+ * we're in teardown. There's nothing to update in HW. Treat as success
+ * and skip issuing Configure Endpoint command.
+ */
+ if (!udev->slot_id) {
+ xhci_dbg(xhci, "Slot already disabled for udev %p\n", udev);
+ return 0;
+ }
+
+ virt_dev = xhci->devs[udev->slot_id];
+ if (!virt_dev) {
+ xhci_dbg(xhci, "virt_dev already freed for slot %d\n", udev->slot_id);
+ return 0;
+ }
if ((xhci->xhc_state & XHCI_STATE_DYING) ||
(xhci->xhc_state & XHCI_STATE_REMOVING))
return -ENODEV;

xhci_dbg(xhci, "%s called for udev %p\n", __func__, udev);
- virt_dev = xhci->devs[udev->slot_id];

command = xhci_alloc_command(xhci, true, GFP_KERNEL);
if (!command)
--
2.34.1