aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Thompson <daniel.thompson@linaro.org>2014-09-03 16:44:58 +0100
committerDaniel Thompson <daniel.thompson@linaro.org>2014-10-20 13:26:53 +0100
commit4a63e8f238c88e3bb0a23f658b68a2eb682237f6 (patch)
tree22062577e93df22225b6ec7fd16a00ebe09da8aa
parent305187021b7837c6a148bc1f30ddb611f9dc1099 (diff)
irqchip: gic: Group 0 workaround.
An ARM system based on GICv1 that runs by default in secure mode and uses both group 0 and group 1 interrupts (in order to exploit FIQ) will suffer a problem where the IRQ handler occasionally spuriously acknowledges a group 0 (FIQ) interrupt. This can be prevented by ensuring the IRQ handler makes non-secure memory access to the GIC registers but this is complex because the non-secure bits cannot be apply to 4k pages (the bit is one level up in the page table and applies to 1MB at a time). This workaround uses an alternative approach that spots the spurious acknowledgment and regenerates the FIQ. This keeps the workaround exclusively within the GIC driver (although there is a runtime perforamnce penalty resulting from this approach). Reported-by: Harro Haan <hrhaan@gmail.com> Signed-off-by: Daniel Thompson <daniel.thompson@linaro.org> Tested-by: Harro Haan <hrhaan@gmail.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Jason Cooper <jason@lakedaemon.net>
-rw-r--r--drivers/irqchip/irq-gic.c52
1 files changed, 49 insertions, 3 deletions
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c
index 908e36dce938..0fc6f341ef89 100644
--- a/drivers/irqchip/irq-gic.c
+++ b/drivers/irqchip/irq-gic.c
@@ -267,14 +267,59 @@ static int gic_set_wake(struct irq_data *d, unsigned int on)
#define gic_set_wake NULL
#endif
+#ifdef CONFIG_FIQ
+/* This is a software emulation of the Aliased Interrupt Acknowledge Register
+ * (GIC_AIAR) found in GICv2+.
+ */
+static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat)
+{
+ u32 irqnr = irqstat & GICC_IAR_INT_ID_MASK;
+ void __iomem *dist_base = gic_data_dist_base(gic);
+ u32 offset, mask;
+
+ if (!readl_relaxed(dist_base + GIC_DIST_IGROUP + 0) || irqnr >= 1021)
+ return irqstat;
+
+ offset = irqnr / 32 * 4;
+ mask = 1 << (irqnr % 32);
+ if (readl_relaxed(dist_base + GIC_DIST_IGROUP + offset) & mask)
+ return irqstat;
+
+ /* this interrupt must be taken as a FIQ so put it back into the
+ * pending state and end our own servicing of it.
+ */
+ writel_relaxed(mask, dist_base + GIC_DIST_PENDING_SET + offset);
+ readl_relaxed(dist_base + GIC_DIST_PENDING_SET + offset);
+ writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI);
+
+ return 1023;
+}
+
+static u32 gic_ack_irq(struct gic_chip_data *gic)
+{
+ u32 irqstat;
+
+ local_fiq_disable();
+ irqstat = readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK);
+ irqstat = gic_handle_spurious_group_0(gic, irqstat);
+ local_fiq_enable();
+
+ return irqstat;
+}
+#else
+static u32 gic_ack_irq(struct gic_chip_data *gic)
+{
+ return readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK);
+}
+#endif
+
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
- void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
- irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
+ irqstat = gic_ack_irq(gic);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (likely(irqnr > 15 && irqnr < 1021)) {
@@ -282,7 +327,8 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
continue;
}
if (irqnr < 16) {
- writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
+ writel_relaxed(irqstat,
+ gic_data_cpu_base(gic) + GIC_CPU_EOI);
#ifdef CONFIG_SMP
handle_IPI(irqnr, regs);
#endif