aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Thompson <daniel.thompson@linaro.org>2015-01-15 16:00:52 +0000
committerDaniel Thompson <daniel.thompson@linaro.org>2015-01-20 15:56:25 +0000
commitb973c4f8f423a632e893f7f752a12a10d27300f4 (patch)
treea1c3b969830e76716aa0e29a72f70a7ee7165cc9
parent5d5399d5efc65fc6f5a26f45c93e4f746169d627 (diff)
arm64: irqflags: Use ICC sysregs to implement IRQ maskinghacking/fvp
Signed-off-by: Daniel Thompson <daniel.thompson@linaro.org>
-rw-r--r--arch/arm64/Kconfig15
-rw-r--r--arch/arm64/include/asm/irqflags.h103
-rw-r--r--arch/arm64/kernel/entry.S23
-rw-r--r--arch/arm64/kernel/head.S14
-rw-r--r--arch/arm64/mm/proc.S13
-rw-r--r--drivers/irqchip/irq-gic-v3.c27
-rw-r--r--include/linux/irqchip/arm-gic-v3.h2
7 files changed, 196 insertions, 1 deletions
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 03ce54ecdc84..6f056d6fbd76 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -542,6 +542,21 @@ config CP15_BARRIER_EMULATION
endif
+config USE_ICC_SYSREGS_FOR_IRQFLAGS
+ bool "Use ICC system registers for IRQ masking"
+ select CONFIG_ARM_GIC_V3
+ help
+ Using the ICC system registers for IRQ masking makes it possible
+ to simulate NMI on ARM64 systems. This allows several interesting
+ features (especially debug features) to be used on these systems.
+
+ Say Y here to implement IRQ masking using ICC system
+ registers. This will result in an unbootable kernel if these
+ registers are not implemented or made inaccessible by the
+ EL3 firmare or EL2 hypervisor (if present).
+
+ If unsure, say N
+
endmenu
menu "Boot options"
diff --git a/arch/arm64/include/asm/irqflags.h b/arch/arm64/include/asm/irqflags.h
index df7477af6389..5e8b4c39ebe4 100644
--- a/arch/arm64/include/asm/irqflags.h
+++ b/arch/arm64/include/asm/irqflags.h
@@ -20,6 +20,8 @@
#include <asm/ptrace.h>
+#ifndef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
+
/*
* CPU interrupt mask handling.
*/
@@ -84,6 +86,107 @@ static inline int arch_irqs_disabled_flags(unsigned long flags)
return flags & PSR_I_BIT;
}
+#else /* CONFIG_IRQFLAGS_GIC_MASKING */
+
+#include <linux/stringify.h>
+#include <asm/sysreg.h>
+
+/* from arm-gic-v3.h */
+#define ICC_PMR_EL1 sys_reg(3, 0, 4, 6, 0)
+
+#define ICC_PMR_EL1_UNMASKED 0xf0
+#define ICC_PMR_EL1_MASKED 0x70
+
+/*
+ * CPU interrupt mask handling.
+ */
+static inline unsigned long arch_local_irq_save(void)
+{
+ unsigned long flags, masked = ICC_PMR_EL1_MASKED;
+
+ asm volatile(
+ "// arch_local_irq_save\n"
+ "mrs_s %0, " __stringify(ICC_PMR_EL1) "\n"
+ "msr_s " __stringify(ICC_PMR_EL1) ",%1\n"
+ "isb\n"
+ "mrs %0, daif\n"
+ "msr daifset, #2"
+ : "=&r" (flags)
+ : "r" (masked)
+ : "memory");
+ return flags;
+}
+
+static inline void arch_local_irq_enable(void)
+{
+ unsigned long unmasked = ICC_PMR_EL1_UNMASKED;
+
+ asm volatile(
+ "// arch_local_irq_enable\n"
+ "msr_s " __stringify(ICC_PMR_EL1) ",%0\n"
+ "isb\n"
+ "msr daifclr, #2"
+ :
+ : "r" (unmasked)
+ : "memory");
+}
+
+static inline void arch_local_irq_disable(void)
+{
+ unsigned long masked = ICC_PMR_EL1_MASKED;
+
+ asm volatile(
+ "// arch_local_irq_disable\n"
+ "msr_s " __stringify(ICC_PMR_EL1) ",%0\n"
+ "isb\n"
+ "msr daifset, #2"
+ :
+ : "r" (masked)
+ : "memory");
+}
+
+/*
+ * Save the current interrupt enable state.
+ */
+static inline unsigned long arch_local_save_flags(void)
+{
+ unsigned long flags;
+
+ asm volatile(
+ "// arch_local_save_flags\n"
+ "mrs_s %0, " __stringify(ICC_PMR_EL1) "\n"
+ "mrs %0, daif"
+ : "=r" (flags)
+ :
+ : "memory");
+ return flags;
+}
+
+/*
+ * restore saved IRQ state
+ */
+static inline void arch_local_irq_restore(unsigned long flags)
+{
+ unsigned long secondary_flags =
+ flags & PSR_I_BIT ? ICC_PMR_EL1_MASKED : ICC_PMR_EL1_UNMASKED;
+
+ asm volatile(
+ "// arch_local_irq_restore\n"
+ "msr_s " __stringify(ICC_PMR_EL1) ",%1\n"
+ "isb\n"
+ "msr daif, %0"
+ :
+ : "r" (flags), "r" (secondary_flags)
+ : "memory");
+}
+
+static inline int arch_irqs_disabled_flags(unsigned long flags)
+{
+ return flags & PSR_I_BIT;
+}
+
+#endif /* CONFIG_IRQFLAGS_GIC_MASKING */
+
#define local_fiq_enable() asm("msr daifclr, #1" : : : "memory")
#define local_fiq_disable() asm("msr daifset, #1" : : : "memory")
diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S
index c0615ecd4b4f..dfe5c054841f 100644
--- a/arch/arm64/kernel/entry.S
+++ b/arch/arm64/kernel/entry.S
@@ -20,6 +20,7 @@
#include <linux/init.h>
#include <linux/linkage.h>
+#include <linux/irqchip/arm-gic-v3.h>
#include <asm/assembler.h>
#include <asm/asm-offsets.h>
@@ -94,6 +95,14 @@
.endif
mrs x22, elr_el1
mrs x23, spsr_el1
+#ifdef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
+ mrs_s x20, ICC_PMR_EL1 // Get PMR
+ and x20, x20, #0x80 // Extract top bit
+ lsl x20, x20, #22-7 // Shift to a PSTATE RES0 bit
+ eor x20, x20, #1 << 22 // Invert bit
+ orr x23, x20, x23 // Store PMR within PSTATE
+#endif
+
stp lr, x21, [sp, #S_LR]
stp x22, x23, [sp, #S_PC]
@@ -121,6 +130,20 @@
ldr x23, [sp, #S_SP] // load return stack pointer
msr sp_el0, x23
.endif
+#ifdef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
+ and x20, x22, #1 << 22 // Get stolen PSTATE bit
+ eor x22, x20, x22 // Clear stolen bit
+ eor x20, x20, #1 << 22 // Invert bit
+ lsr x20, x20, #22-7 // Shift back to PMR mask
+ orr x20, x20, #0x70 // Add in bottom bits
+ msr_s ICC_PMR_EL1, x20 // Write to PMR
+ isb
+#endif
+#if 0
+ mov x20, #0xf0
+ msr_s ICC_PMR_EL1, x20
+ isb
+#endif
msr elr_el1, x21 // set up the return data
msr spsr_el1, x22
.if \ret
diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S
index 47097ee2cded..278b86e0fddc 100644
--- a/arch/arm64/kernel/head.S
+++ b/arch/arm64/kernel/head.S
@@ -464,6 +464,14 @@ __mmap_switched:
str x22, [x4] // Save processor ID
str x21, [x5] // Save FDT pointer
str x24, [x6] // Save PHYS_OFFSET
+
+#ifdef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
+ mrs_s x29, ICC_SRE_EL1
+ orr x29, x29, #ICC_SRE_EL1_SRE // Set ICC_SRE_EL1.SRE==1
+ msr_s ICC_SRE_EL1, x29
+ isb // Make sure SRE is now set
+#endif
+
mov x29, #0
b start_kernel
ENDPROC(__mmap_switched)
@@ -652,6 +660,12 @@ ENDPROC(secondary_startup)
ENTRY(__secondary_switched)
ldr x0, [x21] // get secondary_data.stack
mov sp, x0
+#ifdef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
+ mrs_s x29, ICC_SRE_EL1
+ orr x29, x29, #ICC_SRE_EL1_SRE // Set ICC_SRE_EL1.SRE==1
+ msr_s ICC_SRE_EL1, x29
+ isb // Make sure SRE is now set
+#endif
mov x29, #0
b secondary_start_kernel
ENDPROC(__secondary_switched)
diff --git a/arch/arm64/mm/proc.S b/arch/arm64/mm/proc.S
index 4e778b13291b..45fdf54fd79f 100644
--- a/arch/arm64/mm/proc.S
+++ b/arch/arm64/mm/proc.S
@@ -20,6 +20,7 @@
#include <linux/init.h>
#include <linux/linkage.h>
+#include <linux/irqchip/arm-gic-v3.h>
#include <asm/assembler.h>
#include <asm/asm-offsets.h>
#include <asm/hwcap.h>
@@ -97,8 +98,20 @@ ENDPROC(cpu_soft_restart)
* Idle the processor (wait for interrupt).
*/
ENTRY(cpu_do_idle)
+#ifdef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
+ mrs x0, daif // save I bit
+ msr daifset, #2 // set I bit
+ mrs_s x1, ICC_PMR_EL1 // save PMR
+ orr x2, x1, #0x80 // set top bit
+ msr_s ICC_PMR_EL1, x2 // unmask at PMR
+ isb // ?not needed for wfi?
+#endif
dsb sy // WFI may enter a low-power mode
wfi
+#ifdef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
+ msr_s ICC_PMR_EL1, x1 // restore PMR
+ msr daif, x0 // restore I bit
+#endif
ret
ENDPROC(cpu_do_idle)
diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c
index 1a146ccee701..58ffe79b99ee 100644
--- a/drivers/irqchip/irq-gic-v3.c
+++ b/drivers/irqchip/irq-gic-v3.c
@@ -110,8 +110,33 @@ static void gic_redist_wait_for_rwp(void)
static u64 __maybe_unused gic_read_iar(void)
{
u64 irqstat;
+ u64 __maybe_unused daif;
+ u64 __maybe_unused pmr;
+ u64 __maybe_unused default_pmr_value = DEFAULT_PMR_VALUE;
+#ifndef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
asm volatile("mrs_s %0, " __stringify(ICC_IAR1_EL1) : "=r" (irqstat));
+#else
+ /*
+ * The value of the PMR is unknown when this code is called. In
+ * order to acknowledge all interrupts we must set the PMR to a
+ * known value before reading from the IAR.
+ *
+ * To do this safely we also ensure the I bit is set whilst we
+ * are interfering with the value of the PMR.
+ */
+ asm volatile(
+ "mrs %1, daif\n" /* save I bit */
+ "msr daifset, #2\n" /* set I bit */
+ "mrs_s %2, " __stringify(ICC_PMR_EL1) "\n" /* save PMR */
+ "msr_s " __stringify(ICC_PMR_EL1) ",%3\n" /* set PMR */
+ "mrs_s %0, " __stringify(ICC_IAR1_EL1) "\n" /* ack int */
+ "msr_s " __stringify(ICC_PMR_EL1) ",%2\n" /* restore PMR */
+ "isb\n"
+ "msr daif, %1" /* restore I */
+ : "=r" (irqstat), "=&r" (daif), "=&r" (pmr)
+ : "r" (default_pmr_value));
+#endif
return irqstat;
}
@@ -377,11 +402,13 @@ static int gic_populate_rdist(void)
static void gic_cpu_sys_reg_init(void)
{
+#ifndef CONFIG_USE_ICC_SYSREGS_FOR_IRQFLAGS
/* Enable system registers */
gic_enable_sre();
/* Set priority mask register */
gic_write_pmr(DEFAULT_PMR_VALUE);
+#endif
/* EOI deactivates interrupt too (mode 0) */
gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop_dir);
diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h
index 1e8b0cf30792..1cadb617316a 100644
--- a/include/linux/irqchip/arm-gic-v3.h
+++ b/include/linux/irqchip/arm-gic-v3.h
@@ -205,7 +205,7 @@
*/
#define ICC_CTLR_EL1_EOImode_drop_dir (0U << 1)
#define ICC_CTLR_EL1_EOImode_drop (1U << 1)
-#define ICC_SRE_EL1_SRE (1U << 0)
+#define ICC_SRE_EL1_SRE (1 << 0)
/*
* Hypervisor interface registers (SRE only)