* [PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect
@ 2026-10-10 6:42 Yiyu Zhou
2026-10-10 7:17 ` Yiyu Zhou
2026-10-12 8:41 ` Michal Pecio
0 siblings, 2 replies; 4+ messages in thread
From: Yiyu Zhou @ 2026-10-10 6:42 UTC (permalink / raw)
To: mathias.nyman, gregkh; +Cc: linux-usb, linux-kernel, michal.pecio, Yiyu Zhou
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@yiyuzhou.io>
---
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
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect 2026-10-10 6:42 [PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect Yiyu Zhou @ 2026-10-10 7:17 ` Yiyu Zhou 2026-10-12 8:41 ` Michal Pecio 1 sibling, 0 replies; 4+ messages in thread From: Yiyu Zhou @ 2026-10-10 7:17 UTC (permalink / raw) To: mathias.nyman, gregkh Cc: Yiyu Zhou, linux-usb, linux-kernel, michal.pecio, regressions This fixes a regression introduced in v7.3-rc1. Up to v7.2.1 the keyboard recovered from the same hotplug in well under a second, and reverting 042aad8d0db6 and 7e0ef4332ed9 on top of v7.3-rc5 also fixed it. It would be good to get this into 7.3. #regzbot introduced: 042aad8d0db6 ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect 2026-10-10 6:42 [PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect Yiyu Zhou 2026-10-10 7:17 ` Yiyu Zhou @ 2026-10-12 8:41 ` Michal Pecio 2026-10-12 16:22 ` Mathias Nyman 1 sibling, 1 reply; 4+ messages in thread From: Michal Pecio @ 2026-10-12 8:41 UTC (permalink / raw) To: Yiyu Zhou; +Cc: mathias.nyman, gregkh, linux-usb, linux-kernel On Fri, 9 Oct 2026 23:42:19 -0700, Yiyu Zhou wrote: > 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. Before 7.3 there was a similar device flag VDEV_PORT_ERROR which stuck until the device is reset or deleted. It only applied to USB3 devices. URBs were left hanging, but nobody cared, because disconnect() handler of the class driver would unlink them. > 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. But probe breaks this scheme, it seems we do have no choice but to stop being lazy and give back the URB right there. Regression occurred when the mechanism has been generalized to USB2 by also looking at the PORTSC.CCS bit, even though the problems it tries to solve have been unique to USB3 so far. But this change seems sensible, because a USB3 port may autonomously exit the Error state to Disconnected on receiver termination loss, see xHCI 4.19.1.2.6, and PLS changes to RxDetect. It may even reach Enabled before software intervention; seems we should look at PORTSC.CSC too. (On a USB2 port, like this keyboard uses, such race doesn't exist). And since xhci_irq() clears these per-port flags when USB3 link comes back, there is no guarantee that the permanent EP_DROP_PENDING state will be latched on the lost device. Whether the HW bugs caused by link loss will still be happening on the "new" link is unclear; I only know about Renesas: it doesn't mind and actually recovers on reconnection. But the device is reset, which may or may not catch some xHC off guard. Not sure if going back to a sticky per-device bit wouldn't be better. One downside is that we need to flag all affected devices, which means a loop in IRQ. Not great, but there is never more than 255 devices, so maybe a few microseconds at worst. Likely not enough latency to break anything, and it saves some repetitive checks in other places. I could cook up a patch like that, I think it would make things easier, as there is no need for those very similar checks in urb_enqueue() for example, as seen below. But let's see what Mathias thinks. > 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@yiyuzhou.io> > --- > 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; > +} Not sure if this is big enough to justify a new function, and the comment partly duplicates existing comment at the call site. > 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; > + } It would probably make more sense to log the conditions which set this flag then every failed submission attempt after the flag is set. This is the aforementioned "very similar check", because a few lines prior we have the related test for rhub_port->link_inactive. > > switch (usb_endpoint_type(&urb->ep->desc)) { > > -- > 2.55.0 > ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect 2026-10-12 8:41 ` Michal Pecio @ 2026-10-12 16:22 ` Mathias Nyman 0 siblings, 0 replies; 4+ messages in thread From: Mathias Nyman @ 2026-10-12 16:22 UTC (permalink / raw) To: Michal Pecio, Yiyu Zhou; +Cc: mathias.nyman, gregkh, linux-usb, linux-kernel On 10/12/26 11:41, Michal Pecio wrote: > On Fri, 9 Oct 2026 23:42:19 -0700, Yiyu Zhou wrote: >> 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. > > Before 7.3 there was a similar device flag VDEV_PORT_ERROR which stuck > until the device is reset or deleted. It only applied to USB3 devices. > URBs were left hanging, but nobody cared, because disconnect() handler > of the class driver would unlink them. > >> 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. > > But probe breaks this scheme, it seems we do have no choice but to stop > being lazy and give back the URB right there. > > Regression occurred when the mechanism has been generalized to USB2 by > also looking at the PORTSC.CCS bit, even though the problems it tries > to solve have been unique to USB3 so far. > > But this change seems sensible, because a USB3 port may autonomously > exit the Error state to Disconnected on receiver termination loss, see > xHCI 4.19.1.2.6, and PLS changes to RxDetect. It may even reach Enabled > before software intervention; seems we should look at PORTSC.CSC too. > > (On a USB2 port, like this keyboard uses, such race doesn't exist). > > And since xhci_irq() clears these per-port flags when USB3 link comes > back, there is no guarantee that the permanent EP_DROP_PENDING state > will be latched on the lost device. Whether the HW bugs caused by link > loss will still be happening on the "new" link is unclear; I only know > about Renesas: it doesn't mind and actually recovers on reconnection. > But the device is reset, which may or may not catch some xHC off guard. > > Not sure if going back to a sticky per-device bit wouldn't be better. > One downside is that we need to flag all affected devices, which means > a loop in IRQ. Not great, but there is never more than 255 devices, so > maybe a few microseconds at worst. Likely not enough latency to break > anything, and it saves some repetitive checks in other places. > > I could cook up a patch like that, I think it would make things easier, > as there is no need for those very similar checks in urb_enqueue() for > example, as seen below. But let's see what Mathias thinks. > The solution in this patch seems reasonable to me. It's simple enough and should solve this, and similar cases. We should probably give back all TDs of the halted endpoint, not just the one we halted on. Agree with the minor changes you suggested to this patch. -Mathias ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-12 16:22 UTC | newest] Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-10 6:42 [PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect Yiyu Zhou 2026-10-10 7:17 ` Yiyu Zhou 2026-10-12 8:41 ` Michal Pecio 2026-10-12 16:22 ` Mathias Nyman
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®