For Intel host controllers an interrupt is raised for every isochronous packet, because of the "AVOID_BEI" quirk. The reasoning for this is found in the following commit: commit 227a4fd801c8a9fa2c4700ab98ec1aec06e3b44d Author: Lu Baolu Date: Mon Mar 23 18:27:42 2015 +0200 usb: xhci: apply XHCI_AVOID_BEI quirk to all Intel xHCI controllers When a device with an isochronous endpoint is plugged into the Intel xHCI host controller, and the driver submits multiple frames per URB, the xHCI driver will set the Block Event Interrupt (BEI) flag on all but the last TD for the URB. This causes the host controller to place an event on the event ring, but not send an interrupt. When the last TD for the URB completes, BEI is cleared, and we get an interrupt for the whole URB. However, under Intel xHCI host controllers, if the event ring is full of events from transfers with BEI set, an "Event Ring is Full" event will be posted to the last entry of the event ring, but no interrupt is generated. Host will cease all transfer and command executions and wait until software completes handling the pending events in the event ring. That means xHC stops, but event of "event ring is full" is not notified. As the result, the xHC looks like dead to user. This patch is to apply XHCI_AVOID_BEI quirk to Intel xHC devices. And it should be backported to kernels as old as 3.0, that contains the commit 69e848c2090a ("Intel xhci: Support EHCI/xHCI port switching."). Signed-off-by: Lu Baolu Tested-by: Alistair Grant Cc: stable@vger.kernel.org Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman Because this behavior up to 8000 interrupts/s can be raised by the host controller during isochronous transfer. Since 2020 there exists a patch that relaxes this burden and which we will apply. Author: Mathias Nyman Date: Fr Sep 18 2020 13:17:24 2020 +0200 xhci: Tune interrupt blocking for isochronous transfers controllers with XHCI_AVOID_BEI quirk cause too frequent interrupts and affect power management. To avoid interrupting on every isochronous interval the BEI (Block Event Interrupt) flag is set for all except the last Isoch TRB in a URB. This lead to event ring filling up in case several isoc URB were queued and cancelled rapidly, which some controllers didn't handle well, and thus the XHCI_AVOID_BEI quirk was introduced. see commit 227a4fd801c8 ("usb: xhci: apply XHCI_AVOID_BEI quirk to all Intel xHCI controllers") With the XHCI_AVOID_BEI quirk each Isoch TRB will trigger an interrupt. This can cause up to 8000 interrupts per second for isochronous transfers with HD USB3 cameras, affecting power saving. The event ring fits 256 events, instead of interrupting on every isochronous TRB if XHCI_AVOID_BEI is set we make sure at least every 8th Isochronous TRB asserts an interrupt, clearing the event ring. Signed-off-by: Mathias Nyman diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 028891a..31f6bd3 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -3608,6 +3608,24 @@ static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci, return start_frame; } +/* Check if we should generate event interrupt for a TD in an isoc URB */ +static bool trb_block_event_intr(struct xhci_hcd *xhci, int num_tds, int i) +{ + if (xhci->hci_version < 0x100) + return false; + /* always generate an event interrupt for the last TD */ + if (i == num_tds - 1) + return false; + /* + * If AVOID_BEI is set the host handles full event rings poorly, + * generate an event at least every 8th TD to clear the event ring + */ + if (i && xhci->quirks & XHCI_AVOID_BEI) + return !!(i % 8); + + return true; +} + /* This is for isoc transfer */ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, struct urb *urb, int slot_id, unsigned int ep_index) @@ -3715,10 +3733,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags, more_trbs_coming = false; td->last_trb = ep_ring->enqueue; field |= TRB_IOC; - /* set BEI, except for the last TD */ - if (xhci->hci_version >= 0x100 && - !(xhci->quirks & XHCI_AVOID_BEI) && - i < num_tds - 1) + if (trb_block_event_intr(xhci, num_tds, i)) field |= TRB_BEI; } /* Calculate TRB length */