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>
This commit is contained in:
Arnd Bergmann 2008-11-28 09:51:22 +00:00 коммит произвёл Paul Mackerras
Родитель 4a6186696e
Коммит d015fe9951
1 изменённых файлов: 31 добавлений и 5 удалений

Просмотреть файл

@ -95,6 +95,7 @@ static void axon_msi_cascade(unsigned int irq, struct irq_desc *desc)
struct axon_msic *msic = get_irq_data(irq); struct axon_msic *msic = get_irq_data(irq);
u32 write_offset, msi; u32 write_offset, msi;
int idx; int idx;
int retry = 0;
write_offset = dcr_read(msic->dcr_host, MSIC_WRITE_OFFSET_REG); write_offset = dcr_read(msic->dcr_host, MSIC_WRITE_OFFSET_REG);
pr_debug("axon_msi: original write_offset 0x%x\n", write_offset); pr_debug("axon_msi: original write_offset 0x%x\n", write_offset);
@ -102,7 +103,7 @@ static void axon_msi_cascade(unsigned int irq, struct irq_desc *desc)
/* write_offset doesn't wrap properly, so we have to mask it */ /* write_offset doesn't wrap properly, so we have to mask it */
write_offset &= MSIC_FIFO_SIZE_MASK; write_offset &= MSIC_FIFO_SIZE_MASK;
while (msic->read_offset != write_offset) { while (msic->read_offset != write_offset && retry < 100) {
idx = msic->read_offset / sizeof(__le32); idx = msic->read_offset / sizeof(__le32);
msi = le32_to_cpu(msic->fifo_virt[idx]); msi = le32_to_cpu(msic->fifo_virt[idx]);
msi &= 0xFFFF; msi &= 0xFFFF;
@ -110,13 +111,37 @@ static void axon_msi_cascade(unsigned int irq, struct irq_desc *desc)
pr_debug("axon_msi: woff %x roff %x msi %x\n", pr_debug("axon_msi: woff %x roff %x msi %x\n",
write_offset, msic->read_offset, msi); write_offset, msic->read_offset, msi);
if (msi < NR_IRQS && irq_map[msi].host == msic->irq_host) {
generic_handle_irq(msi);
msic->fifo_virt[idx] = cpu_to_le32(0xffffffff);
} else {
/*
* Reading the MSIC_WRITE_OFFSET_REG does not
* reliably flush the outstanding DMA to the
* FIFO buffer. Here we were reading stale
* data, so we need to retry.
*/
udelay(1);
retry++;
pr_debug("axon_msi: invalid irq 0x%x!\n", msi);
continue;
}
if (retry) {
pr_debug("axon_msi: late irq 0x%x, retry %d\n",
msi, retry);
retry = 0;
}
msic->read_offset += MSIC_FIFO_ENTRY_SIZE; msic->read_offset += MSIC_FIFO_ENTRY_SIZE;
msic->read_offset &= MSIC_FIFO_SIZE_MASK; msic->read_offset &= MSIC_FIFO_SIZE_MASK;
}
if (msi < NR_IRQS && irq_map[msi].host == msic->irq_host) if (retry) {
generic_handle_irq(msi); printk(KERN_WARNING "axon_msi: irq timed out\n");
else
pr_debug("axon_msi: invalid irq 0x%x!\n", msi); msic->read_offset += MSIC_FIFO_ENTRY_SIZE;
msic->read_offset &= MSIC_FIFO_SIZE_MASK;
} }
desc->chip->eoi(irq); desc->chip->eoi(irq);
@ -364,6 +389,7 @@ static int axon_msi_probe(struct of_device *device,
dn->full_name); dn->full_name);
goto out_free_fifo; goto out_free_fifo;
} }
memset(msic->fifo_virt, 0xff, MSIC_FIFO_SIZE_BYTES);
msic->irq_host = irq_alloc_host(dn, IRQ_HOST_MAP_NOMAP, msic->irq_host = irq_alloc_host(dn, IRQ_HOST_MAP_NOMAP,
NR_IRQS, &msic_host_ops, 0); NR_IRQS, &msic_host_ops, 0);