[PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect
From: Yiyu Zhou
Date: Sat Oct 10 2026 - 02:46:15 EST
Commit 042aad8d0db6 ("xhci: prevent endpoint recovery after roothub
disconnect") makes xhci_handle_halted_endpoint() return early when the
root port is disconnected, and commit 7e0ef4332ed9 ("xhci: avoid xHC
endpoint changes after disconnect or link error.") also sets
EP_DROP_PENDING there, which keeps the doorbell from being rung until
the device is reset.
The callers rely on xhci_handle_halted_endpoint() to take over the
halted TD. For example, finish_td() returns right after calling it,
expecting the TD to be given back once the Reset Endpoint command
completes. With the early return, nobody gives the TD back. Its URB
stays pending until the submitter times out and unlinks it. Every URB
queued on the endpoint afterwards never starts, because the doorbell is
blocked, and also has to time out.
Devices that disconnect and reconnect themselves during enumeration hit
this. A PFU HHKB Professional HYBRID Type-S keyboard (04fe:0021) drops
off the bus about 300 ms after its first enumeration, while usbhid is
still probing its third interface. The probe runs in hub_wq, so the
disconnect is not handled until the probe returns. On a root port the
HID descriptor request and its four retries in hid_get_class_descriptor()
each wait for the full 5 second USB_CTRL_GET_TIMEOUT:
[57685.006036] usb 3-2: new full-speed USB device number 24 using xhci_hcd
...
[57685.291304] hid-generic 0003:04FE:0021.0017: input,hidraw1: USB HID v1.11 Keyboard [PFU Limited HHKB-Hybrid] on usb-0000:00:14.0-2/input1
[57711.221083] usbhid 3-2:1.2: can't add hid device: -110
[57711.221613] usbhid 3-2:1.2: probe with driver usbhid failed with error -110
[57711.221842] usb 3-2: USB disconnect, device number 24
The re-enumeration that follows can stall the same way, leaving the
keyboard dead until reboot. Behind an external hub, where the root
port stays connected, the same keyboard recovers in about 300 ms, as it
did on the root port before these commits.
Keep skipping Reset Endpoint on a disconnected or inactive port, but
give the halted TD back right away, with -EPROTO if no error status was
set yet. The endpoint is not restarted until the device is reset or
dropped, so the ring dequeue pointer can stay where it is, as
xhci_invalidate_cancelled_tds() already assumes for cancelled TDs on
such endpoints. Also refuse new URBs on an endpoint with
EP_DROP_PENDING set, like xhci_urb_enqueue() already does for an
inactive link, so they fail right away instead of timing out.
Fixes: 042aad8d0db6 ("xhci: prevent endpoint recovery after roothub disconnect")
Fixes: 7e0ef4332ed9 ("xhci: avoid xHC endpoint changes after disconnect or link error.")
Assisted-by: LLM
Signed-off-by: Yiyu Zhou <yiyu@xxxxxxxxxxx>
---
Tested on v7.3-rc6 on a System76 Galago Pro (Intel 8086:51ed xHCI)
with the keyboard plugged directly into a root port, including twice
right after resume from suspend. On every hotplug the keyboard still
dropped off during the probe of its third interface, and it was
re-enumerated with all three interfaces bound about 0.3 s later.
Before this patch, the same sequence hung usbhid for 26 s, and often
left the keyboard dead until reboot.
drivers/usb/host/xhci-ring.c | 24 ++++++++++++++++++++----
drivers/usb/host/xhci.c | 5 +++++
2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index ec278a9f9540..82190380831d 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -979,6 +979,24 @@ static int xhci_reset_halted_ep(struct xhci_hcd *xhci, unsigned int slot_id,
return ret;
}
+/*
+ * Mark the endpoint to be dropped instead of reset, and give back the halted
+ * TD now, as nobody else will. The endpoint is not restarted before device
+ * reset or drop, so there is no need to move the ring dequeue past the TD.
+ */
+static int xhci_drop_halted_endpoint(struct xhci_hcd *xhci,
+ struct xhci_virt_ep *ep,
+ struct xhci_td *td)
+{
+ ep->ep_state |= EP_DROP_PENDING;
+
+ if (td)
+ xhci_td_cleanup(xhci, td, xhci_urb_to_transfer_ring(xhci, td->urb),
+ td->status ?: -EPROTO);
+
+ return -ENODEV;
+}
+
static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
struct xhci_virt_ep *ep,
struct xhci_td *td,
@@ -993,10 +1011,8 @@ static int xhci_handle_halted_endpoint(struct xhci_hcd *xhci,
* Can cause host hang.
* Device will be reset to recover an inactive link, so don't do anything
*/
- if (rhub_port->link_inactive || !rhub_port->connected) {
- ep->ep_state |= EP_DROP_PENDING;
- return -ENODEV;
- }
+ if (rhub_port->link_inactive || !rhub_port->connected)
+ return xhci_drop_halted_endpoint(xhci, ep, td);
/* add td to cancelled list and let reset ep handler take care of it */
if (reset_type == EP_HARD_RESET) {
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index a9e47e178c28..491a541b57b0 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -1691,6 +1691,11 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
ret = -EINVAL;
goto free_priv;
}
+ if (*ep_state & EP_DROP_PENDING) {
+ xhci_dbg(xhci, "Can't queue urb, ep halted after disconnect or link error\n");
+ ret = -ENODEV;
+ goto free_priv;
+ }
switch (usb_endpoint_type(&urb->ep->desc)) {
--
2.55.0