diff mbox series

[v2,3/7] usb: xhci: Check for blocked disconnection

Message ID 07196754c6de290bb46cc235ce6e96c5df304150.1618014279.git.Thinh.Nguyen@synopsys.com (mailing list archive)
State New, archived
Headers show
Series usb: Set quirks for xhci/dwc3 host mode | expand

Commit Message

Thinh Nguyen April 10, 2021, 12:47 a.m. UTC
If there is a device with active enhanced super-speed (eSS) isoc IN
endpoint(s) behind one or more eSS hubs, DWC_usb31 (v1.90a and prior)
host controller will not detect the device disconnection until no more
isoc URB is submitted. If there's a device disconnection, internally
the wait for tHostTransactionTimeout (USB 3.2 spec 8.13) blocks the
other endpoints from being scheduled. So, it blocks the interrupt
endpoint of the eSS hub indicating the port change status.

This can be an issue for applications that continuously submitting isoc
URBs to the xHCI. To work around this, stop processing new URBs after 3
consecutive isoc transaction errors. If new isoc transfers are queued
after the device is disconnected, the host will respond with USB
transaction error. After 3 consecutive USB transaction errors, the
driver can wait a period of time (at least 2 * largest periodic interval
of the topology) without ringing isoc endpoint doorbell to detect the
port change status. If there is no disconnection detected, ring the
endpoint doorbell to resume isoc transfers.

This workaround tracks the max eSS periodic interval every time there's
an endpoint added or dropped, which happens when there's bandwidth
check. So, scan the topology and update the xhci->max_ess_interval
whenever there's a bandwidth check. Introduced a new flag
VDEV_DISCONN_CHECK_PENDING to prevent ringing the doorbell while waiting
for a disconnection status. After 2 * max_ess_interval time and no
disconnection detected, a delayed work will ring the doorbell to resume
the active isoc transfers.

Signed-off-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
---
 Changes in v2:
 - None

 drivers/usb/host/xhci-mem.c     |  3 ++
 drivers/usb/host/xhci-ring.c    | 76 +++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci.c         | 49 +++++++++++++++++++++
 drivers/usb/host/xhci.h         | 10 +++++
 include/linux/usb/xhci-quirks.h |  1 +
 5 files changed, 139 insertions(+)

Comments

Mathias Nyman April 27, 2021, 1:08 p.m. UTC | #1
Hi Thinh

Sorry about the delay. 

On 10.4.2021 3.47, Thinh Nguyen wrote:
> If there is a device with active enhanced super-speed (eSS) isoc IN
> endpoint(s) behind one or more eSS hubs, DWC_usb31 (v1.90a and prior)
> host controller will not detect the device disconnection until no more
> isoc URB is submitted. If there's a device disconnection, internally
> the wait for tHostTransactionTimeout (USB 3.2 spec 8.13) blocks the
> other endpoints from being scheduled. So, it blocks the interrupt
> endpoint of the eSS hub indicating the port change status.
> 
> This can be an issue for applications that continuously submitting isoc
> URBs to the xHCI. To work around this, stop processing new URBs after 3
> consecutive isoc transaction errors. If new isoc transfers are queued
> after the device is disconnected, the host will respond with USB
> transaction error. After 3 consecutive USB transaction errors, the
> driver can wait a period of time (at least 2 * largest periodic interval
> of the topology) without ringing isoc endpoint doorbell to detect the
> port change status. If there is no disconnection detected, ring the
> endpoint doorbell to resume isoc transfers.

Is that enough? many Isoc URBs queue 16 - 32 Isoc TRBs per URB.
And drivers like UVC queue several URBs in advance.

If I remember correctly then a transaction errors won't stop Isoch endpoints,
so waiting for 2 * Interval after 3 consecutive transaction errors might not
be enough.

How about stopping the endpoint after 3 consecutive transaction errors,
and restating it a bit later? 

> 
> This workaround tracks the max eSS periodic interval every time there's
> an endpoint added or dropped, which happens when there's bandwidth
> check. So, scan the topology and update the xhci->max_ess_interval
> whenever there's a bandwidth check. Introduced a new flag
> VDEV_DISCONN_CHECK_PENDING to prevent ringing the doorbell while waiting
> for a disconnection status. After 2 * max_ess_interval time and no
> disconnection detected, a delayed work will ring the doorbell to resume
> the active isoc transfers.

Sounds very elaborate for a vendor specific disconnect workaround.
Isn't there a simpler way?

Maybe stop all isoc in endpoints if one them has 3 consecutive transaction error,
wait for 2x hub interrupt interval time, and then restart the endpoints if there is
no disconnect?

There is bigger concern with this series, it scatters a lot of vendor specific code 
around the generic xhci driver. It's not very clear afterwards what code is part of the
workaround and what is generic code.

We just got a lot of the Mediatek code moved to xhci-mtk*, maybe its time to add xhci-snps.c
instead of using the generic platform driver with tons of workarounds and quirks.

Thanks
-Mathias
Thinh Nguyen April 27, 2021, 10:30 p.m. UTC | #2
Mathias Nyman wrote:
> Hi Thinh
> 
> Sorry about the delay. 

Np :)

> 
> On 10.4.2021 3.47, Thinh Nguyen wrote:
>> If there is a device with active enhanced super-speed (eSS) isoc IN
>> endpoint(s) behind one or more eSS hubs, DWC_usb31 (v1.90a and prior)
>> host controller will not detect the device disconnection until no more
>> isoc URB is submitted. If there's a device disconnection, internally
>> the wait for tHostTransactionTimeout (USB 3.2 spec 8.13) blocks the
>> other endpoints from being scheduled. So, it blocks the interrupt
>> endpoint of the eSS hub indicating the port change status.
>>
>> This can be an issue for applications that continuously submitting isoc
>> URBs to the xHCI. To work around this, stop processing new URBs after 3
>> consecutive isoc transaction errors. If new isoc transfers are queued
>> after the device is disconnected, the host will respond with USB
>> transaction error. After 3 consecutive USB transaction errors, the
>> driver can wait a period of time (at least 2 * largest periodic interval
>> of the topology) without ringing isoc endpoint doorbell to detect the
>> port change status. If there is no disconnection detected, ring the
>> endpoint doorbell to resume isoc transfers.
> 
> Is that enough? many Isoc URBs queue 16 - 32 Isoc TRBs per URB.
> And drivers like UVC queue several URBs in advance.

That's fine as long as the driver stops ringing more doorbell for a
certain period of time creating a gap that's enough to get the
notification from the interrupt endpoint. We tested with 128 isoc URBs
and was able to detect a disconnect after this delay.

> 
> If I remember correctly then a transaction errors won't stop Isoch endpoints,
> so waiting for 2 * Interval after 3 consecutive transaction errors might not
> be enough.
> 
> How about stopping the endpoint after 3 consecutive transaction errors,
> and restating it a bit later?

There's no need to stop and restart the endpoint.

> 
>>
>> This workaround tracks the max eSS periodic interval every time there's
>> an endpoint added or dropped, which happens when there's bandwidth
>> check. So, scan the topology and update the xhci->max_ess_interval
>> whenever there's a bandwidth check. Introduced a new flag
>> VDEV_DISCONN_CHECK_PENDING to prevent ringing the doorbell while waiting
>> for a disconnection status. After 2 * max_ess_interval time and no
>> disconnection detected, a delayed work will ring the doorbell to resume
>> the active isoc transfers.
> 
> Sounds very elaborate for a vendor specific disconnect workaround.
> Isn't there a simpler way?
> 
> Maybe stop all isoc in endpoints if one them has 3 consecutive transaction error,
> wait for 2x hub interrupt interval time, and then restart the endpoints if there is
> no disconnect?

We can also do this (but without stop + restart the endpoints). It just
creates a slightly larger gap that may be more noticeable to the user if
there's no actual disconnection.

> 
> There is bigger concern with this series, it scatters a lot of vendor specific code 
> around the generic xhci driver. It's not very clear afterwards what code is part of the
> workaround and what is generic code.
> 
> We just got a lot of the Mediatek code moved to xhci-mtk*, maybe its time to add xhci-snps.c
> instead of using the generic platform driver with tons of workarounds and quirks.
> 

Thanks for the reviews. I need to look into how this can be done. May
need your suggestion as not every scenarios can be overridden
easily/cleanly.

What about the other quirks, do you have any comments?

Thanks,
Thinh
Mathias Nyman April 28, 2021, 1:32 p.m. UTC | #3
On 28.4.2021 1.30, Thinh Nguyen wrote:
>> On 10.4.2021 3.47, Thinh Nguyen wrote:
>>> If there is a device with active enhanced super-speed (eSS) isoc IN
>>> endpoint(s) behind one or more eSS hubs, DWC_usb31 (v1.90a and prior)
>>> host controller will not detect the device disconnection until no more
>>> isoc URB is submitted. If there's a device disconnection, internally
>>> the wait for tHostTransactionTimeout (USB 3.2 spec 8.13) blocks the
>>> other endpoints from being scheduled. So, it blocks the interrupt
>>> endpoint of the eSS hub indicating the port change status.
>>>
>>> This can be an issue for applications that continuously submitting isoc
>>> URBs to the xHCI. To work around this, stop processing new URBs after 3
>>> consecutive isoc transaction errors. If new isoc transfers are queued
>>> after the device is disconnected, the host will respond with USB
>>> transaction error. After 3 consecutive USB transaction errors, the
>>> driver can wait a period of time (at least 2 * largest periodic interval
>>> of the topology) without ringing isoc endpoint doorbell to detect the
>>> port change status. If there is no disconnection detected, ring the
>>> endpoint doorbell to resume isoc transfers.
>>
>> Is that enough? many Isoc URBs queue 16 - 32 Isoc TRBs per URB.
>> And drivers like UVC queue several URBs in advance.
> 
> That's fine as long as the driver stops ringing more doorbell for a
> certain period of time creating a gap that's enough to get the
> notification from the interrupt endpoint. We tested with 128 isoc URBs
> and was able to detect a disconnect after this delay.

Ok, if not ringing the endpoint is enough then that is better than
stopping the whole endpoint. 

>>> This workaround tracks the max eSS periodic interval every time there's
>>> an endpoint added or dropped, which happens when there's bandwidth
>>> check. So, scan the topology and update the xhci->max_ess_interval
>>> whenever there's a bandwidth check. Introduced a new flag
>>> VDEV_DISCONN_CHECK_PENDING to prevent ringing the doorbell while waiting
>>> for a disconnection status. After 2 * max_ess_interval time and no
>>> disconnection detected, a delayed work will ring the doorbell to resume
>>> the active isoc transfers.
>>
>> Sounds very elaborate for a vendor specific disconnect workaround.
>> Isn't there a simpler way?
>>
>> Maybe stop all isoc in endpoints if one them has 3 consecutive transaction error,
>> wait for 2x hub interrupt interval time, and then restart the endpoints if there is
>> no disconnect?
> 
> We can also do this (but without stop + restart the endpoints). It just
> creates a slightly larger gap that may be more noticeable to the user if
> there's no actual disconnection.

Ok, if blocking the doorbell is enough then it sounds better.

How about that max interval tracking, is it necessary?
It will block the doorbell from 250us up to several seconds.
Is there some reasonable single value that can be used instead?

>>
>> There is bigger concern with this series, it scatters a lot of vendor specific code 
>> around the generic xhci driver. It's not very clear afterwards what code is part of the
>> workaround and what is generic code.
>>
>> We just got a lot of the Mediatek code moved to xhci-mtk*, maybe its time to add xhci-snps.c
>> instead of using the generic platform driver with tons of workarounds and quirks.
>>
> 
> Thanks for the reviews. I need to look into how this can be done. May
> need your suggestion as not every scenarios can be overridden
> easily/cleanly.

true, no easy overrides for this transfer error / doorbell blocking workaround.
Needs a bit of work

-Mathias
Thinh Nguyen April 29, 2021, 12:54 a.m. UTC | #4
Mathias Nyman wrote:
> On 28.4.2021 1.30, Thinh Nguyen wrote:
>>> On 10.4.2021 3.47, Thinh Nguyen wrote:
>>>> If there is a device with active enhanced super-speed (eSS) isoc IN
>>>> endpoint(s) behind one or more eSS hubs, DWC_usb31 (v1.90a and prior)
>>>> host controller will not detect the device disconnection until no more
>>>> isoc URB is submitted. If there's a device disconnection, internally
>>>> the wait for tHostTransactionTimeout (USB 3.2 spec 8.13) blocks the
>>>> other endpoints from being scheduled. So, it blocks the interrupt
>>>> endpoint of the eSS hub indicating the port change status.
>>>>
>>>> This can be an issue for applications that continuously submitting isoc
>>>> URBs to the xHCI. To work around this, stop processing new URBs after 3
>>>> consecutive isoc transaction errors. If new isoc transfers are queued
>>>> after the device is disconnected, the host will respond with USB
>>>> transaction error. After 3 consecutive USB transaction errors, the
>>>> driver can wait a period of time (at least 2 * largest periodic interval
>>>> of the topology) without ringing isoc endpoint doorbell to detect the
>>>> port change status. If there is no disconnection detected, ring the
>>>> endpoint doorbell to resume isoc transfers.
>>>
>>> Is that enough? many Isoc URBs queue 16 - 32 Isoc TRBs per URB.
>>> And drivers like UVC queue several URBs in advance.
>>
>> That's fine as long as the driver stops ringing more doorbell for a
>> certain period of time creating a gap that's enough to get the
>> notification from the interrupt endpoint. We tested with 128 isoc URBs
>> and was able to detect a disconnect after this delay.
> 
> Ok, if not ringing the endpoint is enough then that is better than
> stopping the whole endpoint. 
> 
>>>> This workaround tracks the max eSS periodic interval every time there's
>>>> an endpoint added or dropped, which happens when there's bandwidth
>>>> check. So, scan the topology and update the xhci->max_ess_interval
>>>> whenever there's a bandwidth check. Introduced a new flag
>>>> VDEV_DISCONN_CHECK_PENDING to prevent ringing the doorbell while waiting
>>>> for a disconnection status. After 2 * max_ess_interval time and no
>>>> disconnection detected, a delayed work will ring the doorbell to resume
>>>> the active isoc transfers.
>>>
>>> Sounds very elaborate for a vendor specific disconnect workaround.
>>> Isn't there a simpler way?
>>>
>>> Maybe stop all isoc in endpoints if one them has 3 consecutive transaction error,
>>> wait for 2x hub interrupt interval time, and then restart the endpoints if there is
>>> no disconnect?
>>
>> We can also do this (but without stop + restart the endpoints). It just
>> creates a slightly larger gap that may be more noticeable to the user if
>> there's no actual disconnection.
> 
> Ok, if blocking the doorbell is enough then it sounds better.
> 
> How about that max interval tracking, is it necessary?
> It will block the doorbell from 250us up to several seconds.
> Is there some reasonable single value that can be used instead?

Yeah, I agree with you. I think we can find a number that satisfies with
most applications while keeping this change small and less intrusive.

> 
>>>
>>> There is bigger concern with this series, it scatters a lot of vendor specific code 
>>> around the generic xhci driver. It's not very clear afterwards what code is part of the
>>> workaround and what is generic code.
>>>
>>> We just got a lot of the Mediatek code moved to xhci-mtk*, maybe its time to add xhci-snps.c
>>> instead of using the generic platform driver with tons of workarounds and quirks.
>>>
>>
>> Thanks for the reviews. I need to look into how this can be done. May
>> need your suggestion as not every scenarios can be overridden
>> easily/cleanly.
> 
> true, no easy overrides for this transfer error / doorbell blocking workaround.
> Needs a bit of work
> 

Thanks,
Thinh
diff mbox series

Patch

diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index f66815fe8482..1053b43008e4 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -995,6 +995,8 @@  int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id,
 	xhci_dbg(xhci, "Slot %d input ctx = 0x%llx (dma)\n", slot_id,
 			(unsigned long long)dev->in_ctx->dma);
 
+	INIT_DELAYED_WORK(&dev->resume_isoc, xhci_resume_isoc);
+
 	/* Initialize the cancellation list and watchdog timers for each ep */
 	for (i = 0; i < 31; i++) {
 		dev->eps[i].ep_index = i;
@@ -1010,6 +1012,7 @@  int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id,
 		goto fail;
 
 	dev->udev = udev;
+	dev->xhci = xhci;
 
 	/* Point to output device context in dcbaa. */
 	xhci->dcbaa->dev_context_ptrs[slot_id] = cpu_to_le64(dev->out_ctx->dma);
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 05c38dd3ee36..a434a4b3609f 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -419,6 +419,14 @@  void xhci_ring_ep_doorbell(struct xhci_hcd *xhci,
 	struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index];
 	unsigned int ep_state = ep->ep_state;
 
+	/*
+	 * Don't ring the doorbell for isoc IN endpoint while checking for
+	 * device disconnection.
+	 */
+	if (ep->ring && ep->ring->type == TYPE_ISOC && !(ep_index % 2) &&
+	    (xhci->devs[slot_id]->flags & VDEV_DISCONN_CHECK_PENDING))
+		return;
+
 	/* Don't ring the doorbell for this endpoint if there are pending
 	 * cancellations because we don't want to interrupt processing.
 	 * We don't want to restart any stream rings if there's a set dequeue
@@ -2330,6 +2338,8 @@  static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
 		struct xhci_ring *ep_ring, struct xhci_td *td,
 		union xhci_trb *ep_trb, struct xhci_transfer_event *event)
 {
+	struct usb_device *udev;
+	struct xhci_virt_device *vdev;
 	struct urb_priv *urb_priv;
 	int idx;
 	struct usb_iso_packet_descriptor *frame;
@@ -2347,10 +2357,13 @@  static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
 	ep_trb_len = TRB_LEN(le32_to_cpu(ep_trb->generic.field[2]));
 	short_framestatus = td->urb->transfer_flags & URB_SHORT_NOT_OK ?
 		-EREMOTEIO : 0;
+	udev = td->urb->dev;
+	vdev = xhci->devs[udev->slot_id];
 
 	/* handle completion code */
 	switch (trb_comp_code) {
 	case COMP_SUCCESS:
+		ep->ring->err_count = 0;
 		if (remaining) {
 			frame->status = short_framestatus;
 			if (xhci->quirks & XHCI_TRUST_TX_LENGTH)
@@ -2375,6 +2388,23 @@  static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
 		frame->status = -EPROTO;
 		break;
 	case COMP_USB_TRANSACTION_ERROR:
+		if ((xhci->quirks & XHCI_ISOC_BLOCKED_DISCONNECT) &&
+		    usb_urb_dir_in(td->urb) &&
+		    udev->parent && udev->parent->parent &&
+		    udev->speed >= USB_SPEED_SUPER) {
+			if (!(vdev->flags & VDEV_DISCONN_CHECK_PENDING) &&
+			    ep->ring->err_count++ >= 3) {
+				unsigned long timeout;
+
+				/* Wait for at least max interval x 2 x 125us */
+				timeout = (1 << xhci->max_ess_interval) * 250;
+				vdev->flags |= VDEV_DISCONN_CHECK_PENDING;
+				queue_delayed_work(system_wq,
+						   &vdev->resume_isoc,
+						   usecs_to_jiffies(timeout));
+			}
+		}
+
 		frame->status = -EPROTO;
 		if (ep_trb != td->last_trb)
 			return 0;
@@ -4171,6 +4201,9 @@  int xhci_queue_isoc_tx_prepare(struct xhci_hcd *xhci, gfp_t mem_flags,
 	for (i = 0; i < num_tds; i++)
 		num_trbs += count_isoc_trbs_needed(urb, i);
 
+	if ((xdev->flags & VDEV_DISCONN_CHECK_PENDING) && usb_urb_dir_in(urb))
+		return -EINVAL;
+
 	/* Check the ring to guarantee there is enough room for the whole urb.
 	 * Do not insert any td of the urb to the ring if the check failed.
 	 */
@@ -4359,3 +4392,46 @@  int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd,
 	return queue_command(xhci, cmd, 0, 0, 0,
 			trb_slot_id | trb_ep_index | type, false);
 }
+
+void xhci_resume_isoc(struct work_struct *work)
+{
+	struct xhci_hcd *xhci;
+	struct xhci_virt_device *vdev;
+	unsigned int slot_id;
+	unsigned long flags;
+
+	vdev = container_of(to_delayed_work(work),
+			    struct xhci_virt_device, resume_isoc);
+	xhci = vdev->xhci;
+
+	spin_lock_irqsave(&xhci->lock, flags);
+
+	/* Check if the device is dropped before this work takes place */
+	if (!vdev->udev)
+		goto out;
+
+	slot_id = vdev->udev->slot_id;
+
+	vdev->flags &= ~VDEV_DISCONN_CHECK_PENDING;
+
+	/* Resume isoc transfers if the device is still connected */
+	if (xhci->devs[slot_id]) {
+		int i;
+
+		/* Ring doorbell for IN isoc endpoints only */
+		for (i = 2; i < 31; i += 2) {
+			struct xhci_virt_ep *ep = &vdev->eps[i];
+
+			if (!ep)
+				break;
+
+			if (ep->ring && ep->ring->type == TYPE_ISOC) {
+				ep->ring->err_count = 0;
+				ring_doorbell_for_active_rings(xhci, slot_id, i);
+			}
+		}
+	}
+
+out:
+	spin_unlock_irqrestore(&xhci->lock, flags);
+}
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index ca9385d22f68..e1136a6b9372 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -2973,6 +2973,44 @@  static void xhci_check_bw_drop_ep_streams(struct xhci_hcd *xhci,
 	}
 }
 
+static void xhci_update_ess_max_interval(struct xhci_hcd *xhci)
+{
+	unsigned int max_ess_interval = 0;
+	int j;
+
+	for (j = 1; j < HCS_MAX_SLOTS(xhci->hcs_params1); j++) {
+		struct xhci_virt_device	*virt_dev;
+		int i;
+
+		virt_dev = xhci->devs[j];
+		if (!virt_dev)
+			continue;
+
+		/* Only update for eSS devices */
+		if (virt_dev->udev &&
+		    virt_dev->udev->speed < USB_SPEED_SUPER)
+			continue;
+
+		for (i = 0; i < 31; i++) {
+			struct xhci_ep_ctx *ep_ctx;
+			unsigned int ep_type;
+			unsigned int interval;
+
+			ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->out_ctx, i);
+			ep_type = CTX_TO_EP_TYPE(le32_to_cpu(ep_ctx->ep_info2));
+
+			if (xhci_is_async_ep(ep_type))
+				continue;
+
+			interval = CTX_TO_EP_INTERVAL(le32_to_cpu(ep_ctx->ep_info));
+			if (interval > max_ess_interval)
+				max_ess_interval = interval;
+		}
+	}
+
+	xhci->max_ess_interval = max_ess_interval;
+}
+
 /* Called after one or more calls to xhci_add_endpoint() or
  * xhci_drop_endpoint().  If this call fails, the USB core is expected
  * to call xhci_reset_bandwidth().
@@ -3047,6 +3085,17 @@  int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev)
 		/* Callee should call reset_bandwidth() */
 		goto command_cleanup;
 
+	if (xhci->quirks & XHCI_ISOC_BLOCKED_DISCONNECT) {
+		xhci_update_ess_max_interval(xhci);
+
+		/* Cancel disconnection check on change of context */
+		if (delayed_work_pending(&virt_dev->resume_isoc) &&
+		    ctrl_ctx->drop_flags) {
+			cancel_delayed_work(&virt_dev->resume_isoc);
+			virt_dev->flags &= ~VDEV_DISCONN_CHECK_PENDING;
+		}
+	}
+
 	/* Free any rings that were dropped, but not changed. */
 	for (i = 1; i < 31; i++) {
 		if ((le32_to_cpu(ctrl_ctx->drop_flags) & (1 << (i + 1))) &&
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 9a4e2808668b..27d2c1176dd1 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1000,6 +1000,7 @@  struct xhci_interval_bw_table {
 
 struct xhci_virt_device {
 	int				slot_id;
+	struct xhci_hcd			*xhci;
 	struct usb_device		*udev;
 	/*
 	 * Commands to the hardware are passed an "input context" that
@@ -1025,11 +1026,15 @@  struct xhci_virt_device {
 	 */
 	unsigned long			flags;
 #define VDEV_PORT_ERROR			BIT(0) /* Port error, link inactive */
+#define VDEV_DISCONN_CHECK_PENDING	BIT(1) /* Disconnection check */
 
 	/* The current max exit latency for the enabled USB3 link states. */
 	u16				current_mel;
 	/* Used for the debugfs interfaces. */
 	void				*debugfs_private;
+
+	/* For undetected disconnection quirk */
+	struct delayed_work		resume_isoc;
 };
 
 /*
@@ -1864,6 +1869,9 @@  struct xhci_hcd {
 /* Compliance Mode Timer Triggered every 2 seconds */
 #define COMP_MODE_RCVRY_MSECS 2000
 
+	/* Track max eSS interval for XHCI_ISOC_BLOCKED_DISCONNECT */
+	unsigned int		max_ess_interval;
+
 	struct dentry		*debugfs_root;
 	struct dentry		*debugfs_slots;
 	struct list_head	regset_list;
@@ -1948,6 +1956,8 @@  char *xhci_get_slot_state(struct xhci_hcd *xhci,
 void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *),
 			const char *fmt, ...);
 
+void xhci_resume_isoc(struct work_struct *work);
+
 /* xHCI memory management */
 void xhci_mem_cleanup(struct xhci_hcd *xhci);
 int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags);
diff --git a/include/linux/usb/xhci-quirks.h b/include/linux/usb/xhci-quirks.h
index 4001b10b418a..65bb62d3d31d 100644
--- a/include/linux/usb/xhci-quirks.h
+++ b/include/linux/usb/xhci-quirks.h
@@ -58,5 +58,6 @@ 
 #define XHCI_DISABLE_SPARSE		BIT_ULL(38)
 #define XHCI_SG_TRB_CACHE_SIZE_QUIRK	BIT_ULL(39)
 #define XHCI_NO_SOFT_RETRY		BIT_ULL(40)
+#define XHCI_ISOC_BLOCKED_DISCONNECT	BIT_ULL(41)
 
 #endif /* __LINUX_USB_XHCI_QUIRKS_H */