[PATCH 19/19] usb: dwc2: wait for the TT clear in endpoint_disable
From: Greg Kroah-Hartman
Date: Tue Oct 27 2026 - 11:07:43 EST
From: Josef Bacik <josef@xxxxxxxxxxxxxx>
When a split transaction fails, dwc2_hc_handle_tt_clear() sets
qh->tt_buffer_dirty and calls usb_hub_clear_tt_buffer(). The hub driver
stores urb->ep in its list and hub_tt_work() hands it back to
_dwc2_hcd_clear_tt_buffer_complete() after the CLEAR_TT_BUFFER request to
the hub, which can take up to a second if the hub does not answer.
Nothing keeps the endpoint until then. dwc2_hcd_endpoint_disable() only
waits for the QTD list to drain and frees the QH. If the full or low
speed device is unplugged, usb_disconnect() goes on to free the
usb_device and its configurations, which hold the struct
usb_host_endpoint. The callback then reads ep->hcpriv from freed memory
and, if it is not NULL, clears a bit in whatever it points to. A change
of the configuration or altsetting only frees the QH, under a callback
which has loaded ep->hcpriv without the lock.
A device which contains a hub can provoke this: fail a transaction of
the downstream device, report its disconnection and hold back the answer
to CLEAR_TT_BUFFER.
The HCD has to wait, like ehci_endpoint_disable(),
xhci_endpoint_disable() and fotg210_endpoint_disable() do. hub_tt_work()
calls the callback for every entry, also if the request failed, and
hub_quiesce() flushes the work, so the wait ends.
Fixes: 7359d482eb4d ("staging: HCD files for the DWC2 driver")
Cc: stable <stable@xxxxxxxxxx>
Cc: Minas Harutyunyan <hminas@xxxxxxxxxxxx>
Signed-off-by: Josef Bacik <josef@xxxxxxxxxxxxxx>
Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
---
drivers/usb/dwc2/hcd.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c
index d5275cd658d4..a6ee55c494f8 100644
--- a/drivers/usb/dwc2/hcd.c
+++ b/drivers/usb/dwc2/hcd.c
@@ -1993,6 +1993,21 @@ static int dwc2_hcd_endpoint_disable(struct dwc2_hsotg *hsotg,
}
}
+ /*
+ * The hub driver still has a pointer to the endpoint. Wait until it
+ * has called _dwc2_hcd_clear_tt_buffer_complete().
+ */
+ while (qh->tt_buffer_dirty) {
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+ msleep(20);
+ spin_lock_irqsave(&hsotg->lock, flags);
+ qh = ep->hcpriv;
+ if (!qh) {
+ rc = -EINVAL;
+ goto err;
+ }
+ }
+
dwc2_hcd_qh_unlink(hsotg, qh);
/* Free each QTD in the QH's QTD list */
--
2.56.0