xhci: Add adaptive interrupt rate for isoch TRBs with XHCI_AVOID_BEI quirk
authorMathias Nyman <mathias.nyman@linux.intel.com>
Thu, 17 Jun 2021 15:03:52 +0000 (18:03 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 17 Jun 2021 15:34:29 +0000 (17:34 +0200)
Save a bit of power by not interrupting so often by default if
XHCI_AVOID_BEI quirk is set.

In normal cases the xhci driver will only generate an interrupt on the last
isochronous TRB of an URB. In a common UVC webcam usecase there are 32 TRBs
per URB.

if AVOID_BEI flag is set then xhci driver will force an interrupt every 8th
isoc TRB to make sure the event ring doesn't get too full.

This is however way too frequent in common single webcam use cases, causing
1000 interrupts/sec and thus poor powermanagement performance.

Instead start with interrupting every 32 isoc TRB, and halve it in case
event ring becomes half-full. Stop halving when reaching a rate of every
8th trb.

This is a one way solution. If interrupt rate is increased it will stay
high until driver is reloaded. The highest rate is the same as the old
default rate.

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20210617150354.1512157-3-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci-mem.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.h

index f66815f..2f6da35 100644 (file)
@@ -2547,6 +2547,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
        xhci_dbg_trace(xhci, trace_xhci_dbg_init,
                        "Wrote ERST address to ir_set 0.");
 
+       xhci->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX;
+
        /*
         * XXX: Might need to set the Interrupter Moderation Register to
         * something other than the default (~1ms minimum between interrupts).
index 6acd232..8fea44b 100644 (file)
@@ -3076,6 +3076,11 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
                if (event_loop++ < TRBS_PER_SEGMENT / 2)
                        continue;
                xhci_update_erst_dequeue(xhci, event_ring_deq);
+
+               /* ring is half-full, force isoc trbs to interrupt more often */
+               if (xhci->isoc_bei_interval > AVOID_BEI_INTERVAL_MIN)
+                       xhci->isoc_bei_interval = xhci->isoc_bei_interval / 2;
+
                event_loop = 0;
        }
 
@@ -3956,7 +3961,7 @@ static bool trb_block_event_intr(struct xhci_hcd *xhci, int num_tds, int i)
         * generate an event at least every 8th TD to clear the event ring
         */
        if (i && xhci->quirks & XHCI_AVOID_BEI)
-               return !!(i % 8);
+               return !!(i % xhci->isoc_bei_interval);
 
        return true;
 }
index 85ba326..5ba01d5 100644 (file)
@@ -1526,6 +1526,12 @@ static inline const char *xhci_trb_type_string(u8 type)
 #define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr)      (TRB_MAX_BUFF_SIZE - \
                                        (addr & (TRB_MAX_BUFF_SIZE - 1)))
 #define MAX_SOFT_RETRY         3
+/*
+ * Limits of consecutive isoc trbs that can Block Event Interrupt (BEI) if
+ * XHCI_AVOID_BEI quirk is in use.
+ */
+#define AVOID_BEI_INTERVAL_MIN 8
+#define AVOID_BEI_INTERVAL_MAX 32
 
 struct xhci_segment {
        union xhci_trb          *trbs;
@@ -1768,6 +1774,7 @@ struct xhci_hcd {
        u8              isoc_threshold;
        /* imod_interval in ns (I * 250ns) */
        u32             imod_interval;
+       u32             isoc_bei_interval;
        int             event_ring_max;
        /* 4KB min, 128MB max */
        int             page_size;