powerpc/cell/axon-msi: Retry on missing interrupt

The MSI capture logic for the axon bridge can sometimes
lose interrupts in case of high DMA and interrupt load,
when it signals an MSI interrupt to the MPIC interrupt
controller while we are already handling another MSI.

Each MSI vector gets written into a FIFO buffer in main
memory using DMA, and that DMA access is normally flushed
by the actual interrupt packet on the IOIF. An MMIO
register in the MSIC holds the position of the last
entry in the FIFO buffer that was written. However,
reading that position does not flush the DMA, so that
we can observe stale data in the buffer.

In a stress test, we have observed the DMA to arrive
up to 14 microseconds after reading the register.

This patch works around this problem by retrying the
access to the FIFO buffer.

We can reliably detect the conditioning by writing
an invalid MSI vector into the FIFO buffer after
reading from it, assuming that all MSIs we get
are valid. After detecting an invalid MSI vector,
we udelay(1) in the interrupt cascade for up to
100 times before giving up.

Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Paul Mackerras <paulus@samba.org>

authored by Arnd Bergmann and committed by Paul Mackerras d015fe99 4a618669

+31 -5
+31 -5
arch/powerpc/platforms/cell/axon_msi.c
··· 95 struct axon_msic *msic = get_irq_data(irq); 96 u32 write_offset, msi; 97 int idx; 98 99 write_offset = dcr_read(msic->dcr_host, MSIC_WRITE_OFFSET_REG); 100 pr_debug("axon_msi: original write_offset 0x%x\n", write_offset); ··· 103 /* write_offset doesn't wrap properly, so we have to mask it */ 104 write_offset &= MSIC_FIFO_SIZE_MASK; 105 106 - while (msic->read_offset != write_offset) { 107 idx = msic->read_offset / sizeof(__le32); 108 msi = le32_to_cpu(msic->fifo_virt[idx]); 109 msi &= 0xFFFF; ··· 111 pr_debug("axon_msi: woff %x roff %x msi %x\n", 112 write_offset, msic->read_offset, msi); 113 114 msic->read_offset += MSIC_FIFO_ENTRY_SIZE; 115 msic->read_offset &= MSIC_FIFO_SIZE_MASK; 116 117 - if (msi < NR_IRQS && irq_map[msi].host == msic->irq_host) 118 - generic_handle_irq(msi); 119 - else 120 - pr_debug("axon_msi: invalid irq 0x%x!\n", msi); 121 } 122 123 desc->chip->eoi(irq); ··· 389 dn->full_name); 390 goto out_free_fifo; 391 } 392 393 msic->irq_host = irq_alloc_host(dn, IRQ_HOST_MAP_NOMAP, 394 NR_IRQS, &msic_host_ops, 0);
··· 95 struct axon_msic *msic = get_irq_data(irq); 96 u32 write_offset, msi; 97 int idx; 98 + int retry = 0; 99 100 write_offset = dcr_read(msic->dcr_host, MSIC_WRITE_OFFSET_REG); 101 pr_debug("axon_msi: original write_offset 0x%x\n", write_offset); ··· 102 /* write_offset doesn't wrap properly, so we have to mask it */ 103 write_offset &= MSIC_FIFO_SIZE_MASK; 104 105 + while (msic->read_offset != write_offset && retry < 100) { 106 idx = msic->read_offset / sizeof(__le32); 107 msi = le32_to_cpu(msic->fifo_virt[idx]); 108 msi &= 0xFFFF; ··· 110 pr_debug("axon_msi: woff %x roff %x msi %x\n", 111 write_offset, msic->read_offset, msi); 112 113 + if (msi < NR_IRQS && irq_map[msi].host == msic->irq_host) { 114 + generic_handle_irq(msi); 115 + msic->fifo_virt[idx] = cpu_to_le32(0xffffffff); 116 + } else { 117 + /* 118 + * Reading the MSIC_WRITE_OFFSET_REG does not 119 + * reliably flush the outstanding DMA to the 120 + * FIFO buffer. Here we were reading stale 121 + * data, so we need to retry. 122 + */ 123 + udelay(1); 124 + retry++; 125 + pr_debug("axon_msi: invalid irq 0x%x!\n", msi); 126 + continue; 127 + } 128 + 129 + if (retry) { 130 + pr_debug("axon_msi: late irq 0x%x, retry %d\n", 131 + msi, retry); 132 + retry = 0; 133 + } 134 + 135 msic->read_offset += MSIC_FIFO_ENTRY_SIZE; 136 msic->read_offset &= MSIC_FIFO_SIZE_MASK; 137 + } 138 139 + if (retry) { 140 + printk(KERN_WARNING "axon_msi: irq timed out\n"); 141 + 142 + msic->read_offset += MSIC_FIFO_ENTRY_SIZE; 143 + msic->read_offset &= MSIC_FIFO_SIZE_MASK; 144 } 145 146 desc->chip->eoi(irq); ··· 364 dn->full_name); 365 goto out_free_fifo; 366 } 367 + memset(msic->fifo_virt, 0xff, MSIC_FIFO_SIZE_BYTES); 368 369 msic->irq_host = irq_alloc_host(dn, IRQ_HOST_MAP_NOMAP, 370 NR_IRQS, &msic_host_ops, 0);