diff options
author | Daniel Thompson <daniel.thompson@linaro.org> | 2015-01-15 16:00:52 +0000 |
---|---|---|
committer | Daniel Thompson <daniel.thompson@linaro.org> | 2015-01-20 15:56:25 +0000 |
commit | b973c4f8f423a632e893f7f752a12a10d27300f4 (patch) | |
tree | a1c3b969830e76716aa0e29a72f70a7ee7165cc9 | |
parent | 5d5399d5efc65fc6f5a26f45c93e4f746169d627 (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/Kconfig | 15 | ||||
-rw-r--r-- | arch/arm64/include/asm/irqflags.h | 103 | ||||
-rw-r--r-- | arch/arm64/kernel/entry.S | 23 | ||||
-rw-r--r-- | arch/arm64/kernel/head.S | 14 | ||||
-rw-r--r-- | arch/arm64/mm/proc.S | 13 | ||||
-rw-r--r-- | drivers/irqchip/irq-gic-v3.c | 27 | ||||
-rw-r--r-- | include/linux/irqchip/arm-gic-v3.h | 2 |
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) |