aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorDaniel Thompson <daniel.thompson@linaro.org>2016-08-19 15:53:40 +0100
committerDaniel Thompson <daniel.thompson@linaro.org>2017-03-30 17:55:54 +0100
commit822c6fa8448f5b54ef1c2bfa37025b27ea0df88f (patch)
treea6b404943f0692163e7c1446f2539d5efe66161a /drivers
parentafd1ebd73afe91475d4e0914ae55711fbfa29e7a (diff)
arm64: Implement IPI_CPU_BACKTRACE using pseudo-NMIs
Recently arm64 gained the capability to (optionally) mask interrupts using the GIC PMR rather than the CPU PSR. That allows us to introduce an NMI-like means to handle backtrace requests. This provides a useful debug aid by allowing the kernel to robustly show a backtrace for every processor in the system when, for example, we hang trying to acquire a spin lock. Signed-off-by: Daniel Thompson <daniel.thompson@linaro.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/irqchip/irq-gic-v3.c62
1 files changed, 62 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c
index 36ec89a438e2..39232014d200 100644
--- a/drivers/irqchip/irq-gic-v3.c
+++ b/drivers/irqchip/irq-gic-v3.c
@@ -23,6 +23,7 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
+#include <linux/nmi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
@@ -341,10 +342,60 @@ static u64 gic_mpidr_to_affinity(unsigned long mpidr)
return aff;
}
+#ifdef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
+static bool gic_handle_nmi(struct pt_regs *regs)
+{
+ u64 irqnr;
+ struct pt_regs *old_regs;
+
+ asm volatile("mrs_s %0, " __stringify(ICC_IAR1_EL1) : "=r"(irqnr));
+
+ /*
+ * If no IRQ is acknowledged at this point then we have entered the
+ * handler due to an normal interrupt (rather than a pseudo-NMI).
+ * If so then unmask the I-bit and return to normal handling.
+ */
+ if (irqnr == ICC_IAR1_EL1_SPURIOUS) {
+ asm volatile("msr daifclr, #2" : : : "memory");
+ return false;
+ }
+
+ old_regs = set_irq_regs(regs);
+ nmi_enter();
+
+ do {
+ if (SMP_IPI_NMI_MASK & (1 << irqnr)) {
+ gic_write_eoir(irqnr);
+ if (static_key_true(&supports_deactivate))
+ gic_write_dir(irqnr);
+ nmi_cpu_backtrace(regs);
+ } else if (unlikely(irqnr != ICC_IAR1_EL1_SPURIOUS)) {
+ gic_write_eoir(irqnr);
+ if (static_key_true(&supports_deactivate))
+ gic_write_dir(irqnr);
+ WARN_ONCE(true, "Unexpected NMI received!\n");
+ }
+
+ asm volatile("mrs_s %0, " __stringify(ICC_IAR1_EL1)
+ : "=r"(irqnr));
+ } while (irqnr != ICC_IAR1_EL1_SPURIOUS);
+
+ nmi_exit();
+ set_irq_regs(old_regs);
+
+ return true;
+}
+#else
+static bool gic_handle_nmi(struct pt_regs *regs) { return false; }
+#endif
+
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqnr;
+ if (gic_handle_nmi(regs))
+ return;
+
do {
irqnr = gic_read_iar();
@@ -524,6 +575,7 @@ static int gic_dist_supports_lpis(void)
static void gic_cpu_init(void)
{
void __iomem *rbase;
+ unsigned long __maybe_unused nmimask, hwirq;
/* Register ourselves with the rest of the world */
if (gic_populate_rdist())
@@ -544,6 +596,16 @@ static void gic_cpu_init(void)
/* initialise system registers */
gic_cpu_sys_reg_init();
+
+#ifdef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
+ /* Boost the priority of any IPI in the mask */
+ nmimask = SMP_IPI_NMI_MASK;
+ for_each_set_bit(hwirq, &nmimask, 16)
+ writeb_relaxed(GICD_INT_DEF_PRI ^ BIT(7),
+ rbase + GIC_DIST_PRI + hwirq);
+ gic_dist_wait_for_rwp();
+ gic_redist_wait_for_rwp();
+#endif
}
#ifdef CONFIG_SMP