diff options
author | Jeremy Kerr <jk@ozlabs.org> | 2008-04-04 17:55:28 +1100 |
---|---|---|
committer | Jeremy Kerr <jk@ozlabs.org> | 2008-05-05 13:33:42 +1000 |
commit | 5711fe900dfef8d9afdbbb6d0f9c9720919d1d66 (patch) | |
tree | 6d9281bf3313488b20c30ad69bd794322b8f91e5 | |
parent | 3b5750644b2ffa2a76fdfe7b4e00e4af2ecf3539 (diff) |
[POWERPC] cell: Fix lost interrupts due to fasteoi handler
We may currently lose interrupts during SPE context switch, as we alter
the INT_Route register. Because the IIC uses a per-thread priority
status, changing the interrupt routing to a different thread means that
the IRQ is no longer masked by the priority status, so we end up with
two fasteoi IRQ handlers executing for the one irq_desc. The fasteoi
handler doesn't handle multiple IRQs, so drops the second one.
Fix this by using our own flow handler. This is based on
handle_edge_irq, but issues an eoi after IRQs are handled, and doesn't
do any mask/unmasking.
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
-rw-r--r-- | arch/powerpc/platforms/cell/interrupt.c | 53 |
1 files changed, 51 insertions, 2 deletions
diff --git a/arch/powerpc/platforms/cell/interrupt.c b/arch/powerpc/platforms/cell/interrupt.c index 04f74f9f9ab..5bf7df14602 100644 --- a/arch/powerpc/platforms/cell/interrupt.c +++ b/arch/powerpc/platforms/cell/interrupt.c @@ -35,6 +35,7 @@ #include <linux/percpu.h> #include <linux/types.h> #include <linux/ioport.h> +#include <linux/kernel_stat.h> #include <asm/io.h> #include <asm/pgtable.h> @@ -231,6 +232,54 @@ static int iic_host_match(struct irq_host *h, struct device_node *node) "IBM,CBEA-Internal-Interrupt-Controller"); } +extern int noirqdebug; + +static void handle_iic_irq(unsigned int irq, struct irq_desc *desc) +{ + const unsigned int cpu = smp_processor_id(); + + spin_lock(&desc->lock); + + desc->status &= ~(IRQ_REPLAY | IRQ_WAITING); + + /* + * If we're currently running this IRQ, or its disabled, + * we shouldn't process the IRQ. Mark it pending, handle + * the necessary masking and go out + */ + if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || + !desc->action)) { + desc->status |= IRQ_PENDING; + goto out_eoi; + } + + kstat_cpu(cpu).irqs[irq]++; + + /* Mark the IRQ currently in progress.*/ + desc->status |= IRQ_INPROGRESS; + + do { + struct irqaction *action = desc->action; + irqreturn_t action_ret; + + if (unlikely(!action)) + goto out_eoi; + + desc->status &= ~IRQ_PENDING; + spin_unlock(&desc->lock); + action_ret = handle_IRQ_event(irq, action); + if (!noirqdebug) + note_interrupt(irq, desc, action_ret); + spin_lock(&desc->lock); + + } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING); + + desc->status &= ~IRQ_INPROGRESS; +out_eoi: + desc->chip->eoi(irq); + spin_unlock(&desc->lock); +} + static int iic_host_map(struct irq_host *h, unsigned int virq, irq_hw_number_t hw) { @@ -240,10 +289,10 @@ static int iic_host_map(struct irq_host *h, unsigned int virq, break; case IIC_IRQ_TYPE_IOEXC: set_irq_chip_and_handler(virq, &iic_ioexc_chip, - handle_fasteoi_irq); + handle_iic_irq); break; default: - set_irq_chip_and_handler(virq, &iic_chip, handle_fasteoi_irq); + set_irq_chip_and_handler(virq, &iic_chip, handle_iic_irq); } return 0; } |