thunderbolt: Disable interrupt auto clear for rings
authorMario Limonciello <mario.limonciello@amd.com>
Fri, 10 Mar 2023 17:20:50 +0000 (11:20 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 30 Mar 2023 10:47:52 +0000 (12:47 +0200)
commit 468c49f44759720a312e52d44a71c3949ed63d7c upstream.

When interrupt auto clear is programmed, any read to the interrupt
status register will clear all interrupts.  If two interrupts have
come in before one can be serviced then this will cause lost interrupts.

On AMD USB4 routers this has manifested in odd problems particularly
with long strings of control tranfers such as reading the DROM via bit
banging.

Instead of clearing interrupts automatically, clear the bit corresponding
to the given ring's interrupt in the ISR.

Fixes: 7a1808f82a37 ("thunderbolt: Handle ring interrupt by reading interrupt status register")
Cc: Sanju Mehta <Sanju.Mehta@amd.com>
Cc: stable@vger.kernel.org
Tested-by: Anson Tsao <anson.tsao@amd.com>
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/thunderbolt/nhi.c
drivers/thunderbolt/nhi_regs.h

index aa6cf7f2f438fc3ef0264b4c6226d1a64f2162ef..b409c2c3b2c4387ce6f3a6dcc1d50e01540248fe 100644 (file)
@@ -68,24 +68,31 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active)
                u32 step, shift, ivr, misc;
                void __iomem *ivr_base;
                int index;
+               int bit;
 
                if (ring->is_tx)
                        index = ring->hop;
                else
                        index = ring->hop + ring->nhi->hop_count;
 
-               if (ring->nhi->quirks & QUIRK_AUTO_CLEAR_INT) {
-                       /*
-                        * Ask the hardware to clear interrupt status
-                        * bits automatically since we already know
-                        * which interrupt was triggered.
-                        */
-                       misc = ioread32(ring->nhi->iobase + REG_DMA_MISC);
-                       if (!(misc & REG_DMA_MISC_INT_AUTO_CLEAR)) {
-                               misc |= REG_DMA_MISC_INT_AUTO_CLEAR;
-                               iowrite32(misc, ring->nhi->iobase + REG_DMA_MISC);
-                       }
-               }
+               /*
+                * Intel routers support a bit that isn't part of
+                * the USB4 spec to ask the hardware to clear
+                * interrupt status bits automatically since
+                * we already know which interrupt was triggered.
+                *
+                * Other routers explicitly disable auto-clear
+                * to prevent conditions that may occur where two
+                * MSIX interrupts are simultaneously active and
+                * reading the register clears both of them.
+                */
+               misc = ioread32(ring->nhi->iobase + REG_DMA_MISC);
+               if (ring->nhi->quirks & QUIRK_AUTO_CLEAR_INT)
+                       bit = REG_DMA_MISC_INT_AUTO_CLEAR;
+               else
+                       bit = REG_DMA_MISC_DISABLE_AUTO_CLEAR;
+               if (!(misc & bit))
+                       iowrite32(misc | bit, ring->nhi->iobase + REG_DMA_MISC);
 
                ivr_base = ring->nhi->iobase + REG_INT_VEC_ALLOC_BASE;
                step = index / REG_INT_VEC_ALLOC_REGS * REG_INT_VEC_ALLOC_BITS;
@@ -390,14 +397,17 @@ EXPORT_SYMBOL_GPL(tb_ring_poll_complete);
 
 static void ring_clear_msix(const struct tb_ring *ring)
 {
+       int bit;
+
        if (ring->nhi->quirks & QUIRK_AUTO_CLEAR_INT)
                return;
 
+       bit = ring_interrupt_index(ring) & 31;
        if (ring->is_tx)
-               ioread32(ring->nhi->iobase + REG_RING_NOTIFY_BASE);
+               iowrite32(BIT(bit), ring->nhi->iobase + REG_RING_INT_CLEAR);
        else
-               ioread32(ring->nhi->iobase + REG_RING_NOTIFY_BASE +
-                        4 * (ring->nhi->hop_count / 32));
+               iowrite32(BIT(bit), ring->nhi->iobase + REG_RING_INT_CLEAR +
+                         4 * (ring->nhi->hop_count / 32));
 }
 
 static irqreturn_t ring_msix(int irq, void *data)
index 0d4970dcef842f7c8841ad933ef36940c8d5ed24..faef165a919ccda93a00fea018151210f68aec1b 100644 (file)
@@ -77,12 +77,13 @@ struct ring_desc {
 
 /*
  * three bitfields: tx, rx, rx overflow
- * Every bitfield contains one bit for every hop (REG_HOP_COUNT). Registers are
- * cleared on read. New interrupts are fired only after ALL registers have been
+ * Every bitfield contains one bit for every hop (REG_HOP_COUNT).
+ * New interrupts are fired only after ALL registers have been
  * read (even those containing only disabled rings).
  */
 #define REG_RING_NOTIFY_BASE   0x37800
 #define RING_NOTIFY_REG_COUNT(nhi) ((31 + 3 * nhi->hop_count) / 32)
+#define REG_RING_INT_CLEAR     0x37808
 
 /*
  * two bitfields: rx, tx
@@ -105,6 +106,7 @@ struct ring_desc {
 
 #define REG_DMA_MISC                   0x39864
 #define REG_DMA_MISC_INT_AUTO_CLEAR     BIT(2)
+#define REG_DMA_MISC_DISABLE_AUTO_CLEAR        BIT(17)
 
 #define REG_INMAIL_DATA                        0x39900