From: Michal Pecio <michal.pecio@gmail.com>
To: Yiyu Zhou <yiyu@yiyuzhou.io>
Cc: mathias.nyman@intel.com, gregkh@linuxfoundation.org,
linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect
Date: Mon, 12 Oct 2026 10:41:22 +0200 [thread overview]
Message-ID: <20261012104122.0af52938.michal.pecio@gmail.com> (raw)
In-Reply-To: <20261010064407.11030-1-yiyu@yiyuzhou.io>
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
>
next prev parent reply other threads:[~2026-10-12 8:41 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-10 6:42 [PATCH] xhci: give back halted TD when skipping endpoint reset after disconnect Yiyu Zhou
2026-10-10 7:06 ` sashiko-bot
2026-10-10 7:17 ` Yiyu Zhou
2026-10-12 8:41 ` Michal Pecio [this message]
2026-10-12 16:22 ` Mathias Nyman
2026-10-13 7:37 ` [PATCH v2] xhci: give back all TDs " Yiyu Zhou
2026-10-13 7:56 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261012104122.0af52938.michal.pecio@gmail.com \
--to=michal.pecio@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=mathias.nyman@intel.com \
--cc=yiyu@yiyuzhou.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox