This patchset makes it possible to use the kgdb NMI infrastructure on ARM platforms.
The kgdb NMI infrastructure works by re-routing an UARTs interrupt signal from IRQ to FIQ. The UART will no longer function normally and will instead be managed by kgdb using the polled I/O functions. Any character delivered to the UART causes the kgdb handler function to be called. Each serial driver explicitly consents (or not) to this abuse by calling the appropriate registration functions.
[PATCH 1/8] arm: fiq: Allow EOI to be communicated to the intc [PATCH 2/8] irqchip: gic: Provide support for interrupt grouping Both these patches lay the ground work to allow modern ARM interrupt controllers to support FIQ correctly.
[PATCH 3/8] ARM: Move some macros from entry-armv to entry-header [PATCH 4/8] ARM: Add KGDB/KDB FIQ debugger generic code This is the heart of the patch series, allowing FIQs to be registered with KGDB and handled by KGDB.
[PATCH 5/8] serial: amba-pl011: Pass on FIQ information to KGDB. [PATCH 6/8] serial: asc: Add support for KGDB's FIQ/NMI mode Extend to UART drivers to allow the register the appropriate FIQ (implicitly promising to behave properly when their own IRQ handler is cut off).
[PATCH 7/8] ARM: VIC: Add vic_set_fiq function to select if an... [PATCH 8/8] arm: fiq: Hack FIQ routing backdoors into GIC and VIC Here we hit the serious request-for-comment section. It is not clear what the best way to get the interrupt controller to re-route an interrupt source from the IRQ signal to the FIQ signal.
Clearly the approach here is wrong but it has been enough for me to test my work so far.
Anton Vorontsov (2): ARM: Move some macros from entry-armv to entry-header ARM: Add KGDB/KDB FIQ debugger generic code
Arve Hjønnevåg (1): ARM: VIC: Add vic_set_fiq function to select if an interrupt should generate an IRQ or FIQ
Daniel Thompson (5): arm: fiq: Allow EOI to be communicated to the intc irqchip: gic: Provide support for interrupt grouping serial: amba-pl011: Pass on FIQ information to KGDB. serial: asc: Add support for KGDB's FIQ/NMI mode arm: fiq: Hack FIQ routing backdoors into GIC and VIC
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++ arch/arm/boot/dts/stih416.dtsi | 2 +- arch/arm/boot/dts/vexpress-v2m-rs1.dtsi | 2 +- arch/arm/include/asm/fiq.h | 1 + arch/arm/include/asm/kgdb.h | 7 ++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/entry-armv.S | 151 +---------------------------- arch/arm/kernel/entry-header.S | 164 ++++++++++++++++++++++++++++++++ arch/arm/kernel/fiq.c | 50 ++++++++++ arch/arm/kernel/kgdb_fiq.c | 117 +++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++++ drivers/irqchip/irq-gic.c | 62 +++++++++++- drivers/irqchip/irq-vic.c | 23 +++++ drivers/tty/serial/amba-pl011.c | 18 +++- drivers/tty/serial/st-asc.c | 25 +++++ include/linux/irqchip/arm-gic.h | 3 + include/linux/irqchip/arm-vic.h | 1 + 18 files changed, 576 insertions(+), 158 deletions(-) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
Modern ARM systems require an EOI to be sent to the interrupt controller on completion of both IRQ and FIQ. The FIQ code currently does not permit this requiring nasty register poke hacks from the FIQ handler. This patch provides a simple interface for C based handlers to complete a FIQ.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/fiq.h | 1 + arch/arm/kernel/fiq.c | 11 +++++++++++ 2 files changed, 12 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..5a2a9b9 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -38,6 +38,7 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern void eoi_fiq(int fiq);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..defbe85 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,7 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -139,6 +140,15 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); }
+void eoi_fiq(int fiq) +{ + struct irq_data *irq_data = irq_get_irq_data(fiq + fiq_start); + struct irq_chip *chip = irq_data_get_irq_chip(irq_data); + + if (chip->irq_eoi) + chip->irq_eoi(irq_data); +} + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -146,6 +156,7 @@ EXPORT_SYMBOL(claim_fiq); EXPORT_SYMBOL(release_fiq); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq); +EXPORT_SYMBOL(eoi_fiq);
void __init init_FIQ(int start) {
GICv2+ implementions that do not implement security extensions (and devices that boot in secure mode by default) allow the interrupt group registers (used for FIQ/IRQ selection) to be accessed from kernel code. However the current gic support does not initialize the controller to make interrupt grouping effective.
The registers involved are RAZ/WI when unimplemented or protected by security policy then it should be safe to set up the grouping unconditionally.
Tested on a (self-written) qemu GICv2 model (written from ARM spec) and an STiH416 (ARM Cortex A9).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Dirk Behme dirk.behme@de.bosch.com --- drivers/irqchip/irq-gic.c | 35 ++++++++++++++++++++++++++++++----- include/linux/irqchip/arm-gic.h | 3 +++ 2 files changed, 33 insertions(+), 5 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 57d165e..aa8efe4 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -408,13 +408,27 @@ static void __init gic_dist_init(struct gic_chip_data *gic) writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4);
/* + * Set all global interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers a read-as-zero/write-ignored. + */ + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, base + GIC_DIST_IGROUP + i * 4 / 32); + + /* * Disable all interrupts. Leave the PPI and SGIs alone * as these enables are banked registers. */ for (i = 32; i < gic_irqs; i += 32) writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4 / 32);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + writel_relaxed(GIC_DIST_CTRL_ENABLE_GRP0_BIT | + GIC_DIST_CTRL_ENABLE_GRP1_BIT, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -452,8 +466,16 @@ static void gic_cpu_init(struct gic_chip_data *gic) for (i = 0; i < 32; i += 4) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4 / 4);
+ /* + * Set all PPI and SGI interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers are read-as-zero/write-ignored. + */ + writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + writel_relaxed(0x1f, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -537,7 +559,9 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(GIC_DIST_CTRL_ENABLE_GRP0_BIT | + GIC_DIST_CTRL_ENABLE_GRP1_BIT, + dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -594,7 +618,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -670,7 +694,8 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + writel_relaxed(map << 16 | irq | 0x8000, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 7ed92d0..919502f 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -37,6 +37,9 @@ #define GIC_DIST_SGI_PENDING_CLEAR 0xf10 #define GIC_DIST_SGI_PENDING_SET 0xf20
+#define GIC_DIST_CTRL_ENABLE_GRP0_BIT (1 << 0) +#define GIC_DIST_CTRL_ENABLE_GRP1_BIT (1 << 1) + #define GICH_HCR 0x0 #define GICH_VTR 0x4 #define GICH_VMCR 0x8
From: Anton Vorontsov anton.vorontsov@linaro.org
Just move the macros into header file as we would want to use them for KGDB FIQ entry code.
The following macros were moved:
- svc_entry - usr_entry - kuser_cmpxchg_check - vector_stub
To make kuser_cmpxchg_check actually work across different files, we also have to make kuser_cmpxchg64_fixup global.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/kernel/entry-armv.S | 151 +------------------------------------ arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 150 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 1879e8d..ed95b95 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -140,53 +140,6 @@ ENDPROC(__und_invalid) * SVC mode handlers */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) -#define SPFIX(code...) code -#else -#define SPFIX(code...) -#endif - - .macro svc_entry, stack_hole=0 - UNWIND(.fnstart ) - UNWIND(.save {r0 - pc} ) - sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) -#ifdef CONFIG_THUMB2_KERNEL - SPFIX( str r0, [sp] ) @ temporarily saved - SPFIX( mov r0, sp ) - SPFIX( tst r0, #4 ) @ test original stack alignment - SPFIX( ldr r0, [sp] ) @ restored -#else - SPFIX( tst sp, #4 ) -#endif - SPFIX( subeq sp, sp, #4 ) - stmia sp, {r1 - r12} - - ldmia r0, {r3 - r5} - add r7, sp, #S_SP - 4 @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) - SPFIX( addeq r2, r2, #4 ) - str r3, [sp, #-4]! @ save the "real" r0 copied - @ from the exception stack - - mov r3, lr - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r2 - sp_svc - @ r3 - lr_svc - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - stmia r7, {r2 - r6} - -#ifdef CONFIG_TRACE_IRQFLAGS - bl trace_hardirqs_off -#endif - .endm - .align 5 __dabt_svc: svc_entry @@ -306,73 +259,8 @@ ENDPROC(__pabt_svc)
/* * User mode handlers - * - * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0 - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - .align 5 __dabt_usr: usr_entry @@ -819,6 +707,7 @@ __kuser_cmpxchg64: @ 0xffff0f60 ldmfd sp!, {r4, r5, r6, pc}
.text + .global kuser_cmpxchg64_fixup kuser_cmpxchg64_fixup: @ Called from kuser_cmpxchg_fixup. @ r4 = address of interrupted insn (must be preserved). @@ -960,44 +849,6 @@ __kuser_helper_end: * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. */ - .macro vector_stub, name, mode, correction=0 - .align 5 - -vector_\name: - .if \correction - sub lr, lr, #\correction - .endif - - @ - @ Save r0, lr_<exception> (parent PC) and spsr_<exception> - @ (parent CPSR) - @ - stmia sp, {r0, lr} @ save r0, lr - mrs lr, spsr - str lr, [sp, #8] @ save spsr - - @ - @ Prepare for SVC32 mode. IRQs remain disabled. - @ - mrs r0, cpsr - eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) - msr spsr_cxsf, r0 - - @ - @ the branch table must immediately follow this code - @ - and lr, lr, #0x0f - THUMB( adr r0, 1f ) - THUMB( ldr lr, [r0, lr, lsl #2] ) - mov r0, sp - ARM( ldr lr, [pc, lr, lsl #2] ) - movs pc, lr @ branch to handler in SVC mode -ENDPROC(vector_\name) - - .align 2 - @ handler addresses follow this label -1: - .endm
.section .stubs, "ax", %progbits __stubs_start: diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 1420725..ab04a67 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -352,3 +352,167 @@ scno .req r7 @ syscall number tbl .req r8 @ syscall table pointer why .req r8 @ Linux syscall (!= 0) tsk .req r9 @ current thread_info + +/* + * SVC mode handler macros + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) +#define SPFIX(code...) code +#else +#define SPFIX(code...) +#endif + + .macro svc_entry, stack_hole=0 + UNWIND(.fnstart ) + UNWIND(.save {r0 - pc} ) + sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) +#ifdef CONFIG_THUMB2_KERNEL + SPFIX( str r0, [sp] ) @ temporarily saved + SPFIX( mov r0, sp ) + SPFIX( tst r0, #4 ) @ test original stack alignment + SPFIX( ldr r0, [sp] ) @ restored +#else + SPFIX( tst sp, #4 ) +#endif + SPFIX( subeq sp, sp, #4 ) + stmia sp, {r1 - r12} + + ldmia r0, {r3 - r5} + add r7, sp, #S_SP - 4 @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) + SPFIX( addeq r2, r2, #4 ) + str r3, [sp, #-4]! @ save the "real" r0 copied + @ from the exception stack + + mov r3, lr + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r2 - sp_svc + @ r3 - lr_svc + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + stmia r7, {r2 - r6} + +#ifdef CONFIG_TRACE_IRQFLAGS + bl trace_hardirqs_off +#endif + .endm + +/* + * User mode handler macros + * + * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0 + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +/* + * Vector stubs macro. + */ + .macro vector_stub, name, mode, correction=0 + .align 5 + +vector_\name: + .if \correction + sub lr, lr, #\correction + .endif + + @ + @ Save r0, lr_<exception> (parent PC) and spsr_<exception> + @ (parent CPSR) + @ + stmia sp, {r0, lr} @ save r0, lr + mrs lr, spsr + str lr, [sp, #8] @ save spsr + + @ + @ Prepare for SVC32 mode. IRQs remain disabled. + @ + mrs r0, cpsr + eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) + msr spsr_cxsf, r0 + + @ + @ the branch table must immediately follow this code + @ + and lr, lr, #0x0f + THUMB( adr r0, 1f ) + THUMB( ldr lr, [r0, lr, lsl #2] ) + mov r0, sp + ARM( ldr lr, [pc, lr, lsl #2] ) + movs pc, lr @ branch to handler in SVC mode +ENDPROC(vector_\name) + + .align 2 + @ handler addresses follow this label +1: + .endm + +
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++++ arch/arm/include/asm/kgdb.h | 7 +++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/kgdb_fiq.c | 117 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++++++++++++++++ 6 files changed, 232 insertions(+) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index db3c541..419fd0a 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -307,6 +307,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -356,6 +357,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 6a2bcfd..1f1bec1 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,24 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be + enabled with kgdb_fiq.enable=1 kernel command line option. + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..5de21f01 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,11 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern char kgdb_fiq_handler; +extern char kgdb_fiq_handler_end; +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs); +extern int kgdb_register_fiq(unsigned int fiq); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 040619c..251f651 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -67,6 +67,7 @@ endif obj-$(CONFIG_OABI_COMPAT) += sys_oabi-compat.o obj-$(CONFIG_ARM_THUMBEE) += thumbee.o obj-$(CONFIG_KGDB) += kgdb.o +obj-$(CONFIG_KGDB_FIQ) += kgdb_fiq_entry.o kgdb_fiq.o obj-$(CONFIG_ARM_UNWIND) += unwind.o obj-$(CONFIG_HAVE_TCM) += tcm.o obj-$(CONFIG_OF) += devtree.o diff --git a/arch/arm/kernel/kgdb_fiq.c b/arch/arm/kernel/kgdb_fiq.c new file mode 100644 index 0000000..b236409 --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq.c @@ -0,0 +1,117 @@ +/* + * KGDB FIQ + * + * Copyright 2010 Google, Inc. + * Arve Hjønnevåg arve@android.com + * Colin Cross ccross@android.com + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/hardirq.h> +#include <linux/atomic.h> +#include <linux/kdb.h> +#include <linux/kgdb.h> +#include <asm/fiq.h> +#include <asm/exception.h> + +static int kgdb_fiq_enabled; +module_param_named(enable, kgdb_fiq_enabled, int, 0600); +MODULE_PARM_DESC(enable, "set to 1 to enable FIQ KGDB"); + +static unsigned int kgdb_fiq; + +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs) +{ + if (kgdb_nmi_poll_knock()) { + nmi_enter(); + kgdb_handle_exception(1, 0, 0, regs); + nmi_exit(); + } + + eoi_fiq(kgdb_fiq); +} + +static struct fiq_handler kgdb_fiq_desc = { + .name = "kgdb", +}; + +static long kgdb_fiq_setup_stack(void *info) +{ + struct pt_regs regs; + + regs.ARM_sp = __get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER) + + THREAD_START_SP; + WARN_ON(!regs.ARM_sp); + + set_fiq_regs(®s); + return 0; +} + +/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * This function manages NMIs that usually cause KGDB to enter. That is, not + * all NMIs should be enabled or disabled, but only those that issue + * kgdb_handle_exception(). + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + static atomic_t cnt; + int ret; + + ret = atomic_add_return(on ? 1 : -1, &cnt); + if (ret > 1 && on) { + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + WARN_ON(1); + return; + } + + if (ret > 0) + enable_fiq(kgdb_fiq); + else + disable_fiq(kgdb_fiq); +} + +int kgdb_register_fiq(unsigned int fiq) +{ + int err; + int cpu; + + if (!kgdb_fiq_enabled) + return -ENODEV; + + kgdb_fiq = fiq; + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + for_each_possible_cpu(cpu) + work_on_cpu(cpu, kgdb_fiq_setup_stack, NULL); + + set_fiq_handler(&kgdb_fiq_handler, + &kgdb_fiq_handler_end - &kgdb_fiq_handler); + + arch_kgdb_ops.enable_nmi = kgdb_fiq_enable_nmi; + return 0; +} diff --git a/arch/arm/kernel/kgdb_fiq_entry.S b/arch/arm/kernel/kgdb_fiq_entry.S new file mode 100644 index 0000000..d6becca --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq_entry.S @@ -0,0 +1,87 @@ +/* + * KGDB FIQ entry + * + * Copyright 1996,1997,1998 Russell King. + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/memory.h> +#include <asm/unwind.h> +#include "entry-header.S" + + .text + +@ This is needed for usr_entry/alignment_trap +.LCcralign: + .long cr_alignment +.LCdohandle: + .long kgdb_fiq_do_handle + + .macro fiq_handler + ldr r1, =.LCdohandle + mov r0, sp + adr lr, BSYM(9997f) + ldr pc, [r1] +9997: + .endm + + .align 5 +__fiq_svc: + svc_entry + fiq_handler + mov r0, sp + ldmib r0, {r1 - r14} + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + .ltorg + + .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + .ltorg + + .global kgdb_fiq_handler +kgdb_fiq_handler: + + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_svc @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f + + .global kgdb_fiq_handler_end +kgdb_fiq_handler_end:
It is important this information comes from the serial driver because, by doing so, the driver offers "permission" for KGDB to route its interrupt signal from the drivers own handler to KGDBs FIQ handler (which will handle the interrupt signal by making polled I/O callbacks).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/tty/serial/amba-pl011.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index dacf0a0..aac817a 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -58,6 +58,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/sizes.h> #include <linux/io.h> +#include <linux/kgdb.h>
#define UART_NR 14
@@ -2091,6 +2092,18 @@ static int pl011_probe_dt_alias(int index, struct device *dev) return ret; }
+#ifdef CONFIG_KGDB_FIQ +/* Register with KGDB if there is a FIQ linked to this device */ +static void pl011_register_fiq(struct amba_device *dev) +{ + int fiq = dev->irq[1]; + if (fiq > 0) + kgdb_register_fiq(fiq); +} +#else +static void pl011_register_fiq(struct platform_device *pdev) {} +#endif + static int pl011_probe(struct amba_device *dev, const struct amba_id *id) { struct uart_amba_port *uap; @@ -2164,11 +2177,14 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) }
ret = uart_add_one_port(&amba_reg, &uap->port); - if (ret) { + if (0 == ret) { + pl011_register_fiq(dev); + } else { amba_ports[i] = NULL; uart_unregister_driver(&amba_reg); pl011_dma_remove(uap); } + out: return ret; }
For a serial driver to support FIQ/NMI debugging it must enable the RX interrupt when a polling client attaches itself (otherwise the FIQ signal will never be asserted). This concept is copied from similar code in amba-pl011.c .
It must also register the appropriate FIQ number with kgdb. kgdb will use this to make calls to enable_fiq/disable_fiq/eoi_fiq. The FIQ number must be supplied via the devices bus (in this case platform bus).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/tty/serial/st-asc.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index c7f61ac..e93803f 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -30,6 +30,7 @@ #include <linux/of_platform.h> #include <linux/serial_core.h> #include <linux/clk.h> +#include <linux/kgdb.h>
#define DRIVER_NAME "st-asc" #define ASC_SERIAL_NAME "ttyAS" @@ -613,6 +614,14 @@ asc_verify_port(struct uart_port *port, struct serial_struct *ser) }
#ifdef CONFIG_CONSOLE_POLL +static int asc_poll_init(struct uart_port *port) +{ + /* enable RX interrupts in case the interrupt is used for NMI entry. */ + asc_enable_rx_interrupts(port); + + return 0; +} + /* * Console polling routines for writing and reading from the uart while * in an interrupt or debug context (i.e. kgdb). @@ -656,11 +665,25 @@ static struct uart_ops asc_uart_ops = { .verify_port = asc_verify_port, .pm = asc_pm, #ifdef CONFIG_CONSOLE_POLL + .poll_init = asc_poll_init, .poll_get_char = asc_get_poll_char, .poll_put_char = asc_put_poll_char, #endif /* CONFIG_CONSOLE_POLL */ };
+#ifdef CONFIG_KGDB_FIQ +/* Register with KGDB if there is a FIQ linked to this device */ +static void asc_register_fiq(struct platform_device *pdev) +{ + int fiq = platform_get_irq(pdev, 1); + if (fiq >= 0) + kgdb_register_fiq(fiq); +} +#else +static void asc_register_fiq(struct platform_device *pdev) {} +#endif + + static int asc_init_port(struct asc_port *ascport, struct platform_device *pdev) { @@ -692,6 +715,8 @@ static int asc_init_port(struct asc_port *ascport, WARN_ON(ascport->port.uartclk == 0); clk_disable_unprepare(ascport->clk);
+ asc_register_fiq(pdev); + return 0; }
From: Arve Hjønnevåg arve@android.com
Signed-off-by: Arve Hjønnevåg arve@android.com Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/irqchip/irq-vic.c | 23 +++++++++++++++++++++++ include/linux/irqchip/arm-vic.h | 1 + 2 files changed, 24 insertions(+)
diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index 7d35287..3eaa2e4 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -337,6 +337,29 @@ static void vic_unmask_irq(struct irq_data *d) writel(1 << irq, base + VIC_INT_ENABLE); }
+static DEFINE_SPINLOCK(vic_intselect_lock); +int vic_set_fiq(unsigned int irq, bool enable) +{ + u32 int_select; + u32 mask; + unsigned long irq_flags; + void __iomem *base = irq_get_chip_data(irq); + irq &= 31; + mask = 1 << irq; + + spin_lock_irqsave(&vic_intselect_lock, irq_flags); + int_select = readl(base + VIC_INT_SELECT); + if (enable) + int_select |= mask; + else + int_select &= ~mask; + writel(int_select, base + VIC_INT_SELECT); + spin_unlock_irqrestore(&vic_intselect_lock, irq_flags); + + return 0; +} +EXPORT_SYMBOL(vic_set_fiq); + #if defined(CONFIG_PM) static struct vic_device *vic_from_irq(unsigned int irq) { diff --git a/include/linux/irqchip/arm-vic.h b/include/linux/irqchip/arm-vic.h index ba46c79..61ee4c9 100644 --- a/include/linux/irqchip/arm-vic.h +++ b/include/linux/irqchip/arm-vic.h @@ -34,5 +34,6 @@ void __vic_init(void __iomem *base, int parent_irq, int irq_start, void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources); int vic_init_cascaded(void __iomem *base, unsigned int parent_irq, u32 vic_sources, u32 resume_sources); +int vic_set_fiq(unsigned int irq, bool enable);
#endif
This is a hack to make it easy to check that the other interfaces in the patchset make sense. It needs to be replaced by code to get the interrupt controllers to expose FIQs.
Unless a better option presents itself I plan to double up each interrupt source (so we get two virqs, one for regular interrupt and one for FIQ). This is what most of the prior art around FIQ in Linux has done in the past.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/boot/dts/stih416.dtsi | 2 +- arch/arm/boot/dts/vexpress-v2m-rs1.dtsi | 2 +- arch/arm/kernel/fiq.c | 39 +++++++++++++++++++++++++++++++++ drivers/irqchip/irq-gic.c | 27 +++++++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-)
diff --git a/arch/arm/boot/dts/stih416.dtsi b/arch/arm/boot/dts/stih416.dtsi index 78746d2..a288898 100644 --- a/arch/arm/boot/dts/stih416.dtsi +++ b/arch/arm/boot/dts/stih416.dtsi @@ -99,7 +99,7 @@ compatible = "st,asc"; status = "disabled"; reg = <0xfe531000 0x2c>; - interrupts = <0 210 0>; + interrupts = <0 210 0>, <0 210 0>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_sbc_serial1>; clocks = <&CLK_SYSIN>; diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi index ac870fb..fab2a40 100644 --- a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi @@ -140,7 +140,7 @@ v2m_serial0: uart@090000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x090000 0x1000>; - interrupts = <5>; + interrupts = <5>, <5>; clocks = <&v2m_oscclk2>, <&smbclk>; clock-names = "uartclk", "apb_pclk"; }; diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index defbe85..efce321 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -41,6 +41,7 @@ #include <linux/interrupt.h> #include <linux/seq_file.h> #include <linux/irq.h> +#include <linux/irqchip/arm-vic.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -130,14 +131,52 @@ void release_fiq(struct fiq_handler *f)
static int fiq_start;
+/* These hacks use backdoors into the interrupt controller to perform FIQ/IRQ + * routing. These hacks are nasty and completely incompatible with (working) + * multiarch kernels. Additionally these hacks don't count enable/disable + * properly... + * + * This should probably all be replaced with virtual interrupt numbers + * that the intc already knows to bind to FIQ. + */ +#ifdef CONFIG_ARCH_VERSATILE +#define USE_VIC_HACK +#else +#define USE_GIC_HACK +#endif + void enable_fiq(int fiq) { +#ifdef USE_VIC_HACK + vic_set_fiq(fiq, true); +#endif +#ifdef USE_GIC_HACK +{ + struct irq_data *irq_data = irq_get_irq_data(fiq); + + extern void gic_set_group_irq(struct irq_data *d, int group); + gic_set_group_irq(irq_data, 0); +} +#endif + enable_irq(fiq + fiq_start); }
void disable_fiq(int fiq) { disable_irq(fiq + fiq_start); + +#ifdef USE_VIC_HACK + vic_set_fiq(fiq, false); +#endif +#ifdef USE_GIC_HACK +{ + struct irq_data *irq_data = irq_get_irq_data(fiq); + + extern void gic_set_group_irq(struct irq_data *d, int group); + gic_set_group_irq(irq_data, 1); +} +#endif }
void eoi_fiq(int fiq) diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index aa8efe4..c25632b 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -173,6 +173,33 @@ static void gic_unmask_irq(struct irq_data *d) raw_spin_unlock(&irq_controller_lock); }
+/*static*/ void gic_set_group_irq(struct irq_data *d, int group) +{ + unsigned long flags; + unsigned int reg = gic_irq(d) / 32 * 4; + u32 mask = 1 << (gic_irq(d) % 32); + u32 val; + + raw_spin_lock_irqsave(&irq_controller_lock, flags); + val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + reg); + if (group) + val |= mask; + else + val &= ~mask; + writel_relaxed(val, gic_dist_base(d) + GIC_DIST_IGROUP + reg); + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); +} + +/*static*/ int gic_get_group_irq(struct irq_data *d) +{ + unsigned int reg = gic_irq(d) / 32 * 4; + u32 mask = 1 << (gic_irq(d) % 32); + u32 val; + + val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + reg); + return !!(val & mask); +} + static void gic_eoi_irq(struct irq_data *d) { if (gic_arch_extn.irq_eoi) {
This patchset makes it possible to use the kgdb NMI infrastructure on ARM platforms by providing a mutli-arch compatible means for drivers to manage FIQ routings.
First a quick summary of how the mainline kgdb NMI infrastructure (mostly found in drivers/tty/serial/kgdb_nmi.c) works. The kgdb infrastructure will re-route the kgdb console UART's interrupt signal from IRQ to FIQ. Naturally the UART will no longer function normally and will instead be managed by kgdb using the polled I/O functions. Any character delivered to the UART causes the kgdb handler function to be called.
Note that, within this patchset a serial driver explicitly consents (or not) to the abuse outlined by calling the appropriate registration during the .poll_init() callback.
The patch set is structured as follows:
The first six patches modify the interrupt system to make it easier to describe FIQ. The key concept is that a call to enable_fiq() must modify the IRQ/FIQ routing to that the FIQ really is enabled (rather than spuriously delivering the signal on the IRQ). To achieve this each interrupt controller registers two virqs for each hwirq (allowing IRQ and FIQ to be readily distinguished).
The next two patches (7 and 8) provide kgdb with a FIQ handler and a means for serial drivers to register their FIQ with kgdb.
Finally two example serial drivers are modified in order to register their FIQs with kgdb.
Major remaining TODO item is to modify the code to halt the other CPUs; at present this code sends IPIs (which use a normal IRQ) and busy waits for the other CPUs to halt. This means the benefits of invoking the debugger via NMI are not support realized on SMP systems. However I plan to tackle that later (i.e. when there's some consensus on whether this approach is the right way to handle FIQ).
Changes since v1:
* Fully fledged multi-arch support.
* Tested for correct FIQ operation on STiH416/B2020 (Cortex A9), qemu/versatile and qemu/vexpress-a15 (with self-written mods to the GIC model to support FIQ).
* Regression tested (and resulting bugs fixed) on qemu/versatile+DT and qemu/integreatorcp.
Anton Vorontsov (2): ARM: Move some macros from entry-armv to entry-header ARM: Add KGDB/KDB FIQ debugger generic code
Daniel Thompson (8): arm: fiq: Allow EOI to be communicated to the intc irqchip: gic: Provide support for interrupt grouping irqchip: gic: Introduce shadow irqs for FIQ ARM: vexpress: Extend UART with FIQ support ARM: STi: STiH41x: Extend UART with FIQ support. irqchip: vic: Introduce shadow irqs for FIQ serial: amba-pl011: Pass on FIQ information to KGDB. serial: asc: Add support for KGDB's FIQ/NMI mode
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 +++ arch/arm/boot/dts/stih415.dtsi | 4 +- arch/arm/boot/dts/stih416.dtsi | 4 +- arch/arm/boot/dts/vexpress-v2m-rs1.dtsi | 8 +- arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts | 10 +- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 8 +- arch/arm/boot/dts/vexpress-v2p-ca5s.dts | 10 +- arch/arm/include/asm/fiq.h | 1 + arch/arm/include/asm/kgdb.h | 7 ++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/entry-armv.S | 151 +------------------------ arch/arm/kernel/entry-header.S | 164 ++++++++++++++++++++++++++++ arch/arm/kernel/fiq.c | 11 ++ arch/arm/kernel/kgdb_fiq.c | 117 ++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++ arch/arm/mach-ep93xx/core.c | 6 +- arch/arm/mach-netx/generic.c | 3 +- arch/arm/mach-s3c64xx/common.c | 6 +- arch/arm/mach-versatile/core.c | 9 +- arch/arm/mach-versatile/include/mach/irqs.h | 76 ++++++------- arch/arm/plat-samsung/s5p-irq.c | 3 +- drivers/irqchip/irq-gic.c | 81 ++++++++++++-- drivers/irqchip/irq-vic.c | 87 ++++++++++++--- drivers/tty/serial/amba-pl011.c | 101 ++++++++++------- drivers/tty/serial/st-asc.c | 27 +++++ include/linux/irqchip/arm-gic.h | 3 + include/linux/irqchip/arm-vic.h | 8 +- 28 files changed, 733 insertions(+), 280 deletions(-) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
Modern ARM systems require an EOI to be sent to the interrupt controller on completion of both IRQ and FIQ. The FIQ code currently does not provide any API to perform this. This patch provides this API, implemented by hooking into main irq driver in a similar way to the existing enable_fiq()/disable_fiq().
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org --- arch/arm/include/asm/fiq.h | 1 + arch/arm/kernel/fiq.c | 11 +++++++++++ 2 files changed, 12 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..5a2a9b9 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -38,6 +38,7 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern void eoi_fiq(int fiq);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..defbe85 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,7 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -139,6 +140,15 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); }
+void eoi_fiq(int fiq) +{ + struct irq_data *irq_data = irq_get_irq_data(fiq + fiq_start); + struct irq_chip *chip = irq_data_get_irq_chip(irq_data); + + if (chip->irq_eoi) + chip->irq_eoi(irq_data); +} + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -146,6 +156,7 @@ EXPORT_SYMBOL(claim_fiq); EXPORT_SYMBOL(release_fiq); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq); +EXPORT_SYMBOL(eoi_fiq);
void __init init_FIQ(int start) {
On Fri, May 23, 2014 at 02:57:49PM +0100, Daniel Thompson wrote:
+void eoi_fiq(int fiq) +{
- struct irq_data *irq_data = irq_get_irq_data(fiq + fiq_start);
- struct irq_chip *chip = irq_data_get_irq_chip(irq_data);
- if (chip->irq_eoi)
chip->irq_eoi(irq_data);
+}
So what if the IRQ chip takes a spinlock? You can't take spinlocks in FIQ context... Who checks for that kind of stuff - just hoping that the IRQ driver doesn't take a spinlock sounds real fragile.
On 23/05/14 16:00, Russell King - ARM Linux wrote:
On Fri, May 23, 2014 at 02:57:49PM +0100, Daniel Thompson wrote:
+void eoi_fiq(int fiq) +{
- struct irq_data *irq_data = irq_get_irq_data(fiq + fiq_start);
- struct irq_chip *chip = irq_data_get_irq_chip(irq_data);
- if (chip->irq_eoi)
chip->irq_eoi(irq_data);
+}
So what if the IRQ chip takes a spinlock? You can't take spinlocks in FIQ context... Who checks for that kind of stuff - just hoping that the IRQ driver doesn't take a spinlock sounds real fragile.
I'll certainly do a better code review before pushing on. I certainly did overlook a spinlock in the GIC code which (I think) is actually reachable on a Tegra platform. I'll have to do something about that.
I've spent a bit of time this week seeing whether lock dep can be provoked into triggering if it sees a spin lock in this code but that isn't turning out very well (it needs CONFIG_LOCKDEP_DEBUG, relies on lockdep believing the FIQ handling state isn't a hardirq and still doesn't work properly).
Fortunately architectures using EOI appear to be pretty rare (I count five in drivers/irqchip/ and three in arch/arm/) so traditional code review and yelling might be sufficient.
Daniel.
All GIC hardware except GICv1 without TrustZone support provides a means to group exceptions into group 0 (which can optionally be signally using use FIQ) and group 1. The kernel currently provides no means to exploit this. This patch alters the initialization of the GIC to place all interrupts into group 1, this is a foundational requirement to meaningfully use FIQ.
Note that the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world". This allows grouping to be used to allow hardware peripherals to send interrupts into the secure world.
On systems without TrustZone support the kernel has the power to route interrupt sources to FIQ, potentially allowing a driver to exploit the NMI-like properties of FIQ.
The registers involved are RAZ/WI when unimplemented or protected by security policy. This patch therefore applies grouping unconditionally.
Tested on a qemu GICv2 model (self-written from GICv2 spec) and an STiH416 (ARM Cortex A9, GICv1, TZ).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com Acked-by: Dirk Behme dirk.behme@de.bosch.com --- drivers/irqchip/irq-gic.c | 35 ++++++++++++++++++++++++++++++----- include/linux/irqchip/arm-gic.h | 3 +++ 2 files changed, 33 insertions(+), 5 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 57d165e..aa8efe4 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -408,13 +408,27 @@ static void __init gic_dist_init(struct gic_chip_data *gic) writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4);
/* + * Set all global interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers a read-as-zero/write-ignored. + */ + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, base + GIC_DIST_IGROUP + i * 4 / 32); + + /* * Disable all interrupts. Leave the PPI and SGIs alone * as these enables are banked registers. */ for (i = 32; i < gic_irqs; i += 32) writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4 / 32);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + writel_relaxed(GIC_DIST_CTRL_ENABLE_GRP0_BIT | + GIC_DIST_CTRL_ENABLE_GRP1_BIT, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -452,8 +466,16 @@ static void gic_cpu_init(struct gic_chip_data *gic) for (i = 0; i < 32; i += 4) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4 / 4);
+ /* + * Set all PPI and SGI interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers are read-as-zero/write-ignored. + */ + writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + writel_relaxed(0x1f, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -537,7 +559,9 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(GIC_DIST_CTRL_ENABLE_GRP0_BIT | + GIC_DIST_CTRL_ENABLE_GRP1_BIT, + dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -594,7 +618,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -670,7 +694,8 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + writel_relaxed(map << 16 | irq | 0x8000, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 7ed92d0..919502f 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -37,6 +37,9 @@ #define GIC_DIST_SGI_PENDING_CLEAR 0xf10 #define GIC_DIST_SGI_PENDING_SET 0xf20
+#define GIC_DIST_CTRL_ENABLE_GRP0_BIT (1 << 0) +#define GIC_DIST_CTRL_ENABLE_GRP1_BIT (1 << 1) + #define GICH_HCR 0x0 #define GICH_VTR 0x4 #define GICH_VMCR 0x8
This patch registers two virqs for each interrupt source it supports. Using multiple virqs allows the GIC driver to automatically modify the group register, allowing the new virqs to be used as argument to enable_fiq(). This also allows FIQ resources to be described in the device tree's interrupt list using a special flag (currently 0x80).
Both these aspects combine and allow a driver to deploy a FIQ handler without any machine specific knowledge; it can be used effectively on multi-arch kernels.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc:: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com --- drivers/irqchip/irq-gic.c | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index aa8efe4..0c259ac 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -48,6 +48,8 @@
#include "irqchip.h"
+#define GIC_INTSPEC_IRQ_IS_FIQ (1 << 7) + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -65,6 +67,7 @@ struct gic_chip_data { #endif struct irq_domain *domain; unsigned int gic_irqs; + unsigned int fiq_shadow_offset; #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif @@ -143,11 +146,34 @@ static inline void __iomem *gic_cpu_base(struct irq_data *d) return gic_data_cpu_base(gic_data); }
+static inline bool gic_is_fiq(struct irq_data *d) +{ + struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d); + return d->hwirq > gic_data->gic_irqs; +} + static inline unsigned int gic_irq(struct irq_data *d) { + struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d); + if (gic_is_fiq(d)) + return d->hwirq - gic_data->fiq_shadow_offset; return d->hwirq; }
+static void gic_set_group_irq(struct irq_data *d, int group) +{ + unsigned int reg = gic_irq(d) / 32 * 4; + u32 mask = 1 << (gic_irq(d) % 32); + u32 val; + + val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + reg); + if (group) + val |= mask; + else + val &= ~mask; + writel_relaxed(val, gic_dist_base(d) + GIC_DIST_IGROUP + reg); +} + /* * Routines to acknowledge, disable and enable interrupts */ @@ -159,6 +185,8 @@ static void gic_mask_irq(struct irq_data *d) writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_CLEAR + (gic_irq(d) / 32) * 4); if (gic_arch_extn.irq_mask) gic_arch_extn.irq_mask(d); + if (gic_is_fiq(d)) + gic_set_group_irq(d, 1); raw_spin_unlock(&irq_controller_lock); }
@@ -167,6 +195,8 @@ static void gic_unmask_irq(struct irq_data *d) u32 mask = 1 << (gic_irq(d) % 32);
raw_spin_lock(&irq_controller_lock); + if (gic_is_fiq(d)) + gic_set_group_irq(d, 0); if (gic_arch_extn.irq_unmask) gic_arch_extn.irq_unmask(d); writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_SET + (gic_irq(d) / 32) * 4); @@ -940,7 +970,12 @@ static int gic_routable_irq_domain_xlate(struct irq_domain *d, unsigned long *out_hwirq, unsigned int *out_type) { + struct gic_chip_data *gic_data = d->host_data; *out_hwirq += 16; + + if (intspec[2] & GIC_INTSPEC_IRQ_IS_FIQ) + *out_hwirq += gic_data->fiq_shadow_offset; + return 0; }
@@ -1026,10 +1061,11 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, gic->gic_irqs = gic_irqs;
gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ + gic->fiq_shadow_offset = gic_irqs;
if (of_property_read_u32(node, "arm,routable-irqs", &nr_routable_irqs)) { - irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, + irq_base = irq_alloc_descs(irq_start, 16, 2 * gic_irqs, numa_node_id()); if (IS_ERR_VALUE(irq_base)) { WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n", @@ -1037,12 +1073,12 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, irq_base = irq_start; }
- gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, - hwirq_base, &gic_irq_domain_ops, gic); + gic->domain = + irq_domain_add_legacy(node, 2 * gic_irqs, irq_base, + hwirq_base, &gic_irq_domain_ops, gic); } else { gic->domain = irq_domain_add_linear(node, nr_routable_irqs, - &gic_irq_domain_ops, - gic); + &gic_irq_domain_ops, gic); }
if (WARN_ON(!gic->domain))
This patch provides the UART with a second interrupt resource that can be used to route the UARTs interrupt to FIQ. The size of the interrupt map is doubled and new mappings for the FIQ shadows added (demarked by setting bit 7 in the final item in the tuple).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Rob Herring robh+dt@kernel.org Cc: Pawel Moll pawel.moll@arm.com Cc: Mark Rutland mark.rutland@arm.com Cc: Ian Campbell ijc+devicetree@hellion.org.uk Cc: Kumar Gala galak@codeaurora.org Cc: Russell King linux@arm.linux.org.uk Cc: devicetree@vger.kernel.org --- arch/arm/boot/dts/vexpress-v2m-rs1.dtsi | 8 ++++---- arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts | 10 ++++++++-- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 8 +++++++- arch/arm/boot/dts/vexpress-v2p-ca5s.dts | 10 ++++++++-- 4 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi index ac870fb..e86936c 100644 --- a/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi +++ b/arch/arm/boot/dts/vexpress-v2m-rs1.dtsi @@ -140,7 +140,7 @@ v2m_serial0: uart@090000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x090000 0x1000>; - interrupts = <5>; + interrupts = <5>, <69>; clocks = <&v2m_oscclk2>, <&smbclk>; clock-names = "uartclk", "apb_pclk"; }; @@ -148,7 +148,7 @@ v2m_serial1: uart@0a0000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x0a0000 0x1000>; - interrupts = <6>; + interrupts = <6>, <70>; clocks = <&v2m_oscclk2>, <&smbclk>; clock-names = "uartclk", "apb_pclk"; }; @@ -156,7 +156,7 @@ v2m_serial2: uart@0b0000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x0b0000 0x1000>; - interrupts = <7>; + interrupts = <7>, <71>; clocks = <&v2m_oscclk2>, <&smbclk>; clock-names = "uartclk", "apb_pclk"; }; @@ -164,7 +164,7 @@ v2m_serial3: uart@0c0000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x0c0000 0x1000>; - interrupts = <8>; + interrupts = <8>, <72>; clocks = <&v2m_oscclk2>, <&smbclk>; clock-names = "uartclk", "apb_pclk"; }; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts index 9420053..9c489fa 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts @@ -233,7 +233,7 @@ <5 0 0 0x10000000 0x04000000>;
#interrupt-cells = <1>; - interrupt-map-mask = <0 0 63>; + interrupt-map-mask = <0 0 127>; interrupt-map = <0 0 0 &gic 0 0 4>, <0 0 1 &gic 0 1 4>, <0 0 2 &gic 0 2 4>, @@ -276,7 +276,13 @@ <0 0 39 &gic 0 39 4>, <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, - <0 0 42 &gic 0 42 4>; + <0 0 42 &gic 0 42 4>, + + /* FIQ shadow routings */ + <0 0 69 &gic 0 5 0x84>, + <0 0 70 &gic 0 6 0x84>, + <0 0 71 &gic 0 7 0x84>, + <0 0 72 &gic 0 8 0x84>;
/include/ "vexpress-v2m-rs1.dtsi" }; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index 15f98cb..75821d2 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -390,7 +390,13 @@ <0 0 39 &gic 0 39 4>, <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, - <0 0 42 &gic 0 42 4>; + <0 0 42 &gic 0 42 4>, + + /* FIQ shadow routings */ + <0 0 69 &gic 0 5 0x84>, + <0 0 70 &gic 0 6 0x84>, + <0 0 71 &gic 0 7 0x84>, + <0 0 72 &gic 0 8 0x84>;
/include/ "vexpress-v2m-rs1.dtsi" }; diff --git a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts index c544a55..930e2ef 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts @@ -195,7 +195,7 @@ <5 0 0x10000000 0x04000000>;
#interrupt-cells = <1>; - interrupt-map-mask = <0 0 63>; + interrupt-map-mask = <0 0 127>; interrupt-map = <0 0 0 &gic 0 0 4>, <0 0 1 &gic 0 1 4>, <0 0 2 &gic 0 2 4>, @@ -238,7 +238,13 @@ <0 0 39 &gic 0 39 4>, <0 0 40 &gic 0 40 4>, <0 0 41 &gic 0 41 4>, - <0 0 42 &gic 0 42 4>; + <0 0 42 &gic 0 42 4>, + + /* FIQ shadow routings */ + <0 0 69 &gic 0 5 0x84>, + <0 0 70 &gic 0 6 0x84>, + <0 0 71 &gic 0 7 0x84>, + <0 0 72 &gic 0 8 0x84>;
/include/ "vexpress-v2m-rs1.dtsi" };
On Fri, May 23, 2014 at 02:57:52PM +0100, Daniel Thompson wrote:
@@ -140,7 +140,7 @@ v2m_serial0: uart@090000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x090000 0x1000>;
interrupts = <5>;
interrupts = <5>, <69>;
NAK. This is obviously a Linux implementation detail - the second number is your "FIQ" number which you've decided will be offset by 64.
On 23/05/14 16:04, Russell King - ARM Linux wrote:
On Fri, May 23, 2014 at 02:57:52PM +0100, Daniel Thompson wrote:
@@ -140,7 +140,7 @@ v2m_serial0: uart@090000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x090000 0x1000>;
interrupts = <5>;
interrupts = <5>, <69>;
NAK. This is obviously a Linux implementation detail - the second number is your "FIQ" number which you've decided will be offset by 64.
Certainly the offset of 64 is a more or less meaningless magic number that I decided on, however I don't think it originates within Linux. It is both introduced and consumed within the device tree itself.
I've proposed adding a flag to the gic bindings to mark a FIQ; the GIC calls interrupt 69 (above) <0 5 0x84> (where 0x80 is the flag to request FIQ routing alongside 0x04 which is the trigger type).
However vexpress-a15 has an interrupt-map between the UART and the GIC. Thus far I've not found a way, other by offset, to allow a UART sitting on a child bus to be provided with both IRQ and FIQ versions of an interrupt.
--- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts @@ -233,7 +233,7 @@ <5 0 0 0x10000000 0x04000000>; #interrupt-cells = <1>;
interrupt-map-mask = <0 0 63>;
interrupt-map-mask = <0 0 127>;
^^^^^
The 64 offset is introduced here. Nothing in Linux imposes anything on the value (I originally had the shadow routings from 59 to 63 and this also worked).
My code may still be inappropriate but, right now, I'm unsure whether the proposed changes to the gic bindings are included in the NAK, not in the NAK or whether they sit in the middle ground where I have to provide better argumentation.
Daniel.
On Thu, May 29, 2014 at 5:31 AM, Daniel Thompson daniel.thompson@linaro.org wrote:
On 23/05/14 16:04, Russell King - ARM Linux wrote:
On Fri, May 23, 2014 at 02:57:52PM +0100, Daniel Thompson wrote:
@@ -140,7 +140,7 @@ v2m_serial0: uart@090000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x090000 0x1000>;
interrupts = <5>;
interrupts = <5>, <69>;
NAK. This is obviously a Linux implementation detail - the second number is your "FIQ" number which you've decided will be offset by 64.
+1
Certainly the offset of 64 is a more or less meaningless magic number that I decided on, however I don't think it originates within Linux. It is both introduced and consumed within the device tree itself.
I've proposed adding a flag to the gic bindings to mark a FIQ; the GIC calls interrupt 69 (above) <0 5 0x84> (where 0x80 is the flag to request FIQ routing alongside 0x04 which is the trigger type).
That would be better than the magic number, but I'm not convinced that belongs in the DT either.
Whether the uart uses FIQ is not dependent on this DT setting, but based on whether the user wants to use the uart for kdb or not. You have enough information already to figure out which interrupt. What's missing is whether you have the capability to use FIQ or not. I don't recall if you can tell that from the GIC or not.
Rob
On 29/05/14 14:44, Rob Herring wrote:
On Thu, May 29, 2014 at 5:31 AM, Daniel Thompson daniel.thompson@linaro.org wrote:
On 23/05/14 16:04, Russell King - ARM Linux wrote:
On Fri, May 23, 2014 at 02:57:52PM +0100, Daniel Thompson wrote:
@@ -140,7 +140,7 @@ v2m_serial0: uart@090000 { compatible = "arm,pl011", "arm,primecell"; reg = <0x090000 0x1000>;
interrupts = <5>;
interrupts = <5>, <69>;
NAK. This is obviously a Linux implementation detail - the second number is your "FIQ" number which you've decided will be offset by 64.
+1
Certainly the offset of 64 is a more or less meaningless magic number that I decided on, however I don't think it originates within Linux. It is both introduced and consumed within the device tree itself.
I've proposed adding a flag to the gic bindings to mark a FIQ; the GIC calls interrupt 69 (above) <0 5 0x84> (where 0x80 is the flag to request FIQ routing alongside 0x04 which is the trigger type).
That would be better than the magic number, but I'm not convinced that belongs in the DT either.
Whether the uart uses FIQ is not dependent on this DT setting, but based on whether the user wants to use the uart for kdb or not. You have enough information already to figure out which interrupt. What's missing is whether you have the capability to use FIQ or not. I don't recall if you can tell that from the GIC or not.
Ok.
I've just started playing with an idea that keeps the shadow virqw for FIQ idea but doesn't require any device tree changes...
Another RFC will follow in due course.
Daniel.
This patch provides the UART with a second interrupt resource that can be used to route the UARTs interrupt to FIQ (demarked by setting bit 7 in the third item in the tuple).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Rob Herring robh+dt@kernel.org Cc: Pawel Moll pawel.moll@arm.com Cc: Mark Rutland mark.rutland@arm.com Cc: Ian Campbell ijc+devicetree@hellion.org.uk Cc: Kumar Gala galak@codeaurora.org Cc: Russell King linux@arm.linux.org.uk Cc: kernel@stlinux.com Cc: devicetree@vger.kernel.org --- arch/arm/boot/dts/stih415.dtsi | 4 ++-- arch/arm/boot/dts/stih416.dtsi | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm/boot/dts/stih415.dtsi b/arch/arm/boot/dts/stih415.dtsi index d89064c..b2c9df4 100644 --- a/arch/arm/boot/dts/stih415.dtsi +++ b/arch/arm/boot/dts/stih415.dtsi @@ -79,7 +79,7 @@ compatible = "st,asc"; status = "disabled"; reg = <0xfed32000 0x2c>; - interrupts = <0 197 0>; + interrupts = <0 197 0>, <0 197 0x80>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_serial2>; clocks = <&CLKS_ICN_REG_0>; @@ -90,7 +90,7 @@ compatible = "st,asc"; status = "disabled"; reg = <0xfe531000 0x2c>; - interrupts = <0 210 0>; + interrupts = <0 210 0>, <0 197 0x80>; clocks = <&CLK_SYSIN>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_sbc_serial1>; diff --git a/arch/arm/boot/dts/stih416.dtsi b/arch/arm/boot/dts/stih416.dtsi index 78746d2..fdf7b43 100644 --- a/arch/arm/boot/dts/stih416.dtsi +++ b/arch/arm/boot/dts/stih416.dtsi @@ -88,7 +88,7 @@ compatible = "st,asc"; status = "disabled"; reg = <0xfed32000 0x2c>; - interrupts = <0 197 0>; + interrupts = <0 197 0>, <0 210 0x80>; clocks = <&CLK_S_ICN_REG_0>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_serial2 &pinctrl_serial2_oe>; @@ -99,7 +99,7 @@ compatible = "st,asc"; status = "disabled"; reg = <0xfe531000 0x2c>; - interrupts = <0 210 0>; + interrupts = <0 210 0>, <0 210 0x80>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_sbc_serial1>; clocks = <&CLK_SYSIN>;
Currently on the ARM Versatile machine both FIQ and IRQ signals share the same irq number. The effect of this is that enable_fiq() will enable an interrupt but will leave it routed to IRQ. This requires a driver utilizing FIQ to employ machine specific knowledge (i.e. that the machine has a VIC).
By introducing shadow irqs to describe FIQs the VIC driver is able to update the routing automatically during enable_fiq()/disable_fiq().
Changes to the vic_init() API allow individual machines to choose where to fit the shadow irqs in the interrupt map and also to choose not to have shadows at all.
This patch introduces shadows for mach-versatile whilst mach-ep93xx, mach-netx, mach-s3c64xx and plat-samsung retain unmodified interrupt maps.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Hartley Sweeten hsweeten@visionengravers.com Cc: Ryan Mallon rmallon@gmail.com Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben-linux@fluff.org Cc: Kukjin Kim kgene.kim@samsung.com Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: linux-samsung-soc@vger.kernel.org --- arch/arm/mach-ep93xx/core.c | 6 +- arch/arm/mach-netx/generic.c | 3 +- arch/arm/mach-s3c64xx/common.c | 6 +- arch/arm/mach-versatile/core.c | 9 +-- arch/arm/mach-versatile/include/mach/irqs.h | 76 ++++++++++++------------- arch/arm/plat-samsung/s5p-irq.c | 3 +- drivers/irqchip/irq-vic.c | 87 +++++++++++++++++++++++------ include/linux/irqchip/arm-vic.h | 8 ++- 8 files changed, 132 insertions(+), 66 deletions(-)
diff --git a/arch/arm/mach-ep93xx/core.c b/arch/arm/mach-ep93xx/core.c index 0e571f1..aa26411 100644 --- a/arch/arm/mach-ep93xx/core.c +++ b/arch/arm/mach-ep93xx/core.c @@ -185,8 +185,10 @@ void __init ep93xx_timer_init(void) *************************************************************************/ void __init ep93xx_init_irq(void) { - vic_init(EP93XX_VIC1_BASE, 0, EP93XX_VIC1_VALID_IRQ_MASK, 0); - vic_init(EP93XX_VIC2_BASE, 32, EP93XX_VIC2_VALID_IRQ_MASK, 0); + vic_init(EP93XX_VIC1_BASE, 0, VIC_FIQ_START_NONE, + EP93XX_VIC1_VALID_IRQ_MASK, 0); + vic_init(EP93XX_VIC2_BASE, 32, VIC_FIQ_START_NONE, + EP93XX_VIC2_VALID_IRQ_MASK, 0); }
diff --git a/arch/arm/mach-netx/generic.c b/arch/arm/mach-netx/generic.c index db25b0c..5398dcd 100644 --- a/arch/arm/mach-netx/generic.c +++ b/arch/arm/mach-netx/generic.c @@ -169,7 +169,8 @@ void __init netx_init_irq(void) { int irq;
- vic_init(io_p2v(NETX_PA_VIC), NETX_IRQ_VIC_START, ~0, 0); + vic_init(io_p2v(NETX_PA_VIC), NETX_IRQ_VIC_START, VIC_FIQ_START_NONE, + ~0, 0);
for (irq = NETX_IRQ_HIF_CHAINED(0); irq <= NETX_IRQ_HIF_LAST; irq++) { irq_set_chip_and_handler(irq, &netx_hif_chip, diff --git a/arch/arm/mach-s3c64xx/common.c b/arch/arm/mach-s3c64xx/common.c index 5c45aae..b98dd48 100644 --- a/arch/arm/mach-s3c64xx/common.c +++ b/arch/arm/mach-s3c64xx/common.c @@ -242,8 +242,10 @@ void __init s3c64xx_init_irq(u32 vic0_valid, u32 vic1_valid) printk(KERN_DEBUG "%s: initialising interrupts\n", __func__);
/* initialise the pair of VICs */ - vic_init(VA_VIC0, IRQ_VIC0_BASE, vic0_valid, IRQ_VIC0_RESUME); - vic_init(VA_VIC1, IRQ_VIC1_BASE, vic1_valid, IRQ_VIC1_RESUME); + vic_init(VA_VIC0, IRQ_VIC0_BASE, VIC_FIQ_START_NONE, vic0_valid, + IRQ_VIC0_RESUME); + vic_init(VA_VIC1, IRQ_VIC1_BASE, VIC_FIQ_START_NONE, vic1_valid, + IRQ_VIC1_RESUME); }
#define eint_offset(irq) ((irq) - IRQ_EINT(0)) diff --git a/arch/arm/mach-versatile/core.c b/arch/arm/mach-versatile/core.c index f2c89fb..3444ca8 100644 --- a/arch/arm/mach-versatile/core.c +++ b/arch/arm/mach-versatile/core.c @@ -108,7 +108,8 @@ void __init versatile_init_irq(void)
np = of_find_matching_node_by_address(NULL, vic_of_match, VERSATILE_VIC_BASE); - __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np); + __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, + np ? -1 : FIQ_VIC_START, ~0, 0, np);
writel(~0, VA_SIC_BASE + SIC_IRQ_ENABLE_CLEAR);
@@ -614,9 +615,9 @@ static struct pl022_ssp_controller ssp0_plat_data = { * These devices are connected via the DMA APB bridge */ #define SCI_IRQ { IRQ_SCIINT } -#define UART0_IRQ { IRQ_UARTINT0 } -#define UART1_IRQ { IRQ_UARTINT1 } -#define UART2_IRQ { IRQ_UARTINT2 } +#define UART0_IRQ { IRQ_UARTINT0, FIQ_UARTINT0 } +#define UART1_IRQ { IRQ_UARTINT1, FIQ_UARTINT1 } +#define UART2_IRQ { IRQ_UARTINT2, FIQ_UARTINT2 } #define SSP_IRQ { IRQ_SSPINT }
/* FPGA Primecells */ diff --git a/arch/arm/mach-versatile/include/mach/irqs.h b/arch/arm/mach-versatile/include/mach/irqs.h index 0fd771c..da3f919 100644 --- a/arch/arm/mach-versatile/include/mach/irqs.h +++ b/arch/arm/mach-versatile/include/mach/irqs.h @@ -60,43 +60,6 @@ #define IRQ_VICSOURCE31 (IRQ_VIC_START + INT_VICSOURCE31) #define IRQ_VIC_END (IRQ_VIC_START + 31)
-/* - * FIQ interrupts definitions are the same as the INT definitions. - */ -#define FIQ_WDOGINT INT_WDOGINT -#define FIQ_SOFTINT INT_SOFTINT -#define FIQ_COMMRx INT_COMMRx -#define FIQ_COMMTx INT_COMMTx -#define FIQ_TIMERINT0_1 INT_TIMERINT0_1 -#define FIQ_TIMERINT2_3 INT_TIMERINT2_3 -#define FIQ_GPIOINT0 INT_GPIOINT0 -#define FIQ_GPIOINT1 INT_GPIOINT1 -#define FIQ_GPIOINT2 INT_GPIOINT2 -#define FIQ_GPIOINT3 INT_GPIOINT3 -#define FIQ_RTCINT INT_RTCINT -#define FIQ_SSPINT INT_SSPINT -#define FIQ_UARTINT0 INT_UARTINT0 -#define FIQ_UARTINT1 INT_UARTINT1 -#define FIQ_UARTINT2 INT_UARTINT2 -#define FIQ_SCIINT INT_SCIINT -#define FIQ_CLCDINT INT_CLCDINT -#define FIQ_DMAINT INT_DMAINT -#define FIQ_PWRFAILINT INT_PWRFAILINT -#define FIQ_MBXINT INT_MBXINT -#define FIQ_GNDINT INT_GNDINT -#define FIQ_VICSOURCE21 INT_VICSOURCE21 -#define FIQ_VICSOURCE22 INT_VICSOURCE22 -#define FIQ_VICSOURCE23 INT_VICSOURCE23 -#define FIQ_VICSOURCE24 INT_VICSOURCE24 -#define FIQ_VICSOURCE25 INT_VICSOURCE25 -#define FIQ_VICSOURCE26 INT_VICSOURCE26 -#define FIQ_VICSOURCE27 INT_VICSOURCE27 -#define FIQ_VICSOURCE28 INT_VICSOURCE28 -#define FIQ_VICSOURCE29 INT_VICSOURCE29 -#define FIQ_VICSOURCE30 INT_VICSOURCE30 -#define FIQ_VICSOURCE31 INT_VICSOURCE31 - - /* * Secondary interrupt controller */ @@ -131,4 +94,41 @@ #define IRQ_GPIO3_START (IRQ_GPIO2_END + 1) #define IRQ_GPIO3_END (IRQ_GPIO3_START + 31)
-#define NR_IRQS (IRQ_GPIO3_END + 1) +/* + * FIQ interrupts definitions shadow the VIC INT definitions. + */ +#define FIQ_VIC_START (IRQ_GPIO3_END + 1) +#define FIQ_WDOGINT (INT_WDOGINT + FIQ_VIC_START) +#define FIQ_SOFTINT (INT_SOFTINT + FIQ_VIC_START) +#define FIQ_COMMRx (INT_COMMRx + FIQ_VIC_START) +#define FIQ_COMMTx (INT_COMMTx + FIQ_VIC_START) +#define FIQ_TIMERINT0_1 (INT_TIMERINT0_1 + FIQ_VIC_START) +#define FIQ_TIMERINT2_3 (INT_TIMERINT2_3 + FIQ_VIC_START) +#define FIQ_GPIOINT0 (INT_GPIOINT0 + FIQ_VIC_START) +#define FIQ_GPIOINT1 (INT_GPIOINT1 + FIQ_VIC_START) +#define FIQ_GPIOINT2 (INT_GPIOINT2 + FIQ_VIC_START) +#define FIQ_GPIOINT3 (INT_GPIOINT3 + FIQ_VIC_START) +#define FIQ_RTCINT (INT_RTCINT + FIQ_VIC_START) +#define FIQ_SSPINT (INT_SSPINT + FIQ_VIC_START) +#define FIQ_UARTINT0 (INT_UARTINT0 + FIQ_VIC_START) +#define FIQ_UARTINT1 (INT_UARTINT1 + FIQ_VIC_START) +#define FIQ_UARTINT2 (INT_UARTINT2 + FIQ_VIC_START) +#define FIQ_SCIINT (INT_SCIINT + FIQ_VIC_START) +#define FIQ_CLCDINT (INT_CLCDINT + FIQ_VIC_START) +#define FIQ_DMAINT (INT_DMAINT + FIQ_VIC_START) +#define FIQ_PWRFAILINT (INT_PWRFAILINT + FIQ_VIC_START) +#define FIQ_MBXINT (INT_MBXINT + FIQ_VIC_START) +#define FIQ_GNDINT (INT_GNDINT + FIQ_VIC_START) +#define FIQ_VICSOURCE21 (INT_VICSOURCE21 + FIQ_VIC_START) +#define FIQ_VICSOURCE22 (INT_VICSOURCE22 + FIQ_VIC_START) +#define FIQ_VICSOURCE23 (INT_VICSOURCE23 + FIQ_VIC_START) +#define FIQ_VICSOURCE24 (INT_VICSOURCE24 + FIQ_VIC_START) +#define FIQ_VICSOURCE25 (INT_VICSOURCE25 + FIQ_VIC_START) +#define FIQ_VICSOURCE26 (INT_VICSOURCE26 + FIQ_VIC_START) +#define FIQ_VICSOURCE27 (INT_VICSOURCE27 + FIQ_VIC_START) +#define FIQ_VICSOURCE28 (INT_VICSOURCE28 + FIQ_VIC_START) +#define FIQ_VICSOURCE29 (INT_VICSOURCE29 + FIQ_VIC_START) +#define FIQ_VICSOURCE30 (INT_VICSOURCE30 + FIQ_VIC_START) +#define FIQ_VICSOURCE31 (INT_VICSOURCE31 + FIQ_VIC_START) + +#define NR_IRQS (FIQ_VICSOURCE31 + 1) diff --git a/arch/arm/plat-samsung/s5p-irq.c b/arch/arm/plat-samsung/s5p-irq.c index ddfaca9..ddb1138 100644 --- a/arch/arm/plat-samsung/s5p-irq.c +++ b/arch/arm/plat-samsung/s5p-irq.c @@ -26,6 +26,7 @@ void __init s5p_init_irq(u32 *vic, u32 num_vic)
/* initialize the VICs */ for (irq = 0; irq < num_vic; irq++) - vic_init(VA_VIC(irq), VIC_BASE(irq), vic[irq], 0); + vic_init(VA_VIC(irq), VIC_BASE(irq), VIC_FIQ_START_NONE, + vic[irq], 0); #endif } diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index 7d35287..82bce53 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -56,6 +56,8 @@
#define VIC_PL192_VECT_ADDR 0xF00
+#define VIC_FIQ_SHADOW_OFFSET 32 + /** * struct vic_device - VIC PM device * @parent_irq: The parent IRQ number of the VIC if cascaded, or 0. @@ -81,8 +83,11 @@ struct vic_device { u32 soft_int; u32 protect; struct irq_domain *domain; + struct irq_domain *fiq_domain; };
+static DEFINE_RAW_SPINLOCK(irq_controller_lock); + /* we cannot allocate memory when VICs are initially registered */ static struct vic_device vic_devices[CONFIG_ARM_VIC_NR];
@@ -197,6 +202,9 @@ static int vic_irqdomain_map(struct irq_domain *d, unsigned int irq, { struct vic_device *v = d->host_data;
+ if (hwirq > VIC_FIQ_SHADOW_OFFSET) + hwirq -= VIC_FIQ_SHADOW_OFFSET; + /* Skip invalid IRQs, only register handlers for the real ones */ if (!(v->valid_sources & (1 << hwirq))) return -EPERM; @@ -277,7 +285,7 @@ static struct irq_domain_ops vic_irqdomain_ops = { * This also configures the IRQ domain for the VIC. */ static void __init vic_register(void __iomem *base, unsigned int parent_irq, - unsigned int irq, + unsigned int irq, int fiq, u32 valid_sources, u32 resume_sources, struct device_node *node) { @@ -307,6 +315,19 @@ static void __init vic_register(void __iomem *base, unsigned int parent_irq, for (i = 0; i < fls(valid_sources); i++) if (valid_sources & (1 << i)) irq_create_mapping(v->domain, i); + + /* create FIQ shadow mapping for each IRQ */ + if (fiq >= 0) { + v->fiq_domain = irq_domain_add_legacy( + node, fls(valid_sources), fiq, + VIC_FIQ_SHADOW_OFFSET, &vic_irqdomain_ops, v); + /* create an IRQ mapping for each valid IRQ */ + for (i = 0; i < fls(valid_sources); i++) + if (valid_sources & (1 << i)) + irq_create_mapping(v->fiq_domain, + i + VIC_FIQ_SHADOW_OFFSET); + } + /* If no base IRQ was passed, figure out our allocated base */ if (irq) v->irq = irq; @@ -314,10 +335,36 @@ static void __init vic_register(void __iomem *base, unsigned int parent_irq, v->irq = irq_find_mapping(v->domain, 0); }
+static inline bool vic_is_fiq(struct irq_data *d) +{ + return d->hwirq >= VIC_FIQ_SHADOW_OFFSET; +} + +static inline unsigned int vic_irq(struct irq_data *d) +{ + return d->hwirq & (VIC_FIQ_SHADOW_OFFSET-1); +} + +static void vic_set_fiq(struct irq_data *d, bool enable) +{ + void __iomem *base = irq_data_get_irq_chip_data(d); + unsigned int irq = vic_irq(d); + u32 val; + + raw_spin_lock(&irq_controller_lock); + val = readl(base + VIC_INT_SELECT); + if (enable) + val |= 1 << irq; + else + val &= ~(1 << irq); + writel(val, base + VIC_INT_SELECT); + raw_spin_unlock(&irq_controller_lock); +} + static void vic_ack_irq(struct irq_data *d) { void __iomem *base = irq_data_get_irq_chip_data(d); - unsigned int irq = d->hwirq; + unsigned int irq = vic_irq(d); writel(1 << irq, base + VIC_INT_ENABLE_CLEAR); /* moreover, clear the soft-triggered, in case it was the reason */ writel(1 << irq, base + VIC_INT_SOFT_CLEAR); @@ -326,17 +373,22 @@ static void vic_ack_irq(struct irq_data *d) static void vic_mask_irq(struct irq_data *d) { void __iomem *base = irq_data_get_irq_chip_data(d); - unsigned int irq = d->hwirq; + unsigned int irq = vic_irq(d); + if (vic_is_fiq(d)) + vic_set_fiq(d, false); writel(1 << irq, base + VIC_INT_ENABLE_CLEAR); }
static void vic_unmask_irq(struct irq_data *d) { void __iomem *base = irq_data_get_irq_chip_data(d); - unsigned int irq = d->hwirq; + unsigned int irq = vic_irq(d); + if (vic_is_fiq(d)) + vic_set_fiq(d, true); writel(1 << irq, base + VIC_INT_ENABLE); }
+ #if defined(CONFIG_PM) static struct vic_device *vic_from_irq(unsigned int irq) { @@ -355,7 +407,7 @@ static struct vic_device *vic_from_irq(unsigned int irq) static int vic_set_wake(struct irq_data *d, unsigned int on) { struct vic_device *v = vic_from_irq(d->irq); - unsigned int off = d->hwirq; + unsigned int off = vic_irq(d); u32 bit = 1 << off;
if (!v) @@ -413,7 +465,8 @@ static void __init vic_clear_interrupts(void __iomem *base) * and 020 within the page. We call this "second block". */ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, - u32 vic_sources, struct device_node *node) + int fiq_start, u32 vic_sources, + struct device_node *node) { unsigned int i; int vic_2nd_block = ((unsigned long)base & ~PAGE_MASK) != 0; @@ -439,12 +492,12 @@ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, writel(32, base + VIC_PL190_DEF_VECT_ADDR); }
- vic_register(base, 0, irq_start, vic_sources, 0, node); + vic_register(base, 0, irq_start, fiq_start, vic_sources, 0, node); }
void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, - struct device_node *node) + int fiq_start, u32 vic_sources, u32 resume_sources, + struct device_node *node) { unsigned int i; u32 cellid = 0; @@ -462,7 +515,7 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
switch(vendor) { case AMBA_VENDOR_ST: - vic_init_st(base, irq_start, vic_sources, node); + vic_init_st(base, irq_start, fiq_start, vic_sources, node); return; default: printk(KERN_WARNING "VIC: unknown vendor, continuing anyways\n"); @@ -479,7 +532,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
vic_init2(base);
- vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, node); + vic_register(base, parent_irq, irq_start, fiq_start, vic_sources, + resume_sources, node); }
/** @@ -490,9 +544,9 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, * @resume_sources: bitmask of interrupt sources to allow for resume */ void __init vic_init(void __iomem *base, unsigned int irq_start, - u32 vic_sources, u32 resume_sources) + int fiq_start, u32 vic_sources, u32 resume_sources) { - __vic_init(base, 0, irq_start, vic_sources, resume_sources, NULL); + __vic_init(base, 0, irq_start, -1, vic_sources, resume_sources, NULL); }
/** @@ -511,7 +565,7 @@ int __init vic_init_cascaded(void __iomem *base, unsigned int parent_irq, struct vic_device *v;
v = &vic_devices[vic_id]; - __vic_init(base, parent_irq, 0, vic_sources, resume_sources, NULL); + __vic_init(base, parent_irq, 0, -1, vic_sources, resume_sources, NULL); /* Return out acquired base */ return v->irq; } @@ -535,9 +589,10 @@ int __init vic_of_init(struct device_node *node, struct device_node *parent) of_property_read_u32(node, "valid-wakeup-mask", &wakeup_mask);
/* - * Passing 0 as first IRQ makes the simple domain allocate descriptors + * Passing 0 as first IRQ (and first FIQ) makes the domain allocate + * descriptors. */ - __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, node); + __vic_init(regs, 0, 0, -1, interrupt_mask, wakeup_mask, node);
return 0; } diff --git a/include/linux/irqchip/arm-vic.h b/include/linux/irqchip/arm-vic.h index ba46c79..fae480d 100644 --- a/include/linux/irqchip/arm-vic.h +++ b/include/linux/irqchip/arm-vic.h @@ -26,12 +26,16 @@ #define VIC_INT_ENABLE 0x10 /* 1 = enable, 0 = disable */ #define VIC_INT_ENABLE_CLEAR 0x14
+#define VIC_FIQ_START_NONE -1 + struct device_node; struct pt_regs;
void __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, struct device_node *node); -void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources); + int fiq_start, u32 vic_sources, u32 resume_sources, + struct device_node *node); +void vic_init(void __iomem *base, unsigned int irq_start, int fiq_start, + u32 vic_sources, u32 resume_sources); int vic_init_cascaded(void __iomem *base, unsigned int parent_irq, u32 vic_sources, u32 resume_sources);
From: Anton Vorontsov anton.vorontsov@linaro.org
Just move the macros into header file as we would want to use them for KGDB FIQ entry code.
The following macros were moved:
- svc_entry - usr_entry - kuser_cmpxchg_check - vector_stub
To make kuser_cmpxchg_check actually work across different files, we also have to make kuser_cmpxchg64_fixup global.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Frederic Weisbecker fweisbec@gmail.com --- arch/arm/kernel/entry-armv.S | 151 +------------------------------------ arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 150 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 1879e8d..ed95b95 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -140,53 +140,6 @@ ENDPROC(__und_invalid) * SVC mode handlers */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) -#define SPFIX(code...) code -#else -#define SPFIX(code...) -#endif - - .macro svc_entry, stack_hole=0 - UNWIND(.fnstart ) - UNWIND(.save {r0 - pc} ) - sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) -#ifdef CONFIG_THUMB2_KERNEL - SPFIX( str r0, [sp] ) @ temporarily saved - SPFIX( mov r0, sp ) - SPFIX( tst r0, #4 ) @ test original stack alignment - SPFIX( ldr r0, [sp] ) @ restored -#else - SPFIX( tst sp, #4 ) -#endif - SPFIX( subeq sp, sp, #4 ) - stmia sp, {r1 - r12} - - ldmia r0, {r3 - r5} - add r7, sp, #S_SP - 4 @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) - SPFIX( addeq r2, r2, #4 ) - str r3, [sp, #-4]! @ save the "real" r0 copied - @ from the exception stack - - mov r3, lr - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r2 - sp_svc - @ r3 - lr_svc - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - stmia r7, {r2 - r6} - -#ifdef CONFIG_TRACE_IRQFLAGS - bl trace_hardirqs_off -#endif - .endm - .align 5 __dabt_svc: svc_entry @@ -306,73 +259,8 @@ ENDPROC(__pabt_svc)
/* * User mode handlers - * - * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0 - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - .align 5 __dabt_usr: usr_entry @@ -819,6 +707,7 @@ __kuser_cmpxchg64: @ 0xffff0f60 ldmfd sp!, {r4, r5, r6, pc}
.text + .global kuser_cmpxchg64_fixup kuser_cmpxchg64_fixup: @ Called from kuser_cmpxchg_fixup. @ r4 = address of interrupted insn (must be preserved). @@ -960,44 +849,6 @@ __kuser_helper_end: * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. */ - .macro vector_stub, name, mode, correction=0 - .align 5 - -vector_\name: - .if \correction - sub lr, lr, #\correction - .endif - - @ - @ Save r0, lr_<exception> (parent PC) and spsr_<exception> - @ (parent CPSR) - @ - stmia sp, {r0, lr} @ save r0, lr - mrs lr, spsr - str lr, [sp, #8] @ save spsr - - @ - @ Prepare for SVC32 mode. IRQs remain disabled. - @ - mrs r0, cpsr - eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) - msr spsr_cxsf, r0 - - @ - @ the branch table must immediately follow this code - @ - and lr, lr, #0x0f - THUMB( adr r0, 1f ) - THUMB( ldr lr, [r0, lr, lsl #2] ) - mov r0, sp - ARM( ldr lr, [pc, lr, lsl #2] ) - movs pc, lr @ branch to handler in SVC mode -ENDPROC(vector_\name) - - .align 2 - @ handler addresses follow this label -1: - .endm
.section .stubs, "ax", %progbits __stubs_start: diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 1420725..ab04a67 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -352,3 +352,167 @@ scno .req r7 @ syscall number tbl .req r8 @ syscall table pointer why .req r8 @ Linux syscall (!= 0) tsk .req r9 @ current thread_info + +/* + * SVC mode handler macros + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) +#define SPFIX(code...) code +#else +#define SPFIX(code...) +#endif + + .macro svc_entry, stack_hole=0 + UNWIND(.fnstart ) + UNWIND(.save {r0 - pc} ) + sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) +#ifdef CONFIG_THUMB2_KERNEL + SPFIX( str r0, [sp] ) @ temporarily saved + SPFIX( mov r0, sp ) + SPFIX( tst r0, #4 ) @ test original stack alignment + SPFIX( ldr r0, [sp] ) @ restored +#else + SPFIX( tst sp, #4 ) +#endif + SPFIX( subeq sp, sp, #4 ) + stmia sp, {r1 - r12} + + ldmia r0, {r3 - r5} + add r7, sp, #S_SP - 4 @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) + SPFIX( addeq r2, r2, #4 ) + str r3, [sp, #-4]! @ save the "real" r0 copied + @ from the exception stack + + mov r3, lr + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r2 - sp_svc + @ r3 - lr_svc + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + stmia r7, {r2 - r6} + +#ifdef CONFIG_TRACE_IRQFLAGS + bl trace_hardirqs_off +#endif + .endm + +/* + * User mode handler macros + * + * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0 + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +/* + * Vector stubs macro. + */ + .macro vector_stub, name, mode, correction=0 + .align 5 + +vector_\name: + .if \correction + sub lr, lr, #\correction + .endif + + @ + @ Save r0, lr_<exception> (parent PC) and spsr_<exception> + @ (parent CPSR) + @ + stmia sp, {r0, lr} @ save r0, lr + mrs lr, spsr + str lr, [sp, #8] @ save spsr + + @ + @ Prepare for SVC32 mode. IRQs remain disabled. + @ + mrs r0, cpsr + eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) + msr spsr_cxsf, r0 + + @ + @ the branch table must immediately follow this code + @ + and lr, lr, #0x0f + THUMB( adr r0, 1f ) + THUMB( ldr lr, [r0, lr, lsl #2] ) + mov r0, sp + ARM( ldr lr, [pc, lr, lsl #2] ) + movs pc, lr @ branch to handler in SVC mode +ENDPROC(vector_\name) + + .align 2 + @ handler addresses follow this label +1: + .endm + +
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Dave Martin Dave.Martin@arm.com --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++++ arch/arm/include/asm/kgdb.h | 7 +++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/kgdb_fiq.c | 117 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++++++++++++++++ 6 files changed, 232 insertions(+) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index db3c541..419fd0a 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -307,6 +307,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -356,6 +357,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 6a2bcfd..1f1bec1 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,24 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be + enabled with kgdb_fiq.enable=1 kernel command line option. + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..5de21f01 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,11 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern char kgdb_fiq_handler; +extern char kgdb_fiq_handler_end; +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs); +extern int kgdb_register_fiq(unsigned int fiq); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 040619c..251f651 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -67,6 +67,7 @@ endif obj-$(CONFIG_OABI_COMPAT) += sys_oabi-compat.o obj-$(CONFIG_ARM_THUMBEE) += thumbee.o obj-$(CONFIG_KGDB) += kgdb.o +obj-$(CONFIG_KGDB_FIQ) += kgdb_fiq_entry.o kgdb_fiq.o obj-$(CONFIG_ARM_UNWIND) += unwind.o obj-$(CONFIG_HAVE_TCM) += tcm.o obj-$(CONFIG_OF) += devtree.o diff --git a/arch/arm/kernel/kgdb_fiq.c b/arch/arm/kernel/kgdb_fiq.c new file mode 100644 index 0000000..b236409 --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq.c @@ -0,0 +1,117 @@ +/* + * KGDB FIQ + * + * Copyright 2010 Google, Inc. + * Arve Hjønnevåg arve@android.com + * Colin Cross ccross@android.com + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/hardirq.h> +#include <linux/atomic.h> +#include <linux/kdb.h> +#include <linux/kgdb.h> +#include <asm/fiq.h> +#include <asm/exception.h> + +static int kgdb_fiq_enabled; +module_param_named(enable, kgdb_fiq_enabled, int, 0600); +MODULE_PARM_DESC(enable, "set to 1 to enable FIQ KGDB"); + +static unsigned int kgdb_fiq; + +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs) +{ + if (kgdb_nmi_poll_knock()) { + nmi_enter(); + kgdb_handle_exception(1, 0, 0, regs); + nmi_exit(); + } + + eoi_fiq(kgdb_fiq); +} + +static struct fiq_handler kgdb_fiq_desc = { + .name = "kgdb", +}; + +static long kgdb_fiq_setup_stack(void *info) +{ + struct pt_regs regs; + + regs.ARM_sp = __get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER) + + THREAD_START_SP; + WARN_ON(!regs.ARM_sp); + + set_fiq_regs(®s); + return 0; +} + +/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * This function manages NMIs that usually cause KGDB to enter. That is, not + * all NMIs should be enabled or disabled, but only those that issue + * kgdb_handle_exception(). + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + static atomic_t cnt; + int ret; + + ret = atomic_add_return(on ? 1 : -1, &cnt); + if (ret > 1 && on) { + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + WARN_ON(1); + return; + } + + if (ret > 0) + enable_fiq(kgdb_fiq); + else + disable_fiq(kgdb_fiq); +} + +int kgdb_register_fiq(unsigned int fiq) +{ + int err; + int cpu; + + if (!kgdb_fiq_enabled) + return -ENODEV; + + kgdb_fiq = fiq; + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + for_each_possible_cpu(cpu) + work_on_cpu(cpu, kgdb_fiq_setup_stack, NULL); + + set_fiq_handler(&kgdb_fiq_handler, + &kgdb_fiq_handler_end - &kgdb_fiq_handler); + + arch_kgdb_ops.enable_nmi = kgdb_fiq_enable_nmi; + return 0; +} diff --git a/arch/arm/kernel/kgdb_fiq_entry.S b/arch/arm/kernel/kgdb_fiq_entry.S new file mode 100644 index 0000000..d6becca --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq_entry.S @@ -0,0 +1,87 @@ +/* + * KGDB FIQ entry + * + * Copyright 1996,1997,1998 Russell King. + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/memory.h> +#include <asm/unwind.h> +#include "entry-header.S" + + .text + +@ This is needed for usr_entry/alignment_trap +.LCcralign: + .long cr_alignment +.LCdohandle: + .long kgdb_fiq_do_handle + + .macro fiq_handler + ldr r1, =.LCdohandle + mov r0, sp + adr lr, BSYM(9997f) + ldr pc, [r1] +9997: + .endm + + .align 5 +__fiq_svc: + svc_entry + fiq_handler + mov r0, sp + ldmib r0, {r1 - r14} + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + .ltorg + + .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + .ltorg + + .global kgdb_fiq_handler +kgdb_fiq_handler: + + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_svc @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f + + .global kgdb_fiq_handler_end +kgdb_fiq_handler_end:
If the AMBA bus has provided the pl011 with a FIQ resource (i.e. a second IRQ) then speculatively register it with KGDB when the polling driver is initialized.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the amba-pl011 driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/amba-pl011.c | 101 ++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 41 deletions(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index dacf0a0..d34c72c 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -58,6 +58,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/sizes.h> #include <linux/io.h> +#include <linux/kgdb.h>
#define UART_NR 14
@@ -158,6 +159,7 @@ struct uart_amba_port { unsigned int old_cr; /* state during shutdown */ bool autorts; char type[12]; + int fiq; #ifdef CONFIG_DMA_ENGINE /* DMA stuff */ bool using_tx_dma; @@ -1416,8 +1418,63 @@ static void pl011_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&uap->port.lock, flags); }
+static int pl011_hwinit(struct uart_port *port) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + int retval; + + /* Optionaly enable pins to be muxed in and configured */ + pinctrl_pm_select_default_state(port->dev); + + /* + * Try to enable the clock producer. + */ + retval = clk_prepare_enable(uap->clk); + if (retval) + goto out; + + uap->port.uartclk = clk_get_rate(uap->clk); + + /* Clear pending error and receive interrupts */ + writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | + UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); + + /* + * Save interrupts enable mask, and enable RX interrupts in case if + * the interrupt is used for NMI entry. + */ + uap->im = readw(uap->port.membase + UART011_IMSC); + writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); + + if (dev_get_platdata(uap->port.dev)) { + struct amba_pl011_data *plat; + + plat = dev_get_platdata(uap->port.dev); + if (plat->init) + plat->init(); + } + return 0; + out: + return retval; +} + #ifdef CONFIG_CONSOLE_POLL
+static int pl011_poll_init(struct uart_port *port) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + int retval; + + retval = pl011_hwinit(port); + +#ifdef CONFIG_KGDB_FIQ + if (retval == 0 && uap->fiq > 0) + kgdb_register_fiq(uap->fiq); +#endif + + return retval; +} + static void pl011_quiesce_irqs(struct uart_port *port) { struct uart_amba_port *uap = (struct uart_amba_port *)port; @@ -1471,46 +1528,6 @@ static void pl011_put_poll_char(struct uart_port *port,
#endif /* CONFIG_CONSOLE_POLL */
-static int pl011_hwinit(struct uart_port *port) -{ - struct uart_amba_port *uap = (struct uart_amba_port *)port; - int retval; - - /* Optionaly enable pins to be muxed in and configured */ - pinctrl_pm_select_default_state(port->dev); - - /* - * Try to enable the clock producer. - */ - retval = clk_prepare_enable(uap->clk); - if (retval) - goto out; - - uap->port.uartclk = clk_get_rate(uap->clk); - - /* Clear pending error and receive interrupts */ - writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | - UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); - - /* - * Save interrupts enable mask, and enable RX interrupts in case if - * the interrupt is used for NMI entry. - */ - uap->im = readw(uap->port.membase + UART011_IMSC); - writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); - - if (dev_get_platdata(uap->port.dev)) { - struct amba_pl011_data *plat; - - plat = dev_get_platdata(uap->port.dev); - if (plat->init) - plat->init(); - } - return 0; - out: - return retval; -} - static void pl011_write_lcr_h(struct uart_amba_port *uap, unsigned int lcr_h) { writew(lcr_h, uap->port.membase + uap->lcrh_rx); @@ -1890,7 +1907,7 @@ static struct uart_ops amba_pl011_pops = { .config_port = pl011_config_port, .verify_port = pl011_verify_port, #ifdef CONFIG_CONSOLE_POLL - .poll_init = pl011_hwinit, + .poll_init = pl011_poll_init, .poll_get_char = pl011_get_poll_char, .poll_put_char = pl011_put_poll_char, #endif @@ -2139,6 +2156,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) uap->port.membase = base; uap->port.iotype = UPIO_MEM; uap->port.irq = dev->irq[0]; + uap->fiq = dev->irq[1]; uap->port.fifosize = uap->fifosize; uap->port.ops = &amba_pl011_pops; uap->port.flags = UPF_BOOT_AUTOCONF; @@ -2169,6 +2187,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) uart_unregister_driver(&amba_reg); pl011_dma_remove(uap); } + out: return ret; }
If the platform bus has provided the st-asc with a FIQ resource (i.e. a second IRQ) then speculatively register it with KGDB when the polling driver is initialized.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the st-asc driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger). This unmask is copied from similar code in amba-pl011.c .
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/st-asc.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index c7f61ac..328720f 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -30,6 +30,7 @@ #include <linux/of_platform.h> #include <linux/serial_core.h> #include <linux/clk.h> +#include <linux/kgdb.h>
#define DRIVER_NAME "st-asc" #define ASC_SERIAL_NAME "ttyAS" @@ -39,6 +40,7 @@ struct asc_port { struct uart_port port; struct clk *clk; + int fiq; unsigned int hw_flow_control:1; unsigned int force_m1:1; }; @@ -613,6 +615,26 @@ asc_verify_port(struct uart_port *port, struct serial_struct *ser) }
#ifdef CONFIG_CONSOLE_POLL + +#ifdef CONFIG_KGDB_FIQ +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int asc_poll_init(struct uart_port *port) +{ + struct asc_port *ascport = container_of(port, struct asc_port, port); + + /* register the FIQ with kgdb */ + if (ascport->fiq >= 0) + kgdb_register_fiq(ascport->fiq); + + /* enable RX interrupts in case the interrupt is used for NMI entry. */ + asc_enable_rx_interrupts(port); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + /* * Console polling routines for writing and reading from the uart while * in an interrupt or debug context (i.e. kgdb). @@ -656,11 +678,15 @@ static struct uart_ops asc_uart_ops = { .verify_port = asc_verify_port, .pm = asc_pm, #ifdef CONFIG_CONSOLE_POLL +#ifdef CONFIG_KGDB_FIQ + .poll_init = asc_poll_init, +#endif /* CONFIG_KGDB_FIQ */ .poll_get_char = asc_get_poll_char, .poll_put_char = asc_put_poll_char, #endif /* CONFIG_CONSOLE_POLL */ };
+ static int asc_init_port(struct asc_port *ascport, struct platform_device *pdev) { @@ -673,6 +699,7 @@ static int asc_init_port(struct asc_port *ascport, port->fifosize = ASC_FIFO_SIZE; port->dev = &pdev->dev; port->irq = platform_get_irq(pdev, 0); + ascport->fiq = platform_get_irq(pdev, 1);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); port->membase = devm_ioremap_resource(&pdev->dev, res);
Hi Dan,
On 23/05/14 14:57, Daniel Thompson wrote:
If the platform bus has provided the st-asc with a FIQ resource (i.e. a second IRQ) then speculatively register it with KGDB when the polling driver is initialized.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the st-asc driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger). This unmask is copied from similar code in amba-pl011.c .
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org
drivers/tty/serial/st-asc.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+)
Also this new entry should be documented in the st-asc dt bindings.
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index c7f61ac..328720f 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -30,6 +30,7 @@ #include <linux/of_platform.h> #include <linux/serial_core.h> #include <linux/clk.h> +#include <linux/kgdb.h>
#define DRIVER_NAME "st-asc" #define ASC_SERIAL_NAME "ttyAS" @@ -39,6 +40,7 @@ struct asc_port { struct uart_port port; struct clk *clk;
- int fiq; unsigned int hw_flow_control:1; unsigned int force_m1:1; };
...
#ifdef CONFIG_CONSOLE_POLL +#ifdef CONFIG_KGDB_FIQ
- .poll_init = asc_poll_init,
+#endif /* CONFIG_KGDB_FIQ */ .poll_get_char = asc_get_poll_char, .poll_put_char = asc_put_poll_char, #endif /* CONFIG_CONSOLE_POLL */ };
no need of extra new line here.
static int asc_init_port(struct asc_port *ascport, struct platform_device *pdev) { @@ -673,6 +699,7 @@ static int asc_init_port(struct asc_port *ascport, port->fifosize = ASC_FIFO_SIZE; port->dev = &pdev->dev; port->irq = platform_get_irq(pdev, 0);
- ascport->fiq = platform_get_irq(pdev, 1);
Probably its better to give names to the irq resources and get them. As forcing the order in DT is always tricky highly possible for an error.
Thanks, srini
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); port->membase = devm_ioremap_resource(&pdev->dev, res);
This patchset makes it possible to use the kgdb NMI infrastructure on ARM platforms by providing a mutli-platform compatible means for drivers to manage FIQ routings.
First a quick summary of how the mainline kgdb NMI infrastructure (mostly found in drivers/tty/serial/kgdb_nmi.c) works. The kgdb infrastructure will re-route the kgdb console UART's interrupt signal from IRQ to FIQ. Naturally the UART will no longer function normally and will instead be managed by kgdb using the polled I/O functions. Any character delivered to the UART causes the kgdb handler function to be called.
Note that, within this patchset a serial driver explicitly consents (or not) to the abuse outlined above by calling the appropriate registration during the .poll_init() callback.
The patch set is structured as follows:
The first five patches modify the interrupt system to make it easier to describe FIQ. The key concept is that a call to enable_fiq() must modify the IRQ/FIQ routing to that the FIQ really is enabled (rather than spuriously delivering the signal on the IRQ). To achieve this each interrupt controller registers two virqs for each hwirq (allowing IRQ and FIQ to be readily distinguished) and registers the mappings using a new call, fiq_add_mapping().
The next two patches (6 and 7) provide kgdb with a FIQ handler and a means for serial drivers to register their FIQ with kgdb.
Finally two example serial drivers are modified in order to register their FIQs with kgdb.
Major remaining TODO item is to modify the code to halt the other CPUs; at present this code sends IPIs (which use a normal IRQ) and busy waits for the other CPUs to halt. This means the benefits of invoking the debugger via NMI are not yet realized on SMP systems. However I plan to tackle that later (i.e. when there's some consensus on whether this approach is the right way to handle FIQ).
Changes since v2:
* Use flexible mappings to link a normal virq to a FIQ virq. This replaces the device tree proposals from the previous RFC (thanks Russell King and Rob Herring).
* Reviewed all use of spin locks within .irq_eoi callbacks (and fixed the issue identified). Added comments to the FIQ registration functions making clear the requirements imposed on interrupt controller that call the FIQ API (thanks Russell King).
* Fixed a few whitespace issues (thanks Srinivas Kandagatla)
* ARM64/defconfig build tests (no problems found)
Changes since v1:
* Fully fledged multi-platform
* Tested for correct FIQ operation on STiH416/B2020 (Cortex A9), qemu/versatile and qemu/vexpress-a15 (with self-written mods to the GIC model to support FIQ).
* Regression tested (and resulting bugs fixed) on qemu/versatile+DT and qemu/integreatorcp.
Anton Vorontsov (2): ARM: Move some macros from entry-armv to entry-header ARM: Add KGDB/KDB FIQ debugger generic code
Daniel Thompson (7): arm: fiq: arbitrary mappings from IRQ to FIQ virqs arm: fiq: Allow EOI to be communicated to the intc irqchip: gic: Provide support for interrupt grouping irqchip: gic: Introduce shadow irqs for FIQ irqchip: vic: Introduce shadow irqs for FIQ serial: amba-pl011: Pass on FIQ information to KGDB. serial: asc: Add support for KGDB's FIQ/NMI mode
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 +++ arch/arm/include/asm/fiq.h | 4 + arch/arm/include/asm/kgdb.h | 7 ++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/entry-armv.S | 151 +------------------------ arch/arm/kernel/entry-header.S | 164 ++++++++++++++++++++++++++++ arch/arm/kernel/fiq.c | 85 +++++++++++++- arch/arm/kernel/kgdb_fiq.c | 124 +++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++ arch/arm/mach-ep93xx/core.c | 6 +- arch/arm/mach-netx/generic.c | 3 +- arch/arm/mach-s3c64xx/common.c | 6 +- arch/arm/mach-versatile/core.c | 9 +- arch/arm/mach-versatile/include/mach/irqs.h | 5 +- arch/arm/plat-samsung/s5p-irq.c | 3 +- drivers/irqchip/irq-gic.c | 97 ++++++++++++++-- drivers/irqchip/irq-vic.c | 102 ++++++++++++++--- drivers/tty/serial/amba-pl011.c | 99 ++++++++++------- drivers/tty/serial/st-asc.c | 23 ++++ include/linux/irqchip/arm-gic.h | 3 + include/linux/irqchip/arm-vic.h | 8 +- 22 files changed, 770 insertions(+), 237 deletions(-) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to gain access to FIQ virqs.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org --- arch/arm/include/asm/fiq.h | 2 ++ arch/arm/kernel/fiq.c | 57 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..75d98c6 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -36,8 +36,10 @@ struct fiq_handler { extern int claim_fiq(struct fiq_handler *f); extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); +extern struct irq_data *lookup_fiq_irq_data(int fiq); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern void fiq_add_mapping(int irq, int fiq_virq, unsigned int length);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..177939c 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,8 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h> +#include <linux/radix-tree.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -53,6 +55,9 @@ })
static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_virq_tree, GFP_KERNEL); +static DEFINE_MUTEX(fiq_virq_mutex);
/* Default reacquire function * - we always relinquish FIQ control @@ -60,8 +65,11 @@ static unsigned long no_fiq_insn; */ static int fiq_def_op(void *ref, int relinquish) { - if (!relinquish) + if (!relinquish) { + unsigned offset = FIQ_OFFSET; + no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); set_fiq_handler(&no_fiq_insn, sizeof(no_fiq_insn)); + }
return 0; } @@ -127,16 +135,33 @@ void release_fiq(struct fiq_handler *f) while (current_fiq->fiq_op(current_fiq->dev_id, 0)); }
-static int fiq_start; +struct irq_data *lookup_fiq_irq_data(int fiq) +{ + struct irq_data *d; + + if (fiq_start != -1) + return irq_get_irq_data(fiq + fiq_start); + + rcu_read_lock(); + d = radix_tree_lookup(&fiq_virq_tree, fiq); + rcu_read_unlock(); + return d; +}
void enable_fiq(int fiq) { - enable_irq(fiq + fiq_start); + struct irq_data *d = lookup_fiq_irq_data(fiq); + if (WARN_ON(!d)) + return; + enable_irq(d->irq); }
void disable_fiq(int fiq) { - disable_irq(fiq + fiq_start); + struct irq_data *d = lookup_fiq_irq_data(fiq); + if (WARN_ON(!d)) + return; + disable_irq(d->irq); }
EXPORT_SYMBOL(set_fiq_handler); @@ -144,12 +169,32 @@ EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(claim_fiq); EXPORT_SYMBOL(release_fiq); +EXPORT_SYMBOL(lookup_fiq_irq_data); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq);
+/* + * Add a mapping between a normal IRQ and a FIQ shadow. + */ +void fiq_add_mapping(int irq, int fiq_virq, unsigned int length) +{ + int i; + + /* fiq_add_mapping can't be mixed with init_FIQ */ + BUG_ON(fiq_start != -1); + + mutex_lock(&fiq_virq_mutex); + for (i = 0; i < length; i++) { + int err = radix_tree_insert(&fiq_virq_tree, irq + i, + irq_get_irq_data(fiq_virq + i)); + if (err) + pr_err("fiq: Cannot map %d to %d\n", irq + i, + fiq_virq + i); + } + mutex_unlock(&fiq_virq_mutex); +} + void __init init_FIQ(int start) { - unsigned offset = FIQ_OFFSET; - no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); fiq_start = start; }
On Thu, Jun 05, 2014 at 10:53:06AM +0100, Daniel Thompson wrote:
static int fiq_def_op(void *ref, int relinquish) {
- if (!relinquish)
- if (!relinquish) {
unsigned offset = FIQ_OFFSET;
set_fiq_handler(&no_fiq_insn, sizeof(no_fiq_insn));no_fiq_insn = *(unsigned long *)(0xffff0000 + offset);
- }
...
void __init init_FIQ(int start) {
- unsigned offset = FIQ_OFFSET;
- no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); fiq_start = start;
}
This is wrong - when the default handler is "reinstalled", this change has the effect that we read the first instruction of the existing handler, and then write that same instruction back, rather than replacing the first instruction with the value that was there at boot.
On 05/06/14 12:51, Russell King - ARM Linux wrote:
On Thu, Jun 05, 2014 at 10:53:06AM +0100, Daniel Thompson wrote:
static int fiq_def_op(void *ref, int relinquish) {
- if (!relinquish)
- if (!relinquish) {
unsigned offset = FIQ_OFFSET;
set_fiq_handler(&no_fiq_insn, sizeof(no_fiq_insn));no_fiq_insn = *(unsigned long *)(0xffff0000 + offset);
- }
...
void __init init_FIQ(int start) {
- unsigned offset = FIQ_OFFSET;
- no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); fiq_start = start;
}
This is wrong - when the default handler is "reinstalled", this change has the effect that we read the first instruction of the existing handler, and then write that same instruction back, rather than replacing the first instruction with the value that was there at boot.
Thanks. I'll fix this.
On Thu, Jun 5, 2014 at 11:53 AM, Daniel Thompson daniel.thompson@linaro.org wrote:
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to gain access to FIQ virqs.
I always had a big problem with the term "virq" which I take to mean "virtual IRQ".
The distinction is:
- Hardware IRQ offsets/numbers - a number encoded in HW specs which we usually call hwirqs
- Linux IRQ numbers - just some number
Sometimes, just sometimes, the Linux IRQ number matches the HW numbers, and then I guess the IRQ is regarded "non-virtual". The only real effect being that the numbers shown in /proc/interrupts are the same in both columns... the leftmost column with the Linux IRQ number and the one right next to the IRQ controller name which is the hwirq local offset.
But all Linux IRQ numbers are virtual by definition. It's just some number that the kernel use as a cookie to look up the irq_desc. And one day we may get rid of IRQ numbers altogether.
I would prefer just to s/virq/irq/g on this patch or if that is disturbing something more to the point like s/virq/linux_irq/g.
Maybe this is a pointless battle, but still...
Yours, Linus Walleij
On 12/06/14 09:37, Linus Walleij wrote:
On Thu, Jun 5, 2014 at 11:53 AM, Daniel Thompson daniel.thompson@linaro.org wrote:
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to gain access to FIQ virqs.
I always had a big problem with the term "virq" which I take to mean "virtual IRQ".
So do I...
That said, I think of it as the virtual as in virtual memory rather the virtual contained within virtualization.
The distinction is:
Hardware IRQ offsets/numbers - a number encoded in HW specs which we usually call hwirqs
Linux IRQ numbers - just some number
Sometimes, just sometimes, the Linux IRQ number matches the HW numbers, and then I guess the IRQ is regarded "non-virtual". The only real effect being that the numbers shown in /proc/interrupts are the same in both columns... the leftmost column with the Linux IRQ number and the one right next to the IRQ controller name which is the hwirq local offset.
But all Linux IRQ numbers are virtual by definition. It's just some number that the kernel use as a cookie to look up the irq_desc. And one day we may get rid of IRQ numbers altogether.
I would prefer just to s/virq/irq/g on this patch or if that is disturbing something more to the point like s/virq/linux_irq/g.
Maybe this is a pointless battle, but still...
I chose the term not because I have a personal attachement but because that is how they are spelt in the C symbols within irqdomain.[ch] which my patch relies upon heavily. The english documentation and comments is (very nearly) strict about using "Linux IRQ" so I'll happily change all the English text.
However...
I don't fancy s/virq/irq/ since this risks confusion between Linux irq and ARM IRQ and I don't fancy s/virq/linux_irq/ because in the kernel today virq is ~750x more frequenctly used than 'linux_irq' (git grep and wc suggests its 1507 versus 2).
How strongly do you feel about this?
On Thu, Jun 5, 2014 at 4:53 AM, Daniel Thompson daniel.thompson@linaro.org wrote:
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to gain access to FIQ virqs.
I don't get why you need a separate linux irq numbers for FIQ. Isn't enabling FIQ simply a property of an irq like edge vs. level trigger? Also, given the constraints on FIQ, we can't really have more that 1 IRQ assigned to FIQ.
Rob
On 13/06/14 15:29, Rob Herring wrote:
On Thu, Jun 5, 2014 at 4:53 AM, Daniel Thompson daniel.thompson@linaro.org wrote:
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to gain access to FIQ virqs.
I don't get why you need a separate linux irq numbers for FIQ. Isn't enabling FIQ simply a property of an irq like edge vs. level trigger? Also, given the constraints on FIQ, we can't really have more that 1 IRQ assigned to FIQ.
No particular reason. I mostly went that way because it mimics the effect of fiq_start on enable_fiq/disable_fiq whilst supporting multi-platform.
I'm tempted to keep the radix tree in the FIQ infrastructure but rather than messing about with shadow virqs use it to lookup a fiq_chip structure. I think this would keep a clean separation between the ARM centric (and slightly weird) FIQ from the generic irq code.
Daniel.
Modern ARM systems require an EOI to be sent to the interrupt controller on completion of both IRQ and FIQ. The FIQ code currently does not provide any API to perform this. This patch provides this API, implemented by hooking into main irq driver in a similar way to the existing enable_fiq()/disable_fiq().
All existing in-kernel callers of init_FIQ() and fiq_add_mapping() have been reviewed to check they meet the documented restriction.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org --- arch/arm/include/asm/fiq.h | 2 ++ arch/arm/kernel/fiq.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index 75d98c6..10d22eb 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -39,6 +39,8 @@ extern void set_fiq_handler(void *start, unsigned int length); extern struct irq_data *lookup_fiq_irq_data(int fiq); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern void eoi_fiq_with_irq_data(struct irq_data *d); +extern void eoi_fiq(int fiq); extern void fiq_add_mapping(int irq, int fiq_virq, unsigned int length);
/* helpers defined in fiqasm.S: */ diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 177939c..ed01162 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -164,6 +164,25 @@ void disable_fiq(int fiq) disable_irq(d->irq); }
+/* This call ends a FIQ and does not perform radix tree lookups. Use this + * if the FIQ interrupt rate is expected to be extremely high. + */ +void eoi_fiq_with_irq_data(struct irq_data *irq_data) +{ + struct irq_chip *chip = irq_data_get_irq_chip(irq_data); + if (chip->irq_eoi) + chip->irq_eoi(irq_data); +} +EXPORT_SYMBOL(eoi_fiq_with_irq_data); + +void eoi_fiq(int fiq) +{ + struct irq_data *d = lookup_fiq_irq_data(fiq); + BUG_ON(!d); + eoi_fiq_with_irq_data(d); +} +EXPORT_SYMBOL(eoi_fiq); + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -175,6 +194,9 @@ EXPORT_SYMBOL(disable_fiq);
/* * Add a mapping between a normal IRQ and a FIQ shadow. + * + * By providing a mapping the interrupt controller is guaranteeing not + * to use spin locks from .irq_eoi */ void fiq_add_mapping(int irq, int fiq_virq, unsigned int length) { @@ -194,6 +216,12 @@ void fiq_add_mapping(int irq, int fiq_virq, unsigned int length) mutex_unlock(&fiq_virq_mutex); }
+/* + * Set the offset between normal IRQs and their FIQ shadows. + * + * By providing an offset the interrupt controller is guaranteeing not + * to use spin locks from .irq_eoi + */ void __init init_FIQ(int start) { fiq_start = start;
All GIC hardware except GICv1 without TrustZone support provides a means to group exceptions into group 0 (which can optionally be signally using use FIQ) and group 1. The kernel currently provides no means to exploit this. This patch alters the initialization of the GIC to place all interrupts into group 1, this is a foundational requirement to meaningfully use FIQ.
Note that the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world". This allows grouping to be used to allow hardware peripherals to send interrupts into the secure world.
On systems without TrustZone support the kernel has the power to route interrupt sources to FIQ, potentially allowing a driver to exploit the NMI-like properties of FIQ.
The registers involved are RAZ/WI when unimplemented or protected by security policy. This patch therefore applies grouping unconditionally.
Tested on a qemu GICv2 model (self-written from GICv2 spec) and an STiH416 (ARM Cortex A9, GICv1, TZ).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com Acked-by: Dirk Behme dirk.behme@de.bosch.com --- drivers/irqchip/irq-gic.c | 35 ++++++++++++++++++++++++++++++----- include/linux/irqchip/arm-gic.h | 3 +++ 2 files changed, 33 insertions(+), 5 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 57d165e..aa8efe4 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -408,13 +408,27 @@ static void __init gic_dist_init(struct gic_chip_data *gic) writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4);
/* + * Set all global interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers a read-as-zero/write-ignored. + */ + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, base + GIC_DIST_IGROUP + i * 4 / 32); + + /* * Disable all interrupts. Leave the PPI and SGIs alone * as these enables are banked registers. */ for (i = 32; i < gic_irqs; i += 32) writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4 / 32);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + writel_relaxed(GIC_DIST_CTRL_ENABLE_GRP0_BIT | + GIC_DIST_CTRL_ENABLE_GRP1_BIT, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -452,8 +466,16 @@ static void gic_cpu_init(struct gic_chip_data *gic) for (i = 0; i < 32; i += 4) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4 / 4);
+ /* + * Set all PPI and SGI interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers are read-as-zero/write-ignored. + */ + writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + writel_relaxed(0x1f, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -537,7 +559,9 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(GIC_DIST_CTRL_ENABLE_GRP0_BIT | + GIC_DIST_CTRL_ENABLE_GRP1_BIT, + dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -594,7 +618,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -670,7 +694,8 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + writel_relaxed(map << 16 | irq | 0x8000, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 7ed92d0..919502f 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -37,6 +37,9 @@ #define GIC_DIST_SGI_PENDING_CLEAR 0xf10 #define GIC_DIST_SGI_PENDING_SET 0xf20
+#define GIC_DIST_CTRL_ENABLE_GRP0_BIT (1 << 0) +#define GIC_DIST_CTRL_ENABLE_GRP1_BIT (1 << 1) + #define GICH_HCR 0x0 #define GICH_VTR 0x4 #define GICH_VMCR 0x8
On Thu, 5 Jun 2014, Daniel Thompson wrote:
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 57d165e..aa8efe4 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -408,13 +408,27 @@ static void __init gic_dist_init(struct gic_chip_data *gic) writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4); /*
* Set all global interrupts to be group 1.
*
* If grouping is not available (not implemented or prohibited by
* security mode) these registers a read-as-zero/write-ignored.
*/
- for (i = 32; i < gic_irqs; i += 32)
writel_relaxed(0xffffffff, base + GIC_DIST_IGROUP + i * 4 / 32);
- /*
*/ for (i = 32; i < gic_irqs; i += 32) writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4 / 32);
- Disable all interrupts. Leave the PPI and SGIs alone
- as these enables are banked registers.
- writel_relaxed(1, base + GIC_DIST_CTRL);
- /*
* Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only,
* bit 1 ignored)
*/
- writel_relaxed(GIC_DIST_CTRL_ENABLE_GRP0_BIT |
GIC_DIST_CTRL_ENABLE_GRP1_BIT, base + GIC_DIST_CTRL);
Could there be more meaningful defines than GIC_DIST_CTRL_ENABLE_GRP0_BIT and GIC_DIST_CTRL_ENABLE_GRP1_BIT for those bits? Otherwise the code would look just as clear and possibly cleaner by simply using 0x3.
Nicolas
This patch registers two virqs for each interrupt source it supports. Using multiple virqs allows the GIC driver to automatically modify the group register, allowing the new virqs to be used as argument to enable_fiq(). This also allows FIQ resources to be described in the device tree's interrupt list using a special flag (currently 0x80).
Both these aspects combine and allow a driver to deploy a FIQ handler without any machine specific knowledge; it can be used effectively on multi-arch kernels.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com --- drivers/irqchip/irq-gic.c | 62 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index aa8efe4..9a4712d 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -42,12 +42,17 @@ #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h>
+#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h>
#include "irqchip.h"
+#define GIC_INTSPEC_IRQ_IS_FIQ (1 << 7) + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -65,6 +70,7 @@ struct gic_chip_data { #endif struct irq_domain *domain; unsigned int gic_irqs; + unsigned int fiq_shadow_offset; #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif @@ -143,11 +149,34 @@ static inline void __iomem *gic_cpu_base(struct irq_data *d) return gic_data_cpu_base(gic_data); }
+static inline bool gic_is_fiq(struct irq_data *d) +{ + struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d); + return d->hwirq > gic_data->gic_irqs; +} + static inline unsigned int gic_irq(struct irq_data *d) { + struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d); + if (gic_is_fiq(d)) + return d->hwirq - gic_data->fiq_shadow_offset; return d->hwirq; }
+static void gic_set_group_irq(struct irq_data *d, int group) +{ + unsigned int reg = gic_irq(d) / 32 * 4; + u32 mask = 1 << (gic_irq(d) % 32); + u32 val; + + val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + reg); + if (group) + val |= mask; + else + val &= ~mask; + writel_relaxed(val, gic_dist_base(d) + GIC_DIST_IGROUP + reg); +} + /* * Routines to acknowledge, disable and enable interrupts */ @@ -159,6 +188,8 @@ static void gic_mask_irq(struct irq_data *d) writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_CLEAR + (gic_irq(d) / 32) * 4); if (gic_arch_extn.irq_mask) gic_arch_extn.irq_mask(d); + if (gic_is_fiq(d)) + gic_set_group_irq(d, 1); raw_spin_unlock(&irq_controller_lock); }
@@ -167,6 +198,8 @@ static void gic_unmask_irq(struct irq_data *d) u32 mask = 1 << (gic_irq(d) % 32);
raw_spin_lock(&irq_controller_lock); + if (gic_is_fiq(d)) + gic_set_group_irq(d, 0); if (gic_arch_extn.irq_unmask) gic_arch_extn.irq_unmask(d); writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_SET + (gic_irq(d) / 32) * 4); @@ -940,7 +973,12 @@ static int gic_routable_irq_domain_xlate(struct irq_domain *d, unsigned long *out_hwirq, unsigned int *out_type) { + struct gic_chip_data *gic_data = d->host_data; *out_hwirq += 16; + + if (intspec[2] & GIC_INTSPEC_IRQ_IS_FIQ) + *out_hwirq += gic_data->fiq_shadow_offset; + return 0; }
@@ -1026,10 +1064,11 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, gic->gic_irqs = gic_irqs;
gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */ + gic->fiq_shadow_offset = gic_irqs;
if (of_property_read_u32(node, "arm,routable-irqs", &nr_routable_irqs)) { - irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, + irq_base = irq_alloc_descs(irq_start, 16, 2 * gic_irqs, numa_node_id()); if (IS_ERR_VALUE(irq_base)) { WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n", @@ -1037,17 +1076,28 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, irq_base = irq_start; }
- gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, - hwirq_base, &gic_irq_domain_ops, gic); + gic->domain = + irq_domain_add_legacy(node, 2 * gic_irqs, irq_base, + hwirq_base, &gic_irq_domain_ops, gic); } else { - gic->domain = irq_domain_add_linear(node, nr_routable_irqs, - &gic_irq_domain_ops, - gic); + gic->domain = irq_domain_add_linear(node, 2 * nr_routable_irqs, + &gic_irq_domain_ops, gic); }
if (WARN_ON(!gic->domain)) return;
+#ifdef CONFIG_FIQ + /* FIQ can only be supported on platforms without an extended irq_eoi + * method (otherwise we take a lock during irq_eoi handling). + */ + if (!gic_arch_extn.irq_eoi) + fiq_add_mapping( + irq_linear_revmap(gic->domain, hwirq_base), + irq_linear_revmap(gic->domain, hwirq_base + gic_irqs), + gic_irqs); +#endif + if (gic_nr == 0) { #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq);
On Thu, Jun 05, 2014 at 11:53:09AM +0200, Daniel Thompson wrote:
This patch registers two virqs for each interrupt source it supports. Using multiple virqs allows the GIC driver to automatically modify the group register, allowing the new virqs to be used as argument to enable_fiq(). This also allows FIQ resources to be described in the device tree's interrupt list using a special flag (currently 0x80).
Both these aspects combine and allow a driver to deploy a FIQ handler without any machine specific knowledge; it can be used effectively on multi-arch kernels.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com
drivers/irqchip/irq-gic.c | 62 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index aa8efe4..9a4712d 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -42,12 +42,17 @@ #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> #include "irqchip.h" +#define GIC_INTSPEC_IRQ_IS_FIQ (1 << 7)
union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -65,6 +70,7 @@ struct gic_chip_data { #endif struct irq_domain *domain; unsigned int gic_irqs;
- unsigned int fiq_shadow_offset;
#ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif @@ -143,11 +149,34 @@ static inline void __iomem *gic_cpu_base(struct irq_data *d) return gic_data_cpu_base(gic_data); } +static inline bool gic_is_fiq(struct irq_data *d) +{
- struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d);
- return d->hwirq > gic_data->gic_irqs;
+}
static inline unsigned int gic_irq(struct irq_data *d) {
- struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d);
- if (gic_is_fiq(d))
return d->hwirq;return d->hwirq - gic_data->fiq_shadow_offset;
} +static void gic_set_group_irq(struct irq_data *d, int group) +{
- unsigned int reg = gic_irq(d) / 32 * 4;
- u32 mask = 1 << (gic_irq(d) % 32);
- u32 val;
- val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + reg);
- if (group)
val |= mask;
- else
val &= ~mask;
- writel_relaxed(val, gic_dist_base(d) + GIC_DIST_IGROUP + reg);
+}
/*
- Routines to acknowledge, disable and enable interrupts
*/ @@ -159,6 +188,8 @@ static void gic_mask_irq(struct irq_data *d) writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_CLEAR + (gic_irq(d) / 32) * 4); if (gic_arch_extn.irq_mask) gic_arch_extn.irq_mask(d);
- if (gic_is_fiq(d))
raw_spin_unlock(&irq_controller_lock);gic_set_group_irq(d, 1);
} @@ -167,6 +198,8 @@ static void gic_unmask_irq(struct irq_data *d) u32 mask = 1 << (gic_irq(d) % 32); raw_spin_lock(&irq_controller_lock);
- if (gic_is_fiq(d))
if (gic_arch_extn.irq_unmask) gic_arch_extn.irq_unmask(d); writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_SET + (gic_irq(d) / 32) * 4);gic_set_group_irq(d, 0);
@@ -940,7 +973,12 @@ static int gic_routable_irq_domain_xlate(struct irq_domain *d, unsigned long *out_hwirq, unsigned int *out_type) {
- struct gic_chip_data *gic_data = d->host_data; *out_hwirq += 16;
- if (intspec[2] & GIC_INTSPEC_IRQ_IS_FIQ)
*out_hwirq += gic_data->fiq_shadow_offset;
- return 0;
} @@ -1026,10 +1064,11 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, gic->gic_irqs = gic_irqs; gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
- gic->fiq_shadow_offset = gic_irqs;
if (of_property_read_u32(node, "arm,routable-irqs", &nr_routable_irqs)) {
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,
if (IS_ERR_VALUE(irq_base)) { WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",irq_base = irq_alloc_descs(irq_start, 16, 2 * gic_irqs, numa_node_id());
@@ -1037,17 +1076,28 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, irq_base = irq_start; }
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);
gic->domain =
irq_domain_add_legacy(node, 2 * gic_irqs, irq_base,
} else {hwirq_base, &gic_irq_domain_ops, gic);
gic->domain = irq_domain_add_linear(node, nr_routable_irqs,
&gic_irq_domain_ops,
gic);
gic->domain = irq_domain_add_linear(node, 2 * nr_routable_irqs,
}&gic_irq_domain_ops, gic);
if (WARN_ON(!gic->domain)) return; +#ifdef CONFIG_FIQ
- /* FIQ can only be supported on platforms without an extended irq_eoi
* method (otherwise we take a lock during irq_eoi handling).
*/
- if (!gic_arch_extn.irq_eoi)
fiq_add_mapping(
irq_linear_revmap(gic->domain, hwirq_base),
irq_linear_revmap(gic->domain, hwirq_base + gic_irqs),
gic_irqs);
+#endif
This is rather unfortunate. On Tegra for example we don't need a lock for the irq_eoi because the eoi ack can be handled with a single write to the appropriate irq ack register.
Cheers,
Peter.
On 06/06/14 08:46, Peter De Schrijver wrote:
@@ -1037,17 +1076,28 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, irq_base = irq_start; }
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);
gic->domain =
irq_domain_add_legacy(node, 2 * gic_irqs, irq_base,
} else {hwirq_base, &gic_irq_domain_ops, gic);
gic->domain = irq_domain_add_linear(node, nr_routable_irqs,
&gic_irq_domain_ops,
gic);
gic->domain = irq_domain_add_linear(node, 2 * nr_routable_irqs,
}&gic_irq_domain_ops, gic);
if (WARN_ON(!gic->domain)) return; +#ifdef CONFIG_FIQ
- /* FIQ can only be supported on platforms without an extended irq_eoi
* method (otherwise we take a lock during irq_eoi handling).
*/
- if (!gic_arch_extn.irq_eoi)
fiq_add_mapping(
irq_linear_revmap(gic->domain, hwirq_base),
irq_linear_revmap(gic->domain, hwirq_base + gic_irqs),
gic_irqs);
+#endif
This is rather unfortunate. On Tegra for example we don't need a lock for the irq_eoi because the eoi ack can be handled with a single write to the appropriate irq ack register.
I believe that Tegra is the only platform that uses this hook so should be safe to remove the locks from gic_irq_eoi().
Certainly looking back at the code history and the mailing list discussions around this code[1] I cannot see any reasoning about the locks that I have missed.
Any objections to just nuking the locks?
Daniel.
[1] http://thread.gmane.org/gmane.linux.ports.arm.kernel/107474 http://thread.gmane.org/gmane.linux.ports.arm.kernel/108361 http://thread.gmane.org/gmane.linux.ports.arm.kernel/109690
Currently on the ARM Versatile machine both FIQ and IRQ signals share the same irq number. The effect of this is that enable_fiq() will enable an interrupt but will leave it routed to IRQ. This requires a driver utilizing FIQ to employ machine specific knowledge (i.e. that the machine has a VIC).
By introducing shadow irqs to describe FIQs the VIC driver is able to update the routing automatically during enable_fiq()/disable_fiq().
Changes to the vic_init() API allow individual machines to choose where to fit the shadow irqs in the interrupt map and also to choose not to have shadows at all.
This patch introduces shadows for mach-versatile whilst mach-ep93xx, mach-netx, mach-s3c64xx and plat-samsung retain unmodified interrupt maps.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Hartley Sweeten hsweeten@visionengravers.com Cc: Ryan Mallon rmallon@gmail.com Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben-linux@fluff.org Cc: Kukjin Kim kgene.kim@samsung.com Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: linux-samsung-soc@vger.kernel.org --- arch/arm/mach-ep93xx/core.c | 6 +- arch/arm/mach-netx/generic.c | 3 +- arch/arm/mach-s3c64xx/common.c | 6 +- arch/arm/mach-versatile/core.c | 9 +-- arch/arm/mach-versatile/include/mach/irqs.h | 5 +- arch/arm/plat-samsung/s5p-irq.c | 3 +- drivers/irqchip/irq-vic.c | 102 +++++++++++++++++++++++----- include/linux/irqchip/arm-vic.h | 8 ++- 8 files changed, 113 insertions(+), 29 deletions(-)
diff --git a/arch/arm/mach-ep93xx/core.c b/arch/arm/mach-ep93xx/core.c index 0e571f1..aa26411 100644 --- a/arch/arm/mach-ep93xx/core.c +++ b/arch/arm/mach-ep93xx/core.c @@ -185,8 +185,10 @@ void __init ep93xx_timer_init(void) *************************************************************************/ void __init ep93xx_init_irq(void) { - vic_init(EP93XX_VIC1_BASE, 0, EP93XX_VIC1_VALID_IRQ_MASK, 0); - vic_init(EP93XX_VIC2_BASE, 32, EP93XX_VIC2_VALID_IRQ_MASK, 0); + vic_init(EP93XX_VIC1_BASE, 0, VIC_FIQ_START_NONE, + EP93XX_VIC1_VALID_IRQ_MASK, 0); + vic_init(EP93XX_VIC2_BASE, 32, VIC_FIQ_START_NONE, + EP93XX_VIC2_VALID_IRQ_MASK, 0); }
diff --git a/arch/arm/mach-netx/generic.c b/arch/arm/mach-netx/generic.c index db25b0c..5398dcd 100644 --- a/arch/arm/mach-netx/generic.c +++ b/arch/arm/mach-netx/generic.c @@ -169,7 +169,8 @@ void __init netx_init_irq(void) { int irq;
- vic_init(io_p2v(NETX_PA_VIC), NETX_IRQ_VIC_START, ~0, 0); + vic_init(io_p2v(NETX_PA_VIC), NETX_IRQ_VIC_START, VIC_FIQ_START_NONE, + ~0, 0);
for (irq = NETX_IRQ_HIF_CHAINED(0); irq <= NETX_IRQ_HIF_LAST; irq++) { irq_set_chip_and_handler(irq, &netx_hif_chip, diff --git a/arch/arm/mach-s3c64xx/common.c b/arch/arm/mach-s3c64xx/common.c index 5c45aae..b98dd48 100644 --- a/arch/arm/mach-s3c64xx/common.c +++ b/arch/arm/mach-s3c64xx/common.c @@ -242,8 +242,10 @@ void __init s3c64xx_init_irq(u32 vic0_valid, u32 vic1_valid) printk(KERN_DEBUG "%s: initialising interrupts\n", __func__);
/* initialise the pair of VICs */ - vic_init(VA_VIC0, IRQ_VIC0_BASE, vic0_valid, IRQ_VIC0_RESUME); - vic_init(VA_VIC1, IRQ_VIC1_BASE, vic1_valid, IRQ_VIC1_RESUME); + vic_init(VA_VIC0, IRQ_VIC0_BASE, VIC_FIQ_START_NONE, vic0_valid, + IRQ_VIC0_RESUME); + vic_init(VA_VIC1, IRQ_VIC1_BASE, VIC_FIQ_START_NONE, vic1_valid, + IRQ_VIC1_RESUME); }
#define eint_offset(irq) ((irq) - IRQ_EINT(0)) diff --git a/arch/arm/mach-versatile/core.c b/arch/arm/mach-versatile/core.c index f2c89fb..3444ca8 100644 --- a/arch/arm/mach-versatile/core.c +++ b/arch/arm/mach-versatile/core.c @@ -108,7 +108,8 @@ void __init versatile_init_irq(void)
np = of_find_matching_node_by_address(NULL, vic_of_match, VERSATILE_VIC_BASE); - __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np); + __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, + np ? -1 : FIQ_VIC_START, ~0, 0, np);
writel(~0, VA_SIC_BASE + SIC_IRQ_ENABLE_CLEAR);
@@ -614,9 +615,9 @@ static struct pl022_ssp_controller ssp0_plat_data = { * These devices are connected via the DMA APB bridge */ #define SCI_IRQ { IRQ_SCIINT } -#define UART0_IRQ { IRQ_UARTINT0 } -#define UART1_IRQ { IRQ_UARTINT1 } -#define UART2_IRQ { IRQ_UARTINT2 } +#define UART0_IRQ { IRQ_UARTINT0, FIQ_UARTINT0 } +#define UART1_IRQ { IRQ_UARTINT1, FIQ_UARTINT1 } +#define UART2_IRQ { IRQ_UARTINT2, FIQ_UARTINT2 } #define SSP_IRQ { IRQ_SSPINT }
/* FPGA Primecells */ diff --git a/arch/arm/mach-versatile/include/mach/irqs.h b/arch/arm/mach-versatile/include/mach/irqs.h index 0fd771c..68171d9 100644 --- a/arch/arm/mach-versatile/include/mach/irqs.h +++ b/arch/arm/mach-versatile/include/mach/irqs.h @@ -131,4 +131,7 @@ #define IRQ_GPIO3_START (IRQ_GPIO2_END + 1) #define IRQ_GPIO3_END (IRQ_GPIO3_START + 31)
-#define NR_IRQS (IRQ_GPIO3_END + 1) +#define FIQ_VIC_START (IRQ_GPIO3_END + 1) +#define FIQ_VIC_END (FIQ_VIC_START + (IRQ_VIC_END - IRQ_VIC_START)) + +#define NR_IRQS (FIQ_VIC_END + 1) diff --git a/arch/arm/plat-samsung/s5p-irq.c b/arch/arm/plat-samsung/s5p-irq.c index ddfaca9..ddb1138 100644 --- a/arch/arm/plat-samsung/s5p-irq.c +++ b/arch/arm/plat-samsung/s5p-irq.c @@ -26,6 +26,7 @@ void __init s5p_init_irq(u32 *vic, u32 num_vic)
/* initialize the VICs */ for (irq = 0; irq < num_vic; irq++) - vic_init(VA_VIC(irq), VIC_BASE(irq), vic[irq], 0); + vic_init(VA_VIC(irq), VIC_BASE(irq), VIC_FIQ_START_NONE, + vic[irq], 0); #endif } diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index 7d35287..748c5af 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -36,6 +36,9 @@
#include <asm/exception.h> #include <asm/irq.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif
#include "irqchip.h"
@@ -56,6 +59,8 @@
#define VIC_PL192_VECT_ADDR 0xF00
+#define VIC_FIQ_SHADOW_OFFSET 32 + /** * struct vic_device - VIC PM device * @parent_irq: The parent IRQ number of the VIC if cascaded, or 0. @@ -81,8 +86,11 @@ struct vic_device { u32 soft_int; u32 protect; struct irq_domain *domain; + struct irq_domain *fiq_domain; };
+static DEFINE_RAW_SPINLOCK(irq_controller_lock); + /* we cannot allocate memory when VICs are initially registered */ static struct vic_device vic_devices[CONFIG_ARM_VIC_NR];
@@ -197,6 +205,9 @@ static int vic_irqdomain_map(struct irq_domain *d, unsigned int irq, { struct vic_device *v = d->host_data;
+ if (hwirq > VIC_FIQ_SHADOW_OFFSET) + hwirq -= VIC_FIQ_SHADOW_OFFSET; + /* Skip invalid IRQs, only register handlers for the real ones */ if (!(v->valid_sources & (1 << hwirq))) return -EPERM; @@ -261,6 +272,15 @@ static struct irq_domain_ops vic_irqdomain_ops = { .xlate = irq_domain_xlate_onetwocell, };
+#ifdef CONFIG_FIQ +static void vic_map_fiq(int irq, int fiq, unsigned int length) +{ + fiq_add_mapping(irq, fiq, length); +} +#else +static inline void vic_map_fiq(int irq, int fiq, unsigned int length) {} +#endif + /** * vic_register() - Register a VIC. * @base: The base address of the VIC. @@ -277,7 +297,7 @@ static struct irq_domain_ops vic_irqdomain_ops = { * This also configures the IRQ domain for the VIC. */ static void __init vic_register(void __iomem *base, unsigned int parent_irq, - unsigned int irq, + unsigned int irq, int fiq, u32 valid_sources, u32 resume_sources, struct device_node *node) { @@ -307,6 +327,22 @@ static void __init vic_register(void __iomem *base, unsigned int parent_irq, for (i = 0; i < fls(valid_sources); i++) if (valid_sources & (1 << i)) irq_create_mapping(v->domain, i); + + /* create FIQ shadow mapping for each IRQ */ + if (fiq >= 0) { + v->fiq_domain = irq_domain_add_legacy( + node, fls(valid_sources), fiq, + VIC_FIQ_SHADOW_OFFSET, &vic_irqdomain_ops, v); + /* create an IRQ mapping for each valid IRQ */ + for (i = 0; i < fls(valid_sources); i++) + if (valid_sources & (1 << i)) { + int fiq_virq = irq_create_mapping( + v->fiq_domain, i + VIC_FIQ_SHADOW_OFFSET); + vic_map_fiq(irq_find_mapping(v->domain, i), + fiq_virq, 1); + } + } + /* If no base IRQ was passed, figure out our allocated base */ if (irq) v->irq = irq; @@ -314,10 +350,36 @@ static void __init vic_register(void __iomem *base, unsigned int parent_irq, v->irq = irq_find_mapping(v->domain, 0); }
+static inline bool vic_is_fiq(struct irq_data *d) +{ + return d->hwirq >= VIC_FIQ_SHADOW_OFFSET; +} + +static inline unsigned int vic_irq(struct irq_data *d) +{ + return d->hwirq & (VIC_FIQ_SHADOW_OFFSET-1); +} + +static void vic_set_fiq(struct irq_data *d, bool enable) +{ + void __iomem *base = irq_data_get_irq_chip_data(d); + unsigned int irq = vic_irq(d); + u32 val; + + raw_spin_lock(&irq_controller_lock); + val = readl(base + VIC_INT_SELECT); + if (enable) + val |= 1 << irq; + else + val &= ~(1 << irq); + writel(val, base + VIC_INT_SELECT); + raw_spin_unlock(&irq_controller_lock); +} + static void vic_ack_irq(struct irq_data *d) { void __iomem *base = irq_data_get_irq_chip_data(d); - unsigned int irq = d->hwirq; + unsigned int irq = vic_irq(d); writel(1 << irq, base + VIC_INT_ENABLE_CLEAR); /* moreover, clear the soft-triggered, in case it was the reason */ writel(1 << irq, base + VIC_INT_SOFT_CLEAR); @@ -326,17 +388,22 @@ static void vic_ack_irq(struct irq_data *d) static void vic_mask_irq(struct irq_data *d) { void __iomem *base = irq_data_get_irq_chip_data(d); - unsigned int irq = d->hwirq; + unsigned int irq = vic_irq(d); + if (vic_is_fiq(d)) + vic_set_fiq(d, false); writel(1 << irq, base + VIC_INT_ENABLE_CLEAR); }
static void vic_unmask_irq(struct irq_data *d) { void __iomem *base = irq_data_get_irq_chip_data(d); - unsigned int irq = d->hwirq; + unsigned int irq = vic_irq(d); + if (vic_is_fiq(d)) + vic_set_fiq(d, true); writel(1 << irq, base + VIC_INT_ENABLE); }
+ #if defined(CONFIG_PM) static struct vic_device *vic_from_irq(unsigned int irq) { @@ -355,7 +422,7 @@ static struct vic_device *vic_from_irq(unsigned int irq) static int vic_set_wake(struct irq_data *d, unsigned int on) { struct vic_device *v = vic_from_irq(d->irq); - unsigned int off = d->hwirq; + unsigned int off = vic_irq(d); u32 bit = 1 << off;
if (!v) @@ -413,7 +480,8 @@ static void __init vic_clear_interrupts(void __iomem *base) * and 020 within the page. We call this "second block". */ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, - u32 vic_sources, struct device_node *node) + int fiq_start, u32 vic_sources, + struct device_node *node) { unsigned int i; int vic_2nd_block = ((unsigned long)base & ~PAGE_MASK) != 0; @@ -439,12 +507,12 @@ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, writel(32, base + VIC_PL190_DEF_VECT_ADDR); }
- vic_register(base, 0, irq_start, vic_sources, 0, node); + vic_register(base, 0, irq_start, fiq_start, vic_sources, 0, node); }
void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, - struct device_node *node) + int fiq_start, u32 vic_sources, u32 resume_sources, + struct device_node *node) { unsigned int i; u32 cellid = 0; @@ -462,7 +530,7 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
switch(vendor) { case AMBA_VENDOR_ST: - vic_init_st(base, irq_start, vic_sources, node); + vic_init_st(base, irq_start, fiq_start, vic_sources, node); return; default: printk(KERN_WARNING "VIC: unknown vendor, continuing anyways\n"); @@ -479,7 +547,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
vic_init2(base);
- vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, node); + vic_register(base, parent_irq, irq_start, fiq_start, vic_sources, + resume_sources, node); }
/** @@ -490,9 +559,9 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, * @resume_sources: bitmask of interrupt sources to allow for resume */ void __init vic_init(void __iomem *base, unsigned int irq_start, - u32 vic_sources, u32 resume_sources) + int fiq_start, u32 vic_sources, u32 resume_sources) { - __vic_init(base, 0, irq_start, vic_sources, resume_sources, NULL); + __vic_init(base, 0, irq_start, -1, vic_sources, resume_sources, NULL); }
/** @@ -511,7 +580,7 @@ int __init vic_init_cascaded(void __iomem *base, unsigned int parent_irq, struct vic_device *v;
v = &vic_devices[vic_id]; - __vic_init(base, parent_irq, 0, vic_sources, resume_sources, NULL); + __vic_init(base, parent_irq, 0, -1, vic_sources, resume_sources, NULL); /* Return out acquired base */ return v->irq; } @@ -535,9 +604,10 @@ int __init vic_of_init(struct device_node *node, struct device_node *parent) of_property_read_u32(node, "valid-wakeup-mask", &wakeup_mask);
/* - * Passing 0 as first IRQ makes the simple domain allocate descriptors + * Passing 0 as first IRQ (and first FIQ) makes the domain allocate + * descriptors. */ - __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, node); + __vic_init(regs, 0, 0, -1, interrupt_mask, wakeup_mask, node);
return 0; } diff --git a/include/linux/irqchip/arm-vic.h b/include/linux/irqchip/arm-vic.h index ba46c79..fae480d 100644 --- a/include/linux/irqchip/arm-vic.h +++ b/include/linux/irqchip/arm-vic.h @@ -26,12 +26,16 @@ #define VIC_INT_ENABLE 0x10 /* 1 = enable, 0 = disable */ #define VIC_INT_ENABLE_CLEAR 0x14
+#define VIC_FIQ_START_NONE -1 + struct device_node; struct pt_regs;
void __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, struct device_node *node); -void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources); + int fiq_start, u32 vic_sources, u32 resume_sources, + struct device_node *node); +void vic_init(void __iomem *base, unsigned int irq_start, int fiq_start, + u32 vic_sources, u32 resume_sources); int vic_init_cascaded(void __iomem *base, unsigned int parent_irq, u32 vic_sources, u32 resume_sources);
From: Anton Vorontsov anton.vorontsov@linaro.org
Just move the macros into header file as we would want to use them for KGDB FIQ entry code.
The following macros were moved:
- svc_entry - usr_entry - kuser_cmpxchg_check - vector_stub
To make kuser_cmpxchg_check actually work across different files, we also have to make kuser_cmpxchg64_fixup global.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Frederic Weisbecker fweisbec@gmail.com --- arch/arm/kernel/entry-armv.S | 151 +------------------------------------ arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 150 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 1879e8d..ed95b95 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -140,53 +140,6 @@ ENDPROC(__und_invalid) * SVC mode handlers */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) -#define SPFIX(code...) code -#else -#define SPFIX(code...) -#endif - - .macro svc_entry, stack_hole=0 - UNWIND(.fnstart ) - UNWIND(.save {r0 - pc} ) - sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) -#ifdef CONFIG_THUMB2_KERNEL - SPFIX( str r0, [sp] ) @ temporarily saved - SPFIX( mov r0, sp ) - SPFIX( tst r0, #4 ) @ test original stack alignment - SPFIX( ldr r0, [sp] ) @ restored -#else - SPFIX( tst sp, #4 ) -#endif - SPFIX( subeq sp, sp, #4 ) - stmia sp, {r1 - r12} - - ldmia r0, {r3 - r5} - add r7, sp, #S_SP - 4 @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) - SPFIX( addeq r2, r2, #4 ) - str r3, [sp, #-4]! @ save the "real" r0 copied - @ from the exception stack - - mov r3, lr - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r2 - sp_svc - @ r3 - lr_svc - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - stmia r7, {r2 - r6} - -#ifdef CONFIG_TRACE_IRQFLAGS - bl trace_hardirqs_off -#endif - .endm - .align 5 __dabt_svc: svc_entry @@ -306,73 +259,8 @@ ENDPROC(__pabt_svc)
/* * User mode handlers - * - * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0 - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - .align 5 __dabt_usr: usr_entry @@ -819,6 +707,7 @@ __kuser_cmpxchg64: @ 0xffff0f60 ldmfd sp!, {r4, r5, r6, pc}
.text + .global kuser_cmpxchg64_fixup kuser_cmpxchg64_fixup: @ Called from kuser_cmpxchg_fixup. @ r4 = address of interrupted insn (must be preserved). @@ -960,44 +849,6 @@ __kuser_helper_end: * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. */ - .macro vector_stub, name, mode, correction=0 - .align 5 - -vector_\name: - .if \correction - sub lr, lr, #\correction - .endif - - @ - @ Save r0, lr_<exception> (parent PC) and spsr_<exception> - @ (parent CPSR) - @ - stmia sp, {r0, lr} @ save r0, lr - mrs lr, spsr - str lr, [sp, #8] @ save spsr - - @ - @ Prepare for SVC32 mode. IRQs remain disabled. - @ - mrs r0, cpsr - eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) - msr spsr_cxsf, r0 - - @ - @ the branch table must immediately follow this code - @ - and lr, lr, #0x0f - THUMB( adr r0, 1f ) - THUMB( ldr lr, [r0, lr, lsl #2] ) - mov r0, sp - ARM( ldr lr, [pc, lr, lsl #2] ) - movs pc, lr @ branch to handler in SVC mode -ENDPROC(vector_\name) - - .align 2 - @ handler addresses follow this label -1: - .endm
.section .stubs, "ax", %progbits __stubs_start: diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index efb208d..5d794d6 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -356,3 +356,167 @@ scno .req r7 @ syscall number tbl .req r8 @ syscall table pointer why .req r8 @ Linux syscall (!= 0) tsk .req r9 @ current thread_info + +/* + * SVC mode handler macros + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) +#define SPFIX(code...) code +#else +#define SPFIX(code...) +#endif + + .macro svc_entry, stack_hole=0 + UNWIND(.fnstart ) + UNWIND(.save {r0 - pc} ) + sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) +#ifdef CONFIG_THUMB2_KERNEL + SPFIX( str r0, [sp] ) @ temporarily saved + SPFIX( mov r0, sp ) + SPFIX( tst r0, #4 ) @ test original stack alignment + SPFIX( ldr r0, [sp] ) @ restored +#else + SPFIX( tst sp, #4 ) +#endif + SPFIX( subeq sp, sp, #4 ) + stmia sp, {r1 - r12} + + ldmia r0, {r3 - r5} + add r7, sp, #S_SP - 4 @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) + SPFIX( addeq r2, r2, #4 ) + str r3, [sp, #-4]! @ save the "real" r0 copied + @ from the exception stack + + mov r3, lr + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r2 - sp_svc + @ r3 - lr_svc + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + stmia r7, {r2 - r6} + +#ifdef CONFIG_TRACE_IRQFLAGS + bl trace_hardirqs_off +#endif + .endm + +/* + * User mode handler macros + * + * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0 + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +/* + * Vector stubs macro. + */ + .macro vector_stub, name, mode, correction=0 + .align 5 + +vector_\name: + .if \correction + sub lr, lr, #\correction + .endif + + @ + @ Save r0, lr_<exception> (parent PC) and spsr_<exception> + @ (parent CPSR) + @ + stmia sp, {r0, lr} @ save r0, lr + mrs lr, spsr + str lr, [sp, #8] @ save spsr + + @ + @ Prepare for SVC32 mode. IRQs remain disabled. + @ + mrs r0, cpsr + eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) + msr spsr_cxsf, r0 + + @ + @ the branch table must immediately follow this code + @ + and lr, lr, #0x0f + THUMB( adr r0, 1f ) + THUMB( ldr lr, [r0, lr, lsl #2] ) + mov r0, sp + ARM( ldr lr, [pc, lr, lsl #2] ) + movs pc, lr @ branch to handler in SVC mode +ENDPROC(vector_\name) + + .align 2 + @ handler addresses follow this label +1: + .endm + +
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Dave Martin Dave.Martin@arm.com --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++++ arch/arm/include/asm/kgdb.h | 7 +++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/kgdb_fiq.c | 124 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++++++++++++++ 6 files changed, 239 insertions(+) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index db3c541..419fd0a 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -307,6 +307,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -356,6 +357,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 6a2bcfd..1f1bec1 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,24 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be + enabled with kgdb_fiq.enable=1 kernel command line option. + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..5de21f01 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,11 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern char kgdb_fiq_handler; +extern char kgdb_fiq_handler_end; +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs); +extern int kgdb_register_fiq(unsigned int fiq); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 040619c..251f651 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -67,6 +67,7 @@ endif obj-$(CONFIG_OABI_COMPAT) += sys_oabi-compat.o obj-$(CONFIG_ARM_THUMBEE) += thumbee.o obj-$(CONFIG_KGDB) += kgdb.o +obj-$(CONFIG_KGDB_FIQ) += kgdb_fiq_entry.o kgdb_fiq.o obj-$(CONFIG_ARM_UNWIND) += unwind.o obj-$(CONFIG_HAVE_TCM) += tcm.o obj-$(CONFIG_OF) += devtree.o diff --git a/arch/arm/kernel/kgdb_fiq.c b/arch/arm/kernel/kgdb_fiq.c new file mode 100644 index 0000000..45f2a79 --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq.c @@ -0,0 +1,124 @@ +/* + * KGDB FIQ + * + * Copyright 2010 Google, Inc. + * Arve Hjønnevåg arve@android.com + * Colin Cross ccross@android.com + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/hardirq.h> +#include <linux/atomic.h> +#include <linux/kdb.h> +#include <linux/kgdb.h> +#include <asm/fiq.h> +#include <asm/exception.h> + +static int kgdb_fiq_enabled; +module_param_named(enable, kgdb_fiq_enabled, int, 0600); +MODULE_PARM_DESC(enable, "set to 1 to enable FIQ KGDB"); + +static unsigned int kgdb_fiq; + +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs) +{ + if (kgdb_nmi_poll_knock()) { + nmi_enter(); + kgdb_handle_exception(1, 0, 0, regs); + nmi_exit(); + } + + eoi_fiq(kgdb_fiq); +} + +static struct fiq_handler kgdb_fiq_desc = { + .name = "kgdb", +}; + +static long kgdb_fiq_setup_stack(void *info) +{ + struct pt_regs regs; + + regs.ARM_sp = __get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER) + + THREAD_START_SP; + WARN_ON(!regs.ARM_sp); + + set_fiq_regs(®s); + return 0; +} + +/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * This function manages NMIs that usually cause KGDB to enter. That is, not + * all NMIs should be enabled or disabled, but only those that issue + * kgdb_handle_exception(). + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + static atomic_t cnt; + int ret; + + ret = atomic_add_return(on ? 1 : -1, &cnt); + if (ret > 1 && on) { + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + WARN_ON(1); + return; + } + + if (ret > 0) + enable_fiq(kgdb_fiq); + else + disable_fiq(kgdb_fiq); +} + +int kgdb_register_fiq(unsigned int fiq) +{ + int err; + int cpu; + + if (!kgdb_fiq_enabled) + return -ENODEV; + + if (!lookup_fiq_irq_data(fiq)) { + pr_warn( + "%s: Cannot register %u (no FIQ with this number)\n", + __func__, fiq); + return -ENODEV; + } + + kgdb_fiq = fiq; + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + for_each_possible_cpu(cpu) + work_on_cpu(cpu, kgdb_fiq_setup_stack, NULL); + + set_fiq_handler(&kgdb_fiq_handler, + &kgdb_fiq_handler_end - &kgdb_fiq_handler); + + arch_kgdb_ops.enable_nmi = kgdb_fiq_enable_nmi; + return 0; +} diff --git a/arch/arm/kernel/kgdb_fiq_entry.S b/arch/arm/kernel/kgdb_fiq_entry.S new file mode 100644 index 0000000..d6becca --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq_entry.S @@ -0,0 +1,87 @@ +/* + * KGDB FIQ entry + * + * Copyright 1996,1997,1998 Russell King. + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/memory.h> +#include <asm/unwind.h> +#include "entry-header.S" + + .text + +@ This is needed for usr_entry/alignment_trap +.LCcralign: + .long cr_alignment +.LCdohandle: + .long kgdb_fiq_do_handle + + .macro fiq_handler + ldr r1, =.LCdohandle + mov r0, sp + adr lr, BSYM(9997f) + ldr pc, [r1] +9997: + .endm + + .align 5 +__fiq_svc: + svc_entry + fiq_handler + mov r0, sp + ldmib r0, {r1 - r14} + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + .ltorg + + .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + .ltorg + + .global kgdb_fiq_handler +kgdb_fiq_handler: + + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_svc @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f + + .global kgdb_fiq_handler_end +kgdb_fiq_handler_end:
If the AMBA bus has provided the pl011 with a FIQ resource (i.e. a second IRQ) then speculatively register it with KGDB when the polling driver is initialized.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the amba-pl011 driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/amba-pl011.c | 99 ++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 41 deletions(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index dacf0a0..778fd38 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -58,6 +58,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/sizes.h> #include <linux/io.h> +#include <linux/kgdb.h>
#define UART_NR 14
@@ -1416,8 +1417,63 @@ static void pl011_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&uap->port.lock, flags); }
+static int pl011_hwinit(struct uart_port *port) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + int retval; + + /* Optionaly enable pins to be muxed in and configured */ + pinctrl_pm_select_default_state(port->dev); + + /* + * Try to enable the clock producer. + */ + retval = clk_prepare_enable(uap->clk); + if (retval) + goto out; + + uap->port.uartclk = clk_get_rate(uap->clk); + + /* Clear pending error and receive interrupts */ + writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | + UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); + + /* + * Save interrupts enable mask, and enable RX interrupts in case if + * the interrupt is used for NMI entry. + */ + uap->im = readw(uap->port.membase + UART011_IMSC); + writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); + + if (dev_get_platdata(uap->port.dev)) { + struct amba_pl011_data *plat; + + plat = dev_get_platdata(uap->port.dev); + if (plat->init) + plat->init(); + } + return 0; + out: + return retval; +} + #ifdef CONFIG_CONSOLE_POLL
+static int pl011_poll_init(struct uart_port *port) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + int retval; + + retval = pl011_hwinit(port); + +#ifdef CONFIG_KGDB_FIQ + if (retval == 0) + kgdb_register_fiq(uap->port.irq); +#endif + + return retval; +} + static void pl011_quiesce_irqs(struct uart_port *port) { struct uart_amba_port *uap = (struct uart_amba_port *)port; @@ -1471,46 +1527,6 @@ static void pl011_put_poll_char(struct uart_port *port,
#endif /* CONFIG_CONSOLE_POLL */
-static int pl011_hwinit(struct uart_port *port) -{ - struct uart_amba_port *uap = (struct uart_amba_port *)port; - int retval; - - /* Optionaly enable pins to be muxed in and configured */ - pinctrl_pm_select_default_state(port->dev); - - /* - * Try to enable the clock producer. - */ - retval = clk_prepare_enable(uap->clk); - if (retval) - goto out; - - uap->port.uartclk = clk_get_rate(uap->clk); - - /* Clear pending error and receive interrupts */ - writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | - UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); - - /* - * Save interrupts enable mask, and enable RX interrupts in case if - * the interrupt is used for NMI entry. - */ - uap->im = readw(uap->port.membase + UART011_IMSC); - writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); - - if (dev_get_platdata(uap->port.dev)) { - struct amba_pl011_data *plat; - - plat = dev_get_platdata(uap->port.dev); - if (plat->init) - plat->init(); - } - return 0; - out: - return retval; -} - static void pl011_write_lcr_h(struct uart_amba_port *uap, unsigned int lcr_h) { writew(lcr_h, uap->port.membase + uap->lcrh_rx); @@ -1890,7 +1906,7 @@ static struct uart_ops amba_pl011_pops = { .config_port = pl011_config_port, .verify_port = pl011_verify_port, #ifdef CONFIG_CONSOLE_POLL - .poll_init = pl011_hwinit, + .poll_init = pl011_poll_init, .poll_get_char = pl011_get_poll_char, .poll_put_char = pl011_put_poll_char, #endif @@ -2169,6 +2185,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) uart_unregister_driver(&amba_reg); pl011_dma_remove(uap); } + out: return ret; }
If the platform bus has provided the st-asc with a FIQ resource (i.e. a second IRQ) then speculatively register it with KGDB when the polling driver is initialized.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the st-asc driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger). This unmask is copied from similar code in amba-pl011.c .
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/st-asc.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index c7f61ac..4f376d8 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -30,6 +30,7 @@ #include <linux/of_platform.h> #include <linux/serial_core.h> #include <linux/clk.h> +#include <linux/kgdb.h>
#define DRIVER_NAME "st-asc" #define ASC_SERIAL_NAME "ttyAS" @@ -613,6 +614,25 @@ asc_verify_port(struct uart_port *port, struct serial_struct *ser) }
#ifdef CONFIG_CONSOLE_POLL + +#ifdef CONFIG_KGDB_FIQ +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int asc_poll_init(struct uart_port *port) +{ + struct asc_port *ascport = container_of(port, struct asc_port, port); + + /* register the FIQ with kgdb */ + kgdb_register_fiq(ascport->port.irq); + + /* enable RX interrupts in case the interrupt is used for NMI entry. */ + asc_enable_rx_interrupts(port); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + /* * Console polling routines for writing and reading from the uart while * in an interrupt or debug context (i.e. kgdb). @@ -656,6 +676,9 @@ static struct uart_ops asc_uart_ops = { .verify_port = asc_verify_port, .pm = asc_pm, #ifdef CONFIG_CONSOLE_POLL +#ifdef CONFIG_KGDB_FIQ + .poll_init = asc_poll_init, +#endif /* CONFIG_KGDB_FIQ */ .poll_get_char = asc_get_poll_char, .poll_put_char = asc_put_poll_char, #endif /* CONFIG_CONSOLE_POLL */
This patchset makes it possible to use the kgdb NMI infrastructure on ARM platforms by providing a mutli-platform compatible means for drivers to manage FIQ routings.
First a quick summary of how the already mainlined kgdb NMI infrastructure (mostly found in drivers/tty/serial/kgdb_nmi.c) works. The kgdb infrastructure will re-route the kgdb console UART's interrupt signal from IRQ to FIQ. Naturally the UART will no longer function normally and will instead be managed by kgdb using the polled I/O functions. Any character delivered to the UART causes the kgdb handler function to be called.
Note that, within this patchset a serial driver explicitly consents (or not) to the abuse outlined above by calling the appropriate registration during the .poll_init() callback. In so doing it also commits to honour the stringent runtime requirements imposed on FIQ handlers within its polled I/O handlers.
Major remaining TODO item is to modify the code to halt the other CPUs; at present this code sends IPIs (which use a normal IRQ) and busy waits for the other CPUs to halt. This means the benefits of invoking the debugger via NMI are only partially realized on SMP systems. However there are no cross dependencies so the code is "good to go" without this feature implemented.
Changes since v3:
* Corrected the FIQ uninstall code (Russell King).
* Removed named constants for EnableGrp0 and EnableGrp1 (Nicolas Pitre).
* Remove spin locks from gic_eoi_irq (Peter De Schrijver).
* Massively simplified things by avoiding allocation of shadow IRQ (Rob Herring)
* Correctly set the priority of FIQ interrupts by making the top bit follow ARM recommendations. Fixes robustness problem in the debugger itself.
* Auto-detect whether the platform can support FIQ and act accordingly. This permits proper dead code elimination when CONFIG_FIQ is not set.
* Ported to and tested on iMX6 (Wandboard quad). Included a patch from Dirk Behme that is required to get this device working properly with kgdb.
* Avoid using writel() (which takes spin locks) in polled I/O callbacks of supported serial drivers.
Changes since v2:
* Use flexible mappings to link a normal virq to a FIQ virq. This replaces the device tree proposals from the previous RFC (review of Russell King and Rob Herring).
* Reviewed all use of spin locks within .irq_eoi callbacks (and fixed the issue identified). Added comments to the FIQ registration functions making clear the requirements imposed on interrupt controller that call the FIQ API (thanks Russell King).
* Fixed a few whitespace issues (review of Srinivas Kandagatla)
* ARM64/defconfig build tests (no problems found)
Changes since v1:
* Fully fledged multi-platform support.
* Tested for correct FIQ operation on STiH416/B2020 (Cortex A9), qemu/versatile and qemu/vexpress-a15 (with self-written mods to the GIC model to support FIQ).
* Regression tested (and resulting bugs fixed) on qemu/versatile+DT and qemu/integreatorcp.
Anton Vorontsov (2): ARM: Move some macros from entry-armv to entry-header ARM: Add KGDB/KDB FIQ debugger generic code
Daniel Thompson (10): arm: fiq: Add callbacks to manage FIQ routings arm: fiq: Allow EOI to be communicated to the intc irqchip: gic: Provide support for interrupt grouping irqchip: gic: Add support for FIQ management irqchip: gic: Remove spin locks from eoi_irq irqchip: vic: Add support for FIQ management serial: amba-pl011: Pass FIQ information to KGDB. serial: asc: Add support for KGDB's FIQ/NMI mode serial: asc: Adopt readl_/writel_relaxed() serial: imx: Add support for KGDB's FIQ/NMI mode
Dirk Behme (1): serial: imx: clean up imx_poll_get_char()
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 +++++ arch/arm/include/asm/fiq.h | 13 ++++ arch/arm/include/asm/kgdb.h | 7 ++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/entry-armv.S | 151 +---------------------------------- arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/fiq.c | 112 +++++++++++++++++++++++++- arch/arm/kernel/kgdb_fiq.c | 124 +++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++++++++ arch/arm/mach-versatile/core.c | 2 +- drivers/irqchip/irq-gic.c | 155 +++++++++++++++++++++++++++++++++--- drivers/irqchip/irq-vic.c | 92 +++++++++++++++++----- drivers/tty/serial/amba-pl011.c | 99 +++++++++++++---------- drivers/tty/serial/imx.c | 88 ++++++++++++--------- drivers/tty/serial/st-asc.c | 34 +++++++- include/linux/irqchip/arm-vic.h | 6 +- 17 files changed, 892 insertions(+), 263 deletions(-) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org --- arch/arm/include/asm/fiq.h | 7 +++ arch/arm/kernel/fiq.c | 103 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..a7806ef 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -18,6 +18,11 @@
#include <asm/ptrace.h>
+struct fiq_chip { + void (*fiq_enable)(struct irq_data *data); + void (*fiq_disable)(struct irq_data *data); +}; + struct fiq_handler { struct fiq_handler *next; /* Name @@ -38,6 +43,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern bool has_fiq(int fiq); +extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..567f8fd 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,9 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h> +#include <linux/radix-tree.h> +#include <linux/slab.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -52,7 +55,15 @@ (unsigned)&vector_fiq_offset; \ })
+struct fiq_data { + struct fiq_chip *fiq_chip; + struct irq_data *irq_data; +}; + static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_data_tree, GFP_KERNEL); +static DEFINE_MUTEX(fiq_data_mutex);
/* Default reacquire function * - we always relinquish FIQ control @@ -127,18 +138,65 @@ void release_fiq(struct fiq_handler *f) while (current_fiq->fiq_op(current_fiq->dev_id, 0)); }
-static int fiq_start; +static struct fiq_data *lookup_fiq_data(int fiq) +{ + struct fiq_data *data; + + rcu_read_lock(); + data = radix_tree_lookup(&fiq_data_tree, fiq); + rcu_read_unlock(); + + return data; +}
void enable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_enable) + data->fiq_chip->fiq_enable(data->irq_data); + enable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + enable_irq(fiq + fiq_start); }
void disable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_disable) + data->fiq_chip->fiq_disable(data->irq_data); + disable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + disable_irq(fiq + fiq_start); }
+bool has_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) + return true; + + if (fiq_start == -1) + return false; + + return fiq > fiq_start; +} +EXPORT_SYMBOL(has_fiq); + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -147,9 +205,50 @@ EXPORT_SYMBOL(release_fiq); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq);
+/* + * Add a mapping from a Linux irq to the fiq data. + */ +void fiq_register_mapping(int irq, struct fiq_chip *chip) +{ + struct fiq_data *fiq_data = NULL; + int res; + + /* fiq_register_mapping can't be mixed with init_FIQ */ + BUG_ON(fiq_start != -1); + + fiq_data = kmalloc(sizeof(*fiq_data), GFP_KERNEL); + if (!fiq_data) + goto err; + + fiq_data->fiq_chip = chip; + fiq_data->irq_data = irq_get_irq_data(irq); + BUG_ON(!fiq_data->irq_data); + + mutex_lock(&fiq_data_mutex); + res = radix_tree_insert(&fiq_data_tree, irq, fiq_data); + mutex_unlock(&fiq_data_mutex); + if (res) + goto err; + + return; + +err: + kfree(fiq_data); + pr_err("fiq: Cannot register mapping %d\n", irq); +} + +/* + * Set the offset between normal IRQs and their FIQ shadows. + */ void __init init_FIQ(int start) { + fiq_start = start; +} + +static int __init init_default_fiq_handler(void) +{ unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); - fiq_start = start; + return 0; } +pure_initcall(init_default_fiq_handler);
Modern ARM systems require an EOI to be sent to the interrupt controller on completion of both IRQ and FIQ. The FIQ code currently does not provide any API to perform this. This patch provides this API, implemented by adding a callback to the fiq_chip structure.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org --- arch/arm/include/asm/fiq.h | 6 ++++++ arch/arm/kernel/fiq.c | 9 +++++++++ 2 files changed, 15 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index a7806ef..e5d9458 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -21,6 +21,11 @@ struct fiq_chip { void (*fiq_enable)(struct irq_data *data); void (*fiq_disable)(struct irq_data *data); + + /* .fiq_eoi() will be called from the FIQ handler. For this + * reason it must not use spin locks (or any other locks). + */ + void (*fiq_eoi)(struct irq_data *data); };
struct fiq_handler { @@ -43,6 +48,7 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 567f8fd..edde332 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -183,6 +183,15 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); }
+void eoi_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_eoi) + data->fiq_chip->fiq_eoi(data->irq_data); +} +EXPORT_SYMBOL(eoi_fiq); + bool has_fiq(int fiq) { struct fiq_data *data = lookup_fiq_data(fiq);
All GIC hardware except GICv1-without-TrustZone support provides a means to group exceptions into group 0 (which can optionally be signally using use FIQ) and group 1. The kernel currently provides no means to exploit this. This patch alters the initialization of the GIC to place all interrupts into group 1 which is the foundational requirement to meaningfully use FIQ.
Note that the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world" (this feature allows grouping to be used to allow hardware peripherals to send interrupts into the secure world). The GIC driver will automatically detect this and disable its attempts to group interrupts.
On systems without TrustZone support the kernel has the power to route interrupt sources to FIQ, potentially allowing a driver to exploit the NMI-like properties of FIQ.
Tested on Freescale i.MX6 (quad A9), STiH416 (dual A9) and a self-written qemu GICv2 model.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com Acked-by: Dirk Behme dirk.behme@de.bosch.com --- drivers/irqchip/irq-gic.c | 99 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 5 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 7e11c9d..bbffca3 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -42,6 +42,9 @@ #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h>
+#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -68,6 +71,9 @@ struct gic_chip_data { #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif +#ifdef CONFIG_FIQ + bool fiq_enable; +#endif };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); @@ -131,6 +137,16 @@ static inline void gic_set_base_accessor(struct gic_chip_data *data, #define gic_set_base_accessor(d, f) #endif
+#ifdef CONFIG_FIQ +static inline bool gic_data_fiq_enable(struct gic_chip_data *data) +{ + return data->fiq_enable; +} +#else +static inline bool gic_data_fiq_enable( + struct gic_chip_data *data) { return false; } +#endif + static inline void __iomem *gic_dist_base(struct irq_data *d) { struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d); @@ -349,6 +365,42 @@ static struct irq_chip gic_chip = { .irq_set_wake = gic_set_wake, };
+#ifdef CONFIG_FIQ +static void __init gic_init_fiq(struct gic_chip_data *gic, + irq_hw_number_t first_irq, + unsigned int num_irqs) +{ + void __iomem *dist_base = gic_data_dist_base(gic_data); + unsigned int i; + + /* + * FIQ can only be supported on platforms without an extended irq_eoi + * method (otherwise we take a lock during eoi handling). + */ + if (gic_arch_extn.irq_eoi) + return; + + /* + * If grouping is not available (not implemented or prohibited by + * security mode) these registers a read-as-zero/write-ignored. + * However as a precaution we restore the reset default regardless of + * the result of the test. + */ + writel_relaxed(1, dist_base + GIC_DIST_IGROUP + 0); + gic->fiq_enable = readl_relaxed(dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0, dist_base + GIC_DIST_IGROUP + 0); + pr_debug("gic: FIQ support %s\n", + gic->fiq_enable ? "enabled" : "disabled"); + + if (!gic->fiq_enable) + return; +} +#else /* CONFIG_FIQ */ +static inline void gic_init_fiq(struct gic_chip_data *gic, + irq_hw_number_t first_irq, + unsigned int num_irqs) {} +#endif /* CONFIG_FIQ */ + void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) { if (gic_nr >= MAX_GIC_NR) @@ -408,13 +460,28 @@ static void __init gic_dist_init(struct gic_chip_data *gic) writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4);
/* + * Optionally set all global interrupts to be group 1. + */ + if (gic_data_fiq_enable(gic)) + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, + base + GIC_DIST_IGROUP + i * 4 / 32); + + /* * Disable all interrupts. Leave the PPI and SGIs alone * as these enables are banked registers. */ for (i = 32; i < gic_irqs; i += 32) writel_relaxed(0xffffffff, base + GIC_DIST_ENABLE_CLEAR + i * 4 / 32);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + if (gic_data_fiq_enable(gic)) + writel_relaxed(3, base + GIC_DIST_CTRL); + else + writel_relaxed(1, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -452,8 +519,20 @@ static void gic_cpu_init(struct gic_chip_data *gic) for (i = 0; i < 32; i += 4) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4 / 4);
+ /* + * Set all PPI and SGI interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers are read-as-zero/write-ignored. + */ + if (gic_data_fiq_enable(gic)) + writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + if (gic_data_fiq_enable(gic)) + writel_relaxed(0x1f, base + GIC_CPU_CTRL); + else + writel_relaxed(1, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -537,7 +616,10 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + if (gic_data_fiq_enable(&gic_data[gic_nr])) + writel_relaxed(3, dist_base + GIC_DIST_CTRL); + else + writel_relaxed(1, dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -594,7 +676,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -656,6 +738,7 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0; + unsigned long softint;
raw_spin_lock_irqsave(&irq_controller_lock, flags);
@@ -670,7 +753,11 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + softint = map << 16 | irq; + if (gic_data_fiq_enable(&gic_data[0])) + softint |= 0x8000; + writel_relaxed(softint, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } @@ -1014,6 +1101,8 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start,
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, hwirq_base, &gic_irq_domain_ops, gic); + + gic_init_fiq(gic, irq_base, gic_irqs); } else { gic->domain = irq_domain_add_linear(node, nr_routable_irqs, &gic_irq_domain_ops,
This patch introduces callbacks to route interrupts to or away from the FIQ signal and registers these callbacks with the FIQ infrastructure (if the device can supports it).
Both these aspects combine and allow a driver to deploy a FIQ handler without any machine specific knowledge; it can be used effectively on multi-platform kernels.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com --- drivers/irqchip/irq-gic.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index bbffca3..0300c08 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -366,6 +366,58 @@ static struct irq_chip gic_chip = { };
#ifdef CONFIG_FIQ +/* + * Shift an interrupt between Group 0 and Group 1. + * + * In addition to changing the group we also modify the priority to + * match what "ARM strongly recommends" for a system where no Group 1 + * interrupt must ever preempt a Group 0 interrupt. + */ +static void gic_set_group_irq(struct irq_data *d, int group) +{ + unsigned int grp_reg = gic_irq(d) / 32 * 4; + u32 grp_mask = 1 << (gic_irq(d) % 32); + u32 grp_val; + + unsigned int pri_reg = (gic_irq(d) / 4) * 4; + u32 pri_mask = 1 << (7 + ((gic_irq(d) % 4) * 8)); + u32 pri_val; + + raw_spin_lock(&irq_controller_lock); + + grp_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + + if (group) { + grp_val |= grp_mask; + pri_val |= pri_mask; + } else { + grp_val &= ~grp_mask; + pri_val &= ~pri_mask; + } + + writel_relaxed(grp_val, gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + + raw_spin_unlock(&irq_controller_lock); +} + +static void gic_enable_fiq(struct irq_data *d) +{ + gic_set_group_irq(d, 0); +} + +static void gic_disable_fiq(struct irq_data *d) +{ + gic_set_group_irq(d, 1); +} + +static struct fiq_chip gic_fiq = { + .fiq_enable = gic_enable_fiq, + .fiq_disable = gic_disable_fiq, + .fiq_eoi = gic_eoi_irq, +}; + static void __init gic_init_fiq(struct gic_chip_data *gic, irq_hw_number_t first_irq, unsigned int num_irqs) @@ -394,6 +446,12 @@ static void __init gic_init_fiq(struct gic_chip_data *gic,
if (!gic->fiq_enable) return; + + /* + * FIQ is supported on this device! Register our chip data. + */ + for (i = 0; i < num_irqs; i++) + fiq_register_mapping(first_irq + i, &gic_fiq); } #else /* CONFIG_FIQ */ static inline void gic_init_fiq(struct gic_chip_data *gic,
This patch is motivated by the comment it removes from gic_init_fiq, namely that the spin locks in eoi_irq preclude certain platforms from supporting FIQ.
Currently there is only one upstream platform (tegra) that actually hooks gic_arch_extn.irq_eoi and it does not require these spin locks.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Peter De Schrijver pdeschrijver@nvidia.com --- drivers/irqchip/irq-gic.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 0300c08..1cbff1d 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -191,11 +191,8 @@ static void gic_unmask_irq(struct irq_data *d)
static void gic_eoi_irq(struct irq_data *d) { - if (gic_arch_extn.irq_eoi) { - raw_spin_lock(&irq_controller_lock); + if (gic_arch_extn.irq_eoi) gic_arch_extn.irq_eoi(d); - raw_spin_unlock(&irq_controller_lock); - }
writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI); } @@ -426,13 +423,6 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, unsigned int i;
/* - * FIQ can only be supported on platforms without an extended irq_eoi - * method (otherwise we take a lock during eoi handling). - */ - if (gic_arch_extn.irq_eoi) - return; - - /* * If grouping is not available (not implemented or prohibited by * security mode) these registers a read-as-zero/write-ignored. * However as a precaution we restore the reset default regardless of
This patch introduces callbacks to route interrupts to or away from the FIQ signal. It also causes these callbacks to be registered with the FIQ infrastructure.
This patch enable FIQ support for mach-versatile whilst mach-ep93xx, mach-netx, mach-s3c64xx and plat-samsung are unmodified (and can therefore continue to use init_FIQ() as before).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Hartley Sweeten hsweeten@visionengravers.com Cc: Ryan Mallon rmallon@gmail.com Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben-linux@fluff.org Cc: Kukjin Kim kgene.kim@samsung.com Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: linux-samsung-soc@vger.kernel.org --- arch/arm/mach-versatile/core.c | 2 +- drivers/irqchip/irq-vic.c | 92 ++++++++++++++++++++++++++++++++--------- include/linux/irqchip/arm-vic.h | 6 ++- 3 files changed, 78 insertions(+), 22 deletions(-)
diff --git a/arch/arm/mach-versatile/core.c b/arch/arm/mach-versatile/core.c index be83ba2..1abf360 100644 --- a/arch/arm/mach-versatile/core.c +++ b/arch/arm/mach-versatile/core.c @@ -108,7 +108,7 @@ void __init versatile_init_irq(void)
np = of_find_matching_node_by_address(NULL, vic_of_match, VERSATILE_VIC_BASE); - __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np); + __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np ? false : true, np);
writel(~0, VA_SIC_BASE + SIC_IRQ_ENABLE_CLEAR);
diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index 7d35287..22aa126 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -36,6 +36,9 @@
#include <asm/exception.h> #include <asm/irq.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif
#include "irqchip.h"
@@ -261,11 +264,53 @@ static struct irq_domain_ops vic_irqdomain_ops = { .xlate = irq_domain_xlate_onetwocell, };
+#ifdef CONFIG_FIQ +static DEFINE_RAW_SPINLOCK(irq_controller_lock); + +static void vic_set_fiq(struct irq_data *d, bool enable) +{ + void __iomem *base = irq_data_get_irq_chip_data(d); + unsigned int irq = d->hwirq; + u32 val; + + raw_spin_lock(&irq_controller_lock); + val = readl(base + VIC_INT_SELECT); + if (enable) + val |= 1 << irq; + else + val &= ~(1 << irq); + writel(val, base + VIC_INT_SELECT); + raw_spin_unlock(&irq_controller_lock); +} + +static void vic_enable_fiq(struct irq_data *d) +{ + vic_set_fiq(d, true); +} + +static void vic_disable_fiq(struct irq_data *d) +{ + vic_set_fiq(d, false); +} + +struct fiq_chip vic_fiq = { + .fiq_enable = vic_enable_fiq, + .fiq_disable = vic_disable_fiq, +}; + +static void vic_register_fiq(int irq) +{ + fiq_register_mapping(irq, &vic_fiq); +} +#else /* CONFIG_FIQ */ +static inline void vic_register_fiq(int irq) {} +#endif /* CONFIG_FIQ */ + /** * vic_register() - Register a VIC. * @base: The base address of the VIC. * @parent_irq: The parent IRQ if cascaded, else 0. - * @irq: The base IRQ for the VIC. + * @irq_start: The base IRQ for the VIC. * @valid_sources: bitmask of valid interrupts * @resume_sources: bitmask of interrupts allowed for resume sources. * @node: The device tree node associated with the VIC. @@ -277,12 +322,13 @@ static struct irq_domain_ops vic_irqdomain_ops = { * This also configures the IRQ domain for the VIC. */ static void __init vic_register(void __iomem *base, unsigned int parent_irq, - unsigned int irq, + unsigned int irq_start, u32 valid_sources, u32 resume_sources, - struct device_node *node) + bool map_fiqs, struct device_node *node) { struct vic_device *v; int i; + unsigned int irq;
if (vic_id >= ARRAY_SIZE(vic_devices)) { printk(KERN_ERR "%s: too few VICs, increase CONFIG_ARM_VIC_NR\n", __func__); @@ -301,15 +347,19 @@ static void __init vic_register(void __iomem *base, unsigned int parent_irq, irq_set_chained_handler(parent_irq, vic_handle_irq_cascaded); }
- v->domain = irq_domain_add_simple(node, fls(valid_sources), irq, + v->domain = irq_domain_add_simple(node, fls(valid_sources), irq_start, &vic_irqdomain_ops, v); /* create an IRQ mapping for each valid IRQ */ - for (i = 0; i < fls(valid_sources); i++) - if (valid_sources & (1 << i)) - irq_create_mapping(v->domain, i); + for (i = 0; i < fls(valid_sources); i++) { + if (valid_sources & (1 << i)) { + irq = irq_create_mapping(v->domain, i); + vic_register_fiq(irq); + } + } + /* If no base IRQ was passed, figure out our allocated base */ - if (irq) - v->irq = irq; + if (irq_start) + v->irq = irq_start; else v->irq = irq_find_mapping(v->domain, 0); } @@ -413,7 +463,8 @@ static void __init vic_clear_interrupts(void __iomem *base) * and 020 within the page. We call this "second block". */ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, - u32 vic_sources, struct device_node *node) + u32 vic_sources, bool map_fiqs, + struct device_node *node) { unsigned int i; int vic_2nd_block = ((unsigned long)base & ~PAGE_MASK) != 0; @@ -439,12 +490,12 @@ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, writel(32, base + VIC_PL190_DEF_VECT_ADDR); }
- vic_register(base, 0, irq_start, vic_sources, 0, node); + vic_register(base, 0, irq_start, vic_sources, 0, map_fiqs, node); }
void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, - struct device_node *node) + u32 vic_sources, u32 resume_sources, + bool map_fiqs, struct device_node *node) { unsigned int i; u32 cellid = 0; @@ -462,7 +513,7 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
switch(vendor) { case AMBA_VENDOR_ST: - vic_init_st(base, irq_start, vic_sources, node); + vic_init_st(base, irq_start, vic_sources, map_fiqs, node); return; default: printk(KERN_WARNING "VIC: unknown vendor, continuing anyways\n"); @@ -479,7 +530,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
vic_init2(base);
- vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, node); + vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, + map_fiqs, node); }
/** @@ -492,7 +544,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, void __init vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources) { - __vic_init(base, 0, irq_start, vic_sources, resume_sources, NULL); + __vic_init(base, 0, irq_start, vic_sources, resume_sources, + false, NULL); }
/** @@ -511,7 +564,8 @@ int __init vic_init_cascaded(void __iomem *base, unsigned int parent_irq, struct vic_device *v;
v = &vic_devices[vic_id]; - __vic_init(base, parent_irq, 0, vic_sources, resume_sources, NULL); + __vic_init(base, parent_irq, 0, vic_sources, resume_sources, false, + NULL); /* Return out acquired base */ return v->irq; } @@ -535,9 +589,9 @@ int __init vic_of_init(struct device_node *node, struct device_node *parent) of_property_read_u32(node, "valid-wakeup-mask", &wakeup_mask);
/* - * Passing 0 as first IRQ makes the simple domain allocate descriptors + * Passing 0 as first IRQ makes the domain allocate descriptors. */ - __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, node); + __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, true, node);
return 0; } diff --git a/include/linux/irqchip/arm-vic.h b/include/linux/irqchip/arm-vic.h index ba46c79..30ab39f 100644 --- a/include/linux/irqchip/arm-vic.h +++ b/include/linux/irqchip/arm-vic.h @@ -30,8 +30,10 @@ struct device_node; struct pt_regs;
void __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, struct device_node *node); -void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources); + u32 vic_sources, u32 resume_sources, + bool map_fiqs, struct device_node *node); +void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, + u32 resume_sources); int vic_init_cascaded(void __iomem *base, unsigned int parent_irq, u32 vic_sources, u32 resume_sources);
From: Anton Vorontsov anton.vorontsov@linaro.org
Just move the macros into header file as we would want to use them for KGDB FIQ entry code.
The following macros were moved:
- svc_entry - usr_entry - kuser_cmpxchg_check - vector_stub
To make kuser_cmpxchg_check actually work across different files, we also have to make kuser_cmpxchg64_fixup global.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Frederic Weisbecker fweisbec@gmail.com --- arch/arm/kernel/entry-armv.S | 151 +----------------- arch/arm/kernel/entry-header.S | 350 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 351 insertions(+), 150 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 52a949a..4172cd6 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -140,53 +140,6 @@ ENDPROC(__und_invalid) * SVC mode handlers */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) -#define SPFIX(code...) code -#else -#define SPFIX(code...) -#endif - - .macro svc_entry, stack_hole=0 - UNWIND(.fnstart ) - UNWIND(.save {r0 - pc} ) - sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) -#ifdef CONFIG_THUMB2_KERNEL - SPFIX( str r0, [sp] ) @ temporarily saved - SPFIX( mov r0, sp ) - SPFIX( tst r0, #4 ) @ test original stack alignment - SPFIX( ldr r0, [sp] ) @ restored -#else - SPFIX( tst sp, #4 ) -#endif - SPFIX( subeq sp, sp, #4 ) - stmia sp, {r1 - r12} - - ldmia r0, {r3 - r5} - add r7, sp, #S_SP - 4 @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) - SPFIX( addeq r2, r2, #4 ) - str r3, [sp, #-4]! @ save the "real" r0 copied - @ from the exception stack - - mov r3, lr - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r2 - sp_svc - @ r3 - lr_svc - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - stmia r7, {r2 - r6} - -#ifdef CONFIG_TRACE_IRQFLAGS - bl trace_hardirqs_off -#endif - .endm - .align 5 __dabt_svc: svc_entry @@ -306,73 +259,8 @@ ENDPROC(__pabt_svc)
/* * User mode handlers - * - * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0, .LCcralign - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - .align 5 __dabt_usr: usr_entry @@ -823,6 +711,7 @@ __kuser_cmpxchg64: @ 0xffff0f60 ldmfd sp!, {r4, r5, r6, pc}
.text + .global kuser_cmpxchg64_fixup kuser_cmpxchg64_fixup: @ Called from kuser_cmpxchg_fixup. @ r4 = address of interrupted insn (must be preserved). @@ -964,44 +853,6 @@ __kuser_helper_end: * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. */ - .macro vector_stub, name, mode, correction=0 - .align 5 - -vector_\name: - .if \correction - sub lr, lr, #\correction - .endif - - @ - @ Save r0, lr_<exception> (parent PC) and spsr_<exception> - @ (parent CPSR) - @ - stmia sp, {r0, lr} @ save r0, lr - mrs lr, spsr - str lr, [sp, #8] @ save spsr - - @ - @ Prepare for SVC32 mode. IRQs remain disabled. - @ - mrs r0, cpsr - eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) - msr spsr_cxsf, r0 - - @ - @ the branch table must immediately follow this code - @ - and lr, lr, #0x0f - THUMB( adr r0, 1f ) - THUMB( ldr lr, [r0, lr, lsl #2] ) - mov r0, sp - ARM( ldr lr, [pc, lr, lsl #2] ) - movs pc, lr @ branch to handler in SVC mode -ENDPROC(vector_\name) - - .align 2 - @ handler addresses follow this label -1: - .endm
.section .stubs, "ax", %progbits __stubs_start: diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 5d702f8..572f4b4 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -356,3 +356,353 @@ scno .req r7 @ syscall number tbl .req r8 @ syscall table pointer why .req r8 @ Linux syscall (!= 0) tsk .req r9 @ current thread_info + +/* + * SVC mode handler macros + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) +#define SPFIX(code...) code +#else +#define SPFIX(code...) +#endif + + .macro svc_entry, stack_hole=0 + UNWIND(.fnstart ) + UNWIND(.save {r0 - pc} ) + sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) +#ifdef CONFIG_THUMB2_KERNEL + SPFIX( str r0, [sp] ) @ temporarily saved + SPFIX( mov r0, sp ) + SPFIX( tst r0, #4 ) @ test original stack alignment + SPFIX( ldr r0, [sp] ) @ restored +#else + SPFIX( tst sp, #4 ) +#endif + SPFIX( subeq sp, sp, #4 ) + stmia sp, {r1 - r12} + + ldmia r0, {r3 - r5} + add r7, sp, #S_SP - 4 @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) + SPFIX( addeq r2, r2, #4 ) + str r3, [sp, #-4]! @ save the "real" r0 copied + @ from the exception stack + + mov r3, lr + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r2 - sp_svc + @ r3 - lr_svc + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + stmia r7, {r2 - r6} + +#ifdef CONFIG_TRACE_IRQFLAGS + bl trace_hardirqs_off +#endif + .endm + +/* + * User mode handler macros + * + * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0, .LCcralign + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0, .LCcralign + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0, .LCcralign + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0 + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +/* + * Vector stubs macro. + */ + .macro vector_stub, name, mode, correction=0 + .align 5 + +vector_\name: + .if \correction + sub lr, lr, #\correction + .endif + + @ + @ Save r0, lr_<exception> (parent PC) and spsr_<exception> + @ (parent CPSR) + @ + stmia sp, {r0, lr} @ save r0, lr + mrs lr, spsr + str lr, [sp, #8] @ save spsr + + @ + @ Prepare for SVC32 mode. IRQs remain disabled. + @ + mrs r0, cpsr + eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) + msr spsr_cxsf, r0 + + @ + @ the branch table must immediately follow this code + @ + and lr, lr, #0x0f + THUMB( adr r0, 1f ) + THUMB( ldr lr, [r0, lr, lsl #2] ) + mov r0, sp + ARM( ldr lr, [pc, lr, lsl #2] ) + movs pc, lr @ branch to handler in SVC mode +ENDPROC(vector_\name) + + .align 2 + @ handler addresses follow this label +1: + .endm + +
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Dave Martin Dave.Martin@arm.com --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++ arch/arm/include/asm/kgdb.h | 7 ++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/entry-header.S | 186 --------------------------------------- arch/arm/kernel/kgdb_fiq.c | 124 ++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 ++++++++++++++++++ 7 files changed, 239 insertions(+), 186 deletions(-) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 87b63fd..32795c2 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -304,6 +304,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -354,6 +355,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 26536f7..c7342b6 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,24 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be + enabled with kgdb_fiq.enable=1 kernel command line option. + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..5de21f01 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,11 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern char kgdb_fiq_handler; +extern char kgdb_fiq_handler_end; +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs); +extern int kgdb_register_fiq(unsigned int fiq); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 38ddd9f..30ee8f3 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -68,6 +68,7 @@ endif obj-$(CONFIG_OABI_COMPAT) += sys_oabi-compat.o obj-$(CONFIG_ARM_THUMBEE) += thumbee.o obj-$(CONFIG_KGDB) += kgdb.o +obj-$(CONFIG_KGDB_FIQ) += kgdb_fiq_entry.o kgdb_fiq.o obj-$(CONFIG_ARM_UNWIND) += unwind.o obj-$(CONFIG_HAVE_TCM) += tcm.o obj-$(CONFIG_OF) += devtree.o diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 572f4b4..eb2c426 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -477,192 +477,6 @@ tsk .req r9 @ current thread_info #endif .endm
-#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0, .LCcralign - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0, .LCcralign - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0 - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - /* * Vector stubs macro. */ diff --git a/arch/arm/kernel/kgdb_fiq.c b/arch/arm/kernel/kgdb_fiq.c new file mode 100644 index 0000000..dbf4873 --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq.c @@ -0,0 +1,124 @@ +/* + * KGDB FIQ + * + * Copyright 2010 Google, Inc. + * Arve Hjønnevåg arve@android.com + * Colin Cross ccross@android.com + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/hardirq.h> +#include <linux/atomic.h> +#include <linux/kdb.h> +#include <linux/kgdb.h> +#include <asm/fiq.h> +#include <asm/exception.h> + +static int kgdb_fiq_enabled; +module_param_named(enable, kgdb_fiq_enabled, int, 0600); +MODULE_PARM_DESC(enable, "set to 1 to enable FIQ KGDB"); + +static unsigned int kgdb_fiq; + +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs) +{ + if (kgdb_nmi_poll_knock()) { + nmi_enter(); + kgdb_handle_exception(1, 0, 0, regs); + nmi_exit(); + } + + eoi_fiq(kgdb_fiq); +} + +static struct fiq_handler kgdb_fiq_desc = { + .name = "kgdb", +}; + +static long kgdb_fiq_setup_stack(void *info) +{ + struct pt_regs regs; + + regs.ARM_sp = __get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER) + + THREAD_START_SP; + WARN_ON(!regs.ARM_sp); + + set_fiq_regs(®s); + return 0; +} + +/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * This function manages NMIs that usually cause KGDB to enter. That is, not + * all NMIs should be enabled or disabled, but only those that issue + * kgdb_handle_exception(). + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + static atomic_t cnt; + int ret; + + ret = atomic_add_return(on ? 1 : -1, &cnt); + if (ret > 1 && on) { + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + WARN_ON(1); + return; + } + + if (ret > 0) + enable_fiq(kgdb_fiq); + else + disable_fiq(kgdb_fiq); +} + +int kgdb_register_fiq(unsigned int fiq) +{ + int err; + int cpu; + + if (!kgdb_fiq_enabled) + return -ENODEV; + + if (!has_fiq(fiq)) { + pr_warn( + "%s: Cannot register %u (no FIQ with this number)\n", + __func__, fiq); + return -ENODEV; + } + + kgdb_fiq = fiq; + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + for_each_possible_cpu(cpu) + work_on_cpu(cpu, kgdb_fiq_setup_stack, NULL); + + set_fiq_handler(&kgdb_fiq_handler, + &kgdb_fiq_handler_end - &kgdb_fiq_handler); + + arch_kgdb_ops.enable_nmi = kgdb_fiq_enable_nmi; + return 0; +} diff --git a/arch/arm/kernel/kgdb_fiq_entry.S b/arch/arm/kernel/kgdb_fiq_entry.S new file mode 100644 index 0000000..d6becca --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq_entry.S @@ -0,0 +1,87 @@ +/* + * KGDB FIQ entry + * + * Copyright 1996,1997,1998 Russell King. + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/memory.h> +#include <asm/unwind.h> +#include "entry-header.S" + + .text + +@ This is needed for usr_entry/alignment_trap +.LCcralign: + .long cr_alignment +.LCdohandle: + .long kgdb_fiq_do_handle + + .macro fiq_handler + ldr r1, =.LCdohandle + mov r0, sp + adr lr, BSYM(9997f) + ldr pc, [r1] +9997: + .endm + + .align 5 +__fiq_svc: + svc_entry + fiq_handler + mov r0, sp + ldmib r0, {r1 - r14} + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + .ltorg + + .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + .ltorg + + .global kgdb_fiq_handler +kgdb_fiq_handler: + + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_svc @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f + + .global kgdb_fiq_handler_end +kgdb_fiq_handler_end:
If the AMBA bus has provided the pl011 with a FIQ resource (i.e. a second IRQ) then speculatively register it with KGDB when the polling driver is initialized.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the amba-pl011 driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/amba-pl011.c | 99 ++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 41 deletions(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 908a6e3..00ba4b6 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -58,6 +58,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/sizes.h> #include <linux/io.h> +#include <linux/kgdb.h>
#define UART_NR 14
@@ -1416,8 +1417,63 @@ static void pl011_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&uap->port.lock, flags); }
+static int pl011_hwinit(struct uart_port *port) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + int retval; + + /* Optionaly enable pins to be muxed in and configured */ + pinctrl_pm_select_default_state(port->dev); + + /* + * Try to enable the clock producer. + */ + retval = clk_prepare_enable(uap->clk); + if (retval) + goto out; + + uap->port.uartclk = clk_get_rate(uap->clk); + + /* Clear pending error and receive interrupts */ + writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | + UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); + + /* + * Save interrupts enable mask, and enable RX interrupts in case if + * the interrupt is used for NMI entry. + */ + uap->im = readw(uap->port.membase + UART011_IMSC); + writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); + + if (dev_get_platdata(uap->port.dev)) { + struct amba_pl011_data *plat; + + plat = dev_get_platdata(uap->port.dev); + if (plat->init) + plat->init(); + } + return 0; + out: + return retval; +} + #ifdef CONFIG_CONSOLE_POLL
+static int pl011_poll_init(struct uart_port *port) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + int retval; + + retval = pl011_hwinit(port); + +#ifdef CONFIG_KGDB_FIQ + if (retval == 0) + kgdb_register_fiq(uap->port.irq); +#endif + + return retval; +} + static void pl011_quiesce_irqs(struct uart_port *port) { struct uart_amba_port *uap = (struct uart_amba_port *)port; @@ -1471,46 +1527,6 @@ static void pl011_put_poll_char(struct uart_port *port,
#endif /* CONFIG_CONSOLE_POLL */
-static int pl011_hwinit(struct uart_port *port) -{ - struct uart_amba_port *uap = (struct uart_amba_port *)port; - int retval; - - /* Optionaly enable pins to be muxed in and configured */ - pinctrl_pm_select_default_state(port->dev); - - /* - * Try to enable the clock producer. - */ - retval = clk_prepare_enable(uap->clk); - if (retval) - goto out; - - uap->port.uartclk = clk_get_rate(uap->clk); - - /* Clear pending error and receive interrupts */ - writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | - UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); - - /* - * Save interrupts enable mask, and enable RX interrupts in case if - * the interrupt is used for NMI entry. - */ - uap->im = readw(uap->port.membase + UART011_IMSC); - writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); - - if (dev_get_platdata(uap->port.dev)) { - struct amba_pl011_data *plat; - - plat = dev_get_platdata(uap->port.dev); - if (plat->init) - plat->init(); - } - return 0; - out: - return retval; -} - static void pl011_write_lcr_h(struct uart_amba_port *uap, unsigned int lcr_h) { writew(lcr_h, uap->port.membase + uap->lcrh_rx); @@ -1890,7 +1906,7 @@ static struct uart_ops amba_pl011_pops = { .config_port = pl011_config_port, .verify_port = pl011_verify_port, #ifdef CONFIG_CONSOLE_POLL - .poll_init = pl011_hwinit, + .poll_init = pl011_poll_init, .poll_get_char = pl011_get_poll_char, .poll_put_char = pl011_put_poll_char, #endif @@ -2198,6 +2214,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) uart_unregister_driver(&amba_reg); pl011_dma_remove(uap); } + out: return ret; }
On Thu, Jun 19, 2014 at 11:38:19AM +0100, Daniel Thompson wrote:
If the AMBA bus has provided the pl011 with a FIQ resource (i.e. a second IRQ) then speculatively register it with KGDB when the polling driver is initialized.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the amba-pl011 driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org
Acked-by: Greg Kroah-Hartman gregkh@linuxfoundation.org
Add a .poll_init() function that enables UART RX and registers the UART's irq with KGDB. By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user).
Note that the RX is not only enabled but also unmasked. This is required because otherwise the FIQ handler could never trigger. This unmask is copied from similar code in amba-pl011.c .
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/st-asc.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index c7f61ac..4f376d8 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -30,6 +30,7 @@ #include <linux/of_platform.h> #include <linux/serial_core.h> #include <linux/clk.h> +#include <linux/kgdb.h>
#define DRIVER_NAME "st-asc" #define ASC_SERIAL_NAME "ttyAS" @@ -613,6 +614,25 @@ asc_verify_port(struct uart_port *port, struct serial_struct *ser) }
#ifdef CONFIG_CONSOLE_POLL + +#ifdef CONFIG_KGDB_FIQ +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int asc_poll_init(struct uart_port *port) +{ + struct asc_port *ascport = container_of(port, struct asc_port, port); + + /* register the FIQ with kgdb */ + kgdb_register_fiq(ascport->port.irq); + + /* enable RX interrupts in case the interrupt is used for NMI entry. */ + asc_enable_rx_interrupts(port); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + /* * Console polling routines for writing and reading from the uart while * in an interrupt or debug context (i.e. kgdb). @@ -656,6 +676,9 @@ static struct uart_ops asc_uart_ops = { .verify_port = asc_verify_port, .pm = asc_pm, #ifdef CONFIG_CONSOLE_POLL +#ifdef CONFIG_KGDB_FIQ + .poll_init = asc_poll_init, +#endif /* CONFIG_KGDB_FIQ */ .poll_get_char = asc_get_poll_char, .poll_put_char = asc_put_poll_char, #endif /* CONFIG_CONSOLE_POLL */
On Thu, Jun 19, 2014 at 11:38:20AM +0100, Daniel Thompson wrote:
Add a .poll_init() function that enables UART RX and registers the UART's irq with KGDB. By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user).
Note that the RX is not only enabled but also unmasked. This is required because otherwise the FIQ handler could never trigger. This unmask is copied from similar code in amba-pl011.c .
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org
Acked-by: Greg Kroah-Hartman gregkh@linuxfoundation.org
The architectures supported by this driver have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This driver is cross compilable for testing purposes and remains compilable on all architectures by falling back to writel() when writel_relaxed() does not exist. We also include explicit compiler barriers. There are redundant on ARM and SH but important on x86 because it defines "relaxed" differently.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/st-asc.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 4f376d8..58aa1c6 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -152,12 +152,21 @@ static inline struct asc_port *to_asc_port(struct uart_port *port)
static inline u32 asc_in(struct uart_port *port, u32 offset) { - return readl(port->membase + offset); + u32 r; + + r = readl_relaxed(port->membase + offset); + barrier(); + return r; }
static inline void asc_out(struct uart_port *port, u32 offset, u32 value) { +#ifdef writel_relaxed + writel_relaxed(value, port->membase + offset); + barrier(); +#else writel(value, port->membase + offset); +#endif }
/*
Hi Dan,
On 19/06/14 11:38, Daniel Thompson wrote:
The architectures supported by this driver have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This driver is cross compilable for testing purposes and remains compilable on all architectures by falling back to writel() when writel_relaxed() does not exist. We also include explicit compiler barriers. There are redundant on ARM and SH but important on x86 because it defines "relaxed" differently.
Why are we concern about x86 for this driver? As per my understanding this IP is only seen on ARM and SH based CPUs so why cant we just use relaxed versions, why ifdefs? I think, this would involve fixing the kconfig and make it depend on SH and ARM based platforms only.
On the other hand, This patch looks more generic and applicable to most of the drivers. Am not sure which way is the right one.
--srini
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org
drivers/tty/serial/st-asc.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 4f376d8..58aa1c6 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -152,12 +152,21 @@ static inline struct asc_port *to_asc_port(struct uart_port *port)
static inline u32 asc_in(struct uart_port *port, u32 offset) {
- return readl(port->membase + offset);
u32 r;
r = readl_relaxed(port->membase + offset);
barrier();
return r; }
static inline void asc_out(struct uart_port *port, u32 offset, u32 value) {
+#ifdef writel_relaxed
- writel_relaxed(value, port->membase + offset);
- barrier();
+#else writel(value, port->membase + offset); +#endif }
/*
On 19/06/14 12:29, Srinivas Kandagatla wrote:
Hi Dan,
On 19/06/14 11:38, Daniel Thompson wrote:
The architectures supported by this driver have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This driver is cross compilable for testing purposes and remains compilable on all architectures by falling back to writel() when writel_relaxed() does not exist. We also include explicit compiler barriers. There are redundant on ARM and SH but important on x86 because it defines "relaxed" differently.
Why are we concern about x86 for this driver? As per my understanding this IP is only seen on ARM and SH based CPUs so why cant we just use relaxed versions, why ifdefs? I think, this would involve fixing the kconfig and make it depend on SH and ARM based platforms only.
You mean just drop the COMPILE_TEST?
In generally I like as much code as possible to compile on x86. Its worthwhile protection against the excessive/accidental ARMisms which could easily impact less common architectures (such as SH).
On the other hand, This patch looks more generic and applicable to most of the drivers. Am not sure which way is the right one.
I'm particularly keen on doing the right thing where readl_relaxed() is concerned because this function has a compiler barrier on ARM but not on x86.
Since having asc_in/asc_out made it easy to portably make these changes I decided is was better to be redundantly exemplary than conceal secret portability issues.
Don't feel that strongly though. Can easily change it if you're unconvinced.
--srini
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org
drivers/tty/serial/st-asc.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 4f376d8..58aa1c6 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -152,12 +152,21 @@ static inline struct asc_port *to_asc_port(struct uart_port *port)
static inline u32 asc_in(struct uart_port *port, u32 offset) {
- return readl(port->membase + offset);
u32 r;
r = readl_relaxed(port->membase + offset);
barrier();
return r; }
static inline void asc_out(struct uart_port *port, u32 offset, u32
value) { +#ifdef writel_relaxed
- writel_relaxed(value, port->membase + offset);
- barrier();
+#else writel(value, port->membase + offset); +#endif }
/*
Hi Daniel,
On 06/19/2014 01:46 PM, Daniel Thompson wrote:
On 19/06/14 12:29, Srinivas Kandagatla wrote:
Hi Dan,
On 19/06/14 11:38, Daniel Thompson wrote:
The architectures supported by this driver have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This driver is cross compilable for testing purposes and remains compilable on all architectures by falling back to writel() when writel_relaxed() does not exist. We also include explicit compiler barriers. There are redundant on ARM and SH but important on x86 because it defines "relaxed" differently.
Why are we concern about x86 for this driver? As per my understanding this IP is only seen on ARM and SH based CPUs so why cant we just use relaxed versions, why ifdefs? I think, this would involve fixing the kconfig and make it depend on SH and ARM based platforms only.
You mean just drop the COMPILE_TEST?
In generally I like as much code as possible to compile on x86. Its worthwhile protection against the excessive/accidental ARMisms which could easily impact less common architectures (such as SH).
Personally, dropping COMPILE_TEST is what I would prefer.
Thanks, Maxime
On the other hand, This patch looks more generic and applicable to most of the drivers. Am not sure which way is the right one.
I'm particularly keen on doing the right thing where readl_relaxed() is concerned because this function has a compiler barrier on ARM but not on x86.
Since having asc_in/asc_out made it easy to portably make these changes I decided is was better to be redundantly exemplary than conceal secret portability issues.
Don't feel that strongly though. Can easily change it if you're unconvinced.
--srini
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/st-asc.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 4f376d8..58aa1c6 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -152,12 +152,21 @@ static inline struct asc_port *to_asc_port(struct uart_port *port)
static inline u32 asc_in(struct uart_port *port, u32 offset) {
- return readl(port->membase + offset); + u32 r; + + r
= readl_relaxed(port->membase + offset); + barrier(); + return r; }
static inline void asc_out(struct uart_port *port, u32 offset, u32 value) { +#ifdef writel_relaxed + writel_relaxed(value, port->membase + offset); + barrier(); +#else writel(value, port->membase + offset); +#endif }
/*
On 19/06/14 12:46, Daniel Thompson wrote:
On 19/06/14 12:29, Srinivas Kandagatla wrote:
Hi Dan,
On 19/06/14 11:38, Daniel Thompson wrote:
The architectures supported by this driver have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This driver is cross compilable for testing purposes and remains compilable on all architectures by falling back to writel() when writel_relaxed() does not exist. We also include explicit compiler barriers. There are redundant on ARM and SH but important on x86 because it defines "relaxed" differently.
Why are we concern about x86 for this driver? As per my understanding this IP is only seen on ARM and SH based CPUs so why cant we just use relaxed versions, why ifdefs? I think, this would involve fixing the kconfig and make it depend on SH and ARM based platforms only.
You mean just drop the COMPILE_TEST?
In generally I like as much code as possible to compile on x86. Its worthwhile protection against the excessive/accidental ARMisms which could easily impact less common architectures (such as SH).
That's fair. Does this mean that we are going do similar changes to other ST drivers too?
TBH, there is no SH based SOCs in mainline which uses this driver. I don't think ST would add SH only SOCs to mainline in near future.
On the other hand, This patch looks more generic and applicable to most of the drivers. Am not sure which way is the right one.
I'm particularly keen on doing the right thing where readl_relaxed() is concerned because this function has a compiler barrier on ARM but not on x86.
My only concern is code duplication all across ST drivers.
Since having asc_in/asc_out made it easy to portably make these changes I decided is was better to be redundantly exemplary than conceal secret portability issues.
Your change would fit in nicely with as asc_in/out are wrappers and fix st-asc but this would be just for asc driver. What about other drivers which fall in same category?
So I think we should just drop COMPILE_TEST and possibly make it specific to ARM and SH or ARM only.
--srini
Don't feel that strongly though. Can easily change it if you're unconvinced.
--srini
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org
drivers/tty/serial/st-asc.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 4f376d8..58aa1c6 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -152,12 +152,21 @@ static inline struct asc_port *to_asc_port(struct uart_port *port)
static inline u32 asc_in(struct uart_port *port, u32 offset) {
- return readl(port->membase + offset);
u32 r;
r = readl_relaxed(port->membase + offset);
barrier();
return r; }
static inline void asc_out(struct uart_port *port, u32 offset, u32
value) { +#ifdef writel_relaxed
- writel_relaxed(value, port->membase + offset);
- barrier();
+#else writel(value, port->membase + offset); +#endif }
/*
On 19/06/14 13:01, Srinivas Kandagatla wrote:
Why are we concern about x86 for this driver? As per my understanding this IP is only seen on ARM and SH based CPUs so why cant we just use relaxed versions, why ifdefs? I think, this would involve fixing the kconfig and make it depend on SH and ARM based platforms only.
You mean just drop the COMPILE_TEST?
In generally I like as much code as possible to compile on x86. Its worthwhile protection against the excessive/accidental ARMisms which could easily impact less common architectures (such as SH).
That's fair. Does this mean that we are going do similar changes to other ST drivers too?
I didn't give any thought at all to other ST drivers. I don't see why a *general* preference (of mine or anyone else) would override what is right for any particular driver.
I don't think "both manage ST peripherals" means drivers have much in common.
On the other hand, This patch looks more generic and applicable to most of the drivers. Am not sure which way is the right one.
I'm particularly keen on doing the right thing where readl_relaxed() is concerned because this function has a compiler barrier on ARM but not on x86.
My only concern is code duplication all across ST drivers.
I really struggle to understand this. Why would anyone copy code out of the asc driver into the network driver (or any other ST driver)?
Since having asc_in/asc_out made it easy to portably make these changes I decided is was better to be redundantly exemplary than conceal secret portability issues.
Your change would fit in nicely with as asc_in/out are wrappers and fix st-asc but this would be just for asc driver. What about other drivers which fall in same category?
So I think we should just drop COMPILE_TEST and possibly make it specific to ARM and SH or ARM only.
I'm slightly uneasy about this primarily because all the rationale above describes a concern about drivers other than the one I seek to change. They ought to be outside the scope of this change.
Nevertheless, since I said I don't feel that strongly about it, as you wish...
I'll change this in v5.
From: Dirk Behme dirk.behme@de.bosch.com
Looking at the get_poll_char() function of the 8250.c serial driver, we learn:
* poll_get_char() doesn't have to save/disable/restore the interrupt registers. No interrupt handling is needed in this function at all. Remove it.
* Don't block in case there is no data available. So instead blocking in the do {} while loop, just return with NO_POLL_CHAR, immediately .
Additionally, while the i.MX6 register URXD[7-0] contain the RX_DATA, the upper bits of this register (URXD[15-10]) might contain some control flags. To ensure that these are not returned with the data read, just mask out URXD[7-0].
These changes fix the 'hang' working with kdb:
$ echo ttymxc3 > /sys/module/kgdboc/parameters/kgdboc $ echo g >/proc/sysrq-trigger [0]kdb> help ... <hang>
Signed-off-by: Dirk Behme dirk.behme@de.bosch.com Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/imx.c | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index e2f9387..25ed1d2 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -80,6 +80,7 @@ #define URXD_FRMERR (1<<12) #define URXD_BRK (1<<11) #define URXD_PRERR (1<<10) +#define URXD_RX_DATA (0xFF<<0) #define UCR1_ADEN (1<<15) /* Auto detect interrupt */ #define UCR1_ADBR (1<<14) /* Auto detect baud rate */ #define UCR1_TRDYEN (1<<13) /* Transmitter ready interrupt enable */ @@ -1503,32 +1504,10 @@ imx_verify_port(struct uart_port *port, struct serial_struct *ser) #if defined(CONFIG_CONSOLE_POLL) static int imx_poll_get_char(struct uart_port *port) { - struct imx_port_ucrs old_ucr; - unsigned int status; - unsigned char c; - - /* save control registers */ - imx_port_ucrs_save(port, &old_ucr); - - /* disable interrupts */ - writel(UCR1_UARTEN, port->membase + UCR1); - writel(old_ucr.ucr2 & ~(UCR2_ATEN | UCR2_RTSEN | UCR2_ESCI), - port->membase + UCR2); - writel(old_ucr.ucr3 & ~(UCR3_DCD | UCR3_RI | UCR3_DTREN), - port->membase + UCR3); - - /* poll */ - do { - status = readl(port->membase + USR2); - } while (~status & USR2_RDR); - - /* read */ - c = readl(port->membase + URXD0); - - /* restore control registers */ - imx_port_ucrs_restore(port, &old_ucr); + if (!(readl(port->membase + USR2) & USR2_RDR)) + return NO_POLL_CHAR;
- return c; + return readl(port->membase + URXD0) & URXD_RX_DATA; }
static void imx_poll_put_char(struct uart_port *port, unsigned char c)
This patch makes it possible to use the imx uart with KGDB's FIQ/NMI mode.
Main changes are:
.poll_init() will, if KGDB+FIQ are enabled, perform deeper hardware initialization to ensure the serial port is always active (required otherwise FIQ is not triggered by UART activity). This has an impact on power usage so it is conservatively enabled.
imx_put_poll_char() has been simplified to remove the code to disable interrupts. The present code can corrupt register state when re-entered from FIQ handler.
Both imx_put_poll_char() and imx_get_poll_char() adopt _relaxed() MMIO functions (which are safe for polled I/O and needed to avoid taking spin locks).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/imx.c | 71 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 19 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 25ed1d2..a2baf7e 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -49,6 +49,7 @@ #include <linux/of_device.h> #include <linux/io.h> #include <linux/dma-mapping.h> +#include <linux/kgdb.h>
#include <asm/irq.h> #include <linux/platform_data/serial-imx.h> @@ -1502,44 +1503,73 @@ imx_verify_port(struct uart_port *port, struct serial_struct *ser) }
#if defined(CONFIG_CONSOLE_POLL) + +#if defined(CONFIG_KGDB_FIQ) +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int imx_poll_init(struct uart_port *port) +{ + struct imx_port *sport = (struct imx_port *)port; + unsigned long flags; + unsigned long temp; + int retval; + + retval = clk_prepare_enable(sport->clk_ipg); + if (retval) + return retval; + retval = clk_prepare_enable(sport->clk_per); + if (retval) + clk_disable_unprepare(sport->clk_ipg); + + imx_setup_ufcr(sport, 0); + + spin_lock_irqsave(&sport->port.lock, flags); + + temp = readl(sport->port.membase + UCR1); + if (is_imx1_uart(sport)) + temp |= IMX1_UCR1_UARTCLKEN; + temp |= UCR1_UARTEN | UCR1_RRDYEN; + temp &= ~(UCR1_TXMPTYEN | UCR1_RTSDEN); + writel(temp, sport->port.membase + UCR1); + + temp = readl(sport->port.membase + UCR2); + temp |= UCR2_RXEN; + writel(temp, sport->port.membase + UCR2); + + spin_unlock_irqrestore(&sport->port.lock, flags); + + /* register the FIQ with kgdb */ + kgdb_register_fiq(sport->port.irq); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + static int imx_poll_get_char(struct uart_port *port) { - if (!(readl(port->membase + USR2) & USR2_RDR)) + if (!(readl_relaxed(port->membase + USR2) & USR2_RDR)) return NO_POLL_CHAR;
- return readl(port->membase + URXD0) & URXD_RX_DATA; + return readl_relaxed(port->membase + URXD0) & URXD_RX_DATA; }
static void imx_poll_put_char(struct uart_port *port, unsigned char c) { - struct imx_port_ucrs old_ucr; unsigned int status;
- /* save control registers */ - imx_port_ucrs_save(port, &old_ucr); - - /* disable interrupts */ - writel(UCR1_UARTEN, port->membase + UCR1); - writel(old_ucr.ucr2 & ~(UCR2_ATEN | UCR2_RTSEN | UCR2_ESCI), - port->membase + UCR2); - writel(old_ucr.ucr3 & ~(UCR3_DCD | UCR3_RI | UCR3_DTREN), - port->membase + UCR3); - /* drain */ do { - status = readl(port->membase + USR1); + status = readl_relaxed(port->membase + USR1); } while (~status & USR1_TRDY);
/* write */ - writel(c, port->membase + URTX0); + writel_relaxed(c, port->membase + URTX0);
/* flush */ do { - status = readl(port->membase + USR2); + status = readl_relaxed(port->membase + USR2); } while (~status & USR2_TXDC); - - /* restore control registers */ - imx_port_ucrs_restore(port, &old_ucr); } #endif
@@ -1560,6 +1590,9 @@ static struct uart_ops imx_pops = { .config_port = imx_config_port, .verify_port = imx_verify_port, #if defined(CONFIG_CONSOLE_POLL) +#if defined(CONFIG_KGDB_FIQ) + .poll_init = imx_poll_init, +#endif .poll_get_char = imx_poll_get_char, .poll_put_char = imx_poll_put_char, #endif
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches have been previously circulated as part of a large patchset mixing together ARM architecture code and driver changes (http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901 ). This patchset is dramatically cut down to include only the arch/arm code. The driver code (irqchip and tty/serial) will follow when/if the arch code is accepted.
The first two patches modify the FIQ infrastructure to allow interrupt controller drivers to register callbacks (the fiq_chip structure) to manage FIQ routings and to signal FIQ EOI. This makes it possible to use FIQ in multi-platform kernels and with recent ARM interrupt controllers.
The remaining two patches provide architecture support for KGDB's NMI feature (and rely upon the preceding changes to the FIQ code).
Tested on qemu (versatile), STiH416 (B2020 board) and Freescale i.MX6 quad (wandboard).
Changes since v5:
- Separated anything not strictly impacting arch/arm. - Fixed a spurious add/remove of code within the series (there was broken code in intermediate patches)
For previous changes see: http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901
Anton Vorontsov (2): ARM: Move some macros from entry-armv to entry-header ARM: Add KGDB/KDB FIQ debugger generic code
Daniel Thompson (2): arm: fiq: Add callbacks to manage FIQ routings arm: fiq: Allow EOI to be communicated to the intc
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 +++++ arch/arm/include/asm/fiq.h | 13 ++++ arch/arm/include/asm/kgdb.h | 7 ++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/entry-armv.S | 151 +---------------------------------- arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/fiq.c | 112 +++++++++++++++++++++++++- arch/arm/kernel/kgdb_fiq.c | 124 +++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++++++++ 10 files changed, 527 insertions(+), 152 deletions(-) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
-- 1.9.3
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org --- arch/arm/include/asm/fiq.h | 7 +++ arch/arm/kernel/fiq.c | 103 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..a7806ef 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -18,6 +18,11 @@
#include <asm/ptrace.h>
+struct fiq_chip { + void (*fiq_enable)(struct irq_data *data); + void (*fiq_disable)(struct irq_data *data); +}; + struct fiq_handler { struct fiq_handler *next; /* Name @@ -38,6 +43,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern bool has_fiq(int fiq); +extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..567f8fd 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,9 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h> +#include <linux/radix-tree.h> +#include <linux/slab.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -52,7 +55,15 @@ (unsigned)&vector_fiq_offset; \ })
+struct fiq_data { + struct fiq_chip *fiq_chip; + struct irq_data *irq_data; +}; + static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_data_tree, GFP_KERNEL); +static DEFINE_MUTEX(fiq_data_mutex);
/* Default reacquire function * - we always relinquish FIQ control @@ -127,18 +138,65 @@ void release_fiq(struct fiq_handler *f) while (current_fiq->fiq_op(current_fiq->dev_id, 0)); }
-static int fiq_start; +static struct fiq_data *lookup_fiq_data(int fiq) +{ + struct fiq_data *data; + + rcu_read_lock(); + data = radix_tree_lookup(&fiq_data_tree, fiq); + rcu_read_unlock(); + + return data; +}
void enable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_enable) + data->fiq_chip->fiq_enable(data->irq_data); + enable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + enable_irq(fiq + fiq_start); }
void disable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_disable) + data->fiq_chip->fiq_disable(data->irq_data); + disable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + disable_irq(fiq + fiq_start); }
+bool has_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) + return true; + + if (fiq_start == -1) + return false; + + return fiq > fiq_start; +} +EXPORT_SYMBOL(has_fiq); + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -147,9 +205,50 @@ EXPORT_SYMBOL(release_fiq); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq);
+/* + * Add a mapping from a Linux irq to the fiq data. + */ +void fiq_register_mapping(int irq, struct fiq_chip *chip) +{ + struct fiq_data *fiq_data = NULL; + int res; + + /* fiq_register_mapping can't be mixed with init_FIQ */ + BUG_ON(fiq_start != -1); + + fiq_data = kmalloc(sizeof(*fiq_data), GFP_KERNEL); + if (!fiq_data) + goto err; + + fiq_data->fiq_chip = chip; + fiq_data->irq_data = irq_get_irq_data(irq); + BUG_ON(!fiq_data->irq_data); + + mutex_lock(&fiq_data_mutex); + res = radix_tree_insert(&fiq_data_tree, irq, fiq_data); + mutex_unlock(&fiq_data_mutex); + if (res) + goto err; + + return; + +err: + kfree(fiq_data); + pr_err("fiq: Cannot register mapping %d\n", irq); +} + +/* + * Set the offset between normal IRQs and their FIQ shadows. + */ void __init init_FIQ(int start) { + fiq_start = start; +} + +static int __init init_default_fiq_handler(void) +{ unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); - fiq_start = start; + return 0; } +pure_initcall(init_default_fiq_handler);
On Tue, 24 Jun 2014, Daniel Thompson wrote:
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org
arch/arm/include/asm/fiq.h | 7 +++ arch/arm/kernel/fiq.c | 103 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-)
[...]
+bool has_fiq(int fiq) +{
- struct fiq_data *data = lookup_fiq_data(fiq);
- if (data)
return true;
- if (fiq_start == -1)
return false;
- return fiq > fiq_start;
Shouldn't this be fiq >= fiq_start ?
Other than that...
Acked-by: Nicolas Pitre nico@linaro.org
Nicolas
On 24/06/14 16:44, Nicolas Pitre wrote:
On Tue, 24 Jun 2014, Daniel Thompson wrote:
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org
arch/arm/include/asm/fiq.h | 7 +++ arch/arm/kernel/fiq.c | 103 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 2 deletions(-)
[...]
+bool has_fiq(int fiq) +{
- struct fiq_data *data = lookup_fiq_data(fiq);
- if (data)
return true;
- if (fiq_start == -1)
return false;
- return fiq > fiq_start;
Shouldn't this be fiq >= fiq_start ?
Absolutely! Will fix that shortly.
Thanks
Daniel.
Modern ARM systems require an EOI to be sent to the interrupt controller on completion of both IRQ and FIQ. The FIQ code currently does not provide any API to perform this. This patch provides this API, implemented by adding a callback to the fiq_chip structure.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org --- arch/arm/include/asm/fiq.h | 6 ++++++ arch/arm/kernel/fiq.c | 9 +++++++++ 2 files changed, 15 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index a7806ef..e5d9458 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -21,6 +21,11 @@ struct fiq_chip { void (*fiq_enable)(struct irq_data *data); void (*fiq_disable)(struct irq_data *data); + + /* .fiq_eoi() will be called from the FIQ handler. For this + * reason it must not use spin locks (or any other locks). + */ + void (*fiq_eoi)(struct irq_data *data); };
struct fiq_handler { @@ -43,6 +48,7 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 567f8fd..edde332 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -183,6 +183,15 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); }
+void eoi_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_eoi) + data->fiq_chip->fiq_eoi(data->irq_data); +} +EXPORT_SYMBOL(eoi_fiq); + bool has_fiq(int fiq) { struct fiq_data *data = lookup_fiq_data(fiq);
On Tue, 24 Jun 2014, Daniel Thompson wrote:
Modern ARM systems require an EOI to be sent to the interrupt controller on completion of both IRQ and FIQ. The FIQ code currently does not provide any API to perform this. This patch provides this API, implemented by adding a callback to the fiq_chip structure.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com Cc: Nicolas Pitre nico@linaro.org
Acked-by: Nicolas Pitre nico@linaro.org
arch/arm/include/asm/fiq.h | 6 ++++++ arch/arm/kernel/fiq.c | 9 +++++++++ 2 files changed, 15 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index a7806ef..e5d9458 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -21,6 +21,11 @@ struct fiq_chip { void (*fiq_enable)(struct irq_data *data); void (*fiq_disable)(struct irq_data *data);
- /* .fiq_eoi() will be called from the FIQ handler. For this
* reason it must not use spin locks (or any other locks).
*/
- void (*fiq_eoi)(struct irq_data *data);
}; struct fiq_handler { @@ -43,6 +48,7 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); extern void fiq_register_mapping(int irq, struct fiq_chip *chip); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 567f8fd..edde332 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -183,6 +183,15 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); } +void eoi_fiq(int fiq) +{
- struct fiq_data *data = lookup_fiq_data(fiq);
- if (data && data->fiq_chip->fiq_eoi)
data->fiq_chip->fiq_eoi(data->irq_data);
+} +EXPORT_SYMBOL(eoi_fiq);
bool has_fiq(int fiq) { struct fiq_data *data = lookup_fiq_data(fiq); -- 1.9.3
From: Anton Vorontsov anton.vorontsov@linaro.org
Just move the macros into header file as we would want to use them for KGDB FIQ entry code.
The following macros were moved:
- svc_entry - usr_entry - kuser_cmpxchg_check - vector_stub
To make kuser_cmpxchg_check actually work across different files, we also have to make kuser_cmpxchg64_fixup global.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Frederic Weisbecker fweisbec@gmail.com --- arch/arm/kernel/entry-armv.S | 151 +------------------------------------ arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 150 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 52a949a..4172cd6 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -140,53 +140,6 @@ ENDPROC(__und_invalid) * SVC mode handlers */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) -#define SPFIX(code...) code -#else -#define SPFIX(code...) -#endif - - .macro svc_entry, stack_hole=0 - UNWIND(.fnstart ) - UNWIND(.save {r0 - pc} ) - sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) -#ifdef CONFIG_THUMB2_KERNEL - SPFIX( str r0, [sp] ) @ temporarily saved - SPFIX( mov r0, sp ) - SPFIX( tst r0, #4 ) @ test original stack alignment - SPFIX( ldr r0, [sp] ) @ restored -#else - SPFIX( tst sp, #4 ) -#endif - SPFIX( subeq sp, sp, #4 ) - stmia sp, {r1 - r12} - - ldmia r0, {r3 - r5} - add r7, sp, #S_SP - 4 @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) - SPFIX( addeq r2, r2, #4 ) - str r3, [sp, #-4]! @ save the "real" r0 copied - @ from the exception stack - - mov r3, lr - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r2 - sp_svc - @ r3 - lr_svc - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - stmia r7, {r2 - r6} - -#ifdef CONFIG_TRACE_IRQFLAGS - bl trace_hardirqs_off -#endif - .endm - .align 5 __dabt_svc: svc_entry @@ -306,73 +259,8 @@ ENDPROC(__pabt_svc)
/* * User mode handlers - * - * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0, .LCcralign - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - .align 5 __dabt_usr: usr_entry @@ -823,6 +711,7 @@ __kuser_cmpxchg64: @ 0xffff0f60 ldmfd sp!, {r4, r5, r6, pc}
.text + .global kuser_cmpxchg64_fixup kuser_cmpxchg64_fixup: @ Called from kuser_cmpxchg_fixup. @ r4 = address of interrupted insn (must be preserved). @@ -964,44 +853,6 @@ __kuser_helper_end: * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. */ - .macro vector_stub, name, mode, correction=0 - .align 5 - -vector_\name: - .if \correction - sub lr, lr, #\correction - .endif - - @ - @ Save r0, lr_<exception> (parent PC) and spsr_<exception> - @ (parent CPSR) - @ - stmia sp, {r0, lr} @ save r0, lr - mrs lr, spsr - str lr, [sp, #8] @ save spsr - - @ - @ Prepare for SVC32 mode. IRQs remain disabled. - @ - mrs r0, cpsr - eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) - msr spsr_cxsf, r0 - - @ - @ the branch table must immediately follow this code - @ - and lr, lr, #0x0f - THUMB( adr r0, 1f ) - THUMB( ldr lr, [r0, lr, lsl #2] ) - mov r0, sp - ARM( ldr lr, [pc, lr, lsl #2] ) - movs pc, lr @ branch to handler in SVC mode -ENDPROC(vector_\name) - - .align 2 - @ handler addresses follow this label -1: - .endm
.section .stubs, "ax", %progbits __stubs_start: diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 5d702f8..eb2c426 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -356,3 +356,167 @@ scno .req r7 @ syscall number tbl .req r8 @ syscall table pointer why .req r8 @ Linux syscall (!= 0) tsk .req r9 @ current thread_info + +/* + * SVC mode handler macros + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) +#define SPFIX(code...) code +#else +#define SPFIX(code...) +#endif + + .macro svc_entry, stack_hole=0 + UNWIND(.fnstart ) + UNWIND(.save {r0 - pc} ) + sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) +#ifdef CONFIG_THUMB2_KERNEL + SPFIX( str r0, [sp] ) @ temporarily saved + SPFIX( mov r0, sp ) + SPFIX( tst r0, #4 ) @ test original stack alignment + SPFIX( ldr r0, [sp] ) @ restored +#else + SPFIX( tst sp, #4 ) +#endif + SPFIX( subeq sp, sp, #4 ) + stmia sp, {r1 - r12} + + ldmia r0, {r3 - r5} + add r7, sp, #S_SP - 4 @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) + SPFIX( addeq r2, r2, #4 ) + str r3, [sp, #-4]! @ save the "real" r0 copied + @ from the exception stack + + mov r3, lr + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r2 - sp_svc + @ r3 - lr_svc + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + stmia r7, {r2 - r6} + +#ifdef CONFIG_TRACE_IRQFLAGS + bl trace_hardirqs_off +#endif + .endm + +/* + * User mode handler macros + * + * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0, .LCcralign + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +/* + * Vector stubs macro. + */ + .macro vector_stub, name, mode, correction=0 + .align 5 + +vector_\name: + .if \correction + sub lr, lr, #\correction + .endif + + @ + @ Save r0, lr_<exception> (parent PC) and spsr_<exception> + @ (parent CPSR) + @ + stmia sp, {r0, lr} @ save r0, lr + mrs lr, spsr + str lr, [sp, #8] @ save spsr + + @ + @ Prepare for SVC32 mode. IRQs remain disabled. + @ + mrs r0, cpsr + eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) + msr spsr_cxsf, r0 + + @ + @ the branch table must immediately follow this code + @ + and lr, lr, #0x0f + THUMB( adr r0, 1f ) + THUMB( ldr lr, [r0, lr, lsl #2] ) + mov r0, sp + ARM( ldr lr, [pc, lr, lsl #2] ) + movs pc, lr @ branch to handler in SVC mode +ENDPROC(vector_\name) + + .align 2 + @ handler addresses follow this label +1: + .endm + +
On Tue, 24 Jun 2014, Daniel Thompson wrote:
From: Anton Vorontsov anton.vorontsov@linaro.org
Just move the macros into header file as we would want to use them for KGDB FIQ entry code.
The following macros were moved:
- svc_entry
- usr_entry
- kuser_cmpxchg_check
- vector_stub
To make kuser_cmpxchg_check actually work across different files, we also have to make kuser_cmpxchg64_fixup global.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Frederic Weisbecker fweisbec@gmail.com
Acked-by: Nicolas Pitre nico@linaro.org
arch/arm/kernel/entry-armv.S | 151 +------------------------------------ arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 150 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 52a949a..4172cd6 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -140,53 +140,6 @@ ENDPROC(__und_invalid)
- SVC mode handlers
*/ -#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) -#define SPFIX(code...) code -#else -#define SPFIX(code...) -#endif
- .macro svc_entry, stack_hole=0
- UNWIND(.fnstart )
- UNWIND(.save {r0 - pc} )
- sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
-#ifdef CONFIG_THUMB2_KERNEL
- SPFIX( str r0, [sp] ) @ temporarily saved
- SPFIX( mov r0, sp )
- SPFIX( tst r0, #4 ) @ test original stack alignment
- SPFIX( ldr r0, [sp] ) @ restored
-#else
- SPFIX( tst sp, #4 )
-#endif
- SPFIX( subeq sp, sp, #4 )
- stmia sp, {r1 - r12}
- ldmia r0, {r3 - r5}
- add r7, sp, #S_SP - 4 @ here for interlock avoidance
- mov r6, #-1 @ "" "" "" ""
- add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4)
- SPFIX( addeq r2, r2, #4 )
- str r3, [sp, #-4]! @ save the "real" r0 copied
@ from the exception stack
- mov r3, lr
- @
- @ We are now ready to fill in the remaining blanks on the stack:
- @
- @ r2 - sp_svc
- @ r3 - lr_svc
- @ r4 - lr_<exception>, already fixed up for correct return/restart
- @ r5 - spsr_<exception>
- @ r6 - orig_r0 (see pt_regs definition in ptrace.h)
- @
- stmia r7, {r2 - r6}
-#ifdef CONFIG_TRACE_IRQFLAGS
- bl trace_hardirqs_off
-#endif
- .endm
- .align 5
__dabt_svc: svc_entry @@ -306,73 +259,8 @@ ENDPROC(__pabt_svc) /*
- User mode handlers
*/
- EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif
- .macro usr_entry
- UNWIND(.fnstart )
- UNWIND(.cantunwind ) @ don't unwind the user space
- sub sp, sp, #S_FRAME_SIZE
- ARM( stmib sp, {r1 - r12} )
- THUMB( stmia sp, {r0 - r12} )
- ldmia r0, {r3 - r5}
- add r0, sp, #S_PC @ here for interlock avoidance
- mov r6, #-1 @ "" "" "" ""
- str r3, [sp] @ save the "real" r0 copied
@ from the exception stack
- @
- @ We are now ready to fill in the remaining blanks on the stack:
- @
- @ r4 - lr_<exception>, already fixed up for correct return/restart
- @ r5 - spsr_<exception>
- @ r6 - orig_r0 (see pt_regs definition in ptrace.h)
- @
- @ Also, separately save sp_usr and lr_usr
- @
- stmia r0, {r4 - r6}
- ARM( stmdb r0, {sp, lr}^ )
- THUMB( store_user_sp_lr r0, r1, S_SP - S_PC )
- @
- @ Enable the alignment trap while in kernel mode
- @
- alignment_trap r0, .LCcralign
- @
- @ Clear FP to mark the first stack frame
- @
- zero_fp
-#ifdef CONFIG_IRQSOFF_TRACER
- bl trace_hardirqs_off
-#endif
- ct_user_exit save = 0
- .endm
- .macro kuser_cmpxchg_check
-#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \
- !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)
-#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else
- @ Make sure our user space atomic helper is restarted
- @ if it was interrupted in a critical region. Here we
- @ perform a quick test inline since it should be false
- @ 99.9999% of the time. The rest is done out of line.
- cmp r4, #TASK_SIZE
- blhs kuser_cmpxchg64_fixup
-#endif -#endif
- .endm
- .align 5
__dabt_usr: usr_entry @@ -823,6 +711,7 @@ __kuser_cmpxchg64: @ 0xffff0f60 ldmfd sp!, {r4, r5, r6, pc} .text
- .global kuser_cmpxchg64_fixup
kuser_cmpxchg64_fixup: @ Called from kuser_cmpxchg_fixup. @ r4 = address of interrupted insn (must be preserved). @@ -964,44 +853,6 @@ __kuser_helper_end:
- SP points to a minimal amount of processor-private memory, the address
- of which is copied into r0 for the mode specific abort handler.
*/
- .macro vector_stub, name, mode, correction=0
- .align 5
-vector_\name:
- .if \correction
- sub lr, lr, #\correction
- .endif
- @
- @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
- @ (parent CPSR)
- @
- stmia sp, {r0, lr} @ save r0, lr
- mrs lr, spsr
- str lr, [sp, #8] @ save spsr
- @
- @ Prepare for SVC32 mode. IRQs remain disabled.
- @
- mrs r0, cpsr
- eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
- msr spsr_cxsf, r0
- @
- @ the branch table must immediately follow this code
- @
- and lr, lr, #0x0f
- THUMB( adr r0, 1f )
- THUMB( ldr lr, [r0, lr, lsl #2] )
- mov r0, sp
- ARM( ldr lr, [pc, lr, lsl #2] )
- movs pc, lr @ branch to handler in SVC mode
-ENDPROC(vector_\name)
- .align 2
- @ handler addresses follow this label
-1:
- .endm
.section .stubs, "ax", %progbits __stubs_start: diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 5d702f8..eb2c426 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -356,3 +356,167 @@ scno .req r7 @ syscall number tbl .req r8 @ syscall table pointer why .req r8 @ Linux syscall (!= 0) tsk .req r9 @ current thread_info
+/*
- SVC mode handler macros
- */
+#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) +#define SPFIX(code...) code +#else +#define SPFIX(code...) +#endif
- .macro svc_entry, stack_hole=0
- UNWIND(.fnstart )
- UNWIND(.save {r0 - pc} )
- sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
+#ifdef CONFIG_THUMB2_KERNEL
- SPFIX( str r0, [sp] ) @ temporarily saved
- SPFIX( mov r0, sp )
- SPFIX( tst r0, #4 ) @ test original stack alignment
- SPFIX( ldr r0, [sp] ) @ restored
+#else
- SPFIX( tst sp, #4 )
+#endif
- SPFIX( subeq sp, sp, #4 )
- stmia sp, {r1 - r12}
- ldmia r0, {r3 - r5}
- add r7, sp, #S_SP - 4 @ here for interlock avoidance
- mov r6, #-1 @ "" "" "" ""
- add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4)
- SPFIX( addeq r2, r2, #4 )
- str r3, [sp, #-4]! @ save the "real" r0 copied
@ from the exception stack
- mov r3, lr
- @
- @ We are now ready to fill in the remaining blanks on the stack:
- @
- @ r2 - sp_svc
- @ r3 - lr_svc
- @ r4 - lr_<exception>, already fixed up for correct return/restart
- @ r5 - spsr_<exception>
- @ r6 - orig_r0 (see pt_regs definition in ptrace.h)
- @
- stmia r7, {r2 - r6}
+#ifdef CONFIG_TRACE_IRQFLAGS
- bl trace_hardirqs_off
+#endif
- .endm
+/*
- User mode handler macros
- EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE
- */
+#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif
- .macro usr_entry
- UNWIND(.fnstart )
- UNWIND(.cantunwind ) @ don't unwind the user space
- sub sp, sp, #S_FRAME_SIZE
- ARM( stmib sp, {r1 - r12} )
- THUMB( stmia sp, {r0 - r12} )
- ldmia r0, {r3 - r5}
- add r0, sp, #S_PC @ here for interlock avoidance
- mov r6, #-1 @ "" "" "" ""
- str r3, [sp] @ save the "real" r0 copied
@ from the exception stack
- @
- @ We are now ready to fill in the remaining blanks on the stack:
- @
- @ r4 - lr_<exception>, already fixed up for correct return/restart
- @ r5 - spsr_<exception>
- @ r6 - orig_r0 (see pt_regs definition in ptrace.h)
- @
- @ Also, separately save sp_usr and lr_usr
- @
- stmia r0, {r4 - r6}
- ARM( stmdb r0, {sp, lr}^ )
- THUMB( store_user_sp_lr r0, r1, S_SP - S_PC )
- @
- @ Enable the alignment trap while in kernel mode
- @
- alignment_trap r0, .LCcralign
- @
- @ Clear FP to mark the first stack frame
- @
- zero_fp
+#ifdef CONFIG_IRQSOFF_TRACER
- bl trace_hardirqs_off
+#endif
- ct_user_exit save = 0
- .endm
- .macro kuser_cmpxchg_check
+#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \
- !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)
+#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else
- @ Make sure our user space atomic helper is restarted
- @ if it was interrupted in a critical region. Here we
- @ perform a quick test inline since it should be false
- @ 99.9999% of the time. The rest is done out of line.
- cmp r4, #TASK_SIZE
- blhs kuser_cmpxchg64_fixup
+#endif +#endif
- .endm
+/*
- Vector stubs macro.
- */
- .macro vector_stub, name, mode, correction=0
- .align 5
+vector_\name:
- .if \correction
- sub lr, lr, #\correction
- .endif
- @
- @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
- @ (parent CPSR)
- @
- stmia sp, {r0, lr} @ save r0, lr
- mrs lr, spsr
- str lr, [sp, #8] @ save spsr
- @
- @ Prepare for SVC32 mode. IRQs remain disabled.
- @
- mrs r0, cpsr
- eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
- msr spsr_cxsf, r0
- @
- @ the branch table must immediately follow this code
- @
- and lr, lr, #0x0f
- THUMB( adr r0, 1f )
- THUMB( ldr lr, [r0, lr, lsl #2] )
- mov r0, sp
- ARM( ldr lr, [pc, lr, lsl #2] )
- movs pc, lr @ branch to handler in SVC mode
+ENDPROC(vector_\name)
- .align 2
- @ handler addresses follow this label
+1:
- .endm
-- 1.9.3
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Dave Martin Dave.Martin@arm.com --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++++ arch/arm/include/asm/kgdb.h | 7 +++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/kgdb_fiq.c | 124 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++++++++++++++ 6 files changed, 239 insertions(+) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 245058b..f385b27 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -297,6 +297,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -346,6 +347,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 26536f7..c7342b6 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,24 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be + enabled with kgdb_fiq.enable=1 kernel command line option. + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..5de21f01 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,11 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern char kgdb_fiq_handler; +extern char kgdb_fiq_handler_end; +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs); +extern int kgdb_register_fiq(unsigned int fiq); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 38ddd9f..30ee8f3 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -68,6 +68,7 @@ endif obj-$(CONFIG_OABI_COMPAT) += sys_oabi-compat.o obj-$(CONFIG_ARM_THUMBEE) += thumbee.o obj-$(CONFIG_KGDB) += kgdb.o +obj-$(CONFIG_KGDB_FIQ) += kgdb_fiq_entry.o kgdb_fiq.o obj-$(CONFIG_ARM_UNWIND) += unwind.o obj-$(CONFIG_HAVE_TCM) += tcm.o obj-$(CONFIG_OF) += devtree.o diff --git a/arch/arm/kernel/kgdb_fiq.c b/arch/arm/kernel/kgdb_fiq.c new file mode 100644 index 0000000..dbf4873 --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq.c @@ -0,0 +1,124 @@ +/* + * KGDB FIQ + * + * Copyright 2010 Google, Inc. + * Arve Hjønnevåg arve@android.com + * Colin Cross ccross@android.com + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/hardirq.h> +#include <linux/atomic.h> +#include <linux/kdb.h> +#include <linux/kgdb.h> +#include <asm/fiq.h> +#include <asm/exception.h> + +static int kgdb_fiq_enabled; +module_param_named(enable, kgdb_fiq_enabled, int, 0600); +MODULE_PARM_DESC(enable, "set to 1 to enable FIQ KGDB"); + +static unsigned int kgdb_fiq; + +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs) +{ + if (kgdb_nmi_poll_knock()) { + nmi_enter(); + kgdb_handle_exception(1, 0, 0, regs); + nmi_exit(); + } + + eoi_fiq(kgdb_fiq); +} + +static struct fiq_handler kgdb_fiq_desc = { + .name = "kgdb", +}; + +static long kgdb_fiq_setup_stack(void *info) +{ + struct pt_regs regs; + + regs.ARM_sp = __get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER) + + THREAD_START_SP; + WARN_ON(!regs.ARM_sp); + + set_fiq_regs(®s); + return 0; +} + +/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * This function manages NMIs that usually cause KGDB to enter. That is, not + * all NMIs should be enabled or disabled, but only those that issue + * kgdb_handle_exception(). + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + static atomic_t cnt; + int ret; + + ret = atomic_add_return(on ? 1 : -1, &cnt); + if (ret > 1 && on) { + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + WARN_ON(1); + return; + } + + if (ret > 0) + enable_fiq(kgdb_fiq); + else + disable_fiq(kgdb_fiq); +} + +int kgdb_register_fiq(unsigned int fiq) +{ + int err; + int cpu; + + if (!kgdb_fiq_enabled) + return -ENODEV; + + if (!has_fiq(fiq)) { + pr_warn( + "%s: Cannot register %u (no FIQ with this number)\n", + __func__, fiq); + return -ENODEV; + } + + kgdb_fiq = fiq; + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + for_each_possible_cpu(cpu) + work_on_cpu(cpu, kgdb_fiq_setup_stack, NULL); + + set_fiq_handler(&kgdb_fiq_handler, + &kgdb_fiq_handler_end - &kgdb_fiq_handler); + + arch_kgdb_ops.enable_nmi = kgdb_fiq_enable_nmi; + return 0; +} diff --git a/arch/arm/kernel/kgdb_fiq_entry.S b/arch/arm/kernel/kgdb_fiq_entry.S new file mode 100644 index 0000000..d6becca --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq_entry.S @@ -0,0 +1,87 @@ +/* + * KGDB FIQ entry + * + * Copyright 1996,1997,1998 Russell King. + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/memory.h> +#include <asm/unwind.h> +#include "entry-header.S" + + .text + +@ This is needed for usr_entry/alignment_trap +.LCcralign: + .long cr_alignment +.LCdohandle: + .long kgdb_fiq_do_handle + + .macro fiq_handler + ldr r1, =.LCdohandle + mov r0, sp + adr lr, BSYM(9997f) + ldr pc, [r1] +9997: + .endm + + .align 5 +__fiq_svc: + svc_entry + fiq_handler + mov r0, sp + ldmib r0, {r1 - r14} + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + .ltorg + + .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + .ltorg + + .global kgdb_fiq_handler +kgdb_fiq_handler: + + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_svc @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f + + .global kgdb_fiq_handler_end +kgdb_fiq_handler_end:
On Tue, Jun 24, 2014 at 04:18:17PM +0100, Daniel Thompson wrote:
- .align 5
+__fiq_svc:
- svc_entry
Remember that the registers you have on the stack here are r0-r12, plus the SVC banked sp and lr registers. These may not be the registers from the mode you took the FIQ (eg, if it was IRQ, or abort mode.)
Also bear in mind that svc_entry calls trace_hardirqs_off - is this appropriate and safe for the FIQ to call?
- fiq_handler
- mov r0, sp
- ldmib r0, {r1 - r14}
So this restores r1 to r12, and the SVC mode sp and lr registers. Nothing touches the SVC SPSR, so we hope that retains its value throughout the FIQ processing. Note that the stack pointer at this point will be above state which we have not yet read, so we better not take any exceptions from this instruction (not even an imprecise abort).
- msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT
Here we switch to FIQ mode. What about the PSR_A_BIT which prevents imprecise aborts on ARMv6+ ?
Nevertheless, I think it's safe because the A bit will be set by the CPU when taking the FIQ exception, and it should remain set since cpsr_c won't modify it.
- add r8, r0, #S_PC
- ldr r9, [r0, #S_PSR]
- msr spsr_cxsf, r9
Here we update the FIQ SPSR with the calling mode's CPSR, ready to return...
- ldr r0, [r0, #S_R0]
Load the calling mode's R0 value.
- ldmia r8, {pc}^
And return (restoring CPSR from SPSR_fiq).
This looks pretty good except for the niggles...
On 24/06/14 17:08, Russell King - ARM Linux wrote:
On Tue, Jun 24, 2014 at 04:18:17PM +0100, Daniel Thompson wrote:
- .align 5
+__fiq_svc:
- svc_entry
Remember that the registers you have on the stack here are r0-r12, plus the SVC banked sp and lr registers. These may not be the registers from the mode you took the FIQ (eg, if it was IRQ, or abort mode.)
We probably ought to save/restore lr_abt and spsr_abt but I think sp_abt and the state for irq and und can be neglected.
The stack pointers are constant anyway and I think it reasonable to assume the FIQ handler doesn't unmask interrupts or attempt to execute undefined instructions.
Also bear in mind that svc_entry calls trace_hardirqs_off - is this appropriate and safe for the FIQ to call?
I personally think it appropriate and it looked safe on the lockdep side of things. However I will look a bit deeper at this since I don't remember how far I chased things back.
Naturally it is a problem that we don't currently call trace_hardirq_on. I'll fix this.
- fiq_handler
- mov r0, sp
- ldmib r0, {r1 - r14}
So this restores r1 to r12, and the SVC mode sp and lr registers. Nothing touches the SVC SPSR, so we hope that retains its value throughout the FIQ processing.
Are you worried about something changing it?
I haven't thought of any good reason for it to change. The FIQ handler can't safely execute a SVC instruction.
Note that the stack pointer at this point will be above state which we have not yet read, so we better not take any exceptions from this instruction (not even an imprecise abort).
Can a comment cover this? We shouldn't get an abort reading the SVC stack and imprecise abort is blocked.
Note we could copy these values back onto the FIQ stack before switching modes if is there's a possibility of an abort we cannot avoid, however I'm not know if this is worthwhile.
- msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT
Here we switch to FIQ mode. What about the PSR_A_BIT which prevents imprecise aborts on ARMv6+ ?
Nevertheless, I think it's safe because the A bit will be set by the CPU when taking the FIQ exception, and it should remain set since cpsr_c won't modify it.
Agreed.
Note that while double checking this I realized that this code will drop the value of PSR_ISETSTATE (T bit) that the vector_stub macro set for us. I'll fix this.
- add r8, r0, #S_PC
- ldr r9, [r0, #S_PSR]
- msr spsr_cxsf, r9
Here we update the FIQ SPSR with the calling mode's CPSR, ready to return...
- ldr r0, [r0, #S_R0]
Load the calling mode's R0 value.
- ldmia r8, {pc}^
And return (restoring CPSR from SPSR_fiq).
This looks pretty good except for the niggles...
Thanks.
I've picked out the following actions from the above:
1. Wrap a save and restore lr_abt and spsr_abt around the FIQ handler 2. Add a paired up trace_hardirqs_on() (and review more deeply). 3. Add comments explaining hazards w.r.t. data abort, 4. Correctly manage T bit during transition back to FIQ mode.
Do I miss anything?
Daniel.
On 26/06/14 10:54, Daniel Thompson wrote:
Also bear in mind that svc_entry calls trace_hardirqs_off - is this appropriate and safe for the FIQ to call?
I personally think it appropriate and it looked safe on the lockdep side of things. However I will look a bit deeper at this since I don't remember how far I chased things back.
I've reviewed as far as I can.
Regarding safety I can't find anything much to upset the FIQ handler. I think it might occasionally trigger the trace code's recursion avoidance causing the trace event to be dropped but that's about it.
I admit I came very close to removing the trace_hardirqs calls from the FIQ code but in the end I've left it. The hardirqs *are* off during FIQ execution.
- msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT
Here we switch to FIQ mode. What about the PSR_A_BIT which prevents imprecise aborts on ARMv6+ ?
Nevertheless, I think it's safe because the A bit will be set by the CPU when taking the FIQ exception, and it should remain set since cpsr_c won't modify it.
Agreed.
Note that while double checking this I realized that this code will drop the value of PSR_ISETSTATE (T bit) that the vector_stub macro set for us. I'll fix this.
I was wrong about this. CPSR T bit is part of execution state can cannot be modified by msr.
I've picked out the following actions from the above:
- Wrap a save and restore lr_abt and spsr_abt around the FIQ handler
Done.
- Add a paired up trace_hardirqs_on() (and review more deeply).
Done.
- Add comments explaining hazards w.r.t. data abort,
Done.
- Correctly manage T bit during transition back to FIQ mode.
Not applicable.
Do I miss anything?
I hope not!
Daniel.
On Tue, 24 Jun 2014, Daniel Thompson wrote:
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Dave Martin Dave.Martin@arm.com
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++++ arch/arm/include/asm/kgdb.h | 7 +++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/kgdb_fiq.c | 124 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++++++++++++++ 6 files changed, 239 insertions(+) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
[...]
+static long kgdb_fiq_setup_stack(void *info) +{
- struct pt_regs regs;
- regs.ARM_sp = __get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER) +
THREAD_START_SP;
- WARN_ON(!regs.ARM_sp);
Isn't this rather fatal if you can't allocate any stack? Why not using BUG_ON(), or better yet propagate a proper error code back?
- set_fiq_regs(®s);
- return 0;
+}
+/**
- kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB
- @on: Flag to either enable or disable an NMI
- This function manages NMIs that usually cause KGDB to enter. That is, not
- all NMIs should be enabled or disabled, but only those that issue
- kgdb_handle_exception().
- The call counts disable requests, and thus allows to nest disables. But
- trying to enable already enabled NMI is an error.
- */
+static void kgdb_fiq_enable_nmi(bool on) +{
- static atomic_t cnt;
- int ret;
- ret = atomic_add_return(on ? 1 : -1, &cnt);
- if (ret > 1 && on) {
/*
* There should be only one instance that calls this function
* in "enable, disable" order. All other users must call
* disable first, then enable. If not, something is wrong.
*/
WARN_ON(1);
return;
- }
Minor style suggestion:
/* * There should be only one instance that calls this function * in "enable, disable" order. All other users must call * disable first, then enable. If not, something is wrong. */ if (WARN_ON(ret > 1 && on)) return;
Other than that...
Acked-by: Nicolas Pitre nico@linaro.org
Nicolas
On 24/06/14 17:22, Nicolas Pitre wrote:
On Tue, 24 Jun 2014, Daniel Thompson wrote:
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Dave Martin Dave.Martin@arm.com
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++++ arch/arm/include/asm/kgdb.h | 7 +++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/kgdb_fiq.c | 124 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 87 +++++++++++++++++++++++++++ 6 files changed, 239 insertions(+) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
[...]
+static long kgdb_fiq_setup_stack(void *info) +{
- struct pt_regs regs;
- regs.ARM_sp = __get_free_pages(GFP_KERNEL, THREAD_SIZE_ORDER) +
THREAD_START_SP;
- WARN_ON(!regs.ARM_sp);
Isn't this rather fatal if you can't allocate any stack? Why not using BUG_ON(), or better yet propagate a proper error code back?
Thanks for raising this.
I think we can get rid of the allocation altogether. This stack is *way* oversized (it only needs to be 12 bytes).
- set_fiq_regs(®s);
- return 0;
+}
+/**
- kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB
- @on: Flag to either enable or disable an NMI
- This function manages NMIs that usually cause KGDB to enter. That is, not
- all NMIs should be enabled or disabled, but only those that issue
- kgdb_handle_exception().
- The call counts disable requests, and thus allows to nest disables. But
- trying to enable already enabled NMI is an error.
- */
+static void kgdb_fiq_enable_nmi(bool on) +{
- static atomic_t cnt;
- int ret;
- ret = atomic_add_return(on ? 1 : -1, &cnt);
- if (ret > 1 && on) {
/*
* There should be only one instance that calls this function
* in "enable, disable" order. All other users must call
* disable first, then enable. If not, something is wrong.
*/
WARN_ON(1);
return;
- }
Minor style suggestion:
/* * There should be only one instance that calls this function * in "enable, disable" order. All other users must call * disable first, then enable. If not, something is wrong. */ if (WARN_ON(ret > 1 && on)) return;
Will adopt this style.
Other than that...
Acked-by: Nicolas Pitre nico@linaro.org
Thanks for review.
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches have been previously circulated as part of a large patchset mixing together ARM architecture code and driver changes (http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901 ). This patchset is dramatically cut down to include only the arch/arm code. The driver code (irqchip and tty/serial) will follow when/if the arch code is accepted.
The first two patches modify the FIQ infrastructure to allow interrupt controller drivers to register callbacks (the fiq_chip structure) to manage FIQ routings and to signal FIQ EOI. This makes it possible to use FIQ in multi-platform kernels and with recent ARM interrupt controllers.
The remaining two patches provide architecture support for KGDB's NMI feature (and rely upon the preceding changes to the FIQ code).
Tested on qemu (versatile), STiH416 (B2020 board) and Freescale i.MX6 quad (wandboard).
Changes since v6:
- Corrected off-by-one comparison in has_fiq() (Nicolas Pitre) - Rewrote the FIQ stack initialization (Nicolas Pitre). This fixes a serious data corruption bug due to bad stack mismanagement. - Introduced __fiq_abt to ensure lr_abt and spsr_abt are saved and restored if we fast-interrupt an abort (Russell King). - Significantly improved the commenting of the exception handlers. - Added a call to trace_hardirqs_on() if we clear the I bit as we exit the exception handler.
Changes since v5:
- Separated anything not strictly impacting arch/arm. - Fixed a spurious add/remove of code within the series (there was broken code in intermediate patches)
For previous changes see: http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901
Anton Vorontsov (2): ARM: Move some macros from entry-armv to entry-header ARM: Add KGDB/KDB FIQ debugger generic code
Daniel Thompson (2): arm: fiq: Add callbacks to manage FIQ routings arm: fiq: Allow EOI to be communicated to the intc
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 +++++ arch/arm/include/asm/fiq.h | 14 ++++ arch/arm/include/asm/kgdb.h | 7 ++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/entry-armv.S | 151 +---------------------------------- arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/fiq.c | 112 +++++++++++++++++++++++++- arch/arm/kernel/kgdb_fiq.c | 127 ++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 130 +++++++++++++++++++++++++++++++ 10 files changed, 574 insertions(+), 152 deletions(-) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
-- 1.9.3
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 8 ++++ arch/arm/kernel/fiq.c | 103 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..ed44528 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -16,8 +16,14 @@ #ifndef __ASM_FIQ_H #define __ASM_FIQ_H
+#include <linux/irq.h> #include <asm/ptrace.h>
+struct fiq_chip { + void (*fiq_enable)(struct irq_data *data); + void (*fiq_disable)(struct irq_data *data); +}; + struct fiq_handler { struct fiq_handler *next; /* Name @@ -38,6 +44,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern bool has_fiq(int fiq); +extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..5d831cf 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,9 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h> +#include <linux/radix-tree.h> +#include <linux/slab.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -52,7 +55,15 @@ (unsigned)&vector_fiq_offset; \ })
+struct fiq_data { + struct fiq_chip *fiq_chip; + struct irq_data *irq_data; +}; + static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_data_tree, GFP_KERNEL); +static DEFINE_MUTEX(fiq_data_mutex);
/* Default reacquire function * - we always relinquish FIQ control @@ -127,18 +138,65 @@ void release_fiq(struct fiq_handler *f) while (current_fiq->fiq_op(current_fiq->dev_id, 0)); }
-static int fiq_start; +static struct fiq_data *lookup_fiq_data(int fiq) +{ + struct fiq_data *data; + + rcu_read_lock(); + data = radix_tree_lookup(&fiq_data_tree, fiq); + rcu_read_unlock(); + + return data; +}
void enable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_enable) + data->fiq_chip->fiq_enable(data->irq_data); + enable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + enable_irq(fiq + fiq_start); }
void disable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_disable) + data->fiq_chip->fiq_disable(data->irq_data); + disable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + disable_irq(fiq + fiq_start); }
+bool has_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) + return true; + + if (fiq_start == -1) + return false; + + return fiq >= fiq_start; +} +EXPORT_SYMBOL(has_fiq); + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -147,9 +205,50 @@ EXPORT_SYMBOL(release_fiq); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq);
+/* + * Add a mapping from a Linux irq to the fiq data. + */ +void fiq_register_mapping(int irq, struct fiq_chip *chip) +{ + struct fiq_data *fiq_data = NULL; + int res; + + /* fiq_register_mapping can't be mixed with init_FIQ */ + BUG_ON(fiq_start != -1); + + fiq_data = kmalloc(sizeof(*fiq_data), GFP_KERNEL); + if (!fiq_data) + goto err; + + fiq_data->fiq_chip = chip; + fiq_data->irq_data = irq_get_irq_data(irq); + BUG_ON(!fiq_data->irq_data); + + mutex_lock(&fiq_data_mutex); + res = radix_tree_insert(&fiq_data_tree, irq, fiq_data); + mutex_unlock(&fiq_data_mutex); + if (res) + goto err; + + return; + +err: + kfree(fiq_data); + pr_err("fiq: Cannot register mapping %d\n", irq); +} + +/* + * Set the offset between normal IRQs and their FIQ shadows. + */ void __init init_FIQ(int start) { + fiq_start = start; +} + +static int __init init_default_fiq_handler(void) +{ unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); - fiq_start = start; + return 0; } +pure_initcall(init_default_fiq_handler);
Modern ARM systems require an EOI to be sent to the interrupt controller on completion of both IRQ and FIQ. The FIQ code currently does not provide any API to perform this. This patch provides this API, implemented by adding a callback to the fiq_chip structure.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 6 ++++++ arch/arm/kernel/fiq.c | 9 +++++++++ 2 files changed, 15 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index ed44528..fbc0df1 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -22,6 +22,11 @@ struct fiq_chip { void (*fiq_enable)(struct irq_data *data); void (*fiq_disable)(struct irq_data *data); + + /* .fiq_eoi() will be called from the FIQ handler. For this + * reason it must not use spin locks (or any other locks). + */ + void (*fiq_eoi)(struct irq_data *data); };
struct fiq_handler { @@ -44,6 +49,7 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 5d831cf..c35fd6a 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -183,6 +183,15 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); }
+void eoi_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_eoi) + data->fiq_chip->fiq_eoi(data->irq_data); +} +EXPORT_SYMBOL(eoi_fiq); + bool has_fiq(int fiq) { struct fiq_data *data = lookup_fiq_data(fiq);
From: Anton Vorontsov anton.vorontsov@linaro.org
Just move the macros into header file as we would want to use them for KGDB FIQ entry code.
The following macros were moved:
- svc_entry - usr_entry - kuser_cmpxchg_check - vector_stub
To make kuser_cmpxchg_check actually work across different files, we also have to make kuser_cmpxchg64_fixup global.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Catalin Marinas catalin.marinas@arm.com Cc: Frederic Weisbecker fweisbec@gmail.com --- arch/arm/kernel/entry-armv.S | 151 +------------------------------------ arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 150 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 52a949a..4172cd6 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -140,53 +140,6 @@ ENDPROC(__und_invalid) * SVC mode handlers */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) -#define SPFIX(code...) code -#else -#define SPFIX(code...) -#endif - - .macro svc_entry, stack_hole=0 - UNWIND(.fnstart ) - UNWIND(.save {r0 - pc} ) - sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) -#ifdef CONFIG_THUMB2_KERNEL - SPFIX( str r0, [sp] ) @ temporarily saved - SPFIX( mov r0, sp ) - SPFIX( tst r0, #4 ) @ test original stack alignment - SPFIX( ldr r0, [sp] ) @ restored -#else - SPFIX( tst sp, #4 ) -#endif - SPFIX( subeq sp, sp, #4 ) - stmia sp, {r1 - r12} - - ldmia r0, {r3 - r5} - add r7, sp, #S_SP - 4 @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) - SPFIX( addeq r2, r2, #4 ) - str r3, [sp, #-4]! @ save the "real" r0 copied - @ from the exception stack - - mov r3, lr - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r2 - sp_svc - @ r3 - lr_svc - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - stmia r7, {r2 - r6} - -#ifdef CONFIG_TRACE_IRQFLAGS - bl trace_hardirqs_off -#endif - .endm - .align 5 __dabt_svc: svc_entry @@ -306,73 +259,8 @@ ENDPROC(__pabt_svc)
/* * User mode handlers - * - * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0, .LCcralign - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - .align 5 __dabt_usr: usr_entry @@ -823,6 +711,7 @@ __kuser_cmpxchg64: @ 0xffff0f60 ldmfd sp!, {r4, r5, r6, pc}
.text + .global kuser_cmpxchg64_fixup kuser_cmpxchg64_fixup: @ Called from kuser_cmpxchg_fixup. @ r4 = address of interrupted insn (must be preserved). @@ -964,44 +853,6 @@ __kuser_helper_end: * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. */ - .macro vector_stub, name, mode, correction=0 - .align 5 - -vector_\name: - .if \correction - sub lr, lr, #\correction - .endif - - @ - @ Save r0, lr_<exception> (parent PC) and spsr_<exception> - @ (parent CPSR) - @ - stmia sp, {r0, lr} @ save r0, lr - mrs lr, spsr - str lr, [sp, #8] @ save spsr - - @ - @ Prepare for SVC32 mode. IRQs remain disabled. - @ - mrs r0, cpsr - eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) - msr spsr_cxsf, r0 - - @ - @ the branch table must immediately follow this code - @ - and lr, lr, #0x0f - THUMB( adr r0, 1f ) - THUMB( ldr lr, [r0, lr, lsl #2] ) - mov r0, sp - ARM( ldr lr, [pc, lr, lsl #2] ) - movs pc, lr @ branch to handler in SVC mode -ENDPROC(vector_\name) - - .align 2 - @ handler addresses follow this label -1: - .endm
.section .stubs, "ax", %progbits __stubs_start: diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 5d702f8..eb2c426 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -356,3 +356,167 @@ scno .req r7 @ syscall number tbl .req r8 @ syscall table pointer why .req r8 @ Linux syscall (!= 0) tsk .req r9 @ current thread_info + +/* + * SVC mode handler macros + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) +#define SPFIX(code...) code +#else +#define SPFIX(code...) +#endif + + .macro svc_entry, stack_hole=0 + UNWIND(.fnstart ) + UNWIND(.save {r0 - pc} ) + sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) +#ifdef CONFIG_THUMB2_KERNEL + SPFIX( str r0, [sp] ) @ temporarily saved + SPFIX( mov r0, sp ) + SPFIX( tst r0, #4 ) @ test original stack alignment + SPFIX( ldr r0, [sp] ) @ restored +#else + SPFIX( tst sp, #4 ) +#endif + SPFIX( subeq sp, sp, #4 ) + stmia sp, {r1 - r12} + + ldmia r0, {r3 - r5} + add r7, sp, #S_SP - 4 @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) + SPFIX( addeq r2, r2, #4 ) + str r3, [sp, #-4]! @ save the "real" r0 copied + @ from the exception stack + + mov r3, lr + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r2 - sp_svc + @ r3 - lr_svc + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + stmia r7, {r2 - r6} + +#ifdef CONFIG_TRACE_IRQFLAGS + bl trace_hardirqs_off +#endif + .endm + +/* + * User mode handler macros + * + * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0, .LCcralign + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +/* + * Vector stubs macro. + */ + .macro vector_stub, name, mode, correction=0 + .align 5 + +vector_\name: + .if \correction + sub lr, lr, #\correction + .endif + + @ + @ Save r0, lr_<exception> (parent PC) and spsr_<exception> + @ (parent CPSR) + @ + stmia sp, {r0, lr} @ save r0, lr + mrs lr, spsr + str lr, [sp, #8] @ save spsr + + @ + @ Prepare for SVC32 mode. IRQs remain disabled. + @ + mrs r0, cpsr + eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) + msr spsr_cxsf, r0 + + @ + @ the branch table must immediately follow this code + @ + and lr, lr, #0x0f + THUMB( adr r0, 1f ) + THUMB( ldr lr, [r0, lr, lsl #2] ) + mov r0, sp + ARM( ldr lr, [pc, lr, lsl #2] ) + movs pc, lr @ branch to handler in SVC mode +ENDPROC(vector_\name) + + .align 2 + @ handler addresses follow this label +1: + .endm + +
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org [daniel.thompson@linaro.org: Added __fiq_abt, rewrote stack init] Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Dave Martin Dave.Martin@arm.com --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++++ arch/arm/include/asm/kgdb.h | 7 +++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/kgdb_fiq.c | 127 ++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 130 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 285 insertions(+) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 245058b..f385b27 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -297,6 +297,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -346,6 +347,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 26536f7..c7342b6 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,24 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be + enabled with kgdb_fiq.enable=1 kernel command line option. + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..5de21f01 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,11 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern char kgdb_fiq_handler; +extern char kgdb_fiq_handler_end; +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs); +extern int kgdb_register_fiq(unsigned int fiq); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 38ddd9f..30ee8f3 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -68,6 +68,7 @@ endif obj-$(CONFIG_OABI_COMPAT) += sys_oabi-compat.o obj-$(CONFIG_ARM_THUMBEE) += thumbee.o obj-$(CONFIG_KGDB) += kgdb.o +obj-$(CONFIG_KGDB_FIQ) += kgdb_fiq_entry.o kgdb_fiq.o obj-$(CONFIG_ARM_UNWIND) += unwind.o obj-$(CONFIG_HAVE_TCM) += tcm.o obj-$(CONFIG_OF) += devtree.o diff --git a/arch/arm/kernel/kgdb_fiq.c b/arch/arm/kernel/kgdb_fiq.c new file mode 100644 index 0000000..99d23cc --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq.c @@ -0,0 +1,127 @@ +/* + * KGDB FIQ + * + * Copyright 2010 Google, Inc. + * Arve Hjønnevåg arve@android.com + * Colin Cross ccross@android.com + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/hardirq.h> +#include <linux/atomic.h> +#include <linux/kdb.h> +#include <linux/kgdb.h> +#include <asm/fiq.h> +#include <asm/exception.h> + +static int kgdb_fiq_enabled; +module_param_named(enable, kgdb_fiq_enabled, int, 0600); +MODULE_PARM_DESC(enable, "set to 1 to enable FIQ KGDB"); + +static unsigned int kgdb_fiq; + +struct fiq_stack { + u32 fiq[3]; +} ____cacheline_aligned; + +static struct fiq_stack stacks[NR_CPUS]; + +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs) +{ + if (kgdb_nmi_poll_knock()) { + nmi_enter(); + kgdb_handle_exception(1, 0, 0, regs); + nmi_exit(); + } + + eoi_fiq(kgdb_fiq); +} + +static struct fiq_handler kgdb_fiq_desc = { + .name = "kgdb", +}; + +static long kgdb_fiq_setup_stack(void *info) +{ + struct pt_regs regs; + + regs.ARM_sp = (unsigned long) stacks[smp_processor_id()].fiq; + set_fiq_regs(®s); + + return 0; +} + +/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * This function manages NMIs that usually cause KGDB to enter. That is, not + * all NMIs should be enabled or disabled, but only those that issue + * kgdb_handle_exception(). + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + static atomic_t cnt; + int ret; + + ret = atomic_add_return(on ? 1 : -1, &cnt); + + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + if (WARN_ON(ret > 1 && on)) + return; + + if (ret > 0) + enable_fiq(kgdb_fiq); + else + disable_fiq(kgdb_fiq); +} + +int kgdb_register_fiq(unsigned int fiq) +{ + int err; + int cpu; + + if (!kgdb_fiq_enabled) + return -ENODEV; + + if (!has_fiq(fiq)) { + pr_warn( + "%s: Cannot register %u (no FIQ with this number)\n", + __func__, fiq); + return -ENODEV; + } + + kgdb_fiq = fiq; + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + for_each_possible_cpu(cpu) + work_on_cpu(cpu, kgdb_fiq_setup_stack, NULL); + + set_fiq_handler(&kgdb_fiq_handler, + &kgdb_fiq_handler_end - &kgdb_fiq_handler); + + arch_kgdb_ops.enable_nmi = kgdb_fiq_enable_nmi; + return 0; +} diff --git a/arch/arm/kernel/kgdb_fiq_entry.S b/arch/arm/kernel/kgdb_fiq_entry.S new file mode 100644 index 0000000..50499c0 --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq_entry.S @@ -0,0 +1,130 @@ +/* + * KGDB FIQ entry + * + * Copyright 1996,1997,1998 Russell King. + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/memory.h> +#include <asm/unwind.h> +#include "entry-header.S" + + .text + +@ This is needed for usr_entry/alignment_trap +.LCcralign: + .long cr_alignment +.LCdohandle: + .long kgdb_fiq_do_handle + + .macro fiq_handler + ldr r1, =.LCdohandle + mov r0, sp + adr lr, BSYM(9997f) + ldr pc, [r1] +9997: + .endm + +/* + * svc_exit_via_fiq + * + * This macro acts in a similar manner to svc_exit but switches to FIQ + * mode to restore the final part of the register state. + * + * We cannot use the normal svc_exit procedure because that would + * clobber spsr_svc (FIQ could be delivered during the first few instructions + * of vector_swi meaning its contents have not been saved anywhere). + */ + .macro svc_exit_via_fiq, rpsr + +#ifdef CONFIG_TRACE_IRQFLAGS + tst \rpsr, #PSR_I_BIT + bleq trace_hardirqs_on +#endif + + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + .endm + + .align 5 +__fiq_svc: + svc_entry + fiq_handler + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + .ltorg + + .align 5 +__fiq_abt: + svc_entry + + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov r0, lr @ Save lr_abt + mrs r1, spsr @ Save spsr_abt, abort is now safe + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + push {r0 - r1} + + fiq_handler + + pop {r0 - r1} + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov lr, r0 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r1 @ Restore spsr_abt + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + .ltorg + + .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + .ltorg + + .global kgdb_fiq_handler +kgdb_fiq_handler: + + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f + + .global kgdb_fiq_handler_end +kgdb_fiq_handler_end:
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches have been previously circulated as part of a large patchset mixing together ARM architecture code and driver changes (http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901 ). This patchset is dramatically cut down to include only the arch/arm code. The driver code (irqchip and tty/serial) will follow when/if the arch code is accepted.
The first two patches modify the FIQ infrastructure to allow interrupt controller drivers to register callbacks (the fiq_chip structure) to manage FIQ routings and to ACK and EOI the FIQ. This makes it possible to use FIQ in multi-platform kernels and with recent ARM interrupt controllers.
The remaining two patches provide architecture support for KGDB's NMI feature (and rely upon the preceding changes to the FIQ code).
Tested on qemu (versatile), STiH416 (B2020 board) and Freescale i.MX6 quad (wandboard).
Changes since v7:
- Introduced ack_fiq() to complement eoi_fiq(). Without this it is not possible to meet the GIC specification (previous versions worked when tested but are unpredictable according to the specification). ack_fiq() also makes is possible for drivers for devices with multiple interrupt lines to discover the interrupt source correctly.
Changes since v6:
- Corrected off-by-one comparison in has_fiq() (Nicolas Pitre) - Rewrote the FIQ stack initialization (Nicolas Pitre). This fixes a serious data corruption bug due to bad stack mismanagement. - Introduced __fiq_abt to ensure lr_abt and spsr_abt are saved and restored if we fast-interrupt an abort (Russell King). - Significantly improved the commenting of the exception handlers. - Added a call to trace_hardirqs_on() if we clear the I bit as we exit the exception handler.
Changes since v5:
- Separated anything not strictly impacting arch/arm. - Fixed a spurious add/remove of code within the series (there was broken code in intermediate patches)
For previous changes see: http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901
Anton Vorontsov (2): ARM: Move some macros from entry-armv to entry-header ARM: Add KGDB/KDB FIQ debugger generic code
Daniel Thompson (2): arm: fiq: Add callbacks to manage FIQ routings arm: fiq: Allow ACK and EOI to be passed to the intc
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 +++++ arch/arm/include/asm/fiq.h | 17 ++++ arch/arm/include/asm/kgdb.h | 7 ++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/entry-armv.S | 151 +---------------------------------- arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/fiq.c | 122 ++++++++++++++++++++++++++++- arch/arm/kernel/kgdb_fiq.c | 132 +++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 130 +++++++++++++++++++++++++++++++ 10 files changed, 592 insertions(+), 152 deletions(-) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
-- 1.9.3
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 8 ++++ arch/arm/kernel/fiq.c | 103 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..ed44528 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -16,8 +16,14 @@ #ifndef __ASM_FIQ_H #define __ASM_FIQ_H
+#include <linux/irq.h> #include <asm/ptrace.h>
+struct fiq_chip { + void (*fiq_enable)(struct irq_data *data); + void (*fiq_disable)(struct irq_data *data); +}; + struct fiq_handler { struct fiq_handler *next; /* Name @@ -38,6 +44,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern bool has_fiq(int fiq); +extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..5d831cf 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,9 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h> +#include <linux/radix-tree.h> +#include <linux/slab.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -52,7 +55,15 @@ (unsigned)&vector_fiq_offset; \ })
+struct fiq_data { + struct fiq_chip *fiq_chip; + struct irq_data *irq_data; +}; + static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_data_tree, GFP_KERNEL); +static DEFINE_MUTEX(fiq_data_mutex);
/* Default reacquire function * - we always relinquish FIQ control @@ -127,18 +138,65 @@ void release_fiq(struct fiq_handler *f) while (current_fiq->fiq_op(current_fiq->dev_id, 0)); }
-static int fiq_start; +static struct fiq_data *lookup_fiq_data(int fiq) +{ + struct fiq_data *data; + + rcu_read_lock(); + data = radix_tree_lookup(&fiq_data_tree, fiq); + rcu_read_unlock(); + + return data; +}
void enable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_enable) + data->fiq_chip->fiq_enable(data->irq_data); + enable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + enable_irq(fiq + fiq_start); }
void disable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_disable) + data->fiq_chip->fiq_disable(data->irq_data); + disable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + disable_irq(fiq + fiq_start); }
+bool has_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) + return true; + + if (fiq_start == -1) + return false; + + return fiq >= fiq_start; +} +EXPORT_SYMBOL(has_fiq); + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -147,9 +205,50 @@ EXPORT_SYMBOL(release_fiq); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq);
+/* + * Add a mapping from a Linux irq to the fiq data. + */ +void fiq_register_mapping(int irq, struct fiq_chip *chip) +{ + struct fiq_data *fiq_data = NULL; + int res; + + /* fiq_register_mapping can't be mixed with init_FIQ */ + BUG_ON(fiq_start != -1); + + fiq_data = kmalloc(sizeof(*fiq_data), GFP_KERNEL); + if (!fiq_data) + goto err; + + fiq_data->fiq_chip = chip; + fiq_data->irq_data = irq_get_irq_data(irq); + BUG_ON(!fiq_data->irq_data); + + mutex_lock(&fiq_data_mutex); + res = radix_tree_insert(&fiq_data_tree, irq, fiq_data); + mutex_unlock(&fiq_data_mutex); + if (res) + goto err; + + return; + +err: + kfree(fiq_data); + pr_err("fiq: Cannot register mapping %d\n", irq); +} + +/* + * Set the offset between normal IRQs and their FIQ shadows. + */ void __init init_FIQ(int start) { + fiq_start = start; +} + +static int __init init_default_fiq_handler(void) +{ unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); - fiq_start = start; + return 0; } +pure_initcall(init_default_fiq_handler);
Modern ARM interrupt controllers require an ACK as interrupts are taken and an EOI on completion. The FIQ code currently does not provide any API to perform this.
This patch provides this API, implemented by adding two callbacks to the fiq_chip structure.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 9 +++++++++ arch/arm/kernel/fiq.c | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index ed44528..a25c952 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -22,6 +22,13 @@ struct fiq_chip { void (*fiq_enable)(struct irq_data *data); void (*fiq_disable)(struct irq_data *data); + + /* .fiq_ack() and .fiq_eoi() will be called from the FIQ + * handler. For this reason they must not use spin locks (or any + * other locks). + */ + int (*fiq_ack)(struct irq_data *data); + void (*fiq_eoi)(struct irq_data *data); };
struct fiq_handler { @@ -44,6 +51,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern int ack_fiq(int fiq); +extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 5d831cf..3ccaa8c 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -183,6 +183,25 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); }
+int ack_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_ack) + return data->fiq_chip->fiq_ack(data->irq_data); + + return fiq; +} + +void eoi_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_eoi) + data->fiq_chip->fiq_eoi(data->irq_data); +} +EXPORT_SYMBOL(eoi_fiq); + bool has_fiq(int fiq) { struct fiq_data *data = lookup_fiq_data(fiq);
From: Anton Vorontsov anton.vorontsov@linaro.org
Just move the macros into header file as we would want to use them for KGDB FIQ entry code.
The following macros were moved:
- svc_entry - usr_entry - kuser_cmpxchg_check - vector_stub
To make kuser_cmpxchg_check actually work across different files, we also have to make kuser_cmpxchg64_fixup global.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Catalin Marinas catalin.marinas@arm.com Cc: Frederic Weisbecker fweisbec@gmail.com --- arch/arm/kernel/entry-armv.S | 151 +------------------------------------ arch/arm/kernel/entry-header.S | 164 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 150 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 52a949a..4172cd6 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -140,53 +140,6 @@ ENDPROC(__und_invalid) * SVC mode handlers */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) -#define SPFIX(code...) code -#else -#define SPFIX(code...) -#endif - - .macro svc_entry, stack_hole=0 - UNWIND(.fnstart ) - UNWIND(.save {r0 - pc} ) - sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) -#ifdef CONFIG_THUMB2_KERNEL - SPFIX( str r0, [sp] ) @ temporarily saved - SPFIX( mov r0, sp ) - SPFIX( tst r0, #4 ) @ test original stack alignment - SPFIX( ldr r0, [sp] ) @ restored -#else - SPFIX( tst sp, #4 ) -#endif - SPFIX( subeq sp, sp, #4 ) - stmia sp, {r1 - r12} - - ldmia r0, {r3 - r5} - add r7, sp, #S_SP - 4 @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) - SPFIX( addeq r2, r2, #4 ) - str r3, [sp, #-4]! @ save the "real" r0 copied - @ from the exception stack - - mov r3, lr - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r2 - sp_svc - @ r3 - lr_svc - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - stmia r7, {r2 - r6} - -#ifdef CONFIG_TRACE_IRQFLAGS - bl trace_hardirqs_off -#endif - .endm - .align 5 __dabt_svc: svc_entry @@ -306,73 +259,8 @@ ENDPROC(__pabt_svc)
/* * User mode handlers - * - * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE */
-#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) -#error "sizeof(struct pt_regs) must be a multiple of 8" -#endif - - .macro usr_entry - UNWIND(.fnstart ) - UNWIND(.cantunwind ) @ don't unwind the user space - sub sp, sp, #S_FRAME_SIZE - ARM( stmib sp, {r1 - r12} ) - THUMB( stmia sp, {r0 - r12} ) - - ldmia r0, {r3 - r5} - add r0, sp, #S_PC @ here for interlock avoidance - mov r6, #-1 @ "" "" "" "" - - str r3, [sp] @ save the "real" r0 copied - @ from the exception stack - - @ - @ We are now ready to fill in the remaining blanks on the stack: - @ - @ r4 - lr_<exception>, already fixed up for correct return/restart - @ r5 - spsr_<exception> - @ r6 - orig_r0 (see pt_regs definition in ptrace.h) - @ - @ Also, separately save sp_usr and lr_usr - @ - stmia r0, {r4 - r6} - ARM( stmdb r0, {sp, lr}^ ) - THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) - - @ - @ Enable the alignment trap while in kernel mode - @ - alignment_trap r0, .LCcralign - - @ - @ Clear FP to mark the first stack frame - @ - zero_fp - -#ifdef CONFIG_IRQSOFF_TRACER - bl trace_hardirqs_off -#endif - ct_user_exit save = 0 - .endm - - .macro kuser_cmpxchg_check -#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ - !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) -#ifndef CONFIG_MMU -#warning "NPTL on non MMU needs fixing" -#else - @ Make sure our user space atomic helper is restarted - @ if it was interrupted in a critical region. Here we - @ perform a quick test inline since it should be false - @ 99.9999% of the time. The rest is done out of line. - cmp r4, #TASK_SIZE - blhs kuser_cmpxchg64_fixup -#endif -#endif - .endm - .align 5 __dabt_usr: usr_entry @@ -823,6 +711,7 @@ __kuser_cmpxchg64: @ 0xffff0f60 ldmfd sp!, {r4, r5, r6, pc}
.text + .global kuser_cmpxchg64_fixup kuser_cmpxchg64_fixup: @ Called from kuser_cmpxchg_fixup. @ r4 = address of interrupted insn (must be preserved). @@ -964,44 +853,6 @@ __kuser_helper_end: * SP points to a minimal amount of processor-private memory, the address * of which is copied into r0 for the mode specific abort handler. */ - .macro vector_stub, name, mode, correction=0 - .align 5 - -vector_\name: - .if \correction - sub lr, lr, #\correction - .endif - - @ - @ Save r0, lr_<exception> (parent PC) and spsr_<exception> - @ (parent CPSR) - @ - stmia sp, {r0, lr} @ save r0, lr - mrs lr, spsr - str lr, [sp, #8] @ save spsr - - @ - @ Prepare for SVC32 mode. IRQs remain disabled. - @ - mrs r0, cpsr - eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) - msr spsr_cxsf, r0 - - @ - @ the branch table must immediately follow this code - @ - and lr, lr, #0x0f - THUMB( adr r0, 1f ) - THUMB( ldr lr, [r0, lr, lsl #2] ) - mov r0, sp - ARM( ldr lr, [pc, lr, lsl #2] ) - movs pc, lr @ branch to handler in SVC mode -ENDPROC(vector_\name) - - .align 2 - @ handler addresses follow this label -1: - .endm
.section .stubs, "ax", %progbits __stubs_start: diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 5d702f8..eb2c426 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -356,3 +356,167 @@ scno .req r7 @ syscall number tbl .req r8 @ syscall table pointer why .req r8 @ Linux syscall (!= 0) tsk .req r9 @ current thread_info + +/* + * SVC mode handler macros + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) +#define SPFIX(code...) code +#else +#define SPFIX(code...) +#endif + + .macro svc_entry, stack_hole=0 + UNWIND(.fnstart ) + UNWIND(.save {r0 - pc} ) + sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) +#ifdef CONFIG_THUMB2_KERNEL + SPFIX( str r0, [sp] ) @ temporarily saved + SPFIX( mov r0, sp ) + SPFIX( tst r0, #4 ) @ test original stack alignment + SPFIX( ldr r0, [sp] ) @ restored +#else + SPFIX( tst sp, #4 ) +#endif + SPFIX( subeq sp, sp, #4 ) + stmia sp, {r1 - r12} + + ldmia r0, {r3 - r5} + add r7, sp, #S_SP - 4 @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + add r2, sp, #(S_FRAME_SIZE + \stack_hole - 4) + SPFIX( addeq r2, r2, #4 ) + str r3, [sp, #-4]! @ save the "real" r0 copied + @ from the exception stack + + mov r3, lr + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r2 - sp_svc + @ r3 - lr_svc + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + stmia r7, {r2 - r6} + +#ifdef CONFIG_TRACE_IRQFLAGS + bl trace_hardirqs_off +#endif + .endm + +/* + * User mode handler macros + * + * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE + */ + +#if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7) +#error "sizeof(struct pt_regs) must be a multiple of 8" +#endif + + .macro usr_entry + UNWIND(.fnstart ) + UNWIND(.cantunwind ) @ don't unwind the user space + sub sp, sp, #S_FRAME_SIZE + ARM( stmib sp, {r1 - r12} ) + THUMB( stmia sp, {r0 - r12} ) + + ldmia r0, {r3 - r5} + add r0, sp, #S_PC @ here for interlock avoidance + mov r6, #-1 @ "" "" "" "" + + str r3, [sp] @ save the "real" r0 copied + @ from the exception stack + + @ + @ We are now ready to fill in the remaining blanks on the stack: + @ + @ r4 - lr_<exception>, already fixed up for correct return/restart + @ r5 - spsr_<exception> + @ r6 - orig_r0 (see pt_regs definition in ptrace.h) + @ + @ Also, separately save sp_usr and lr_usr + @ + stmia r0, {r4 - r6} + ARM( stmdb r0, {sp, lr}^ ) + THUMB( store_user_sp_lr r0, r1, S_SP - S_PC ) + + @ + @ Enable the alignment trap while in kernel mode + @ + alignment_trap r0, .LCcralign + + @ + @ Clear FP to mark the first stack frame + @ + zero_fp + +#ifdef CONFIG_IRQSOFF_TRACER + bl trace_hardirqs_off +#endif + ct_user_exit save = 0 + .endm + + .macro kuser_cmpxchg_check +#if !defined(CONFIG_CPU_32v6K) && defined(CONFIG_KUSER_HELPERS) && \ + !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG) +#ifndef CONFIG_MMU +#warning "NPTL on non MMU needs fixing" +#else + @ Make sure our user space atomic helper is restarted + @ if it was interrupted in a critical region. Here we + @ perform a quick test inline since it should be false + @ 99.9999% of the time. The rest is done out of line. + cmp r4, #TASK_SIZE + blhs kuser_cmpxchg64_fixup +#endif +#endif + .endm + +/* + * Vector stubs macro. + */ + .macro vector_stub, name, mode, correction=0 + .align 5 + +vector_\name: + .if \correction + sub lr, lr, #\correction + .endif + + @ + @ Save r0, lr_<exception> (parent PC) and spsr_<exception> + @ (parent CPSR) + @ + stmia sp, {r0, lr} @ save r0, lr + mrs lr, spsr + str lr, [sp, #8] @ save spsr + + @ + @ Prepare for SVC32 mode. IRQs remain disabled. + @ + mrs r0, cpsr + eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) + msr spsr_cxsf, r0 + + @ + @ the branch table must immediately follow this code + @ + and lr, lr, #0x0f + THUMB( adr r0, 1f ) + THUMB( ldr lr, [r0, lr, lsl #2] ) + mov r0, sp + ARM( ldr lr, [pc, lr, lsl #2] ) + movs pc, lr @ branch to handler in SVC mode +ENDPROC(vector_\name) + + .align 2 + @ handler addresses follow this label +1: + .endm + +
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
Signed-off-by: Anton Vorontsov anton.vorontsov@linaro.org Signed-off-by: John Stultz john.stultz@linaro.org [daniel.thompson@linaro.org: Added __fiq_abt, rewrote stack init] Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Dave Martin Dave.Martin@arm.com --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 18 ++++++ arch/arm/include/asm/kgdb.h | 7 +++ arch/arm/kernel/Makefile | 1 + arch/arm/kernel/kgdb_fiq.c | 132 +++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/kgdb_fiq_entry.S | 130 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 290 insertions(+) create mode 100644 arch/arm/kernel/kgdb_fiq.c create mode 100644 arch/arm/kernel/kgdb_fiq_entry.S
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 245058b..f385b27 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -297,6 +297,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -346,6 +347,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index 26536f7..c7342b6 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,24 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be + enabled with kgdb_fiq.enable=1 kernel command line option. + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..5de21f01 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,11 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern char kgdb_fiq_handler; +extern char kgdb_fiq_handler_end; +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs); +extern int kgdb_register_fiq(unsigned int fiq); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile index 38ddd9f..30ee8f3 100644 --- a/arch/arm/kernel/Makefile +++ b/arch/arm/kernel/Makefile @@ -68,6 +68,7 @@ endif obj-$(CONFIG_OABI_COMPAT) += sys_oabi-compat.o obj-$(CONFIG_ARM_THUMBEE) += thumbee.o obj-$(CONFIG_KGDB) += kgdb.o +obj-$(CONFIG_KGDB_FIQ) += kgdb_fiq_entry.o kgdb_fiq.o obj-$(CONFIG_ARM_UNWIND) += unwind.o obj-$(CONFIG_HAVE_TCM) += tcm.o obj-$(CONFIG_OF) += devtree.o diff --git a/arch/arm/kernel/kgdb_fiq.c b/arch/arm/kernel/kgdb_fiq.c new file mode 100644 index 0000000..a894dde --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq.c @@ -0,0 +1,132 @@ +/* + * KGDB FIQ + * + * Copyright 2010 Google, Inc. + * Arve Hjønnevåg arve@android.com + * Colin Cross ccross@android.com + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/hardirq.h> +#include <linux/atomic.h> +#include <linux/kdb.h> +#include <linux/kgdb.h> +#include <asm/fiq.h> +#include <asm/exception.h> + +static int kgdb_fiq_enabled; +module_param_named(enable, kgdb_fiq_enabled, int, 0600); +MODULE_PARM_DESC(enable, "set to 1 to enable FIQ KGDB"); + +static unsigned int kgdb_fiq; + +struct fiq_stack { + u32 fiq[3]; +} ____cacheline_aligned; + +static struct fiq_stack stacks[NR_CPUS]; + +asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs) +{ + int actual; + + nmi_enter(); + actual = ack_fiq(kgdb_fiq); + WARN_ON(actual != kgdb_fiq); + + /* there's no harm in doing this regardless of the above WARN_ON() */ + if (kgdb_nmi_poll_knock()) + kgdb_handle_exception(1, 0, 0, regs); + + eoi_fiq(actual); + nmi_exit(); +} + +static struct fiq_handler kgdb_fiq_desc = { + .name = "kgdb", +}; + +static long kgdb_fiq_setup_stack(void *info) +{ + struct pt_regs regs; + + regs.ARM_sp = (unsigned long) stacks[smp_processor_id()].fiq; + set_fiq_regs(®s); + + return 0; +} + +/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * This function manages NMIs that usually cause KGDB to enter. That is, not + * all NMIs should be enabled or disabled, but only those that issue + * kgdb_handle_exception(). + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + static atomic_t cnt; + int ret; + + ret = atomic_add_return(on ? 1 : -1, &cnt); + + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + if (WARN_ON(ret > 1 && on)) + return; + + if (ret > 0) + enable_fiq(kgdb_fiq); + else + disable_fiq(kgdb_fiq); +} + +int kgdb_register_fiq(unsigned int fiq) +{ + int err; + int cpu; + + if (!kgdb_fiq_enabled) + return -ENODEV; + + if (!has_fiq(fiq)) { + pr_warn( + "%s: Cannot register %u (no FIQ with this number)\n", + __func__, fiq); + return -ENODEV; + } + + kgdb_fiq = fiq; + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + for_each_possible_cpu(cpu) + work_on_cpu(cpu, kgdb_fiq_setup_stack, NULL); + + set_fiq_handler(&kgdb_fiq_handler, + &kgdb_fiq_handler_end - &kgdb_fiq_handler); + + arch_kgdb_ops.enable_nmi = kgdb_fiq_enable_nmi; + return 0; +} diff --git a/arch/arm/kernel/kgdb_fiq_entry.S b/arch/arm/kernel/kgdb_fiq_entry.S new file mode 100644 index 0000000..50499c0 --- /dev/null +++ b/arch/arm/kernel/kgdb_fiq_entry.S @@ -0,0 +1,130 @@ +/* + * KGDB FIQ entry + * + * Copyright 1996,1997,1998 Russell King. + * Copyright 2012 Linaro Ltd. + * Anton Vorontsov anton.vorontsov@linaro.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/linkage.h> +#include <asm/assembler.h> +#include <asm/memory.h> +#include <asm/unwind.h> +#include "entry-header.S" + + .text + +@ This is needed for usr_entry/alignment_trap +.LCcralign: + .long cr_alignment +.LCdohandle: + .long kgdb_fiq_do_handle + + .macro fiq_handler + ldr r1, =.LCdohandle + mov r0, sp + adr lr, BSYM(9997f) + ldr pc, [r1] +9997: + .endm + +/* + * svc_exit_via_fiq + * + * This macro acts in a similar manner to svc_exit but switches to FIQ + * mode to restore the final part of the register state. + * + * We cannot use the normal svc_exit procedure because that would + * clobber spsr_svc (FIQ could be delivered during the first few instructions + * of vector_swi meaning its contents have not been saved anywhere). + */ + .macro svc_exit_via_fiq, rpsr + +#ifdef CONFIG_TRACE_IRQFLAGS + tst \rpsr, #PSR_I_BIT + bleq trace_hardirqs_on +#endif + + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + .endm + + .align 5 +__fiq_svc: + svc_entry + fiq_handler + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + .ltorg + + .align 5 +__fiq_abt: + svc_entry + + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov r0, lr @ Save lr_abt + mrs r1, spsr @ Save spsr_abt, abort is now safe + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + push {r0 - r1} + + fiq_handler + + pop {r0 - r1} + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov lr, r0 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r1 @ Restore spsr_abt + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + .ltorg + + .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + .ltorg + + .global kgdb_fiq_handler +kgdb_fiq_handler: + + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f + + .global kgdb_fiq_handler_end +kgdb_fiq_handler_end:
On Thu, Jul 10, 2014 at 09:03:47AM +0100, Daniel Thompson wrote:
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
I know you've been around the loop on this patch set quite a number of times. However, there are two issues. The first is a simple concern, the second is more a design decision...
I've recently been hitting a problem on iMX6Q with a irqs-off deadlock on CPU0 (somehow, it always hit CPU0 every time I tested.) This wasn't particularly good as it prevented much in the way of diagnosis.
Of course, things like the spinlock lockup fired... but nothing could give me a trace from CPU0.
On x86, they have this fixed by using the NMI to trigger a backtrace on all CPUs when a RCU lockup or spinlock lockup occurs. There's a generic hook for this called arch_trigger_all_cpu_backtrace().
So, I set about using the contents of some of your patches to implement this for ARM, and I came out with something which works. In doing this, I started wondering whether the default FIQ handler should not be just "subs pc, lr, #4" but mostly your FIQ assembly code you have below. This, along with your GIC patches to move all IRQs to group 1, then gives us a way to send a FIQ IPI to CPUs in the system - and the FIQ IPI could be caught and used to dump a backtrace.
Here's the changes I did for that, which are a tad hacky:
irq-gic.c - SGI 8 gets used to trigger a backtrace. Note it must be high priority too.
gic_cpu_init() + /* + * Set all PPI and SGI interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers are read-as-zero/write-ignored. + */ + writel_relaxed(0xfffffeff, dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0xa0a0a000, dist_base + GIC_DIST_PRI + 8);
gic_raise_softirq() + softirq = map << 16 | irq; + if (irq != 8) + softirq |= 0x8000; +
arch/arm/kernel/smp.c: +/* For reliability, we're prepared to waste bits here. */ +static DECLARE_BITMAP(backtrace_mask, NR_CPUS) __read_mostly; + ... +static void ipi_cpu_backtrace(struct pt_regs *regs) +{ + int cpu = smp_processor_id(); + + if (cpumask_test_cpu(cpu, to_cpumask(backtrace_mask))) { + static arch_spinlock_t lock = __ARCH_SPIN_LOCK_UNLOCKED; + + arch_spin_lock(&lock); + printk(KERN_WARNING "FIQ backtrace for cpu %d\n", cpu); + show_regs(regs); + arch_spin_unlock(&lock); + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + } +} + ... +void arch_trigger_all_cpu_backtrace(bool include_self) +{ + static unsigned long backtrace_flag; + int i, cpu = get_cpu(); + + if (test_and_set_bit(0, &backtrace_flag)) { + /* + * If there is already a trigger_all_cpu_backtrace() in progress + * (backtrace_flag == 1), don't output double cpu dump infos. + */ + put_cpu(); + return; + } + + cpumask_copy(to_cpumask(backtrace_mask), cpu_online_mask); + if (!include_self) + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + + if (!cpumask_empty(to_cpumask(backtrace_mask))) { + pr_info("Sending FIQ to %s CPUs:\n", + (include_self ? "all" : "other")); + smp_cross_call(to_cpumask(backtrace_mask), IPI_CPU_BACKTRACE); + } + + /* Wait for up to 10 seconds for all CPUs to do the backtrace */ + for (i = 0; i < 10 * 1000; i++) { + if (cpumask_empty(to_cpumask(backtrace_mask))) + break; + + mdelay(1); + } + + clear_bit(0, &backtrace_flag); + smp_mb__after_atomic(); + put_cpu(); +} + +void __fiq_handle(struct pt_regs *regs) +{ + ipi_cpu_backtrace(regs); +}
arch/arm/kernel/setup.c: +static unsigned int fiq_stack[4][1024]; + ... cpu_init() - "msr cpsr_c, %7" + "msr cpsr_c, %7\n\t" + "mov sp, %8\n\t" + "msr cpsr_c, %9" ... + PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "r" (&fiq_stack[cpu][1024]),
The FIQ assembly code is basically the same as yours, but with:
+ .macro fiq_handler + bl __fiq_handle + .endm
and the code in svc_exit_via_fiq testing the PSR I flag and calling trace_hardirqs_on removed.
This does have one deficiency, and that is it doesn't EOI the FIQ interrupt - that's something which should be fixed, but for my purposes to track down where the locked CPU was, it wasn't strictly necessary that the system continued to work after this point.
This brings me to my second concern, and is the reason I decided not to ask for it for this merge window.
Calling the trace_* functions is a no-no from FIQ code. trace_hardirqs_on() can itself take locks, which can result in a deadlock.
I thought I'd made it clear that FIQ code can't take locks because there's no way of knowing what state they're in at the point that the FIQ fires - _irq() variants won't save you - and that's kind of the point of FIQ. It's almost never masked by the kernel.
Now, You'll be forgiven if you now point out that in the code above, I'm taking a spinlock. That's absolutely true. Analyse the code a little closer and you'll notice it's done in a safe way. There's only one time where that spinlock is taken and that's from FIQ code, never from any other code, and only once per CPU - notice how arch_trigger_all_cpu_backtrace() protects itself against multiple callers, and how ipi_cpu_backtrace() is careful to check that its CPU bit is set. This is exactly the same method which x86 code uses (in fact, much of the above code was stolen from x86!)
So, how about moving the FIQ assembly code to entry-armv.S and making it less kgdb specific? (Though... we do want to keep a /very/ close eye on users to ensure that they don't do silly stuff with locking.)
On 13/08/14 22:45, Russell King - ARM Linux wrote:
On Thu, Jul 10, 2014 at 09:03:47AM +0100, Daniel Thompson wrote:
From: Anton Vorontsov anton.vorontsov@linaro.org
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
By default KGDB FIQ is disabled in runtime, but can be enabled with kgdb_fiq.enable=1 kernel command line option.
I know you've been around the loop on this patch set quite a number of times. However, there are two issues. The first is a simple concern, the second is more a design decision...
I've recently been hitting a problem on iMX6Q with a irqs-off deadlock on CPU0 (somehow, it always hit CPU0 every time I tested.) This wasn't particularly good as it prevented much in the way of diagnosis.
Of course, things like the spinlock lockup fired... but nothing could give me a trace from CPU0.
On x86, they have this fixed by using the NMI to trigger a backtrace on all CPUs when a RCU lockup or spinlock lockup occurs. There's a generic hook for this called arch_trigger_all_cpu_backtrace().
So, I set about using the contents of some of your patches to implement this for ARM, and I came out with something which works. In doing this, I started wondering whether the default FIQ handler should not be just "subs pc, lr, #4" but mostly your FIQ assembly code you have below. This, along with your GIC patches to move all IRQs to group 1, then gives us a way to send a FIQ IPI to CPUs in the system - and the FIQ IPI could be caught and used to dump a backtrace.
Here's the changes I did for that, which are a tad hacky:
irq-gic.c - SGI 8 gets used to trigger a backtrace. Note it must be high priority too.
gic_cpu_init()
/*
* Set all PPI and SGI interrupts to be group 1.
*
* If grouping is not available (not implemented or prohibited by
* security mode) these registers are read-as-zero/write-ignored.
*/
writel_relaxed(0xfffffeff, dist_base + GIC_DIST_IGROUP + 0);
writel_relaxed(0xa0a0a000, dist_base + GIC_DIST_PRI + 8);
gic_raise_softirq()
softirq = map << 16 | irq;
if (irq != 8)
softirq |= 0x8000;
Let me dig out some of my patches in this area.
I've added an IPI to be used to quiesce the CPUs for KGDB. My code is in essence the same as yours but uses a bitmask for the IPIs that should be delivered using FIQ (in fact I wrote that deliberately to leave space to easily implement arch_trigger_all_cpu_backtrace() )
arch/arm/kernel/smp.c: +/* For reliability, we're prepared to waste bits here. */ +static DECLARE_BITMAP(backtrace_mask, NR_CPUS) __read_mostly;
... +static void ipi_cpu_backtrace(struct pt_regs *regs) +{
int cpu = smp_processor_id();
if (cpumask_test_cpu(cpu, to_cpumask(backtrace_mask))) {
static arch_spinlock_t lock = __ARCH_SPIN_LOCK_UNLOCKED;
arch_spin_lock(&lock);
printk(KERN_WARNING "FIQ backtrace for cpu %d\n", cpu);
show_regs(regs);
arch_spin_unlock(&lock);
cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask));
}
+}
... +void arch_trigger_all_cpu_backtrace(bool include_self) +{
static unsigned long backtrace_flag;
int i, cpu = get_cpu();
if (test_and_set_bit(0, &backtrace_flag)) {
/*
* If there is already a trigger_all_cpu_backtrace() in progress
* (backtrace_flag == 1), don't output double cpu dump infos.
*/
put_cpu();
return;
}
cpumask_copy(to_cpumask(backtrace_mask), cpu_online_mask);
if (!include_self)
cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask));
if (!cpumask_empty(to_cpumask(backtrace_mask))) {
pr_info("Sending FIQ to %s CPUs:\n",
(include_self ? "all" : "other"));
smp_cross_call(to_cpumask(backtrace_mask), IPI_CPU_BACKTRACE);
}
/* Wait for up to 10 seconds for all CPUs to do the backtrace */
for (i = 0; i < 10 * 1000; i++) {
if (cpumask_empty(to_cpumask(backtrace_mask)))
break;
mdelay(1);
}
clear_bit(0, &backtrace_flag);
smp_mb__after_atomic();
put_cpu();
+}
+void __fiq_handle(struct pt_regs *regs) +{
ipi_cpu_backtrace(regs);
+}
arch/arm/kernel/setup.c: +static unsigned int fiq_stack[4][1024];
... cpu_init()
"msr cpsr_c, %7"
"msr cpsr_c, %7\n\t"
"mov sp, %8\n\t"
"msr cpsr_c, %9"
...
PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE),
"r" (&fiq_stack[cpu][1024]),
The FIQ assembly code is basically the same as yours, but with:
.macro fiq_handler
bl __fiq_handle
.endm
and the code in svc_exit_via_fiq testing the PSR I flag and calling trace_hardirqs_on removed.
This does have one deficiency, and that is it doesn't EOI the FIQ interrupt - that's something which should be fixed, but for my purposes to track down where the locked CPU was, it wasn't strictly necessary that the system continued to work after this point.
EOIing IPIs should be pretty easy (it is safe to EOI them immediately after the ack). This is also included in my existing IPI patches.
This brings me to my second concern, and is the reason I decided not to ask for it for this merge window.
Calling the trace_* functions is a no-no from FIQ code. trace_hardirqs_on() can itself take locks, which can result in a deadlock.
I thought I'd made it clear that FIQ code can't take locks because there's no way of knowing what state they're in at the point that the FIQ fires - _irq() variants won't save you - and that's kind of the point of FIQ. It's almost never masked by the kernel.
Don't worry, you certainly did make it clear!
I actually went as far as removing the trace_* calls before having a change of heart. As a result I spent some time looking at the trace_hardirqs_on() code and, as far as I remember, I couldn't find a lock in the code that wasn't bypassed when in_nmi() returned true.
However the code is pretty complex and its certainly possible that I missed something. Are you in a position to point any particular lock to show I messed this up or are you working from memory?
Now, You'll be forgiven if you now point out that in the code above, I'm taking a spinlock. That's absolutely true. Analyse the code a little closer and you'll notice it's done in a safe way. There's only one time where that spinlock is taken and that's from FIQ code, never from any other code, and only once per CPU - notice how arch_trigger_all_cpu_backtrace() protects itself against multiple callers, and how ipi_cpu_backtrace() is careful to check that its CPU bit is set. This is exactly the same method which x86 code uses (in fact, much of the above code was stolen from x86!)
Agreed. I also plan to use FIQ-safe locks in the GIC code to raise an IPI. The b.L switcher code explicitly calls local_fiq_disable() so providing the locks are contested only between the b.L switcher logic and FIQ handlers it will be safe.
That said my original approach (again, I'll post it in a moment) was pretty ugly which is why I'm so pleased with Stephen Boyd's recently proposed change.
So, how about moving the FIQ assembly code to entry-armv.S and making it less kgdb specific? (Though... we do want to keep a /very/ close eye on users to ensure that they don't do silly stuff with locking.)
I think I can do that.
Something like this?
1. Install the current trap handler code by default (either with or without trace_* calls).
2. Have default trap handler call an RCU notifier chain to allow it to hook up with "normal" code without any hard coding (kgdb, IPI handling, etc)
3. Retain existing install_fiq() behaviour for people who want FIQ to be a fast-interrupt rather than an NMI (for example, the Raspberry pi USB optimizations).
4. Ensure default handler is an exported symbol to allow the meths drinkers from #3 to chain to logic for NMI/debug features if they want to.
Daniel.
This is a work-in-progress set of patches to add support for implementing IPIs using FIQ. Currently KGDB is the only user of this feature although others could easily be added due to the use of notifier chains.
Patches depend on the KGDB NMI/FIQ patch series. I'll send a complete patchset for the KGDB NMI/FIQ shortly after 3.17rc1 comes out.
-- 1.9.3
Cross CPU signalling based on FIQ is especially useful for kgdb since it makes stopping all the CPUs during breakpointing more robust (some of the other architectures already roundup the CPUs using NMIs).
The approach taken provides infrastructure that can be called (or not) by the driver's FIQ handler depending upon it requirements. In other words nothing is added here that prevents the driver from accessing "bare metal" performance.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/hardirq.h | 2 +- arch/arm/include/asm/smp.h | 11 +++++++++++ arch/arm/kernel/smp.c | 44 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-)
diff --git a/arch/arm/include/asm/hardirq.h b/arch/arm/include/asm/hardirq.h index fe3ea77..5df33e3 100644 --- a/arch/arm/include/asm/hardirq.h +++ b/arch/arm/include/asm/hardirq.h @@ -5,7 +5,7 @@ #include <linux/threads.h> #include <asm/irq.h>
-#define NR_IPI 8 +#define NR_IPI 9
typedef struct { unsigned int __softirq_pending; diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 2ec765c..6a969f8 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -20,6 +20,9 @@
#define raw_smp_processor_id() (current_thread_info()->cpu)
+/* bitmap of IPIs that must be signalled using FIQ */ +#define SMP_IPI_FIQ_MASK 0x0100 + struct seq_file;
/* @@ -87,6 +90,14 @@ extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
extern int register_ipi_completion(struct completion *completion, int cpu);
+#ifdef CONFIG_FIQ +extern void send_fiq_ipi_mask(const struct cpumask *); +extern int __init register_fiq_ipi_notifier(struct notifier_block *nb); +void handle_IPI_FIQ(struct pt_regs *regs); +#else +#define register_fiq_ipi_notifier(nb) +#endif + struct smp_operations { #ifdef CONFIG_SMP /* diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 9388a3d..71557bc 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -72,6 +72,7 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_FIQ, };
static DECLARE_COMPLETION(cpu_running); @@ -451,6 +452,7 @@ static const char *ipi_types[NR_IPI] __tracepoint_string = { S(IPI_CPU_STOP, "CPU stop interrupts"), S(IPI_IRQ_WORK, "IRQ work interrupts"), S(IPI_COMPLETION, "completion interrupts"), + S(IPI_FIQ, "FIQ interrupts"), };
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr) @@ -552,6 +554,42 @@ static void ipi_complete(unsigned int cpu) complete(per_cpu(cpu_completion, cpu)); }
+#ifdef CONFIG_FIQ +static ATOMIC_NOTIFIER_HEAD(fiq_ipi_chain); + +/* + * Caller must ensure a FIQ handler that can clear the IPI is installed + * before calling this function. This is normally achieved by calling + * handle_IPI_FIQ() from the FIQ handler. + */ +void send_fiq_ipi_mask(const struct cpumask *mask) +{ + smp_cross_call(mask, IPI_FIQ); +} + +int __init register_fiq_ipi_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&fiq_ipi_chain, nb); +} + +void handle_IPI_FIQ(struct pt_regs *regs) +{ + unsigned int cpu = smp_processor_id(); + struct pt_regs *old_regs = set_irq_regs(regs); + + /* Make sure the FIQ mask matches our assumptions */ + BUILD_BUG_ON(SMP_IPI_FIQ_MASK ^ (1 << IPI_FIQ)); + + __inc_irq_stat(cpu, ipi_irqs[IPI_FIQ]); + + nmi_enter(); + atomic_notifier_call_chain(&fiq_ipi_chain, IPI_FIQ, NULL); + nmi_exit(); + + set_irq_regs(old_regs); +} +#endif + /* * Main handler for inter-processor interrupts */ @@ -618,6 +656,12 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break;
+#ifdef CONFIG_FIQ + case IPI_FIQ: + pr_crit("CPU%u: IPI FIQ delivered via IRQ vector\n", cpu); + break; +#endif + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr);
Rounding up the other CPUs using FIQ improves debugger robustness.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/kgdb.h | 1 + arch/arm/kernel/kgdb.c | 20 ++++++++++++++++---- arch/arm/kernel/kgdb_fiq.c | 7 ++++++- 3 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 5de21f01..ceb466f 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -50,6 +50,7 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern int kgdb_fiq_enabled; extern char kgdb_fiq_handler; extern char kgdb_fiq_handler_end; asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs); diff --git a/arch/arm/kernel/kgdb.c b/arch/arm/kernel/kgdb.c index a74b53c..e5c3ec0 100644 --- a/arch/arm/kernel/kgdb.c +++ b/arch/arm/kernel/kgdb.c @@ -175,14 +175,26 @@ static struct undef_hook kgdb_compiled_brkpt_hook = {
static void kgdb_call_nmi_hook(void *ignored) { - kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs()); + kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs()); }
void kgdb_roundup_cpus(unsigned long flags) { - local_irq_enable(); - smp_call_function(kgdb_call_nmi_hook, NULL, 0); - local_irq_disable(); +#ifdef CONFIG_FIQ + struct cpumask mask; + + if (kgdb_fiq_enabled) { + cpumask_copy(&mask, cpu_online_mask); + cpumask_clear_cpu(raw_smp_processor_id(), &mask); + if (!cpumask_empty(&mask)) + send_fiq_ipi_mask(&mask); + return; + } +#endif + + local_irq_enable(); + smp_call_function(kgdb_call_nmi_hook, NULL, 0); + local_irq_disable(); }
static int __kgdb_notify(struct die_args *args, unsigned long cmd) diff --git a/arch/arm/kernel/kgdb_fiq.c b/arch/arm/kernel/kgdb_fiq.c index a894dde..f7a2c3d 100644 --- a/arch/arm/kernel/kgdb_fiq.c +++ b/arch/arm/kernel/kgdb_fiq.c @@ -24,7 +24,7 @@ #include <asm/fiq.h> #include <asm/exception.h>
-static int kgdb_fiq_enabled; +int kgdb_fiq_enabled; module_param_named(enable, kgdb_fiq_enabled, int, 0600); MODULE_PARM_DESC(enable, "set to 1 to enable FIQ KGDB");
@@ -40,6 +40,11 @@ asmlinkage void __exception_irq_entry kgdb_fiq_do_handle(struct pt_regs *regs) { int actual;
+ if (!kgdb_nmicallback(raw_smp_processor_id(), regs)) { + handle_IPI_FIQ(regs); + return; + } + nmi_enter(); actual = ack_fiq(kgdb_fiq); WARN_ON(actual != kgdb_fiq);
To support IPI FIQ we alter gic_cpu_init() to honour SMP_IPI_FIQ_MASK and register a fairly high priority notifier to acknowledge and clear the IPI when it is triggered.
For the IPI FIQ to be useful we must also make it safe to call gic_raise_softirq() from the FIQ handler by altering the locking strategy slightly.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/irqchip/irq-gic.c | 126 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 107 insertions(+), 19 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index d928912..240cc87 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -39,6 +39,7 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/ratelimit.h>
#include <asm/cputype.h> #ifdef CONFIG_FIQ @@ -51,6 +52,10 @@ #include "irq-gic-common.h" #include "irqchip.h"
+#ifndef SMP_IPI_FIQ_MASK +#define SMP_IPI_FIQ_MASK 0 +#endif + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -77,6 +82,8 @@ struct gic_chip_data { };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); +/* A fiq-safe spinlock must only be locked when the FIQ is masked */ +static DEFINE_RAW_SPINLOCK(fiq_safe_migration_lock);
/* * The GIC mapping of CPU interfaces does not necessarily match @@ -346,20 +353,21 @@ static struct irq_chip gic_chip = { * match what "ARM strongly recommends" for a system where no Group 1 * interrupt must ever preempt a Group 0 interrupt. */ -static void gic_set_group_irq(struct irq_data *d, int group) +static void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) { - unsigned int grp_reg = gic_irq(d) / 32 * 4; - u32 grp_mask = 1 << (gic_irq(d) % 32); + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_mask = 1 << (hwirq % 32); u32 grp_val;
- unsigned int pri_reg = (gic_irq(d) / 4) * 4; - u32 pri_mask = 1 << (7 + ((gic_irq(d) % 4) * 8)); + unsigned int pri_reg = (hwirq / 4) * 4; + u32 pri_mask = 1 << (7 + ((hwirq % 4) * 8)); u32 pri_val;
raw_spin_lock(&irq_controller_lock);
- grp_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); - pri_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(base + GIC_DIST_PRI + pri_reg);
if (group) { grp_val |= grp_mask; @@ -369,20 +377,20 @@ static void gic_set_group_irq(struct irq_data *d, int group) pri_val &= ~pri_mask; }
- writel_relaxed(grp_val, gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); - writel_relaxed(pri_val, gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + writel_relaxed(grp_val, base + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, base + GIC_DIST_PRI + pri_reg);
raw_spin_unlock(&irq_controller_lock); }
static void gic_enable_fiq(struct irq_data *d) { - gic_set_group_irq(d, 0); + gic_set_group_irq(gic_dist_base(d), gic_irq(d), 0); }
static void gic_disable_fiq(struct irq_data *d) { - gic_set_group_irq(d, 1); + gic_set_group_irq(gic_dist_base(d), gic_irq(d), 1); }
static int gic_ack_fiq(struct irq_data *d) @@ -430,7 +438,63 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, for (i = 0; i < num_irqs; i++) fiq_register_mapping(first_irq + i, &gic_fiq); } + +/* + * Fully acknowledge (both ack and eoi) a FIQ-based IPI + */ +static int gic_eoi_fiq_ipi(struct notifier_block *nb, unsigned long expected, + void *data) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *cpu_base = gic_data_cpu_base(gic); + unsigned long irqstat, irqnr, last_irqnr; + + if (WARN_ON(!in_nmi())) + return NOTIFY_BAD; + + irqnr = -1; + do { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + + last_irqnr = irqnr; + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + if (likely(irqnr == expected)) + return NOTIFY_OK; + + /* We're in pretty serious trouble if we get here. We cannot + * safely call the handler for the unexpected interrupt either + * because we don't know how (if it is a FIQ) or we can't do so + * safely (if it is an IRQ). The only recovery possible is to + * spuriously EOI (which we've already done by this point) and + * hope this is sufficient to clear the spurious FIQ. + */ + WARN_RATELIMIT(1, "Unexpected irqnr %lu (expected %lu)\n", + irqnr, expected); + } while (last_irqnr != irqnr); + + /* We've become stuck EOIing the same interrupt. There's nothing + * more we can do here except hope that "something has changed" and + * that the FIQ handler doesn't re-enter. + * + * We ratelimit the message because expecting something to change + * is really quite optimistic. + */ + pr_crit_ratelimited("gic_eoi_fiq_ipi: Stuck on %lu, giving up\n", + irqnr); + return NOTIFY_BAD; +} + +/* + * Notifier to ensure IPI FIQ is acknowledged correctly. + */ +static struct notifier_block gic_fiq_ipi_notifier = { + .notifier_call = gic_eoi_fiq_ipi, + .priority = 100, +}; #else /* CONFIG_FIQ */ +static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) {} static inline void gic_init_fiq(struct gic_chip_data *gic, irq_hw_number_t first_irq, unsigned int num_irqs) {} @@ -508,6 +572,7 @@ static void gic_cpu_init(struct gic_chip_data *gic) void __iomem *base = gic_data_cpu_base(gic); unsigned int cpu_mask, cpu = smp_processor_id(); int i; + u32 val;
/* * Get what the GIC says our CPU mask is. @@ -527,14 +592,19 @@ static void gic_cpu_init(struct gic_chip_data *gic) gic_cpu_config(dist_base, NULL);
/* - * Set all PPI and SGI interrupts to be group 1. - * - * If grouping is not available (not implemented or prohibited by - * security mode) these registers are read-as-zero/write-ignored. + * Optionally set all PPI and SGI interrupts to be group 1. */ if (gic_data_fiq_enable(gic)) writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0);
+ /* + * Optionally shift the FIQ based IPIs to group 0. + */ + if (gic_data_fiq_enable(gic)) + for (i = 0; i < 16; i++) + if (SMP_IPI_FIQ_MASK & (1 << i)) + gic_set_group_irq(dist_base, i, 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); if (gic_data_fiq_enable(gic)) writel_relaxed(0x1f, base + GIC_CPU_CTRL); @@ -747,7 +817,17 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) unsigned long flags, map = 0; unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags); + /* + * The locking in this function ensures we don't use stale cpu mappings + * and thus we never route an IPI to the wrong physical core during a + * big.LITTLE switch. The switch code takes both of these locks meaning + * we can choose whichever lock is safe to use from our current calling + * context. + */ + if (in_nmi()) + raw_spin_lock(&fiq_safe_migration_lock); + else + raw_spin_lock_irqsave(&irq_controller_lock, flags);
/* Convert our logical CPU mask into a physical one. */ for_each_cpu(cpu, mask) @@ -761,12 +841,16 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
/* this always happens on GIC0 */ softint = map << 16 | irq; - if (gic_data_fiq_enable(&gic_data[0])) + if (gic_data_fiq_enable(&gic_data[0]) && + !(SMP_IPI_FIQ_MASK & (1 << irq))) softint |= 0x8000; writel_relaxed(softint, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
- raw_spin_unlock_irqrestore(&irq_controller_lock, flags); + if (in_nmi()) + raw_spin_unlock(&fiq_safe_migration_lock); + else + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } #endif
@@ -814,7 +898,7 @@ int gic_get_cpu_id(unsigned int cpu) * Migrate all peripheral interrupts with a target matching the current CPU * to the interface corresponding to @new_cpu_id. The CPU interface mapping * is also updated. Targets to other CPU interfaces are unchanged. - * This must be called with IRQs locally disabled. + * This must be called with IRQ and FIQ locally disabled. */ void gic_migrate_target(unsigned int new_cpu_id) { @@ -836,6 +920,7 @@ void gic_migrate_target(unsigned int new_cpu_id) ror_val = (cur_cpu_id - new_cpu_id) & 31;
raw_spin_lock(&irq_controller_lock); + raw_spin_lock(&fiq_safe_migration_lock);
/* Update the target interface for this logical CPU */ gic_cpu_map[cpu] = 1 << new_cpu_id; @@ -855,6 +940,7 @@ void gic_migrate_target(unsigned int new_cpu_id) } }
+ raw_spin_unlock(&fiq_safe_migration_lock); raw_spin_unlock(&irq_controller_lock);
/* @@ -1125,6 +1211,8 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); + if (gic_data_fiq_enable(gic)) + register_fiq_ipi_notifier(&gic_fiq_ipi_notifier); #endif set_handle_irq(gic_handle_irq); }
On Thu, Aug 14, 2014 at 11:48:36AM +0100, Daniel Thompson wrote:
Don't worry, you certainly did make it clear!
I actually went as far as removing the trace_* calls before having a change of heart. As a result I spent some time looking at the trace_hardirqs_on() code and, as far as I remember, I couldn't find a lock in the code that wasn't bypassed when in_nmi() returned true.
However the code is pretty complex and its certainly possible that I missed something. Are you in a position to point any particular lock to show I messed this up or are you working from memory?
The complexity alone is an argument against calling it from FIQ context, because there's no guarantee that what's there will stay that way.
trace_hardirqs_on() is made more complex because there's not one function with that name, but two. One is in the trace code, the other is in the lockdep code. Here's the trace code, which has at least two problems:
trace_hardirqs_on -> stop_critical_timing -> check_critical_timing -> raw_spin_lock_irqsave(&max_trace_lock, flags); -> __trace_stack -> __ftrace_trace_stack -> save_stack_trace -> __save_stack_trace -> walk_stackframe -> unwind_frame -> unwind_find_idx -> spin_lock_irqsave(&unwind_lock, flags);
So, how about moving the FIQ assembly code to entry-armv.S and making it less kgdb specific? (Though... we do want to keep a /very/ close eye on users to ensure that they don't do silly stuff with locking.)
I think I can do that.
Something like this?
- Install the current trap handler code by default (either with or without trace_* calls).
Without trace_* calls. The FIQ should be transparent to tracing - tracing and lockdep is too much of an unknown (due to size and complexity) to ensure that it always follows the rules.
- Have default trap handler call an RCU notifier chain to allow it to hook up with "normal" code without any hard coding (kgdb, IPI handling, etc)
Maybe... that sounds like it opens up FIQ for general purpose use which is something I want to avoid - I've little motivation to ensure that everyone plays by the rules. Given the choice, I'd rather maintain our present stance that using FIQs is hard and requires a lot of thought.
- Retain existing install_fiq() behaviour for people who want FIQ to be a fast-interrupt rather than an NMI (for example, the Raspberry pi USB optimizations).
Yes, arch/arm/kernel/fiq.c should be relatively untouched by the core mods I suggested - we're just replacing the default "subs" instruction (which just ignores the FIQ) with something which can do something with it.
It should be noted that using arch/arm/kernel/fiq.c would override this default FIQ handler - and that's a feature of it - fiq.c is based on the premise that there is only one single owner of the FIQ at any one time and there is no sharing of it, and that's something we want to retain.
- Ensure default handler is an exported symbol to allow the meths drinkers from #3 to chain to logic for NMI/debug features if they want to.
That's not something I particularly want to permit - especially as the requirements for each "handler" are different - this new default code relies upon r13 pointing at some storage to save some registers, which may not be true of other FIQ handlers. Chaining it means that we force that use of r13 on others, and we then have to also come up with some way to export the correct r13 value.
Given that fiq.c doesn't work with SMP, this isn't something I want to encourage.
Further more, I can't get excited about Raspberry Pi "optimisations" using FIQ until I see the code, and I see no reason to think about catering for such stuff until the patches become visible here.
On 14/08/14 13:36, Russell King - ARM Linux wrote:
On Thu, Aug 14, 2014 at 11:48:36AM +0100, Daniel Thompson wrote:
Don't worry, you certainly did make it clear!
I actually went as far as removing the trace_* calls before having a change of heart. As a result I spent some time looking at the trace_hardirqs_on() code and, as far as I remember, I couldn't find a lock in the code that wasn't bypassed when in_nmi() returned true.
However the code is pretty complex and its certainly possible that I missed something. Are you in a position to point any particular lock to show I messed this up or are you working from memory?
The complexity alone is an argument against calling it from FIQ context, because there's no guarantee that what's there will stay that way.
trace_hardirqs_on() is made more complex because there's not one function with that name, but two. One is in the trace code, the other is in the lockdep code. Here's the trace code, which has at least two problems:
trace_hardirqs_on -> stop_critical_timing -> check_critical_timing -> raw_spin_lock_irqsave(&max_trace_lock, flags); -> __trace_stack -> __ftrace_trace_stack -> save_stack_trace -> __save_stack_trace -> walk_stackframe -> unwind_frame -> unwind_find_idx -> spin_lock_irqsave(&unwind_lock, flags);
Looks like I had an out-by-one error when reviewing this... sadly the value was boolean.
Thanks this is very clear.
So, how about moving the FIQ assembly code to entry-armv.S and making it less kgdb specific? (Though... we do want to keep a /very/ close eye on users to ensure that they don't do silly stuff with locking.)
I think I can do that.
Something like this?
- Install the current trap handler code by default (either with or without trace_* calls).
Without trace_* calls. The FIQ should be transparent to tracing - tracing and lockdep is too much of an unknown (due to size and complexity) to ensure that it always follows the rules.
Will do.
- Have default trap handler call an RCU notifier chain to allow it to hook up with "normal" code without any hard coding (kgdb, IPI handling, etc)
Maybe... that sounds like it opens up FIQ for general purpose use which is something I want to avoid - I've little motivation to ensure that everyone plays by the rules. Given the choice, I'd rather maintain our present stance that using FIQs is hard and requires a lot of thought.
I'll see what feels natural. Not exporting the symbol to register for notification would go some way to preventing abuse whilst still allowing decoupling from kgdb (which cannot be a module).
- Retain existing install_fiq() behaviour for people who want FIQ to be a fast-interrupt rather than an NMI (for example, the Raspberry pi USB optimizations).
Yes, arch/arm/kernel/fiq.c should be relatively untouched by the core mods I suggested - we're just replacing the default "subs" instruction (which just ignores the FIQ) with something which can do something with it.
It should be noted that using arch/arm/kernel/fiq.c would override this default FIQ handler - and that's a feature of it - fiq.c is based on the premise that there is only one single owner of the FIQ at any one time and there is no sharing of it, and that's something we want to retain.
However, particularly for IPIs, if we've build useful debug features using them it would be nice to provide hooks that the owner can use to keep them working if they wish.
However I agree with your response to #4; interfacing at a level lower than C ABI isn't right. To use any hooks the owner must first make it safe to call C.
- Ensure default handler is an exported symbol to allow the meths drinkers from #3 to chain to logic for NMI/debug features if they want to.
That's not something I particularly want to permit - especially as the requirements for each "handler" are different - this new default code relies upon r13 pointing at some storage to save some registers, which may not be true of other FIQ handlers. Chaining it means that we force that use of r13 on others, and we then have to also come up with some way to export the correct r13 value.
Given that fiq.c doesn't work with SMP, this isn't something I want to encourage.
Makes sense.
Further more, I can't get excited about Raspberry Pi "optimisations" using FIQ until I see the code, and I see no reason to think about catering for such stuff until the patches become visible here.
IIRC they don't add any use-cases beyond the existing in-kernel FIQ users (such as R-PC floppy disc). It's just that they do it on more recent hardware (and as you say, out-of-tree).
On 10 July 2014 10:03, Daniel Thompson daniel.thompson@linaro.org wrote:
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches have been previously circulated as part of a large patchset mixing together ARM architecture code and driver changes (http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901 ). This patchset is dramatically cut down to include only the arch/arm code. The driver code (irqchip and tty/serial) will follow when/if the arch code is accepted.
The first two patches modify the FIQ infrastructure to allow interrupt controller drivers to register callbacks (the fiq_chip structure) to manage FIQ routings and to ACK and EOI the FIQ. This makes it possible to use FIQ in multi-platform kernels and with recent ARM interrupt controllers.
Daniel,
Thanks for the patches. I have tested the fiq and irq-gic patches on an i.MX6 (SabreSD board) for a different purpose: A FIQ timer interrupt at 1 kHz. The TWD watchdog block is used in timer mode for this (interrupt ID 30). The GIC affinity is set to CPU core 0 only for this interrupt ID.
I was surprised by the following behavior: $ cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 29: 5459 3381 3175 3029 GIC 29 twd 30: 11 0 0 0 GIC 30 fake-fiq
Once every few seconds is interrupt ID 30 handled by the regular GIC handler instead of the FIQ exception path (which causes for example a bit more latencies). The other thousands of FIQ's are handled by the FIQ exception path. The problem is also confirmed by the following stackframe of the Lauterbach tooling: fake_fiq_handler(irq = 30, ...) handle_percpu_devid_irq(irq = 30, ...) generic_handle_irq(irq = 30) handle_IRQ(irq = 30, ...) gic_handle_irq __irq_svc -->exception
Notes: - The problem will occur more often by executing the following command: $ while true; do hackbench 20; done & - Reading the GIC_CPU_INTACK register returns the interrupt ID of the highest priority pending interrupt. - The GIC_CPU_INTACK register (used by fiq_ack) will return spurious interrupt ID 0x3FF when read in fake_fiq_handler, because GIC_CPU_INTACK is already read by gic_handle_irq. - The FIQ exception will not occur anymore after gic_handle_irq read/acknowledges the FIQ by reading the GIC_CPU_INTACK register
From the behavior above I conclude that the GIC_CPU_INTACK register is
first updated before the FIQ exception is generated, so there is a small period of time that gic_handle_irq can read/acknowledge the FIQ.
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 { + while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET) & (1 << 30)) + printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n"); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
Regards,
Harro Haan
On 14/07/14 14:51, Harro Haan wrote:
On 10 July 2014 10:03, Daniel Thompson daniel.thompson@linaro.org wrote:
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches have been previously circulated as part of a large patchset mixing together ARM architecture code and driver changes (http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901 ). This patchset is dramatically cut down to include only the arch/arm code. The driver code (irqchip and tty/serial) will follow when/if the arch code is accepted.
The first two patches modify the FIQ infrastructure to allow interrupt controller drivers to register callbacks (the fiq_chip structure) to manage FIQ routings and to ACK and EOI the FIQ. This makes it possible to use FIQ in multi-platform kernels and with recent ARM interrupt controllers.
Daniel,
Thanks for the patches. I have tested the fiq and irq-gic patches on an i.MX6 (SabreSD board) for a different purpose: A FIQ timer interrupt at 1 kHz. The TWD watchdog block is used in timer mode for this (interrupt ID 30). The GIC affinity is set to CPU core 0 only for this interrupt ID.
I was surprised by the following behavior: $ cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 29: 5459 3381 3175 3029 GIC 29 twd 30: 11 0 0 0 GIC 30 fake-fiq
Once every few seconds is interrupt ID 30 handled by the regular GIC handler instead of the FIQ exception path (which causes for example a bit more latencies). The other thousands of FIQ's are handled by the FIQ exception path. The problem is also confirmed by the following stackframe of the Lauterbach tooling: fake_fiq_handler(irq = 30, ...) handle_percpu_devid_irq(irq = 30, ...) generic_handle_irq(irq = 30) handle_IRQ(irq = 30, ...) gic_handle_irq __irq_svc -->exception
Notes:
- The problem will occur more often by executing the following command: $ while true; do hackbench 20; done &
- Reading the GIC_CPU_INTACK register returns the interrupt ID of the
highest priority pending interrupt.
- The GIC_CPU_INTACK register (used by fiq_ack) will return spurious
interrupt ID 0x3FF when read in fake_fiq_handler, because GIC_CPU_INTACK is already read by gic_handle_irq.
- The FIQ exception will not occur anymore after gic_handle_irq
read/acknowledges the FIQ by reading the GIC_CPU_INTACK register
From the behavior above I conclude that the GIC_CPU_INTACK register is first updated before the FIQ exception is generated, so there is a small period of time that gic_handle_irq can read/acknowledge the FIQ.
Makes sense.
Avoiding this problem on GICv2 is easy (thanks to the aliased intack register) but on iMX.6 we have only a GICv1.
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
- printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n"); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 73ae896..309bf2c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -303,6 +303,28 @@ static int gic_set_wake(struct irq_data *d, unsigned int on) #define gic_set_wake NULL #endif
+/* Check for group 0 interrupt spuriously acked as a normal IRQ. This + * workaround will only work for level triggered interrupts (and in + * its current form is actively harmful on systems that don't support + * FIQ). + */ +static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat) +{ + u32 irqnr = irqstat & GICC_IAR_INT_ID_MASK; + + if (!gic_data_fiq_enable(gic) || irqnr >= 1021) + return irqnr; + + if (readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_IGROUP + + (irqnr / 32 * 4)) & + BIT(irqnr % 32)) + return irqnr; + + /* this interrupt was spurious (needs to be handled as FIQ) */ + writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI); + return 1023; +} + static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u32 irqstat, irqnr; @@ -310,8 +332,10 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) void __iomem *cpu_base = gic_data_cpu_base(gic);
do { + local_fiq_disable(); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); - irqnr = irqstat & GICC_IAR_INT_ID_MASK; + irqnr = gic_handle_spurious_group_0(gic, irqstat); + local_fiq_enable();
if (likely(irqnr > 15 && irqnr < 1021)) { irqnr = irq_find_mapping(gic->domain, irqnr);
On 15 July 2014 11:41, Daniel Thompson daniel.thompson@linaro.org wrote:
On 14/07/14 14:51, Harro Haan wrote:
On 10 July 2014 10:03, Daniel Thompson daniel.thompson@linaro.org wrote:
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches have been previously circulated as part of a large patchset mixing together ARM architecture code and driver changes (http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901 ). This patchset is dramatically cut down to include only the arch/arm code. The driver code (irqchip and tty/serial) will follow when/if the arch code is accepted.
The first two patches modify the FIQ infrastructure to allow interrupt controller drivers to register callbacks (the fiq_chip structure) to manage FIQ routings and to ACK and EOI the FIQ. This makes it possible to use FIQ in multi-platform kernels and with recent ARM interrupt controllers.
Daniel,
Thanks for the patches. I have tested the fiq and irq-gic patches on an i.MX6 (SabreSD board) for a different purpose: A FIQ timer interrupt at 1 kHz. The TWD watchdog block is used in timer mode for this (interrupt ID 30). The GIC affinity is set to CPU core 0 only for this interrupt ID.
I was surprised by the following behavior: $ cat /proc/interrupts CPU0 CPU1 CPU2 CPU3 29: 5459 3381 3175 3029 GIC 29 twd 30: 11 0 0 0 GIC 30 fake-fiq
Once every few seconds is interrupt ID 30 handled by the regular GIC handler instead of the FIQ exception path (which causes for example a bit more latencies). The other thousands of FIQ's are handled by the FIQ exception path. The problem is also confirmed by the following stackframe of the Lauterbach tooling: fake_fiq_handler(irq = 30, ...) handle_percpu_devid_irq(irq = 30, ...) generic_handle_irq(irq = 30) handle_IRQ(irq = 30, ...) gic_handle_irq __irq_svc -->exception
Notes:
- The problem will occur more often by executing the following command: $ while true; do hackbench 20; done &
- Reading the GIC_CPU_INTACK register returns the interrupt ID of the
highest priority pending interrupt.
- The GIC_CPU_INTACK register (used by fiq_ack) will return spurious
interrupt ID 0x3FF when read in fake_fiq_handler, because GIC_CPU_INTACK is already read by gic_handle_irq.
- The FIQ exception will not occur anymore after gic_handle_irq
read/acknowledges the FIQ by reading the GIC_CPU_INTACK register
From the behavior above I conclude that the GIC_CPU_INTACK register is first updated before the FIQ exception is generated, so there is a small period of time that gic_handle_irq can read/acknowledge the FIQ.
Makes sense.
Avoiding this problem on GICv2 is easy (thanks to the aliased intack register) but on iMX.6 we have only a GICv1.
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
- printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n"); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
Thanks Daniel, I have tested it, see the comments below.
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 73ae896..309bf2c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -303,6 +303,28 @@ static int gic_set_wake(struct irq_data *d, unsigned int on) #define gic_set_wake NULL #endif
+/* Check for group 0 interrupt spuriously acked as a normal IRQ. This
- workaround will only work for level triggered interrupts (and in
- its current form is actively harmful on systems that don't support
- FIQ).
- */
+static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat) +{
u32 irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (!gic_data_fiq_enable(gic) || irqnr >= 1021)
return irqnr;
if (readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_IGROUP +
(irqnr / 32 * 4)) &
BIT(irqnr % 32))
return irqnr;
/* this interrupt was spurious (needs to be handled as FIQ) */
writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI);
This will NOT work, because of the note I mentioned above: "The FIQ exception will not occur anymore after gic_handle_irq read/acknowledges the FIQ by reading the GIC_CPU_INTACK register" So with this code you will say End Of Interrupt at the GIC level, without actually handling the interrupt, so you are missing an interrupt. I did the following test to confirm the missing interrupt: I have changed the periodic timer interrupt by an one-shot timer interrupt. The one-shot timer interrupt is programmed by the FIQ handler for the next FIQ interrupt. As expected: When the problem occurs, no more FIQ interrupts are generated.
return 1023;
+}
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u32 irqstat, irqnr; @@ -310,8 +332,10 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
local_fiq_disable();
It is a bit weird to disable the "Non-Maskable Interrupt" at every interrupt, because of a problem that occurs once every few thousand interrupts
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
irqnr = gic_handle_spurious_group_0(gic, irqstat);
local_fiq_enable(); if (likely(irqnr > 15 && irqnr < 1021)) { irqnr = irq_find_mapping(gic->domain, irqnr);
The following type of changes could fix the problem for me:
@@ -290,19 +290,66 @@ static int gic_set_wake(struct irq_data *d, unsigned int on)
#else #define gic_set_wake NULL #endif
+extern void (*fiq_handler)(void); + +/* Check for group 0 interrupt spuriously acked as a normal IRQ. This + * workaround will only work for level triggered interrupts (and in + * its current form is actively harmful on systems that don't support + * FIQ). + */ +static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat) +{ + u32 irqnr = irqstat & ~0x1c00; + + if (!gic_data_fiq_enable(gic) || irqnr >= 1021) + return irqnr; + + if (readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_IGROUP + + (irqnr / 32 * 4)) & BIT(irqnr % 32)) + return irqnr; + + /* + * The FIQ should be disabled before the next FIQ interrupt occurs, + * so this only works when the next FIQ interrupt is not "too fast" + * after the previous one. + */ + local_fiq_disable(); + + /* + * Notes: + * - The FIQ exception will not occur anymore for this current + * interrupt, because gic_handle_irq has already read/acknowledged + * the GIC_CPU_INTACK register. So handle the FIQ here. + * - The fiq_handler below should not call ack_fiq and eoi_fiq, + * because rereading the GIC_CPU_INTACK register returns spurious + * interrupt ID 0x3FF. So probably you will have to add sometime like + * the following to fiq_handler: + * u32 cpsr, irqstat; + * __asm__("mrs %0, cpsr" : "=r" (cpsr)); + * if ((cpsr & MODE_MASK) == FIQ_MODE) + * irqstat = ack_fiq(); + */ + (*fiq_handler)(); + + writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI); + local_fiq_enable(); + + return 1023; +} + static asmlinkage 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); - irqnr = irqstat & ~0x1c00; + irqnr = gic_handle_spurious_group_0(gic, irqstat);
if (likely(irqnr > 15 && irqnr < 1021)) {
Regards,
Harro
On 15/07/14 14:04, Harro Haan wrote:
Makes sense.
Avoiding this problem on GICv2 is easy (thanks to the aliased intack register) but on iMX.6 we have only a GICv1.
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
- printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n"); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
Thanks Daniel, I have tested it, see the comments below.
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 73ae896..309bf2c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -303,6 +303,28 @@ static int gic_set_wake(struct irq_data *d, unsigned int on) #define gic_set_wake NULL #endif
+/* Check for group 0 interrupt spuriously acked as a normal IRQ. This
- workaround will only work for level triggered interrupts (and in
- its current form is actively harmful on systems that don't support
- FIQ).
- */
+static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat) +{
u32 irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (!gic_data_fiq_enable(gic) || irqnr >= 1021)
return irqnr;
if (readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_IGROUP +
(irqnr / 32 * 4)) &
BIT(irqnr % 32))
return irqnr;
/* this interrupt was spurious (needs to be handled as FIQ) */
writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI);
This will NOT work, because of the note I mentioned above: "The FIQ exception will not occur anymore after gic_handle_irq read/acknowledges the FIQ by reading the GIC_CPU_INTACK register" So with this code you will say End Of Interrupt at the GIC level, without actually handling the interrupt, so you are missing an interrupt. I did the following test to confirm the missing interrupt: I have changed the periodic timer interrupt by an one-shot timer interrupt. The one-shot timer interrupt is programmed by the FIQ handler for the next FIQ interrupt. As expected: When the problem occurs, no more FIQ interrupts are generated.
Can you confirm whether your timer interrupts are configured level or edge triggered? Or whether EOIing the GIC causes them to be cleared by some other means.
A level triggered interrupt that hasn't been serviced should return the pending state (shouldn't it?).
return 1023;
+}
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u32 irqstat, irqnr; @@ -310,8 +332,10 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
local_fiq_disable();
It is a bit weird to disable the "Non-Maskable Interrupt" at every interrupt, because of a problem that occurs once every few thousand interrupts
Given that simply reading from GIC_CPU_INTACK has significantly interferes with FIQ reception means that I'm not too worried about this.
Note also that leaving the FIQ unmasked increases worst case latency here because once we get a group 0 interrupt back from intack then spurious entry to the FIQ handler (which would see an ACK of 1023) just wastes cycles.
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
irqnr = gic_handle_spurious_group_0(gic, irqstat);
local_fiq_enable(); if (likely(irqnr > 15 && irqnr < 1021)) { irqnr = irq_find_mapping(gic->domain, irqnr);
The following type of changes could fix the problem for me:
@@ -290,19 +290,66 @@ static int gic_set_wake(struct irq_data *d, unsigned int on)
#else #define gic_set_wake NULL #endif
+extern void (*fiq_handler)(void);
+/* Check for group 0 interrupt spuriously acked as a normal IRQ. This
- workaround will only work for level triggered interrupts (and in
- its current form is actively harmful on systems that don't support
- FIQ).
- */
+static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat) +{
- u32 irqnr = irqstat & ~0x1c00;
- if (!gic_data_fiq_enable(gic) || irqnr >= 1021)
- return irqnr;
- if (readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_IGROUP +
- (irqnr / 32 * 4)) & BIT(irqnr % 32))
- return irqnr;
- /*
- The FIQ should be disabled before the next FIQ interrupt occurs,
- so this only works when the next FIQ interrupt is not "too fast"
- after the previous one.
- */
- local_fiq_disable();
- /*
- Notes:
- The FIQ exception will not occur anymore for this current
- interrupt, because gic_handle_irq has already read/acknowledged
- the GIC_CPU_INTACK register. So handle the FIQ here.
- The fiq_handler below should not call ack_fiq and eoi_fiq,
- because rereading the GIC_CPU_INTACK register returns spurious
- interrupt ID 0x3FF. So probably you will have to add sometime like
- the following to fiq_handler:
- u32 cpsr, irqstat;
- __asm__("mrs %0, cpsr" : "=r" (cpsr));
- if ((cpsr & MODE_MASK) == FIQ_MODE)
- irqstat = ack_fiq();
- */
- (*fiq_handler)();
Any portable approach would have to switch to FIQ mode to run the handler in order to provide the proper register banks for FIQ handlers written in assembler.
If we can't get level triggering to work then we have to:
1. Write code to jump correctly into FIQ mode.
2. Modify the gic's ack_fiq() callback to automatically avoid reading intack when the workaround is deployed.
The above is why I wanted to see if we can make do with level triggering (and automatic re-triggering for interrupts such as SGIs that are cleared by EOI).
On 15 July 2014 16:52, Daniel Thompson daniel.thompson@linaro.org wrote:
On 15/07/14 14:04, Harro Haan wrote:
Makes sense.
Avoiding this problem on GICv2 is easy (thanks to the aliased intack register) but on iMX.6 we have only a GICv1.
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
- printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n"); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
Thanks Daniel, I have tested it, see the comments below.
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 73ae896..309bf2c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -303,6 +303,28 @@ static int gic_set_wake(struct irq_data *d, unsigned int on) #define gic_set_wake NULL #endif
+/* Check for group 0 interrupt spuriously acked as a normal IRQ. This
- workaround will only work for level triggered interrupts (and in
- its current form is actively harmful on systems that don't support
- FIQ).
- */
+static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat) +{
u32 irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (!gic_data_fiq_enable(gic) || irqnr >= 1021)
return irqnr;
if (readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_IGROUP +
(irqnr / 32 * 4)) &
BIT(irqnr % 32))
return irqnr;
/* this interrupt was spurious (needs to be handled as FIQ) */
writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI);
This will NOT work, because of the note I mentioned above: "The FIQ exception will not occur anymore after gic_handle_irq read/acknowledges the FIQ by reading the GIC_CPU_INTACK register" So with this code you will say End Of Interrupt at the GIC level, without actually handling the interrupt, so you are missing an interrupt. I did the following test to confirm the missing interrupt: I have changed the periodic timer interrupt by an one-shot timer interrupt. The one-shot timer interrupt is programmed by the FIQ handler for the next FIQ interrupt. As expected: When the problem occurs, no more FIQ interrupts are generated.
Can you confirm whether your timer interrupts are configured level or edge triggered? Or whether EOIing the GIC causes them to be cleared by some other means.
From page 48 of DDI0407I_cortex_a9_mpcore_r4p1_trm.pdf:
Watchdog timers, PPI(3) Each Cortex-A9 processor has its own watchdog timers that can generate interrupts, using ID30.
From page 56:
PPI[0], [2],and[3]:b11 interrupt is rising-edge sensitive.
A level triggered interrupt that hasn't been serviced should return the pending state (shouldn't it?).
return 1023;
+}
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u32 irqstat, irqnr; @@ -310,8 +332,10 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
local_fiq_disable();
It is a bit weird to disable the "Non-Maskable Interrupt" at every interrupt, because of a problem that occurs once every few thousand interrupts
Given that simply reading from GIC_CPU_INTACK has significantly interferes with FIQ reception means that I'm not too worried about this.
Note also that leaving the FIQ unmasked increases worst case latency here because once we get a group 0 interrupt back from intack then spurious entry to the FIQ handler (which would see an ACK of 1023) just wastes cycles.
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
irqnr = gic_handle_spurious_group_0(gic, irqstat);
local_fiq_enable(); if (likely(irqnr > 15 && irqnr < 1021)) { irqnr = irq_find_mapping(gic->domain, irqnr);
The following type of changes could fix the problem for me:
@@ -290,19 +290,66 @@ static int gic_set_wake(struct irq_data *d, unsigned int on)
#else #define gic_set_wake NULL #endif
+extern void (*fiq_handler)(void);
+/* Check for group 0 interrupt spuriously acked as a normal IRQ. This
- workaround will only work for level triggered interrupts (and in
- its current form is actively harmful on systems that don't support
- FIQ).
- */
+static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat) +{
- u32 irqnr = irqstat & ~0x1c00;
- if (!gic_data_fiq_enable(gic) || irqnr >= 1021)
- return irqnr;
- if (readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_IGROUP +
- (irqnr / 32 * 4)) & BIT(irqnr % 32))
- return irqnr;
- /*
- The FIQ should be disabled before the next FIQ interrupt occurs,
- so this only works when the next FIQ interrupt is not "too fast"
- after the previous one.
- */
- local_fiq_disable();
- /*
- Notes:
- The FIQ exception will not occur anymore for this current
- interrupt, because gic_handle_irq has already read/acknowledged
- the GIC_CPU_INTACK register. So handle the FIQ here.
- The fiq_handler below should not call ack_fiq and eoi_fiq,
- because rereading the GIC_CPU_INTACK register returns spurious
- interrupt ID 0x3FF. So probably you will have to add sometime like
- the following to fiq_handler:
- u32 cpsr, irqstat;
- __asm__("mrs %0, cpsr" : "=r" (cpsr));
- if ((cpsr & MODE_MASK) == FIQ_MODE)
- irqstat = ack_fiq();
- */
- (*fiq_handler)();
Any portable approach would have to switch to FIQ mode to run the handler in order to provide the proper register banks for FIQ handlers written in assembler.
If we can't get level triggering to work then we have to:
Write code to jump correctly into FIQ mode.
Modify the gic's ack_fiq() callback to automatically avoid reading intack when the workaround is deployed.
The above is why I wanted to see if we can make do with level triggering (and automatic re-triggering for interrupts such as SGIs that are cleared by EOI).
But the re-triggering introduces extra latencies and a lot of use cases of FIQ's try to avoid that.
On 15/07/14 16:59, Harro Haan wrote:
On 15 July 2014 16:52, Daniel Thompson daniel.thompson@linaro.org wrote:
On 15/07/14 14:04, Harro Haan wrote:
Makes sense.
Avoiding this problem on GICv2 is easy (thanks to the aliased intack register) but on iMX.6 we have only a GICv1.
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
- printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n"); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
Thanks Daniel, I have tested it, see the comments below.
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 73ae896..309bf2c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -303,6 +303,28 @@ static int gic_set_wake(struct irq_data *d, unsigned int on) #define gic_set_wake NULL #endif
+/* Check for group 0 interrupt spuriously acked as a normal IRQ. This
- workaround will only work for level triggered interrupts (and in
- its current form is actively harmful on systems that don't support
- FIQ).
- */
+static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat) +{
u32 irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (!gic_data_fiq_enable(gic) || irqnr >= 1021)
return irqnr;
if (readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_IGROUP +
(irqnr / 32 * 4)) &
BIT(irqnr % 32))
return irqnr;
/* this interrupt was spurious (needs to be handled as FIQ) */
writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI);
This will NOT work, because of the note I mentioned above: "The FIQ exception will not occur anymore after gic_handle_irq read/acknowledges the FIQ by reading the GIC_CPU_INTACK register" So with this code you will say End Of Interrupt at the GIC level, without actually handling the interrupt, so you are missing an interrupt. I did the following test to confirm the missing interrupt: I have changed the periodic timer interrupt by an one-shot timer interrupt. The one-shot timer interrupt is programmed by the FIQ handler for the next FIQ interrupt. As expected: When the problem occurs, no more FIQ interrupts are generated.
Can you confirm whether your timer interrupts are configured level or edge triggered? Or whether EOIing the GIC causes them to be cleared by some other means.
From page 48 of DDI0407I_cortex_a9_mpcore_r4p1_trm.pdf: Watchdog timers, PPI(3) Each Cortex-A9 processor has its own watchdog timers that can generate interrupts, using ID30.
From page 56: PPI[0], [2],and[3]:b11 interrupt is rising-edge sensitive.
Thanks. This is clear.
If we can't get level triggering to work then we have to:
Write code to jump correctly into FIQ mode.
Modify the gic's ack_fiq() callback to automatically avoid reading intack when the workaround is deployed.
The above is why I wanted to see if we can make do with level triggering (and automatic re-triggering for interrupts such as SGIs that are cleared by EOI).
But the re-triggering introduces extra latencies and a lot of use cases of FIQ's try to avoid that.
I'm not really clear why retriggering should be significantly more expensive than the dance required to fake up entry the FIQ handler.
On the other hand retriggering allows us to avoid hacks in the FIQ handler to stop it acknowledging the interrupt. Given hacks like that won't be needed on A7/A15 this seems like a good outcome.
Anyhow I've put together a new version of my earlier patch that I think will retrigger all interrupts except SGIs (I'll look at SGIs and compatibility with non-Freescale parts only if this improved approach works).
Reported-by: Harro Haan hrhaan@gmail.com Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/irqchip/irq-gic.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 73ae896..88f92e6 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -303,6 +303,33 @@ static int gic_set_wake(struct irq_data *d, unsigned int on) #define gic_set_wake NULL #endif
+/* 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 (!gic_data_fiq_enable(gic) || irqnr >= 1021) + return irqnr; + + offset = irqnr / 32 * 4; + mask = 1 << (irqnr % 32); + if (readl_relaxed(dist_base + GIC_DIST_IGROUP + offset) & mask) + return irqnr; + + /* 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 void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u32 irqstat, irqnr; @@ -310,8 +337,10 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) void __iomem *cpu_base = gic_data_cpu_base(gic);
do { + local_fiq_disable(); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); - irqnr = irqstat & GICC_IAR_INT_ID_MASK; + irqnr = gic_handle_spurious_group_0(gic, irqstat); + local_fiq_enable();
if (likely(irqnr > 15 && irqnr < 1021)) { irqnr = irq_find_mapping(gic->domain, irqnr);
On 15 July 2014 19:08, Daniel Thompson daniel.thompson@linaro.org wrote:
On 15/07/14 16:59, Harro Haan wrote:
On 15 July 2014 16:52, Daniel Thompson daniel.thompson@linaro.org wrote:
On 15/07/14 14:04, Harro Haan wrote:
Makes sense.
Avoiding this problem on GICv2 is easy (thanks to the aliased intack register) but on iMX.6 we have only a GICv1.
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
- printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n"); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
Thanks Daniel, I have tested it, see the comments below.
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 73ae896..309bf2c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -303,6 +303,28 @@ static int gic_set_wake(struct irq_data *d, unsigned int on) #define gic_set_wake NULL #endif
+/* Check for group 0 interrupt spuriously acked as a normal IRQ. This
- workaround will only work for level triggered interrupts (and in
- its current form is actively harmful on systems that don't support
- FIQ).
- */
+static u32 gic_handle_spurious_group_0(struct gic_chip_data *gic, u32 irqstat) +{
u32 irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (!gic_data_fiq_enable(gic) || irqnr >= 1021)
return irqnr;
if (readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_IGROUP +
(irqnr / 32 * 4)) &
BIT(irqnr % 32))
return irqnr;
/* this interrupt was spurious (needs to be handled as FIQ) */
writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI);
This will NOT work, because of the note I mentioned above: "The FIQ exception will not occur anymore after gic_handle_irq read/acknowledges the FIQ by reading the GIC_CPU_INTACK register" So with this code you will say End Of Interrupt at the GIC level, without actually handling the interrupt, so you are missing an interrupt. I did the following test to confirm the missing interrupt: I have changed the periodic timer interrupt by an one-shot timer interrupt. The one-shot timer interrupt is programmed by the FIQ handler for the next FIQ interrupt. As expected: When the problem occurs, no more FIQ interrupts are generated.
Can you confirm whether your timer interrupts are configured level or edge triggered? Or whether EOIing the GIC causes them to be cleared by some other means.
From page 48 of DDI0407I_cortex_a9_mpcore_r4p1_trm.pdf: Watchdog timers, PPI(3) Each Cortex-A9 processor has its own watchdog timers that can generate interrupts, using ID30.
From page 56: PPI[0], [2],and[3]:b11 interrupt is rising-edge sensitive.
Thanks. This is clear.
If we can't get level triggering to work then we have to:
Write code to jump correctly into FIQ mode.
Modify the gic's ack_fiq() callback to automatically avoid reading intack when the workaround is deployed.
The above is why I wanted to see if we can make do with level triggering (and automatic re-triggering for interrupts such as SGIs that are cleared by EOI).
But the re-triggering introduces extra latencies and a lot of use cases of FIQ's try to avoid that.
I'm not really clear why retriggering should be significantly more expensive than the dance required to fake up entry the FIQ handler.
On the other hand retriggering allows us to avoid hacks in the FIQ handler to stop it acknowledging the interrupt. Given hacks like that won't be needed on A7/A15 this seems like a good outcome.
Anyhow I've put together a new version of my earlier patch that I think will retrigger all interrupts except SGIs (I'll look at SGIs and compatibility with non-Freescale parts only if this improved approach works).
Reported-by: Harro Haan hrhaan@gmail.com Signed-off-by: Daniel Thompson daniel.thompson@linaro.org
drivers/irqchip/irq-gic.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 73ae896..88f92e6 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -303,6 +303,33 @@ static int gic_set_wake(struct irq_data *d, unsigned int on) #define gic_set_wake NULL #endif
+/* 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 (!gic_data_fiq_enable(gic) || irqnr >= 1021)
return irqnr;
offset = irqnr / 32 * 4;
mask = 1 << (irqnr % 32);
if (readl_relaxed(dist_base + GIC_DIST_IGROUP + offset) & mask)
return irqnr;
/* 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 void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u32 irqstat, irqnr; @@ -310,8 +337,10 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
local_fiq_disable(); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
irqnr = gic_handle_spurious_group_0(gic, irqstat);
local_fiq_enable(); if (likely(irqnr > 15 && irqnr < 1021)) { irqnr = irq_find_mapping(gic->domain, irqnr);
-- 1.9.3
I just tested the above code. This approach also works as expected for edge sensitive interrupts.
On 16/07/14 18:15, Harro Haan wrote:
Anyhow I've put together a new version of my earlier patch that I think will retrigger all interrupts except SGIs (I'll look at SGIs and compatibility with non-Freescale parts only if this improved approach works).
Reported-by: Harro Haan hrhaan@gmail.com Signed-off-by: Daniel Thompson daniel.thompson@linaro.org
drivers/irqchip/irq-gic.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 73ae896..88f92e6 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -303,6 +303,33 @@ static int gic_set_wake(struct irq_data *d, unsigned int on) #define gic_set_wake NULL #endif
+/* 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 (!gic_data_fiq_enable(gic) || irqnr >= 1021)
return irqnr;
offset = irqnr / 32 * 4;
mask = 1 << (irqnr % 32);
if (readl_relaxed(dist_base + GIC_DIST_IGROUP + offset) & mask)
return irqnr;
/* 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 void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) { u32 irqstat, irqnr; @@ -310,8 +337,10 @@ static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
local_fiq_disable(); irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
irqnr = gic_handle_spurious_group_0(gic, irqstat);
local_fiq_enable(); if (likely(irqnr > 15 && irqnr < 1021)) { irqnr = irq_find_mapping(gic->domain, irqnr);
-- 1.9.3
I just tested the above code. This approach also works as expected for edge sensitive interrupts.
Awesome [and also a personal relief to get it right this time around ;-) ].
So we have two completely different confirmed-as-working workarounds!
On Tuesday, July 15, 2014 at 11:41:25 AM, Daniel Thompson wrote:
On 14/07/14 14:51, Harro Haan wrote:
On 10 July 2014 10:03, Daniel Thompson daniel.thompson@linaro.org wrote:
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches have been previously circulated as part of a large patchset mixing together ARM architecture code and driver changes (http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901 ). This patchset is dramatically cut down to include only the arch/arm code. The driver code (irqchip and tty/serial) will follow when/if the arch code is accepted.
The first two patches modify the FIQ infrastructure to allow interrupt controller drivers to register callbacks (the fiq_chip structure) to manage FIQ routings and to ACK and EOI the FIQ. This makes it possible to use FIQ in multi-platform kernels and with recent ARM interrupt controllers.
Daniel,
Thanks for the patches. I have tested the fiq and irq-gic patches on an i.MX6 (SabreSD board) for a different purpose: A FIQ timer interrupt at 1 kHz. The TWD watchdog block is used in timer mode for this (interrupt ID 30). The GIC affinity is set to CPU core 0 only for this interrupt ID.
I was surprised by the following behavior: $ cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
29: 5459 3381 3175 3029 GIC 29 twd 30: 11 0 0 0 GIC 30 fake-fiq
Once every few seconds is interrupt ID 30 handled by the regular GIC handler instead of the FIQ exception path (which causes for example a bit more latencies). The other thousands of FIQ's are handled by the FIQ exception path. The problem is also confirmed by the following stackframe of the Lauterbach tooling: fake_fiq_handler(irq = 30, ...) handle_percpu_devid_irq(irq = 30, ...) generic_handle_irq(irq = 30) handle_IRQ(irq = 30, ...) gic_handle_irq __irq_svc -->exception
Notes:
The problem will occur more often by executing the following command: $ while true; do hackbench 20; done &
Reading the GIC_CPU_INTACK register returns the interrupt ID of the
highest priority pending interrupt.
- The GIC_CPU_INTACK register (used by fiq_ack) will return spurious
interrupt ID 0x3FF when read in fake_fiq_handler, because GIC_CPU_INTACK is already read by gic_handle_irq.
- The FIQ exception will not occur anymore after gic_handle_irq
read/acknowledges the FIQ by reading the GIC_CPU_INTACK register
From the behavior above I conclude that the GIC_CPU_INTACK register is first updated before the FIQ exception is generated, so there is a small period of time that gic_handle_irq can read/acknowledge the FIQ.
Makes sense.
Avoiding this problem on GICv2 is easy (thanks to the aliased intack register) but on iMX.6 we have only a GICv1.
Yep.
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n");
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
There's also another workaround, look at [1], but it's really a perverse hack thus far (blush). What I did there is I got hinted that an L1 page table can have this NS bit set. If this bit is set for a mapping, all accesses to memory area via that mapping will be non-secure. And then, in turn, by doing a non- secure read of the INTACK register, it will not ever happen that the FIQ number will pop up in the INTACK. I only do a non-secure read of the INTACK register, all other registers of the GICv1 are read via regular secure-mode accesses.
[1] http://git.denx.de/?p=linux-denx/linux-denx- marex.git;a=shortlog;h=refs/heads/topic/socfpga/fiq-2014-07-10_01
On 15/07/14 19:45, Marek Vasut wrote:
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n");
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
There's also another workaround, look at [1], but it's really a perverse hack thus far (blush). What I did there is I got hinted that an L1 page table can have this NS bit set. If this bit is set for a mapping, all accesses to memory area via that mapping will be non-secure. And then, in turn, by doing a non- secure read of the INTACK register, it will not ever happen that the FIQ number will pop up in the INTACK. I only do a non-secure read of the INTACK register, all other registers of the GICv1 are read via regular secure-mode accesses.
I'll be looking into this approach.
It is technically a better approach than mine since it prevents the IRQ handler from ever reading a group 0 interrupt from INTACK.
Unfortunately the tentacles of this workaround reach pretty deep in the memory management code (rather than being concentrated in the GIC driver) but the improved runtime behaviour might be worth it.
On 16 July 2014 14:54, Daniel Thompson daniel.thompson@linaro.org wrote:
On 15/07/14 19:45, Marek Vasut wrote:
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n");
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
There's also another workaround, look at [1], but it's really a perverse hack thus far (blush). What I did there is I got hinted that an L1 page table can have this NS bit set. If this bit is set for a mapping, all accesses to memory area via that mapping will be non-secure. And then, in turn, by doing a non- secure read of the INTACK register, it will not ever happen that the FIQ number will pop up in the INTACK. I only do a non-secure read of the INTACK register, all other registers of the GICv1 are read via regular secure-mode accesses.
I'll be looking into this approach.
It is technically a better approach than mine since it prevents the IRQ handler from ever reading a group 0 interrupt from INTACK.
Agree, preventing the problem is better than fixing it afterwards.
Unfortunately the tentacles of this workaround reach pretty deep in the memory management code (rather than being concentrated in the GIC driver) but the improved runtime behaviour might be worth it.
I did some worst case measurements on the SabreSD while running: $ while true; do hackbench 20; done &
Use banked non-secure GIC_CPU_INTACK register for regular interrupts (patches by Marek): The FIQ handler reads the TWD_TIMER_COUNTER 2570 ticks (which is x 1000 / 498 = 5161 nsec) after FIQ interrupt ID30 is generated. The average is around 497 ticks. The minimum is around 34 ticks.
Use re-trigger approach by putting it back to pending state (latest patch by Daniel): The FIQ handler reads the TWD_TIMER_COUNTER 2678 ticks (which is x 1000 / 498 = 5378 nsec) after FIQ interrupt ID30 is generated. The average is around 563 ticks (note: almost everything is normal path) The minimum is around 34 ticks (note: this is the normal path, not the re-trigger path)
So the results are quite similar.
On 16/07/14 18:21, Harro Haan wrote:
On 16 July 2014 14:54, Daniel Thompson daniel.thompson@linaro.org wrote:
On 15/07/14 19:45, Marek Vasut wrote:
I can reduce the number of occurrences (not prevent it) by adding the following hack to irq-gic.c @@ -297,10 +309,12 @@ static asmlinkage 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 {
- while(readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_PENDING_SET)
& (1 << 30))
printk(KERN_ERR "TEMP: gic_handle_irq: wait for FIQ exception\n");
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); irqnr = irqstat & ~0x1c00;
I've made a more complete attempt to fix this. Could you test the following? (and be prepared to fuzz the line numbers)
There's also another workaround, look at [1], but it's really a perverse hack thus far (blush). What I did there is I got hinted that an L1 page table can have this NS bit set. If this bit is set for a mapping, all accesses to memory area via that mapping will be non-secure. And then, in turn, by doing a non- secure read of the INTACK register, it will not ever happen that the FIQ number will pop up in the INTACK. I only do a non-secure read of the INTACK register, all other registers of the GICv1 are read via regular secure-mode accesses.
I'll be looking into this approach.
It is technically a better approach than mine since it prevents the IRQ handler from ever reading a group 0 interrupt from INTACK.
Agree, preventing the problem is better than fixing it afterwards.
Unfortunately the tentacles of this workaround reach pretty deep in the memory management code (rather than being concentrated in the GIC driver) but the improved runtime behaviour might be worth it.
I did some worst case measurements on the SabreSD while running: $ while true; do hackbench 20; done &
Use banked non-secure GIC_CPU_INTACK register for regular interrupts (patches by Marek): The FIQ handler reads the TWD_TIMER_COUNTER 2570 ticks (which is x 1000 / 498 = 5161 nsec) after FIQ interrupt ID30 is generated. The average is around 497 ticks. The minimum is around 34 ticks.
Use re-trigger approach by putting it back to pending state (latest patch by Daniel): The FIQ handler reads the TWD_TIMER_COUNTER 2678 ticks (which is x 1000 / 498 = 5378 nsec) after FIQ interrupt ID30 is generated. The average is around 563 ticks (note: almost everything is normal path) The minimum is around 34 ticks (note: this is the normal path, not the re-trigger path)
So the results are quite similar.
This is great work.
Be aware that I'd also expect the the performance of my workaround would drop a little bit when when support for SGIs is added (mostly due to due to increased code size).
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches are seperated into three distinct groups:
1. arm specific changes; these provide multi-platform support for FIQ (including raising an IPI using FIQ to ensure effective SMP support) and extend ARM KGDB support to use the features provided.
2. irqchip changes; updates to the gic and vic drivers to provide support for routing certain interrupt sources to FIQ.
3. serial changes; driver support to allow the UART interrupt to be routed to FIQ. The already mainlined kgdb NMI infrastructure (mostly found in drivers/tty/serial/kgdb_nmi.c) will re-route the kgdb console UART's interrupt signal from IRQ to FIQ. Naturally the UART will no longer function normally and will instead be managed by kgdb using the polled I/O functions. Any character delivered to the UART causes the kgdb handler function to be called.
Tested on qemu (versatile), STiH416 (B2020 board) and Freescale i.MX6 quad (wandboard).
Changes since v8:
- Significant rework to separate the FIQ exception handler from kgdb. This allows other features typically implemented using NMI on x86 to reuse the same exception handling code (Russell King) - Reunited arch/arm and driver code into a single patch series again. - Added support for raising IPIs using FIQ (makes kgdb quiesce must more robust). - Introduced a workaround for GICv1 devices to avoid FIQs being spuriously handled as IRQs.
Changes since v7:
- Introduced ack_fiq() to complement eoi_fiq(). Without this it is not possible to meet the GIC specification (previous versions worked when tested but are unpredictable according to the specification). ack_fiq() also makes is possible for drivers for devices with multiple interrupt lines to discover the interrupt source correctly.
Changes since v6:
- Corrected off-by-one comparison in has_fiq() (Nicolas Pitre) - Rewrote the FIQ stack initialization (Nicolas Pitre). This fixes a serious data corruption bug due to bad stack mismanagement. - Introduced __fiq_abt to ensure lr_abt and spsr_abt are saved and restored if we fast-interrupt an abort (Russell King). - Significantly improved the commenting of the exception handlers. - Added a call to trace_hardirqs_on() if we clear the I bit as we exit the exception handler.
Changes since v5:
- Separated anything not strictly impacting arch/arm. - Fixed a spurious add/remove of code within the series (there was broken code in intermediate patches)
For previous changes see: http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901
Daniel Thompson (15): arm: fiq: Add callbacks to manage FIQ routings arm: fiq: Allow ACK and EOI to be passed to the intc arm: fiq: Replace default FIQ handler arm: smp: Introduce a special IPI signalled using FIQ arm: KGDB/KDB FIQ support irqchip: gic: Provide support for interrupt grouping irqchip: gic: Add support for FIQ management irqchip: gic: Remove spin locks from eoi_irq irqchip: gic: Add support for IPI FIQ irqchip: gic: Group 0 workaround. irqchip: vic: Add support for FIQ management serial: amba-pl011: Pass FIQ information to KGDB. serial: asc: Add support for KGDB's FIQ/NMI mode serial: asc: Adopt readl_/writel_relaxed() serial: imx: Add support for KGDB's FIQ/NMI mode
Dirk Behme (1): serial: imx: clean up imx_poll_get_char()
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 19 +++ arch/arm/include/asm/fiq.h | 18 +++ arch/arm/include/asm/hardirq.h | 2 +- arch/arm/include/asm/kgdb.h | 4 + arch/arm/include/asm/smp.h | 7 + arch/arm/kernel/entry-armv.S | 125 ++++++++++++++-- arch/arm/kernel/fiq.c | 139 +++++++++++++++++- arch/arm/kernel/kgdb.c | 115 ++++++++++++++- arch/arm/kernel/setup.c | 8 +- arch/arm/kernel/smp.c | 15 ++ arch/arm/mach-versatile/core.c | 2 +- drivers/irqchip/irq-gic.c | 307 +++++++++++++++++++++++++++++++++++++--- drivers/irqchip/irq-vic.c | 92 +++++++++--- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/amba-pl011.c | 94 +++++++----- drivers/tty/serial/imx.c | 88 +++++++----- drivers/tty/serial/st-asc.c | 27 +++- include/linux/irqchip/arm-vic.h | 6 +- 19 files changed, 934 insertions(+), 138 deletions(-)
-- 1.9.3
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 8 ++++ arch/arm/kernel/fiq.c | 103 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..ed44528 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -16,8 +16,14 @@ #ifndef __ASM_FIQ_H #define __ASM_FIQ_H
+#include <linux/irq.h> #include <asm/ptrace.h>
+struct fiq_chip { + void (*fiq_enable)(struct irq_data *data); + void (*fiq_disable)(struct irq_data *data); +}; + struct fiq_handler { struct fiq_handler *next; /* Name @@ -38,6 +44,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern bool has_fiq(int fiq); +extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..5d831cf 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,9 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h> +#include <linux/radix-tree.h> +#include <linux/slab.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -52,7 +55,15 @@ (unsigned)&vector_fiq_offset; \ })
+struct fiq_data { + struct fiq_chip *fiq_chip; + struct irq_data *irq_data; +}; + static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_data_tree, GFP_KERNEL); +static DEFINE_MUTEX(fiq_data_mutex);
/* Default reacquire function * - we always relinquish FIQ control @@ -127,18 +138,65 @@ void release_fiq(struct fiq_handler *f) while (current_fiq->fiq_op(current_fiq->dev_id, 0)); }
-static int fiq_start; +static struct fiq_data *lookup_fiq_data(int fiq) +{ + struct fiq_data *data; + + rcu_read_lock(); + data = radix_tree_lookup(&fiq_data_tree, fiq); + rcu_read_unlock(); + + return data; +}
void enable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_enable) + data->fiq_chip->fiq_enable(data->irq_data); + enable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + enable_irq(fiq + fiq_start); }
void disable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_disable) + data->fiq_chip->fiq_disable(data->irq_data); + disable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + disable_irq(fiq + fiq_start); }
+bool has_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) + return true; + + if (fiq_start == -1) + return false; + + return fiq >= fiq_start; +} +EXPORT_SYMBOL(has_fiq); + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -147,9 +205,50 @@ EXPORT_SYMBOL(release_fiq); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq);
+/* + * Add a mapping from a Linux irq to the fiq data. + */ +void fiq_register_mapping(int irq, struct fiq_chip *chip) +{ + struct fiq_data *fiq_data = NULL; + int res; + + /* fiq_register_mapping can't be mixed with init_FIQ */ + BUG_ON(fiq_start != -1); + + fiq_data = kmalloc(sizeof(*fiq_data), GFP_KERNEL); + if (!fiq_data) + goto err; + + fiq_data->fiq_chip = chip; + fiq_data->irq_data = irq_get_irq_data(irq); + BUG_ON(!fiq_data->irq_data); + + mutex_lock(&fiq_data_mutex); + res = radix_tree_insert(&fiq_data_tree, irq, fiq_data); + mutex_unlock(&fiq_data_mutex); + if (res) + goto err; + + return; + +err: + kfree(fiq_data); + pr_err("fiq: Cannot register mapping %d\n", irq); +} + +/* + * Set the offset between normal IRQs and their FIQ shadows. + */ void __init init_FIQ(int start) { + fiq_start = start; +} + +static int __init init_default_fiq_handler(void) +{ unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); - fiq_start = start; + return 0; } +pure_initcall(init_default_fiq_handler);
Modern ARM interrupt controllers require an ACK as interrupts are taken and an EOI on completion. The FIQ code currently does not provide any API to perform this.
This patch provides this API, implemented by adding two callbacks to the fiq_chip structure.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 9 +++++++++ arch/arm/kernel/fiq.c | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index ed44528..a25c952 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -22,6 +22,13 @@ struct fiq_chip { void (*fiq_enable)(struct irq_data *data); void (*fiq_disable)(struct irq_data *data); + + /* .fiq_ack() and .fiq_eoi() will be called from the FIQ + * handler. For this reason they must not use spin locks (or any + * other locks). + */ + int (*fiq_ack)(struct irq_data *data); + void (*fiq_eoi)(struct irq_data *data); };
struct fiq_handler { @@ -44,6 +51,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern int ack_fiq(int fiq); +extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 5d831cf..3ccaa8c 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -183,6 +183,25 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); }
+int ack_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_ack) + return data->fiq_chip->fiq_ack(data->irq_data); + + return fiq; +} + +void eoi_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_eoi) + data->fiq_chip->fiq_eoi(data->irq_data); +} +EXPORT_SYMBOL(eoi_fiq); + bool has_fiq(int fiq) { struct fiq_data *data = lookup_fiq_data(fiq);
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com --- arch/arm/include/asm/fiq.h | 1 + arch/arm/kernel/entry-armv.S | 125 +++++++++++++++++++++++++++++++++++++++---- arch/arm/kernel/fiq.c | 17 ++++++ arch/arm/kernel/setup.c | 8 ++- 4 files changed, 140 insertions(+), 11 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index a25c952..175bfed 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -54,6 +54,7 @@ extern void disable_fiq(int fiq); extern int ack_fiq(int fiq); extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); +extern int register_fiq_nmi_notifier(struct notifier_block *nb); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..ba0234b 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -79,6 +79,15 @@ #endif .endm
+ .macro fiq_handler + ldr r1, =.LChandle_fiq + mov r0, sp + adr lr, BSYM(9998f) + ldr pc, [r1] +9998: + .endm + + #ifdef CONFIG_KPROBES .section .kprobes.text,"ax",%progbits #else @@ -146,7 +155,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0 + .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) @@ -183,10 +192,35 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6}
#ifdef CONFIG_TRACE_IRQFLAGS + .if \call_trace bl trace_hardirqs_off + .endif #endif .endm
+@ +@ svc_exit_via_fiq - similar to svc_exit but switches to FIQ mode before exit +@ +@ This macro acts in a similar manner to svc_exit but switches to FIQ +@ mode to restore the final part of the register state. +@ +@ We cannot use the normal svc_exit procedure because that would +@ clobber spsr_svc (FIQ could be delivered during the first few instructions +@ of vector_swi meaning its contents have not been saved anywhere). +@ + .macro svc_exit_via_fiq, rpsr + + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + .endm + .align 5 __dabt_svc: svc_entry @@ -295,6 +329,14 @@ __pabt_svc: ENDPROC(__pabt_svc)
.align 5 +__fiq_svc: + svc_entry 0, 0 + fiq_handler + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + + .align 5 .LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -303,6 +345,39 @@ ENDPROC(__pabt_svc) #endif .LCfp: .word fp_enter +.LChandle_fiq: + .word fiq_nmi_handler + +/* + * Abort mode handlers + */ + +@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@ + .align 5 +__fiq_abt: + svc_entry 0, 0 + + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov r0, lr @ Save lr_abt + mrs r1, spsr @ Save spsr_abt, abort is now safe + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + push {r0 - r1} + + fiq_handler + + pop {r0 - r1} + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov lr, r0 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r1 @ Restore spsr_abt + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc)
/* * User mode handlers @@ -683,6 +758,17 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
+ .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + /* * Register switch for ARMv3 and ARMv4 processors * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info @@ -1118,17 +1204,36 @@ vector_addrexcptn: b vector_addrexcptn
/*============================================================================= - * Undefined FIQs + * FIQ "NMI" handler *----------------------------------------------------------------------------- - * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC - * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg. - * Basically to switch modes, we *HAVE* to clobber one register... brain - * damage alert! I don't think that we can execute any code in here in any - * other mode than FIQ... Ok you can switch to another mode, but you can't - * get out of that mode without clobbering one register. + * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 + * systems. The runtime environment for NMIs is extremely restrictive + * (NMIs can pre-empt critical sections meaning almost all locking is + * forbidden) meaning this default FIQ handling must only be used in + * circumstances where non-maskability improves robustness, such as + * watchdog or debug logic. + * + * This handler is inappropriate for high performance (fast) interrupt + * servicing and can be overridden using set_fiq_handler. */ -vector_fiq: - subs pc, lr, #4 + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 3ccaa8c..b2bd1c7 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -46,6 +46,7 @@
#include <asm/cacheflush.h> #include <asm/cp15.h> +#include <asm/exception.h> #include <asm/fiq.h> #include <asm/irq.h> #include <asm/traps.h> @@ -64,6 +65,7 @@ static unsigned long no_fiq_insn; static int fiq_start = -1; static RADIX_TREE(fiq_data_tree, GFP_KERNEL); static DEFINE_MUTEX(fiq_data_mutex); +static ATOMIC_NOTIFIER_HEAD(fiq_nmi_chain);
/* Default reacquire function * - we always relinquish FIQ control @@ -216,6 +218,21 @@ bool has_fiq(int fiq) } EXPORT_SYMBOL(has_fiq);
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&fiq_nmi_chain, nb); +} + +asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + nmi_enter(); + atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL); + nmi_exit(); + set_irq_regs(old_regs); +} + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3]; + u32 fiq[3]; } ____cacheline_aligned;
#ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t" - "msr cpsr_c, %7" + "msr cpsr_c, %7\n\t" + "add r14, %0, %8\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE), @@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])), + PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14"); #endif
Cross CPU signalling based on FIQ is especially useful for kgdb since it makes stopping all the CPUs during breakpointing more robust (some of the other architectures already roundup the CPUs using NMIs).
The approach taken provides infrastructure that can be called (or not) by the driver's FIQ handler depending upon it requirements. In other words nothing is added here that prevents the driver from accessing "bare metal" performance.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/hardirq.h | 2 +- arch/arm/include/asm/smp.h | 7 +++++++ arch/arm/kernel/smp.c | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/arch/arm/include/asm/hardirq.h b/arch/arm/include/asm/hardirq.h index fe3ea77..5df33e3 100644 --- a/arch/arm/include/asm/hardirq.h +++ b/arch/arm/include/asm/hardirq.h @@ -5,7 +5,7 @@ #include <linux/threads.h> #include <asm/irq.h>
-#define NR_IPI 8 +#define NR_IPI 9
typedef struct { unsigned int __softirq_pending; diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 2ec765c..215c927 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -20,6 +20,9 @@
#define raw_smp_processor_id() (current_thread_info()->cpu)
+/* bitmap of IPIs that must be signalled using FIQ */ +#define SMP_IPI_FIQ_MASK 0x0100 + struct seq_file;
/* @@ -87,6 +90,10 @@ extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
extern int register_ipi_completion(struct completion *completion, int cpu);
+#ifdef CONFIG_FIQ +extern void send_fiq_ipi_mask(const struct cpumask *); +#endif + struct smp_operations { #ifdef CONFIG_SMP /* diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 9388a3d..d386c32 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -72,6 +72,7 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_FIQ, };
static DECLARE_COMPLETION(cpu_running); @@ -451,6 +452,7 @@ static const char *ipi_types[NR_IPI] __tracepoint_string = { S(IPI_CPU_STOP, "CPU stop interrupts"), S(IPI_IRQ_WORK, "IRQ work interrupts"), S(IPI_COMPLETION, "completion interrupts"), + S(IPI_FIQ, "FIQ interrupts"), };
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr) @@ -552,6 +554,13 @@ static void ipi_complete(unsigned int cpu) complete(per_cpu(cpu_completion, cpu)); }
+#ifdef CONFIG_FIQ +void send_fiq_ipi_mask(const struct cpumask *mask) +{ + smp_cross_call(mask, IPI_FIQ); +} +#endif + /* * Main handler for inter-processor interrupts */ @@ -618,6 +627,12 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break;
+#ifdef CONFIG_FIQ + case IPI_FIQ: + pr_crit("CPU%u: IPI FIQ delivered via IRQ vector\n", cpu); + break; +#endif + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr);
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of comments and other small fragments still survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Omar Sandoval osandov@osandov.com --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 19 ++++++++ arch/arm/include/asm/kgdb.h | 4 ++ arch/arm/kernel/kgdb.c | 115 +++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 134 insertions(+), 6 deletions(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index c49a775..e6380b3 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -305,6 +305,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -352,6 +353,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index f304694..a99f8d2 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,25 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be enabled + by setting the console to ttyNMI0 (and choosing the underlying + serial port using kgdboc) + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..6563da0 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,8 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern int kgdb_register_fiq(unsigned int fiq); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/kgdb.c b/arch/arm/kernel/kgdb.c index a74b53c..928c205 100644 --- a/arch/arm/kernel/kgdb.c +++ b/arch/arm/kernel/kgdb.c @@ -12,8 +12,11 @@ #include <linux/irq.h> #include <linux/kdebug.h> #include <linux/kgdb.h> +#include <asm/fiq.h> #include <asm/traps.h>
+static unsigned int kgdb_fiq; + struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = { { "r0", 4, offsetof(struct pt_regs, ARM_r0)}, @@ -175,14 +178,26 @@ static struct undef_hook kgdb_compiled_brkpt_hook = {
static void kgdb_call_nmi_hook(void *ignored) { - kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs()); + kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs()); }
void kgdb_roundup_cpus(unsigned long flags) { - local_irq_enable(); - smp_call_function(kgdb_call_nmi_hook, NULL, 0); - local_irq_disable(); +#if defined CONFIG_KGDB_FIQ && defined CONFIG_SMP + struct cpumask mask; + + if (in_nmi()) { + cpumask_copy(&mask, cpu_online_mask); + cpumask_clear_cpu(raw_smp_processor_id(), &mask); + if (!cpumask_empty(&mask)) + send_fiq_ipi_mask(&mask); + return; + } +#endif + + local_irq_enable(); + smp_call_function(kgdb_call_nmi_hook, NULL, 0); + local_irq_disable(); }
static int __kgdb_notify(struct die_args *args, unsigned long cmd) @@ -244,6 +259,41 @@ void kgdb_arch_exit(void) unregister_die_notifier(&kgdb_notifier); }
+/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + /* warn if ttyNMI0 is active but brings no benefit */ + if (WARN_ONCE(!kgdb_fiq, "kgdb: no FIQ available\n")) + return; + +#ifdef CONFIG_KGDB_FIQ + static atomic_t cnt; + int ret; + + ret = atomic_add_return(on ? 1 : -1, &cnt); + + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + if (WARN_ON(ret > 1 && on)) + return; + + if (ret > 0) + enable_fiq(kgdb_fiq); + else + disable_fiq(kgdb_fiq); +#endif +} + + /* * Register our undef instruction hooks with ARM undef core. * We regsiter a hook specifically looking for the KGB break inst @@ -252,8 +302,61 @@ void kgdb_arch_exit(void) */ struct kgdb_arch arch_kgdb_ops = { #ifndef __ARMEB__ - .gdb_bpt_instr = {0xfe, 0xde, 0xff, 0xe7} + .gdb_bpt_instr = {0xfe, 0xde, 0xff, 0xe7}, #else /* ! __ARMEB__ */ - .gdb_bpt_instr = {0xe7, 0xff, 0xde, 0xfe} + .gdb_bpt_instr = {0xe7, 0xff, 0xde, 0xfe}, #endif + .enable_nmi = kgdb_fiq_enable_nmi }; + +#ifdef CONFIG_KGDB_FIQ +static int kgdb_handle_fiq(struct notifier_block *nb, unsigned long arg, + void *data) +{ + struct pt_regs *regs = (void *) arg; + int actual; + + if (!kgdb_nmicallback(raw_smp_processor_id(), regs)) + return NOTIFY_OK; + + actual = ack_fiq(kgdb_fiq); + WARN_ON(actual != kgdb_fiq); + + /* there's no harm in doing this regardless of the above WARN_ON() */ + if (kgdb_nmi_poll_knock()) + kgdb_handle_exception(1, 0, 0, regs); + + eoi_fiq(actual); + + return NOTIFY_OK; +} + +static struct notifier_block kgdb_fiq_notifier = { + .notifier_call = kgdb_handle_fiq, + .priority = 100, +}; + +int kgdb_register_fiq(unsigned int fiq) +{ + static struct fiq_handler kgdb_fiq_desc = { .name = "kgdb", }; + int err; + + if (!has_fiq(fiq)) { + pr_warn( + "%s: Cannot register %u (no FIQ with this number)\n", + __func__, fiq); + return -ENODEV; + } + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + kgdb_fiq = fiq; + register_fiq_nmi_notifier(&kgdb_fiq_notifier); + + return 0; +} +#endif
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches are seperated into three distinct groups:
1. arm specific changes; these provide multi-platform support for FIQ (including raising an IPI using FIQ to ensure effective SMP support) and extend ARM KGDB support to use the features provided.
2. irqchip changes; updates to the gic and vic drivers to provide support for routing certain interrupt sources to FIQ.
3. serial changes; driver support to allow the UART interrupt to be routed to FIQ. The already mainlined kgdb NMI infrastructure (mostly found in drivers/tty/serial/kgdb_nmi.c) will re-route the kgdb console UART's interrupt signal from IRQ to FIQ. Naturally the UART will no longer function normally and will instead be managed by kgdb using the polled I/O functions. Any character delivered to the UART causes the kgdb handler function to be called.
Tested on qemu (versatile), STiH416 (B2020 board) and Freescale i.MX6 quad (wandboard).
Changes since v9:
- Split PL011 code movement into a seperate patch (Peter Hurley) - Use container_of() to convert pointers to uart_amba_port (Peter Hurley) - Clean up redundant code from pl011_poll_init (Peter Hurley) - Call do_unexp_fiq() if we receive a FIQ and CONFIG_FIQ is not set. - Ensure irq-gic.c does not call FIQ functions unless CONFIG_FIQ is set. - Introduced patch to avoid ttyNMI0 being enabled by default (replaces architecture dependant enable/disable logic). - Fixed bug in kgdb_fiq_enable_nmi() that causes SysRq-g (non-NMI debugging) to wedge the debugger.
Changes since v8:
- Significant rework to separate the FIQ exception handler from kgdb. This allows other features typically implemented using NMI on x86 to reuse the same exception handling code (Russell King) - Reunited arch/arm and driver code into a single patch series again. - Added support for raising IPIs using FIQ (makes kgdb quiesce must more robust). - Introduced a workaround for GICv1 devices to avoid FIQs being spuriously handled as IRQs.
Changes since v7:
- Introduced ack_fiq() to complement eoi_fiq(). Without this it is not possible to meet the GIC specification (previous versions worked when tested but are unpredictable according to the specification). ack_fiq() also makes is possible for drivers for devices with multiple interrupt lines to discover the interrupt source correctly.
Changes since v6:
- Corrected off-by-one comparison in has_fiq() (Nicolas Pitre) - Rewrote the FIQ stack initialization (Nicolas Pitre). This fixes a serious data corruption bug due to bad stack mismanagement. - Introduced __fiq_abt to ensure lr_abt and spsr_abt are saved and restored if we fast-interrupt an abort (Russell King). - Significantly improved the commenting of the exception handlers. - Added a call to trace_hardirqs_on() if we clear the I bit as we exit the exception handler.
Changes since v5:
- Separated anything not strictly impacting arch/arm. - Fixed a spurious add/remove of code within the series (there was broken code in intermediate patches)
For previous changes see: http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901
Daniel Thompson (18): arm: fiq: Add callbacks to manage FIQ routings arm: fiq: Allow ACK and EOI to be passed to the intc arm: fiq: Replace default FIQ handler arm: smp: Introduce a special IPI signalled using FIQ arm: KGDB/KDB FIQ support irqchip: gic: Provide support for interrupt grouping irqchip: gic: Add support for FIQ management irqchip: gic: Remove spin locks from eoi_irq irqchip: gic: Add support for IPI FIQ irqchip: gic: Group 0 workaround. irqchip: vic: Add support for FIQ management serial: kgdb_nmi: No CON_ENABLED by default serial: amba-pl011: Use container_of() to get uart_amba_port serial: amba-pl011: Move pl011_hwinit() serial: amba-pl011: Pass FIQ information to KGDB. serial: asc: Add support for KGDB's FIQ/NMI mode serial: asc: Adopt readl_/writel_relaxed() serial: imx: Add support for KGDB's FIQ/NMI mode
Dirk Behme (1): serial: imx: clean up imx_poll_get_char()
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 19 +++ arch/arm/include/asm/fiq.h | 18 +++ arch/arm/include/asm/hardirq.h | 2 +- arch/arm/include/asm/kgdb.h | 4 + arch/arm/include/asm/smp.h | 7 + arch/arm/kernel/entry-armv.S | 129 +++++++++++++++-- arch/arm/kernel/fiq.c | 139 +++++++++++++++++- arch/arm/kernel/kgdb.c | 117 ++++++++++++++- arch/arm/kernel/setup.c | 8 +- arch/arm/kernel/smp.c | 15 ++ arch/arm/mach-versatile/core.c | 2 +- drivers/irqchip/irq-gic.c | 309 +++++++++++++++++++++++++++++++++++++--- drivers/irqchip/irq-vic.c | 92 +++++++++--- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/amba-pl011.c | 143 +++++++++++-------- drivers/tty/serial/imx.c | 88 +++++++----- drivers/tty/serial/kgdb_nmi.c | 5 +- drivers/tty/serial/st-asc.c | 27 +++- include/linux/irqchip/arm-vic.h | 6 +- 20 files changed, 977 insertions(+), 157 deletions(-)
-- 1.9.3
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 8 ++++ arch/arm/kernel/fiq.c | 103 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..ed44528 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -16,8 +16,14 @@ #ifndef __ASM_FIQ_H #define __ASM_FIQ_H
+#include <linux/irq.h> #include <asm/ptrace.h>
+struct fiq_chip { + void (*fiq_enable)(struct irq_data *data); + void (*fiq_disable)(struct irq_data *data); +}; + struct fiq_handler { struct fiq_handler *next; /* Name @@ -38,6 +44,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern bool has_fiq(int fiq); +extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..5d831cf 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,9 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h> +#include <linux/radix-tree.h> +#include <linux/slab.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -52,7 +55,15 @@ (unsigned)&vector_fiq_offset; \ })
+struct fiq_data { + struct fiq_chip *fiq_chip; + struct irq_data *irq_data; +}; + static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_data_tree, GFP_KERNEL); +static DEFINE_MUTEX(fiq_data_mutex);
/* Default reacquire function * - we always relinquish FIQ control @@ -127,18 +138,65 @@ void release_fiq(struct fiq_handler *f) while (current_fiq->fiq_op(current_fiq->dev_id, 0)); }
-static int fiq_start; +static struct fiq_data *lookup_fiq_data(int fiq) +{ + struct fiq_data *data; + + rcu_read_lock(); + data = radix_tree_lookup(&fiq_data_tree, fiq); + rcu_read_unlock(); + + return data; +}
void enable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_enable) + data->fiq_chip->fiq_enable(data->irq_data); + enable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + enable_irq(fiq + fiq_start); }
void disable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_disable) + data->fiq_chip->fiq_disable(data->irq_data); + disable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + disable_irq(fiq + fiq_start); }
+bool has_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) + return true; + + if (fiq_start == -1) + return false; + + return fiq >= fiq_start; +} +EXPORT_SYMBOL(has_fiq); + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -147,9 +205,50 @@ EXPORT_SYMBOL(release_fiq); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq);
+/* + * Add a mapping from a Linux irq to the fiq data. + */ +void fiq_register_mapping(int irq, struct fiq_chip *chip) +{ + struct fiq_data *fiq_data = NULL; + int res; + + /* fiq_register_mapping can't be mixed with init_FIQ */ + BUG_ON(fiq_start != -1); + + fiq_data = kmalloc(sizeof(*fiq_data), GFP_KERNEL); + if (!fiq_data) + goto err; + + fiq_data->fiq_chip = chip; + fiq_data->irq_data = irq_get_irq_data(irq); + BUG_ON(!fiq_data->irq_data); + + mutex_lock(&fiq_data_mutex); + res = radix_tree_insert(&fiq_data_tree, irq, fiq_data); + mutex_unlock(&fiq_data_mutex); + if (res) + goto err; + + return; + +err: + kfree(fiq_data); + pr_err("fiq: Cannot register mapping %d\n", irq); +} + +/* + * Set the offset between normal IRQs and their FIQ shadows. + */ void __init init_FIQ(int start) { + fiq_start = start; +} + +static int __init init_default_fiq_handler(void) +{ unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); - fiq_start = start; + return 0; } +pure_initcall(init_default_fiq_handler);
Modern ARM interrupt controllers require an ACK as interrupts are taken and an EOI on completion. The FIQ code currently does not provide any API to perform this.
This patch provides this API, implemented by adding two callbacks to the fiq_chip structure.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 9 +++++++++ arch/arm/kernel/fiq.c | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index ed44528..a25c952 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -22,6 +22,13 @@ struct fiq_chip { void (*fiq_enable)(struct irq_data *data); void (*fiq_disable)(struct irq_data *data); + + /* .fiq_ack() and .fiq_eoi() will be called from the FIQ + * handler. For this reason they must not use spin locks (or any + * other locks). + */ + int (*fiq_ack)(struct irq_data *data); + void (*fiq_eoi)(struct irq_data *data); };
struct fiq_handler { @@ -44,6 +51,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern int ack_fiq(int fiq); +extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 5d831cf..3ccaa8c 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -183,6 +183,25 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); }
+int ack_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_ack) + return data->fiq_chip->fiq_ack(data->irq_data); + + return fiq; +} + +void eoi_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_eoi) + data->fiq_chip->fiq_eoi(data->irq_data); +} +EXPORT_SYMBOL(eoi_fiq); + bool has_fiq(int fiq) { struct fiq_data *data = lookup_fiq_data(fiq);
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com --- arch/arm/include/asm/fiq.h | 1 + arch/arm/kernel/entry-armv.S | 129 +++++++++++++++++++++++++++++++++++++++---- arch/arm/kernel/fiq.c | 17 ++++++ arch/arm/kernel/setup.c | 8 ++- 4 files changed, 144 insertions(+), 11 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index a25c952..175bfed 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -54,6 +54,7 @@ extern void disable_fiq(int fiq); extern int ack_fiq(int fiq); extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); +extern int register_fiq_nmi_notifier(struct notifier_block *nb); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..ef64333 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -79,6 +79,15 @@ #endif .endm
+ .macro fiq_handler + ldr r1, =.LChandle_fiq + mov r0, sp + adr lr, BSYM(9998f) + ldr pc, [r1] +9998: + .endm + + #ifdef CONFIG_KPROBES .section .kprobes.text,"ax",%progbits #else @@ -146,7 +155,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0 + .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) @@ -183,10 +192,35 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6}
#ifdef CONFIG_TRACE_IRQFLAGS + .if \call_trace bl trace_hardirqs_off + .endif #endif .endm
+@ +@ svc_exit_via_fiq - similar to svc_exit but switches to FIQ mode before exit +@ +@ This macro acts in a similar manner to svc_exit but switches to FIQ +@ mode to restore the final part of the register state. +@ +@ We cannot use the normal svc_exit procedure because that would +@ clobber spsr_svc (FIQ could be delivered during the first few instructions +@ of vector_swi meaning its contents have not been saved anywhere). +@ + .macro svc_exit_via_fiq, rpsr + + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + .endm + .align 5 __dabt_svc: svc_entry @@ -295,6 +329,14 @@ __pabt_svc: ENDPROC(__pabt_svc)
.align 5 +__fiq_svc: + svc_entry 0, 0 + fiq_handler + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + + .align 5 .LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -303,6 +345,43 @@ ENDPROC(__pabt_svc) #endif .LCfp: .word fp_enter +.LChandle_fiq: +#ifdef CONFIG_FIQ + .word fiq_nmi_handler +#else + .word do_unexp_fiq +#endif + +/* + * Abort mode handlers + */ + +@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@ + .align 5 +__fiq_abt: + svc_entry 0, 0 + + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov r0, lr @ Save lr_abt + mrs r1, spsr @ Save spsr_abt, abort is now safe + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + push {r0 - r1} + + fiq_handler + + pop {r0 - r1} + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov lr, r0 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r1 @ Restore spsr_abt + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc)
/* * User mode handlers @@ -683,6 +762,17 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
+ .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + /* * Register switch for ARMv3 and ARMv4 processors * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info @@ -1118,17 +1208,36 @@ vector_addrexcptn: b vector_addrexcptn
/*============================================================================= - * Undefined FIQs + * FIQ "NMI" handler *----------------------------------------------------------------------------- - * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC - * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg. - * Basically to switch modes, we *HAVE* to clobber one register... brain - * damage alert! I don't think that we can execute any code in here in any - * other mode than FIQ... Ok you can switch to another mode, but you can't - * get out of that mode without clobbering one register. + * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 + * systems. The runtime environment for NMIs is extremely restrictive + * (NMIs can pre-empt critical sections meaning almost all locking is + * forbidden) meaning this default FIQ handling must only be used in + * circumstances where non-maskability improves robustness, such as + * watchdog or debug logic. + * + * This handler is inappropriate for high performance (fast) interrupt + * servicing and can be overridden using set_fiq_handler. */ -vector_fiq: - subs pc, lr, #4 + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 3ccaa8c..b2bd1c7 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -46,6 +46,7 @@
#include <asm/cacheflush.h> #include <asm/cp15.h> +#include <asm/exception.h> #include <asm/fiq.h> #include <asm/irq.h> #include <asm/traps.h> @@ -64,6 +65,7 @@ static unsigned long no_fiq_insn; static int fiq_start = -1; static RADIX_TREE(fiq_data_tree, GFP_KERNEL); static DEFINE_MUTEX(fiq_data_mutex); +static ATOMIC_NOTIFIER_HEAD(fiq_nmi_chain);
/* Default reacquire function * - we always relinquish FIQ control @@ -216,6 +218,21 @@ bool has_fiq(int fiq) } EXPORT_SYMBOL(has_fiq);
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&fiq_nmi_chain, nb); +} + +asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + nmi_enter(); + atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL); + nmi_exit(); + set_irq_regs(old_regs); +} + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3]; + u32 fiq[3]; } ____cacheline_aligned;
#ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t" - "msr cpsr_c, %7" + "msr cpsr_c, %7\n\t" + "add r14, %0, %8\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE), @@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])), + PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14"); #endif
On Tue, Aug 19, 2014 at 05:45:53PM +0100, Daniel Thompson wrote:
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
+}
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- nmi_exit();
- set_irq_regs(old_regs);
+}
Really not happy with this. What happens if a FIQ occurs while we're inside register_fiq_nmi_notifier() - more specifically inside atomic_notifier_chain_register() ?
This is how easy it is to end up with a deadlock-able situation with FIQs, and it is why I said:
| > 2. Have default trap handler call an RCU notifier chain to allow it to | > hook up with "normal" code without any hard coding (kgdb, IPI | > handling, etc) | | Maybe... that sounds like it opens up FIQ for general purpose use which | is something I want to avoid - I've little motivation to ensure that | everyone plays by the rules. Given the choice, I'd rather maintain our | present stance that using FIQs is hard and requires a lot of thought.
You've just proven my point.
So, what I want is to make FIQs hard to use. No notifier chain at all please, people can't be trusted to know all the details (even if they do know the details, it's just been proven that it's incredibly difficult to do it right.)
What I instead want to see is that users get added to the above code fragment with #ifdef's around the calls - that way ensuring that people have to modify this file, and therefore /should/ be copying me with their patches. That means less chance for someone to sneak a change in by merely calling some register function without first having to copy this mailing list.
On 19/08/14 18:37, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 05:45:53PM +0100, Daniel Thompson wrote:
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
+}
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- nmi_exit();
- set_irq_regs(old_regs);
+}
Really not happy with this. What happens if a FIQ occurs while we're inside register_fiq_nmi_notifier() - more specifically inside atomic_notifier_chain_register() ?
Should depend on which side of the rcu update we're on.
This is how easy it is to end up with a deadlock-able situation with FIQs, and it is why I said:
| > 2. Have default trap handler call an RCU notifier chain to allow it to | > hook up with "normal" code without any hard coding (kgdb, IPI | > handling, etc) | | Maybe... that sounds like it opens up FIQ for general purpose use which | is something I want to avoid - I've little motivation to ensure that | everyone plays by the rules. Given the choice, I'd rather maintain our | present stance that using FIQs is hard and requires a lot of thought.
You've just proven my point.
So, what I want is to make FIQs hard to use. No notifier chain at all please, people can't be trusted to know all the details (even if they do know the details, it's just been proven that it's incredibly difficult to do it right.)
For what its worth the reason I stuck to the notifier idea was that there has to be some dynamic registration; the gic is registering something to clear IPIs. Once I had dynamic registration notifiers felt most elegant.
However, the dynamic registration requirement is in fact very basic since and can be registered by function pointer since only irqchip instance is responsible for IPIs. It might even be possible to hardcode direct calls into the gic driver and have the gic driver maintain a flag.
What I instead want to see is that users get added to the above code fragment with #ifdef's around the calls - that way ensuring that people have to modify this file, and therefore /should/ be copying me with their patches. That means less chance for someone to sneak a change in by merely calling some register function without first having to copy this mailing list.
I can do this.
Note that I'm about to go and live in a field for a week so there will be a bit of a delay since the field interferes substantially with hacking time.
On Tue, Aug 19, 2014 at 07:12:07PM +0100, Daniel Thompson wrote:
On 19/08/14 18:37, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 05:45:53PM +0100, Daniel Thompson wrote:
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
+}
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- nmi_exit();
- set_irq_regs(old_regs);
+}
Really not happy with this. What happens if a FIQ occurs while we're inside register_fiq_nmi_notifier() - more specifically inside atomic_notifier_chain_register() ?
Should depend on which side of the rcu update we're on.
I just asked Paul McKenney, our RCU expert... essentially, yes, RCU stuff itself is safe in this context. However, RCU stuff can call into lockdep if lockdep is configured, and there are questions over lockdep.
There's some things which can be done to reduce the lockdep exposure to it, such as ensuring that rcu_read_lock() is first called outside of FIQ context.
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
On Thu, Aug 28, 2014 at 04:01:12PM +0100, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 07:12:07PM +0100, Daniel Thompson wrote:
On 19/08/14 18:37, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 05:45:53PM +0100, Daniel Thompson wrote:
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
+}
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- nmi_exit();
- set_irq_regs(old_regs);
+}
Really not happy with this. What happens if a FIQ occurs while we're inside register_fiq_nmi_notifier() - more specifically inside atomic_notifier_chain_register() ?
Should depend on which side of the rcu update we're on.
I just asked Paul McKenney, our RCU expert... essentially, yes, RCU stuff itself is safe in this context. However, RCU stuff can call into lockdep if lockdep is configured, and there are questions over lockdep.
There's some things which can be done to reduce the lockdep exposure to it, such as ensuring that rcu_read_lock() is first called outside of FIQ context.
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
In the worst case, it would be possible to create a parallel notifier that was intended for use from NMI. There would be no need for rcu_read_lock() in that case, we would instead be using RCU-sched, for which NMI handlers are automatically RCU-sched read-side critical sections. Instead of synchronize_rcu(), this NMI version would use synchronize_sched().
But if lockdep works from NMI, then the current notifiers would work just fine.
Thanx, Paul
On 28/08/14 16:01, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 07:12:07PM +0100, Daniel Thompson wrote:
On 19/08/14 18:37, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 05:45:53PM +0100, Daniel Thompson wrote:
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
+}
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- nmi_exit();
- set_irq_regs(old_regs);
+}
Really not happy with this. What happens if a FIQ occurs while we're inside register_fiq_nmi_notifier() - more specifically inside atomic_notifier_chain_register() ?
Should depend on which side of the rcu update we're on.
I just asked Paul McKenney, our RCU expert... essentially, yes, RCU stuff itself is safe in this context. However, RCU stuff can call into lockdep if lockdep is configured, and there are questions over lockdep.
Thanks for following this up.
I originally formed the opinion RCU was safe from FIQ because it is also used to manage the NMI notification handlers for x86 (register_nmi_handler) and I understood the runtime constraints on FIQ to be very similar.
Note that x86 manages the notifiers itself so it uses list_for_each_entry_rcu() rather atomic_notifier_call_chain() but nevertheless I think this boils down to the same thing w.r.t. safety concerns.
There's some things which can be done to reduce the lockdep exposure to it, such as ensuring that rcu_read_lock() is first called outside of FIQ context.
lockdep is automatically disabled by calling nmi_enter() so all the lockdep calls should end up following the early exit path based on current->lockdep_recursion.
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
On Thu, Aug 28, 2014 at 04:54:25PM +0100, Daniel Thompson wrote:
On 28/08/14 16:01, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 07:12:07PM +0100, Daniel Thompson wrote:
On 19/08/14 18:37, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 05:45:53PM +0100, Daniel Thompson wrote:
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
+}
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- nmi_exit();
- set_irq_regs(old_regs);
+}
Really not happy with this. What happens if a FIQ occurs while we're inside register_fiq_nmi_notifier() - more specifically inside atomic_notifier_chain_register() ?
Should depend on which side of the rcu update we're on.
I just asked Paul McKenney, our RCU expert... essentially, yes, RCU stuff itself is safe in this context. However, RCU stuff can call into lockdep if lockdep is configured, and there are questions over lockdep.
Thanks for following this up.
I originally formed the opinion RCU was safe from FIQ because it is also used to manage the NMI notification handlers for x86 (register_nmi_handler) and I understood the runtime constraints on FIQ to be very similar.
Note that x86 manages the notifiers itself so it uses list_for_each_entry_rcu() rather atomic_notifier_call_chain() but nevertheless I think this boils down to the same thing w.r.t. safety concerns.
There's some things which can be done to reduce the lockdep exposure to it, such as ensuring that rcu_read_lock() is first called outside of FIQ context.
lockdep is automatically disabled by calling nmi_enter() so all the lockdep calls should end up following the early exit path based on current->lockdep_recursion.
Ah, that was what I was missing. Then the notification should be safe from NMI, so have at it! ;-)
Thanx, Paul
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
On 28/08/14 17:15, Paul E. McKenney wrote:
On Thu, Aug 28, 2014 at 04:54:25PM +0100, Daniel Thompson wrote:
On 28/08/14 16:01, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 07:12:07PM +0100, Daniel Thompson wrote:
On 19/08/14 18:37, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 05:45:53PM +0100, Daniel Thompson wrote:
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
+}
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- nmi_exit();
- set_irq_regs(old_regs);
+}
Really not happy with this. What happens if a FIQ occurs while we're inside register_fiq_nmi_notifier() - more specifically inside atomic_notifier_chain_register() ?
Should depend on which side of the rcu update we're on.
I just asked Paul McKenney, our RCU expert... essentially, yes, RCU stuff itself is safe in this context. However, RCU stuff can call into lockdep if lockdep is configured, and there are questions over lockdep.
Thanks for following this up.
I originally formed the opinion RCU was safe from FIQ because it is also used to manage the NMI notification handlers for x86 (register_nmi_handler) and I understood the runtime constraints on FIQ to be very similar.
Note that x86 manages the notifiers itself so it uses list_for_each_entry_rcu() rather atomic_notifier_call_chain() but nevertheless I think this boils down to the same thing w.r.t. safety concerns.
There's some things which can be done to reduce the lockdep exposure to it, such as ensuring that rcu_read_lock() is first called outside of FIQ context.
lockdep is automatically disabled by calling nmi_enter() so all the lockdep calls should end up following the early exit path based on current->lockdep_recursion.
Ah, that was what I was missing. Then the notification should be safe from NMI, so have at it! ;-)
Thanx, Paul
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
Having esablished (above) that RCU usage is safe from FIQ I have been working on the assumption that your feeling regarding unreviewed code is sufficient on its own to avoid using notifiers (and also to avoid a list of function pointers like on x86).
Therefore I have made these changes I've produced a before/after patch to show the impact of this. I will merge this into the FIQ patchset shortly.
Personally I still favour using notifiers and think the coupling below is excessive. Nevertheless I've run a couple of basic tests on the code below and it works fine.
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index 175bfed..a25c952 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -54,7 +54,6 @@ extern void disable_fiq(int fiq); extern int ack_fiq(int fiq); extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); -extern int register_fiq_nmi_notifier(struct notifier_block *nb); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 6563da0..cb5ccd6 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -51,6 +51,7 @@ extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
extern int kgdb_register_fiq(unsigned int fiq); +extern void kgdb_handle_fiq(struct pt_regs *regs);
#endif /* !__ASSEMBLY__ */
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index b2bd1c7..7422b58 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -43,12 +43,14 @@ #include <linux/irq.h> #include <linux/radix-tree.h> #include <linux/slab.h> +#include <linux/irqchip/arm-gic.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> #include <asm/exception.h> #include <asm/fiq.h> #include <asm/irq.h> +#include <asm/kgdb.h> #include <asm/traps.h>
#define FIQ_OFFSET ({ \ @@ -65,7 +67,6 @@ static unsigned long no_fiq_insn; static int fiq_start = -1; static RADIX_TREE(fiq_data_tree, GFP_KERNEL); static DEFINE_MUTEX(fiq_data_mutex); -static ATOMIC_NOTIFIER_HEAD(fiq_nmi_chain);
/* Default reacquire function * - we always relinquish FIQ control @@ -218,17 +219,19 @@ bool has_fiq(int fiq) } EXPORT_SYMBOL(has_fiq);
-int register_fiq_nmi_notifier(struct notifier_block *nb) -{ - return atomic_notifier_chain_register(&fiq_nmi_chain, nb); -} - asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs);
nmi_enter(); - atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL); + + /* these callbacks deliberately avoid using a notifier chain in + * order to ensure code review happens (drivers cannot "secretly" + * employ FIQ without modifying this chain of calls). + */ + kgdb_handle_fiq(regs); + gic_handle_fiq_ipi(); + nmi_exit(); set_irq_regs(old_regs); } diff --git a/arch/arm/kernel/kgdb.c b/arch/arm/kernel/kgdb.c index b77b885..630a3ef 100644 --- a/arch/arm/kernel/kgdb.c +++ b/arch/arm/kernel/kgdb.c @@ -312,12 +312,13 @@ struct kgdb_arch arch_kgdb_ops = { };
#ifdef CONFIG_KGDB_FIQ -static int kgdb_handle_fiq(struct notifier_block *nb, unsigned long arg, - void *data) +void kgdb_handle_fiq(struct pt_regs *regs) { - struct pt_regs *regs = (void *) arg; int actual;
+ if (!kgdb_fiq) + return; + if (!kgdb_nmicallback(raw_smp_processor_id(), regs)) return NOTIFY_OK;
@@ -333,11 +334,6 @@ static int kgdb_handle_fiq(struct notifier_block *nb, unsigned long arg, return NOTIFY_OK; }
-static struct notifier_block kgdb_fiq_notifier = { - .notifier_call = kgdb_handle_fiq, - .priority = 100, -}; - int kgdb_register_fiq(unsigned int fiq) { static struct fiq_handler kgdb_fiq_desc = { .name = "kgdb", }; @@ -357,7 +353,6 @@ int kgdb_register_fiq(unsigned int fiq) }
kgdb_fiq = fiq; - register_fiq_nmi_notifier(&kgdb_fiq_notifier);
return 0; } diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index bda5a91..8821160 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -502,13 +502,17 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, /* * Fully acknowledge (both ack and eoi) a FIQ-based IPI */ -static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs, - void *data) +void gic_handle_fiq_ipi(void) { struct gic_chip_data *gic = &gic_data[0]; - void __iomem *cpu_base = gic_data_cpu_base(gic); + void __iomem *cpu_base; unsigned long irqstat, irqnr;
+ if (!gic || !gic->fiq_enable) + return; + + cpu_base = gic_data_cpu_base(gic); + if (WARN_ON(!in_nmi())) return NOTIFY_BAD;
@@ -525,13 +529,6 @@ static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs,
return NOTIFY_OK; } - -/* - * Notifier to ensure IPI FIQ is acknowledged correctly. - */ -static struct notifier_block gic_fiq_ipi_notifier = { - .notifier_call = gic_handle_fiq_ipi, -}; #else /* CONFIG_FIQ */ static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, int group) {} @@ -1250,10 +1247,6 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); -#ifdef CONFIG_FIQ - if (gic_data_fiq_enable(gic)) - register_fiq_nmi_notifier(&gic_fiq_ipi_notifier); -#endif #endif set_handle_irq(gic_handle_irq); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 45e2d8c..52a5676 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -101,5 +101,8 @@ static inline void __init register_routable_domain_ops { gic_routable_irq_domain_ops = ops; } + +void gic_handle_fiq_ipi(void); + #endif /* __ASSEMBLY */ #endif
On Tue, Sep 02, 2014 at 12:03:11PM +0100, Daniel Thompson wrote:
On 28/08/14 17:15, Paul E. McKenney wrote:
On Thu, Aug 28, 2014 at 04:54:25PM +0100, Daniel Thompson wrote:
On 28/08/14 16:01, Russell King - ARM Linux wrote:
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
Having esablished (above) that RCU usage is safe from FIQ I have been working on the assumption that your feeling regarding unreviewed code is sufficient on its own to avoid using notifiers (and also to avoid a list of function pointers like on x86).
I'm assuming that "your" above refers to Paul here, since you addressed your message To: Paul and only copied me for information purposes.
If not, please address your message more appropriately so as to avoid confusion.
Thanks.
On 28/08/14 16:01, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 07:12:07PM +0100, Daniel Thompson wrote:
On 19/08/14 18:37, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 05:45:53PM +0100, Daniel Thompson wrote:
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
+}
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- nmi_exit();
- set_irq_regs(old_regs);
+}
Really not happy with this. What happens if a FIQ occurs while we're inside register_fiq_nmi_notifier() - more specifically inside atomic_notifier_chain_register() ?
Should depend on which side of the rcu update we're on.
I just asked Paul McKenney, our RCU expert... essentially, yes, RCU stuff itself is safe in this context. However, RCU stuff can call into lockdep if lockdep is configured, and there are questions over lockdep.
There's some things which can be done to reduce the lockdep exposure to it, such as ensuring that rcu_read_lock() is first called outside of FIQ context.
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
Having esablished (elsewhere in the thread) that RCU usage is safe from FIQ I have been working on the assumption that your feeling regarding unreviewed code is sufficient on its own to avoid using notifiers (and also to avoid a list of function pointers like on x86).
Therefore I have made the changes requested and produced a before/after patch to show the impact of this. I will merge this into the FIQ patchset shortly (I need to run a few more build tests first).
Personally I still favour using notifiers and think the coupling below is excessive. Nevertheless I've run a couple of basic tests on the code below and it works fine.
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index 175bfed..a25c952 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -54,7 +54,6 @@ extern void disable_fiq(int fiq); extern int ack_fiq(int fiq); extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); -extern int register_fiq_nmi_notifier(struct notifier_block *nb); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 6563da0..cb5ccd6 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -51,6 +51,7 @@ extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
extern int kgdb_register_fiq(unsigned int fiq); +extern void kgdb_handle_fiq(struct pt_regs *regs);
#endif /* !__ASSEMBLY__ */
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index b2bd1c7..7422b58 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -43,12 +43,14 @@ #include <linux/irq.h> #include <linux/radix-tree.h> #include <linux/slab.h> +#include <linux/irqchip/arm-gic.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> #include <asm/exception.h> #include <asm/fiq.h> #include <asm/irq.h> +#include <asm/kgdb.h> #include <asm/traps.h>
#define FIQ_OFFSET ({ \ @@ -65,7 +67,6 @@ static unsigned long no_fiq_insn; static int fiq_start = -1; static RADIX_TREE(fiq_data_tree, GFP_KERNEL); static DEFINE_MUTEX(fiq_data_mutex); -static ATOMIC_NOTIFIER_HEAD(fiq_nmi_chain);
/* Default reacquire function * - we always relinquish FIQ control @@ -218,17 +219,23 @@ bool has_fiq(int fiq) } EXPORT_SYMBOL(has_fiq);
-int register_fiq_nmi_notifier(struct notifier_block *nb) -{ - return atomic_notifier_chain_register(&fiq_nmi_chain, nb); -} - asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs);
nmi_enter(); - atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL); + + /* these callbacks deliberately avoid using a notifier chain in + * order to ensure code review happens (drivers cannot "secretly" + * employ FIQ without modifying this chain of calls). + */ +#ifdef CONFIG_KGDB_FIQ + kgdb_handle_fiq(regs); +#endif +#ifdef CONFIG_ARM_GIC + gic_handle_fiq_ipi(); +#endif + nmi_exit(); set_irq_regs(old_regs); } diff --git a/arch/arm/kernel/kgdb.c b/arch/arm/kernel/kgdb.c index b77b885..630a3ef 100644 --- a/arch/arm/kernel/kgdb.c +++ b/arch/arm/kernel/kgdb.c @@ -312,12 +312,13 @@ struct kgdb_arch arch_kgdb_ops = { };
#ifdef CONFIG_KGDB_FIQ -static int kgdb_handle_fiq(struct notifier_block *nb, unsigned long arg, - void *data) +void kgdb_handle_fiq(struct pt_regs *regs) { - struct pt_regs *regs = (void *) arg; int actual;
+ if (!kgdb_fiq) + return; + if (!kgdb_nmicallback(raw_smp_processor_id(), regs)) return NOTIFY_OK;
@@ -333,11 +334,6 @@ static int kgdb_handle_fiq(struct notifier_block *nb, unsigned long arg, return NOTIFY_OK; }
-static struct notifier_block kgdb_fiq_notifier = { - .notifier_call = kgdb_handle_fiq, - .priority = 100, -}; - int kgdb_register_fiq(unsigned int fiq) { static struct fiq_handler kgdb_fiq_desc = { .name = "kgdb", }; @@ -357,7 +353,6 @@ int kgdb_register_fiq(unsigned int fiq) }
kgdb_fiq = fiq; - register_fiq_nmi_notifier(&kgdb_fiq_notifier);
return 0; } diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index bda5a91..8821160 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -502,13 +502,17 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, /* * Fully acknowledge (both ack and eoi) a FIQ-based IPI */ -static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs, - void *data) +void gic_handle_fiq_ipi(void) { struct gic_chip_data *gic = &gic_data[0]; - void __iomem *cpu_base = gic_data_cpu_base(gic); + void __iomem *cpu_base; unsigned long irqstat, irqnr;
+ if (!gic || !gic->fiq_enable) + return; + + cpu_base = gic_data_cpu_base(gic); + if (WARN_ON(!in_nmi())) return NOTIFY_BAD;
@@ -525,13 +529,6 @@ static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs,
return NOTIFY_OK; } - -/* - * Notifier to ensure IPI FIQ is acknowledged correctly. - */ -static struct notifier_block gic_fiq_ipi_notifier = { - .notifier_call = gic_handle_fiq_ipi, -}; #else /* CONFIG_FIQ */ static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, int group) {} @@ -1250,10 +1247,6 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); -#ifdef CONFIG_FIQ - if (gic_data_fiq_enable(gic)) - register_fiq_nmi_notifier(&gic_fiq_ipi_notifier); -#endif #endif set_handle_irq(gic_handle_irq); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 45e2d8c..52a5676 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -101,5 +101,8 @@ static inline void __init register_routable_domain_ops { gic_routable_irq_domain_ops = ops; } + +void gic_handle_fiq_ipi(void); + #endif /* __ASSEMBLY */ #endif
On Tue, Sep 02, 2014 at 12:49:16PM +0100, Daniel Thompson wrote:
On 28/08/14 16:01, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 07:12:07PM +0100, Daniel Thompson wrote:
On 19/08/14 18:37, Russell King - ARM Linux wrote:
On Tue, Aug 19, 2014 at 05:45:53PM +0100, Daniel Thompson wrote:
+int register_fiq_nmi_notifier(struct notifier_block *nb) +{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
+}
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- nmi_exit();
- set_irq_regs(old_regs);
+}
Really not happy with this. What happens if a FIQ occurs while we're inside register_fiq_nmi_notifier() - more specifically inside atomic_notifier_chain_register() ?
Should depend on which side of the rcu update we're on.
I just asked Paul McKenney, our RCU expert... essentially, yes, RCU stuff itself is safe in this context. However, RCU stuff can call into lockdep if lockdep is configured, and there are questions over lockdep.
There's some things which can be done to reduce the lockdep exposure to it, such as ensuring that rcu_read_lock() is first called outside of FIQ context.
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
Having esablished (elsewhere in the thread) that RCU usage is safe from FIQ I have been working on the assumption that your feeling regarding unreviewed code is sufficient on its own to avoid using notifiers (and also to avoid a list of function pointers like on x86).
There was a later clarification from a lockdep expert showing that the code was in fact safe, so the notifier approach should be just fine.
Thanx, Paul
Therefore I have made the changes requested and produced a before/after patch to show the impact of this. I will merge this into the FIQ patchset shortly (I need to run a few more build tests first).
Personally I still favour using notifiers and think the coupling below is excessive. Nevertheless I've run a couple of basic tests on the code below and it works fine.
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index 175bfed..a25c952 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -54,7 +54,6 @@ extern void disable_fiq(int fiq); extern int ack_fiq(int fiq); extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); -extern int register_fiq_nmi_notifier(struct notifier_block *nb); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 6563da0..cb5ccd6 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -51,6 +51,7 @@ extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
extern int kgdb_register_fiq(unsigned int fiq); +extern void kgdb_handle_fiq(struct pt_regs *regs);
#endif /* !__ASSEMBLY__ */
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index b2bd1c7..7422b58 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -43,12 +43,14 @@ #include <linux/irq.h> #include <linux/radix-tree.h> #include <linux/slab.h> +#include <linux/irqchip/arm-gic.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> #include <asm/exception.h> #include <asm/fiq.h> #include <asm/irq.h> +#include <asm/kgdb.h> #include <asm/traps.h>
#define FIQ_OFFSET ({ \ @@ -65,7 +67,6 @@ static unsigned long no_fiq_insn; static int fiq_start = -1; static RADIX_TREE(fiq_data_tree, GFP_KERNEL); static DEFINE_MUTEX(fiq_data_mutex); -static ATOMIC_NOTIFIER_HEAD(fiq_nmi_chain);
/* Default reacquire function
- we always relinquish FIQ control
@@ -218,17 +219,23 @@ bool has_fiq(int fiq) } EXPORT_SYMBOL(has_fiq);
-int register_fiq_nmi_notifier(struct notifier_block *nb) -{
- return atomic_notifier_chain_register(&fiq_nmi_chain, nb);
-}
asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs);
nmi_enter();
- atomic_notifier_call_chain(&fiq_nmi_chain, (unsigned long)regs, NULL);
- /* these callbacks deliberately avoid using a notifier chain in
* order to ensure code review happens (drivers cannot "secretly"
* employ FIQ without modifying this chain of calls).
*/
+#ifdef CONFIG_KGDB_FIQ
- kgdb_handle_fiq(regs);
+#endif +#ifdef CONFIG_ARM_GIC
- gic_handle_fiq_ipi();
+#endif
- nmi_exit(); set_irq_regs(old_regs);
} diff --git a/arch/arm/kernel/kgdb.c b/arch/arm/kernel/kgdb.c index b77b885..630a3ef 100644 --- a/arch/arm/kernel/kgdb.c +++ b/arch/arm/kernel/kgdb.c @@ -312,12 +312,13 @@ struct kgdb_arch arch_kgdb_ops = { };
#ifdef CONFIG_KGDB_FIQ -static int kgdb_handle_fiq(struct notifier_block *nb, unsigned long arg,
void *data)
+void kgdb_handle_fiq(struct pt_regs *regs) {
- struct pt_regs *regs = (void *) arg; int actual;
- if (!kgdb_fiq)
return;
- if (!kgdb_nmicallback(raw_smp_processor_id(), regs)) return NOTIFY_OK;
@@ -333,11 +334,6 @@ static int kgdb_handle_fiq(struct notifier_block *nb, unsigned long arg, return NOTIFY_OK; }
-static struct notifier_block kgdb_fiq_notifier = {
- .notifier_call = kgdb_handle_fiq,
- .priority = 100,
-};
int kgdb_register_fiq(unsigned int fiq) { static struct fiq_handler kgdb_fiq_desc = { .name = "kgdb", }; @@ -357,7 +353,6 @@ int kgdb_register_fiq(unsigned int fiq) }
kgdb_fiq = fiq;
register_fiq_nmi_notifier(&kgdb_fiq_notifier);
return 0;
} diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index bda5a91..8821160 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -502,13 +502,17 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, /*
- Fully acknowledge (both ack and eoi) a FIQ-based IPI
*/ -static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs,
void *data)
+void gic_handle_fiq_ipi(void) { struct gic_chip_data *gic = &gic_data[0];
- void __iomem *cpu_base = gic_data_cpu_base(gic);
void __iomem *cpu_base; unsigned long irqstat, irqnr;
if (!gic || !gic->fiq_enable)
return;
cpu_base = gic_data_cpu_base(gic);
if (WARN_ON(!in_nmi())) return NOTIFY_BAD;
@@ -525,13 +529,6 @@ static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs,
return NOTIFY_OK; }
-/*
- Notifier to ensure IPI FIQ is acknowledged correctly.
- */
-static struct notifier_block gic_fiq_ipi_notifier = {
- .notifier_call = gic_handle_fiq_ipi,
-}; #else /* CONFIG_FIQ */ static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, int group) {} @@ -1250,10 +1247,6 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); -#ifdef CONFIG_FIQ
if (gic_data_fiq_enable(gic))
register_fiq_nmi_notifier(&gic_fiq_ipi_notifier);
-#endif #endif set_handle_irq(gic_handle_irq); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 45e2d8c..52a5676 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -101,5 +101,8 @@ static inline void __init register_routable_domain_ops { gic_routable_irq_domain_ops = ops; }
+void gic_handle_fiq_ipi(void);
#endif /* __ASSEMBLY */ #endif
On Tue, Sep 02, 2014 at 12:49:16PM +0100, Daniel Thompson wrote:
On 28/08/14 16:01, Russell King - ARM Linux wrote:
I just asked Paul McKenney, our RCU expert... essentially, yes, RCU stuff itself is safe in this context. However, RCU stuff can call into lockdep if lockdep is configured, and there are questions over lockdep.
There's some things which can be done to reduce the lockdep exposure to it, such as ensuring that rcu_read_lock() is first called outside of FIQ context.
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
Having esablished (elsewhere in the thread) that RCU usage is safe from FIQ I have been working on the assumption that your feeling regarding unreviewed code is sufficient on its own to avoid using notifiers (and also to avoid a list of function pointers like on x86).
Yes, it does, because unlike the x86 community, we have a wide range of platforms, and platform code does not go through the same path or get the same review as core ARM code.
As I already pointed out, with a notifier, it's very easy to sneak something into the FIQ path by submitting a patch for platform code which calls the registration function. That's going to be pretty difficult to spot amongst the 3000+ messages on the linux-arm-kernel list each month in order to give it the review that it would need. That's especially true as I now ignore almost all most platform code patches as we have Arnd and Olof to look at that.
So, unless you can come up with a proposal which ensures that there is sufficient review triggered when someone decides to call the notifier registration function...
How about something like:
static const char *allowable_callers[] = { ... };
snprintf(caller, sizeof(caller), "%pf", __builtin_return_address(0));
for (i = 0; i < ARRAY_SIZE(allowable_callers); i++) if (!strcmp(caller, allowable_callers[i])) break;
if (i == ARRAY_SIZE(allowable_callers)) { printk(KERN_ERR "%s is not permitted to register a FIQ notifer\n", caller); return; }
This gives us the advantage of using the notifier, but also gives us the requirement that the file has to be modified to permit new registrations, thereby triggering the closer review.
The other question I have is that if we permit kgdb and nmi tracing with this mechanism, how do the hooked callers distinguish between these different purposes? I don't see how that works with your notifier setup.
On 02/09/14 17:42, Russell King - ARM Linux wrote:
On Tue, Sep 02, 2014 at 12:49:16PM +0100, Daniel Thompson wrote:
On 28/08/14 16:01, Russell King - ARM Linux wrote:
I just asked Paul McKenney, our RCU expert... essentially, yes, RCU stuff itself is safe in this context. However, RCU stuff can call into lockdep if lockdep is configured, and there are questions over lockdep.
There's some things which can be done to reduce the lockdep exposure to it, such as ensuring that rcu_read_lock() is first called outside of FIQ context.
There's concerns with whether either printk() in check_flags() could be reached too (flags there should always indicate that IRQs were disabled, so that reduces down to a question about just the first printk() there.)
There's also the very_verbose() stuff for RCU lockdep classes which Paul says must not be enabled.
Lastly, Paul isn't a lockdep expert, but he sees nothing that prevents lockdep doing the deadlock checking as a result of the above call.
So... this coupled with my feeling that notifiers make it too easy for unreviewed code to be hooked into this path, I'm fairly sure that we don't want to be calling atomic notifier chains from FIQ context.
Having esablished (elsewhere in the thread) that RCU usage is safe from FIQ I have been working on the assumption that your feeling regarding unreviewed code is sufficient on its own to avoid using notifiers (and also to avoid a list of function pointers like on x86).
Yes, it does, because unlike the x86 community, we have a wide range of platforms, and platform code does not go through the same path or get the same review as core ARM code.
As I already pointed out, with a notifier, it's very easy to sneak something into the FIQ path by submitting a patch for platform code which calls the registration function. That's going to be pretty difficult to spot amongst the 3000+ messages on the linux-arm-kernel list each month in order to give it the review that it would need. That's especially true as I now ignore almost all most platform code patches as we have Arnd and Olof to look at that.
So, unless you can come up with a proposal which ensures that there is sufficient review triggered when someone decides to call the notifier registration function...
Reflecting upon this and upon Thomas' comments about only using FIQ for watchdog, backtrace and performance monitoring...
The short version is, "I was wrong and should have done what you said in the first place".
The long version adds, "because the coupling concerns were spurious; the only proposed users of the default FIQ handler outside of core ARM code, are ARM-centric irqchip drivers."
How about something like:
static const char *allowable_callers[] = { ... };
snprintf(caller, sizeof(caller), "%pf", __builtin_return_address(0));
for (i = 0; i < ARRAY_SIZE(allowable_callers); i++) if (!strcmp(caller, allowable_callers[i])) break;
if (i == ARRAY_SIZE(allowable_callers)) { printk(KERN_ERR "%s is not permitted to register a FIQ notifer\n", caller); return; }
This gives us the advantage of using the notifier, but also gives us the requirement that the file has to be modified to permit new registrations, thereby triggering the closer review.
Cool trick. However since I'm wrong...
The other question I have is that if we permit kgdb and nmi tracing with this mechanism, how do the hooked callers distinguish between these different purposes? I don't see how that works with your notifier setup.
The notifier as found in the existing patchs allowed the sharing only of IPI FIQ (for example between stack dump code and kgdb).
This worked due to the way IPIs can be automatically EOIed and, because they are software generated, software can use flags. In fact the kgdb handler already uses this to discriminate between IPI FIQ and UART interrupts (although the flag is set kgdb core logic rather than in the ARM code).
For SPIs it's safety depended upon drivers using claim_fiq() to arbitrate. There was no enforcement mechanism to prevent abuse.
On Wed, Sep 03, 2014 at 11:21:30AM +0100, Daniel Thompson wrote:
On 02/09/14 17:42, Russell King - ARM Linux wrote:
Yes, it does, because unlike the x86 community, we have a wide range of platforms, and platform code does not go through the same path or get the same review as core ARM code.
As I already pointed out, with a notifier, it's very easy to sneak something into the FIQ path by submitting a patch for platform code which calls the registration function. That's going to be pretty difficult to spot amongst the 3000+ messages on the linux-arm-kernel list each month in order to give it the review that it would need. That's especially true as I now ignore almost all most platform code patches as we have Arnd and Olof to look at that.
So, unless you can come up with a proposal which ensures that there is sufficient review triggered when someone decides to call the notifier registration function...
Reflecting upon this and upon Thomas' comments about only using FIQ for watchdog, backtrace and performance monitoring...
The short version is, "I was wrong and should have done what you said in the first place".
The long version adds, "because the coupling concerns were spurious; the only proposed users of the default FIQ handler outside of core ARM code, are ARM-centric irqchip drivers."
I would say that the ARM specific changes to entry-armv.S and setup.c are correct. All that you're doing there is to replace the existing default no-op FIQ handler with some additional code which gets us into SVC mode and back out, but itself is also a no-op. In other words, no real change.
That's a good first patch, and one which I would actually like to have in my tree sooner rather than later, so that I can split that out from my prototype code.
I can also split out from it the ARM generic changes for implementing the FIQ dumping too, which gives us a second patch. With a bit of additional work, much of that should actually be generic code, not ARM or x86 specific code. That's going to annoy x86 people a little because some of that is being reworked...
That will leave the problem of how to deal with the IRQ controller specifics, and how to properly wire it together with the IRQ controller in the loop - that is where Thomas' concerns are focused.
On 03/09/14 20:34, Russell King - ARM Linux wrote:
On Wed, Sep 03, 2014 at 11:21:30AM +0100, Daniel Thompson wrote:
On 02/09/14 17:42, Russell King - ARM Linux wrote:
Yes, it does, because unlike the x86 community, we have a wide range of platforms, and platform code does not go through the same path or get the same review as core ARM code.
As I already pointed out, with a notifier, it's very easy to sneak something into the FIQ path by submitting a patch for platform code which calls the registration function. That's going to be pretty difficult to spot amongst the 3000+ messages on the linux-arm-kernel list each month in order to give it the review that it would need. That's especially true as I now ignore almost all most platform code patches as we have Arnd and Olof to look at that.
So, unless you can come up with a proposal which ensures that there is sufficient review triggered when someone decides to call the notifier registration function...
Reflecting upon this and upon Thomas' comments about only using FIQ for watchdog, backtrace and performance monitoring...
The short version is, "I was wrong and should have done what you said in the first place".
The long version adds, "because the coupling concerns were spurious; the only proposed users of the default FIQ handler outside of core ARM code, are ARM-centric irqchip drivers."
I would say that the ARM specific changes to entry-armv.S and setup.c are correct. All that you're doing there is to replace the existing default no-op FIQ handler with some additional code which gets us into SVC mode and back out, but itself is also a no-op. In other words, no real change.
That's a good first patch, and one which I would actually like to have in my tree sooner rather than later, so that I can split that out from my prototype code.
So would I!
I did some rebasing yesterday to put anything to do with kgdb right at the back of the queue. This "good first patch" is now actually the first patch; where the nofifier used to be it currently calls do_unexp_fiq() making it very close to "no real change".
BTW do_unexp_fiq() calls printk() but
I can also split out from it the ARM generic changes for implementing the FIQ dumping too, which gives us a second patch. With a bit of additional work, much of that should actually be generic code, not ARM or x86 specific code. That's going to annoy x86 people a little because some of that is being reworked...
So far I have been testing the action after patch review using the big kgdb patches on top of it.
Today I plan to remove the kgdb stuff, drop in your version of arch_trigger_all_cpu_backtrace() and test the results. I was going to use the code you shared on 13 August meaning all the cpu mask bit manipulation is in the arm-only code.
If you want me to work with something more recent then feel free to point me at it...
That will leave the problem of how to deal with the IRQ controller specifics, and how to properly wire it together with the IRQ controller in the loop - that is where Thomas' concerns are focused.
I'm working on those and its looking pretty good so far. This is mostly because SGIs don't need to allocate virqs so the controversial bits of my last patchset disappear completely.
On Thu, Sep 04, 2014 at 10:09:20AM +0100, Daniel Thompson wrote:
On 03/09/14 20:34, Russell King - ARM Linux wrote:
I would say that the ARM specific changes to entry-armv.S and setup.c are correct. All that you're doing there is to replace the existing default no-op FIQ handler with some additional code which gets us into SVC mode and back out, but itself is also a no-op. In other words, no real change.
That's a good first patch, and one which I would actually like to have in my tree sooner rather than later, so that I can split that out from my prototype code.
So would I!
I did some rebasing yesterday to put anything to do with kgdb right at the back of the queue. This "good first patch" is now actually the first patch; where the nofifier used to be it currently calls do_unexp_fiq() making it very close to "no real change".
BTW do_unexp_fiq() calls printk() but
You're making the assumption that something called do_unexp_fiq() before your patches. It seems that that function is dead code, and now that you've pointed that out, I will kill this function.
The current situation is that if the CPU receives a FIQ, it enters the FIQ vector, which contains an immediate return instruction.
If you want me to work with something more recent then feel free to point me at it...
I'll post some of that stuff later today, probably this evening.
On 04/09/14 10:45, Russell King - ARM Linux wrote:
On Thu, Sep 04, 2014 at 10:09:20AM +0100, Daniel Thompson wrote:
On 03/09/14 20:34, Russell King - ARM Linux wrote:
I would say that the ARM specific changes to entry-armv.S and setup.c are correct. All that you're doing there is to replace the existing default no-op FIQ handler with some additional code which gets us into SVC mode and back out, but itself is also a no-op. In other words, no real change.
That's a good first patch, and one which I would actually like to have in my tree sooner rather than later, so that I can split that out from my prototype code.
So would I!
I did some rebasing yesterday to put anything to do with kgdb right at the back of the queue. This "good first patch" is now actually the first patch; where the nofifier used to be it currently calls do_unexp_fiq() making it very close to "no real change".
BTW do_unexp_fiq() calls printk() but
You're making the assumption that something called do_unexp_fiq() before your patches. It seems that that function is dead code, and now that you've pointed that out, I will kill this function.
The current situation is that if the CPU receives a FIQ, it enters the FIQ vector, which contains an immediate return instruction.
Actually it was the comment above the return instruction in the original code about getting a message out that made me try to reconnect do_unexp_fiq().
printk() has reasonably strong defences against being called twice by the same CPU in that we ought to avoid deadlock if the current CPU owns the printk locks (although the message would be dropped). I therefore thought do_unexp_fiq() was a best-effort attempt to inform the user.
That said, assuming the FIQ re-enters, then without a rate limiter the message will be rather insistent.
For now I will remove calls to do_unexp_fiq().
If you want me to work with something more recent then feel free to point me at it...
I'll post some of that stuff later today, probably this evening.
Hopefully so shall I.
Cross CPU signalling based on FIQ is especially useful for kgdb since it makes stopping all the CPUs during breakpointing more robust (some of the other architectures already roundup the CPUs using NMIs).
The approach taken provides infrastructure that can be called (or not) by the driver's FIQ handler depending upon it requirements. In other words nothing is added here that prevents the driver from accessing "bare metal" performance.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/hardirq.h | 2 +- arch/arm/include/asm/smp.h | 7 +++++++ arch/arm/kernel/smp.c | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/arch/arm/include/asm/hardirq.h b/arch/arm/include/asm/hardirq.h index fe3ea77..5df33e3 100644 --- a/arch/arm/include/asm/hardirq.h +++ b/arch/arm/include/asm/hardirq.h @@ -5,7 +5,7 @@ #include <linux/threads.h> #include <asm/irq.h>
-#define NR_IPI 8 +#define NR_IPI 9
typedef struct { unsigned int __softirq_pending; diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 2ec765c..215c927 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -20,6 +20,9 @@
#define raw_smp_processor_id() (current_thread_info()->cpu)
+/* bitmap of IPIs that must be signalled using FIQ */ +#define SMP_IPI_FIQ_MASK 0x0100 + struct seq_file;
/* @@ -87,6 +90,10 @@ extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
extern int register_ipi_completion(struct completion *completion, int cpu);
+#ifdef CONFIG_FIQ +extern void send_fiq_ipi_mask(const struct cpumask *); +#endif + struct smp_operations { #ifdef CONFIG_SMP /* diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 9388a3d..d386c32 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -72,6 +72,7 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_FIQ, };
static DECLARE_COMPLETION(cpu_running); @@ -451,6 +452,7 @@ static const char *ipi_types[NR_IPI] __tracepoint_string = { S(IPI_CPU_STOP, "CPU stop interrupts"), S(IPI_IRQ_WORK, "IRQ work interrupts"), S(IPI_COMPLETION, "completion interrupts"), + S(IPI_FIQ, "FIQ interrupts"), };
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr) @@ -552,6 +554,13 @@ static void ipi_complete(unsigned int cpu) complete(per_cpu(cpu_completion, cpu)); }
+#ifdef CONFIG_FIQ +void send_fiq_ipi_mask(const struct cpumask *mask) +{ + smp_cross_call(mask, IPI_FIQ); +} +#endif + /* * Main handler for inter-processor interrupts */ @@ -618,6 +627,12 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break;
+#ifdef CONFIG_FIQ + case IPI_FIQ: + pr_crit("CPU%u: IPI FIQ delivered via IRQ vector\n", cpu); + break; +#endif + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr);
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of comments and other small fragments still survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Omar Sandoval osandov@osandov.com --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 19 +++++++ arch/arm/include/asm/kgdb.h | 4 ++ arch/arm/kernel/kgdb.c | 117 +++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 136 insertions(+), 6 deletions(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index c49a775..e6380b3 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -305,6 +305,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -352,6 +353,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index b11ad54..df3f0bf 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,25 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be enabled + by setting the console to ttyNMI0 (and choosing the underlying + serial port using kgdboc) + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..6563da0 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,8 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern int kgdb_register_fiq(unsigned int fiq); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/kgdb.c b/arch/arm/kernel/kgdb.c index a74b53c..b77b885 100644 --- a/arch/arm/kernel/kgdb.c +++ b/arch/arm/kernel/kgdb.c @@ -12,8 +12,11 @@ #include <linux/irq.h> #include <linux/kdebug.h> #include <linux/kgdb.h> +#include <asm/fiq.h> #include <asm/traps.h>
+static unsigned int kgdb_fiq; + struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = { { "r0", 4, offsetof(struct pt_regs, ARM_r0)}, @@ -175,14 +178,26 @@ static struct undef_hook kgdb_compiled_brkpt_hook = {
static void kgdb_call_nmi_hook(void *ignored) { - kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs()); + kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs()); }
void kgdb_roundup_cpus(unsigned long flags) { - local_irq_enable(); - smp_call_function(kgdb_call_nmi_hook, NULL, 0); - local_irq_disable(); +#if defined CONFIG_KGDB_FIQ && defined CONFIG_SMP + struct cpumask mask; + + if (in_nmi()) { + cpumask_copy(&mask, cpu_online_mask); + cpumask_clear_cpu(raw_smp_processor_id(), &mask); + if (!cpumask_empty(&mask)) + send_fiq_ipi_mask(&mask); + return; + } +#endif + + local_irq_enable(); + smp_call_function(kgdb_call_nmi_hook, NULL, 0); + local_irq_disable(); }
static int __kgdb_notify(struct die_args *args, unsigned long cmd) @@ -244,6 +259,43 @@ void kgdb_arch_exit(void) unregister_die_notifier(&kgdb_notifier); }
+/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + if (!kgdb_fiq) + return; + +#ifdef CONFIG_KGDB_FIQ + static atomic_t cnt; + int ret; + + if (on) { + ret = atomic_add_return(1, &cnt); + if (ret == 1) + enable_fiq(kgdb_fiq); + + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + if (WARN_ON(ret > 1)) + return; + } else { + ret = atomic_add_return(-1, &cnt); + if (ret == 0) + disable_fiq(kgdb_fiq); + } +#endif +} + + /* * Register our undef instruction hooks with ARM undef core. * We regsiter a hook specifically looking for the KGB break inst @@ -252,8 +304,61 @@ void kgdb_arch_exit(void) */ struct kgdb_arch arch_kgdb_ops = { #ifndef __ARMEB__ - .gdb_bpt_instr = {0xfe, 0xde, 0xff, 0xe7} + .gdb_bpt_instr = {0xfe, 0xde, 0xff, 0xe7}, #else /* ! __ARMEB__ */ - .gdb_bpt_instr = {0xe7, 0xff, 0xde, 0xfe} + .gdb_bpt_instr = {0xe7, 0xff, 0xde, 0xfe}, #endif + .enable_nmi = kgdb_fiq_enable_nmi }; + +#ifdef CONFIG_KGDB_FIQ +static int kgdb_handle_fiq(struct notifier_block *nb, unsigned long arg, + void *data) +{ + struct pt_regs *regs = (void *) arg; + int actual; + + if (!kgdb_nmicallback(raw_smp_processor_id(), regs)) + return NOTIFY_OK; + + actual = ack_fiq(kgdb_fiq); + WARN_ON(actual != kgdb_fiq); + + /* there's no harm in doing this regardless of the above WARN_ON() */ + if (kgdb_nmi_poll_knock()) + kgdb_handle_exception(1, 0, 0, regs); + + eoi_fiq(actual); + + return NOTIFY_OK; +} + +static struct notifier_block kgdb_fiq_notifier = { + .notifier_call = kgdb_handle_fiq, + .priority = 100, +}; + +int kgdb_register_fiq(unsigned int fiq) +{ + static struct fiq_handler kgdb_fiq_desc = { .name = "kgdb", }; + int err; + + if (!has_fiq(fiq)) { + pr_warn( + "%s: Cannot register %u (no FIQ with this number)\n", + __func__, fiq); + return -ENODEV; + } + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + kgdb_fiq = fiq; + register_fiq_nmi_notifier(&kgdb_fiq_notifier); + + return 0; +} +#endif
All GIC hardware except GICv1-without-TrustZone support provides a means to group exceptions into group 0 (which can optionally be signally using use FIQ) and group 1. The kernel currently provides no means to exploit this. This patch alters the initialization of the GIC to place all interrupts into group 1 which is the foundational requirement to meaningfully use FIQ.
Note that the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world" (this feature allows grouping to be used to allow hardware peripherals to send interrupts into the secure world). The GIC driver will automatically detect this and disable its attempts to group interrupts.
On systems without TrustZone support the kernel has the power to route interrupt sources to FIQ, potentially allowing a driver to exploit the NMI-like properties of FIQ.
Tested on Freescale i.MX6 (quad A9), STiH416 (dual A9) and a self-written qemu GICv2 model.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com Cc: Marc Zyngier marc.zyngier@arm.com Acked-by: Dirk Behme dirk.behme@de.bosch.com --- drivers/irqchip/irq-gic.c | 99 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 5 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..423707c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -41,6 +41,9 @@ #include <linux/irqchip/arm-gic.h>
#include <asm/cputype.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -68,6 +71,9 @@ struct gic_chip_data { #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif +#ifdef CONFIG_FIQ + bool fiq_enable; +#endif };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); @@ -131,6 +137,16 @@ static inline void gic_set_base_accessor(struct gic_chip_data *data, #define gic_set_base_accessor(d, f) #endif
+#ifdef CONFIG_FIQ +static inline bool gic_data_fiq_enable(struct gic_chip_data *data) +{ + return data->fiq_enable; +} +#else +static inline bool gic_data_fiq_enable( + struct gic_chip_data *data) { return false; } +#endif + static inline void __iomem *gic_dist_base(struct irq_data *d) { struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d); @@ -325,6 +341,42 @@ static struct irq_chip gic_chip = { .irq_set_wake = gic_set_wake, };
+#ifdef CONFIG_FIQ +static void __init gic_init_fiq(struct gic_chip_data *gic, + irq_hw_number_t first_irq, + unsigned int num_irqs) +{ + void __iomem *dist_base = gic_data_dist_base(gic_data); + unsigned int i; + + /* + * FIQ can only be supported on platforms without an extended irq_eoi + * method (otherwise we take a lock during eoi handling). + */ + if (gic_arch_extn.irq_eoi) + return; + + /* + * If grouping is not available (not implemented or prohibited by + * security mode) these registers a read-as-zero/write-ignored. + * However as a precaution we restore the reset default regardless of + * the result of the test. + */ + writel_relaxed(1, dist_base + GIC_DIST_IGROUP + 0); + gic->fiq_enable = readl_relaxed(dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0, dist_base + GIC_DIST_IGROUP + 0); + pr_debug("gic: FIQ support %s\n", + gic->fiq_enable ? "enabled" : "disabled"); + + if (!gic->fiq_enable) + return; +} +#else /* CONFIG_FIQ */ +static inline void gic_init_fiq(struct gic_chip_data *gic, + irq_hw_number_t first_irq, + unsigned int num_irqs) {} +#endif /* CONFIG_FIQ */ + void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) { if (gic_nr >= MAX_GIC_NR) @@ -373,7 +425,22 @@ static void __init gic_dist_init(struct gic_chip_data *gic)
gic_dist_config(base, gic_irqs, NULL);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Optionally set all global interrupts to be group 1. + */ + if (gic_data_fiq_enable(gic)) + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, + base + GIC_DIST_IGROUP + i * 4 / 32); + + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + if (gic_data_fiq_enable(gic)) + writel_relaxed(3, base + GIC_DIST_CTRL); + else + writel_relaxed(1, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -400,8 +467,20 @@ static void gic_cpu_init(struct gic_chip_data *gic)
gic_cpu_config(dist_base, NULL);
+ /* + * Set all PPI and SGI interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers are read-as-zero/write-ignored. + */ + if (gic_data_fiq_enable(gic)) + writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + if (gic_data_fiq_enable(gic)) + writel_relaxed(0x1f, base + GIC_CPU_CTRL); + else + writel_relaxed(1, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -485,7 +564,10 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + if (gic_data_fiq_enable(&gic_data[gic_nr])) + writel_relaxed(3, dist_base + GIC_DIST_CTRL); + else + writel_relaxed(1, dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -542,7 +624,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,6 +686,7 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0; + unsigned long softint;
raw_spin_lock_irqsave(&irq_controller_lock, flags);
@@ -618,7 +701,11 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + softint = map << 16 | irq; + if (gic_data_fiq_enable(&gic_data[0])) + softint |= 0x8000; + writel_relaxed(softint, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } @@ -964,6 +1051,8 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start,
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, hwirq_base, &gic_irq_domain_ops, gic); + + gic_init_fiq(gic, irq_base, gic_irqs); } else { gic->domain = irq_domain_add_linear(node, nr_routable_irqs, &gic_irq_domain_ops,
This patch introduces callbacks to route interrupts to or away from the FIQ signal and registers these callbacks with the FIQ infrastructure (if the device can supports it).
Both these aspects combine and allow a driver to deploy a FIQ handler without any machine specific knowledge; it can be used effectively on multi-platform kernels.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com --- drivers/irqchip/irq-gic.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 423707c..6fa0542 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -342,6 +342,69 @@ static struct irq_chip gic_chip = { };
#ifdef CONFIG_FIQ +/* + * Shift an interrupt between Group 0 and Group 1. + * + * In addition to changing the group we also modify the priority to + * match what "ARM strongly recommends" for a system where no Group 1 + * interrupt must ever preempt a Group 0 interrupt. + */ +static void gic_set_group_irq(struct irq_data *d, int group) +{ + unsigned int grp_reg = gic_irq(d) / 32 * 4; + u32 grp_mask = 1 << (gic_irq(d) % 32); + u32 grp_val; + + unsigned int pri_reg = (gic_irq(d) / 4) * 4; + u32 pri_mask = 1 << (7 + ((gic_irq(d) % 4) * 8)); + u32 pri_val; + + raw_spin_lock(&irq_controller_lock); + + grp_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + + if (group) { + grp_val |= grp_mask; + pri_val |= pri_mask; + } else { + grp_val &= ~grp_mask; + pri_val &= ~pri_mask; + } + + writel_relaxed(grp_val, gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + + raw_spin_unlock(&irq_controller_lock); +} + +static void gic_enable_fiq(struct irq_data *d) +{ + gic_set_group_irq(d, 0); +} + +static void gic_disable_fiq(struct irq_data *d) +{ + gic_set_group_irq(d, 1); +} + +static int gic_ack_fiq(struct irq_data *d) +{ + struct gic_chip_data *gic = irq_data_get_irq_chip_data(d); + u32 irqstat, irqnr; + + irqstat = readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK); + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + return irq_find_mapping(gic->domain, irqnr); +} + +static struct fiq_chip gic_fiq = { + .fiq_enable = gic_enable_fiq, + .fiq_disable = gic_disable_fiq, + .fiq_ack = gic_ack_fiq, + .fiq_eoi = gic_eoi_irq, +}; + static void __init gic_init_fiq(struct gic_chip_data *gic, irq_hw_number_t first_irq, unsigned int num_irqs) @@ -370,6 +433,12 @@ static void __init gic_init_fiq(struct gic_chip_data *gic,
if (!gic->fiq_enable) return; + + /* + * FIQ is supported on this device! Register our chip data. + */ + for (i = 0; i < num_irqs; i++) + fiq_register_mapping(first_irq + i, &gic_fiq); } #else /* CONFIG_FIQ */ static inline void gic_init_fiq(struct gic_chip_data *gic,
This patch is motivated by the comment it removes from gic_init_fiq, namely that the spin locks in eoi_irq preclude certain platforms from supporting FIQ.
Currently there is only one upstream platform (tegra) that actually hooks gic_arch_extn.irq_eoi and it does not require these spin locks.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Peter De Schrijver pdeschrijver@nvidia.com --- drivers/irqchip/irq-gic.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 6fa0542..d928912 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -191,11 +191,8 @@ static void gic_unmask_irq(struct irq_data *d)
static void gic_eoi_irq(struct irq_data *d) { - if (gic_arch_extn.irq_eoi) { - raw_spin_lock(&irq_controller_lock); + if (gic_arch_extn.irq_eoi) gic_arch_extn.irq_eoi(d); - raw_spin_unlock(&irq_controller_lock); - }
writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI); } @@ -413,13 +410,6 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, unsigned int i;
/* - * FIQ can only be supported on platforms without an extended irq_eoi - * method (otherwise we take a lock during eoi handling). - */ - if (gic_arch_extn.irq_eoi) - return; - - /* * If grouping is not available (not implemented or prohibited by * security mode) these registers a read-as-zero/write-ignored. * However as a precaution we restore the reset default regardless of
To support IPI FIQ we alter gic_cpu_init() to honour SMP_IPI_FIQ_MASK and register a fairly high priority notifier to acknowledge and clear the IPI when it is triggered.
For the IPI FIQ to be useful we must also make it safe to call gic_raise_softirq() from the FIQ handler by altering the locking strategy slightly.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/irqchip/irq-gic.c | 125 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 21 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index d928912..8834749 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -39,6 +39,7 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/ratelimit.h>
#include <asm/cputype.h> #ifdef CONFIG_FIQ @@ -51,6 +52,10 @@ #include "irq-gic-common.h" #include "irqchip.h"
+#ifndef SMP_IPI_FIQ_MASK +#define SMP_IPI_FIQ_MASK 0 +#endif + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -77,6 +82,8 @@ struct gic_chip_data { };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); +/* A fiq-safe spinlock must only be locked when the FIQ is masked */ +static DEFINE_RAW_SPINLOCK(fiq_safe_migration_lock);
/* * The GIC mapping of CPU interfaces does not necessarily match @@ -346,20 +353,21 @@ static struct irq_chip gic_chip = { * match what "ARM strongly recommends" for a system where no Group 1 * interrupt must ever preempt a Group 0 interrupt. */ -static void gic_set_group_irq(struct irq_data *d, int group) +static void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) { - unsigned int grp_reg = gic_irq(d) / 32 * 4; - u32 grp_mask = 1 << (gic_irq(d) % 32); + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_mask = 1 << (hwirq % 32); u32 grp_val;
- unsigned int pri_reg = (gic_irq(d) / 4) * 4; - u32 pri_mask = 1 << (7 + ((gic_irq(d) % 4) * 8)); + unsigned int pri_reg = (hwirq / 4) * 4; + u32 pri_mask = 1 << (7 + ((hwirq % 4) * 8)); u32 pri_val;
raw_spin_lock(&irq_controller_lock);
- grp_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); - pri_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(base + GIC_DIST_PRI + pri_reg);
if (group) { grp_val |= grp_mask; @@ -369,20 +377,20 @@ static void gic_set_group_irq(struct irq_data *d, int group) pri_val &= ~pri_mask; }
- writel_relaxed(grp_val, gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); - writel_relaxed(pri_val, gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + writel_relaxed(grp_val, base + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, base + GIC_DIST_PRI + pri_reg);
raw_spin_unlock(&irq_controller_lock); }
static void gic_enable_fiq(struct irq_data *d) { - gic_set_group_irq(d, 0); + gic_set_group_irq(gic_dist_base(d), gic_irq(d), 0); }
static void gic_disable_fiq(struct irq_data *d) { - gic_set_group_irq(d, 1); + gic_set_group_irq(gic_dist_base(d), gic_irq(d), 1); }
static int gic_ack_fiq(struct irq_data *d) @@ -390,8 +398,22 @@ static int gic_ack_fiq(struct irq_data *d) struct gic_chip_data *gic = irq_data_get_irq_chip_data(d); u32 irqstat, irqnr;
- irqstat = readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK); - irqnr = irqstat & GICC_IAR_INT_ID_MASK; + while (1) { + writel_relaxed(0x70, gic_data_cpu_base(gic) + GIC_CPU_PRIMASK); + irqstat = + readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK); + writel_relaxed(0xf0, gic_data_cpu_base(gic) + GIC_CPU_PRIMASK); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + if (irqnr > 15) + break; + + /* we've got an IPI which we can simply acknowledge + * and move on + */ + gic_eoi_irq(d); + } + return irq_find_mapping(gic->domain, irqnr); }
@@ -430,7 +452,43 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, for (i = 0; i < num_irqs; i++) fiq_register_mapping(first_irq + i, &gic_fiq); } + +/* + * Fully acknowledge (both ack and eoi) a FIQ-based IPI + */ +static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs, + void *data) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *cpu_base = gic_data_cpu_base(gic); + unsigned long irqstat, irqnr; + + if (WARN_ON(!in_nmi())) + return NOTIFY_BAD; + + while ((1u << readl_relaxed(cpu_base + GIC_CPU_HIGHPRI)) & + SMP_IPI_FIQ_MASK) { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + WARN_RATELIMIT(irqnr > 16, + "Unexpected irqnr %lu (bad prioritization?)\n", + irqnr); + } + + return NOTIFY_OK; +} + +/* + * Notifier to ensure IPI FIQ is acknowledged correctly. + */ +static struct notifier_block gic_fiq_ipi_notifier = { + .notifier_call = gic_handle_fiq_ipi, +}; #else /* CONFIG_FIQ */ +static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) {} static inline void gic_init_fiq(struct gic_chip_data *gic, irq_hw_number_t first_irq, unsigned int num_irqs) {} @@ -527,14 +585,19 @@ static void gic_cpu_init(struct gic_chip_data *gic) gic_cpu_config(dist_base, NULL);
/* - * Set all PPI and SGI interrupts to be group 1. - * - * If grouping is not available (not implemented or prohibited by - * security mode) these registers are read-as-zero/write-ignored. + * Optionally set all PPI and SGI interrupts to be group 1. */ if (gic_data_fiq_enable(gic)) writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0);
+ /* + * Optionally shift the FIQ based IPIs to group 0. + */ + if (gic_data_fiq_enable(gic)) + for (i = 0; i < 16; i++) + if (SMP_IPI_FIQ_MASK & (1 << i)) + gic_set_group_irq(dist_base, i, 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); if (gic_data_fiq_enable(gic)) writel_relaxed(0x1f, base + GIC_CPU_CTRL); @@ -747,7 +810,17 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) unsigned long flags, map = 0; unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags); + /* + * The locking in this function ensures we don't use stale cpu mappings + * and thus we never route an IPI to the wrong physical core during a + * big.LITTLE switch. The switch code takes both of these locks meaning + * we can choose whichever lock is safe to use from our current calling + * context. + */ + if (in_nmi()) + raw_spin_lock(&fiq_safe_migration_lock); + else + raw_spin_lock_irqsave(&irq_controller_lock, flags);
/* Convert our logical CPU mask into a physical one. */ for_each_cpu(cpu, mask) @@ -761,12 +834,16 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
/* this always happens on GIC0 */ softint = map << 16 | irq; - if (gic_data_fiq_enable(&gic_data[0])) + if (gic_data_fiq_enable(&gic_data[0]) && + !(SMP_IPI_FIQ_MASK & (1 << irq))) softint |= 0x8000; writel_relaxed(softint, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
- raw_spin_unlock_irqrestore(&irq_controller_lock, flags); + if (in_nmi()) + raw_spin_unlock(&fiq_safe_migration_lock); + else + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } #endif
@@ -814,7 +891,7 @@ int gic_get_cpu_id(unsigned int cpu) * Migrate all peripheral interrupts with a target matching the current CPU * to the interface corresponding to @new_cpu_id. The CPU interface mapping * is also updated. Targets to other CPU interfaces are unchanged. - * This must be called with IRQs locally disabled. + * This must be called with IRQ and FIQ locally disabled. */ void gic_migrate_target(unsigned int new_cpu_id) { @@ -836,6 +913,7 @@ void gic_migrate_target(unsigned int new_cpu_id) ror_val = (cur_cpu_id - new_cpu_id) & 31;
raw_spin_lock(&irq_controller_lock); + raw_spin_lock(&fiq_safe_migration_lock);
/* Update the target interface for this logical CPU */ gic_cpu_map[cpu] = 1 << new_cpu_id; @@ -855,6 +933,7 @@ void gic_migrate_target(unsigned int new_cpu_id) } }
+ raw_spin_unlock(&fiq_safe_migration_lock); raw_spin_unlock(&irq_controller_lock);
/* @@ -1125,6 +1204,10 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); +#ifdef CONFIG_FIQ + if (gic_data_fiq_enable(gic)) + register_fiq_nmi_notifier(&gic_fiq_ipi_notifier); +#endif #endif set_handle_irq(gic_handle_irq); }
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 --- drivers/irqchip/irq-gic.c | 52 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 8834749..bda5a91 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -279,14 +279,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 (!gic_data_fiq_enable(gic) || 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)) { @@ -295,7 +340,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
This patch introduces callbacks to route interrupts to or away from the FIQ signal. It also causes these callbacks to be registered with the FIQ infrastructure.
This patch enable FIQ support for mach-versatile whilst mach-ep93xx, mach-netx, mach-s3c64xx and plat-samsung are unmodified (and can therefore continue to use init_FIQ() as before).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Hartley Sweeten hsweeten@visionengravers.com Cc: Ryan Mallon rmallon@gmail.com Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben-linux@fluff.org Cc: Kukjin Kim kgene.kim@samsung.com Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: linux-samsung-soc@vger.kernel.org --- arch/arm/mach-versatile/core.c | 2 +- drivers/irqchip/irq-vic.c | 92 ++++++++++++++++++++++++++++++++--------- include/linux/irqchip/arm-vic.h | 6 ++- 3 files changed, 78 insertions(+), 22 deletions(-)
diff --git a/arch/arm/mach-versatile/core.c b/arch/arm/mach-versatile/core.c index 08fb8c8..bad1d30 100644 --- a/arch/arm/mach-versatile/core.c +++ b/arch/arm/mach-versatile/core.c @@ -108,7 +108,7 @@ void __init versatile_init_irq(void)
np = of_find_matching_node_by_address(NULL, vic_of_match, VERSATILE_VIC_BASE); - __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np); + __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np ? false : true, np);
writel(~0, VA_SIC_BASE + SIC_IRQ_ENABLE_CLEAR);
diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index 7d35287..22aa126 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -36,6 +36,9 @@
#include <asm/exception.h> #include <asm/irq.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif
#include "irqchip.h"
@@ -261,11 +264,53 @@ static struct irq_domain_ops vic_irqdomain_ops = { .xlate = irq_domain_xlate_onetwocell, };
+#ifdef CONFIG_FIQ +static DEFINE_RAW_SPINLOCK(irq_controller_lock); + +static void vic_set_fiq(struct irq_data *d, bool enable) +{ + void __iomem *base = irq_data_get_irq_chip_data(d); + unsigned int irq = d->hwirq; + u32 val; + + raw_spin_lock(&irq_controller_lock); + val = readl(base + VIC_INT_SELECT); + if (enable) + val |= 1 << irq; + else + val &= ~(1 << irq); + writel(val, base + VIC_INT_SELECT); + raw_spin_unlock(&irq_controller_lock); +} + +static void vic_enable_fiq(struct irq_data *d) +{ + vic_set_fiq(d, true); +} + +static void vic_disable_fiq(struct irq_data *d) +{ + vic_set_fiq(d, false); +} + +struct fiq_chip vic_fiq = { + .fiq_enable = vic_enable_fiq, + .fiq_disable = vic_disable_fiq, +}; + +static void vic_register_fiq(int irq) +{ + fiq_register_mapping(irq, &vic_fiq); +} +#else /* CONFIG_FIQ */ +static inline void vic_register_fiq(int irq) {} +#endif /* CONFIG_FIQ */ + /** * vic_register() - Register a VIC. * @base: The base address of the VIC. * @parent_irq: The parent IRQ if cascaded, else 0. - * @irq: The base IRQ for the VIC. + * @irq_start: The base IRQ for the VIC. * @valid_sources: bitmask of valid interrupts * @resume_sources: bitmask of interrupts allowed for resume sources. * @node: The device tree node associated with the VIC. @@ -277,12 +322,13 @@ static struct irq_domain_ops vic_irqdomain_ops = { * This also configures the IRQ domain for the VIC. */ static void __init vic_register(void __iomem *base, unsigned int parent_irq, - unsigned int irq, + unsigned int irq_start, u32 valid_sources, u32 resume_sources, - struct device_node *node) + bool map_fiqs, struct device_node *node) { struct vic_device *v; int i; + unsigned int irq;
if (vic_id >= ARRAY_SIZE(vic_devices)) { printk(KERN_ERR "%s: too few VICs, increase CONFIG_ARM_VIC_NR\n", __func__); @@ -301,15 +347,19 @@ static void __init vic_register(void __iomem *base, unsigned int parent_irq, irq_set_chained_handler(parent_irq, vic_handle_irq_cascaded); }
- v->domain = irq_domain_add_simple(node, fls(valid_sources), irq, + v->domain = irq_domain_add_simple(node, fls(valid_sources), irq_start, &vic_irqdomain_ops, v); /* create an IRQ mapping for each valid IRQ */ - for (i = 0; i < fls(valid_sources); i++) - if (valid_sources & (1 << i)) - irq_create_mapping(v->domain, i); + for (i = 0; i < fls(valid_sources); i++) { + if (valid_sources & (1 << i)) { + irq = irq_create_mapping(v->domain, i); + vic_register_fiq(irq); + } + } + /* If no base IRQ was passed, figure out our allocated base */ - if (irq) - v->irq = irq; + if (irq_start) + v->irq = irq_start; else v->irq = irq_find_mapping(v->domain, 0); } @@ -413,7 +463,8 @@ static void __init vic_clear_interrupts(void __iomem *base) * and 020 within the page. We call this "second block". */ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, - u32 vic_sources, struct device_node *node) + u32 vic_sources, bool map_fiqs, + struct device_node *node) { unsigned int i; int vic_2nd_block = ((unsigned long)base & ~PAGE_MASK) != 0; @@ -439,12 +490,12 @@ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, writel(32, base + VIC_PL190_DEF_VECT_ADDR); }
- vic_register(base, 0, irq_start, vic_sources, 0, node); + vic_register(base, 0, irq_start, vic_sources, 0, map_fiqs, node); }
void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, - struct device_node *node) + u32 vic_sources, u32 resume_sources, + bool map_fiqs, struct device_node *node) { unsigned int i; u32 cellid = 0; @@ -462,7 +513,7 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
switch(vendor) { case AMBA_VENDOR_ST: - vic_init_st(base, irq_start, vic_sources, node); + vic_init_st(base, irq_start, vic_sources, map_fiqs, node); return; default: printk(KERN_WARNING "VIC: unknown vendor, continuing anyways\n"); @@ -479,7 +530,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
vic_init2(base);
- vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, node); + vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, + map_fiqs, node); }
/** @@ -492,7 +544,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, void __init vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources) { - __vic_init(base, 0, irq_start, vic_sources, resume_sources, NULL); + __vic_init(base, 0, irq_start, vic_sources, resume_sources, + false, NULL); }
/** @@ -511,7 +564,8 @@ int __init vic_init_cascaded(void __iomem *base, unsigned int parent_irq, struct vic_device *v;
v = &vic_devices[vic_id]; - __vic_init(base, parent_irq, 0, vic_sources, resume_sources, NULL); + __vic_init(base, parent_irq, 0, vic_sources, resume_sources, false, + NULL); /* Return out acquired base */ return v->irq; } @@ -535,9 +589,9 @@ int __init vic_of_init(struct device_node *node, struct device_node *parent) of_property_read_u32(node, "valid-wakeup-mask", &wakeup_mask);
/* - * Passing 0 as first IRQ makes the simple domain allocate descriptors + * Passing 0 as first IRQ makes the domain allocate descriptors. */ - __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, node); + __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, true, node);
return 0; } diff --git a/include/linux/irqchip/arm-vic.h b/include/linux/irqchip/arm-vic.h index ba46c79..30ab39f 100644 --- a/include/linux/irqchip/arm-vic.h +++ b/include/linux/irqchip/arm-vic.h @@ -30,8 +30,10 @@ struct device_node; struct pt_regs;
void __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, struct device_node *node); -void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources); + u32 vic_sources, u32 resume_sources, + bool map_fiqs, struct device_node *node); +void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, + u32 resume_sources); int vic_init_cascaded(void __iomem *base, unsigned int parent_irq, u32 vic_sources, u32 resume_sources);
At present this console is selectively enabled/disabled by NULL checking arch_kgdb_ops.enable_nmi. In practice this requires the architecture dependant code to implement some kind of control (e.g. module arguments) to enable/disable the feature.
The kernel already provide the perfectly adequade console= argument to do this. Let's us that instead, if nothing else, it makes any documentation architecture neutral.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/tty/serial/kgdb_nmi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/tty/serial/kgdb_nmi.c b/drivers/tty/serial/kgdb_nmi.c index 6ec7501..129dc5b 100644 --- a/drivers/tty/serial/kgdb_nmi.c +++ b/drivers/tty/serial/kgdb_nmi.c @@ -46,6 +46,8 @@ static atomic_t kgdb_nmi_num_readers = ATOMIC_INIT(0);
static int kgdb_nmi_console_setup(struct console *co, char *options) { + arch_kgdb_ops.enable_nmi(1); + /* The NMI console uses the dbg_io_ops to issue console messages. To * avoid duplicate messages during kdb sessions we must inform kdb's * I/O utilities that messages sent to the console will automatically @@ -77,7 +79,7 @@ static struct console kgdb_nmi_console = { .setup = kgdb_nmi_console_setup, .write = kgdb_nmi_console_write, .device = kgdb_nmi_console_device, - .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_ENABLED, + .flags = CON_PRINTBUFFER | CON_ANYTIME, .index = -1, };
@@ -354,7 +356,6 @@ int kgdb_register_nmi_console(void) }
register_console(&kgdb_nmi_console); - arch_kgdb_ops.enable_nmi(1);
return 0; err_drv_reg:
Universally adopt container_of() for all pointer conversion from uart_port to uart_amba_port.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/tty/serial/amba-pl011.c | 54 +++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 18 deletions(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 8572f2a..02016fc 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -678,7 +678,8 @@ static void pl011_dma_flush_buffer(struct uart_port *port) __releases(&uap->port.lock) __acquires(&uap->port.lock) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
if (!uap->using_tx_dma) return; @@ -1163,7 +1164,8 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap)
static void pl011_stop_tx(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
uap->im &= ~UART011_TXIM; writew(uap->im, uap->port.membase + UART011_IMSC); @@ -1172,7 +1174,8 @@ static void pl011_stop_tx(struct uart_port *port)
static void pl011_start_tx(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
if (!pl011_dma_tx_start(uap)) { uap->im |= UART011_TXIM; @@ -1182,7 +1185,8 @@ static void pl011_start_tx(struct uart_port *port)
static void pl011_stop_rx(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
uap->im &= ~(UART011_RXIM|UART011_RTIM|UART011_FEIM| UART011_PEIM|UART011_BEIM|UART011_OEIM); @@ -1193,7 +1197,8 @@ static void pl011_stop_rx(struct uart_port *port)
static void pl011_enable_ms(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
uap->im |= UART011_RIMIM|UART011_CTSMIM|UART011_DCDMIM|UART011_DSRMIM; writew(uap->im, uap->port.membase + UART011_IMSC); @@ -1349,14 +1354,16 @@ static irqreturn_t pl011_int(int irq, void *dev_id)
static unsigned int pl011_tx_empty(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int status = readw(uap->port.membase + UART01x_FR); return status & (UART01x_FR_BUSY|UART01x_FR_TXFF) ? 0 : TIOCSER_TEMT; }
static unsigned int pl011_get_mctrl(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int result = 0; unsigned int status = readw(uap->port.membase + UART01x_FR);
@@ -1374,7 +1381,8 @@ static unsigned int pl011_get_mctrl(struct uart_port *port)
static void pl011_set_mctrl(struct uart_port *port, unsigned int mctrl) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int cr;
cr = readw(uap->port.membase + UART011_CR); @@ -1402,7 +1410,8 @@ static void pl011_set_mctrl(struct uart_port *port, unsigned int mctrl)
static void pl011_break_ctl(struct uart_port *port, int break_state) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned long flags; unsigned int lcr_h;
@@ -1420,7 +1429,8 @@ static void pl011_break_ctl(struct uart_port *port, int break_state)
static void pl011_quiesce_irqs(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned char __iomem *regs = uap->port.membase;
writew(readw(regs + UART011_MIS), regs + UART011_ICR); @@ -1442,7 +1452,8 @@ static void pl011_quiesce_irqs(struct uart_port *port)
static int pl011_get_poll_char(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int status;
/* @@ -1461,7 +1472,8 @@ static int pl011_get_poll_char(struct uart_port *port) static void pl011_put_poll_char(struct uart_port *port, unsigned char ch) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_TXFF) barrier(); @@ -1473,7 +1485,8 @@ static void pl011_put_poll_char(struct uart_port *port,
static int pl011_hwinit(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); int retval;
/* Optionaly enable pins to be muxed in and configured */ @@ -1526,7 +1539,8 @@ static void pl011_write_lcr_h(struct uart_amba_port *uap, unsigned int lcr_h)
static int pl011_startup(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int cr, lcr_h, fbrd, ibrd; int retval;
@@ -1618,7 +1632,8 @@ static void pl011_shutdown_channel(struct uart_amba_port *uap,
static void pl011_shutdown(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int cr;
/* @@ -1680,7 +1695,8 @@ static void pl011_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int lcr_h, old_cr; unsigned long flags; unsigned int baud, quot, clkdiv; @@ -1822,7 +1838,8 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios,
static const char *pl011_type(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); return uap->port.type == PORT_AMBA ? uap->type : NULL; }
@@ -1900,7 +1917,8 @@ static struct uart_amba_port *amba_ports[UART_NR];
static void pl011_console_putchar(struct uart_port *port, int ch) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_TXFF) barrier();
This patch hoists pl011_hwinit() further up within the driver. This permits a later patch to introduce an extended .poll_init callback that does more than pure hardware initialization.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/amba-pl011.c | 78 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 39 deletions(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 02016fc..0b06dcf 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1425,6 +1425,45 @@ static void pl011_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&uap->port.lock, flags); }
+static int pl011_hwinit(struct uart_port *port) +{ + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); + int retval; + + /* Optionaly enable pins to be muxed in and configured */ + pinctrl_pm_select_default_state(port->dev); + + /* + * Try to enable the clock producer. + */ + retval = clk_prepare_enable(uap->clk); + if (retval) + return retval; + + uap->port.uartclk = clk_get_rate(uap->clk); + + /* Clear pending error and receive interrupts */ + writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | + UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); + + /* + * Save interrupts enable mask, and enable RX interrupts in case if + * the interrupt is used for NMI entry. + */ + uap->im = readw(uap->port.membase + UART011_IMSC); + writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); + + if (dev_get_platdata(uap->port.dev)) { + struct amba_pl011_data *plat; + + plat = dev_get_platdata(uap->port.dev); + if (plat->init) + plat->init(); + } + return 0; +} + #ifdef CONFIG_CONSOLE_POLL
static void pl011_quiesce_irqs(struct uart_port *port) @@ -1483,45 +1522,6 @@ static void pl011_put_poll_char(struct uart_port *port,
#endif /* CONFIG_CONSOLE_POLL */
-static int pl011_hwinit(struct uart_port *port) -{ - struct uart_amba_port *uap = - container_of(port, struct uart_amba_port, port); - int retval; - - /* Optionaly enable pins to be muxed in and configured */ - pinctrl_pm_select_default_state(port->dev); - - /* - * Try to enable the clock producer. - */ - retval = clk_prepare_enable(uap->clk); - if (retval) - return retval; - - uap->port.uartclk = clk_get_rate(uap->clk); - - /* Clear pending error and receive interrupts */ - writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | - UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); - - /* - * Save interrupts enable mask, and enable RX interrupts in case if - * the interrupt is used for NMI entry. - */ - uap->im = readw(uap->port.membase + UART011_IMSC); - writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); - - if (dev_get_platdata(uap->port.dev)) { - struct amba_pl011_data *plat; - - plat = dev_get_platdata(uap->port.dev); - if (plat->init) - plat->init(); - } - return 0; -} - static void pl011_write_lcr_h(struct uart_amba_port *uap, unsigned int lcr_h) { writew(lcr_h, uap->port.membase + uap->lcrh_rx);
Speculatively register a FIQ resource with KGDB. KGDB will only accept it if the kgdb/fiq feature is enabled (both with compile time and runtime switches) and the interrupt controller supports FIQ.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the amba-pl011 driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/amba-pl011.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 0b06dcf..63c67b0 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -58,6 +58,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/sizes.h> #include <linux/io.h> +#include <linux/kgdb.h>
#define UART_NR 14
@@ -1466,6 +1467,18 @@ static int pl011_hwinit(struct uart_port *port)
#ifdef CONFIG_CONSOLE_POLL
+static int pl011_poll_init(struct uart_port *port) +{ + int retval = pl011_hwinit(port); + +#ifdef CONFIG_KGDB_FIQ + if (retval == 0) + kgdb_register_fiq(port->irq); +#endif + + return retval; +} + static void pl011_quiesce_irqs(struct uart_port *port) { struct uart_amba_port *uap = @@ -1905,7 +1918,7 @@ static struct uart_ops amba_pl011_pops = { .config_port = pl011_config_port, .verify_port = pl011_verify_port, #ifdef CONFIG_CONSOLE_POLL - .poll_init = pl011_hwinit, + .poll_init = pl011_poll_init, .poll_get_char = pl011_get_poll_char, .poll_put_char = pl011_put_poll_char, #endif
Add a .poll_init() function that enables UART RX and registers the UART's irq with KGDB. By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user).
Note that the RX is not only enabled but also unmasked. This is required because otherwise the FIQ handler could never trigger. This unmask is copied from similar code in amba-pl011.c .
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/st-asc.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 8b2d735..2b5eb6e 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -30,6 +30,7 @@ #include <linux/of_platform.h> #include <linux/serial_core.h> #include <linux/clk.h> +#include <linux/kgdb.h>
#define DRIVER_NAME "st-asc" #define ASC_SERIAL_NAME "ttyAS" @@ -607,6 +608,25 @@ asc_verify_port(struct uart_port *port, struct serial_struct *ser) }
#ifdef CONFIG_CONSOLE_POLL + +#ifdef CONFIG_KGDB_FIQ +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int asc_poll_init(struct uart_port *port) +{ + struct asc_port *ascport = container_of(port, struct asc_port, port); + + /* register the FIQ with kgdb */ + kgdb_register_fiq(ascport->port.irq); + + /* enable RX interrupts in case the interrupt is used for NMI entry. */ + asc_enable_rx_interrupts(port); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + /* * Console polling routines for writing and reading from the uart while * in an interrupt or debug context (i.e. kgdb). @@ -649,6 +669,9 @@ static struct uart_ops asc_uart_ops = { .verify_port = asc_verify_port, .pm = asc_pm, #ifdef CONFIG_CONSOLE_POLL +#ifdef CONFIG_KGDB_FIQ + .poll_init = asc_poll_init, +#endif /* CONFIG_KGDB_FIQ */ .poll_get_char = asc_get_poll_char, .poll_put_char = asc_put_poll_char, #endif /* CONFIG_CONSOLE_POLL */
The architectures where this peripheral exists (ARM and SH) have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This change means the driver is no longer portable and therefore no longer suitable for compile testing.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/st-asc.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 26cec64..e9b1735 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1527,7 +1527,7 @@ config SERIAL_FSL_LPUART_CONSOLE config SERIAL_ST_ASC tristate "ST ASC serial port support" select SERIAL_CORE - depends on ARM || COMPILE_TEST + depends on ARM help This driver is for the on-chip Asychronous Serial Controller on STMicroelectronics STi SoCs. diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 2b5eb6e..df709ee 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -152,12 +152,12 @@ static inline struct asc_port *to_asc_port(struct uart_port *port)
static inline u32 asc_in(struct uart_port *port, u32 offset) { - return readl(port->membase + offset); + return readl_relaxed(port->membase + offset); }
static inline void asc_out(struct uart_port *port, u32 offset, u32 value) { - writel(value, port->membase + offset); + writel_relaxed(value, port->membase + offset); }
/*
From: Dirk Behme dirk.behme@de.bosch.com
Looking at the get_poll_char() function of the 8250.c serial driver, we learn:
* poll_get_char() doesn't have to save/disable/restore the interrupt registers. No interrupt handling is needed in this function at all. Remove it.
* Don't block in case there is no data available. So instead blocking in the do {} while loop, just return with NO_POLL_CHAR, immediately .
Additionally, while the i.MX6 register URXD[7-0] contain the RX_DATA, the upper bits of this register (URXD[15-10]) might contain some control flags. To ensure that these are not returned with the data read, just mask out URXD[7-0].
These changes fix the 'hang' working with kdb:
$ echo ttymxc3 > /sys/module/kgdboc/parameters/kgdboc $ echo g >/proc/sysrq-trigger [0]kdb> help ... <hang>
Signed-off-by: Dirk Behme dirk.behme@de.bosch.com Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/imx.c | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 044e86d..983668a 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -80,6 +80,7 @@ #define URXD_FRMERR (1<<12) #define URXD_BRK (1<<11) #define URXD_PRERR (1<<10) +#define URXD_RX_DATA (0xFF<<0) #define UCR1_ADEN (1<<15) /* Auto detect interrupt */ #define UCR1_ADBR (1<<14) /* Auto detect baud rate */ #define UCR1_TRDYEN (1<<13) /* Transmitter ready interrupt enable */ @@ -1506,32 +1507,10 @@ imx_verify_port(struct uart_port *port, struct serial_struct *ser) #if defined(CONFIG_CONSOLE_POLL) static int imx_poll_get_char(struct uart_port *port) { - struct imx_port_ucrs old_ucr; - unsigned int status; - unsigned char c; - - /* save control registers */ - imx_port_ucrs_save(port, &old_ucr); - - /* disable interrupts */ - writel(UCR1_UARTEN, port->membase + UCR1); - writel(old_ucr.ucr2 & ~(UCR2_ATEN | UCR2_RTSEN | UCR2_ESCI), - port->membase + UCR2); - writel(old_ucr.ucr3 & ~(UCR3_DCD | UCR3_RI | UCR3_DTREN), - port->membase + UCR3); - - /* poll */ - do { - status = readl(port->membase + USR2); - } while (~status & USR2_RDR); - - /* read */ - c = readl(port->membase + URXD0); - - /* restore control registers */ - imx_port_ucrs_restore(port, &old_ucr); + if (!(readl(port->membase + USR2) & USR2_RDR)) + return NO_POLL_CHAR;
- return c; + return readl(port->membase + URXD0) & URXD_RX_DATA; }
static void imx_poll_put_char(struct uart_port *port, unsigned char c)
This patch makes it possible to use the imx uart with KGDB's FIQ/NMI mode.
Main changes are:
.poll_init() will, if KGDB+FIQ are enabled, perform deeper hardware initialization to ensure the serial port is always active (required otherwise FIQ is not triggered by UART activity). This has an impact on power usage so it is conservatively enabled.
imx_put_poll_char() has been simplified to remove the code to disable interrupts. The present code can corrupt register state when re-entered from FIQ handler.
Both imx_put_poll_char() and imx_get_poll_char() adopt _relaxed() MMIO functions (which are safe for polled I/O and needed to avoid taking spin locks).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org Acked-by: Dirk Behme dirk.behme@de.bosch.com --- drivers/tty/serial/imx.c | 71 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 19 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 983668a..a201c61 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -49,6 +49,7 @@ #include <linux/of_device.h> #include <linux/io.h> #include <linux/dma-mapping.h> +#include <linux/kgdb.h>
#include <asm/irq.h> #include <linux/platform_data/serial-imx.h> @@ -1505,44 +1506,73 @@ imx_verify_port(struct uart_port *port, struct serial_struct *ser) }
#if defined(CONFIG_CONSOLE_POLL) + +#if defined(CONFIG_KGDB_FIQ) +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int imx_poll_init(struct uart_port *port) +{ + struct imx_port *sport = (struct imx_port *)port; + unsigned long flags; + unsigned long temp; + int retval; + + retval = clk_prepare_enable(sport->clk_ipg); + if (retval) + return retval; + retval = clk_prepare_enable(sport->clk_per); + if (retval) + clk_disable_unprepare(sport->clk_ipg); + + imx_setup_ufcr(sport, 0); + + spin_lock_irqsave(&sport->port.lock, flags); + + temp = readl(sport->port.membase + UCR1); + if (is_imx1_uart(sport)) + temp |= IMX1_UCR1_UARTCLKEN; + temp |= UCR1_UARTEN | UCR1_RRDYEN; + temp &= ~(UCR1_TXMPTYEN | UCR1_RTSDEN); + writel(temp, sport->port.membase + UCR1); + + temp = readl(sport->port.membase + UCR2); + temp |= UCR2_RXEN; + writel(temp, sport->port.membase + UCR2); + + spin_unlock_irqrestore(&sport->port.lock, flags); + + /* register the FIQ with kgdb */ + kgdb_register_fiq(sport->port.irq); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + static int imx_poll_get_char(struct uart_port *port) { - if (!(readl(port->membase + USR2) & USR2_RDR)) + if (!(readl_relaxed(port->membase + USR2) & USR2_RDR)) return NO_POLL_CHAR;
- return readl(port->membase + URXD0) & URXD_RX_DATA; + return readl_relaxed(port->membase + URXD0) & URXD_RX_DATA; }
static void imx_poll_put_char(struct uart_port *port, unsigned char c) { - struct imx_port_ucrs old_ucr; unsigned int status;
- /* save control registers */ - imx_port_ucrs_save(port, &old_ucr); - - /* disable interrupts */ - writel(UCR1_UARTEN, port->membase + UCR1); - writel(old_ucr.ucr2 & ~(UCR2_ATEN | UCR2_RTSEN | UCR2_ESCI), - port->membase + UCR2); - writel(old_ucr.ucr3 & ~(UCR3_DCD | UCR3_RI | UCR3_DTREN), - port->membase + UCR3); - /* drain */ do { - status = readl(port->membase + USR1); + status = readl_relaxed(port->membase + USR1); } while (~status & USR1_TRDY);
/* write */ - writel(c, port->membase + URTX0); + writel_relaxed(c, port->membase + URTX0);
/* flush */ do { - status = readl(port->membase + USR2); + status = readl_relaxed(port->membase + USR2); } while (~status & USR2_TXDC); - - /* restore control registers */ - imx_port_ucrs_restore(port, &old_ucr); } #endif
@@ -1563,6 +1593,9 @@ static struct uart_ops imx_pops = { .config_port = imx_config_port, .verify_port = imx_verify_port, #if defined(CONFIG_CONSOLE_POLL) +#if defined(CONFIG_KGDB_FIQ) + .poll_init = imx_poll_init, +#endif .poll_get_char = imx_poll_get_char, .poll_put_char = imx_poll_put_char, #endif
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches are seperated into three distinct groups:
1. arm specific changes; these provide multi-platform support for FIQ (including raising an IPI using FIQ to ensure effective SMP support) and extend ARM KGDB support to use the features provided.
2. irqchip changes; updates to the gic and vic drivers to provide support for routing certain interrupt sources to FIQ.
3. serial changes; driver support to allow the UART interrupt to be routed to FIQ. The already mainlined kgdb NMI infrastructure (mostly found in drivers/tty/serial/kgdb_nmi.c) will re-route the kgdb console UART's interrupt signal from IRQ to FIQ. Naturally the UART will no longer function normally and will instead be managed by kgdb using the polled I/O functions. Any character delivered to the UART causes the kgdb handler function to be called.
Tested on qemu (versatile), STiH416 (B2020 board) and Freescale i.MX6 quad (wandboard).
Changes since v10:
- Removed use of RCU notifier chains to encourage code review by ensuring no driver can use FIQ without touching code in arch/arm/kernel (Russell King)
Changes since v9:
- Split PL011 code movement into a seperate patch (Peter Hurley) - Use container_of() to convert pointers to uart_amba_port (Peter Hurley) - Clean up redundant code from pl011_poll_init (Peter Hurley) - Call do_unexp_fiq() if we receive a FIQ and CONFIG_FIQ is not set. - Ensure irq-gic.c does not call FIQ functions unless CONFIG_FIQ is set. - Introduced patch to avoid ttyNMI0 being enabled by default (replaces architecture dependant enable/disable logic). - Fixed bug in kgdb_fiq_enable_nmi() that causes SysRq-g (non-NMI debugging) to wedge the debugger.
Changes since v8:
- Significant rework to separate the FIQ exception handler from kgdb. This allows other features typically implemented using NMI on x86 to reuse the same exception handling code (Russell King) - Reunited arch/arm and driver code into a single patch series again. - Added support for raising IPIs using FIQ (makes kgdb quiesce must more robust). - Introduced a workaround for GICv1 devices to avoid FIQs being spuriously handled as IRQs.
Changes since v7:
- Introduced ack_fiq() to complement eoi_fiq(). Without this it is not possible to meet the GIC specification (previous versions worked when tested but are unpredictable according to the specification). ack_fiq() also makes is possible for drivers for devices with multiple interrupt lines to discover the interrupt source correctly.
Changes since v6:
- Corrected off-by-one comparison in has_fiq() (Nicolas Pitre) - Rewrote the FIQ stack initialization (Nicolas Pitre). This fixes a serious data corruption bug due to bad stack mismanagement. - Introduced __fiq_abt to ensure lr_abt and spsr_abt are saved and restored if we fast-interrupt an abort (Russell King). - Significantly improved the commenting of the exception handlers. - Added a call to trace_hardirqs_on() if we clear the I bit as we exit the exception handler.
Changes since v5:
- Separated anything not strictly impacting arch/arm. - Fixed a spurious add/remove of code within the series (there was broken code in intermediate patches)
For previous changes see: http://thread.gmane.org/gmane.linux.ports.arm.kernel/333901
Daniel Thompson (18): arm: fiq: Add callbacks to manage FIQ routings arm: fiq: Allow ACK and EOI to be passed to the intc arm: fiq: Replace default FIQ handler arm: smp: Introduce a special IPI signalled using FIQ arm: KGDB/KDB FIQ support irqchip: gic: Provide support for interrupt grouping irqchip: gic: Add support for FIQ management irqchip: gic: Remove spin locks from eoi_irq irqchip: gic: Add support for IPI FIQ irqchip: gic: Group 0 workaround. irqchip: vic: Add support for FIQ management serial: kgdb_nmi: No CON_ENABLED by default serial: amba-pl011: Use container_of() to get uart_amba_port serial: amba-pl011: Move pl011_hwinit() serial: amba-pl011: Pass FIQ information to KGDB. serial: asc: Add support for KGDB's FIQ/NMI mode serial: asc: Adopt readl_/writel_relaxed() serial: imx: Add support for KGDB's FIQ/NMI mode
Dirk Behme (1): serial: imx: clean up imx_poll_get_char()
arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 19 +++ arch/arm/include/asm/fiq.h | 17 +++ arch/arm/include/asm/hardirq.h | 2 +- arch/arm/include/asm/kgdb.h | 5 + arch/arm/include/asm/smp.h | 7 + arch/arm/kernel/entry-armv.S | 129 +++++++++++++++-- arch/arm/kernel/fiq.c | 146 ++++++++++++++++++- arch/arm/kernel/kgdb.c | 112 ++++++++++++++- arch/arm/kernel/setup.c | 8 +- arch/arm/kernel/smp.c | 15 ++ arch/arm/mach-versatile/core.c | 2 +- drivers/irqchip/irq-gic.c | 302 +++++++++++++++++++++++++++++++++++++--- drivers/irqchip/irq-vic.c | 92 +++++++++--- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/amba-pl011.c | 143 +++++++++++-------- drivers/tty/serial/imx.c | 88 +++++++----- drivers/tty/serial/kgdb_nmi.c | 5 +- drivers/tty/serial/st-asc.c | 27 +++- include/linux/irqchip/arm-gic.h | 3 + include/linux/irqchip/arm-vic.h | 6 +- 21 files changed, 975 insertions(+), 157 deletions(-)
-- 1.9.3
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
We solve this by introducing a flexible mapping that allows interrupt controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 8 ++++ arch/arm/kernel/fiq.c | 103 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 2 deletions(-)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index d493d0b..ed44528 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -16,8 +16,14 @@ #ifndef __ASM_FIQ_H #define __ASM_FIQ_H
+#include <linux/irq.h> #include <asm/ptrace.h>
+struct fiq_chip { + void (*fiq_enable)(struct irq_data *data); + void (*fiq_disable)(struct irq_data *data); +}; + struct fiq_handler { struct fiq_handler *next; /* Name @@ -38,6 +44,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern bool has_fiq(int fiq); +extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
/* helpers defined in fiqasm.S: */ extern void __set_fiq_regs(unsigned long const *regs); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d..5d831cf 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -40,6 +40,9 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/seq_file.h> +#include <linux/irq.h> +#include <linux/radix-tree.h> +#include <linux/slab.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> @@ -52,7 +55,15 @@ (unsigned)&vector_fiq_offset; \ })
+struct fiq_data { + struct fiq_chip *fiq_chip; + struct irq_data *irq_data; +}; + static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_data_tree, GFP_KERNEL); +static DEFINE_MUTEX(fiq_data_mutex);
/* Default reacquire function * - we always relinquish FIQ control @@ -127,18 +138,65 @@ void release_fiq(struct fiq_handler *f) while (current_fiq->fiq_op(current_fiq->dev_id, 0)); }
-static int fiq_start; +static struct fiq_data *lookup_fiq_data(int fiq) +{ + struct fiq_data *data; + + rcu_read_lock(); + data = radix_tree_lookup(&fiq_data_tree, fiq); + rcu_read_unlock(); + + return data; +}
void enable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_enable) + data->fiq_chip->fiq_enable(data->irq_data); + enable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + enable_irq(fiq + fiq_start); }
void disable_fiq(int fiq) { + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) { + if (data->fiq_chip->fiq_disable) + data->fiq_chip->fiq_disable(data->irq_data); + disable_irq(fiq); + return; + } + + if (WARN_ON(fiq_start == -1)) + return; + disable_irq(fiq + fiq_start); }
+bool has_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data) + return true; + + if (fiq_start == -1) + return false; + + return fiq >= fiq_start; +} +EXPORT_SYMBOL(has_fiq); + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ @@ -147,9 +205,50 @@ EXPORT_SYMBOL(release_fiq); EXPORT_SYMBOL(enable_fiq); EXPORT_SYMBOL(disable_fiq);
+/* + * Add a mapping from a Linux irq to the fiq data. + */ +void fiq_register_mapping(int irq, struct fiq_chip *chip) +{ + struct fiq_data *fiq_data = NULL; + int res; + + /* fiq_register_mapping can't be mixed with init_FIQ */ + BUG_ON(fiq_start != -1); + + fiq_data = kmalloc(sizeof(*fiq_data), GFP_KERNEL); + if (!fiq_data) + goto err; + + fiq_data->fiq_chip = chip; + fiq_data->irq_data = irq_get_irq_data(irq); + BUG_ON(!fiq_data->irq_data); + + mutex_lock(&fiq_data_mutex); + res = radix_tree_insert(&fiq_data_tree, irq, fiq_data); + mutex_unlock(&fiq_data_mutex); + if (res) + goto err; + + return; + +err: + kfree(fiq_data); + pr_err("fiq: Cannot register mapping %d\n", irq); +} + +/* + * Set the offset between normal IRQs and their FIQ shadows. + */ void __init init_FIQ(int start) { + fiq_start = start; +} + +static int __init init_default_fiq_handler(void) +{ unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); - fiq_start = start; + return 0; } +pure_initcall(init_default_fiq_handler);
On Tue, Sep 02, 2014 at 02:00:35PM +0100, Daniel Thompson wrote:
void enable_fiq(int fiq) {
- struct fiq_data *data = lookup_fiq_data(fiq);
- if (data) {
if (data->fiq_chip->fiq_enable)
data->fiq_chip->fiq_enable(data->irq_data);
enable_irq(fiq);
Why do we call the FIQ chip's enable and enable_irq() as well?
void disable_fiq(int fiq) {
- struct fiq_data *data = lookup_fiq_data(fiq);
- if (data) {
if (data->fiq_chip->fiq_disable)
data->fiq_chip->fiq_disable(data->irq_data);
disable_irq(fiq);
Same question here.
+bool has_fiq(int fiq) +{
- struct fiq_data *data = lookup_fiq_data(fiq);
- if (data)
return true;
- if (fiq_start == -1)
return false;
- return fiq >= fiq_start;
Are you sure this is correct... it looks wrong to me.
On Tue, 2 Sep 2014, Daniel Thompson wrote:
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
You are missing to explain WHY it is too inflexible ....
We solve this by introducing a flexible mapping that allows interrupt
Flexible in that context reads like random semantics. And that's definitely nothing we want to see in the context of interrupts/fiqs
controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
+#include <linux/irq.h> #include <asm/ptrace.h> +struct fiq_chip {
- void (*fiq_enable)(struct irq_data *data);
- void (*fiq_disable)(struct irq_data *data);
+};
So you define an ARM specific extension to the irq infrastructure which is completely ignoring that there might be other architectures which have the same issue?
Aside of that you define that structure w/o documenting in any way what the semantics of the callbacks are and how struct irq_data is supposed to be (ab)used there.
+struct fiq_data {
- struct fiq_chip *fiq_chip;
- struct irq_data *irq_data;
+};
Nice. Another completely undocumented data structure.
static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_data_tree, GFP_KERNEL);
Along with a radix tree, which again lacks any form of documentation.
-static int fiq_start; +static struct fiq_data *lookup_fiq_data(int fiq) +{
- struct fiq_data *data;
- rcu_read_lock();
- data = radix_tree_lookup(&fiq_data_tree, fiq);
- rcu_read_unlock();
- return data;
That makes a lot of sense. NOT!
What kind of protection is that rcu_read_lock/unlock pair providing for the return value? Just that it has been valid at lookup time?
What protects against concurrent calls to the various usage sites which shine by the lack of serialization ...
void enable_fiq(int fiq) {
- struct fiq_data *data = lookup_fiq_data(fiq);
- if (data) {
if (data->fiq_chip->fiq_enable)
data->fiq_chip->fiq_enable(data->irq_data);
enable_irq(fiq);
This is ass backwards, really.
The FIQ target is a property of the interrupt line right?
So why the heck do you need a separate chip/data representation including a radix tree and whatever? Just because you can?
What is wrong to tell the irq core code via a flag that a particular interrupt should be targeted at FIQ instead of a regular interrupt?
Nothing as far as I can tell. And that would even allow to retarget an already enabled interrupt to FIQ or vice versa.
So all your patch does is trying to (ab)use an existing interface which was created in the last millenium for totally different reasons and in a totally different context.
But you fail completely to understand what that interface is doing:
It merily maps the Linux virq to a FIQ via an offset.
While I understand that this is not workable on multiplatform kernels, I really cannot understand your attempt to change this by adding complex functionality to a primitive offset mapping function.
You just add some randomly defined new chip/data combination which abuses the irq_data structure under the hood instead of properly integrating that irq chip property into irqchip/irqdata itself.
So instead of thinking about the properties of the hardware and questioning the existing mechanism, you just bolt some crap onto it and inflict a gazillion of pointless modifications to the irq chip drivers without any value.
So all it needs is
1) Adding a property flag to the irq chip which signals that it supports FIQ/NMI targets
2) Adding a interface which allows to flag a particular interrupt line targeted to either the regular or the FIQ/NMI which consults the flag added by #1
3) Let the chip driver deal with the target flag at a minimal intrusive level w/o registering racy callbacks designed in hell and imposing arch specific data structures, storage etc. for no value
Thanks,
tglx
On 03/09/14 01:03, Thomas Gleixner wrote:
On Tue, 2 Sep 2014, Daniel Thompson wrote:
Currently enable_fiq/disable_fiq use a simple offset to convert an IRQ virq into a FIQ virq. This is too inflexible for multi-platform kernels and makes runtime error checking impossible.
You are missing to explain WHY it is too inflexible ....
We solve this by introducing a flexible mapping that allows interrupt
Flexible in that context reads like random semantics. And that's definitely nothing we want to see in the context of interrupts/fiqs
controllers that support FIQ to register those mappings. This, in turn, makes it much possible for drivers in DT kernels to install FIQ handlers without knowing anything about the interrupt controller.
+#include <linux/irq.h> #include <asm/ptrace.h> +struct fiq_chip {
- void (*fiq_enable)(struct irq_data *data);
- void (*fiq_disable)(struct irq_data *data);
+};
So you define an ARM specific extension to the irq infrastructure which is completely ignoring that there might be other architectures which have the same issue?
Aside of that you define that structure w/o documenting in any way what the semantics of the callbacks are and how struct irq_data is supposed to be (ab)used there.
+struct fiq_data {
- struct fiq_chip *fiq_chip;
- struct irq_data *irq_data;
+};
Nice. Another completely undocumented data structure.
static unsigned long no_fiq_insn; +static int fiq_start = -1; +static RADIX_TREE(fiq_data_tree, GFP_KERNEL);
Along with a radix tree, which again lacks any form of documentation.
-static int fiq_start; +static struct fiq_data *lookup_fiq_data(int fiq) +{
- struct fiq_data *data;
- rcu_read_lock();
- data = radix_tree_lookup(&fiq_data_tree, fiq);
- rcu_read_unlock();
- return data;
That makes a lot of sense. NOT!
What kind of protection is that rcu_read_lock/unlock pair providing for the return value? Just that it has been valid at lookup time?
What protects against concurrent calls to the various usage sites which shine by the lack of serialization ...
The locking here ought to safe because there is no means to unregister fiq data (the two irqchip drivers I looked at has no need for that) so once looked up the data will be valid.
Thus it's only purpose was to prevent incorrect traversals of the radix tree.
That said, this doesn't make it right.
void enable_fiq(int fiq) {
- struct fiq_data *data = lookup_fiq_data(fiq);
- if (data) {
if (data->fiq_chip->fiq_enable)
data->fiq_chip->fiq_enable(data->irq_data);
enable_irq(fiq);
This is ass backwards, really.
The FIQ target is a property of the interrupt line right?
So why the heck do you need a separate chip/data representation including a radix tree and whatever? Just because you can?
What is wrong to tell the irq core code via a flag that a particular interrupt should be targeted at FIQ instead of a regular interrupt?
Nothing as far as I can tell. And that would even allow to retarget an already enabled interrupt to FIQ or vice versa.
So all your patch does is trying to (ab)use an existing interface which was created in the last millenium for totally different reasons and in a totally different context.
But you fail completely to understand what that interface is doing:
It merily maps the Linux virq to a FIQ via an offset.
While I understand that this is not workable on multiplatform kernels, I really cannot understand your attempt to change this by adding complex functionality to a primitive offset mapping function.
You just add some randomly defined new chip/data combination which abuses the irq_data structure under the hood instead of properly integrating that irq chip property into irqchip/irqdata itself.
So instead of thinking about the properties of the hardware and questioning the existing mechanism, you just bolt some crap onto it and inflict a gazillion of pointless modifications to the irq chip drivers without any value.
So all it needs is
Adding a property flag to the irq chip which signals that it supports FIQ/NMI targets
Adding a interface which allows to flag a particular interrupt line targeted to either the regular or the FIQ/NMI which consults the flag added by #1
Let the chip driver deal with the target flag at a minimal intrusive level w/o registering racy callbacks designed in hell and imposing arch specific data structures, storage etc. for no value
Thanks for the comments.
I'll get working along these lines.
Modern ARM interrupt controllers require an ACK as interrupts are taken and an EOI on completion. The FIQ code currently does not provide any API to perform this.
This patch provides this API, implemented by adding two callbacks to the fiq_chip structure.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Acked-by: Nicolas Pitre nico@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Fabio Estevam festevam@gmail.com --- arch/arm/include/asm/fiq.h | 9 +++++++++ arch/arm/kernel/fiq.c | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+)
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index ed44528..a25c952 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -22,6 +22,13 @@ struct fiq_chip { void (*fiq_enable)(struct irq_data *data); void (*fiq_disable)(struct irq_data *data); + + /* .fiq_ack() and .fiq_eoi() will be called from the FIQ + * handler. For this reason they must not use spin locks (or any + * other locks). + */ + int (*fiq_ack)(struct irq_data *data); + void (*fiq_eoi)(struct irq_data *data); };
struct fiq_handler { @@ -44,6 +51,8 @@ extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); +extern int ack_fiq(int fiq); +extern void eoi_fiq(int fiq); extern bool has_fiq(int fiq); extern void fiq_register_mapping(int irq, struct fiq_chip *chip);
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 5d831cf..3ccaa8c 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -183,6 +183,25 @@ void disable_fiq(int fiq) disable_irq(fiq + fiq_start); }
+int ack_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_ack) + return data->fiq_chip->fiq_ack(data->irq_data); + + return fiq; +} + +void eoi_fiq(int fiq) +{ + struct fiq_data *data = lookup_fiq_data(fiq); + + if (data && data->fiq_chip->fiq_eoi) + data->fiq_chip->fiq_eoi(data->irq_data); +} +EXPORT_SYMBOL(eoi_fiq); + bool has_fiq(int fiq) { struct fiq_data *data = lookup_fiq_data(fiq);
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com --- arch/arm/kernel/entry-armv.S | 129 +++++++++++++++++++++++++++++++++++++++---- arch/arm/kernel/fiq.c | 19 +++++++ arch/arm/kernel/setup.c | 8 ++- 3 files changed, 145 insertions(+), 11 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..ef64333 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -79,6 +79,15 @@ #endif .endm
+ .macro fiq_handler + ldr r1, =.LChandle_fiq + mov r0, sp + adr lr, BSYM(9998f) + ldr pc, [r1] +9998: + .endm + + #ifdef CONFIG_KPROBES .section .kprobes.text,"ax",%progbits #else @@ -146,7 +155,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0 + .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) @@ -183,10 +192,35 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6}
#ifdef CONFIG_TRACE_IRQFLAGS + .if \call_trace bl trace_hardirqs_off + .endif #endif .endm
+@ +@ svc_exit_via_fiq - similar to svc_exit but switches to FIQ mode before exit +@ +@ This macro acts in a similar manner to svc_exit but switches to FIQ +@ mode to restore the final part of the register state. +@ +@ We cannot use the normal svc_exit procedure because that would +@ clobber spsr_svc (FIQ could be delivered during the first few instructions +@ of vector_swi meaning its contents have not been saved anywhere). +@ + .macro svc_exit_via_fiq, rpsr + + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + .endm + .align 5 __dabt_svc: svc_entry @@ -295,6 +329,14 @@ __pabt_svc: ENDPROC(__pabt_svc)
.align 5 +__fiq_svc: + svc_entry 0, 0 + fiq_handler + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + + .align 5 .LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -303,6 +345,43 @@ ENDPROC(__pabt_svc) #endif .LCfp: .word fp_enter +.LChandle_fiq: +#ifdef CONFIG_FIQ + .word fiq_nmi_handler +#else + .word do_unexp_fiq +#endif + +/* + * Abort mode handlers + */ + +@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@ + .align 5 +__fiq_abt: + svc_entry 0, 0 + + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov r0, lr @ Save lr_abt + mrs r1, spsr @ Save spsr_abt, abort is now safe + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + push {r0 - r1} + + fiq_handler + + pop {r0 - r1} + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov lr, r0 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r1 @ Restore spsr_abt + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc)
/* * User mode handlers @@ -683,6 +762,17 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
+ .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + fiq_handler + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + /* * Register switch for ARMv3 and ARMv4 processors * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info @@ -1118,17 +1208,36 @@ vector_addrexcptn: b vector_addrexcptn
/*============================================================================= - * Undefined FIQs + * FIQ "NMI" handler *----------------------------------------------------------------------------- - * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC - * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg. - * Basically to switch modes, we *HAVE* to clobber one register... brain - * damage alert! I don't think that we can execute any code in here in any - * other mode than FIQ... Ok you can switch to another mode, but you can't - * get out of that mode without clobbering one register. + * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 + * systems. The runtime environment for NMIs is extremely restrictive + * (NMIs can pre-empt critical sections meaning almost all locking is + * forbidden) meaning this default FIQ handling must only be used in + * circumstances where non-maskability improves robustness, such as + * watchdog or debug logic. + * + * This handler is inappropriate for high performance (fast) interrupt + * servicing and can be overridden using set_fiq_handler. */ -vector_fiq: - subs pc, lr, #4 + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 3ccaa8c..77c62b2 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -43,11 +43,14 @@ #include <linux/irq.h> #include <linux/radix-tree.h> #include <linux/slab.h> +#include <linux/irqchip/arm-gic.h>
#include <asm/cacheflush.h> #include <asm/cp15.h> +#include <asm/exception.h> #include <asm/fiq.h> #include <asm/irq.h> +#include <asm/kgdb.h> #include <asm/traps.h>
#define FIQ_OFFSET ({ \ @@ -216,6 +219,22 @@ bool has_fiq(int fiq) } EXPORT_SYMBOL(has_fiq);
+asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + nmi_enter(); + + /* these callbacks deliberately avoid using a notifier chain in + * order to ensure code review happens (drivers cannot "secretly" + * employ FIQ without modifying this chain of calls). + */ + /* list is currently empty */ + + nmi_exit(); + set_irq_regs(old_regs); +} + EXPORT_SYMBOL(set_fiq_handler); EXPORT_SYMBOL(__set_fiq_regs); /* defined in fiqasm.S */ EXPORT_SYMBOL(__get_fiq_regs); /* defined in fiqasm.S */ diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3]; + u32 fiq[3]; } ____cacheline_aligned;
#ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t" - "msr cpsr_c, %7" + "msr cpsr_c, %7\n\t" + "add r14, %0, %8\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE), @@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])), + PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14"); #endif
Cross CPU signalling based on FIQ is especially useful for kgdb since it makes stopping all the CPUs during breakpointing more robust (some of the other architectures already roundup the CPUs using NMIs).
The approach taken provides infrastructure that can be called (or not) by the driver's FIQ handler depending upon it requirements. In other words nothing is added here that prevents the driver from accessing "bare metal" performance.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/hardirq.h | 2 +- arch/arm/include/asm/smp.h | 7 +++++++ arch/arm/kernel/smp.c | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/arch/arm/include/asm/hardirq.h b/arch/arm/include/asm/hardirq.h index fe3ea77..5df33e3 100644 --- a/arch/arm/include/asm/hardirq.h +++ b/arch/arm/include/asm/hardirq.h @@ -5,7 +5,7 @@ #include <linux/threads.h> #include <asm/irq.h>
-#define NR_IPI 8 +#define NR_IPI 9
typedef struct { unsigned int __softirq_pending; diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 2ec765c..215c927 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -20,6 +20,9 @@
#define raw_smp_processor_id() (current_thread_info()->cpu)
+/* bitmap of IPIs that must be signalled using FIQ */ +#define SMP_IPI_FIQ_MASK 0x0100 + struct seq_file;
/* @@ -87,6 +90,10 @@ extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
extern int register_ipi_completion(struct completion *completion, int cpu);
+#ifdef CONFIG_FIQ +extern void send_fiq_ipi_mask(const struct cpumask *); +#endif + struct smp_operations { #ifdef CONFIG_SMP /* diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 9388a3d..d386c32 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -72,6 +72,7 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_FIQ, };
static DECLARE_COMPLETION(cpu_running); @@ -451,6 +452,7 @@ static const char *ipi_types[NR_IPI] __tracepoint_string = { S(IPI_CPU_STOP, "CPU stop interrupts"), S(IPI_IRQ_WORK, "IRQ work interrupts"), S(IPI_COMPLETION, "completion interrupts"), + S(IPI_FIQ, "FIQ interrupts"), };
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr) @@ -552,6 +554,13 @@ static void ipi_complete(unsigned int cpu) complete(per_cpu(cpu_completion, cpu)); }
+#ifdef CONFIG_FIQ +void send_fiq_ipi_mask(const struct cpumask *mask) +{ + smp_cross_call(mask, IPI_FIQ); +} +#endif + /* * Main handler for inter-processor interrupts */ @@ -618,6 +627,12 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break;
+#ifdef CONFIG_FIQ + case IPI_FIQ: + pr_crit("CPU%u: IPI FIQ delivered via IRQ vector\n", cpu); + break; +#endif + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr);
The FIQ debugger may be used to debug situations when the kernel stuck in uninterruptable sections, e.g. the kernel infinitely loops or deadlocked in an interrupt or with interrupts disabled.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of comments and other small fragments still survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben.dooks@codethink.co.uk Cc: Omar Sandoval osandov@osandov.com --- arch/arm/Kconfig | 2 + arch/arm/Kconfig.debug | 19 ++++++++ arch/arm/include/asm/kgdb.h | 5 ++ arch/arm/kernel/fiq.c | 4 +- arch/arm/kernel/kgdb.c | 112 +++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 135 insertions(+), 7 deletions(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index c49a775..e6380b3 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -305,6 +305,7 @@ choice config ARCH_MULTIPLATFORM bool "Allow multiple platforms to be selected" depends on MMU + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_HAS_SG_CHAIN select ARM_PATCH_PHYS_VIRT @@ -352,6 +353,7 @@ config ARCH_REALVIEW
config ARCH_VERSATILE bool "ARM Ltd. Versatile family" + select ARCH_MIGHT_HAVE_KGDB_FIQ select ARCH_WANT_OPTIONAL_GPIOLIB select ARM_AMBA select ARM_TIMER_SP804 diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index b11ad54..df3f0bf 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -2,6 +2,25 @@ menu "Kernel hacking"
source "lib/Kconfig.debug"
+config ARCH_MIGHT_HAVE_KGDB_FIQ + bool + +config KGDB_FIQ + bool "KGDB FIQ support" + depends on KGDB_KDB && ARCH_MIGHT_HAVE_KGDB_FIQ && !THUMB2_KERNEL + select FIQ + help + The FIQ debugger may be used to debug situations when the + kernel stuck in uninterruptable sections, e.g. the kernel + infinitely loops or deadlocked in an interrupt or with + interrupts disabled. + + By default KGDB FIQ is disabled at runtime, but can be enabled + by setting the console to ttyNMI0 (and choosing the underlying + serial port using kgdboc) + + If unsure, say N. + config ARM_PTDUMP bool "Export kernel pagetable layout to userspace via debugfs" depends on DEBUG_KERNEL diff --git a/arch/arm/include/asm/kgdb.h b/arch/arm/include/asm/kgdb.h index 0a9d5dd..cb5ccd6 100644 --- a/arch/arm/include/asm/kgdb.h +++ b/arch/arm/include/asm/kgdb.h @@ -11,7 +11,9 @@ #define __ARM_KGDB_H__
#include <linux/ptrace.h> +#include <linux/linkage.h> #include <asm/opcodes.h> +#include <asm/exception.h>
/* * GDB assumes that we're a user process being debugged, so @@ -48,6 +50,9 @@ static inline void arch_kgdb_breakpoint(void) extern void kgdb_handle_bus_error(void); extern int kgdb_fault_expected;
+extern int kgdb_register_fiq(unsigned int fiq); +extern void kgdb_handle_fiq(struct pt_regs *regs); + #endif /* !__ASSEMBLY__ */
/* diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 77c62b2..c6b3bed 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -229,7 +229,9 @@ asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) * order to ensure code review happens (drivers cannot "secretly" * employ FIQ without modifying this chain of calls). */ - /* list is currently empty */ +#ifdef CONFIG_KGDB_FIQ + kgdb_handle_fiq(regs); +#endif
nmi_exit(); set_irq_regs(old_regs); diff --git a/arch/arm/kernel/kgdb.c b/arch/arm/kernel/kgdb.c index a74b53c..630a3ef 100644 --- a/arch/arm/kernel/kgdb.c +++ b/arch/arm/kernel/kgdb.c @@ -12,8 +12,11 @@ #include <linux/irq.h> #include <linux/kdebug.h> #include <linux/kgdb.h> +#include <asm/fiq.h> #include <asm/traps.h>
+static unsigned int kgdb_fiq; + struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = { { "r0", 4, offsetof(struct pt_regs, ARM_r0)}, @@ -175,14 +178,26 @@ static struct undef_hook kgdb_compiled_brkpt_hook = {
static void kgdb_call_nmi_hook(void *ignored) { - kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs()); + kgdb_nmicallback(raw_smp_processor_id(), get_irq_regs()); }
void kgdb_roundup_cpus(unsigned long flags) { - local_irq_enable(); - smp_call_function(kgdb_call_nmi_hook, NULL, 0); - local_irq_disable(); +#if defined CONFIG_KGDB_FIQ && defined CONFIG_SMP + struct cpumask mask; + + if (in_nmi()) { + cpumask_copy(&mask, cpu_online_mask); + cpumask_clear_cpu(raw_smp_processor_id(), &mask); + if (!cpumask_empty(&mask)) + send_fiq_ipi_mask(&mask); + return; + } +#endif + + local_irq_enable(); + smp_call_function(kgdb_call_nmi_hook, NULL, 0); + local_irq_disable(); }
static int __kgdb_notify(struct die_args *args, unsigned long cmd) @@ -244,6 +259,43 @@ void kgdb_arch_exit(void) unregister_die_notifier(&kgdb_notifier); }
+/** + * kgdb_fiq_enable_nmi - Manage NMI-triggered entry to KGDB + * @on: Flag to either enable or disable an NMI + * + * The call counts disable requests, and thus allows to nest disables. But + * trying to enable already enabled NMI is an error. + */ +static void kgdb_fiq_enable_nmi(bool on) +{ + if (!kgdb_fiq) + return; + +#ifdef CONFIG_KGDB_FIQ + static atomic_t cnt; + int ret; + + if (on) { + ret = atomic_add_return(1, &cnt); + if (ret == 1) + enable_fiq(kgdb_fiq); + + /* + * There should be only one instance that calls this function + * in "enable, disable" order. All other users must call + * disable first, then enable. If not, something is wrong. + */ + if (WARN_ON(ret > 1)) + return; + } else { + ret = atomic_add_return(-1, &cnt); + if (ret == 0) + disable_fiq(kgdb_fiq); + } +#endif +} + + /* * Register our undef instruction hooks with ARM undef core. * We regsiter a hook specifically looking for the KGB break inst @@ -252,8 +304,56 @@ void kgdb_arch_exit(void) */ struct kgdb_arch arch_kgdb_ops = { #ifndef __ARMEB__ - .gdb_bpt_instr = {0xfe, 0xde, 0xff, 0xe7} + .gdb_bpt_instr = {0xfe, 0xde, 0xff, 0xe7}, #else /* ! __ARMEB__ */ - .gdb_bpt_instr = {0xe7, 0xff, 0xde, 0xfe} + .gdb_bpt_instr = {0xe7, 0xff, 0xde, 0xfe}, #endif + .enable_nmi = kgdb_fiq_enable_nmi }; + +#ifdef CONFIG_KGDB_FIQ +void kgdb_handle_fiq(struct pt_regs *regs) +{ + int actual; + + if (!kgdb_fiq) + return; + + if (!kgdb_nmicallback(raw_smp_processor_id(), regs)) + return NOTIFY_OK; + + actual = ack_fiq(kgdb_fiq); + WARN_ON(actual != kgdb_fiq); + + /* there's no harm in doing this regardless of the above WARN_ON() */ + if (kgdb_nmi_poll_knock()) + kgdb_handle_exception(1, 0, 0, regs); + + eoi_fiq(actual); + + return NOTIFY_OK; +} + +int kgdb_register_fiq(unsigned int fiq) +{ + static struct fiq_handler kgdb_fiq_desc = { .name = "kgdb", }; + int err; + + if (!has_fiq(fiq)) { + pr_warn( + "%s: Cannot register %u (no FIQ with this number)\n", + __func__, fiq); + return -ENODEV; + } + + err = claim_fiq(&kgdb_fiq_desc); + if (err) { + pr_warn("%s: unable to claim fiq", __func__); + return err; + } + + kgdb_fiq = fiq; + + return 0; +} +#endif
All GIC hardware except GICv1-without-TrustZone support provides a means to group exceptions into group 0 (which can optionally be signally using use FIQ) and group 1. The kernel currently provides no means to exploit this. This patch alters the initialization of the GIC to place all interrupts into group 1 which is the foundational requirement to meaningfully use FIQ.
Note that the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world" (this feature allows grouping to be used to allow hardware peripherals to send interrupts into the secure world). The GIC driver will automatically detect this and disable its attempts to group interrupts.
On systems without TrustZone support the kernel has the power to route interrupt sources to FIQ, potentially allowing a driver to exploit the NMI-like properties of FIQ.
Tested on Freescale i.MX6 (quad A9), STiH416 (dual A9) and a self-written qemu GICv2 model.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com Cc: Marc Zyngier marc.zyngier@arm.com Acked-by: Dirk Behme dirk.behme@de.bosch.com --- drivers/irqchip/irq-gic.c | 99 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 5 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..423707c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -41,6 +41,9 @@ #include <linux/irqchip/arm-gic.h>
#include <asm/cputype.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -68,6 +71,9 @@ struct gic_chip_data { #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif +#ifdef CONFIG_FIQ + bool fiq_enable; +#endif };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); @@ -131,6 +137,16 @@ static inline void gic_set_base_accessor(struct gic_chip_data *data, #define gic_set_base_accessor(d, f) #endif
+#ifdef CONFIG_FIQ +static inline bool gic_data_fiq_enable(struct gic_chip_data *data) +{ + return data->fiq_enable; +} +#else +static inline bool gic_data_fiq_enable( + struct gic_chip_data *data) { return false; } +#endif + static inline void __iomem *gic_dist_base(struct irq_data *d) { struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d); @@ -325,6 +341,42 @@ static struct irq_chip gic_chip = { .irq_set_wake = gic_set_wake, };
+#ifdef CONFIG_FIQ +static void __init gic_init_fiq(struct gic_chip_data *gic, + irq_hw_number_t first_irq, + unsigned int num_irqs) +{ + void __iomem *dist_base = gic_data_dist_base(gic_data); + unsigned int i; + + /* + * FIQ can only be supported on platforms without an extended irq_eoi + * method (otherwise we take a lock during eoi handling). + */ + if (gic_arch_extn.irq_eoi) + return; + + /* + * If grouping is not available (not implemented or prohibited by + * security mode) these registers a read-as-zero/write-ignored. + * However as a precaution we restore the reset default regardless of + * the result of the test. + */ + writel_relaxed(1, dist_base + GIC_DIST_IGROUP + 0); + gic->fiq_enable = readl_relaxed(dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0, dist_base + GIC_DIST_IGROUP + 0); + pr_debug("gic: FIQ support %s\n", + gic->fiq_enable ? "enabled" : "disabled"); + + if (!gic->fiq_enable) + return; +} +#else /* CONFIG_FIQ */ +static inline void gic_init_fiq(struct gic_chip_data *gic, + irq_hw_number_t first_irq, + unsigned int num_irqs) {} +#endif /* CONFIG_FIQ */ + void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) { if (gic_nr >= MAX_GIC_NR) @@ -373,7 +425,22 @@ static void __init gic_dist_init(struct gic_chip_data *gic)
gic_dist_config(base, gic_irqs, NULL);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Optionally set all global interrupts to be group 1. + */ + if (gic_data_fiq_enable(gic)) + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, + base + GIC_DIST_IGROUP + i * 4 / 32); + + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + if (gic_data_fiq_enable(gic)) + writel_relaxed(3, base + GIC_DIST_CTRL); + else + writel_relaxed(1, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -400,8 +467,20 @@ static void gic_cpu_init(struct gic_chip_data *gic)
gic_cpu_config(dist_base, NULL);
+ /* + * Set all PPI and SGI interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers are read-as-zero/write-ignored. + */ + if (gic_data_fiq_enable(gic)) + writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + if (gic_data_fiq_enable(gic)) + writel_relaxed(0x1f, base + GIC_CPU_CTRL); + else + writel_relaxed(1, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -485,7 +564,10 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + if (gic_data_fiq_enable(&gic_data[gic_nr])) + writel_relaxed(3, dist_base + GIC_DIST_CTRL); + else + writel_relaxed(1, dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -542,7 +624,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,6 +686,7 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0; + unsigned long softint;
raw_spin_lock_irqsave(&irq_controller_lock, flags);
@@ -618,7 +701,11 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + softint = map << 16 | irq; + if (gic_data_fiq_enable(&gic_data[0])) + softint |= 0x8000; + writel_relaxed(softint, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } @@ -964,6 +1051,8 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start,
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, hwirq_base, &gic_irq_domain_ops, gic); + + gic_init_fiq(gic, irq_base, gic_irqs); } else { gic->domain = irq_domain_add_linear(node, nr_routable_irqs, &gic_irq_domain_ops,
On Tue, Sep 02, 2014 at 02:00:40PM +0100, Daniel Thompson wrote:
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..423707c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -41,6 +41,9 @@ #include <linux/irqchip/arm-gic.h> #include <asm/cputype.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif
Is there much advantage to this ifdef over providing a dummy asm/fiq.h in ARM64?
#include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -68,6 +71,9 @@ struct gic_chip_data { #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif +#ifdef CONFIG_FIQ
- bool fiq_enable;
+#endif }; static DEFINE_RAW_SPINLOCK(irq_controller_lock); @@ -131,6 +137,16 @@ static inline void gic_set_base_accessor(struct gic_chip_data *data, #define gic_set_base_accessor(d, f) #endif +#ifdef CONFIG_FIQ +static inline bool gic_data_fiq_enable(struct gic_chip_data *data) +{
- return data->fiq_enable;
+} +#else +static inline bool gic_data_fiq_enable(
struct gic_chip_data *data) { return false; }
I really hate this style. Just lay it out as a normal function.
- /*
* If grouping is not available (not implemented or prohibited by
* security mode) these registers a read-as-zero/write-ignored.
* However as a precaution we restore the reset default regardless of
* the result of the test.
*/
Have we found that this additional complexity is actually necessary? If not, we're over-engineering the code, making it more complex (and hence more likely to be buggy) for very little reason.
Last night, I booted an unconditional version of this on OMAP3430, and OMAP4430. It's also been booted on the range of iMX6 CPUs. Nothing here has shown any signs of problems to having these registers written.
- /*
* Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only,
* bit 1 ignored)
*/
- if (gic_data_fiq_enable(gic))
writel_relaxed(3, base + GIC_DIST_CTRL);
- else
writel_relaxed(1, base + GIC_DIST_CTRL);
If we are going to do this conditionally, and the only thing which is variable is the value to be written, I much prefer the conditional bit to be on the value and not the write. The compiler doesn't always optimise these things very well. So:
writel_relaxed(gic_data_fiq_enable(gic) ? 3 : 1, base + GIC_DIST_CTRL);
works well enough for me. If you feel better by using a temporary local, that works for me too.
- if (gic_data_fiq_enable(gic))
writel_relaxed(0x1f, base + GIC_CPU_CTRL);
- else
writel_relaxed(1, base + GIC_CPU_CTRL);
Same here.
@@ -485,7 +564,10 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL);
- if (gic_data_fiq_enable(&gic_data[gic_nr]))
writel_relaxed(3, dist_base + GIC_DIST_CTRL);
- else
writel_relaxed(1, dist_base + GIC_DIST_CTRL);
And here.
@@ -542,7 +624,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4); writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK);
- writel_relaxed(1, cpu_base + GIC_CPU_CTRL);
- writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL);
Interestingly, here you write 0x1f unconditionally.
} static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,6 +686,7 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0;
- unsigned long softint;
raw_spin_lock_irqsave(&irq_controller_lock, flags); @@ -618,7 +701,11 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst); /* this always happens on GIC0 */
- writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
- softint = map << 16 | irq;
- if (gic_data_fiq_enable(&gic_data[0]))
softint |= 0x8000;
I guess that this always has to be done conditionally. I'd prefer this test to be done slightly differently (and we might as well wrap in a bit of patch 9 here):
if (sgi_is_nonsecure(irq, &gic_data[0])) softint |= 0x8000;
which follows the true purpose of that bit. This bit only has effect if we are running in secure mode, where it must match the status of the target interrupt (as programmed into GIC_DIST_IGROUP).
We probably should do this based on a bitmask of SGIs in the gic_chip_data, which is initialised according to how we've been able to setup the GIC_DIST_IGROUP register(s).
On 2 Sep 2014, at 20:33, Russell King - ARM Linux linux@arm.linux.org.uk wrote:
On Tue, Sep 02, 2014 at 02:00:40PM +0100, Daniel Thompson wrote:
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..423707c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -41,6 +41,9 @@ #include <linux/irqchip/arm-gic.h>
#include <asm/cputype.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif
Is there much advantage to this ifdef over providing a dummy asm/fiq.h in ARM64?
While it’s unlikely we’ll use FIQs on arm64 (they are generally reserved for the secure world/firmware), I don’t mind an empty asm/fiq.h file.
Catalin
On 02/09/14 22:36, Catalin Marinas wrote:
On 2 Sep 2014, at 20:33, Russell King - ARM Linux linux@arm.linux.org.uk wrote:
On Tue, Sep 02, 2014 at 02:00:40PM +0100, Daniel Thompson wrote:
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..423707c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -41,6 +41,9 @@ #include <linux/irqchip/arm-gic.h>
#include <asm/cputype.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif
Is there much advantage to this ifdef over providing a dummy asm/fiq.h in ARM64?
While it’s unlikely we’ll use FIQs on arm64 (they are generally reserved for the secure world/firmware), I don’t mind an empty asm/fiq.h file.
Thanks Catalin, Thanks Russell.
I will do this.
On 02/09/14 20:33, Russell King - ARM Linux wrote:
On Tue, Sep 02, 2014 at 02:00:40PM +0100, Daniel Thompson wrote:
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..423707c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -41,6 +41,9 @@ #include <linux/irqchip/arm-gic.h> #include <asm/cputype.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif
Is there much advantage to this ifdef over providing a dummy asm/fiq.h in ARM64?
#include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -68,6 +71,9 @@ struct gic_chip_data { #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif +#ifdef CONFIG_FIQ
- bool fiq_enable;
+#endif }; static DEFINE_RAW_SPINLOCK(irq_controller_lock); @@ -131,6 +137,16 @@ static inline void gic_set_base_accessor(struct gic_chip_data *data, #define gic_set_base_accessor(d, f) #endif +#ifdef CONFIG_FIQ +static inline bool gic_data_fiq_enable(struct gic_chip_data *data) +{
- return data->fiq_enable;
+} +#else +static inline bool gic_data_fiq_enable(
struct gic_chip_data *data) { return false; }
I really hate this style. Just lay it out as a normal function.
Will do.
- /*
* If grouping is not available (not implemented or prohibited by
* security mode) these registers a read-as-zero/write-ignored.
* However as a precaution we restore the reset default regardless of
* the result of the test.
*/
Have we found that this additional complexity is actually necessary? If not, we're over-engineering the code, making it more complex (and hence more likely to be buggy) for very little reason.
Last night, I booted an unconditional version of this on OMAP3430, and OMAP4430. It's also been booted on the range of iMX6 CPUs. Nothing here has shown any signs of problems to having these registers written.
No, I haven't proven that most of the conditional code based on gic_data_fiq_enable() is required.
I should certainly be safe to remove the conditional code from registers specified was RAZ/WI.
I suspect we could also remove it from registers with bits that are reserved (although spec. doesn't not state this explicitly).
I could aggressively remove the conditionals and keep the current code on a branch to make it quicker to support any reported regressions.
- /*
* Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only,
* bit 1 ignored)
*/
- if (gic_data_fiq_enable(gic))
writel_relaxed(3, base + GIC_DIST_CTRL);
- else
writel_relaxed(1, base + GIC_DIST_CTRL);
If we are going to do this conditionally, and the only thing which is variable is the value to be written, I much prefer the conditional bit to be on the value and not the write. The compiler doesn't always optimise these things very well. So:
writel_relaxed(gic_data_fiq_enable(gic) ? 3 : 1, base + GIC_DIST_CTRL);
works well enough for me. If you feel better by using a temporary local, that works for me too.
Ternary is fine by me although I'm inclined to be more aggressive with removal of conditional code.
- if (gic_data_fiq_enable(gic))
writel_relaxed(0x1f, base + GIC_CPU_CTRL);
- else
writel_relaxed(1, base + GIC_CPU_CTRL);
Same here.
Ok.
@@ -485,7 +564,10 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL);
- if (gic_data_fiq_enable(&gic_data[gic_nr]))
writel_relaxed(3, dist_base + GIC_DIST_CTRL);
- else
writel_relaxed(1, dist_base + GIC_DIST_CTRL);
And here.
Ok.
@@ -542,7 +624,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4); writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK);
- writel_relaxed(1, cpu_base + GIC_CPU_CTRL);
- writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL);
Interestingly, here you write 0x1f unconditionally.
Oops. Can I pretend this was was a premonition?
} static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,6 +686,7 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0;
- unsigned long softint;
raw_spin_lock_irqsave(&irq_controller_lock, flags); @@ -618,7 +701,11 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst); /* this always happens on GIC0 */
- writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
- softint = map << 16 | irq;
- if (gic_data_fiq_enable(&gic_data[0]))
softint |= 0x8000;
I guess that this always has to be done conditionally. I'd prefer this test to be done slightly differently (and we might as well wrap in a bit of patch 9 here):
if (sgi_is_nonsecure(irq, &gic_data[0])) softint |= 0x8000;
which follows the true purpose of that bit. This bit only has effect if we are running in secure mode, where it must match the status of the target interrupt (as programmed into GIC_DIST_IGROUP).
We probably should do this based on a bitmask of SGIs in the gic_chip_data, which is initialised according to how we've been able to setup the GIC_DIST_IGROUP register(s).
Will do.
This patch introduces callbacks to route interrupts to or away from the FIQ signal and registers these callbacks with the FIQ infrastructure (if the device can supports it).
Both these aspects combine and allow a driver to deploy a FIQ handler without any machine specific knowledge; it can be used effectively on multi-platform kernels.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com --- drivers/irqchip/irq-gic.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 423707c..6fa0542 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -342,6 +342,69 @@ static struct irq_chip gic_chip = { };
#ifdef CONFIG_FIQ +/* + * Shift an interrupt between Group 0 and Group 1. + * + * In addition to changing the group we also modify the priority to + * match what "ARM strongly recommends" for a system where no Group 1 + * interrupt must ever preempt a Group 0 interrupt. + */ +static void gic_set_group_irq(struct irq_data *d, int group) +{ + unsigned int grp_reg = gic_irq(d) / 32 * 4; + u32 grp_mask = 1 << (gic_irq(d) % 32); + u32 grp_val; + + unsigned int pri_reg = (gic_irq(d) / 4) * 4; + u32 pri_mask = 1 << (7 + ((gic_irq(d) % 4) * 8)); + u32 pri_val; + + raw_spin_lock(&irq_controller_lock); + + grp_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + + if (group) { + grp_val |= grp_mask; + pri_val |= pri_mask; + } else { + grp_val &= ~grp_mask; + pri_val &= ~pri_mask; + } + + writel_relaxed(grp_val, gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + + raw_spin_unlock(&irq_controller_lock); +} + +static void gic_enable_fiq(struct irq_data *d) +{ + gic_set_group_irq(d, 0); +} + +static void gic_disable_fiq(struct irq_data *d) +{ + gic_set_group_irq(d, 1); +} + +static int gic_ack_fiq(struct irq_data *d) +{ + struct gic_chip_data *gic = irq_data_get_irq_chip_data(d); + u32 irqstat, irqnr; + + irqstat = readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK); + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + return irq_find_mapping(gic->domain, irqnr); +} + +static struct fiq_chip gic_fiq = { + .fiq_enable = gic_enable_fiq, + .fiq_disable = gic_disable_fiq, + .fiq_ack = gic_ack_fiq, + .fiq_eoi = gic_eoi_irq, +}; + static void __init gic_init_fiq(struct gic_chip_data *gic, irq_hw_number_t first_irq, unsigned int num_irqs) @@ -370,6 +433,12 @@ static void __init gic_init_fiq(struct gic_chip_data *gic,
if (!gic->fiq_enable) return; + + /* + * FIQ is supported on this device! Register our chip data. + */ + for (i = 0; i < num_irqs; i++) + fiq_register_mapping(first_irq + i, &gic_fiq); } #else /* CONFIG_FIQ */ static inline void gic_init_fiq(struct gic_chip_data *gic,
On Tue, Sep 02, 2014 at 02:00:41PM +0100, Daniel Thompson wrote:
+static void gic_enable_fiq(struct irq_data *d) +{
- gic_set_group_irq(d, 0);
+}
+static void gic_disable_fiq(struct irq_data *d) +{
- gic_set_group_irq(d, 1);
+}
I think both the above functions should also call gic_unmask_irq() and gic_mask_irq() directly rather than looping through enable_irq()/ disable_irq().
This patch is motivated by the comment it removes from gic_init_fiq, namely that the spin locks in eoi_irq preclude certain platforms from supporting FIQ.
Currently there is only one upstream platform (tegra) that actually hooks gic_arch_extn.irq_eoi and it does not require these spin locks.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Peter De Schrijver pdeschrijver@nvidia.com --- drivers/irqchip/irq-gic.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 6fa0542..d928912 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -191,11 +191,8 @@ static void gic_unmask_irq(struct irq_data *d)
static void gic_eoi_irq(struct irq_data *d) { - if (gic_arch_extn.irq_eoi) { - raw_spin_lock(&irq_controller_lock); + if (gic_arch_extn.irq_eoi) gic_arch_extn.irq_eoi(d); - raw_spin_unlock(&irq_controller_lock); - }
writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI); } @@ -413,13 +410,6 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, unsigned int i;
/* - * FIQ can only be supported on platforms without an extended irq_eoi - * method (otherwise we take a lock during eoi handling). - */ - if (gic_arch_extn.irq_eoi) - return; - - /* * If grouping is not available (not implemented or prohibited by * security mode) these registers a read-as-zero/write-ignored. * However as a precaution we restore the reset default regardless of
To support IPI FIQ we alter gic_cpu_init() to honour SMP_IPI_FIQ_MASK and register a fairly high priority notifier to acknowledge and clear the IPI when it is triggered.
For the IPI FIQ to be useful we must also make it safe to call gic_raise_softirq() from the FIQ handler by altering the locking strategy slightly.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/kernel/fiq.c | 3 ++ drivers/irqchip/irq-gic.c | 125 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 107 insertions(+), 21 deletions(-)
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index c6b3bed..115d319 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -232,6 +232,9 @@ asmlinkage void __exception_irq_entry fiq_nmi_handler(struct pt_regs *regs) #ifdef CONFIG_KGDB_FIQ kgdb_handle_fiq(regs); #endif +#ifdef CONFIG_ARM_GIC + gic_handle_fiq_ipi(); +#endif
nmi_exit(); set_irq_regs(old_regs); diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index d928912..8834749 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -39,6 +39,7 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/ratelimit.h>
#include <asm/cputype.h> #ifdef CONFIG_FIQ @@ -51,6 +52,10 @@ #include "irq-gic-common.h" #include "irqchip.h"
+#ifndef SMP_IPI_FIQ_MASK +#define SMP_IPI_FIQ_MASK 0 +#endif + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -77,6 +82,8 @@ struct gic_chip_data { };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); +/* A fiq-safe spinlock must only be locked when the FIQ is masked */ +static DEFINE_RAW_SPINLOCK(fiq_safe_migration_lock);
/* * The GIC mapping of CPU interfaces does not necessarily match @@ -346,20 +353,21 @@ static struct irq_chip gic_chip = { * match what "ARM strongly recommends" for a system where no Group 1 * interrupt must ever preempt a Group 0 interrupt. */ -static void gic_set_group_irq(struct irq_data *d, int group) +static void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) { - unsigned int grp_reg = gic_irq(d) / 32 * 4; - u32 grp_mask = 1 << (gic_irq(d) % 32); + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_mask = 1 << (hwirq % 32); u32 grp_val;
- unsigned int pri_reg = (gic_irq(d) / 4) * 4; - u32 pri_mask = 1 << (7 + ((gic_irq(d) % 4) * 8)); + unsigned int pri_reg = (hwirq / 4) * 4; + u32 pri_mask = 1 << (7 + ((hwirq % 4) * 8)); u32 pri_val;
raw_spin_lock(&irq_controller_lock);
- grp_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); - pri_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(base + GIC_DIST_PRI + pri_reg);
if (group) { grp_val |= grp_mask; @@ -369,20 +377,20 @@ static void gic_set_group_irq(struct irq_data *d, int group) pri_val &= ~pri_mask; }
- writel_relaxed(grp_val, gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); - writel_relaxed(pri_val, gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + writel_relaxed(grp_val, base + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, base + GIC_DIST_PRI + pri_reg);
raw_spin_unlock(&irq_controller_lock); }
static void gic_enable_fiq(struct irq_data *d) { - gic_set_group_irq(d, 0); + gic_set_group_irq(gic_dist_base(d), gic_irq(d), 0); }
static void gic_disable_fiq(struct irq_data *d) { - gic_set_group_irq(d, 1); + gic_set_group_irq(gic_dist_base(d), gic_irq(d), 1); }
static int gic_ack_fiq(struct irq_data *d) @@ -390,8 +398,22 @@ static int gic_ack_fiq(struct irq_data *d) struct gic_chip_data *gic = irq_data_get_irq_chip_data(d); u32 irqstat, irqnr;
- irqstat = readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK); - irqnr = irqstat & GICC_IAR_INT_ID_MASK; + while (1) { + writel_relaxed(0x70, gic_data_cpu_base(gic) + GIC_CPU_PRIMASK); + irqstat = + readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK); + writel_relaxed(0xf0, gic_data_cpu_base(gic) + GIC_CPU_PRIMASK); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + if (irqnr > 15) + break; + + /* we've got an IPI which we can simply acknowledge + * and move on + */ + gic_eoi_irq(d); + } + return irq_find_mapping(gic->domain, irqnr); }
@@ -430,7 +452,43 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, for (i = 0; i < num_irqs; i++) fiq_register_mapping(first_irq + i, &gic_fiq); } + +/* + * Fully acknowledge (both ack and eoi) a FIQ-based IPI + */ +static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs, + void *data) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *cpu_base = gic_data_cpu_base(gic); + unsigned long irqstat, irqnr; + + if (WARN_ON(!in_nmi())) + return NOTIFY_BAD; + + while ((1u << readl_relaxed(cpu_base + GIC_CPU_HIGHPRI)) & + SMP_IPI_FIQ_MASK) { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + WARN_RATELIMIT(irqnr > 16, + "Unexpected irqnr %lu (bad prioritization?)\n", + irqnr); + } + + return NOTIFY_OK; +} + +/* + * Notifier to ensure IPI FIQ is acknowledged correctly. + */ +static struct notifier_block gic_fiq_ipi_notifier = { + .notifier_call = gic_handle_fiq_ipi, +}; #else /* CONFIG_FIQ */ +static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) {} static inline void gic_init_fiq(struct gic_chip_data *gic, irq_hw_number_t first_irq, unsigned int num_irqs) {} @@ -527,14 +585,19 @@ static void gic_cpu_init(struct gic_chip_data *gic) gic_cpu_config(dist_base, NULL);
/* - * Set all PPI and SGI interrupts to be group 1. - * - * If grouping is not available (not implemented or prohibited by - * security mode) these registers are read-as-zero/write-ignored. + * Optionally set all PPI and SGI interrupts to be group 1. */ if (gic_data_fiq_enable(gic)) writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0);
+ /* + * Optionally shift the FIQ based IPIs to group 0. + */ + if (gic_data_fiq_enable(gic)) + for (i = 0; i < 16; i++) + if (SMP_IPI_FIQ_MASK & (1 << i)) + gic_set_group_irq(dist_base, i, 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); if (gic_data_fiq_enable(gic)) writel_relaxed(0x1f, base + GIC_CPU_CTRL); @@ -747,7 +810,17 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) unsigned long flags, map = 0; unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags); + /* + * The locking in this function ensures we don't use stale cpu mappings + * and thus we never route an IPI to the wrong physical core during a + * big.LITTLE switch. The switch code takes both of these locks meaning + * we can choose whichever lock is safe to use from our current calling + * context. + */ + if (in_nmi()) + raw_spin_lock(&fiq_safe_migration_lock); + else + raw_spin_lock_irqsave(&irq_controller_lock, flags);
/* Convert our logical CPU mask into a physical one. */ for_each_cpu(cpu, mask) @@ -761,12 +834,16 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
/* this always happens on GIC0 */ softint = map << 16 | irq; - if (gic_data_fiq_enable(&gic_data[0])) + if (gic_data_fiq_enable(&gic_data[0]) && + !(SMP_IPI_FIQ_MASK & (1 << irq))) softint |= 0x8000; writel_relaxed(softint, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
- raw_spin_unlock_irqrestore(&irq_controller_lock, flags); + if (in_nmi()) + raw_spin_unlock(&fiq_safe_migration_lock); + else + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } #endif
@@ -814,7 +891,7 @@ int gic_get_cpu_id(unsigned int cpu) * Migrate all peripheral interrupts with a target matching the current CPU * to the interface corresponding to @new_cpu_id. The CPU interface mapping * is also updated. Targets to other CPU interfaces are unchanged. - * This must be called with IRQs locally disabled. + * This must be called with IRQ and FIQ locally disabled. */ void gic_migrate_target(unsigned int new_cpu_id) { @@ -836,6 +913,7 @@ void gic_migrate_target(unsigned int new_cpu_id) ror_val = (cur_cpu_id - new_cpu_id) & 31;
raw_spin_lock(&irq_controller_lock); + raw_spin_lock(&fiq_safe_migration_lock);
/* Update the target interface for this logical CPU */ gic_cpu_map[cpu] = 1 << new_cpu_id; @@ -855,6 +933,7 @@ void gic_migrate_target(unsigned int new_cpu_id) } }
+ raw_spin_unlock(&fiq_safe_migration_lock); raw_spin_unlock(&irq_controller_lock);
/* @@ -1125,6 +1204,10 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); +#ifdef CONFIG_FIQ + if (gic_data_fiq_enable(gic)) + register_fiq_nmi_notifier(&gic_fiq_ipi_notifier); +#endif #endif set_handle_irq(gic_handle_irq); }
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 --- drivers/irqchip/irq-gic.c | 52 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 8834749..bda5a91 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -279,14 +279,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 (!gic_data_fiq_enable(gic) || 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)) { @@ -295,7 +340,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
This patch introduces callbacks to route interrupts to or away from the FIQ signal. It also causes these callbacks to be registered with the FIQ infrastructure.
This patch enable FIQ support for mach-versatile whilst mach-ep93xx, mach-netx, mach-s3c64xx and plat-samsung are unmodified (and can therefore continue to use init_FIQ() as before).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Hartley Sweeten hsweeten@visionengravers.com Cc: Ryan Mallon rmallon@gmail.com Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben-linux@fluff.org Cc: Kukjin Kim kgene.kim@samsung.com Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: linux-samsung-soc@vger.kernel.org --- arch/arm/mach-versatile/core.c | 2 +- drivers/irqchip/irq-gic.c | 21 ++++------ drivers/irqchip/irq-vic.c | 92 ++++++++++++++++++++++++++++++++--------- include/linux/irqchip/arm-gic.h | 3 ++ include/linux/irqchip/arm-vic.h | 6 ++- 5 files changed, 88 insertions(+), 36 deletions(-)
diff --git a/arch/arm/mach-versatile/core.c b/arch/arm/mach-versatile/core.c index 08fb8c8..bad1d30 100644 --- a/arch/arm/mach-versatile/core.c +++ b/arch/arm/mach-versatile/core.c @@ -108,7 +108,7 @@ void __init versatile_init_irq(void)
np = of_find_matching_node_by_address(NULL, vic_of_match, VERSATILE_VIC_BASE); - __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np); + __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np ? false : true, np);
writel(~0, VA_SIC_BASE + SIC_IRQ_ENABLE_CLEAR);
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index bda5a91..8821160 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -502,13 +502,17 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, /* * Fully acknowledge (both ack and eoi) a FIQ-based IPI */ -static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs, - void *data) +void gic_handle_fiq_ipi(void) { struct gic_chip_data *gic = &gic_data[0]; - void __iomem *cpu_base = gic_data_cpu_base(gic); + void __iomem *cpu_base; unsigned long irqstat, irqnr;
+ if (!gic || !gic->fiq_enable) + return; + + cpu_base = gic_data_cpu_base(gic); + if (WARN_ON(!in_nmi())) return NOTIFY_BAD;
@@ -525,13 +529,6 @@ static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs,
return NOTIFY_OK; } - -/* - * Notifier to ensure IPI FIQ is acknowledged correctly. - */ -static struct notifier_block gic_fiq_ipi_notifier = { - .notifier_call = gic_handle_fiq_ipi, -}; #else /* CONFIG_FIQ */ static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, int group) {} @@ -1250,10 +1247,6 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); -#ifdef CONFIG_FIQ - if (gic_data_fiq_enable(gic)) - register_fiq_nmi_notifier(&gic_fiq_ipi_notifier); -#endif #endif set_handle_irq(gic_handle_irq); } diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index 7d35287..22aa126 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -36,6 +36,9 @@
#include <asm/exception.h> #include <asm/irq.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif
#include "irqchip.h"
@@ -261,11 +264,53 @@ static struct irq_domain_ops vic_irqdomain_ops = { .xlate = irq_domain_xlate_onetwocell, };
+#ifdef CONFIG_FIQ +static DEFINE_RAW_SPINLOCK(irq_controller_lock); + +static void vic_set_fiq(struct irq_data *d, bool enable) +{ + void __iomem *base = irq_data_get_irq_chip_data(d); + unsigned int irq = d->hwirq; + u32 val; + + raw_spin_lock(&irq_controller_lock); + val = readl(base + VIC_INT_SELECT); + if (enable) + val |= 1 << irq; + else + val &= ~(1 << irq); + writel(val, base + VIC_INT_SELECT); + raw_spin_unlock(&irq_controller_lock); +} + +static void vic_enable_fiq(struct irq_data *d) +{ + vic_set_fiq(d, true); +} + +static void vic_disable_fiq(struct irq_data *d) +{ + vic_set_fiq(d, false); +} + +struct fiq_chip vic_fiq = { + .fiq_enable = vic_enable_fiq, + .fiq_disable = vic_disable_fiq, +}; + +static void vic_register_fiq(int irq) +{ + fiq_register_mapping(irq, &vic_fiq); +} +#else /* CONFIG_FIQ */ +static inline void vic_register_fiq(int irq) {} +#endif /* CONFIG_FIQ */ + /** * vic_register() - Register a VIC. * @base: The base address of the VIC. * @parent_irq: The parent IRQ if cascaded, else 0. - * @irq: The base IRQ for the VIC. + * @irq_start: The base IRQ for the VIC. * @valid_sources: bitmask of valid interrupts * @resume_sources: bitmask of interrupts allowed for resume sources. * @node: The device tree node associated with the VIC. @@ -277,12 +322,13 @@ static struct irq_domain_ops vic_irqdomain_ops = { * This also configures the IRQ domain for the VIC. */ static void __init vic_register(void __iomem *base, unsigned int parent_irq, - unsigned int irq, + unsigned int irq_start, u32 valid_sources, u32 resume_sources, - struct device_node *node) + bool map_fiqs, struct device_node *node) { struct vic_device *v; int i; + unsigned int irq;
if (vic_id >= ARRAY_SIZE(vic_devices)) { printk(KERN_ERR "%s: too few VICs, increase CONFIG_ARM_VIC_NR\n", __func__); @@ -301,15 +347,19 @@ static void __init vic_register(void __iomem *base, unsigned int parent_irq, irq_set_chained_handler(parent_irq, vic_handle_irq_cascaded); }
- v->domain = irq_domain_add_simple(node, fls(valid_sources), irq, + v->domain = irq_domain_add_simple(node, fls(valid_sources), irq_start, &vic_irqdomain_ops, v); /* create an IRQ mapping for each valid IRQ */ - for (i = 0; i < fls(valid_sources); i++) - if (valid_sources & (1 << i)) - irq_create_mapping(v->domain, i); + for (i = 0; i < fls(valid_sources); i++) { + if (valid_sources & (1 << i)) { + irq = irq_create_mapping(v->domain, i); + vic_register_fiq(irq); + } + } + /* If no base IRQ was passed, figure out our allocated base */ - if (irq) - v->irq = irq; + if (irq_start) + v->irq = irq_start; else v->irq = irq_find_mapping(v->domain, 0); } @@ -413,7 +463,8 @@ static void __init vic_clear_interrupts(void __iomem *base) * and 020 within the page. We call this "second block". */ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, - u32 vic_sources, struct device_node *node) + u32 vic_sources, bool map_fiqs, + struct device_node *node) { unsigned int i; int vic_2nd_block = ((unsigned long)base & ~PAGE_MASK) != 0; @@ -439,12 +490,12 @@ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, writel(32, base + VIC_PL190_DEF_VECT_ADDR); }
- vic_register(base, 0, irq_start, vic_sources, 0, node); + vic_register(base, 0, irq_start, vic_sources, 0, map_fiqs, node); }
void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, - struct device_node *node) + u32 vic_sources, u32 resume_sources, + bool map_fiqs, struct device_node *node) { unsigned int i; u32 cellid = 0; @@ -462,7 +513,7 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
switch(vendor) { case AMBA_VENDOR_ST: - vic_init_st(base, irq_start, vic_sources, node); + vic_init_st(base, irq_start, vic_sources, map_fiqs, node); return; default: printk(KERN_WARNING "VIC: unknown vendor, continuing anyways\n"); @@ -479,7 +530,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
vic_init2(base);
- vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, node); + vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, + map_fiqs, node); }
/** @@ -492,7 +544,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, void __init vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources) { - __vic_init(base, 0, irq_start, vic_sources, resume_sources, NULL); + __vic_init(base, 0, irq_start, vic_sources, resume_sources, + false, NULL); }
/** @@ -511,7 +564,8 @@ int __init vic_init_cascaded(void __iomem *base, unsigned int parent_irq, struct vic_device *v;
v = &vic_devices[vic_id]; - __vic_init(base, parent_irq, 0, vic_sources, resume_sources, NULL); + __vic_init(base, parent_irq, 0, vic_sources, resume_sources, false, + NULL); /* Return out acquired base */ return v->irq; } @@ -535,9 +589,9 @@ int __init vic_of_init(struct device_node *node, struct device_node *parent) of_property_read_u32(node, "valid-wakeup-mask", &wakeup_mask);
/* - * Passing 0 as first IRQ makes the simple domain allocate descriptors + * Passing 0 as first IRQ makes the domain allocate descriptors. */ - __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, node); + __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, true, node);
return 0; } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 45e2d8c..52a5676 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -101,5 +101,8 @@ static inline void __init register_routable_domain_ops { gic_routable_irq_domain_ops = ops; } + +void gic_handle_fiq_ipi(void); + #endif /* __ASSEMBLY */ #endif diff --git a/include/linux/irqchip/arm-vic.h b/include/linux/irqchip/arm-vic.h index ba46c79..30ab39f 100644 --- a/include/linux/irqchip/arm-vic.h +++ b/include/linux/irqchip/arm-vic.h @@ -30,8 +30,10 @@ struct device_node; struct pt_regs;
void __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, struct device_node *node); -void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources); + u32 vic_sources, u32 resume_sources, + bool map_fiqs, struct device_node *node); +void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, + u32 resume_sources); int vic_init_cascaded(void __iomem *base, unsigned int parent_irq, u32 vic_sources, u32 resume_sources);
On Tue, Sep 02, 2014 at 02:00:45PM +0100, Daniel Thompson wrote:
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index bda5a91..8821160 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -502,13 +502,17 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, /*
- Fully acknowledge (both ack and eoi) a FIQ-based IPI
*/ -static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs,
void *data)
+void gic_handle_fiq_ipi(void) { struct gic_chip_data *gic = &gic_data[0];
- void __iomem *cpu_base = gic_data_cpu_base(gic);
- void __iomem *cpu_base; unsigned long irqstat, irqnr;
- if (!gic || !gic->fiq_enable)
return;
- cpu_base = gic_data_cpu_base(gic);
- if (WARN_ON(!in_nmi())) return NOTIFY_BAD;
@@ -525,13 +529,6 @@ static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs, return NOTIFY_OK; }
-/*
- Notifier to ensure IPI FIQ is acknowledged correctly.
- */
-static struct notifier_block gic_fiq_ipi_notifier = {
- .notifier_call = gic_handle_fiq_ipi,
-}; #else /* CONFIG_FIQ */ static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, int group) {} @@ -1250,10 +1247,6 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); -#ifdef CONFIG_FIQ
if (gic_data_fiq_enable(gic))
register_fiq_nmi_notifier(&gic_fiq_ipi_notifier);
-#endif #endif set_handle_irq(gic_handle_irq); }
Shouldn't this be merged into some other patch?
At present this console is selectively enabled/disabled by NULL checking arch_kgdb_ops.enable_nmi. In practice this requires the architecture dependant code to implement some kind of control (e.g. module arguments) to enable/disable the feature.
The kernel already provide the perfectly adequade console= argument to do this. Let's us that instead, if nothing else, it makes any documentation architecture neutral.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/tty/serial/kgdb_nmi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/tty/serial/kgdb_nmi.c b/drivers/tty/serial/kgdb_nmi.c index 6ec7501..129dc5b 100644 --- a/drivers/tty/serial/kgdb_nmi.c +++ b/drivers/tty/serial/kgdb_nmi.c @@ -46,6 +46,8 @@ static atomic_t kgdb_nmi_num_readers = ATOMIC_INIT(0);
static int kgdb_nmi_console_setup(struct console *co, char *options) { + arch_kgdb_ops.enable_nmi(1); + /* The NMI console uses the dbg_io_ops to issue console messages. To * avoid duplicate messages during kdb sessions we must inform kdb's * I/O utilities that messages sent to the console will automatically @@ -77,7 +79,7 @@ static struct console kgdb_nmi_console = { .setup = kgdb_nmi_console_setup, .write = kgdb_nmi_console_write, .device = kgdb_nmi_console_device, - .flags = CON_PRINTBUFFER | CON_ANYTIME | CON_ENABLED, + .flags = CON_PRINTBUFFER | CON_ANYTIME, .index = -1, };
@@ -354,7 +356,6 @@ int kgdb_register_nmi_console(void) }
register_console(&kgdb_nmi_console); - arch_kgdb_ops.enable_nmi(1);
return 0; err_drv_reg:
Universally adopt container_of() for all pointer conversion from uart_port to uart_amba_port.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/tty/serial/amba-pl011.c | 54 +++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 18 deletions(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 8572f2a..02016fc 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -678,7 +678,8 @@ static void pl011_dma_flush_buffer(struct uart_port *port) __releases(&uap->port.lock) __acquires(&uap->port.lock) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
if (!uap->using_tx_dma) return; @@ -1163,7 +1164,8 @@ static inline bool pl011_dma_rx_running(struct uart_amba_port *uap)
static void pl011_stop_tx(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
uap->im &= ~UART011_TXIM; writew(uap->im, uap->port.membase + UART011_IMSC); @@ -1172,7 +1174,8 @@ static void pl011_stop_tx(struct uart_port *port)
static void pl011_start_tx(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
if (!pl011_dma_tx_start(uap)) { uap->im |= UART011_TXIM; @@ -1182,7 +1185,8 @@ static void pl011_start_tx(struct uart_port *port)
static void pl011_stop_rx(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
uap->im &= ~(UART011_RXIM|UART011_RTIM|UART011_FEIM| UART011_PEIM|UART011_BEIM|UART011_OEIM); @@ -1193,7 +1197,8 @@ static void pl011_stop_rx(struct uart_port *port)
static void pl011_enable_ms(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
uap->im |= UART011_RIMIM|UART011_CTSMIM|UART011_DCDMIM|UART011_DSRMIM; writew(uap->im, uap->port.membase + UART011_IMSC); @@ -1349,14 +1354,16 @@ static irqreturn_t pl011_int(int irq, void *dev_id)
static unsigned int pl011_tx_empty(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int status = readw(uap->port.membase + UART01x_FR); return status & (UART01x_FR_BUSY|UART01x_FR_TXFF) ? 0 : TIOCSER_TEMT; }
static unsigned int pl011_get_mctrl(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int result = 0; unsigned int status = readw(uap->port.membase + UART01x_FR);
@@ -1374,7 +1381,8 @@ static unsigned int pl011_get_mctrl(struct uart_port *port)
static void pl011_set_mctrl(struct uart_port *port, unsigned int mctrl) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int cr;
cr = readw(uap->port.membase + UART011_CR); @@ -1402,7 +1410,8 @@ static void pl011_set_mctrl(struct uart_port *port, unsigned int mctrl)
static void pl011_break_ctl(struct uart_port *port, int break_state) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned long flags; unsigned int lcr_h;
@@ -1420,7 +1429,8 @@ static void pl011_break_ctl(struct uart_port *port, int break_state)
static void pl011_quiesce_irqs(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned char __iomem *regs = uap->port.membase;
writew(readw(regs + UART011_MIS), regs + UART011_ICR); @@ -1442,7 +1452,8 @@ static void pl011_quiesce_irqs(struct uart_port *port)
static int pl011_get_poll_char(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int status;
/* @@ -1461,7 +1472,8 @@ static int pl011_get_poll_char(struct uart_port *port) static void pl011_put_poll_char(struct uart_port *port, unsigned char ch) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_TXFF) barrier(); @@ -1473,7 +1485,8 @@ static void pl011_put_poll_char(struct uart_port *port,
static int pl011_hwinit(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); int retval;
/* Optionaly enable pins to be muxed in and configured */ @@ -1526,7 +1539,8 @@ static void pl011_write_lcr_h(struct uart_amba_port *uap, unsigned int lcr_h)
static int pl011_startup(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int cr, lcr_h, fbrd, ibrd; int retval;
@@ -1618,7 +1632,8 @@ static void pl011_shutdown_channel(struct uart_amba_port *uap,
static void pl011_shutdown(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int cr;
/* @@ -1680,7 +1695,8 @@ static void pl011_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); unsigned int lcr_h, old_cr; unsigned long flags; unsigned int baud, quot, clkdiv; @@ -1822,7 +1838,8 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios,
static const char *pl011_type(struct uart_port *port) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); return uap->port.type == PORT_AMBA ? uap->type : NULL; }
@@ -1900,7 +1917,8 @@ static struct uart_amba_port *amba_ports[UART_NR];
static void pl011_console_putchar(struct uart_port *port, int ch) { - struct uart_amba_port *uap = (struct uart_amba_port *)port; + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port);
while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_TXFF) barrier();
This patch hoists pl011_hwinit() further up within the driver. This permits a later patch to introduce an extended .poll_init callback that does more than pure hardware initialization.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/amba-pl011.c | 78 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 39 deletions(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 02016fc..0b06dcf 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1425,6 +1425,45 @@ static void pl011_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&uap->port.lock, flags); }
+static int pl011_hwinit(struct uart_port *port) +{ + struct uart_amba_port *uap = + container_of(port, struct uart_amba_port, port); + int retval; + + /* Optionaly enable pins to be muxed in and configured */ + pinctrl_pm_select_default_state(port->dev); + + /* + * Try to enable the clock producer. + */ + retval = clk_prepare_enable(uap->clk); + if (retval) + return retval; + + uap->port.uartclk = clk_get_rate(uap->clk); + + /* Clear pending error and receive interrupts */ + writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | + UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); + + /* + * Save interrupts enable mask, and enable RX interrupts in case if + * the interrupt is used for NMI entry. + */ + uap->im = readw(uap->port.membase + UART011_IMSC); + writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); + + if (dev_get_platdata(uap->port.dev)) { + struct amba_pl011_data *plat; + + plat = dev_get_platdata(uap->port.dev); + if (plat->init) + plat->init(); + } + return 0; +} + #ifdef CONFIG_CONSOLE_POLL
static void pl011_quiesce_irqs(struct uart_port *port) @@ -1483,45 +1522,6 @@ static void pl011_put_poll_char(struct uart_port *port,
#endif /* CONFIG_CONSOLE_POLL */
-static int pl011_hwinit(struct uart_port *port) -{ - struct uart_amba_port *uap = - container_of(port, struct uart_amba_port, port); - int retval; - - /* Optionaly enable pins to be muxed in and configured */ - pinctrl_pm_select_default_state(port->dev); - - /* - * Try to enable the clock producer. - */ - retval = clk_prepare_enable(uap->clk); - if (retval) - return retval; - - uap->port.uartclk = clk_get_rate(uap->clk); - - /* Clear pending error and receive interrupts */ - writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | - UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); - - /* - * Save interrupts enable mask, and enable RX interrupts in case if - * the interrupt is used for NMI entry. - */ - uap->im = readw(uap->port.membase + UART011_IMSC); - writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); - - if (dev_get_platdata(uap->port.dev)) { - struct amba_pl011_data *plat; - - plat = dev_get_platdata(uap->port.dev); - if (plat->init) - plat->init(); - } - return 0; -} - static void pl011_write_lcr_h(struct uart_amba_port *uap, unsigned int lcr_h) { writew(lcr_h, uap->port.membase + uap->lcrh_rx);
Speculatively register a FIQ resource with KGDB. KGDB will only accept it if the kgdb/fiq feature is enabled (both with compile time and runtime switches) and the interrupt controller supports FIQ.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the amba-pl011 driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/amba-pl011.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 0b06dcf..63c67b0 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -58,6 +58,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/sizes.h> #include <linux/io.h> +#include <linux/kgdb.h>
#define UART_NR 14
@@ -1466,6 +1467,18 @@ static int pl011_hwinit(struct uart_port *port)
#ifdef CONFIG_CONSOLE_POLL
+static int pl011_poll_init(struct uart_port *port) +{ + int retval = pl011_hwinit(port); + +#ifdef CONFIG_KGDB_FIQ + if (retval == 0) + kgdb_register_fiq(port->irq); +#endif + + return retval; +} + static void pl011_quiesce_irqs(struct uart_port *port) { struct uart_amba_port *uap = @@ -1905,7 +1918,7 @@ static struct uart_ops amba_pl011_pops = { .config_port = pl011_config_port, .verify_port = pl011_verify_port, #ifdef CONFIG_CONSOLE_POLL - .poll_init = pl011_hwinit, + .poll_init = pl011_poll_init, .poll_get_char = pl011_get_poll_char, .poll_put_char = pl011_put_poll_char, #endif
Add a .poll_init() function that enables UART RX and registers the UART's irq with KGDB. By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user).
Note that the RX is not only enabled but also unmasked. This is required because otherwise the FIQ handler could never trigger. This unmask is copied from similar code in amba-pl011.c .
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/st-asc.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 8b2d735..2b5eb6e 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -30,6 +30,7 @@ #include <linux/of_platform.h> #include <linux/serial_core.h> #include <linux/clk.h> +#include <linux/kgdb.h>
#define DRIVER_NAME "st-asc" #define ASC_SERIAL_NAME "ttyAS" @@ -607,6 +608,25 @@ asc_verify_port(struct uart_port *port, struct serial_struct *ser) }
#ifdef CONFIG_CONSOLE_POLL + +#ifdef CONFIG_KGDB_FIQ +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int asc_poll_init(struct uart_port *port) +{ + struct asc_port *ascport = container_of(port, struct asc_port, port); + + /* register the FIQ with kgdb */ + kgdb_register_fiq(ascport->port.irq); + + /* enable RX interrupts in case the interrupt is used for NMI entry. */ + asc_enable_rx_interrupts(port); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + /* * Console polling routines for writing and reading from the uart while * in an interrupt or debug context (i.e. kgdb). @@ -649,6 +669,9 @@ static struct uart_ops asc_uart_ops = { .verify_port = asc_verify_port, .pm = asc_pm, #ifdef CONFIG_CONSOLE_POLL +#ifdef CONFIG_KGDB_FIQ + .poll_init = asc_poll_init, +#endif /* CONFIG_KGDB_FIQ */ .poll_get_char = asc_get_poll_char, .poll_put_char = asc_put_poll_char, #endif /* CONFIG_CONSOLE_POLL */
The architectures where this peripheral exists (ARM and SH) have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This change means the driver is no longer portable and therefore no longer suitable for compile testing.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/st-asc.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 26cec64d..e9b1735 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1527,7 +1527,7 @@ config SERIAL_FSL_LPUART_CONSOLE config SERIAL_ST_ASC tristate "ST ASC serial port support" select SERIAL_CORE - depends on ARM || COMPILE_TEST + depends on ARM help This driver is for the on-chip Asychronous Serial Controller on STMicroelectronics STi SoCs. diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 2b5eb6e..df709ee 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -152,12 +152,12 @@ static inline struct asc_port *to_asc_port(struct uart_port *port)
static inline u32 asc_in(struct uart_port *port, u32 offset) { - return readl(port->membase + offset); + return readl_relaxed(port->membase + offset); }
static inline void asc_out(struct uart_port *port, u32 offset, u32 value) { - writel(value, port->membase + offset); + writel_relaxed(value, port->membase + offset); }
/*
On Tue, 02 Sep 2014, Daniel Thompson wrote:
The architectures where this peripheral exists (ARM and SH) have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This change means the driver is no longer portable and therefore no longer suitable for compile testing.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org
Acked-by: Peter Griffin peter.griffin@linaro.org
Hi Daniel,
On 09/02/2014 03:00 PM, Daniel Thompson wrote:
The architectures where this peripheral exists (ARM and SH) have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This change means the driver is no longer portable and therefore no longer suitable for compile testing.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org
drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/st-asc.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)
You can add my: Acked-by: Maxime Coquelin maxime.coquelin@st.com
thanks! Maxime
From: Dirk Behme dirk.behme@de.bosch.com
Looking at the get_poll_char() function of the 8250.c serial driver, we learn:
* poll_get_char() doesn't have to save/disable/restore the interrupt registers. No interrupt handling is needed in this function at all. Remove it.
* Don't block in case there is no data available. So instead blocking in the do {} while loop, just return with NO_POLL_CHAR, immediately .
Additionally, while the i.MX6 register URXD[7-0] contain the RX_DATA, the upper bits of this register (URXD[15-10]) might contain some control flags. To ensure that these are not returned with the data read, just mask out URXD[7-0].
These changes fix the 'hang' working with kdb:
$ echo ttymxc3 > /sys/module/kgdboc/parameters/kgdboc $ echo g >/proc/sysrq-trigger [0]kdb> help ... <hang>
Signed-off-by: Dirk Behme dirk.behme@de.bosch.com Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/imx.c | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 044e86d..983668a 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -80,6 +80,7 @@ #define URXD_FRMERR (1<<12) #define URXD_BRK (1<<11) #define URXD_PRERR (1<<10) +#define URXD_RX_DATA (0xFF<<0) #define UCR1_ADEN (1<<15) /* Auto detect interrupt */ #define UCR1_ADBR (1<<14) /* Auto detect baud rate */ #define UCR1_TRDYEN (1<<13) /* Transmitter ready interrupt enable */ @@ -1506,32 +1507,10 @@ imx_verify_port(struct uart_port *port, struct serial_struct *ser) #if defined(CONFIG_CONSOLE_POLL) static int imx_poll_get_char(struct uart_port *port) { - struct imx_port_ucrs old_ucr; - unsigned int status; - unsigned char c; - - /* save control registers */ - imx_port_ucrs_save(port, &old_ucr); - - /* disable interrupts */ - writel(UCR1_UARTEN, port->membase + UCR1); - writel(old_ucr.ucr2 & ~(UCR2_ATEN | UCR2_RTSEN | UCR2_ESCI), - port->membase + UCR2); - writel(old_ucr.ucr3 & ~(UCR3_DCD | UCR3_RI | UCR3_DTREN), - port->membase + UCR3); - - /* poll */ - do { - status = readl(port->membase + USR2); - } while (~status & USR2_RDR); - - /* read */ - c = readl(port->membase + URXD0); - - /* restore control registers */ - imx_port_ucrs_restore(port, &old_ucr); + if (!(readl(port->membase + USR2) & USR2_RDR)) + return NO_POLL_CHAR;
- return c; + return readl(port->membase + URXD0) & URXD_RX_DATA; }
static void imx_poll_put_char(struct uart_port *port, unsigned char c)
This patch makes it possible to use the imx uart with KGDB's FIQ/NMI mode.
Main changes are:
.poll_init() will, if KGDB+FIQ are enabled, perform deeper hardware initialization to ensure the serial port is always active (required otherwise FIQ is not triggered by UART activity). This has an impact on power usage so it is conservatively enabled.
imx_put_poll_char() has been simplified to remove the code to disable interrupts. The present code can corrupt register state when re-entered from FIQ handler.
Both imx_put_poll_char() and imx_get_poll_char() adopt _relaxed() MMIO functions (which are safe for polled I/O and needed to avoid taking spin locks).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org Acked-by: Dirk Behme dirk.behme@de.bosch.com --- drivers/tty/serial/imx.c | 71 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 19 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 983668a..a201c61 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -49,6 +49,7 @@ #include <linux/of_device.h> #include <linux/io.h> #include <linux/dma-mapping.h> +#include <linux/kgdb.h>
#include <asm/irq.h> #include <linux/platform_data/serial-imx.h> @@ -1505,44 +1506,73 @@ imx_verify_port(struct uart_port *port, struct serial_struct *ser) }
#if defined(CONFIG_CONSOLE_POLL) + +#if defined(CONFIG_KGDB_FIQ) +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int imx_poll_init(struct uart_port *port) +{ + struct imx_port *sport = (struct imx_port *)port; + unsigned long flags; + unsigned long temp; + int retval; + + retval = clk_prepare_enable(sport->clk_ipg); + if (retval) + return retval; + retval = clk_prepare_enable(sport->clk_per); + if (retval) + clk_disable_unprepare(sport->clk_ipg); + + imx_setup_ufcr(sport, 0); + + spin_lock_irqsave(&sport->port.lock, flags); + + temp = readl(sport->port.membase + UCR1); + if (is_imx1_uart(sport)) + temp |= IMX1_UCR1_UARTCLKEN; + temp |= UCR1_UARTEN | UCR1_RRDYEN; + temp &= ~(UCR1_TXMPTYEN | UCR1_RTSDEN); + writel(temp, sport->port.membase + UCR1); + + temp = readl(sport->port.membase + UCR2); + temp |= UCR2_RXEN; + writel(temp, sport->port.membase + UCR2); + + spin_unlock_irqrestore(&sport->port.lock, flags); + + /* register the FIQ with kgdb */ + kgdb_register_fiq(sport->port.irq); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + static int imx_poll_get_char(struct uart_port *port) { - if (!(readl(port->membase + USR2) & USR2_RDR)) + if (!(readl_relaxed(port->membase + USR2) & USR2_RDR)) return NO_POLL_CHAR;
- return readl(port->membase + URXD0) & URXD_RX_DATA; + return readl_relaxed(port->membase + URXD0) & URXD_RX_DATA; }
static void imx_poll_put_char(struct uart_port *port, unsigned char c) { - struct imx_port_ucrs old_ucr; unsigned int status;
- /* save control registers */ - imx_port_ucrs_save(port, &old_ucr); - - /* disable interrupts */ - writel(UCR1_UARTEN, port->membase + UCR1); - writel(old_ucr.ucr2 & ~(UCR2_ATEN | UCR2_RTSEN | UCR2_ESCI), - port->membase + UCR2); - writel(old_ucr.ucr3 & ~(UCR3_DCD | UCR3_RI | UCR3_DTREN), - port->membase + UCR3); - /* drain */ do { - status = readl(port->membase + USR1); + status = readl_relaxed(port->membase + USR1); } while (~status & USR1_TRDY);
/* write */ - writel(c, port->membase + URTX0); + writel_relaxed(c, port->membase + URTX0);
/* flush */ do { - status = readl(port->membase + USR2); + status = readl_relaxed(port->membase + USR2); } while (~status & USR2_TXDC); - - /* restore control registers */ - imx_port_ucrs_restore(port, &old_ucr); } #endif
@@ -1563,6 +1593,9 @@ static struct uart_ops imx_pops = { .config_port = imx_config_port, .verify_port = imx_verify_port, #if defined(CONFIG_CONSOLE_POLL) +#if defined(CONFIG_KGDB_FIQ) + .poll_init = imx_poll_init, +#endif .poll_get_char = imx_poll_get_char, .poll_put_char = imx_poll_put_char, #endif
On Tue, 2 Sep 2014, Daniel Thompson wrote:
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches are seperated into three distinct groups:
arm specific changes; these provide multi-platform support for FIQ (including raising an IPI using FIQ to ensure effective SMP support) and extend ARM KGDB support to use the features provided.
irqchip changes; updates to the gic and vic drivers to provide support for routing certain interrupt sources to FIQ.
serial changes; driver support to allow the UART interrupt to be routed to FIQ. The already mainlined kgdb NMI infrastructure (mostly found in drivers/tty/serial/kgdb_nmi.c) will re-route the kgdb console UART's interrupt signal from IRQ to FIQ. Naturally the UART will no longer function normally and will instead be managed by kgdb using the polled I/O functions. Any character delivered to the UART causes the kgdb handler function to be called.
To be honest, what you are doing is just ass backwards.
The use case you are looking for is the most irrelevant of all. Just because KGDB is on some managerial "must have items" checklist does not make it useful.
The only relevant use cases of FIQs are the same as those of NMIs on x86:
- Watchdog to detect stuck cpus and issue stack traces
- Performace monitoring
KGDB falls into place once you solved the above.
That said for the general approach, I'll have a look at the irq related patches in a minute.
Thanks,
tglx
On 03/09/14 00:02, Thomas Gleixner wrote:
On Tue, 2 Sep 2014, Daniel Thompson wrote:
This patchset makes it possible to use kgdb's NMI infrastructure on ARM platforms.
The patches are seperated into three distinct groups:
arm specific changes; these provide multi-platform support for FIQ (including raising an IPI using FIQ to ensure effective SMP support) and extend ARM KGDB support to use the features provided.
irqchip changes; updates to the gic and vic drivers to provide support for routing certain interrupt sources to FIQ.
serial changes; driver support to allow the UART interrupt to be routed to FIQ. The already mainlined kgdb NMI infrastructure (mostly found in drivers/tty/serial/kgdb_nmi.c) will re-route the kgdb console UART's interrupt signal from IRQ to FIQ. Naturally the UART will no longer function normally and will instead be managed by kgdb using the polled I/O functions. Any character delivered to the UART causes the kgdb handler function to be called.
To be honest, what you are doing is just ass backwards.
The use case you are looking for is the most irrelevant of all. Just because KGDB is on some managerial "must have items" checklist does not make it useful.
The FIQ based interactive debugger use case is fairly common on Android, especially for Nexus devices (they have an out-of-tree debugger similar to kdb for this).
I think it finds favour there because during the development phases where the console is unplugged to allow developers to go walkabout live with a prototype phone. The interactive debugger is used for post-morteming when something breaks. At this stage of development are reluctant to expose/consume hardware resources (JTAG pins, RAM, FLASH) for JTAG or kexec/kdump post-mortems.
The only relevant use cases of FIQs are the same as those of NMIs on x86:
- Watchdog to detect stuck cpus and issue stack traces
Russell put together a quick 'n dirty version of the NMI stack trace code based on a subset of my patchset. Based on his feedback, later revisions of my patchset are structured to simplify adding this code.
Daniel.
- Performace monitoring
KGDB falls into place once you solved the above.
That said for the general approach, I'll have a look at the irq related patches in a minute.
Thanks,
tglx
On Wed, 3 Sep 2014, Daniel Thompson wrote:
On 03/09/14 00:02, Thomas Gleixner wrote:
The use case you are looking for is the most irrelevant of all. Just because KGDB is on some managerial "must have items" checklist does not make it useful.
The FIQ based interactive debugger use case is fairly common on Android, especially for Nexus devices (they have an out-of-tree debugger similar to kdb for this).
I think it finds favour there because during the development phases where the console is unplugged to allow developers to go walkabout live with a prototype phone. The interactive debugger is used for post-morteming when something breaks. At this stage of development are reluctant to expose/consume hardware resources (JTAG pins, RAM, FLASH) for JTAG or kexec/kdump post-mortems.
If things are common and favoured for whatever reasons, that does not make them a proper solution per se.
I rather have a kexec debug kernel started if my production/test kernel explodes than hooking up a lousy debugger via serial, but thats a matter of taste and reason.
The only relevant use cases of FIQs are the same as those of NMIs on x86:
- Watchdog to detect stuck cpus and issue stack traces
Russell put together a quick 'n dirty version of the NMI stack trace code based on a subset of my patchset. Based on his feedback, later revisions of my patchset are structured to simplify adding this code.
And I still say, that this is the first use case which should be provided as it is simple enough, immediately usefull and testable for everyone.
So, really what I want to see in the first place is a minimalistic patch series which
1) Implements the core infrastructure for FIQ support
2) Converts a single interrupt controller to play with #1
3) Provides the simplest useful use case using #1
That's at max 5 patches, which are easy enough to review, and not a patch series which changes the world and some more in one go.
We need to get the design and the infrastructure right in the first place. What I've seen so far is just a complete lack of design. If you take off your KGDB blinkers, you might notice that yourself.
As I said before:
KGDB falls into place once you solved the above.
Thanks,
tglx
On 03/09/14 11:06, Thomas Gleixner wrote:
On Wed, 3 Sep 2014, Daniel Thompson wrote:
On 03/09/14 00:02, Thomas Gleixner wrote:
The use case you are looking for is the most irrelevant of all. Just because KGDB is on some managerial "must have items" checklist does not make it useful.
The FIQ based interactive debugger use case is fairly common on Android, especially for Nexus devices (they have an out-of-tree debugger similar to kdb for this).
I think it finds favour there because during the development phases where the console is unplugged to allow developers to go walkabout live with a prototype phone. The interactive debugger is used for post-morteming when something breaks. At this stage of development are reluctant to expose/consume hardware resources (JTAG pins, RAM, FLASH) for JTAG or kexec/kdump post-mortems.
If things are common and favoured for whatever reasons, that does not make them a proper solution per se.
I rather have a kexec debug kernel started if my production/test kernel explodes than hooking up a lousy debugger via serial, but thats a matter of taste and reason.
The only relevant use cases of FIQs are the same as those of NMIs on x86:
- Watchdog to detect stuck cpus and issue stack traces
Russell put together a quick 'n dirty version of the NMI stack trace code based on a subset of my patchset. Based on his feedback, later revisions of my patchset are structured to simplify adding this code.
And I still say, that this is the first use case which should be provided as it is simple enough, immediately usefull and testable for everyone.
So, really what I want to see in the first place is a minimalistic patch series which
Implements the core infrastructure for FIQ support
Converts a single interrupt controller to play with #1
Provides the simplest useful use case using #1
That's at max 5 patches, which are easy enough to review, and not a patch series which changes the world and some more in one go.
Ok. I'll look into this.
We need to get the design and the infrastructure right in the first place. What I've seen so far is just a complete lack of design. If you take off your KGDB blinkers, you might notice that yourself.
Good point. I have tried shrinking the patchset previously but I ended up splitting it by sub-system rather than simplifying the use-case.
The guess the effect of shrinking the patchset in this way was more to shrink the pool of likely reviewers than to shrink the size of the problem... Clearly not a good idea (and not intentional on my part).
As I said before:
KGDB falls into place once you solved the above.
I hope so...
This patchset implements arch_trigger_all_cpu_backtrace for arm.
Non-maskable signalling relies on the kernel being able to access FIQ and therefore, for devices that implement TrustZone, it will work only on systems that boot the kernel in secure mode.
Tested on Freescale iMX.6 (both via SysRq-l and by deliberately locking up the kernel with CONFIG_DEBUG_SPINLOCK=y).
Changes *before* v1:
- This patchset is a hugely cut-down successor to "[PATCH v11 00/19] arm: KGDB NMI/FIQ support". Thanks to Thomas Gleixner for suggesting the new structure. For historic details see: https://lkml.org/lkml/2014/9/2/227
- Fix bug in __fiq_abt (no longer passes a bad struct pt_regs value). In fixing this we also remove the useless indirection previously found in the fiq_handler macro.
- Make default fiq handler "always on" by migrating from fiq.c to traps.c and replace do_unexp_fiq with the new handler (Russell King).
- Add arm64 version of fiq.h (Russell King)
- Removed conditional branching and code from irq-gic.c, this is replaced by much simpler code that relies on the GIC specification's heavy use of read-as-zero/write-ignored (Russell King)
Daniel Thompson (5): arm: fiq: Replace default FIQ handler arm: smp: Introduce a non-maskable IPI arm64: Introduce dummy version of asm/fiq.h irqchip: gic: Add support for IPI FIQ irqchip: gic: Group 0 workaround.
Russell King (1): arm: Implement arch_trigger_all_cpu_backtrace
arch/arm/Kconfig.debug | 14 +++ arch/arm/include/asm/hardirq.h | 2 +- arch/arm/include/asm/irq.h | 5 + arch/arm/include/asm/smp.h | 7 ++ arch/arm/kernel/entry-armv.S | 110 ++++++++++++++++++-- arch/arm/kernel/setup.c | 8 +- arch/arm/kernel/smp.c | 67 ++++++++++++ arch/arm/kernel/traps.c | 34 +++++- arch/arm64/include/asm/fiq.h | 18 ++++ drivers/irqchip/irq-gic.c | 223 +++++++++++++++++++++++++++++++++++++--- include/linux/irqchip/arm-gic.h | 3 + 11 files changed, 464 insertions(+), 27 deletions(-) create mode 100644 arch/arm64/include/asm/fiq.h
-- 1.9.3
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com --- arch/arm/kernel/entry-armv.S | 110 +++++++++++++++++++++++++++++++++++++++---- arch/arm/kernel/setup.c | 8 +++- arch/arm/kernel/traps.c | 26 ++++++++-- 3 files changed, 130 insertions(+), 14 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..03dc0e0 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -146,7 +146,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0 + .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) @@ -183,10 +183,35 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6}
#ifdef CONFIG_TRACE_IRQFLAGS + .if \call_trace bl trace_hardirqs_off + .endif #endif .endm
+@ +@ svc_exit_via_fiq - similar to svc_exit but switches to FIQ mode before exit +@ +@ This macro acts in a similar manner to svc_exit but switches to FIQ +@ mode to restore the final part of the register state. +@ +@ We cannot use the normal svc_exit procedure because that would +@ clobber spsr_svc (FIQ could be delivered during the first few instructions +@ of vector_swi meaning its contents have not been saved anywhere). +@ + .macro svc_exit_via_fiq, rpsr + + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + .endm + .align 5 __dabt_svc: svc_entry @@ -295,6 +320,15 @@ __pabt_svc: ENDPROC(__pabt_svc)
.align 5 +__fiq_svc: + svc_entry 0, 0 + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + + .align 5 .LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -305,6 +339,38 @@ ENDPROC(__pabt_svc) .word fp_enter
/* + * Abort mode handlers + */ + +@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@ + .align 5 +__fiq_abt: + svc_entry 0, 0 + + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov r0, lr @ Save lr_abt + mrs r1, spsr @ Save spsr_abt, abort is now safe + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + push {r0 - r1} + + sub r0, sp, #8 @ struct pt_regs *regs + bl handle_fiq_as_nmi + + pop {r0 - r1} + msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT + mov lr, r0 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r1 @ Restore spsr_abt + msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT + + svc_exit_via_fiq r5 + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + +/* * User mode handlers * * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE @@ -683,6 +749,18 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
+ .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + /* * Register switch for ARMv3 and ARMv4 processors * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info @@ -1118,17 +1196,29 @@ vector_addrexcptn: b vector_addrexcptn
/*============================================================================= - * Undefined FIQs + * FIQ "NMI" handler *----------------------------------------------------------------------------- - * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC - * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg. - * Basically to switch modes, we *HAVE* to clobber one register... brain - * damage alert! I don't think that we can execute any code in here in any - * other mode than FIQ... Ok you can switch to another mode, but you can't - * get out of that mode without clobbering one register. + * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 + * systems. */ -vector_fiq: - subs pc, lr, #4 + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3]; + u32 fiq[3]; } ____cacheline_aligned;
#ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t" - "msr cpsr_c, %7" + "msr cpsr_c, %7\n\t" + "add r14, %0, %8\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE), @@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])), + PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14"); #endif diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index c8e4bb7..7912a9e 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -25,6 +25,7 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/irq.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -460,10 +461,29 @@ die_sig: arm_notify_die("Oops - undefined instruction", regs, &info, 0, 6); }
-asmlinkage void do_unexp_fiq (struct pt_regs *regs) +/* + * Handle FIQ similarly to NMI on x86 systems. + * + * The runtime environment for NMIs is extremely restrictive + * (NMIs can pre-empt critical sections meaning almost all locking is + * forbidden) meaning this default FIQ handling must only be used in + * circumstances where non-maskability improves robustness, such as + * watchdog or debug logic. + * + * This handler is not appropriate for general purpose use in drivers + * platform code and can be overrideen using set_fiq_handler. + */ +asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) { - printk("Hmm. Unexpected FIQ received, but trying to continue\n"); - printk("You may have a hardware problem...\n"); +#ifdef CONFIG_FIQ + struct pt_regs *old_regs = set_irq_regs(regs); + + nmi_enter(); + /* nop for now */ + nmi_exit(); + + set_irq_regs(old_regs); +#endif }
/*
On Thu, 4 Sep 2014, Daniel Thompson wrote:
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com
arch/arm/kernel/entry-armv.S | 110 +++++++++++++++++++++++++++++++++++++++---- arch/arm/kernel/setup.c | 8 +++- arch/arm/kernel/traps.c | 26 ++++++++-- 3 files changed, 130 insertions(+), 14 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..03dc0e0 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -146,7 +146,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0
- .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
@@ -183,10 +183,35 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6} #ifdef CONFIG_TRACE_IRQFLAGS
- .if \call_trace bl trace_hardirqs_off
- .endif
#endif .endm +@ +@ svc_exit_via_fiq - similar to svc_exit but switches to FIQ mode before exit +@ +@ This macro acts in a similar manner to svc_exit but switches to FIQ +@ mode to restore the final part of the register state. +@ +@ We cannot use the normal svc_exit procedure because that would +@ clobber spsr_svc (FIQ could be delivered during the first few instructions +@ of vector_swi meaning its contents have not been saved anywhere). +@
Wouldn't it be better for this macro to live in entry-header.S alongside the others? Also you should probably create a Thumb2 version.
- .macro svc_exit_via_fiq, rpsr
- mov r0, sp
- ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will
@ clobber state restored below)
- msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT
- add r8, r0, #S_PC
- ldr r9, [r0, #S_PSR]
- msr spsr_cxsf, r9
- ldr r0, [r0, #S_R0]
- ldmia r8, {pc}^
- .endm
- .align 5
__dabt_svc: svc_entry @@ -295,6 +320,15 @@ __pabt_svc: ENDPROC(__pabt_svc) .align 5 +__fiq_svc:
- svc_entry 0, 0
- mov r0, sp @ struct pt_regs *regs
- bl handle_fiq_as_nmi
- svc_exit_via_fiq r5
- UNWIND(.fnend )
+ENDPROC(__fiq_svc)
- .align 5
.LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -305,6 +339,38 @@ ENDPROC(__pabt_svc) .word fp_enter /*
- Abort mode handlers
- */
+@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@
- .align 5
+__fiq_abt:
- svc_entry 0, 0
- msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT
- mov r0, lr @ Save lr_abt
- mrs r1, spsr @ Save spsr_abt, abort is now safe
- msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT
- push {r0 - r1}
- sub r0, sp, #8 @ struct pt_regs *regs
- bl handle_fiq_as_nmi
- pop {r0 - r1}
- msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT
- mov lr, r0 @ Restore lr_abt, abort is unsafe
- msr spsr_cxsf, r1 @ Restore spsr_abt
- msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT
- svc_exit_via_fiq r5
- UNWIND(.fnend )
+ENDPROC(__fiq_svc)
+/*
- User mode handlers
- EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE
@@ -683,6 +749,18 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
- .align 5
+__fiq_usr:
- usr_entry
- kuser_cmpxchg_check
- mov r0, sp @ struct pt_regs *regs
- bl handle_fiq_as_nmi
- get_thread_info tsk
- mov why, #0
- b ret_to_user_from_irq
- UNWIND(.fnend )
+ENDPROC(__fiq_usr)
/*
- Register switch for ARMv3 and ARMv4 processors
- r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info
@@ -1118,17 +1196,29 @@ vector_addrexcptn: b vector_addrexcptn /*=============================================================================
- Undefined FIQs
*-----------------------------------------------------------------------------
- FIQ "NMI" handler
- Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC
- MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg.
- Basically to switch modes, we *HAVE* to clobber one register... brain
- damage alert! I don't think that we can execute any code in here in any
- other mode than FIQ... Ok you can switch to another mode, but you can't
- get out of that mode without clobbering one register.
- Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86
*/
- systems.
-vector_fiq:
- subs pc, lr, #4
- vector_stub fiq, FIQ_MODE, 4
- .long __fiq_usr @ 0 (USR_26 / USR_32)
- .long __fiq_svc @ 1 (FIQ_26 / FIQ_32)
- .long __fiq_svc @ 2 (IRQ_26 / IRQ_32)
- .long __fiq_svc @ 3 (SVC_26 / SVC_32)
- .long __fiq_svc @ 4
- .long __fiq_svc @ 5
- .long __fiq_svc @ 6
- .long __fiq_abt @ 7
- .long __fiq_svc @ 8
- .long __fiq_svc @ 9
- .long __fiq_svc @ a
- .long __fiq_svc @ b
- .long __fiq_svc @ c
- .long __fiq_svc @ d
- .long __fiq_svc @ e
- .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3];
- u32 fiq[3];
} ____cacheline_aligned; #ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t"
- "msr cpsr_c, %7"
- "msr cpsr_c, %7\n\t"
- "add r14, %0, %8\n\t"
- "mov sp, r14\n\t"
- "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
@@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])),
PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE),
"I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14");
#endif diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index c8e4bb7..7912a9e 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -25,6 +25,7 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/irq.h> #include <linux/atomic.h> #include <asm/cacheflush.h> @@ -460,10 +461,29 @@ die_sig: arm_notify_die("Oops - undefined instruction", regs, &info, 0, 6); } -asmlinkage void do_unexp_fiq (struct pt_regs *regs) +/*
- Handle FIQ similarly to NMI on x86 systems.
- The runtime environment for NMIs is extremely restrictive
- (NMIs can pre-empt critical sections meaning almost all locking is
- forbidden) meaning this default FIQ handling must only be used in
- circumstances where non-maskability improves robustness, such as
- watchdog or debug logic.
- This handler is not appropriate for general purpose use in drivers
- platform code and can be overrideen using set_fiq_handler.
- */
+asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) {
- printk("Hmm. Unexpected FIQ received, but trying to continue\n");
- printk("You may have a hardware problem...\n");
+#ifdef CONFIG_FIQ
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- /* nop for now */
- nmi_exit();
- set_irq_regs(old_regs);
+#endif } /* -- 1.9.3
On 04/09/14 19:57, Nicolas Pitre wrote:
On Thu, 4 Sep 2014, Daniel Thompson wrote:
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com
arch/arm/kernel/entry-armv.S | 110 +++++++++++++++++++++++++++++++++++++++---- arch/arm/kernel/setup.c | 8 +++- arch/arm/kernel/traps.c | 26 ++++++++-- 3 files changed, 130 insertions(+), 14 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..03dc0e0 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -146,7 +146,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0
- .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
@@ -183,10 +183,35 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6} #ifdef CONFIG_TRACE_IRQFLAGS
- .if \call_trace bl trace_hardirqs_off
- .endif
#endif .endm +@ +@ svc_exit_via_fiq - similar to svc_exit but switches to FIQ mode before exit +@ +@ This macro acts in a similar manner to svc_exit but switches to FIQ +@ mode to restore the final part of the register state. +@ +@ We cannot use the normal svc_exit procedure because that would +@ clobber spsr_svc (FIQ could be delivered during the first few instructions +@ of vector_swi meaning its contents have not been saved anywhere). +@
Wouldn't it be better for this macro to live in entry-header.S alongside the others?
I'm not sure either way.
svc_exit_from_fiq isn't needed by entry-common.S and cannot be used by entry-v7m.S because v7m has no FIQ. For that reason I decided to place it alongside svc_entry in entry-armv.S rather than alongside svc_exit in entry-header.S .
I am happy to move it if you have a strong preference here. Please let me know.
Also you should probably create a Thumb2 version.
I'll look at this.
Daniel.
- .macro svc_exit_via_fiq, rpsr
- mov r0, sp
- ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will
@ clobber state restored below)
- msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT
- add r8, r0, #S_PC
- ldr r9, [r0, #S_PSR]
- msr spsr_cxsf, r9
- ldr r0, [r0, #S_R0]
- ldmia r8, {pc}^
- .endm
- .align 5
__dabt_svc: svc_entry @@ -295,6 +320,15 @@ __pabt_svc: ENDPROC(__pabt_svc) .align 5 +__fiq_svc:
- svc_entry 0, 0
- mov r0, sp @ struct pt_regs *regs
- bl handle_fiq_as_nmi
- svc_exit_via_fiq r5
- UNWIND(.fnend )
+ENDPROC(__fiq_svc)
- .align 5
.LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -305,6 +339,38 @@ ENDPROC(__pabt_svc) .word fp_enter /*
- Abort mode handlers
- */
+@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@
- .align 5
+__fiq_abt:
- svc_entry 0, 0
- msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT
- mov r0, lr @ Save lr_abt
- mrs r1, spsr @ Save spsr_abt, abort is now safe
- msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT
- push {r0 - r1}
- sub r0, sp, #8 @ struct pt_regs *regs
- bl handle_fiq_as_nmi
- pop {r0 - r1}
- msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT
- mov lr, r0 @ Restore lr_abt, abort is unsafe
- msr spsr_cxsf, r1 @ Restore spsr_abt
- msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT
- svc_exit_via_fiq r5
- UNWIND(.fnend )
+ENDPROC(__fiq_svc)
+/*
- User mode handlers
- EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE
@@ -683,6 +749,18 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
- .align 5
+__fiq_usr:
- usr_entry
- kuser_cmpxchg_check
- mov r0, sp @ struct pt_regs *regs
- bl handle_fiq_as_nmi
- get_thread_info tsk
- mov why, #0
- b ret_to_user_from_irq
- UNWIND(.fnend )
+ENDPROC(__fiq_usr)
/*
- Register switch for ARMv3 and ARMv4 processors
- r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info
@@ -1118,17 +1196,29 @@ vector_addrexcptn: b vector_addrexcptn /*=============================================================================
- Undefined FIQs
*-----------------------------------------------------------------------------
- FIQ "NMI" handler
- Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC
- MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg.
- Basically to switch modes, we *HAVE* to clobber one register... brain
- damage alert! I don't think that we can execute any code in here in any
- other mode than FIQ... Ok you can switch to another mode, but you can't
- get out of that mode without clobbering one register.
- Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86
*/
- systems.
-vector_fiq:
- subs pc, lr, #4
- vector_stub fiq, FIQ_MODE, 4
- .long __fiq_usr @ 0 (USR_26 / USR_32)
- .long __fiq_svc @ 1 (FIQ_26 / FIQ_32)
- .long __fiq_svc @ 2 (IRQ_26 / IRQ_32)
- .long __fiq_svc @ 3 (SVC_26 / SVC_32)
- .long __fiq_svc @ 4
- .long __fiq_svc @ 5
- .long __fiq_svc @ 6
- .long __fiq_abt @ 7
- .long __fiq_svc @ 8
- .long __fiq_svc @ 9
- .long __fiq_svc @ a
- .long __fiq_svc @ b
- .long __fiq_svc @ c
- .long __fiq_svc @ d
- .long __fiq_svc @ e
- .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3];
- u32 fiq[3];
} ____cacheline_aligned; #ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t"
- "msr cpsr_c, %7"
- "msr cpsr_c, %7\n\t"
- "add r14, %0, %8\n\t"
- "mov sp, r14\n\t"
- "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
@@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])),
PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE),
"I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14");
#endif diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index c8e4bb7..7912a9e 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -25,6 +25,7 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/irq.h> #include <linux/atomic.h> #include <asm/cacheflush.h> @@ -460,10 +461,29 @@ die_sig: arm_notify_die("Oops - undefined instruction", regs, &info, 0, 6); } -asmlinkage void do_unexp_fiq (struct pt_regs *regs) +/*
- Handle FIQ similarly to NMI on x86 systems.
- The runtime environment for NMIs is extremely restrictive
- (NMIs can pre-empt critical sections meaning almost all locking is
- forbidden) meaning this default FIQ handling must only be used in
- circumstances where non-maskability improves robustness, such as
- watchdog or debug logic.
- This handler is not appropriate for general purpose use in drivers
- platform code and can be overrideen using set_fiq_handler.
- */
+asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) {
- printk("Hmm. Unexpected FIQ received, but trying to continue\n");
- printk("You may have a hardware problem...\n");
+#ifdef CONFIG_FIQ
- struct pt_regs *old_regs = set_irq_regs(regs);
- nmi_enter();
- /* nop for now */
- nmi_exit();
- set_irq_regs(old_regs);
+#endif } /* -- 1.9.3
On Fri, 5 Sep 2014, Daniel Thompson wrote:
On 04/09/14 19:57, Nicolas Pitre wrote:
On Thu, 4 Sep 2014, Daniel Thompson wrote:
+@ svc_exit_via_fiq - similar to svc_exit but switches to FIQ mode before exit +@ +@ This macro acts in a similar manner to svc_exit but switches to FIQ +@ mode to restore the final part of the register state. +@ +@ We cannot use the normal svc_exit procedure because that would +@ clobber spsr_svc (FIQ could be delivered during the first few instructions +@ of vector_swi meaning its contents have not been saved anywhere). +@
Wouldn't it be better for this macro to live in entry-header.S alongside the others?
I'm not sure either way.
svc_exit_from_fiq isn't needed by entry-common.S and cannot be used by entry-v7m.S because v7m has no FIQ. For that reason I decided to place it alongside svc_entry in entry-armv.S rather than alongside svc_exit in entry-header.S .
Here's a list of macros from entry-headers.S that I don't see being used in entry-v7m.S: zero_fp, alignment_trap, store_user_sp_lr, load_user_sp_lr, svc_exit, ct_user_exit, ct_user_enter.
Yet, entry-header.S contains macros such as v7m_exception_entry and v7m_exception_slow_exit which are unlikely to ever be used in entry-armv.S.
I am happy to move it if you have a strong preference here. Please let me know.
I don't have a strong preference, but a preference nevertheless.
It just looks odd that a file is already dedicated to gather all those macros for similar purposes and this one is not there. That makes future code review/maintenance a bit annoying if similar things are not kept in one place.
Nicolas
On 05/09/14 19:04, Nicolas Pitre wrote:
On Fri, 5 Sep 2014, Daniel Thompson wrote:
On 04/09/14 19:57, Nicolas Pitre wrote:
On Thu, 4 Sep 2014, Daniel Thompson wrote:
+@ svc_exit_via_fiq - similar to svc_exit but switches to FIQ mode before exit +@ +@ This macro acts in a similar manner to svc_exit but switches to FIQ +@ mode to restore the final part of the register state. +@ +@ We cannot use the normal svc_exit procedure because that would +@ clobber spsr_svc (FIQ could be delivered during the first few instructions +@ of vector_swi meaning its contents have not been saved anywhere). +@
Wouldn't it be better for this macro to live in entry-header.S alongside the others?
I'm not sure either way.
svc_exit_from_fiq isn't needed by entry-common.S and cannot be used by entry-v7m.S because v7m has no FIQ. For that reason I decided to place it alongside svc_entry in entry-armv.S rather than alongside svc_exit in entry-header.S .
Here's a list of macros from entry-headers.S that I don't see being used in entry-v7m.S: zero_fp, alignment_trap, store_user_sp_lr, load_user_sp_lr, svc_exit, ct_user_exit, ct_user_enter.
All except one of of these are consumed either by entry-common.S or by entry-header.S itself.
svc_exit is the only exception, it is exclusively consumed by entry-armv.S.
Yet, entry-header.S contains macros such as v7m_exception_entry and v7m_exception_slow_exit which are unlikely to ever be used in entry-armv.S.
Although these macros are v7m specific they are used in conditionally compiled code within entry-common.S in order to implement system call handlers.
I am happy to move it if you have a strong preference here. Please let me know.
I don't have a strong preference, but a preference nevertheless.
It just looks odd that a file is already dedicated to gather all those macros for similar purposes and this one is not there. That makes future code review/maintenance a bit annoying if similar things are not kept in one place.
I will move the macro to entry-common.S in order to keep it alongside its workalike function svc_exit.
That said, I do retain a (pretty weakly held) view that both these macros might be better off moved to entry-armv.S .
This patch introduces a new IPI intended to be routed to FIQ rather than IRQ. An IPI based upon FIQ can be used to implement debug features that require non-maskable interrupts.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk --- arch/arm/include/asm/hardirq.h | 2 +- arch/arm/include/asm/smp.h | 3 +++ arch/arm/kernel/smp.c | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/arch/arm/include/asm/hardirq.h b/arch/arm/include/asm/hardirq.h index fe3ea77..5df33e3 100644 --- a/arch/arm/include/asm/hardirq.h +++ b/arch/arm/include/asm/hardirq.h @@ -5,7 +5,7 @@ #include <linux/threads.h> #include <asm/irq.h>
-#define NR_IPI 8 +#define NR_IPI 9
typedef struct { unsigned int __softirq_pending; diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 2ec765c..0e23384 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -20,6 +20,9 @@
#define raw_smp_processor_id() (current_thread_info()->cpu)
+/* bitmap of IPIs that must be signalled using FIQ */ +#define SMP_IPI_FIQ_MASK 0x0100 + struct seq_file;
/* diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 9388a3d..b2404d0 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -72,6 +72,7 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_FIQ, };
static DECLARE_COMPLETION(cpu_running); @@ -451,6 +452,7 @@ static const char *ipi_types[NR_IPI] __tracepoint_string = { S(IPI_CPU_STOP, "CPU stop interrupts"), S(IPI_IRQ_WORK, "IRQ work interrupts"), S(IPI_COMPLETION, "completion interrupts"), + S(IPI_FIQ, "FIQ interrupts"), };
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr) @@ -618,6 +620,11 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break;
+ case IPI_FIQ: + BUILD_BUG_ON(SMP_IPI_FIQ_MASK != BIT(IPI_FIQ)); + pr_warn("CPU%u: IPI FIQ delivered via IRQ vector\n", cpu); + break; + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr);
Drivers that are shared between arm and arm64 and which employ FIQ on arm cannot include asm/fiq.h without #ifdef'ing. This patch introduces a dummy version of asm/fiq.h to arm64 to avoid this.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Will Deacon will.deacon@arm.com --- arch/arm64/include/asm/fiq.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 arch/arm64/include/asm/fiq.h
diff --git a/arch/arm64/include/asm/fiq.h b/arch/arm64/include/asm/fiq.h new file mode 100644 index 0000000..909ec54 --- /dev/null +++ b/arch/arm64/include/asm/fiq.h @@ -0,0 +1,18 @@ +/* + * arch/arm64/include/asm/fiq.h + * + * Placeholder to reduce #ifdef'ing in shared arm/arm64 drivers. + */ + +#ifndef __ASM_FIQ_H +#define __ASM_FIQ_H + +/* + * This placeholder allows code of the following form to be simplified: + * + * #ifdef CONFIG_FIQ + * #include <asm/fiq.h> + * #endif + */ + +#endif
This patch provides support for arm's newly added IPI FIQ. It works by placing all interrupt sources *except* IPI FIQ in group 1 and then flips a configuration bit in the GIC such that group 1 interrupts use IRQ and group 0 interrupts use FIQ.
All GIC hardware except GICv1-without-TrustZone support provides a means to group exceptions into group 0 and group 1. However the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world" (a feature that allows grouping to be used to allow hardware peripherals to send interrupts into the secure world). However when grouping is not available we can rely on the GIC's RAZ/WI semantics and avoid conditional code.
Tested on Freescale i.MX6 (quad A9, GICv1-with-TrustZone running in secure mode).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net --- arch/arm/kernel/traps.c | 7 +- drivers/irqchip/irq-gic.c | 171 +++++++++++++++++++++++++++++++++++++--- include/linux/irqchip/arm-gic.h | 3 + 3 files changed, 171 insertions(+), 10 deletions(-)
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index 7912a9e..2c219c7 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -26,6 +26,7 @@ #include <linux/init.h> #include <linux/sched.h> #include <linux/irq.h> +#include <linux/irqchip/arm-gic.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -479,7 +480,11 @@ asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) struct pt_regs *old_regs = set_irq_regs(regs);
nmi_enter(); - /* nop for now */ + +#ifdef CONFIG_ARM_GIC + gic_handle_fiq_ipi(); +#endif + nmi_exit();
set_irq_regs(old_regs); diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..ffc64ed 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -39,8 +39,10 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/ratelimit.h>
#include <asm/cputype.h> +#include <asm/fiq.h> #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -48,6 +50,10 @@ #include "irq-gic-common.h" #include "irqchip.h"
+#ifndef SMP_IPI_FIQ_MASK +#define SMP_IPI_FIQ_MASK 0 +#endif + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -71,6 +77,8 @@ struct gic_chip_data { };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); +/* A fiq-safe spinlock must only be locked when the FIQ is masked */ +static DEFINE_RAW_SPINLOCK(fiq_safe_migration_lock);
/* * The GIC mapping of CPU interfaces does not necessarily match @@ -325,6 +333,106 @@ static struct irq_chip gic_chip = { .irq_set_wake = gic_set_wake, };
+#ifdef CONFIG_FIQ +/* + * Shift an interrupt between Group 0 and Group 1. + * + * In addition to changing the group we also modify the priority to + * match what "ARM strongly recommends" for a system where no Group 1 + * interrupt must ever preempt a Group 0 interrupt. + * + * If is safe to call this function on systems which do not support + * grouping (it will have no effect). + */ +static void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_mask = BIT(hwirq % 32); + u32 grp_val; + + unsigned int pri_reg = (hwirq / 4) * 4; + u32 pri_mask = BIT(7 + ((hwirq % 4) * 8)); + u32 pri_val; + + /* + * Systems which do not support grouping will have no bits + * set in IGROUP[0] (and all systems which do will have set bits). + */ + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + 0); + if (!grp_val) + return; + + raw_spin_lock(&irq_controller_lock); + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(base + GIC_DIST_PRI + pri_reg); + + if (group) { + grp_val |= grp_mask; + pri_val |= pri_mask; + } else { + grp_val &= ~grp_mask; + pri_val &= ~pri_mask; + } + + writel_relaxed(grp_val, base + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, base + GIC_DIST_PRI + pri_reg); + + raw_spin_unlock(&irq_controller_lock); +} + +/* + * Test which group an interrupt belongs to. + * + * Returns 0 if the controller does not support grouping. + */ +static int gic_get_group_irq(void __iomem *base, unsigned int hwirq) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_val; + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + + return (grp_val >> (hwirq % 32)) & 1; +} + +/* + * Fully acknowledge (both ack and eoi) any outstanding FIQ-based IPI, + * otherwise do nothing. + */ +void gic_handle_fiq_ipi(void) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *cpu_base = gic_data_cpu_base(gic); + unsigned long irqstat, irqnr; + + if (WARN_ON(!in_nmi())) + return; + + while ((1u << readl_relaxed(cpu_base + GIC_CPU_HIGHPRI)) & + SMP_IPI_FIQ_MASK) { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + WARN_RATELIMIT(irqnr > 16, + "Unexpected irqnr %lu (bad prioritization?)\n", + irqnr); + } +} +#else /* CONFIG_FIQ */ +static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) +{ +} + +static inline int gic_get_group_irq(void __iomem *base, unsigned int hwirq) +{ + return 0; +} +#endif /* CONFIG_FIQ */ + void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) { if (gic_nr >= MAX_GIC_NR) @@ -373,7 +481,18 @@ static void __init gic_dist_init(struct gic_chip_data *gic)
gic_dist_config(base, gic_irqs, NULL);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Set all global interrupts to be group 1 (RAZ/WI when not + * available) + */ + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, base + GIC_DIST_IGROUP + i * 4 / 32); + + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + writel_relaxed(3, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -382,6 +501,7 @@ static void gic_cpu_init(struct gic_chip_data *gic) void __iomem *base = gic_data_cpu_base(gic); unsigned int cpu_mask, cpu = smp_processor_id(); int i; + unsigned long secure_irqs, secure_irq;
/* * Get what the GIC says our CPU mask is. @@ -400,8 +520,21 @@ static void gic_cpu_init(struct gic_chip_data *gic)
gic_cpu_config(dist_base, NULL);
+ /* + * Set any PPI and SGI interrupts not set in SMP_IPI_FIQ_MASK + * to be group 1 (RAZ/WI when not available) + */ + writel_relaxed(~SMP_IPI_FIQ_MASK, dist_base + GIC_DIST_IGROUP + 0); + + /* + * Update the priority of any resulting group0 interrupts. + */ + secure_irqs = ~readl_relaxed(dist_base + GIC_DIST_IGROUP + 0); + for_each_set_bit(secure_irq, &secure_irqs, 16) + gic_set_group_irq(dist_base, i, 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + writel_relaxed(0x1f, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -485,7 +618,7 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(3, dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -542,7 +675,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,8 +737,19 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0; + unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags); + /* + * The locking in this function ensures we don't use stale cpu mappings + * and thus we never route an IPI to the wrong physical core during a + * big.LITTLE switch. The switch code takes both of these locks meaning + * we can choose whichever lock is safe to use from our current calling + * context. + */ + if (in_nmi()) + raw_spin_lock(&fiq_safe_migration_lock); + else + raw_spin_lock_irqsave(&irq_controller_lock, flags);
/* Convert our logical CPU mask into a physical one. */ for_each_cpu(cpu, mask) @@ -618,9 +762,16 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); - - raw_spin_unlock_irqrestore(&irq_controller_lock, flags); + softint = map << 16 | irq; + if (gic_get_group_irq(gic_data_dist_base(&gic_data[0]), irq)) + softint |= 0x8000; + writel_relaxed(softint, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + + if (in_nmi()) + raw_spin_unlock(&fiq_safe_migration_lock); + else + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } #endif
@@ -668,7 +819,7 @@ int gic_get_cpu_id(unsigned int cpu) * Migrate all peripheral interrupts with a target matching the current CPU * to the interface corresponding to @new_cpu_id. The CPU interface mapping * is also updated. Targets to other CPU interfaces are unchanged. - * This must be called with IRQs locally disabled. + * This must be called with IRQ and FIQ locally disabled. */ void gic_migrate_target(unsigned int new_cpu_id) { @@ -690,6 +841,7 @@ void gic_migrate_target(unsigned int new_cpu_id) ror_val = (cur_cpu_id - new_cpu_id) & 31;
raw_spin_lock(&irq_controller_lock); + raw_spin_lock(&fiq_safe_migration_lock);
/* Update the target interface for this logical CPU */ gic_cpu_map[cpu] = 1 << new_cpu_id; @@ -709,6 +861,7 @@ void gic_migrate_target(unsigned int new_cpu_id) } }
+ raw_spin_unlock(&fiq_safe_migration_lock); raw_spin_unlock(&irq_controller_lock);
/* diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 45e2d8c..52a5676 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -101,5 +101,8 @@ static inline void __init register_routable_domain_ops { gic_routable_irq_domain_ops = ops; } + +void gic_handle_fiq_ipi(void); + #endif /* __ASSEMBLY */ #endif
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 --- drivers/irqchip/irq-gic.c | 52 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index ffc64ed..4b25ed9 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)) { @@ -283,7 +328,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
From: Russell King linux@arm.linux.org.uk
Currently arm does not implement arch_trigger_all_cpu_backtrace because, on SMP systems, there has never before been a means to route a non-maskable interrupt to the other CPUs in the system. With the introduction of IPI FIQ this is no longer the case. This patch provides an implementation of arch_trigger_all_cpu_backtrace and the associated KConfig machinary to enable it.
This is mostly Russell's code but since it was shared as an example rather than a fully fledged patch currently this code is currently Not-yet-signed-off-by: Russell King linux@arm.linux.org.uk
[Fill in headers and Kconfig, checkpatch.pl substitutions: CONFIG_NR_CPUS for NR_CPUS and pr_warn() for printk(KERN_WARNING, )] Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/Kconfig.debug | 14 +++++++++++ arch/arm/include/asm/irq.h | 5 ++++ arch/arm/include/asm/smp.h | 4 ++++ arch/arm/kernel/smp.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++ arch/arm/kernel/traps.c | 3 +++ 5 files changed, 86 insertions(+)
diff --git a/arch/arm/Kconfig.debug b/arch/arm/Kconfig.debug index b11ad54..2fa932d 100644 --- a/arch/arm/Kconfig.debug +++ b/arch/arm/Kconfig.debug @@ -1281,4 +1281,18 @@ config DEBUG_SET_MODULE_RONX against certain classes of kernel exploits. If in doubt, say "N".
+config ARM_BACKTRACE_TRIGGERING + bool "Support non-maskable backtrace triggering" + depends on SMP + select FIQ + help + Say Y here if you want to provide support for non-maskable + backtrace triggering. This is used to generate a backtrace + from all CPUs in a non-responsive system. Backtrace requests + can be issued by lockup and hung task detectors, spin lock + debugging or magic sysrq. + + This option is only effective when the kernel is run on a + platform capable of generating FIQs. + endmenu diff --git a/arch/arm/include/asm/irq.h b/arch/arm/include/asm/irq.h index 53c15de..a5cc2ed 100644 --- a/arch/arm/include/asm/irq.h +++ b/arch/arm/include/asm/irq.h @@ -35,6 +35,11 @@ extern void (*handle_arch_irq)(struct pt_regs *); extern void set_handle_irq(void (*handle_irq)(struct pt_regs *)); #endif
+#ifdef CONFIG_ARM_BACKTRACE_TRIGGERING +void arch_trigger_all_cpu_backtrace(bool); +#define arch_trigger_all_cpu_backtrace arch_trigger_all_cpu_backtrace +#endif + #endif
#endif diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 0e23384..d20bd95 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -90,6 +90,10 @@ extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
extern int register_ipi_completion(struct completion *completion, int cpu);
+#ifdef CONFIG_ARM_BACKTRACE_TRIGGERING +extern void ipi_cpu_backtrace(struct pt_regs *regs); +#endif + struct smp_operations { #ifdef CONFIG_SMP /* diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index b2404d0..d1765a6 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -554,6 +554,63 @@ static void ipi_complete(unsigned int cpu) complete(per_cpu(cpu_completion, cpu)); }
+#ifdef CONFIG_ARM_BACKTRACE_TRIGGERING +/* For reliability, we're prepared to waste bits here. */ +static DECLARE_BITMAP(backtrace_mask, CONFIG_NR_CPUS) __read_mostly; + +void ipi_cpu_backtrace(struct pt_regs *regs) +{ + int cpu = smp_processor_id(); + + if (cpumask_test_cpu(cpu, to_cpumask(backtrace_mask))) { + static arch_spinlock_t lock = __ARCH_SPIN_LOCK_UNLOCKED; + + arch_spin_lock(&lock); + pr_warn("FIQ backtrace for cpu %d\n", cpu); + show_regs(regs); + arch_spin_unlock(&lock); + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + } +} + +void arch_trigger_all_cpu_backtrace(bool include_self) +{ + static unsigned long backtrace_flag; + int i, cpu = get_cpu(); + + if (test_and_set_bit(0, &backtrace_flag)) { + /* + * If there is already a trigger_all_cpu_backtrace() in progress + * (backtrace_flag == 1), don't output double cpu dump infos. + */ + put_cpu(); + return; + } + + cpumask_copy(to_cpumask(backtrace_mask), cpu_online_mask); + if (!include_self) + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + + if (!cpumask_empty(to_cpumask(backtrace_mask))) { + pr_info("Sending FIQ to %s CPUs:\n", + (include_self ? "all" : "other")); + smp_cross_call(to_cpumask(backtrace_mask), IPI_FIQ); + } + + /* Wait for up to 10 seconds for all CPUs to do the backtrace */ + for (i = 0; i < 10 * 1000; i++) { + if (cpumask_empty(to_cpumask(backtrace_mask))) + break; + + mdelay(1); + } + + clear_bit(0, &backtrace_flag); + smp_mb__after_atomic(); + put_cpu(); +} +#endif + /* * Main handler for inter-processor interrupts */ @@ -623,6 +680,9 @@ void handle_IPI(int ipinr, struct pt_regs *regs) case IPI_FIQ: BUILD_BUG_ON(SMP_IPI_FIQ_MASK != BIT(IPI_FIQ)); pr_warn("CPU%u: IPI FIQ delivered via IRQ vector\n", cpu); +#ifdef CONFIG_ARM_BACKTRACE_TRIGGERING + ipi_cpu_backtrace(regs); +#endif break;
default: diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index 2c219c7..b7e1e85 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -484,6 +484,9 @@ asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) #ifdef CONFIG_ARM_GIC gic_handle_fiq_ipi(); #endif +#ifdef CONFIG_ARM_BACKTRACE_TRIGGERING + ipi_cpu_backtrace(regs); +#endif
nmi_exit();
This patchset implements arch_trigger_all_cpu_backtrace for arm.
Non-maskable signalling relies on the kernel being able to access FIQ and therefore, for devices that implement TrustZone, it will work only on systems that boot the kernel in secure mode.
Tested on Freescale iMX.6 (both via SysRq-l and by deliberately locking up the kernel with CONFIG_DEBUG_SPINLOCK=y).
Changes since v1:
* Restructured to sit nicely on a similar FYI patchset from Russell King. It now effectively replaces the work in progress final patch with something much more complete.
* Implemented (and tested) a Thumb-2 implementation of svc_exit_via_fiq (review of Nicolas Pitre)
* Dropped the GIC group 0 workaround patch. The issue of FIQ interrupts being acknowledged by the IRQ handler does still exist but should be harmless because the IRQ handler will still wind up calling ipi_cpu_backtrace().
* Removed any dependency on CONFIG_FIQ; all cpu backtrace effectively becomes a platform feature (although the use of non-maskable interrupts to implement it is best effort rather than guaranteed).
* Better comments highlighting usage of RAZ/WI registers (and parts of registers) in the GIC code.
Changes *before* v1:
* This patchset is a hugely cut-down successor to "[PATCH v11 00/19] arm: KGDB NMI/FIQ support". Thanks to Thomas Gleixner for suggesting the new structure. For historic details see: https://lkml.org/lkml/2014/9/2/227
* Fix bug in __fiq_abt (no longer passes a bad struct pt_regs value). In fixing this we also remove the useless indirection previously found in the fiq_handler macro.
* Make default fiq handler "always on" by migrating from fiq.c to traps.c and replace do_unexp_fiq with the new handler (review of Russell King).
* Add arm64 version of fiq.h (review of Russell King)
* Removed conditional branching and code from irq-gic.c, this is replaced by much simpler code that relies on the GIC specification's heavy use of read-as-zero/write-ignored (review of Russell King)
Daniel Thompson (3): arm: fiq: Replace default FIQ handler arm64: Introduce dummy version of asm/fiq.h irqchip: gic: Add support for IPI FIQ
Russell King (2): ARM: remove unused do_unexp_fiq() function ARM: add basic support for on-demand backtrace of other CPUs
arch/arm/include/asm/irq.h | 5 ++ arch/arm/include/asm/smp.h | 3 + arch/arm/kernel/entry-armv.S | 137 ++++++++++++++++++++++++++++++--- arch/arm/kernel/setup.c | 8 +- arch/arm/kernel/smp.c | 64 ++++++++++++++++ arch/arm/kernel/traps.c | 32 +++++++- arch/arm64/include/asm/fiq.h | 18 +++++ drivers/irqchip/irq-gic.c | 165 +++++++++++++++++++++++++++++++++++++--- include/linux/irqchip/arm-gic.h | 3 + 9 files changed, 412 insertions(+), 23 deletions(-) create mode 100644 arch/arm64/include/asm/fiq.h
-- 1.9.3
From: Russell King rmk+kernel@arm.linux.org.uk
do_unexp_fiq() has never been called by any code in the last 10 years, it's about time it was removed!
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk --- arch/arm/kernel/traps.c | 6 ------ 1 file changed, 6 deletions(-)
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index c8e4bb7..a447dcc 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -460,12 +460,6 @@ die_sig: arm_notify_die("Oops - undefined instruction", regs, &info, 0, 6); }
-asmlinkage void do_unexp_fiq (struct pt_regs *regs) -{ - printk("Hmm. Unexpected FIQ received, but trying to continue\n"); - printk("You may have a hardware problem...\n"); -} - /* * bad_mode handles the impossible case in the vectors. If you see one of * these, then it's extremely serious, and could mean you have buggy hardware.
From: Russell King rmk+kernel@arm.linux.org.uk
Add basic infrastructure for triggering a backtrace of other CPUs via an IPI, preferably at FIQ level. It is intended that this shall be used for cases where we have detected that something has already failed in the kernel.
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk --- arch/arm/include/asm/irq.h | 5 ++++ arch/arm/kernel/smp.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+)
diff --git a/arch/arm/include/asm/irq.h b/arch/arm/include/asm/irq.h index 53c15de..be1d07d 100644 --- a/arch/arm/include/asm/irq.h +++ b/arch/arm/include/asm/irq.h @@ -35,6 +35,11 @@ extern void (*handle_arch_irq)(struct pt_regs *); extern void set_handle_irq(void (*handle_irq)(struct pt_regs *)); #endif
+#ifdef CONFIG_SMP +extern void arch_trigger_all_cpu_backtrace(bool); +#define arch_trigger_all_cpu_backtrace(x) arch_trigger_all_cpu_backtrace(x) +#endif + #endif
#endif diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 9388a3d..94959f9 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -72,8 +72,12 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_CPU_BACKTRACE, };
+/* For reliability, we're prepared to waste bits here. */ +static DECLARE_BITMAP(backtrace_mask, NR_CPUS) __read_mostly; + static DECLARE_COMPLETION(cpu_running);
static struct smp_operations smp_ops; @@ -539,6 +543,21 @@ static void ipi_cpu_stop(unsigned int cpu) cpu_relax(); }
+static void ipi_cpu_backtrace(struct pt_regs *regs) +{ + int cpu = smp_processor_id(); + + if (cpumask_test_cpu(cpu, to_cpumask(backtrace_mask))) { + static arch_spinlock_t lock = __ARCH_SPIN_LOCK_UNLOCKED; + + arch_spin_lock(&lock); + printk(KERN_WARNING "FIQ backtrace for cpu %d\n", cpu); + show_regs(regs); + arch_spin_unlock(&lock); + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + } +} + static DEFINE_PER_CPU(struct completion *, cpu_completion);
int register_ipi_completion(struct completion *completion, int cpu) @@ -618,6 +637,12 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break;
+ case IPI_CPU_BACKTRACE: + irq_enter(); + ipi_cpu_backtrace(regs); + irq_exit(); + break; + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr); @@ -712,3 +737,40 @@ static int __init register_cpufreq_notifier(void) core_initcall(register_cpufreq_notifier);
#endif + +void arch_trigger_all_cpu_backtrace(bool include_self) +{ + static unsigned long backtrace_flag; + int i, cpu = get_cpu(); + + if (test_and_set_bit(0, &backtrace_flag)) { + /* + * If there is already a trigger_all_cpu_backtrace() in progress + * (backtrace_flag == 1), don't output double cpu dump infos. + */ + put_cpu(); + return; + } + + cpumask_copy(to_cpumask(backtrace_mask), cpu_online_mask); + if (!include_self) + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + + if (!cpumask_empty(to_cpumask(backtrace_mask))) { + pr_info("Sending FIQ to %s CPUs:\n", + (include_self ? "all" : "other")); + smp_cross_call(to_cpumask(backtrace_mask), IPI_CPU_BACKTRACE); + } + + /* Wait for up to 10 seconds for all CPUs to do the backtrace */ + for (i = 0; i < 10 * 1000; i++) { + if (cpumask_empty(to_cpumask(backtrace_mask))) + break; + + mdelay(1); + } + + clear_bit(0, &backtrace_flag); + smp_mb__after_atomic(); + put_cpu(); +}
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com --- arch/arm/include/asm/smp.h | 3 + arch/arm/kernel/entry-armv.S | 137 +++++++++++++++++++++++++++++++++++++++---- arch/arm/kernel/setup.c | 8 ++- arch/arm/kernel/smp.c | 4 +- arch/arm/kernel/traps.c | 28 +++++++++ 5 files changed, 168 insertions(+), 12 deletions(-)
diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 2ec765c..0580270 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -18,6 +18,8 @@ # error "<asm/smp.h> included in non-SMP build" #endif
+#define SMP_IPI_FIQ_MASK 0x0100 + #define raw_smp_processor_id() (current_thread_info()->cpu)
struct seq_file; @@ -85,6 +87,7 @@ extern void arch_send_call_function_single_ipi(int cpu); extern void arch_send_call_function_ipi_mask(const struct cpumask *mask); extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
+extern void ipi_cpu_backtrace(struct pt_regs *regs); extern int register_ipi_completion(struct completion *completion, int cpu);
struct smp_operations { diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..eb37aa5 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -146,7 +146,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0 + .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) @@ -183,7 +183,51 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6}
#ifdef CONFIG_TRACE_IRQFLAGS + .if \call_trace bl trace_hardirqs_off + .endif +#endif + .endm + +@ +@ svc_exit_via_fiq - similar to svc_exit but switches to FIQ mode before exit +@ +@ This macro acts in a similar manner to svc_exit but switches to FIQ +@ mode to restore the final part of the register state. +@ +@ We cannot use the normal svc_exit procedure because that would +@ clobber spsr_svc (FIQ could be delivered during the first few instructions +@ of vector_swi meaning its contents have not been saved anywhere). +@ +@ Note that, unlike svc_exit, this macro also does not allow a caller +@ supplied rpsr. This is because the FIQ exceptions are not re-entrant +@ and the handlers cannot call into the scheduler (meaning the value +@ on the stack remains correct). +@ + .macro svc_exit_via_fiq + +#ifndef CONFIG_THUMB2_KERNEL + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ +#else + add r0, sp, #S_R2 + ldr lr, [sp, #S_LR] + ldr sp, [sp, #S_SP] @ abort is deadly from here onward (it will + @ clobber state restored below) + ldmia r0, {r2 - r12} + mov r1, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + msr cpsr_c, r1 + sub r0, #S_R2 + add r8, r0, #S_PC + ldmia r0, {r0 - r1} + rfeia r8 #endif .endm
@@ -295,6 +339,15 @@ __pabt_svc: ENDPROC(__pabt_svc)
.align 5 +__fiq_svc: + svc_entry 0, 0 + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + svc_exit_via_fiq + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + + .align 5 .LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -305,6 +358,46 @@ ENDPROC(__pabt_svc) .word fp_enter
/* + * Abort mode handlers + */ + +@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@ + .align 5 +__fiq_abt: + svc_entry 0, 0 + + ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + mov r1, lr @ Save lr_abt + mrs r2, spsr @ Save spsr_abt, abort is now safe + ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + push {r1 - r2} + + sub r0, sp, #8 @ struct pt_regs *regs + bl handle_fiq_as_nmi + + pop {r1 - r2} + ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + mov lr, r1 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r2 @ Restore spsr_abt + ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + + svc_exit_via_fiq + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + +/* * User mode handlers * * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE @@ -683,6 +776,18 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
+ .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + /* * Register switch for ARMv3 and ARMv4 processors * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info @@ -1118,17 +1223,29 @@ vector_addrexcptn: b vector_addrexcptn
/*============================================================================= - * Undefined FIQs + * FIQ "NMI" handler *----------------------------------------------------------------------------- - * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC - * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg. - * Basically to switch modes, we *HAVE* to clobber one register... brain - * damage alert! I don't think that we can execute any code in here in any - * other mode than FIQ... Ok you can switch to another mode, but you can't - * get out of that mode without clobbering one register. + * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 + * systems. */ -vector_fiq: - subs pc, lr, #4 + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3]; + u32 fiq[3]; } ____cacheline_aligned;
#ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t" - "msr cpsr_c, %7" + "msr cpsr_c, %7\n\t" + "add r14, %0, %8\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE), @@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])), + PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14"); #endif diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 94959f9..7a79d11 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -543,7 +543,7 @@ static void ipi_cpu_stop(unsigned int cpu) cpu_relax(); }
-static void ipi_cpu_backtrace(struct pt_regs *regs) +void ipi_cpu_backtrace(struct pt_regs *regs) { int cpu = smp_processor_id();
@@ -584,6 +584,8 @@ void handle_IPI(int ipinr, struct pt_regs *regs) unsigned int cpu = smp_processor_id(); struct pt_regs *old_regs = set_irq_regs(regs);
+ BUILD_BUG_ON(SMP_IPI_FIQ_MASK != BIT(IPI_CPU_BACKTRACE)); + if ((unsigned)ipinr < NR_IPI) { trace_ipi_entry(ipi_types[ipinr]); __inc_irq_stat(cpu, ipi_irqs[ipinr]); diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index a447dcc..f8189c2 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -25,6 +25,7 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/irq.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -461,6 +462,33 @@ die_sig: }
/* + * Handle FIQ similarly to NMI on x86 systems. + * + * The runtime environment for NMIs is extremely restrictive + * (NMIs can pre-empt critical sections meaning almost all locking is + * forbidden) meaning this default FIQ handling must only be used in + * circumstances where non-maskability improves robustness, such as + * watchdog or debug logic. + * + * This handler is not appropriate for general purpose use in drivers + * platform code and can be overrideen using set_fiq_handler. + */ +asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + nmi_enter(); + +#ifdef CONFIG_SMP + ipi_cpu_backtrace(regs); +#endif + + nmi_exit(); + + set_irq_regs(old_regs); +} + +/* * bad_mode handles the impossible case in the vectors. If you see one of * these, then it's extremely serious, and could mean you have buggy hardware. * It never returns, and never tries to sync. We hope that we can at least
Drivers that are shared between arm and arm64 and which employ FIQ on arm cannot include asm/fiq.h without #ifdef'ing. This patch introduces a dummy version of asm/fiq.h to arm64 to avoid this.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Will Deacon will.deacon@arm.com --- arch/arm64/include/asm/fiq.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 arch/arm64/include/asm/fiq.h
diff --git a/arch/arm64/include/asm/fiq.h b/arch/arm64/include/asm/fiq.h new file mode 100644 index 0000000..909ec54 --- /dev/null +++ b/arch/arm64/include/asm/fiq.h @@ -0,0 +1,18 @@ +/* + * arch/arm64/include/asm/fiq.h + * + * Placeholder to reduce #ifdef'ing in shared arm/arm64 drivers. + */ + +#ifndef __ASM_FIQ_H +#define __ASM_FIQ_H + +/* + * This placeholder allows code of the following form to be simplified: + * + * #ifdef CONFIG_FIQ + * #include <asm/fiq.h> + * #endif + */ + +#endif
On Fri, Sep 05, 2014 at 04:33:17PM +0100, Daniel Thompson wrote:
Drivers that are shared between arm and arm64 and which employ FIQ on arm cannot include asm/fiq.h without #ifdef'ing. This patch introduces a dummy version of asm/fiq.h to arm64 to avoid this.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Will Deacon will.deacon@arm.com
arch/arm64/include/asm/fiq.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 arch/arm64/include/asm/fiq.h
diff --git a/arch/arm64/include/asm/fiq.h b/arch/arm64/include/asm/fiq.h new file mode 100644 index 0000000..909ec54 --- /dev/null +++ b/arch/arm64/include/asm/fiq.h @@ -0,0 +1,18 @@ +/*
- arch/arm64/include/asm/fiq.h
- Placeholder to reduce #ifdef'ing in shared arm/arm64 drivers.
- */
+#ifndef __ASM_FIQ_H +#define __ASM_FIQ_H
+/*
- This placeholder allows code of the following form to be simplified:
- #ifdef CONFIG_FIQ
- #include <asm/fiq.h>
- #endif
- */
+#endif
OK, we add a dummy file, but please keep it simple. Comments are fine but no need for header guards (nor the file name, on arm64 we try to get rid of them, though some still slip through).
Thanks.
On 05/09/14 17:50, Catalin Marinas wrote:
On Fri, Sep 05, 2014 at 04:33:17PM +0100, Daniel Thompson wrote:
Drivers that are shared between arm and arm64 and which employ FIQ on arm cannot include asm/fiq.h without #ifdef'ing. This patch introduces a dummy version of asm/fiq.h to arm64 to avoid this.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Will Deacon will.deacon@arm.com
arch/arm64/include/asm/fiq.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 arch/arm64/include/asm/fiq.h
diff --git a/arch/arm64/include/asm/fiq.h b/arch/arm64/include/asm/fiq.h new file mode 100644 index 0000000..909ec54 --- /dev/null +++ b/arch/arm64/include/asm/fiq.h @@ -0,0 +1,18 @@ +/*
- arch/arm64/include/asm/fiq.h
- Placeholder to reduce #ifdef'ing in shared arm/arm64 drivers.
- */
+#ifndef __ASM_FIQ_H +#define __ASM_FIQ_H
+/*
- This placeholder allows code of the following form to be simplified:
- #ifdef CONFIG_FIQ
- #include <asm/fiq.h>
- #endif
- */
+#endif
OK, we add a dummy file, but please keep it simple. Comments are fine but no need for header guards (nor the file name, on arm64 we try to get rid of them, though some still slip through).
Ok. I'll fix this.
This patch provides support for arm's newly added IPI FIQ. It works by placing all interrupt sources *except* IPI FIQ in group 1 and then flips a configuration bit in the GIC such that group 1 interrupts use IRQ and group 0 interrupts use FIQ.
All GIC hardware except GICv1-without-TrustZone support provides a means to group exceptions into group 0 and group 1. However the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world" (a feature that allows grouping to be used to allow hardware peripherals to send interrupts into the secure world). However when grouping is not available we can rely on the GIC's RAZ/WI semantics and avoid conditional code.
Tested on Freescale i.MX6 (quad A9, GICv1-with-TrustZone running in secure mode).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net --- arch/arm/kernel/traps.c | 4 + drivers/irqchip/irq-gic.c | 165 +++++++++++++++++++++++++++++++++++++--- include/linux/irqchip/arm-gic.h | 3 + 3 files changed, 163 insertions(+), 9 deletions(-)
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index f8189c2..40b1de7 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -26,6 +26,7 @@ #include <linux/init.h> #include <linux/sched.h> #include <linux/irq.h> +#include <linux/irqchip/arm-gic.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -479,6 +480,9 @@ asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs)
nmi_enter();
+#ifdef CONFIG_ARM_GIC + gic_handle_fiq_ipi(); +#endif #ifdef CONFIG_SMP ipi_cpu_backtrace(regs); #endif diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..f554ee5 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -39,8 +39,10 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/ratelimit.h>
#include <asm/cputype.h> +#include <asm/fiq.h> #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -48,6 +50,10 @@ #include "irq-gic-common.h" #include "irqchip.h"
+#ifndef SMP_IPI_FIQ_MASK +#define SMP_IPI_FIQ_MASK 0 +#endif + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -71,6 +77,8 @@ struct gic_chip_data { };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); +/* A fiq-safe spinlock must only be locked when the FIQ is masked */ +static DEFINE_RAW_SPINLOCK(fiq_safe_migration_lock);
/* * The GIC mapping of CPU interfaces does not necessarily match @@ -325,6 +333,94 @@ static struct irq_chip gic_chip = { .irq_set_wake = gic_set_wake, };
+/* + * Shift an interrupt between Group 0 and Group 1. + * + * In addition to changing the group we also modify the priority to + * match what "ARM strongly recommends" for a system where no Group 1 + * interrupt must ever preempt a Group 0 interrupt. + * + * If is safe to call this function on systems which do not support + * grouping (it will have no effect). + */ +static void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_mask = BIT(hwirq % 32); + u32 grp_val; + + unsigned int pri_reg = (hwirq / 4) * 4; + u32 pri_mask = BIT(7 + ((hwirq % 4) * 8)); + u32 pri_val; + + /* + * Systems which do not support grouping will have no bits + * set in IGROUP[0] (and all systems which do will have set bits). + */ + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + 0); + if (!grp_val) + return; + + raw_spin_lock(&irq_controller_lock); + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(base + GIC_DIST_PRI + pri_reg); + + if (group) { + grp_val |= grp_mask; + pri_val |= pri_mask; + } else { + grp_val &= ~grp_mask; + pri_val &= ~pri_mask; + } + + writel_relaxed(grp_val, base + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, base + GIC_DIST_PRI + pri_reg); + + raw_spin_unlock(&irq_controller_lock); +} + +/* + * Test which group an interrupt belongs to. + * + * Returns 0 if the controller does not support grouping. + */ +static int gic_get_group_irq(void __iomem *base, unsigned int hwirq) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_val; + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + + return (grp_val >> (hwirq % 32)) & 1; +} + +/* + * Fully acknowledge (both ack and eoi) any outstanding FIQ-based IPI, + * otherwise do nothing. + */ +void gic_handle_fiq_ipi(void) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *cpu_base = gic_data_cpu_base(gic); + unsigned long irqstat, irqnr; + + if (WARN_ON(!in_nmi())) + return; + + while ((1u << readl_relaxed(cpu_base + GIC_CPU_HIGHPRI)) & + SMP_IPI_FIQ_MASK) { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + WARN_RATELIMIT(irqnr > 16, + "Unexpected irqnr %lu (bad prioritization?)\n", + irqnr); + } +} + void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) { if (gic_nr >= MAX_GIC_NR) @@ -373,7 +469,18 @@ static void __init gic_dist_init(struct gic_chip_data *gic)
gic_dist_config(base, gic_irqs, NULL);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Set all global interrupts to be group 1 (this register is + * RAZ/WI if not accessible in current mode) + */ + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, base + GIC_DIST_IGROUP + i * 4 / 32); + + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + writel_relaxed(3, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -382,6 +489,7 @@ static void gic_cpu_init(struct gic_chip_data *gic) void __iomem *base = gic_data_cpu_base(gic); unsigned int cpu_mask, cpu = smp_processor_id(); int i; + unsigned long secure_irqs, secure_irq;
/* * Get what the GIC says our CPU mask is. @@ -400,8 +508,27 @@ static void gic_cpu_init(struct gic_chip_data *gic)
gic_cpu_config(dist_base, NULL);
+ /* + * Set any PPI and SGI interrupts not set in SMP_IPI_FIQ_MASK + * to be group 1 (this register is RAZ/WI if not accessible) + */ + writel_relaxed(~SMP_IPI_FIQ_MASK, dist_base + GIC_DIST_IGROUP + 0); + + /* + * Update the priority of any resulting group0 interrupts. + */ + secure_irqs = ~readl_relaxed(dist_base + GIC_DIST_IGROUP + 0); + for_each_set_bit(secure_irq, &secure_irqs, 16) + gic_set_group_irq(dist_base, i, 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + + /* The bottom most bit will be set for all GIC variants (and is + * called Enable or EnableGrp0 depending on operating mode). The + * remaining four bits (CBPR, FIQEn, AckCtl and EnableGrp1) are + * RAZ/WI if not accessible. + */ + writel_relaxed(0x1f, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -485,7 +612,7 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(3, dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -542,7 +669,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,8 +731,19 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0; + unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags); + /* + * The locking in this function ensures we don't use stale cpu mappings + * and thus we never route an IPI to the wrong physical core during a + * big.LITTLE switch. The switch code takes both of these locks meaning + * we can choose whichever lock is safe to use from our current calling + * context. + */ + if (in_nmi()) + raw_spin_lock(&fiq_safe_migration_lock); + else + raw_spin_lock_irqsave(&irq_controller_lock, flags);
/* Convert our logical CPU mask into a physical one. */ for_each_cpu(cpu, mask) @@ -618,9 +756,16 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); - - raw_spin_unlock_irqrestore(&irq_controller_lock, flags); + softint = map << 16 | irq; + if (gic_get_group_irq(gic_data_dist_base(&gic_data[0]), irq)) + softint |= 0x8000; + writel_relaxed(softint, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + + if (in_nmi()) + raw_spin_unlock(&fiq_safe_migration_lock); + else + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } #endif
@@ -668,7 +813,7 @@ int gic_get_cpu_id(unsigned int cpu) * Migrate all peripheral interrupts with a target matching the current CPU * to the interface corresponding to @new_cpu_id. The CPU interface mapping * is also updated. Targets to other CPU interfaces are unchanged. - * This must be called with IRQs locally disabled. + * This must be called with IRQ and FIQ locally disabled. */ void gic_migrate_target(unsigned int new_cpu_id) { @@ -690,6 +835,7 @@ void gic_migrate_target(unsigned int new_cpu_id) ror_val = (cur_cpu_id - new_cpu_id) & 31;
raw_spin_lock(&irq_controller_lock); + raw_spin_lock(&fiq_safe_migration_lock);
/* Update the target interface for this logical CPU */ gic_cpu_map[cpu] = 1 << new_cpu_id; @@ -709,6 +855,7 @@ void gic_migrate_target(unsigned int new_cpu_id) } }
+ raw_spin_unlock(&fiq_safe_migration_lock); raw_spin_unlock(&irq_controller_lock);
/* diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 45e2d8c..52a5676 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -101,5 +101,8 @@ static inline void __init register_routable_domain_ops { gic_routable_irq_domain_ops = ops; } + +void gic_handle_fiq_ipi(void); + #endif /* __ASSEMBLY */ #endif
This patchset implements arch_trigger_all_cpu_backtrace for arm.
Non-maskable signalling relies on the kernel being able to access FIQ and therefore, for devices that implement TrustZone, it will work only on systems that boot the kernel in secure mode.
Tested on Freescale iMX.6 (both via SysRq-l and by deliberately locking up the kernel with CONFIG_DEBUG_SPINLOCK=y).
Changes since v2:
* Removed redundant header guards from arch/arm64/include/asm/fiq.h (review of Catalin Marinas).
* Moved svc_exit_via_fiq macro to entry-header.S (review of Nicolas Pitre).
Changes since v1:
* Restructured to sit nicely on a similar FYI patchset from Russell King. It now effectively replaces the work in progress final patch with something much more complete.
* Implemented (and tested) a Thumb-2 implementation of svc_exit_via_fiq (review of Nicolas Pitre)
* Dropped the GIC group 0 workaround patch. The issue of FIQ interrupts being acknowledged by the IRQ handler does still exist but should be harmless because the IRQ handler will still wind up calling ipi_cpu_backtrace().
* Removed any dependency on CONFIG_FIQ; all cpu backtrace effectively becomes a platform feature (although the use of non-maskable interrupts to implement it is best effort rather than guaranteed).
* Better comments highlighting usage of RAZ/WI registers (and parts of registers) in the GIC code.
Changes *before* v1:
* This patchset is a hugely cut-down successor to "[PATCH v11 00/19] arm: KGDB NMI/FIQ support". Thanks to Thomas Gleixner for suggesting the new structure. For historic details see: https://lkml.org/lkml/2014/9/2/227
* Fix bug in __fiq_abt (no longer passes a bad struct pt_regs value). In fixing this we also remove the useless indirection previously found in the fiq_handler macro.
* Make default fiq handler "always on" by migrating from fiq.c to traps.c and replace do_unexp_fiq with the new handler (review of Russell King).
* Add arm64 version of fiq.h (review of Russell King)
* Removed conditional branching and code from irq-gic.c, this is replaced by much simpler code that relies on the GIC specification's heavy use of read-as-zero/write-ignored (review of Russell King)
Daniel Thompson (3): arm: fiq: Replace default FIQ handler arm64: Introduce dummy version of asm/fiq.h irqchip: gic: Add support for IPI FIQ
Russell King (2): ARM: remove unused do_unexp_fiq() function ARM: add basic support for on-demand backtrace of other CPUs
arch/arm/include/asm/irq.h | 5 ++ arch/arm/include/asm/smp.h | 3 + arch/arm/kernel/entry-armv.S | 95 ++++++++++++++++++++--- arch/arm/kernel/entry-header.S | 47 ++++++++++++ arch/arm/kernel/setup.c | 8 +- arch/arm/kernel/smp.c | 64 ++++++++++++++++ arch/arm/kernel/traps.c | 32 +++++++- arch/arm64/include/asm/fiq.h | 8 ++ drivers/irqchip/irq-gic.c | 165 +++++++++++++++++++++++++++++++++++++--- include/linux/irqchip/arm-gic.h | 3 + 10 files changed, 407 insertions(+), 23 deletions(-) create mode 100644 arch/arm64/include/asm/fiq.h
-- 1.9.3
From: Russell King rmk+kernel@arm.linux.org.uk
do_unexp_fiq() has never been called by any code in the last 10 years, it's about time it was removed!
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk --- arch/arm/kernel/traps.c | 6 ------ 1 file changed, 6 deletions(-)
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index c8e4bb7..a447dcc 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -460,12 +460,6 @@ die_sig: arm_notify_die("Oops - undefined instruction", regs, &info, 0, 6); }
-asmlinkage void do_unexp_fiq (struct pt_regs *regs) -{ - printk("Hmm. Unexpected FIQ received, but trying to continue\n"); - printk("You may have a hardware problem...\n"); -} - /* * bad_mode handles the impossible case in the vectors. If you see one of * these, then it's extremely serious, and could mean you have buggy hardware.
From: Russell King rmk+kernel@arm.linux.org.uk
Add basic infrastructure for triggering a backtrace of other CPUs via an IPI, preferably at FIQ level. It is intended that this shall be used for cases where we have detected that something has already failed in the kernel.
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk --- arch/arm/include/asm/irq.h | 5 ++++ arch/arm/kernel/smp.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+)
diff --git a/arch/arm/include/asm/irq.h b/arch/arm/include/asm/irq.h index 53c15de..be1d07d 100644 --- a/arch/arm/include/asm/irq.h +++ b/arch/arm/include/asm/irq.h @@ -35,6 +35,11 @@ extern void (*handle_arch_irq)(struct pt_regs *); extern void set_handle_irq(void (*handle_irq)(struct pt_regs *)); #endif
+#ifdef CONFIG_SMP +extern void arch_trigger_all_cpu_backtrace(bool); +#define arch_trigger_all_cpu_backtrace(x) arch_trigger_all_cpu_backtrace(x) +#endif + #endif
#endif diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 9388a3d..94959f9 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -72,8 +72,12 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_CPU_BACKTRACE, };
+/* For reliability, we're prepared to waste bits here. */ +static DECLARE_BITMAP(backtrace_mask, NR_CPUS) __read_mostly; + static DECLARE_COMPLETION(cpu_running);
static struct smp_operations smp_ops; @@ -539,6 +543,21 @@ static void ipi_cpu_stop(unsigned int cpu) cpu_relax(); }
+static void ipi_cpu_backtrace(struct pt_regs *regs) +{ + int cpu = smp_processor_id(); + + if (cpumask_test_cpu(cpu, to_cpumask(backtrace_mask))) { + static arch_spinlock_t lock = __ARCH_SPIN_LOCK_UNLOCKED; + + arch_spin_lock(&lock); + printk(KERN_WARNING "FIQ backtrace for cpu %d\n", cpu); + show_regs(regs); + arch_spin_unlock(&lock); + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + } +} + static DEFINE_PER_CPU(struct completion *, cpu_completion);
int register_ipi_completion(struct completion *completion, int cpu) @@ -618,6 +637,12 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break;
+ case IPI_CPU_BACKTRACE: + irq_enter(); + ipi_cpu_backtrace(regs); + irq_exit(); + break; + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr); @@ -712,3 +737,40 @@ static int __init register_cpufreq_notifier(void) core_initcall(register_cpufreq_notifier);
#endif + +void arch_trigger_all_cpu_backtrace(bool include_self) +{ + static unsigned long backtrace_flag; + int i, cpu = get_cpu(); + + if (test_and_set_bit(0, &backtrace_flag)) { + /* + * If there is already a trigger_all_cpu_backtrace() in progress + * (backtrace_flag == 1), don't output double cpu dump infos. + */ + put_cpu(); + return; + } + + cpumask_copy(to_cpumask(backtrace_mask), cpu_online_mask); + if (!include_self) + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + + if (!cpumask_empty(to_cpumask(backtrace_mask))) { + pr_info("Sending FIQ to %s CPUs:\n", + (include_self ? "all" : "other")); + smp_cross_call(to_cpumask(backtrace_mask), IPI_CPU_BACKTRACE); + } + + /* Wait for up to 10 seconds for all CPUs to do the backtrace */ + for (i = 0; i < 10 * 1000; i++) { + if (cpumask_empty(to_cpumask(backtrace_mask))) + break; + + mdelay(1); + } + + clear_bit(0, &backtrace_flag); + smp_mb__after_atomic(); + put_cpu(); +}
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Nicolas Pitre nico@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com --- arch/arm/include/asm/smp.h | 3 ++ arch/arm/kernel/entry-armv.S | 95 +++++++++++++++++++++++++++++++++++++----- arch/arm/kernel/entry-header.S | 47 +++++++++++++++++++++ arch/arm/kernel/setup.c | 8 +++- arch/arm/kernel/smp.c | 4 +- arch/arm/kernel/traps.c | 28 +++++++++++++ 6 files changed, 173 insertions(+), 12 deletions(-)
diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 2ec765c..0580270 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -18,6 +18,8 @@ # error "<asm/smp.h> included in non-SMP build" #endif
+#define SMP_IPI_FIQ_MASK 0x0100 + #define raw_smp_processor_id() (current_thread_info()->cpu)
struct seq_file; @@ -85,6 +87,7 @@ extern void arch_send_call_function_single_ipi(int cpu); extern void arch_send_call_function_ipi_mask(const struct cpumask *mask); extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
+extern void ipi_cpu_backtrace(struct pt_regs *regs); extern int register_ipi_completion(struct completion *completion, int cpu);
struct smp_operations { diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..7cf6b37 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -146,7 +146,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0 + .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) @@ -183,7 +183,9 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6}
#ifdef CONFIG_TRACE_IRQFLAGS + .if \call_trace bl trace_hardirqs_off + .endif #endif .endm
@@ -295,6 +297,15 @@ __pabt_svc: ENDPROC(__pabt_svc)
.align 5 +__fiq_svc: + svc_entry 0, 0 + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + svc_exit_via_fiq + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + + .align 5 .LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -305,6 +316,46 @@ ENDPROC(__pabt_svc) .word fp_enter
/* + * Abort mode handlers + */ + +@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@ + .align 5 +__fiq_abt: + svc_entry 0, 0 + + ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + mov r1, lr @ Save lr_abt + mrs r2, spsr @ Save spsr_abt, abort is now safe + ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + push {r1 - r2} + + sub r0, sp, #8 @ struct pt_regs *regs + bl handle_fiq_as_nmi + + pop {r1 - r2} + ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + mov lr, r1 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r2 @ Restore spsr_abt + ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + + svc_exit_via_fiq + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + +/* * User mode handlers * * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE @@ -683,6 +734,18 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
+ .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + /* * Register switch for ARMv3 and ARMv4 processors * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info @@ -1118,17 +1181,29 @@ vector_addrexcptn: b vector_addrexcptn
/*============================================================================= - * Undefined FIQs + * FIQ "NMI" handler *----------------------------------------------------------------------------- - * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC - * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg. - * Basically to switch modes, we *HAVE* to clobber one register... brain - * damage alert! I don't think that we can execute any code in here in any - * other mode than FIQ... Ok you can switch to another mode, but you can't - * get out of that mode without clobbering one register. + * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 + * systems. */ -vector_fiq: - subs pc, lr, #4 + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 2fdf867..0d91ca0 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -216,6 +216,34 @@ ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr .endm
+ @ + @ svc_exit_via_fiq - like svc_exit but switches to FIQ mode before exit + @ + @ This macro acts in a similar manner to svc_exit but switches to FIQ + @ mode to restore the final part of the register state. + @ + @ We cannot use the normal svc_exit procedure because that would + @ clobber spsr_svc (FIQ could be delivered during the first few + @ instructions of vector_swi meaning its contents have not been + @ saved anywhere). + @ + @ Note that, unlike svc_exit, this macro also does not allow a caller + @ supplied rpsr. This is because the FIQ exceptions are not re-entrant + @ and the handlers cannot call into the scheduler (meaning the value + @ on the stack remains correct). + @ + .macro svc_exit_via_fiq + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + .endm + .macro restore_user_regs, fast = 0, offset = 0 ldr r1, [sp, #\offset + S_PSR] @ get calling cpsr ldr lr, [sp, #\offset + S_PC]! @ get pc @@ -267,6 +295,25 @@ rfeia sp! .endm
+ @ + @ svc_exit_via_fiq - like svc_exit but switches to FIQ mode before exit + @ + @ For full details see non-Thumb implementation above. + @ + .macro svc_exit_via_fiq + add r0, sp, #S_R2 + ldr lr, [sp, #S_LR] + ldr sp, [sp, #S_SP] @ abort is deadly from here onward (it will + @ clobber state restored below) + ldmia r0, {r2 - r12} + mov r1, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + msr cpsr_c, r1 + sub r0, #S_R2 + add r8, r0, #S_PC + ldmia r0, {r0 - r1} + rfeia r8 + .endm + #ifdef CONFIG_CPU_V7M /* * Note we don't need to do clrex here as clearing the local monitor is diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3]; + u32 fiq[3]; } ____cacheline_aligned;
#ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t" - "msr cpsr_c, %7" + "msr cpsr_c, %7\n\t" + "add r14, %0, %8\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE), @@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])), + PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14"); #endif diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 94959f9..7a79d11 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -543,7 +543,7 @@ static void ipi_cpu_stop(unsigned int cpu) cpu_relax(); }
-static void ipi_cpu_backtrace(struct pt_regs *regs) +void ipi_cpu_backtrace(struct pt_regs *regs) { int cpu = smp_processor_id();
@@ -584,6 +584,8 @@ void handle_IPI(int ipinr, struct pt_regs *regs) unsigned int cpu = smp_processor_id(); struct pt_regs *old_regs = set_irq_regs(regs);
+ BUILD_BUG_ON(SMP_IPI_FIQ_MASK != BIT(IPI_CPU_BACKTRACE)); + if ((unsigned)ipinr < NR_IPI) { trace_ipi_entry(ipi_types[ipinr]); __inc_irq_stat(cpu, ipi_irqs[ipinr]); diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index a447dcc..f8189c2 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -25,6 +25,7 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/irq.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -461,6 +462,33 @@ die_sig: }
/* + * Handle FIQ similarly to NMI on x86 systems. + * + * The runtime environment for NMIs is extremely restrictive + * (NMIs can pre-empt critical sections meaning almost all locking is + * forbidden) meaning this default FIQ handling must only be used in + * circumstances where non-maskability improves robustness, such as + * watchdog or debug logic. + * + * This handler is not appropriate for general purpose use in drivers + * platform code and can be overrideen using set_fiq_handler. + */ +asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + nmi_enter(); + +#ifdef CONFIG_SMP + ipi_cpu_backtrace(regs); +#endif + + nmi_exit(); + + set_irq_regs(old_regs); +} + +/* * bad_mode handles the impossible case in the vectors. If you see one of * these, then it's extremely serious, and could mean you have buggy hardware. * It never returns, and never tries to sync. We hope that we can at least
On Mon, 8 Sep 2014, Daniel Thompson wrote:
+@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@
- .align 5
+__fiq_abt:
- svc_entry 0, 0
- ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT )
- THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT )
- THUMB( msr cpsr_c, r0 )
- mov r1, lr @ Save lr_abt
- mrs r2, spsr @ Save spsr_abt, abort is now safe
- ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT )
- THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT )
- THUMB( msr cpsr_c, r0 )
- push {r1 - r2}
stmfd sp!, {r1 - r2}
- sub r0, sp, #8 @ struct pt_regs *regs
- bl handle_fiq_as_nmi
- pop {r1 - r2}
ldmfd sp!, {r1 - r2}
... so that old binutils we still accept to compile the kernel (in ARM mode) are happy.
Otherwise...
Acked-by: Nicolas Pitre nico@linaro.org
Nicolas
On 08/09/14 16:49, Nicolas Pitre wrote:
On Mon, 8 Sep 2014, Daniel Thompson wrote:
+@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@
- .align 5
+__fiq_abt:
- svc_entry 0, 0
- ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT )
- THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT )
- THUMB( msr cpsr_c, r0 )
- mov r1, lr @ Save lr_abt
- mrs r2, spsr @ Save spsr_abt, abort is now safe
- ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT )
- THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT )
- THUMB( msr cpsr_c, r0 )
- push {r1 - r2}
stmfd sp!, {r1 - r2}
- sub r0, sp, #8 @ struct pt_regs *regs
- bl handle_fiq_as_nmi
- pop {r1 - r2}
ldmfd sp!, {r1 - r2}
... so that old binutils we still accept to compile the kernel (in ARM mode) are happy.
Will do.
Otherwise...
Acked-by: Nicolas Pitre nico@linaro.org
Thanks :-)
Drivers that are shared between arm and arm64 and which employ FIQ on arm cannot include asm/fiq.h without #ifdef'ing. This patch introduces a dummy version of asm/fiq.h to arm64 to avoid this.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Will Deacon will.deacon@arm.com --- arch/arm64/include/asm/fiq.h | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 arch/arm64/include/asm/fiq.h
diff --git a/arch/arm64/include/asm/fiq.h b/arch/arm64/include/asm/fiq.h new file mode 100644 index 0000000..d3776b8 --- /dev/null +++ b/arch/arm64/include/asm/fiq.h @@ -0,0 +1,8 @@ +/* + * Placeholder to reduce #ifdef'ing in shared arm/arm64 drivers. It + * allows code of the following form to be made unconditional. + * + * #ifdef CONFIG_FIQ + * #include <asm/fiq.h> + * #endif + */
This patch provides support for arm's newly added IPI FIQ. It works by placing all interrupt sources *except* IPI FIQ in group 1 and then flips a configuration bit in the GIC such that group 1 interrupts use IRQ and group 0 interrupts use FIQ.
All GIC hardware except GICv1-without-TrustZone support provides a means to group exceptions into group 0 and group 1. However the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world" (a feature that allows grouping to be used to allow hardware peripherals to send interrupts into the secure world). However when grouping is not available we can rely on the GIC's RAZ/WI semantics and avoid conditional code.
Tested on Freescale i.MX6 (quad A9, GICv1-with-TrustZone running in secure mode).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net --- arch/arm/kernel/traps.c | 4 + drivers/irqchip/irq-gic.c | 165 +++++++++++++++++++++++++++++++++++++--- include/linux/irqchip/arm-gic.h | 3 + 3 files changed, 163 insertions(+), 9 deletions(-)
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index f8189c2..40b1de7 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -26,6 +26,7 @@ #include <linux/init.h> #include <linux/sched.h> #include <linux/irq.h> +#include <linux/irqchip/arm-gic.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -479,6 +480,9 @@ asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs)
nmi_enter();
+#ifdef CONFIG_ARM_GIC + gic_handle_fiq_ipi(); +#endif #ifdef CONFIG_SMP ipi_cpu_backtrace(regs); #endif diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..f554ee5 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -39,8 +39,10 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/ratelimit.h>
#include <asm/cputype.h> +#include <asm/fiq.h> #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -48,6 +50,10 @@ #include "irq-gic-common.h" #include "irqchip.h"
+#ifndef SMP_IPI_FIQ_MASK +#define SMP_IPI_FIQ_MASK 0 +#endif + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -71,6 +77,8 @@ struct gic_chip_data { };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); +/* A fiq-safe spinlock must only be locked when the FIQ is masked */ +static DEFINE_RAW_SPINLOCK(fiq_safe_migration_lock);
/* * The GIC mapping of CPU interfaces does not necessarily match @@ -325,6 +333,94 @@ static struct irq_chip gic_chip = { .irq_set_wake = gic_set_wake, };
+/* + * Shift an interrupt between Group 0 and Group 1. + * + * In addition to changing the group we also modify the priority to + * match what "ARM strongly recommends" for a system where no Group 1 + * interrupt must ever preempt a Group 0 interrupt. + * + * If is safe to call this function on systems which do not support + * grouping (it will have no effect). + */ +static void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_mask = BIT(hwirq % 32); + u32 grp_val; + + unsigned int pri_reg = (hwirq / 4) * 4; + u32 pri_mask = BIT(7 + ((hwirq % 4) * 8)); + u32 pri_val; + + /* + * Systems which do not support grouping will have no bits + * set in IGROUP[0] (and all systems which do will have set bits). + */ + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + 0); + if (!grp_val) + return; + + raw_spin_lock(&irq_controller_lock); + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(base + GIC_DIST_PRI + pri_reg); + + if (group) { + grp_val |= grp_mask; + pri_val |= pri_mask; + } else { + grp_val &= ~grp_mask; + pri_val &= ~pri_mask; + } + + writel_relaxed(grp_val, base + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, base + GIC_DIST_PRI + pri_reg); + + raw_spin_unlock(&irq_controller_lock); +} + +/* + * Test which group an interrupt belongs to. + * + * Returns 0 if the controller does not support grouping. + */ +static int gic_get_group_irq(void __iomem *base, unsigned int hwirq) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_val; + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + + return (grp_val >> (hwirq % 32)) & 1; +} + +/* + * Fully acknowledge (both ack and eoi) any outstanding FIQ-based IPI, + * otherwise do nothing. + */ +void gic_handle_fiq_ipi(void) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *cpu_base = gic_data_cpu_base(gic); + unsigned long irqstat, irqnr; + + if (WARN_ON(!in_nmi())) + return; + + while ((1u << readl_relaxed(cpu_base + GIC_CPU_HIGHPRI)) & + SMP_IPI_FIQ_MASK) { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + WARN_RATELIMIT(irqnr > 16, + "Unexpected irqnr %lu (bad prioritization?)\n", + irqnr); + } +} + void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) { if (gic_nr >= MAX_GIC_NR) @@ -373,7 +469,18 @@ static void __init gic_dist_init(struct gic_chip_data *gic)
gic_dist_config(base, gic_irqs, NULL);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Set all global interrupts to be group 1 (this register is + * RAZ/WI if not accessible in current mode) + */ + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, base + GIC_DIST_IGROUP + i * 4 / 32); + + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + writel_relaxed(3, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -382,6 +489,7 @@ static void gic_cpu_init(struct gic_chip_data *gic) void __iomem *base = gic_data_cpu_base(gic); unsigned int cpu_mask, cpu = smp_processor_id(); int i; + unsigned long secure_irqs, secure_irq;
/* * Get what the GIC says our CPU mask is. @@ -400,8 +508,27 @@ static void gic_cpu_init(struct gic_chip_data *gic)
gic_cpu_config(dist_base, NULL);
+ /* + * Set any PPI and SGI interrupts not set in SMP_IPI_FIQ_MASK + * to be group 1 (this register is RAZ/WI if not accessible) + */ + writel_relaxed(~SMP_IPI_FIQ_MASK, dist_base + GIC_DIST_IGROUP + 0); + + /* + * Update the priority of any resulting group0 interrupts. + */ + secure_irqs = ~readl_relaxed(dist_base + GIC_DIST_IGROUP + 0); + for_each_set_bit(secure_irq, &secure_irqs, 16) + gic_set_group_irq(dist_base, i, 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + + /* The bottom most bit will be set for all GIC variants (and is + * called Enable or EnableGrp0 depending on operating mode). The + * remaining four bits (CBPR, FIQEn, AckCtl and EnableGrp1) are + * RAZ/WI if not accessible. + */ + writel_relaxed(0x1f, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -485,7 +612,7 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(3, dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -542,7 +669,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,8 +731,19 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0; + unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags); + /* + * The locking in this function ensures we don't use stale cpu mappings + * and thus we never route an IPI to the wrong physical core during a + * big.LITTLE switch. The switch code takes both of these locks meaning + * we can choose whichever lock is safe to use from our current calling + * context. + */ + if (in_nmi()) + raw_spin_lock(&fiq_safe_migration_lock); + else + raw_spin_lock_irqsave(&irq_controller_lock, flags);
/* Convert our logical CPU mask into a physical one. */ for_each_cpu(cpu, mask) @@ -618,9 +756,16 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); - - raw_spin_unlock_irqrestore(&irq_controller_lock, flags); + softint = map << 16 | irq; + if (gic_get_group_irq(gic_data_dist_base(&gic_data[0]), irq)) + softint |= 0x8000; + writel_relaxed(softint, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + + if (in_nmi()) + raw_spin_unlock(&fiq_safe_migration_lock); + else + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } #endif
@@ -668,7 +813,7 @@ int gic_get_cpu_id(unsigned int cpu) * Migrate all peripheral interrupts with a target matching the current CPU * to the interface corresponding to @new_cpu_id. The CPU interface mapping * is also updated. Targets to other CPU interfaces are unchanged. - * This must be called with IRQs locally disabled. + * This must be called with IRQ and FIQ locally disabled. */ void gic_migrate_target(unsigned int new_cpu_id) { @@ -690,6 +835,7 @@ void gic_migrate_target(unsigned int new_cpu_id) ror_val = (cur_cpu_id - new_cpu_id) & 31;
raw_spin_lock(&irq_controller_lock); + raw_spin_lock(&fiq_safe_migration_lock);
/* Update the target interface for this logical CPU */ gic_cpu_map[cpu] = 1 << new_cpu_id; @@ -709,6 +855,7 @@ void gic_migrate_target(unsigned int new_cpu_id) } }
+ raw_spin_unlock(&fiq_safe_migration_lock); raw_spin_unlock(&irq_controller_lock);
/* diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 45e2d8c..52a5676 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -101,5 +101,8 @@ static inline void __init register_routable_domain_ops { gic_routable_irq_domain_ops = ops; } + +void gic_handle_fiq_ipi(void); + #endif /* __ASSEMBLY */ #endif
On Mon, Sep 08, 2014 at 04:28:35PM +0100, Daniel Thompson wrote:
@@ -604,8 +731,19 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0;
- unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags);
- /*
* The locking in this function ensures we don't use stale cpu mappings
* and thus we never route an IPI to the wrong physical core during a
* big.LITTLE switch. The switch code takes both of these locks meaning
* we can choose whichever lock is safe to use from our current calling
* context.
*/
- if (in_nmi())
raw_spin_lock(&fiq_safe_migration_lock);
- else
raw_spin_lock_irqsave(&irq_controller_lock, flags);
Firstly, why would gic_raise_softirq() be called in FIQ context? Secondly, this doesn't save you. If you were in the middle of gic_migrate_target() when the FIQ happened that (for some reason prompted you to call this), you would immediately deadlock trying to that this IRQ.
I suggest not even trying to solve this "race" which I don't think is one which needs to even be considered (due to the first point.)
On 08/09/14 17:23, Russell King - ARM Linux wrote:
On Mon, Sep 08, 2014 at 04:28:35PM +0100, Daniel Thompson wrote:
@@ -604,8 +731,19 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0;
- unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags);
- /*
* The locking in this function ensures we don't use stale cpu mappings
* and thus we never route an IPI to the wrong physical core during a
* big.LITTLE switch. The switch code takes both of these locks meaning
* we can choose whichever lock is safe to use from our current calling
* context.
*/
- if (in_nmi())
raw_spin_lock(&fiq_safe_migration_lock);
- else
raw_spin_lock_irqsave(&irq_controller_lock, flags);
Firstly, why would gic_raise_softirq() be called in FIQ context?
Oops.
This code should have been removed. It *is* required for kgdb (which needs to send FIQ to other processors via IPI and may itself be running from FIQ) but it not needed for the currently targeted use case.
Secondly, this doesn't save you. If you were in the middle of gic_migrate_target() when the FIQ happened that (for some reason prompted you to call this), you would immediately deadlock trying to that this IRQ.
This cannot happen because gic_migrate_target() runs with FIQ disabled.
I suggest not even trying to solve this "race" which I don't think is one which needs to even be considered (due to the first point.)
As mentioned above I believe it eventually needs to be addressed by some means but it certainly doesn't belong in the current patchset.
I will remove it.
On 09/09/14 09:24, Daniel Thompson wrote:
On 08/09/14 17:23, Russell King - ARM Linux wrote:
On Mon, Sep 08, 2014 at 04:28:35PM +0100, Daniel Thompson wrote:
@@ -604,8 +731,19 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0;
- unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags);
- /*
* The locking in this function ensures we don't use stale cpu mappings
* and thus we never route an IPI to the wrong physical core during a
* big.LITTLE switch. The switch code takes both of these locks meaning
* we can choose whichever lock is safe to use from our current calling
* context.
*/
- if (in_nmi())
raw_spin_lock(&fiq_safe_migration_lock);
- else
raw_spin_lock_irqsave(&irq_controller_lock, flags);
Firstly, why would gic_raise_softirq() be called in FIQ context?
Oops.
This code should have been removed. It *is* required for kgdb (which needs to send FIQ to other processors via IPI and may itself be running from FIQ) but it not needed for the currently targeted use case.
I'm afraid I was wrong about this. gic_raise_softitq() is called during console unlocking inside wake_up_klogd(). This means it is required even to support arch_trigger_all_cpu_backtrace.
I'm trying to get a (tested) refresh of the FIQ + trigger_backtrace out today. Thus for now I plan to reinstate the code above (which I believe to be safe because FIQ is disabled throughout a b.L switch).
Nevertheless I won't ignore this comment! I think a using a r/w lock here can be made FIQ-safe without having to rely on in_nmi() based conditional branches.
Daniel.
Secondly, this doesn't save you. If you were in the middle of gic_migrate_target() when the FIQ happened that (for some reason prompted you to call this), you would immediately deadlock trying to that this IRQ.
This cannot happen because gic_migrate_target() runs with FIQ disabled.
I suggest not even trying to solve this "race" which I don't think is one which needs to even be considered (due to the first point.)
As mentioned above I believe it eventually needs to be addressed by some means but it certainly doesn't belong in the current patchset.
I will remove it.
On Mon, Sep 08, 2014 at 04:28:30PM +0100, Daniel Thompson wrote:
- Restructured to sit nicely on a similar FYI patchset from Russell King. It now effectively replaces the work in progress final patch with something much more complete.
I'd suggest that patch 2 should be in a different place - get the FIQ infrastructure in place first before adding any users of it would be a much more sensible patch ordering.
On 08/09/14 17:24, Russell King - ARM Linux wrote:
On Mon, Sep 08, 2014 at 04:28:30PM +0100, Daniel Thompson wrote:
- Restructured to sit nicely on a similar FYI patchset from Russell King. It now effectively replaces the work in progress final patch with something much more complete.
I'd suggest that patch 2 should be in a different place - get the FIQ infrastructure in place first before adding any users of it would be a much more sensible patch ordering.
Ok. I will push this to the back.
This patchset implements arch_trigger_all_cpu_backtrace for arm.
Non-maskable signalling relies on the kernel being able to access FIQ and therefore, for devices that implement TrustZone, it will work only on systems that boot the kernel in secure mode.
Tested on Freescale iMX.6 (both via SysRq-l and by deliberately locking up the kernel with CONFIG_DEBUG_SPINLOCK=y).
Changes since v3:
* Replaced push/pop with stmfd/ldmfd respectively (review of Nicolas Pitre).
* Really fix bad pt_regs pointer generation in __fiq_abt.
* Remove fiq_safe_migration_lock and associated logic in gic_raise_softirq() (review of Russell King)
* Restructured to introduce the default FIQ handler first, before the new features (review of Russell King).
Changes since v2:
* Removed redundant header guards from arch/arm64/include/asm/fiq.h (review of Catalin Marinas).
* Moved svc_exit_via_fiq macro to entry-header.S (review of Nicolas Pitre).
Changes since v1:
* Restructured to sit nicely on a similar FYI patchset from Russell King. It now effectively replaces the work in progress final patch with something much more complete.
* Implemented (and tested) a Thumb-2 implementation of svc_exit_via_fiq (review of Nicolas Pitre)
* Dropped the GIC group 0 workaround patch. The issue of FIQ interrupts being acknowledged by the IRQ handler does still exist but should be harmless because the IRQ handler will still wind up calling ipi_cpu_backtrace().
* Removed any dependency on CONFIG_FIQ; all cpu backtrace effectively becomes a platform feature (although the use of non-maskable interrupts to implement it is best effort rather than guaranteed).
* Better comments highlighting usage of RAZ/WI registers (and parts of registers) in the GIC code.
Changes *before* v1:
* This patchset is a hugely cut-down successor to "[PATCH v11 00/19] arm: KGDB NMI/FIQ support". Thanks to Thomas Gleixner for suggesting the new structure. For historic details see: https://lkml.org/lkml/2014/9/2/227
* Fix bug in __fiq_abt (no longer passes a bad struct pt_regs value). In fixing this we also remove the useless indirection previously found in the fiq_handler macro.
* Make default fiq handler "always on" by migrating from fiq.c to traps.c and replace do_unexp_fiq with the new handler (review of Russell King).
* Add arm64 version of fiq.h (review of Russell King)
* Removed conditional branching and code from irq-gic.c, this is replaced by much simpler code that relies on the GIC specification's heavy use of read-as-zero/write-ignored (review of Russell King)
Daniel Thompson (4): arm: fiq: Replace default FIQ handler arm64: Introduce dummy version of asm/fiq.h irqchip: gic: Add support for IPI FIQ arm: smp: Handle ipi_cpu_backtrace() using FIQ (if available)
Russell King (2): ARM: remove unused do_unexp_fiq() function ARM: add basic support for on-demand backtrace of other CPUs
arch/arm/include/asm/irq.h | 5 ++ arch/arm/include/asm/smp.h | 3 + arch/arm/kernel/entry-armv.S | 95 ++++++++++++++++++++++++--- arch/arm/kernel/entry-header.S | 47 ++++++++++++++ arch/arm/kernel/setup.c | 8 ++- arch/arm/kernel/smp.c | 65 +++++++++++++++++++ arch/arm/kernel/traps.c | 32 ++++++++- arch/arm64/include/asm/fiq.h | 8 +++ drivers/irqchip/irq-gic.c | 140 ++++++++++++++++++++++++++++++++++++++-- include/linux/irqchip/arm-gic.h | 3 + 10 files changed, 387 insertions(+), 19 deletions(-) create mode 100644 arch/arm64/include/asm/fiq.h
-- 1.9.3
From: Russell King rmk+kernel@arm.linux.org.uk
do_unexp_fiq() has never been called by any code in the last 10 years, it's about time it was removed!
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/kernel/traps.c | 6 ------ 1 file changed, 6 deletions(-)
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index c8e4bb7..a447dcc 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -460,12 +460,6 @@ die_sig: arm_notify_die("Oops - undefined instruction", regs, &info, 0, 6); }
-asmlinkage void do_unexp_fiq (struct pt_regs *regs) -{ - printk("Hmm. Unexpected FIQ received, but trying to continue\n"); - printk("You may have a hardware problem...\n"); -} - /* * bad_mode handles the impossible case in the vectors. If you see one of * these, then it's extremely serious, and could mean you have buggy hardware.
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Catalin Marinas catalin.marinas@arm.com Acked-by: Nicolas Pitre nico@linaro.org --- arch/arm/kernel/entry-armv.S | 95 +++++++++++++++++++++++++++++++++++++----- arch/arm/kernel/entry-header.S | 47 +++++++++++++++++++++ arch/arm/kernel/setup.c | 8 +++- arch/arm/kernel/traps.c | 26 ++++++++++++ 4 files changed, 165 insertions(+), 11 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..0c70fee 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -146,7 +146,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0 + .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) @@ -183,7 +183,9 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6}
#ifdef CONFIG_TRACE_IRQFLAGS + .if \call_trace bl trace_hardirqs_off + .endif #endif .endm
@@ -295,6 +297,15 @@ __pabt_svc: ENDPROC(__pabt_svc)
.align 5 +__fiq_svc: + svc_entry 0, 0 + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + svc_exit_via_fiq + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + + .align 5 .LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -305,6 +316,46 @@ ENDPROC(__pabt_svc) .word fp_enter
/* + * Abort mode handlers + */ + +@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@ + .align 5 +__fiq_abt: + svc_entry 0, 0 + + ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + mov r1, lr @ Save lr_abt + mrs r2, spsr @ Save spsr_abt, abort is now safe + ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + stmfd sp!, {r1 - r2} + + add r0, sp, #8 @ struct pt_regs *regs + bl handle_fiq_as_nmi + + ldmfd sp!, {r1 - r2} + ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + mov lr, r1 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r2 @ Restore spsr_abt + ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + + svc_exit_via_fiq + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + +/* * User mode handlers * * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE @@ -683,6 +734,18 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
+ .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + /* * Register switch for ARMv3 and ARMv4 processors * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info @@ -1118,17 +1181,29 @@ vector_addrexcptn: b vector_addrexcptn
/*============================================================================= - * Undefined FIQs + * FIQ "NMI" handler *----------------------------------------------------------------------------- - * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC - * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg. - * Basically to switch modes, we *HAVE* to clobber one register... brain - * damage alert! I don't think that we can execute any code in here in any - * other mode than FIQ... Ok you can switch to another mode, but you can't - * get out of that mode without clobbering one register. + * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 + * systems. */ -vector_fiq: - subs pc, lr, #4 + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 2fdf867..0d91ca0 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -216,6 +216,34 @@ ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr .endm
+ @ + @ svc_exit_via_fiq - like svc_exit but switches to FIQ mode before exit + @ + @ This macro acts in a similar manner to svc_exit but switches to FIQ + @ mode to restore the final part of the register state. + @ + @ We cannot use the normal svc_exit procedure because that would + @ clobber spsr_svc (FIQ could be delivered during the first few + @ instructions of vector_swi meaning its contents have not been + @ saved anywhere). + @ + @ Note that, unlike svc_exit, this macro also does not allow a caller + @ supplied rpsr. This is because the FIQ exceptions are not re-entrant + @ and the handlers cannot call into the scheduler (meaning the value + @ on the stack remains correct). + @ + .macro svc_exit_via_fiq + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + .endm + .macro restore_user_regs, fast = 0, offset = 0 ldr r1, [sp, #\offset + S_PSR] @ get calling cpsr ldr lr, [sp, #\offset + S_PC]! @ get pc @@ -267,6 +295,25 @@ rfeia sp! .endm
+ @ + @ svc_exit_via_fiq - like svc_exit but switches to FIQ mode before exit + @ + @ For full details see non-Thumb implementation above. + @ + .macro svc_exit_via_fiq + add r0, sp, #S_R2 + ldr lr, [sp, #S_LR] + ldr sp, [sp, #S_SP] @ abort is deadly from here onward (it will + @ clobber state restored below) + ldmia r0, {r2 - r12} + mov r1, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + msr cpsr_c, r1 + sub r0, #S_R2 + add r8, r0, #S_PC + ldmia r0, {r0 - r1} + rfeia r8 + .endm + #ifdef CONFIG_CPU_V7M /* * Note we don't need to do clrex here as clearing the local monitor is diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3]; + u32 fiq[3]; } ____cacheline_aligned;
#ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t" - "msr cpsr_c, %7" + "msr cpsr_c, %7\n\t" + "add r14, %0, %8\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE), @@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])), + PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14"); #endif diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index a447dcc..439138d 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -25,6 +25,7 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/irq.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -461,6 +462,31 @@ die_sig: }
/* + * Handle FIQ similarly to NMI on x86 systems. + * + * The runtime environment for NMIs is extremely restrictive + * (NMIs can pre-empt critical sections meaning almost all locking is + * forbidden) meaning this default FIQ handling must only be used in + * circumstances where non-maskability improves robustness, such as + * watchdog or debug logic. + * + * This handler is not appropriate for general purpose use in drivers + * platform code and can be overrideen using set_fiq_handler. + */ +asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + nmi_enter(); + + /* nop. FIQ handlers for special arch/arm features can be added here. */ + + nmi_exit(); + + set_irq_regs(old_regs); +} + +/* * bad_mode handles the impossible case in the vectors. If you see one of * these, then it's extremely serious, and could mean you have buggy hardware. * It never returns, and never tries to sync. We hope that we can at least
Drivers that are shared between arm and arm64 and which employ FIQ on arm cannot include asm/fiq.h without #ifdef'ing. This patch introduces a dummy version of asm/fiq.h to arm64 to avoid this.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Will Deacon will.deacon@arm.com --- arch/arm64/include/asm/fiq.h | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 arch/arm64/include/asm/fiq.h
diff --git a/arch/arm64/include/asm/fiq.h b/arch/arm64/include/asm/fiq.h new file mode 100644 index 0000000..d3776b8 --- /dev/null +++ b/arch/arm64/include/asm/fiq.h @@ -0,0 +1,8 @@ +/* + * Placeholder to reduce #ifdef'ing in shared arm/arm64 drivers. It + * allows code of the following form to be made unconditional. + * + * #ifdef CONFIG_FIQ + * #include <asm/fiq.h> + * #endif + */
This patch provides support for arm's newly added IPI FIQ. It works by placing all interrupt sources *except* IPI FIQ in group 1 and then flips a configuration bit in the GIC such that group 1 interrupts use IRQ and group 0 interrupts use FIQ.
All GIC hardware except GICv1-without-TrustZone support provides a means to group exceptions into group 0 and group 1. However the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world" (a feature that allows grouping to be used to allow hardware peripherals to send interrupts into the secure world). However when grouping is not available we can rely on the GIC's RAZ/WI semantics and avoid conditional code.
Tested on Freescale i.MX6 (quad A9, GICv1-with-TrustZone running in secure mode).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net --- arch/arm/kernel/traps.c | 5 +- drivers/irqchip/irq-gic.c | 140 ++++++++++++++++++++++++++++++++++++++-- include/linux/irqchip/arm-gic.h | 3 + 3 files changed, 142 insertions(+), 6 deletions(-)
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index 439138d..92c4ea1 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -26,6 +26,7 @@ #include <linux/init.h> #include <linux/sched.h> #include <linux/irq.h> +#include <linux/irqchip/arm-gic.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -479,7 +480,9 @@ asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs)
nmi_enter();
- /* nop. FIQ handlers for special arch/arm features can be added here. */ +#ifdef CONFIG_ARM_GIC + gic_handle_fiq_ipi(); +#endif
nmi_exit();
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..0d67b0e 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -39,8 +39,10 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/ratelimit.h>
#include <asm/cputype.h> +#include <asm/fiq.h> #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -48,6 +50,10 @@ #include "irq-gic-common.h" #include "irqchip.h"
+#ifndef SMP_IPI_FIQ_MASK +#define SMP_IPI_FIQ_MASK 0 +#endif + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -325,6 +331,94 @@ static struct irq_chip gic_chip = { .irq_set_wake = gic_set_wake, };
+/* + * Shift an interrupt between Group 0 and Group 1. + * + * In addition to changing the group we also modify the priority to + * match what "ARM strongly recommends" for a system where no Group 1 + * interrupt must ever preempt a Group 0 interrupt. + * + * If is safe to call this function on systems which do not support + * grouping (it will have no effect). + */ +static void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_mask = BIT(hwirq % 32); + u32 grp_val; + + unsigned int pri_reg = (hwirq / 4) * 4; + u32 pri_mask = BIT(7 + ((hwirq % 4) * 8)); + u32 pri_val; + + /* + * Systems which do not support grouping will have no bits + * set in IGROUP[0] (and all systems which do will have set bits). + */ + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + 0); + if (!grp_val) + return; + + raw_spin_lock(&irq_controller_lock); + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(base + GIC_DIST_PRI + pri_reg); + + if (group) { + grp_val |= grp_mask; + pri_val |= pri_mask; + } else { + grp_val &= ~grp_mask; + pri_val &= ~pri_mask; + } + + writel_relaxed(grp_val, base + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, base + GIC_DIST_PRI + pri_reg); + + raw_spin_unlock(&irq_controller_lock); +} + +/* + * Test which group an interrupt belongs to. + * + * Returns 0 if the controller does not support grouping. + */ +static int gic_get_group_irq(void __iomem *base, unsigned int hwirq) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_val; + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + + return (grp_val >> (hwirq % 32)) & 1; +} + +/* + * Fully acknowledge (both ack and eoi) any outstanding FIQ-based IPI, + * otherwise do nothing. + */ +void gic_handle_fiq_ipi(void) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *cpu_base = gic_data_cpu_base(gic); + unsigned long irqstat, irqnr; + + if (WARN_ON(!in_nmi())) + return; + + while ((1u << readl_relaxed(cpu_base + GIC_CPU_HIGHPRI)) & + SMP_IPI_FIQ_MASK) { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + WARN_RATELIMIT(irqnr > 16, + "Unexpected irqnr %lu (bad prioritization?)\n", + irqnr); + } +} + void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) { if (gic_nr >= MAX_GIC_NR) @@ -373,7 +467,18 @@ static void __init gic_dist_init(struct gic_chip_data *gic)
gic_dist_config(base, gic_irqs, NULL);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Set all global interrupts to be group 1 (this register is + * RAZ/WI if not accessible in current mode) + */ + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, base + GIC_DIST_IGROUP + i * 4 / 32); + + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + writel_relaxed(3, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -382,6 +487,7 @@ static void gic_cpu_init(struct gic_chip_data *gic) void __iomem *base = gic_data_cpu_base(gic); unsigned int cpu_mask, cpu = smp_processor_id(); int i; + unsigned long secure_irqs, secure_irq;
/* * Get what the GIC says our CPU mask is. @@ -400,8 +506,27 @@ static void gic_cpu_init(struct gic_chip_data *gic)
gic_cpu_config(dist_base, NULL);
+ /* + * Set any PPI and SGI interrupts not set in SMP_IPI_FIQ_MASK + * to be group 1 (this register is RAZ/WI if not accessible) + */ + writel_relaxed(~SMP_IPI_FIQ_MASK, dist_base + GIC_DIST_IGROUP + 0); + + /* + * Update the priority of any resulting group0 interrupts. + */ + secure_irqs = ~readl_relaxed(dist_base + GIC_DIST_IGROUP + 0); + for_each_set_bit(secure_irq, &secure_irqs, 16) + gic_set_group_irq(dist_base, i, 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + + /* The bottom most bit will be set for all GIC variants (and is + * called Enable or EnableGrp0 depending on operating mode). The + * remaining four bits (CBPR, FIQEn, AckCtl and EnableGrp1) are + * RAZ/WI if not accessible. + */ + writel_relaxed(0x1f, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -485,7 +610,7 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(3, dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -542,7 +667,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,6 +729,7 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0; + unsigned long softint;
raw_spin_lock_irqsave(&irq_controller_lock, flags);
@@ -618,7 +744,11 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + softint = map << 16 | irq; + if (gic_get_group_irq(gic_data_dist_base(&gic_data[0]), irq)) + softint |= 0x8000; + writel_relaxed(softint, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 45e2d8c..52a5676 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -101,5 +101,8 @@ static inline void __init register_routable_domain_ops { gic_routable_irq_domain_ops = ops; } + +void gic_handle_fiq_ipi(void); + #endif /* __ASSEMBLY */ #endif
From: Russell King rmk+kernel@arm.linux.org.uk
Add basic infrastructure for triggering a backtrace of other CPUs via an IPI, preferably at FIQ level. It is intended that this shall be used for cases where we have detected that something has already failed in the kernel.
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/irq.h | 5 ++++ arch/arm/kernel/smp.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+)
diff --git a/arch/arm/include/asm/irq.h b/arch/arm/include/asm/irq.h index 53c15de..be1d07d 100644 --- a/arch/arm/include/asm/irq.h +++ b/arch/arm/include/asm/irq.h @@ -35,6 +35,11 @@ extern void (*handle_arch_irq)(struct pt_regs *); extern void set_handle_irq(void (*handle_irq)(struct pt_regs *)); #endif
+#ifdef CONFIG_SMP +extern void arch_trigger_all_cpu_backtrace(bool); +#define arch_trigger_all_cpu_backtrace(x) arch_trigger_all_cpu_backtrace(x) +#endif + #endif
#endif diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 9388a3d..94959f9 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -72,8 +72,12 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_CPU_BACKTRACE, };
+/* For reliability, we're prepared to waste bits here. */ +static DECLARE_BITMAP(backtrace_mask, NR_CPUS) __read_mostly; + static DECLARE_COMPLETION(cpu_running);
static struct smp_operations smp_ops; @@ -539,6 +543,21 @@ static void ipi_cpu_stop(unsigned int cpu) cpu_relax(); }
+static void ipi_cpu_backtrace(struct pt_regs *regs) +{ + int cpu = smp_processor_id(); + + if (cpumask_test_cpu(cpu, to_cpumask(backtrace_mask))) { + static arch_spinlock_t lock = __ARCH_SPIN_LOCK_UNLOCKED; + + arch_spin_lock(&lock); + printk(KERN_WARNING "FIQ backtrace for cpu %d\n", cpu); + show_regs(regs); + arch_spin_unlock(&lock); + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + } +} + static DEFINE_PER_CPU(struct completion *, cpu_completion);
int register_ipi_completion(struct completion *completion, int cpu) @@ -618,6 +637,12 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break;
+ case IPI_CPU_BACKTRACE: + irq_enter(); + ipi_cpu_backtrace(regs); + irq_exit(); + break; + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr); @@ -712,3 +737,40 @@ static int __init register_cpufreq_notifier(void) core_initcall(register_cpufreq_notifier);
#endif + +void arch_trigger_all_cpu_backtrace(bool include_self) +{ + static unsigned long backtrace_flag; + int i, cpu = get_cpu(); + + if (test_and_set_bit(0, &backtrace_flag)) { + /* + * If there is already a trigger_all_cpu_backtrace() in progress + * (backtrace_flag == 1), don't output double cpu dump infos. + */ + put_cpu(); + return; + } + + cpumask_copy(to_cpumask(backtrace_mask), cpu_online_mask); + if (!include_self) + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + + if (!cpumask_empty(to_cpumask(backtrace_mask))) { + pr_info("Sending FIQ to %s CPUs:\n", + (include_self ? "all" : "other")); + smp_cross_call(to_cpumask(backtrace_mask), IPI_CPU_BACKTRACE); + } + + /* Wait for up to 10 seconds for all CPUs to do the backtrace */ + for (i = 0; i < 10 * 1000; i++) { + if (cpumask_empty(to_cpumask(backtrace_mask))) + break; + + mdelay(1); + } + + clear_bit(0, &backtrace_flag); + smp_mb__after_atomic(); + put_cpu(); +}
Previous changes have introduced a replacement default FIQ handler and an implementation of arch_trigger_all_cpu_backtrace for ARM but these are currently independant.
This patch plumbs together these features making it possible, on platforms that support it, to trigger backtrace using FIQ.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/smp.h | 3 +++ arch/arm/kernel/smp.c | 5 ++++- arch/arm/kernel/traps.c | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 2ec765c..0580270 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -18,6 +18,8 @@ # error "<asm/smp.h> included in non-SMP build" #endif
+#define SMP_IPI_FIQ_MASK 0x0100 + #define raw_smp_processor_id() (current_thread_info()->cpu)
struct seq_file; @@ -85,6 +87,7 @@ extern void arch_send_call_function_single_ipi(int cpu); extern void arch_send_call_function_ipi_mask(const struct cpumask *mask); extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
+extern void ipi_cpu_backtrace(struct pt_regs *regs); extern int register_ipi_completion(struct completion *completion, int cpu);
struct smp_operations { diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 94959f9..2a33bab 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -455,6 +455,7 @@ static const char *ipi_types[NR_IPI] __tracepoint_string = { S(IPI_CPU_STOP, "CPU stop interrupts"), S(IPI_IRQ_WORK, "IRQ work interrupts"), S(IPI_COMPLETION, "completion interrupts"), + S(IPI_BACKTRACE, "backtrace trigger interrupts"), };
static void smp_cross_call(const struct cpumask *target, unsigned int ipinr) @@ -543,7 +544,7 @@ static void ipi_cpu_stop(unsigned int cpu) cpu_relax(); }
-static void ipi_cpu_backtrace(struct pt_regs *regs) +void ipi_cpu_backtrace(struct pt_regs *regs) { int cpu = smp_processor_id();
@@ -584,6 +585,8 @@ void handle_IPI(int ipinr, struct pt_regs *regs) unsigned int cpu = smp_processor_id(); struct pt_regs *old_regs = set_irq_regs(regs);
+ BUILD_BUG_ON(SMP_IPI_FIQ_MASK != BIT(IPI_CPU_BACKTRACE)); + if ((unsigned)ipinr < NR_IPI) { trace_ipi_entry(ipi_types[ipinr]); __inc_irq_stat(cpu, ipi_irqs[ipinr]); diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index 92c4ea1..40b1de7 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -483,6 +483,9 @@ asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) #ifdef CONFIG_ARM_GIC gic_handle_fiq_ipi(); #endif +#ifdef CONFIG_SMP + ipi_cpu_backtrace(regs); +#endif
nmi_exit();
This patchset implements arch_trigger_all_cpu_backtrace for arm.
Non-maskable signalling relies on the kernel being able to access FIQ and therefore, for devices that implement TrustZone, it will work only on systems that boot the kernel in secure mode.
Tested on Freescale iMX.6 (both via SysRq-l and by deliberately locking up the kernel with CONFIG_DEBUG_SPINLOCK=y).
Changes since v4:
* Rebased on 3.17-rc4.
* Removed a spurious line from the final "glue it together" patch that broke the build.
Changes since v3:
* Replaced push/pop with stmfd/ldmfd respectively (review of Nicolas Pitre).
* Really fix bad pt_regs pointer generation in __fiq_abt.
* Remove fiq_safe_migration_lock and associated logic in gic_raise_softirq() (review of Russell King)
* Restructured to introduce the default FIQ handler first, before the new features (review of Russell King).
Changes since v2:
* Removed redundant header guards from arch/arm64/include/asm/fiq.h (review of Catalin Marinas).
* Moved svc_exit_via_fiq macro to entry-header.S (review of Nicolas Pitre).
Changes since v1:
* Restructured to sit nicely on a similar FYI patchset from Russell King. It now effectively replaces the work in progress final patch with something much more complete.
* Implemented (and tested) a Thumb-2 implementation of svc_exit_via_fiq (review of Nicolas Pitre)
* Dropped the GIC group 0 workaround patch. The issue of FIQ interrupts being acknowledged by the IRQ handler does still exist but should be harmless because the IRQ handler will still wind up calling ipi_cpu_backtrace().
* Removed any dependency on CONFIG_FIQ; all cpu backtrace effectively becomes a platform feature (although the use of non-maskable interrupts to implement it is best effort rather than guaranteed).
* Better comments highlighting usage of RAZ/WI registers (and parts of registers) in the GIC code.
Changes *before* v1:
* This patchset is a hugely cut-down successor to "[PATCH v11 00/19] arm: KGDB NMI/FIQ support". Thanks to Thomas Gleixner for suggesting the new structure. For historic details see: https://lkml.org/lkml/2014/9/2/227
* Fix bug in __fiq_abt (no longer passes a bad struct pt_regs value). In fixing this we also remove the useless indirection previously found in the fiq_handler macro.
* Make default fiq handler "always on" by migrating from fiq.c to traps.c and replace do_unexp_fiq with the new handler (review of Russell King).
* Add arm64 version of fiq.h (review of Russell King)
* Removed conditional branching and code from irq-gic.c, this is replaced by much simpler code that relies on the GIC specification's heavy use of read-as-zero/write-ignored (review of Russell King)
Daniel Thompson (5): arm: fiq: Replace default FIQ handler arm64: Introduce dummy version of asm/fiq.h irqchip: gic: Add support for IPI FIQ ARM: add basic support for on-demand backtrace of other CPUs arm: smp: Handle ipi_cpu_backtrace() using FIQ (if available)
Russell King (1): ARM: remove unused do_unexp_fiq() function
arch/arm/include/asm/irq.h | 5 ++ arch/arm/include/asm/smp.h | 3 + arch/arm/kernel/entry-armv.S | 95 ++++++++++++++++++++++++--- arch/arm/kernel/entry-header.S | 47 ++++++++++++++ arch/arm/kernel/setup.c | 8 ++- arch/arm/kernel/smp.c | 64 ++++++++++++++++++ arch/arm/kernel/traps.c | 32 ++++++++- arch/arm64/include/asm/fiq.h | 8 +++ drivers/irqchip/irq-gic.c | 140 ++++++++++++++++++++++++++++++++++++++-- include/linux/irqchip/arm-gic.h | 3 + 10 files changed, 386 insertions(+), 19 deletions(-) create mode 100644 arch/arm64/include/asm/fiq.h
-- 1.9.3
From: Russell King rmk+kernel@arm.linux.org.uk
do_unexp_fiq() has never been called by any code in the last 10 years, it's about time it was removed!
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/kernel/traps.c | 6 ------ 1 file changed, 6 deletions(-)
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index c8e4bb7..a447dcc 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -460,12 +460,6 @@ die_sig: arm_notify_die("Oops - undefined instruction", regs, &info, 0, 6); }
-asmlinkage void do_unexp_fiq (struct pt_regs *regs) -{ - printk("Hmm. Unexpected FIQ received, but trying to continue\n"); - printk("You may have a hardware problem...\n"); -} - /* * bad_mode handles the impossible case in the vectors. If you see one of * these, then it's extremely serious, and could mean you have buggy hardware.
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Credit:
This patch is a near complete re-write of a patch originally provided by Anton Vorontsov. Today only a couple of small fragments survive, however without Anton's work to build from this patch would not exist.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Catalin Marinas catalin.marinas@arm.com Acked-by: Nicolas Pitre nico@linaro.org --- arch/arm/kernel/entry-armv.S | 95 +++++++++++++++++++++++++++++++++++++----- arch/arm/kernel/entry-header.S | 47 +++++++++++++++++++++ arch/arm/kernel/setup.c | 8 +++- arch/arm/kernel/traps.c | 26 ++++++++++++ 4 files changed, 165 insertions(+), 11 deletions(-)
diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 36276cd..0c70fee 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -146,7 +146,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0 + .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) @@ -183,7 +183,9 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6}
#ifdef CONFIG_TRACE_IRQFLAGS + .if \call_trace bl trace_hardirqs_off + .endif #endif .endm
@@ -295,6 +297,15 @@ __pabt_svc: ENDPROC(__pabt_svc)
.align 5 +__fiq_svc: + svc_entry 0, 0 + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + svc_exit_via_fiq + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + + .align 5 .LCcralign: .word cr_alignment #ifdef MULTI_DABORT @@ -305,6 +316,46 @@ ENDPROC(__pabt_svc) .word fp_enter
/* + * Abort mode handlers + */ + +@ +@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode +@ and reuses the same macros. However in abort mode we must also +@ save/restore lr_abt and spsr_abt to make nested aborts safe. +@ + .align 5 +__fiq_abt: + svc_entry 0, 0 + + ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + mov r1, lr @ Save lr_abt + mrs r2, spsr @ Save spsr_abt, abort is now safe + ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + stmfd sp!, {r1 - r2} + + add r0, sp, #8 @ struct pt_regs *regs + bl handle_fiq_as_nmi + + ldmfd sp!, {r1 - r2} + ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + mov lr, r1 @ Restore lr_abt, abort is unsafe + msr spsr_cxsf, r2 @ Restore spsr_abt + ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT ) + THUMB( msr cpsr_c, r0 ) + + svc_exit_via_fiq + UNWIND(.fnend ) +ENDPROC(__fiq_svc) + +/* * User mode handlers * * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE @@ -683,6 +734,18 @@ ENTRY(ret_from_exception) ENDPROC(__pabt_usr) ENDPROC(ret_from_exception)
+ .align 5 +__fiq_usr: + usr_entry + kuser_cmpxchg_check + mov r0, sp @ struct pt_regs *regs + bl handle_fiq_as_nmi + get_thread_info tsk + mov why, #0 + b ret_to_user_from_irq + UNWIND(.fnend ) +ENDPROC(__fiq_usr) + /* * Register switch for ARMv3 and ARMv4 processors * r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info @@ -1118,17 +1181,29 @@ vector_addrexcptn: b vector_addrexcptn
/*============================================================================= - * Undefined FIQs + * FIQ "NMI" handler *----------------------------------------------------------------------------- - * Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC - * MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg. - * Basically to switch modes, we *HAVE* to clobber one register... brain - * damage alert! I don't think that we can execute any code in here in any - * other mode than FIQ... Ok you can switch to another mode, but you can't - * get out of that mode without clobbering one register. + * Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86 + * systems. */ -vector_fiq: - subs pc, lr, #4 + vector_stub fiq, FIQ_MODE, 4 + + .long __fiq_usr @ 0 (USR_26 / USR_32) + .long __fiq_svc @ 1 (FIQ_26 / FIQ_32) + .long __fiq_svc @ 2 (IRQ_26 / IRQ_32) + .long __fiq_svc @ 3 (SVC_26 / SVC_32) + .long __fiq_svc @ 4 + .long __fiq_svc @ 5 + .long __fiq_svc @ 6 + .long __fiq_abt @ 7 + .long __fiq_svc @ 8 + .long __fiq_svc @ 9 + .long __fiq_svc @ a + .long __fiq_svc @ b + .long __fiq_svc @ c + .long __fiq_svc @ d + .long __fiq_svc @ e + .long __fiq_svc @ f
.globl vector_fiq_offset .equ vector_fiq_offset, vector_fiq diff --git a/arch/arm/kernel/entry-header.S b/arch/arm/kernel/entry-header.S index 2fdf867..0d91ca0 100644 --- a/arch/arm/kernel/entry-header.S +++ b/arch/arm/kernel/entry-header.S @@ -216,6 +216,34 @@ ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr .endm
+ @ + @ svc_exit_via_fiq - like svc_exit but switches to FIQ mode before exit + @ + @ This macro acts in a similar manner to svc_exit but switches to FIQ + @ mode to restore the final part of the register state. + @ + @ We cannot use the normal svc_exit procedure because that would + @ clobber spsr_svc (FIQ could be delivered during the first few + @ instructions of vector_swi meaning its contents have not been + @ saved anywhere). + @ + @ Note that, unlike svc_exit, this macro also does not allow a caller + @ supplied rpsr. This is because the FIQ exceptions are not re-entrant + @ and the handlers cannot call into the scheduler (meaning the value + @ on the stack remains correct). + @ + .macro svc_exit_via_fiq + mov r0, sp + ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will + @ clobber state restored below) + msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + add r8, r0, #S_PC + ldr r9, [r0, #S_PSR] + msr spsr_cxsf, r9 + ldr r0, [r0, #S_R0] + ldmia r8, {pc}^ + .endm + .macro restore_user_regs, fast = 0, offset = 0 ldr r1, [sp, #\offset + S_PSR] @ get calling cpsr ldr lr, [sp, #\offset + S_PC]! @ get pc @@ -267,6 +295,25 @@ rfeia sp! .endm
+ @ + @ svc_exit_via_fiq - like svc_exit but switches to FIQ mode before exit + @ + @ For full details see non-Thumb implementation above. + @ + .macro svc_exit_via_fiq + add r0, sp, #S_R2 + ldr lr, [sp, #S_LR] + ldr sp, [sp, #S_SP] @ abort is deadly from here onward (it will + @ clobber state restored below) + ldmia r0, {r2 - r12} + mov r1, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT + msr cpsr_c, r1 + sub r0, #S_R2 + add r8, r0, #S_PC + ldmia r0, {r0 - r1} + rfeia r8 + .endm + #ifdef CONFIG_CPU_V7M /* * Note we don't need to do clrex here as clearing the local monitor is diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c index 84db893d..c031063 100644 --- a/arch/arm/kernel/setup.c +++ b/arch/arm/kernel/setup.c @@ -133,6 +133,7 @@ struct stack { u32 irq[3]; u32 abt[3]; u32 und[3]; + u32 fiq[3]; } ____cacheline_aligned;
#ifndef CONFIG_CPU_V7M @@ -470,7 +471,10 @@ void notrace cpu_init(void) "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t" - "msr cpsr_c, %7" + "msr cpsr_c, %7\n\t" + "add r14, %0, %8\n\t" + "mov sp, r14\n\t" + "msr cpsr_c, %9" : : "r" (stk), PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE), @@ -479,6 +483,8 @@ void notrace cpu_init(void) "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])), + PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE), + "I" (offsetof(struct stack, fiq[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14"); #endif diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index a447dcc..439138d 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -25,6 +25,7 @@ #include <linux/delay.h> #include <linux/init.h> #include <linux/sched.h> +#include <linux/irq.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -461,6 +462,31 @@ die_sig: }
/* + * Handle FIQ similarly to NMI on x86 systems. + * + * The runtime environment for NMIs is extremely restrictive + * (NMIs can pre-empt critical sections meaning almost all locking is + * forbidden) meaning this default FIQ handling must only be used in + * circumstances where non-maskability improves robustness, such as + * watchdog or debug logic. + * + * This handler is not appropriate for general purpose use in drivers + * platform code and can be overrideen using set_fiq_handler. + */ +asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) +{ + struct pt_regs *old_regs = set_irq_regs(regs); + + nmi_enter(); + + /* nop. FIQ handlers for special arch/arm features can be added here. */ + + nmi_exit(); + + set_irq_regs(old_regs); +} + +/* * bad_mode handles the impossible case in the vectors. If you see one of * these, then it's extremely serious, and could mean you have buggy hardware. * It never returns, and never tries to sync. We hope that we can at least
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
- .macro svc_entry, stack_hole=0
- .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
@@ -183,7 +183,9 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6} #ifdef CONFIG_TRACE_IRQFLAGS
- .if \call_trace bl trace_hardirqs_off
- .endif
#endif
Good, you picked this up from my patch. But what about the call into lockdep from usr_entry?
Yes, it should be safe if we're entering from user mode, because by definition, the kernel can't be holding any locks at that point. However, I'd much prefer to keep to a set of simple rules here: avoid lockdep in FIQ code altogether.
That's much easier to understand than "we can call into lockdep provided we've been entered from user mode".
The other thing you miss is that /potentially/ call into the scheduler as well from a FIQ. Do we /really/ want to do that kind of work here?
Not happy.
On Fri, Sep 12, 2014 at 06:03:07PM +0100, Russell King - ARM Linux wrote:
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
- .macro svc_entry, stack_hole=0
- .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
@@ -183,7 +183,9 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6} #ifdef CONFIG_TRACE_IRQFLAGS
- .if \call_trace bl trace_hardirqs_off
- .endif
#endif
Good, you picked this up from my patch. But what about the call into lockdep from usr_entry?
Yes, it should be safe if we're entering from user mode, because by definition, the kernel can't be holding any locks at that point. However, I'd much prefer to keep to a set of simple rules here: avoid lockdep in FIQ code altogether.
That's much easier to understand than "we can call into lockdep provided we've been entered from user mode".
The other thing you miss is that /potentially/ call into the scheduler as well from a FIQ. Do we /really/ want to do that kind of work here?
Not happy.
And you're also missing a .cantunwind for __fiq_usr, which means that the Dwarf doesn't contain an explicit point to stop unwinding.
Lastly, don't thread your new patches to the old ones. I utterly hate that behaviour. It makes subject lines totally pointless because all I end up seeing is "[PAT" on the very right hand of the screen. Far from useful.
On 12/09/14 18:03, Russell King - ARM Linux wrote:
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
- .macro svc_entry, stack_hole=0
- .macro svc_entry, stack_hole=0, call_trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
@@ -183,7 +183,9 @@ ENDPROC(__und_invalid) stmia r7, {r2 - r6} #ifdef CONFIG_TRACE_IRQFLAGS
- .if \call_trace bl trace_hardirqs_off
- .endif
#endif
Good, you picked this up from my patch. But what about the call into lockdep from usr_entry?
That was writen from your review comment rather than taken from your patch.
Yes, it should be safe if we're entering from user mode, because by definition, the kernel can't be holding any locks at that point. However, I'd much prefer to keep to a set of simple rules here: avoid lockdep in FIQ code altogether.
Ok. You're right that I followed the "can't be holding any locks" logic when I didn't update usr_entry in reaction to the original review comment.
I'm also happy with the "avoid lockdep in FIQ code altogether" approach. I'll do this.
That's much easier to understand than "we can call into lockdep provided we've been entered from user mode".
The other thing you miss is that /potentially/ call into the scheduler as well from a FIQ. Do we /really/ want to do that kind of work here?
Not happy.
Sorry. I will fix these.
Daniel.
And another thing...
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
- .align 5
+__fiq_abt:
- svc_entry 0, 0
Right, so this function is called __fiq_abt. ...
- svc_exit_via_fiq
- UNWIND(.fnend )
+ENDPROC(__fiq_svc)
Or is it called __fiq_svc ?
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Okay, lastly... I sent you my version of this change, which contained the changes I've detailed in the previous three emails, which are absent from your version.
However, you've taken on board the "trace" thing to svc_entry, and renamed it to "call_trace".
Now if I add this to usr_entry, "call_trace" doesn't make any sense, nor does the .if/.endif placement because it's not just trace_hardirqs_off which needs to be disabled there, but also ct_user_exit as well.
I'm beginning to wonder why I tried to be nice here... it would've been a lot faster for me to take your patch, make my own changes and commit that instead.
Frustrated.
On Fri, Sep 12, 2014 at 06:14:04PM +0100, Russell King - ARM Linux wrote:
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Okay, lastly... I sent you my version of this change, which contained the changes I've detailed in the previous three emails, which are absent from your version.
However, you've taken on board the "trace" thing to svc_entry, and renamed it to "call_trace".
Now if I add this to usr_entry, "call_trace" doesn't make any sense, nor does the .if/.endif placement because it's not just trace_hardirqs_off which needs to be disabled there, but also ct_user_exit as well.
I'm beginning to wonder why I tried to be nice here... it would've been a lot faster for me to take your patch, make my own changes and commit that instead.
Frustrated.
And another thing you're missing are the updates to arch/arm/kernel/fiq.c to ensure that the FIQ registers are preserved when we restore this new default FIQ handler.
On Fri, Sep 12, 2014 at 06:19:08PM +0100, Russell King - ARM Linux wrote:
On Fri, Sep 12, 2014 at 06:14:04PM +0100, Russell King - ARM Linux wrote:
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Okay, lastly... I sent you my version of this change, which contained the changes I've detailed in the previous three emails, which are absent from your version.
However, you've taken on board the "trace" thing to svc_entry, and renamed it to "call_trace".
Now if I add this to usr_entry, "call_trace" doesn't make any sense, nor does the .if/.endif placement because it's not just trace_hardirqs_off which needs to be disabled there, but also ct_user_exit as well.
I'm beginning to wonder why I tried to be nice here... it would've been a lot faster for me to take your patch, make my own changes and commit that instead.
Frustrated.
And another thing you're missing are the updates to arch/arm/kernel/fiq.c to ensure that the FIQ registers are preserved when we restore this new default FIQ handler.
Right, here's my remaining delta from your patch addressing all the points from the last five emails. If you have any disagreements with any of these changes, then please discuss rather than choosing to ignore them.
107e32b0b4ef5fa4191c9fc8415ca172b886e958 diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 0c70fee9a7c9..3f6293ce0f2d 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -146,7 +146,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0, call_trace=1 + .macro svc_entry, stack_hole=0, trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4) @@ -182,11 +182,11 @@ ENDPROC(__und_invalid) @ stmia r7, {r2 - r6}
+ .if \trace #ifdef CONFIG_TRACE_IRQFLAGS - .if \call_trace bl trace_hardirqs_off - .endif #endif + .endif .endm
.align 5 @@ -298,7 +298,7 @@ ENDPROC(__pabt_svc)
.align 5 __fiq_svc: - svc_entry 0, 0 + svc_entry trace=0 mov r0, sp @ struct pt_regs *regs bl handle_fiq_as_nmi svc_exit_via_fiq @@ -326,7 +326,7 @@ ENDPROC(__fiq_svc) @ .align 5 __fiq_abt: - svc_entry 0, 0 + svc_entry trace=0
ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) @@ -353,7 +353,7 @@ __fiq_abt:
svc_exit_via_fiq UNWIND(.fnend ) -ENDPROC(__fiq_svc) +ENDPROC(__fiq_abt)
/* * User mode handlers @@ -365,7 +365,7 @@ ENDPROC(__fiq_svc) #error "sizeof(struct pt_regs) must be a multiple of 8" #endif
- .macro usr_entry + .macro usr_entry, trace=1 UNWIND(.fnstart ) UNWIND(.cantunwind ) @ don't unwind the user space sub sp, sp, #S_FRAME_SIZE @@ -402,10 +402,12 @@ ENDPROC(__fiq_svc) @ zero_fp
+ .if \trace #ifdef CONFIG_IRQSOFF_TRACER bl trace_hardirqs_off #endif ct_user_exit save = 0 + .endif .endm
.macro kuser_cmpxchg_check @@ -736,13 +738,13 @@ ENDPROC(ret_from_exception)
.align 5 __fiq_usr: - usr_entry + usr_entry trace=0 kuser_cmpxchg_check mov r0, sp @ struct pt_regs *regs bl handle_fiq_as_nmi get_thread_info tsk - mov why, #0 - b ret_to_user_from_irq + restore_user_regs fast = 0, offset = 0 + UNWIND(.cantunwind ) UNWIND(.fnend ) ENDPROC(__fiq_usr)
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d96d5d..1743049c433b 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -53,6 +53,7 @@ })
static unsigned long no_fiq_insn; +static struct pt_regs def_fiq_regs;
/* Default reacquire function * - we always relinquish FIQ control @@ -60,8 +61,15 @@ static unsigned long no_fiq_insn; */ static int fiq_def_op(void *ref, int relinquish) { - if (!relinquish) + if (!relinquish) { + /* Restore default handler and registers */ + local_fiq_disable(); + set_fiq_regs(&dfl_fiq_regs); set_fiq_handler(&no_fiq_insn, sizeof(no_fiq_insn)); + local_fiq_enable(); + + /* FIXME: notify irq controller to standard enable FIQs */ + }
return 0; } @@ -151,5 +159,6 @@ void __init init_FIQ(int start) { unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset); + get_fiq_regs(&dfl_fiq_regs); fiq_start = start; }
On 12/09/14 18:23, Russell King - ARM Linux wrote:
On Fri, Sep 12, 2014 at 06:19:08PM +0100, Russell King - ARM Linux wrote:
On Fri, Sep 12, 2014 at 06:14:04PM +0100, Russell King - ARM Linux wrote:
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Okay, lastly... I sent you my version of this change, which contained the changes I've detailed in the previous three emails, which are absent from your version.
However, you've taken on board the "trace" thing to svc_entry, and renamed it to "call_trace".
Now if I add this to usr_entry, "call_trace" doesn't make any sense, nor does the .if/.endif placement because it's not just trace_hardirqs_off which needs to be disabled there, but also ct_user_exit as well.
I'm beginning to wonder why I tried to be nice here... it would've been a lot faster for me to take your patch, make my own changes and commit that instead.
Frustrated.
And another thing you're missing are the updates to arch/arm/kernel/fiq.c to ensure that the FIQ registers are preserved when we restore this new default FIQ handler.
Right, here's my remaining delta from your patch addressing all the points from the last five emails. If you have any disagreements with any of these changes, then please discuss rather than choosing to ignore them.
Thanks for the diff.
107e32b0b4ef5fa4191c9fc8415ca172b886e958 diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 0c70fee9a7c9..3f6293ce0f2d 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S @@ -146,7 +146,7 @@ ENDPROC(__und_invalid) #define SPFIX(code...) #endif
- .macro svc_entry, stack_hole=0, call_trace=1
- .macro svc_entry, stack_hole=0, trace=1 UNWIND(.fnstart ) UNWIND(.save {r0 - pc} ) sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
@@ -182,11 +182,11 @@ ENDPROC(__und_invalid) @ stmia r7, {r2 - r6}
- .if \trace
#ifdef CONFIG_TRACE_IRQFLAGS
- .if \call_trace bl trace_hardirqs_off
- .endif
#endif
- .endif .endm
.align 5 @@ -298,7 +298,7 @@ ENDPROC(__pabt_svc) .align 5 __fiq_svc:
- svc_entry 0, 0
- svc_entry trace=0 mov r0, sp @ struct pt_regs *regs bl handle_fiq_as_nmi svc_exit_via_fiq
@@ -326,7 +326,7 @@ ENDPROC(__fiq_svc) @ .align 5 __fiq_abt:
- svc_entry 0, 0
- svc_entry trace=0
ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT ) @@ -353,7 +353,7 @@ __fiq_abt: svc_exit_via_fiq UNWIND(.fnend ) -ENDPROC(__fiq_svc) +ENDPROC(__fiq_abt) /*
- User mode handlers
@@ -365,7 +365,7 @@ ENDPROC(__fiq_svc) #error "sizeof(struct pt_regs) must be a multiple of 8" #endif
- .macro usr_entry
- .macro usr_entry, trace=1 UNWIND(.fnstart ) UNWIND(.cantunwind ) @ don't unwind the user space sub sp, sp, #S_FRAME_SIZE
@@ -402,10 +402,12 @@ ENDPROC(__fiq_svc) @ zero_fp
- .if \trace
#ifdef CONFIG_IRQSOFF_TRACER bl trace_hardirqs_off #endif ct_user_exit save = 0
- .endif .endm
.macro kuser_cmpxchg_check @@ -736,13 +738,13 @@ ENDPROC(ret_from_exception) .align 5 __fiq_usr:
- usr_entry
- usr_entry trace=0 kuser_cmpxchg_check mov r0, sp @ struct pt_regs *regs bl handle_fiq_as_nmi get_thread_info tsk
- mov why, #0
- b ret_to_user_from_irq
- restore_user_regs fast = 0, offset = 0
- UNWIND(.cantunwind ) UNWIND(.fnend )
ENDPROC(__fiq_usr) diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d96d5d..1743049c433b 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -53,6 +53,7 @@ }) static unsigned long no_fiq_insn; +static struct pt_regs def_fiq_regs; /* Default reacquire function
- we always relinquish FIQ control
@@ -60,8 +61,15 @@ static unsigned long no_fiq_insn; */ static int fiq_def_op(void *ref, int relinquish) {
- if (!relinquish)
- if (!relinquish) {
/* Restore default handler and registers */
local_fiq_disable();
set_fiq_regs(&dfl_fiq_regs);
This variable was declared as def_fiq_regs .
set_fiq_handler(&no_fiq_insn, sizeof(no_fiq_insn));
local_fiq_enable();
/* FIXME: notify irq controller to standard enable FIQs */
I don't understand what we need to tell the irq controller at this point.
If we do want to mask sources of FIQ from arch code then in the case of IPI_CPU_BACKTRACE we might be better off disabling it at point of generation than at the interrupt controller (to avoid the long timeout).
- }
return 0; } @@ -151,5 +159,6 @@ void __init init_FIQ(int start) { unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset);
- get_fiq_regs(&dfl_fiq_regs); fiq_start = start;
}
On Sun, Sep 14, 2014 at 07:36:31AM +0100, Daniel Thompson wrote:
- if (!relinquish)
- if (!relinquish) {
/* Restore default handler and registers */
local_fiq_disable();
set_fiq_regs(&dfl_fiq_regs);
This variable was declared as def_fiq_regs .
Yep.
set_fiq_handler(&no_fiq_insn, sizeof(no_fiq_insn));
local_fiq_enable();
/* FIXME: notify irq controller to standard enable FIQs */
I don't understand what we need to tell the irq controller at this point.
If we do want to mask sources of FIQ from arch code then in the case of IPI_CPU_BACKTRACE we might be better off disabling it at point of generation than at the interrupt controller (to avoid the long timeout).
The point is, when a platform decides that it wants to insert its own /fast/ interrupt handler into the vector, our default one is moved out of the way. Those fast interrupt handlers assume that they are the only user of FIQ (since only one fast interrupt handler routine can be in place at any one time.) So, it's not going to expect to receive any other FIQs other than the one(s) it's written for.
So, when the platform gives up the FIQ vector, and we restore the default FIQ code, we need to make sure that the FIQ sources for that code are re-enabled.
Yes, you may not like this, and you may want to turn FIQ into a sprawling mess with interrupt source demux and such like, but that's not really on. The clue is in the name. *Fast*. On certain platforms, it gets used for time critical activities, and it being low latency is an absolute must.
On 12/09/14 18:23, Russell King - ARM Linux wrote:
On Fri, Sep 12, 2014 at 06:19:08PM +0100, Russell King - ARM Linux wrote:
On Fri, Sep 12, 2014 at 06:14:04PM +0100, Russell King - ARM Linux wrote:
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Okay, lastly... I sent you my version of this change, which contained the changes I've detailed in the previous three emails, which are absent from your version.
However, you've taken on board the "trace" thing to svc_entry, and renamed it to "call_trace".
Now if I add this to usr_entry, "call_trace" doesn't make any sense, nor does the .if/.endif placement because it's not just trace_hardirqs_off which needs to be disabled there, but also ct_user_exit as well.
I'm beginning to wonder why I tried to be nice here... it would've been a lot faster for me to take your patch, make my own changes and commit that instead.
Frustrated.
And another thing you're missing are the updates to arch/arm/kernel/fiq.c to ensure that the FIQ registers are preserved when we restore this new default FIQ handler.
Right, here's my remaining delta from your patch addressing all the points from the last five emails. If you have any disagreements with any of these changes, then please discuss rather than choosing to ignore them.
107e32b0b4ef5fa4191c9fc8415ca172b886e958 diff --git a/arch/arm/kernel/entry-armv.S b/arch/arm/kernel/entry-armv.S index 0c70fee9a7c9..3f6293ce0f2d 100644 --- a/arch/arm/kernel/entry-armv.S +++ b/arch/arm/kernel/entry-armv.S
<snip> @@ -736,13 +738,13 @@ ENDPROC(ret_from_exception) .align 5 __fiq_usr: - usr_entry + usr_entry trace=0 kuser_cmpxchg_check mov r0, sp @ struct pt_regs *regs bl handle_fiq_as_nmi get_thread_info tsk - mov why, #0 - b ret_to_user_from_irq + restore_user_regs fast = 0, offset = 0 + UNWIND(.cantunwind )
.cantunwind is already provided by the usr_entry macro (also adding .cantunwind here causes my assembler (2.24) to fail).
UNWIND(.fnend ) ENDPROC(__fiq_usr) diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 918875d96d5d..1743049c433b 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -53,6 +53,7 @@ }) static unsigned long no_fiq_insn; +static struct pt_regs def_fiq_regs; /* Default reacquire function
- we always relinquish FIQ control
@@ -60,8 +61,15 @@ static unsigned long no_fiq_insn; */ static int fiq_def_op(void *ref, int relinquish) {
- if (!relinquish)
- if (!relinquish) {
/* Restore default handler and registers */
local_fiq_disable();
set_fiq_handler(&no_fiq_insn, sizeof(no_fiq_insn));set_fiq_regs(&dfl_fiq_regs);
local_fiq_enable();
/* FIXME: notify irq controller to standard enable FIQs */
- }
return 0; } @@ -151,5 +159,6 @@ void __init init_FIQ(int start) { unsigned offset = FIQ_OFFSET; no_fiq_insn = *(unsigned long *)(0xffff0000 + offset);
- get_fiq_regs(&dfl_fiq_regs); fiq_start = start;
}
On 12/09/14 18:14, Russell King - ARM Linux wrote:
On Thu, Sep 11, 2014 at 12:31:14PM +0100, Daniel Thompson wrote:
This patch introduces a new default FIQ handler that is structured in a similar way to the existing ARM exception handler and result in the FIQ being handled by C code running on the SVC stack (despite this code run in the FIQ handler is subject to severe limitations with respect to locking making normal interaction with the kernel impossible).
This default handler allows concepts that on x86 would be handled using NMIs to be realized on ARM.
Okay, lastly... I sent you my version of this change, which contained the changes I've detailed in the previous three emails, which are absent from your version.
However, you've taken on board the "trace" thing to svc_entry, and renamed it to "call_trace".
Now if I add this to usr_entry, "call_trace" doesn't make any sense, nor does the .if/.endif placement because it's not just trace_hardirqs_off which needs to be disabled there, but also ct_user_exit as well.
I'm beginning to wonder why I tried to be nice here... it would've been a lot faster for me to take your patch, make my own changes and commit that instead.
I did not do a side by side diff of your FYI patch with my current code and hence overlooked all these changes.
Sorry. I should have done that.
Drivers that are shared between arm and arm64 and which employ FIQ on arm cannot include asm/fiq.h without #ifdef'ing. This patch introduces a dummy version of asm/fiq.h to arm64 to avoid this.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Catalin Marinas catalin.marinas@arm.com Cc: Will Deacon will.deacon@arm.com --- arch/arm64/include/asm/fiq.h | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 arch/arm64/include/asm/fiq.h
diff --git a/arch/arm64/include/asm/fiq.h b/arch/arm64/include/asm/fiq.h new file mode 100644 index 0000000..d3776b8 --- /dev/null +++ b/arch/arm64/include/asm/fiq.h @@ -0,0 +1,8 @@ +/* + * Placeholder to reduce #ifdef'ing in shared arm/arm64 drivers. It + * allows code of the following form to be made unconditional. + * + * #ifdef CONFIG_FIQ + * #include <asm/fiq.h> + * #endif + */
This patch provides support for arm's newly added IPI FIQ. It works by placing all interrupt sources *except* IPI FIQ in group 1 and then flips a configuration bit in the GIC such that group 1 interrupts use IRQ and group 0 interrupts use FIQ.
All GIC hardware except GICv1-without-TrustZone support provides a means to group exceptions into group 0 and group 1. However the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world" (a feature that allows grouping to be used to allow hardware peripherals to send interrupts into the secure world). However when grouping is not available we can rely on the GIC's RAZ/WI semantics and avoid conditional code.
Tested on Freescale i.MX6 (quad A9, GICv1-with-TrustZone running in secure mode).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net --- arch/arm/kernel/traps.c | 5 +- drivers/irqchip/irq-gic.c | 140 ++++++++++++++++++++++++++++++++++++++-- include/linux/irqchip/arm-gic.h | 3 + 3 files changed, 142 insertions(+), 6 deletions(-)
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index 439138d..92c4ea1 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -26,6 +26,7 @@ #include <linux/init.h> #include <linux/sched.h> #include <linux/irq.h> +#include <linux/irqchip/arm-gic.h>
#include <linux/atomic.h> #include <asm/cacheflush.h> @@ -479,7 +480,9 @@ asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs)
nmi_enter();
- /* nop. FIQ handlers for special arch/arm features can be added here. */ +#ifdef CONFIG_ARM_GIC + gic_handle_fiq_ipi(); +#endif
nmi_exit();
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..0d67b0e 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -39,8 +39,10 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/ratelimit.h>
#include <asm/cputype.h> +#include <asm/fiq.h> #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -48,6 +50,10 @@ #include "irq-gic-common.h" #include "irqchip.h"
+#ifndef SMP_IPI_FIQ_MASK +#define SMP_IPI_FIQ_MASK 0 +#endif + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -325,6 +331,94 @@ static struct irq_chip gic_chip = { .irq_set_wake = gic_set_wake, };
+/* + * Shift an interrupt between Group 0 and Group 1. + * + * In addition to changing the group we also modify the priority to + * match what "ARM strongly recommends" for a system where no Group 1 + * interrupt must ever preempt a Group 0 interrupt. + * + * If is safe to call this function on systems which do not support + * grouping (it will have no effect). + */ +static void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_mask = BIT(hwirq % 32); + u32 grp_val; + + unsigned int pri_reg = (hwirq / 4) * 4; + u32 pri_mask = BIT(7 + ((hwirq % 4) * 8)); + u32 pri_val; + + /* + * Systems which do not support grouping will have no bits + * set in IGROUP[0] (and all systems which do will have set bits). + */ + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + 0); + if (!grp_val) + return; + + raw_spin_lock(&irq_controller_lock); + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(base + GIC_DIST_PRI + pri_reg); + + if (group) { + grp_val |= grp_mask; + pri_val |= pri_mask; + } else { + grp_val &= ~grp_mask; + pri_val &= ~pri_mask; + } + + writel_relaxed(grp_val, base + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, base + GIC_DIST_PRI + pri_reg); + + raw_spin_unlock(&irq_controller_lock); +} + +/* + * Test which group an interrupt belongs to. + * + * Returns 0 if the controller does not support grouping. + */ +static int gic_get_group_irq(void __iomem *base, unsigned int hwirq) +{ + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_val; + + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + + return (grp_val >> (hwirq % 32)) & 1; +} + +/* + * Fully acknowledge (both ack and eoi) any outstanding FIQ-based IPI, + * otherwise do nothing. + */ +void gic_handle_fiq_ipi(void) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *cpu_base = gic_data_cpu_base(gic); + unsigned long irqstat, irqnr; + + if (WARN_ON(!in_nmi())) + return; + + while ((1u << readl_relaxed(cpu_base + GIC_CPU_HIGHPRI)) & + SMP_IPI_FIQ_MASK) { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + WARN_RATELIMIT(irqnr > 16, + "Unexpected irqnr %lu (bad prioritization?)\n", + irqnr); + } +} + void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) { if (gic_nr >= MAX_GIC_NR) @@ -373,7 +467,18 @@ static void __init gic_dist_init(struct gic_chip_data *gic)
gic_dist_config(base, gic_irqs, NULL);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Set all global interrupts to be group 1 (this register is + * RAZ/WI if not accessible in current mode) + */ + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, base + GIC_DIST_IGROUP + i * 4 / 32); + + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + writel_relaxed(3, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -382,6 +487,7 @@ static void gic_cpu_init(struct gic_chip_data *gic) void __iomem *base = gic_data_cpu_base(gic); unsigned int cpu_mask, cpu = smp_processor_id(); int i; + unsigned long secure_irqs, secure_irq;
/* * Get what the GIC says our CPU mask is. @@ -400,8 +506,27 @@ static void gic_cpu_init(struct gic_chip_data *gic)
gic_cpu_config(dist_base, NULL);
+ /* + * Set any PPI and SGI interrupts not set in SMP_IPI_FIQ_MASK + * to be group 1 (this register is RAZ/WI if not accessible) + */ + writel_relaxed(~SMP_IPI_FIQ_MASK, dist_base + GIC_DIST_IGROUP + 0); + + /* + * Update the priority of any resulting group0 interrupts. + */ + secure_irqs = ~readl_relaxed(dist_base + GIC_DIST_IGROUP + 0); + for_each_set_bit(secure_irq, &secure_irqs, 16) + gic_set_group_irq(dist_base, i, 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + + /* The bottom most bit will be set for all GIC variants (and is + * called Enable or EnableGrp0 depending on operating mode). The + * remaining four bits (CBPR, FIQEn, AckCtl and EnableGrp1) are + * RAZ/WI if not accessible. + */ + writel_relaxed(0x1f, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -485,7 +610,7 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + writel_relaxed(3, dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -542,7 +667,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,6 +729,7 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0; + unsigned long softint;
raw_spin_lock_irqsave(&irq_controller_lock, flags);
@@ -618,7 +744,11 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + softint = map << 16 | irq; + if (gic_get_group_irq(gic_data_dist_base(&gic_data[0]), irq)) + softint |= 0x8000; + writel_relaxed(softint, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 45e2d8c..52a5676 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -101,5 +101,8 @@ static inline void __init register_routable_domain_ops { gic_routable_irq_domain_ops = ops; } + +void gic_handle_fiq_ipi(void); + #endif /* __ASSEMBLY */ #endif
Add basic infrastructure for triggering a backtrace of other CPUs via an IPI, preferably at FIQ level. It is intended that this shall be used for cases where we have detected that something has already failed in the kernel.
Signed-off-by: Russell King rmk+kernel@arm.linux.org.uk Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/irq.h | 5 ++++ arch/arm/kernel/smp.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+)
diff --git a/arch/arm/include/asm/irq.h b/arch/arm/include/asm/irq.h index 53c15de..be1d07d 100644 --- a/arch/arm/include/asm/irq.h +++ b/arch/arm/include/asm/irq.h @@ -35,6 +35,11 @@ extern void (*handle_arch_irq)(struct pt_regs *); extern void set_handle_irq(void (*handle_irq)(struct pt_regs *)); #endif
+#ifdef CONFIG_SMP +extern void arch_trigger_all_cpu_backtrace(bool); +#define arch_trigger_all_cpu_backtrace(x) arch_trigger_all_cpu_backtrace(x) +#endif + #endif
#endif diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 9388a3d..94959f9 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -72,8 +72,12 @@ enum ipi_msg_type { IPI_CPU_STOP, IPI_IRQ_WORK, IPI_COMPLETION, + IPI_CPU_BACKTRACE, };
+/* For reliability, we're prepared to waste bits here. */ +static DECLARE_BITMAP(backtrace_mask, NR_CPUS) __read_mostly; + static DECLARE_COMPLETION(cpu_running);
static struct smp_operations smp_ops; @@ -539,6 +543,21 @@ static void ipi_cpu_stop(unsigned int cpu) cpu_relax(); }
+static void ipi_cpu_backtrace(struct pt_regs *regs) +{ + int cpu = smp_processor_id(); + + if (cpumask_test_cpu(cpu, to_cpumask(backtrace_mask))) { + static arch_spinlock_t lock = __ARCH_SPIN_LOCK_UNLOCKED; + + arch_spin_lock(&lock); + printk(KERN_WARNING "FIQ backtrace for cpu %d\n", cpu); + show_regs(regs); + arch_spin_unlock(&lock); + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + } +} + static DEFINE_PER_CPU(struct completion *, cpu_completion);
int register_ipi_completion(struct completion *completion, int cpu) @@ -618,6 +637,12 @@ void handle_IPI(int ipinr, struct pt_regs *regs) irq_exit(); break;
+ case IPI_CPU_BACKTRACE: + irq_enter(); + ipi_cpu_backtrace(regs); + irq_exit(); + break; + default: printk(KERN_CRIT "CPU%u: Unknown IPI message 0x%x\n", cpu, ipinr); @@ -712,3 +737,40 @@ static int __init register_cpufreq_notifier(void) core_initcall(register_cpufreq_notifier);
#endif + +void arch_trigger_all_cpu_backtrace(bool include_self) +{ + static unsigned long backtrace_flag; + int i, cpu = get_cpu(); + + if (test_and_set_bit(0, &backtrace_flag)) { + /* + * If there is already a trigger_all_cpu_backtrace() in progress + * (backtrace_flag == 1), don't output double cpu dump infos. + */ + put_cpu(); + return; + } + + cpumask_copy(to_cpumask(backtrace_mask), cpu_online_mask); + if (!include_self) + cpumask_clear_cpu(cpu, to_cpumask(backtrace_mask)); + + if (!cpumask_empty(to_cpumask(backtrace_mask))) { + pr_info("Sending FIQ to %s CPUs:\n", + (include_self ? "all" : "other")); + smp_cross_call(to_cpumask(backtrace_mask), IPI_CPU_BACKTRACE); + } + + /* Wait for up to 10 seconds for all CPUs to do the backtrace */ + for (i = 0; i < 10 * 1000; i++) { + if (cpumask_empty(to_cpumask(backtrace_mask))) + break; + + mdelay(1); + } + + clear_bit(0, &backtrace_flag); + smp_mb__after_atomic(); + put_cpu(); +}
Previous changes have introduced a replacement default FIQ handler and an implementation of arch_trigger_all_cpu_backtrace for ARM but these are currently independant.
This patch plumbs together these features making it possible, on platforms that support it, to trigger backtrace using FIQ.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- arch/arm/include/asm/smp.h | 3 +++ arch/arm/kernel/smp.c | 4 +++- arch/arm/kernel/traps.c | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/arch/arm/include/asm/smp.h b/arch/arm/include/asm/smp.h index 2ec765c..0580270 100644 --- a/arch/arm/include/asm/smp.h +++ b/arch/arm/include/asm/smp.h @@ -18,6 +18,8 @@ # error "<asm/smp.h> included in non-SMP build" #endif
+#define SMP_IPI_FIQ_MASK 0x0100 + #define raw_smp_processor_id() (current_thread_info()->cpu)
struct seq_file; @@ -85,6 +87,7 @@ extern void arch_send_call_function_single_ipi(int cpu); extern void arch_send_call_function_ipi_mask(const struct cpumask *mask); extern void arch_send_wakeup_ipi_mask(const struct cpumask *mask);
+extern void ipi_cpu_backtrace(struct pt_regs *regs); extern int register_ipi_completion(struct completion *completion, int cpu);
struct smp_operations { diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 94959f9..7a79d11 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -543,7 +543,7 @@ static void ipi_cpu_stop(unsigned int cpu) cpu_relax(); }
-static void ipi_cpu_backtrace(struct pt_regs *regs) +void ipi_cpu_backtrace(struct pt_regs *regs) { int cpu = smp_processor_id();
@@ -584,6 +584,8 @@ void handle_IPI(int ipinr, struct pt_regs *regs) unsigned int cpu = smp_processor_id(); struct pt_regs *old_regs = set_irq_regs(regs);
+ BUILD_BUG_ON(SMP_IPI_FIQ_MASK != BIT(IPI_CPU_BACKTRACE)); + if ((unsigned)ipinr < NR_IPI) { trace_ipi_entry(ipi_types[ipinr]); __inc_irq_stat(cpu, ipi_irqs[ipinr]); diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index 92c4ea1..40b1de7 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c @@ -483,6 +483,9 @@ asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs) #ifdef CONFIG_ARM_GIC gic_handle_fiq_ipi(); #endif +#ifdef CONFIG_SMP + ipi_cpu_backtrace(regs); +#endif
nmi_exit();
All GIC hardware except GICv1-without-TrustZone support provides a means to group exceptions into group 0 (which can optionally be signally using use FIQ) and group 1. The kernel currently provides no means to exploit this. This patch alters the initialization of the GIC to place all interrupts into group 1 which is the foundational requirement to meaningfully use FIQ.
Note that the hardware functionality is unavailable to the kernel when a secure monitor is present because access to the grouping registers are prohibited outside "secure world" (this feature allows grouping to be used to allow hardware peripherals to send interrupts into the secure world). The GIC driver will automatically detect this and disable its attempts to group interrupts.
On systems without TrustZone support the kernel has the power to route interrupt sources to FIQ, potentially allowing a driver to exploit the NMI-like properties of FIQ.
Tested on Freescale i.MX6 (quad A9), STiH416 (dual A9) and a self-written qemu GICv2 model.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com Cc: Marc Zyngier marc.zyngier@arm.com Acked-by: Dirk Behme dirk.behme@de.bosch.com --- drivers/irqchip/irq-gic.c | 99 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 5 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 4b959e6..423707c 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -41,6 +41,9 @@ #include <linux/irqchip/arm-gic.h>
#include <asm/cputype.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> @@ -68,6 +71,9 @@ struct gic_chip_data { #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif +#ifdef CONFIG_FIQ + bool fiq_enable; +#endif };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); @@ -131,6 +137,16 @@ static inline void gic_set_base_accessor(struct gic_chip_data *data, #define gic_set_base_accessor(d, f) #endif
+#ifdef CONFIG_FIQ +static inline bool gic_data_fiq_enable(struct gic_chip_data *data) +{ + return data->fiq_enable; +} +#else +static inline bool gic_data_fiq_enable( + struct gic_chip_data *data) { return false; } +#endif + static inline void __iomem *gic_dist_base(struct irq_data *d) { struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d); @@ -325,6 +341,42 @@ static struct irq_chip gic_chip = { .irq_set_wake = gic_set_wake, };
+#ifdef CONFIG_FIQ +static void __init gic_init_fiq(struct gic_chip_data *gic, + irq_hw_number_t first_irq, + unsigned int num_irqs) +{ + void __iomem *dist_base = gic_data_dist_base(gic_data); + unsigned int i; + + /* + * FIQ can only be supported on platforms without an extended irq_eoi + * method (otherwise we take a lock during eoi handling). + */ + if (gic_arch_extn.irq_eoi) + return; + + /* + * If grouping is not available (not implemented or prohibited by + * security mode) these registers a read-as-zero/write-ignored. + * However as a precaution we restore the reset default regardless of + * the result of the test. + */ + writel_relaxed(1, dist_base + GIC_DIST_IGROUP + 0); + gic->fiq_enable = readl_relaxed(dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0, dist_base + GIC_DIST_IGROUP + 0); + pr_debug("gic: FIQ support %s\n", + gic->fiq_enable ? "enabled" : "disabled"); + + if (!gic->fiq_enable) + return; +} +#else /* CONFIG_FIQ */ +static inline void gic_init_fiq(struct gic_chip_data *gic, + irq_hw_number_t first_irq, + unsigned int num_irqs) {} +#endif /* CONFIG_FIQ */ + void __init gic_cascade_irq(unsigned int gic_nr, unsigned int irq) { if (gic_nr >= MAX_GIC_NR) @@ -373,7 +425,22 @@ static void __init gic_dist_init(struct gic_chip_data *gic)
gic_dist_config(base, gic_irqs, NULL);
- writel_relaxed(1, base + GIC_DIST_CTRL); + /* + * Optionally set all global interrupts to be group 1. + */ + if (gic_data_fiq_enable(gic)) + for (i = 32; i < gic_irqs; i += 32) + writel_relaxed(0xffffffff, + base + GIC_DIST_IGROUP + i * 4 / 32); + + /* + * Set EnableGrp1/EnableGrp0 (bit 1 and 0) or EnableGrp (bit 0 only, + * bit 1 ignored) + */ + if (gic_data_fiq_enable(gic)) + writel_relaxed(3, base + GIC_DIST_CTRL); + else + writel_relaxed(1, base + GIC_DIST_CTRL); }
static void gic_cpu_init(struct gic_chip_data *gic) @@ -400,8 +467,20 @@ static void gic_cpu_init(struct gic_chip_data *gic)
gic_cpu_config(dist_base, NULL);
+ /* + * Set all PPI and SGI interrupts to be group 1. + * + * If grouping is not available (not implemented or prohibited by + * security mode) these registers are read-as-zero/write-ignored. + */ + if (gic_data_fiq_enable(gic)) + writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + if (gic_data_fiq_enable(gic)) + writel_relaxed(0x1f, base + GIC_CPU_CTRL); + else + writel_relaxed(1, base + GIC_CPU_CTRL); }
void gic_cpu_if_down(void) @@ -485,7 +564,10 @@ static void gic_dist_restore(unsigned int gic_nr) writel_relaxed(gic_data[gic_nr].saved_spi_enable[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
- writel_relaxed(1, dist_base + GIC_DIST_CTRL); + if (gic_data_fiq_enable(&gic_data[gic_nr])) + writel_relaxed(3, dist_base + GIC_DIST_CTRL); + else + writel_relaxed(1, dist_base + GIC_DIST_CTRL); }
static void gic_cpu_save(unsigned int gic_nr) @@ -542,7 +624,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + writel_relaxed(0x1f, cpu_base + GIC_CPU_CTRL); }
static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -604,6 +686,7 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) { int cpu; unsigned long flags, map = 0; + unsigned long softint;
raw_spin_lock_irqsave(&irq_controller_lock, flags);
@@ -618,7 +701,11 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) dmb(ishst);
/* this always happens on GIC0 */ - writel_relaxed(map << 16 | irq, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT); + softint = map << 16 | irq; + if (gic_data_fiq_enable(&gic_data[0])) + softint |= 0x8000; + writel_relaxed(softint, + gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } @@ -964,6 +1051,8 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start,
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, hwirq_base, &gic_irq_domain_ops, gic); + + gic_init_fiq(gic, irq_base, gic_irqs); } else { gic->domain = irq_domain_add_linear(node, nr_routable_irqs, &gic_irq_domain_ops,
This patch introduces callbacks to route interrupts to or away from the FIQ signal and registers these callbacks with the FIQ infrastructure (if the device can supports it).
Both these aspects combine and allow a driver to deploy a FIQ handler without any machine specific knowledge; it can be used effectively on multi-platform kernels.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Nicolas Pitre nicolas.pitre@linaro.org Cc: Christoffer Dall christoffer.dall@linaro.org Cc: Sricharan R r.sricharan@ti.com --- drivers/irqchip/irq-gic.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 423707c..6fa0542 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -342,6 +342,69 @@ static struct irq_chip gic_chip = { };
#ifdef CONFIG_FIQ +/* + * Shift an interrupt between Group 0 and Group 1. + * + * In addition to changing the group we also modify the priority to + * match what "ARM strongly recommends" for a system where no Group 1 + * interrupt must ever preempt a Group 0 interrupt. + */ +static void gic_set_group_irq(struct irq_data *d, int group) +{ + unsigned int grp_reg = gic_irq(d) / 32 * 4; + u32 grp_mask = 1 << (gic_irq(d) % 32); + u32 grp_val; + + unsigned int pri_reg = (gic_irq(d) / 4) * 4; + u32 pri_mask = 1 << (7 + ((gic_irq(d) % 4) * 8)); + u32 pri_val; + + raw_spin_lock(&irq_controller_lock); + + grp_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + + if (group) { + grp_val |= grp_mask; + pri_val |= pri_mask; + } else { + grp_val &= ~grp_mask; + pri_val &= ~pri_mask; + } + + writel_relaxed(grp_val, gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + + raw_spin_unlock(&irq_controller_lock); +} + +static void gic_enable_fiq(struct irq_data *d) +{ + gic_set_group_irq(d, 0); +} + +static void gic_disable_fiq(struct irq_data *d) +{ + gic_set_group_irq(d, 1); +} + +static int gic_ack_fiq(struct irq_data *d) +{ + struct gic_chip_data *gic = irq_data_get_irq_chip_data(d); + u32 irqstat, irqnr; + + irqstat = readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK); + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + return irq_find_mapping(gic->domain, irqnr); +} + +static struct fiq_chip gic_fiq = { + .fiq_enable = gic_enable_fiq, + .fiq_disable = gic_disable_fiq, + .fiq_ack = gic_ack_fiq, + .fiq_eoi = gic_eoi_irq, +}; + static void __init gic_init_fiq(struct gic_chip_data *gic, irq_hw_number_t first_irq, unsigned int num_irqs) @@ -370,6 +433,12 @@ static void __init gic_init_fiq(struct gic_chip_data *gic,
if (!gic->fiq_enable) return; + + /* + * FIQ is supported on this device! Register our chip data. + */ + for (i = 0; i < num_irqs; i++) + fiq_register_mapping(first_irq + i, &gic_fiq); } #else /* CONFIG_FIQ */ static inline void gic_init_fiq(struct gic_chip_data *gic,
This patch is motivated by the comment it removes from gic_init_fiq, namely that the spin locks in eoi_irq preclude certain platforms from supporting FIQ.
Currently there is only one upstream platform (tegra) that actually hooks gic_arch_extn.irq_eoi and it does not require these spin locks.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: Peter De Schrijver pdeschrijver@nvidia.com --- drivers/irqchip/irq-gic.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 6fa0542..d928912 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -191,11 +191,8 @@ static void gic_unmask_irq(struct irq_data *d)
static void gic_eoi_irq(struct irq_data *d) { - if (gic_arch_extn.irq_eoi) { - raw_spin_lock(&irq_controller_lock); + if (gic_arch_extn.irq_eoi) gic_arch_extn.irq_eoi(d); - raw_spin_unlock(&irq_controller_lock); - }
writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI); } @@ -413,13 +410,6 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, unsigned int i;
/* - * FIQ can only be supported on platforms without an extended irq_eoi - * method (otherwise we take a lock during eoi handling). - */ - if (gic_arch_extn.irq_eoi) - return; - - /* * If grouping is not available (not implemented or prohibited by * security mode) these registers a read-as-zero/write-ignored. * However as a precaution we restore the reset default regardless of
To support IPI FIQ we alter gic_cpu_init() to honour SMP_IPI_FIQ_MASK and register a fairly high priority notifier to acknowledge and clear the IPI when it is triggered.
For the IPI FIQ to be useful we must also make it safe to call gic_raise_softirq() from the FIQ handler by altering the locking strategy slightly.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- drivers/irqchip/irq-gic.c | 123 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 102 insertions(+), 21 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index d928912..3fa824e 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -39,6 +39,7 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/ratelimit.h>
#include <asm/cputype.h> #ifdef CONFIG_FIQ @@ -51,6 +52,10 @@ #include "irq-gic-common.h" #include "irqchip.h"
+#ifndef SMP_IPI_FIQ_MASK +#define SMP_IPI_FIQ_MASK 0 +#endif + union gic_base { void __iomem *common_base; void __percpu * __iomem *percpu_base; @@ -77,6 +82,8 @@ struct gic_chip_data { };
static DEFINE_RAW_SPINLOCK(irq_controller_lock); +/* A fiq-safe spinlock must only be locked when the FIQ is masked */ +static DEFINE_RAW_SPINLOCK(fiq_safe_migration_lock);
/* * The GIC mapping of CPU interfaces does not necessarily match @@ -346,20 +353,21 @@ static struct irq_chip gic_chip = { * match what "ARM strongly recommends" for a system where no Group 1 * interrupt must ever preempt a Group 0 interrupt. */ -static void gic_set_group_irq(struct irq_data *d, int group) +static void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) { - unsigned int grp_reg = gic_irq(d) / 32 * 4; - u32 grp_mask = 1 << (gic_irq(d) % 32); + unsigned int grp_reg = hwirq / 32 * 4; + u32 grp_mask = 1 << (hwirq % 32); u32 grp_val;
- unsigned int pri_reg = (gic_irq(d) / 4) * 4; - u32 pri_mask = 1 << (7 + ((gic_irq(d) % 4) * 8)); + unsigned int pri_reg = (hwirq / 4) * 4; + u32 pri_mask = 1 << (7 + ((hwirq % 4) * 8)); u32 pri_val;
raw_spin_lock(&irq_controller_lock);
- grp_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); - pri_val = readl_relaxed(gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + grp_val = readl_relaxed(base + GIC_DIST_IGROUP + grp_reg); + pri_val = readl_relaxed(base + GIC_DIST_PRI + pri_reg);
if (group) { grp_val |= grp_mask; @@ -369,20 +377,20 @@ static void gic_set_group_irq(struct irq_data *d, int group) pri_val &= ~pri_mask; }
- writel_relaxed(grp_val, gic_dist_base(d) + GIC_DIST_IGROUP + grp_reg); - writel_relaxed(pri_val, gic_dist_base(d) + GIC_DIST_PRI + pri_reg); + writel_relaxed(grp_val, base + GIC_DIST_IGROUP + grp_reg); + writel_relaxed(pri_val, base + GIC_DIST_PRI + pri_reg);
raw_spin_unlock(&irq_controller_lock); }
static void gic_enable_fiq(struct irq_data *d) { - gic_set_group_irq(d, 0); + gic_set_group_irq(gic_dist_base(d), gic_irq(d), 0); }
static void gic_disable_fiq(struct irq_data *d) { - gic_set_group_irq(d, 1); + gic_set_group_irq(gic_dist_base(d), gic_irq(d), 1); }
static int gic_ack_fiq(struct irq_data *d) @@ -390,8 +398,22 @@ static int gic_ack_fiq(struct irq_data *d) struct gic_chip_data *gic = irq_data_get_irq_chip_data(d); u32 irqstat, irqnr;
- irqstat = readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK); - irqnr = irqstat & GICC_IAR_INT_ID_MASK; + while (1) { + writel_relaxed(0x70, gic_data_cpu_base(gic) + GIC_CPU_PRIMASK); + irqstat = + readl_relaxed(gic_data_cpu_base(gic) + GIC_CPU_INTACK); + writel_relaxed(0xf0, gic_data_cpu_base(gic) + GIC_CPU_PRIMASK); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + if (irqnr > 15) + break; + + /* we've got an IPI which we can simply acknowledge + * and move on + */ + gic_eoi_irq(d); + } + return irq_find_mapping(gic->domain, irqnr); }
@@ -430,7 +452,43 @@ static void __init gic_init_fiq(struct gic_chip_data *gic, for (i = 0; i < num_irqs; i++) fiq_register_mapping(first_irq + i, &gic_fiq); } + +/* + * Fully acknowledge (both ack and eoi) a FIQ-based IPI + */ +static int gic_handle_fiq_ipi(struct notifier_block *nb, unsigned long regs, + void *data) +{ + struct gic_chip_data *gic = &gic_data[0]; + void __iomem *cpu_base = gic_data_cpu_base(gic); + unsigned long irqstat, irqnr; + + if (WARN_ON(!in_nmi())) + return NOTIFY_BAD; + + while ((1u << readl_relaxed(cpu_base + GIC_CPU_HIGHPRI)) & + SMP_IPI_FIQ_MASK) { + irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK); + writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + + irqnr = irqstat & GICC_IAR_INT_ID_MASK; + WARN_RATELIMIT(irqnr > 16, + "Unexpected irqnr %lu (bad prioritization?)\n", + irqnr); + } + + return NOTIFY_OK; +} + +/* + * Notifier to ensure IPI FIQ is acknowledged correctly. + */ +static struct notifier_block gic_fiq_ipi_notifier = { + .notifier_call = gic_handle_fiq_ipi, +}; #else /* CONFIG_FIQ */ +static inline void gic_set_group_irq(void __iomem *base, unsigned int hwirq, + int group) {} static inline void gic_init_fiq(struct gic_chip_data *gic, irq_hw_number_t first_irq, unsigned int num_irqs) {} @@ -527,14 +585,19 @@ static void gic_cpu_init(struct gic_chip_data *gic) gic_cpu_config(dist_base, NULL);
/* - * Set all PPI and SGI interrupts to be group 1. - * - * If grouping is not available (not implemented or prohibited by - * security mode) these registers are read-as-zero/write-ignored. + * Optionally set all PPI and SGI interrupts to be group 1. */ if (gic_data_fiq_enable(gic)) writel_relaxed(0xffffffff, dist_base + GIC_DIST_IGROUP + 0);
+ /* + * Optionally shift the FIQ based IPIs to group 0. + */ + if (gic_data_fiq_enable(gic)) + for (i = 0; i < 16; i++) + if (SMP_IPI_FIQ_MASK & (1 << i)) + gic_set_group_irq(dist_base, i, 0); + writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); if (gic_data_fiq_enable(gic)) writel_relaxed(0x1f, base + GIC_CPU_CTRL); @@ -747,7 +810,17 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq) unsigned long flags, map = 0; unsigned long softint;
- raw_spin_lock_irqsave(&irq_controller_lock, flags); + /* + * The locking in this function ensures we don't use stale cpu mappings + * and thus we never route an IPI to the wrong physical core during a + * big.LITTLE switch. The switch code takes both of these locks meaning + * we can choose whichever lock is safe to use from our current calling + * context. + */ + if (in_nmi()) + raw_spin_lock(&fiq_safe_migration_lock); + else + raw_spin_lock_irqsave(&irq_controller_lock, flags);
/* Convert our logical CPU mask into a physical one. */ for_each_cpu(cpu, mask) @@ -761,12 +834,16 @@ static void gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
/* this always happens on GIC0 */ softint = map << 16 | irq; - if (gic_data_fiq_enable(&gic_data[0])) + if (gic_data_fiq_enable(&gic_data[0]) && + !(SMP_IPI_FIQ_MASK & (1 << irq))) softint |= 0x8000; writel_relaxed(softint, gic_data_dist_base(&gic_data[0]) + GIC_DIST_SOFTINT);
- raw_spin_unlock_irqrestore(&irq_controller_lock, flags); + if (in_nmi()) + raw_spin_unlock(&fiq_safe_migration_lock); + else + raw_spin_unlock_irqrestore(&irq_controller_lock, flags); } #endif
@@ -814,7 +891,7 @@ int gic_get_cpu_id(unsigned int cpu) * Migrate all peripheral interrupts with a target matching the current CPU * to the interface corresponding to @new_cpu_id. The CPU interface mapping * is also updated. Targets to other CPU interfaces are unchanged. - * This must be called with IRQs locally disabled. + * This must be called with IRQ and FIQ locally disabled. */ void gic_migrate_target(unsigned int new_cpu_id) { @@ -836,6 +913,7 @@ void gic_migrate_target(unsigned int new_cpu_id) ror_val = (cur_cpu_id - new_cpu_id) & 31;
raw_spin_lock(&irq_controller_lock); + raw_spin_lock(&fiq_safe_migration_lock);
/* Update the target interface for this logical CPU */ gic_cpu_map[cpu] = 1 << new_cpu_id; @@ -855,6 +933,7 @@ void gic_migrate_target(unsigned int new_cpu_id) } }
+ raw_spin_unlock(&fiq_safe_migration_lock); raw_spin_unlock(&irq_controller_lock);
/* @@ -1125,6 +1204,8 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, #ifdef CONFIG_SMP set_smp_cross_call(gic_raise_softirq); register_cpu_notifier(&gic_cpu_notifier); + if (gic_data_fiq_enable(gic)) + register_fiq_nmi_notifier(&gic_fiq_ipi_notifier); #endif set_handle_irq(gic_handle_irq); }
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 --- drivers/irqchip/irq-gic.c | 52 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-)
diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 3fa824e..a670e72 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -279,14 +279,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 (!gic_data_fiq_enable(gic) || 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)) { @@ -295,7 +340,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
This patch introduces callbacks to route interrupts to or away from the FIQ signal. It also causes these callbacks to be registered with the FIQ infrastructure.
This patch enable FIQ support for mach-versatile whilst mach-ep93xx, mach-netx, mach-s3c64xx and plat-samsung are unmodified (and can therefore continue to use init_FIQ() as before).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Hartley Sweeten hsweeten@visionengravers.com Cc: Ryan Mallon rmallon@gmail.com Cc: Russell King linux@arm.linux.org.uk Cc: Ben Dooks ben-linux@fluff.org Cc: Kukjin Kim kgene.kim@samsung.com Cc: Thomas Gleixner tglx@linutronix.de Cc: Jason Cooper jason@lakedaemon.net Cc: linux-samsung-soc@vger.kernel.org --- arch/arm/mach-versatile/core.c | 2 +- drivers/irqchip/irq-vic.c | 92 ++++++++++++++++++++++++++++++++--------- include/linux/irqchip/arm-vic.h | 6 ++- 3 files changed, 78 insertions(+), 22 deletions(-)
diff --git a/arch/arm/mach-versatile/core.c b/arch/arm/mach-versatile/core.c index 08fb8c8..bad1d30 100644 --- a/arch/arm/mach-versatile/core.c +++ b/arch/arm/mach-versatile/core.c @@ -108,7 +108,7 @@ void __init versatile_init_irq(void)
np = of_find_matching_node_by_address(NULL, vic_of_match, VERSATILE_VIC_BASE); - __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np); + __vic_init(VA_VIC_BASE, 0, IRQ_VIC_START, ~0, 0, np ? false : true, np);
writel(~0, VA_SIC_BASE + SIC_IRQ_ENABLE_CLEAR);
diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index 7d35287..22aa126 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -36,6 +36,9 @@
#include <asm/exception.h> #include <asm/irq.h> +#ifdef CONFIG_FIQ +#include <asm/fiq.h> +#endif
#include "irqchip.h"
@@ -261,11 +264,53 @@ static struct irq_domain_ops vic_irqdomain_ops = { .xlate = irq_domain_xlate_onetwocell, };
+#ifdef CONFIG_FIQ +static DEFINE_RAW_SPINLOCK(irq_controller_lock); + +static void vic_set_fiq(struct irq_data *d, bool enable) +{ + void __iomem *base = irq_data_get_irq_chip_data(d); + unsigned int irq = d->hwirq; + u32 val; + + raw_spin_lock(&irq_controller_lock); + val = readl(base + VIC_INT_SELECT); + if (enable) + val |= 1 << irq; + else + val &= ~(1 << irq); + writel(val, base + VIC_INT_SELECT); + raw_spin_unlock(&irq_controller_lock); +} + +static void vic_enable_fiq(struct irq_data *d) +{ + vic_set_fiq(d, true); +} + +static void vic_disable_fiq(struct irq_data *d) +{ + vic_set_fiq(d, false); +} + +struct fiq_chip vic_fiq = { + .fiq_enable = vic_enable_fiq, + .fiq_disable = vic_disable_fiq, +}; + +static void vic_register_fiq(int irq) +{ + fiq_register_mapping(irq, &vic_fiq); +} +#else /* CONFIG_FIQ */ +static inline void vic_register_fiq(int irq) {} +#endif /* CONFIG_FIQ */ + /** * vic_register() - Register a VIC. * @base: The base address of the VIC. * @parent_irq: The parent IRQ if cascaded, else 0. - * @irq: The base IRQ for the VIC. + * @irq_start: The base IRQ for the VIC. * @valid_sources: bitmask of valid interrupts * @resume_sources: bitmask of interrupts allowed for resume sources. * @node: The device tree node associated with the VIC. @@ -277,12 +322,13 @@ static struct irq_domain_ops vic_irqdomain_ops = { * This also configures the IRQ domain for the VIC. */ static void __init vic_register(void __iomem *base, unsigned int parent_irq, - unsigned int irq, + unsigned int irq_start, u32 valid_sources, u32 resume_sources, - struct device_node *node) + bool map_fiqs, struct device_node *node) { struct vic_device *v; int i; + unsigned int irq;
if (vic_id >= ARRAY_SIZE(vic_devices)) { printk(KERN_ERR "%s: too few VICs, increase CONFIG_ARM_VIC_NR\n", __func__); @@ -301,15 +347,19 @@ static void __init vic_register(void __iomem *base, unsigned int parent_irq, irq_set_chained_handler(parent_irq, vic_handle_irq_cascaded); }
- v->domain = irq_domain_add_simple(node, fls(valid_sources), irq, + v->domain = irq_domain_add_simple(node, fls(valid_sources), irq_start, &vic_irqdomain_ops, v); /* create an IRQ mapping for each valid IRQ */ - for (i = 0; i < fls(valid_sources); i++) - if (valid_sources & (1 << i)) - irq_create_mapping(v->domain, i); + for (i = 0; i < fls(valid_sources); i++) { + if (valid_sources & (1 << i)) { + irq = irq_create_mapping(v->domain, i); + vic_register_fiq(irq); + } + } + /* If no base IRQ was passed, figure out our allocated base */ - if (irq) - v->irq = irq; + if (irq_start) + v->irq = irq_start; else v->irq = irq_find_mapping(v->domain, 0); } @@ -413,7 +463,8 @@ static void __init vic_clear_interrupts(void __iomem *base) * and 020 within the page. We call this "second block". */ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, - u32 vic_sources, struct device_node *node) + u32 vic_sources, bool map_fiqs, + struct device_node *node) { unsigned int i; int vic_2nd_block = ((unsigned long)base & ~PAGE_MASK) != 0; @@ -439,12 +490,12 @@ static void __init vic_init_st(void __iomem *base, unsigned int irq_start, writel(32, base + VIC_PL190_DEF_VECT_ADDR); }
- vic_register(base, 0, irq_start, vic_sources, 0, node); + vic_register(base, 0, irq_start, vic_sources, 0, map_fiqs, node); }
void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, - struct device_node *node) + u32 vic_sources, u32 resume_sources, + bool map_fiqs, struct device_node *node) { unsigned int i; u32 cellid = 0; @@ -462,7 +513,7 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
switch(vendor) { case AMBA_VENDOR_ST: - vic_init_st(base, irq_start, vic_sources, node); + vic_init_st(base, irq_start, vic_sources, map_fiqs, node); return; default: printk(KERN_WARNING "VIC: unknown vendor, continuing anyways\n"); @@ -479,7 +530,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start,
vic_init2(base);
- vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, node); + vic_register(base, parent_irq, irq_start, vic_sources, resume_sources, + map_fiqs, node); }
/** @@ -492,7 +544,8 @@ void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, void __init vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources) { - __vic_init(base, 0, irq_start, vic_sources, resume_sources, NULL); + __vic_init(base, 0, irq_start, vic_sources, resume_sources, + false, NULL); }
/** @@ -511,7 +564,8 @@ int __init vic_init_cascaded(void __iomem *base, unsigned int parent_irq, struct vic_device *v;
v = &vic_devices[vic_id]; - __vic_init(base, parent_irq, 0, vic_sources, resume_sources, NULL); + __vic_init(base, parent_irq, 0, vic_sources, resume_sources, false, + NULL); /* Return out acquired base */ return v->irq; } @@ -535,9 +589,9 @@ int __init vic_of_init(struct device_node *node, struct device_node *parent) of_property_read_u32(node, "valid-wakeup-mask", &wakeup_mask);
/* - * Passing 0 as first IRQ makes the simple domain allocate descriptors + * Passing 0 as first IRQ makes the domain allocate descriptors. */ - __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, node); + __vic_init(regs, 0, 0, interrupt_mask, wakeup_mask, true, node);
return 0; } diff --git a/include/linux/irqchip/arm-vic.h b/include/linux/irqchip/arm-vic.h index ba46c79..30ab39f 100644 --- a/include/linux/irqchip/arm-vic.h +++ b/include/linux/irqchip/arm-vic.h @@ -30,8 +30,10 @@ struct device_node; struct pt_regs;
void __vic_init(void __iomem *base, int parent_irq, int irq_start, - u32 vic_sources, u32 resume_sources, struct device_node *node); -void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, u32 resume_sources); + u32 vic_sources, u32 resume_sources, + bool map_fiqs, struct device_node *node); +void vic_init(void __iomem *base, unsigned int irq_start, u32 vic_sources, + u32 resume_sources); int vic_init_cascaded(void __iomem *base, unsigned int parent_irq, u32 vic_sources, u32 resume_sources);
Speculatively register a FIQ resource with KGDB. KGDB will only accept it if the kgdb/fiq feature is enabled (both with compile time and runtime switches) and the interrupt controller supports FIQ.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the amba-pl011 driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/amba-pl011.c | 94 ++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 39 deletions(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 8572f2a..ec8ddc7 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -58,6 +58,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/sizes.h> #include <linux/io.h> +#include <linux/kgdb.h>
#define UART_NR 14
@@ -1416,8 +1417,61 @@ static void pl011_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&uap->port.lock, flags); }
+static int pl011_hwinit(struct uart_port *port) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + int retval; + + /* Optionaly enable pins to be muxed in and configured */ + pinctrl_pm_select_default_state(port->dev); + + /* + * Try to enable the clock producer. + */ + retval = clk_prepare_enable(uap->clk); + if (retval) + return retval; + + uap->port.uartclk = clk_get_rate(uap->clk); + + /* Clear pending error and receive interrupts */ + writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | + UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); + + /* + * Save interrupts enable mask, and enable RX interrupts in case if + * the interrupt is used for NMI entry. + */ + uap->im = readw(uap->port.membase + UART011_IMSC); + writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); + + if (dev_get_platdata(uap->port.dev)) { + struct amba_pl011_data *plat; + + plat = dev_get_platdata(uap->port.dev); + if (plat->init) + plat->init(); + } + return 0; +} + #ifdef CONFIG_CONSOLE_POLL
+static int pl011_poll_init(struct uart_port *port) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + int retval; + + retval = pl011_hwinit(port); + +#ifdef CONFIG_KGDB_FIQ + if (retval == 0) + kgdb_register_fiq(uap->port.irq); +#endif + + return retval; +} + static void pl011_quiesce_irqs(struct uart_port *port) { struct uart_amba_port *uap = (struct uart_amba_port *)port; @@ -1471,44 +1525,6 @@ static void pl011_put_poll_char(struct uart_port *port,
#endif /* CONFIG_CONSOLE_POLL */
-static int pl011_hwinit(struct uart_port *port) -{ - struct uart_amba_port *uap = (struct uart_amba_port *)port; - int retval; - - /* Optionaly enable pins to be muxed in and configured */ - pinctrl_pm_select_default_state(port->dev); - - /* - * Try to enable the clock producer. - */ - retval = clk_prepare_enable(uap->clk); - if (retval) - return retval; - - uap->port.uartclk = clk_get_rate(uap->clk); - - /* Clear pending error and receive interrupts */ - writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS | - UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR); - - /* - * Save interrupts enable mask, and enable RX interrupts in case if - * the interrupt is used for NMI entry. - */ - uap->im = readw(uap->port.membase + UART011_IMSC); - writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC); - - if (dev_get_platdata(uap->port.dev)) { - struct amba_pl011_data *plat; - - plat = dev_get_platdata(uap->port.dev); - if (plat->init) - plat->init(); - } - return 0; -} - static void pl011_write_lcr_h(struct uart_amba_port *uap, unsigned int lcr_h) { writew(lcr_h, uap->port.membase + uap->lcrh_rx); @@ -1888,7 +1904,7 @@ static struct uart_ops amba_pl011_pops = { .config_port = pl011_config_port, .verify_port = pl011_verify_port, #ifdef CONFIG_CONSOLE_POLL - .poll_init = pl011_hwinit, + .poll_init = pl011_poll_init, .poll_get_char = pl011_get_poll_char, .poll_put_char = pl011_put_poll_char, #endif
Hi Daniel,
On 08/18/2014 10:28 AM, Daniel Thompson wrote:
Speculatively register a FIQ resource with KGDB. KGDB will only accept it if the kgdb/fiq feature is enabled (both with compile time and runtime switches) and the interrupt controller supports FIQ.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the amba-pl011 driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org
drivers/tty/serial/amba-pl011.c | 94 ++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 39 deletions(-)
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 8572f2a..ec8ddc7 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -58,6 +58,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/sizes.h> #include <linux/io.h> +#include <linux/kgdb.h> #define UART_NR 14 @@ -1416,8 +1417,61 @@ static void pl011_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&uap->port.lock, flags); }
Is pl011_hwinit() just being relocated in source to avoid the forward declaration? If so, this is usually split into its own commit.
+static int pl011_hwinit(struct uart_port *port) +{
- struct uart_amba_port *uap = (struct uart_amba_port *)port;
- int retval;
- /* Optionaly enable pins to be muxed in and configured */
- pinctrl_pm_select_default_state(port->dev);
- /*
* Try to enable the clock producer.
*/
- retval = clk_prepare_enable(uap->clk);
- if (retval)
return retval;
- uap->port.uartclk = clk_get_rate(uap->clk);
- /* Clear pending error and receive interrupts */
- writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS |
UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR);
- /*
* Save interrupts enable mask, and enable RX interrupts in case if
* the interrupt is used for NMI entry.
*/
- uap->im = readw(uap->port.membase + UART011_IMSC);
- writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC);
- if (dev_get_platdata(uap->port.dev)) {
struct amba_pl011_data *plat;
plat = dev_get_platdata(uap->port.dev);
if (plat->init)
plat->init();
- }
- return 0;
+}
#ifdef CONFIG_CONSOLE_POLL +static int pl011_poll_init(struct uart_port *port) +{
- struct uart_amba_port *uap = (struct uart_amba_port *)port;
Please use container_of() in new code.
- int retval;
- retval = pl011_hwinit(port);
+#ifdef CONFIG_KGDB_FIQ
- if (retval == 0)
kgdb_register_fiq(uap->port.irq);
The uap->port dereference is unnecessary since the port parameter is the same thing.
kgdb_register_fiq(port->irq);
Regards, Peter Hurley
+#endif
- return retval;
+}
static void pl011_quiesce_irqs(struct uart_port *port) { struct uart_amba_port *uap = (struct uart_amba_port *)port; @@ -1471,44 +1525,6 @@ static void pl011_put_poll_char(struct uart_port *port, #endif /* CONFIG_CONSOLE_POLL */ -static int pl011_hwinit(struct uart_port *port) -{
- struct uart_amba_port *uap = (struct uart_amba_port *)port;
- int retval;
- /* Optionaly enable pins to be muxed in and configured */
- pinctrl_pm_select_default_state(port->dev);
- /*
* Try to enable the clock producer.
*/
- retval = clk_prepare_enable(uap->clk);
- if (retval)
return retval;
- uap->port.uartclk = clk_get_rate(uap->clk);
- /* Clear pending error and receive interrupts */
- writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS |
UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR);
- /*
* Save interrupts enable mask, and enable RX interrupts in case if
* the interrupt is used for NMI entry.
*/
- uap->im = readw(uap->port.membase + UART011_IMSC);
- writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC);
- if (dev_get_platdata(uap->port.dev)) {
struct amba_pl011_data *plat;
plat = dev_get_platdata(uap->port.dev);
if (plat->init)
plat->init();
- }
- return 0;
-}
static void pl011_write_lcr_h(struct uart_amba_port *uap, unsigned int lcr_h) { writew(lcr_h, uap->port.membase + uap->lcrh_rx); @@ -1888,7 +1904,7 @@ static struct uart_ops amba_pl011_pops = { .config_port = pl011_config_port, .verify_port = pl011_verify_port, #ifdef CONFIG_CONSOLE_POLL
- .poll_init = pl011_hwinit,
- .poll_init = pl011_poll_init, .poll_get_char = pl011_get_poll_char, .poll_put_char = pl011_put_poll_char,
#endif
On 18/08/14 19:30, Peter Hurley wrote:
Hi Daniel,
On 08/18/2014 10:28 AM, Daniel Thompson wrote:
Speculatively register a FIQ resource with KGDB. KGDB will only accept it if the kgdb/fiq feature is enabled (both with compile time and runtime switches) and the interrupt controller supports FIQ.
By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user). This permission also implies the amba-pl011 driver has already unmasked RX interrupts (otherwise the FIQ handler will never trigger).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Russell King linux@arm.linux.org.uk Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org
drivers/tty/serial/amba-pl011.c | 94 ++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 39 deletions(-)#
Thanks for the review.
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 8572f2a..ec8ddc7 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -58,6 +58,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/sizes.h> #include <linux/io.h> +#include <linux/kgdb.h> #define UART_NR 14 @@ -1416,8 +1417,61 @@ static void pl011_break_ctl(struct uart_port *port, int break_state) spin_unlock_irqrestore(&uap->port.lock, flags); }
Is pl011_hwinit() just being relocated in source to avoid the forward declaration? If so, this is usually split into its own commit.
Ok. I'll do this.
+static int pl011_hwinit(struct uart_port *port) +{
- struct uart_amba_port *uap = (struct uart_amba_port *)port;
- int retval;
- /* Optionaly enable pins to be muxed in and configured */
- pinctrl_pm_select_default_state(port->dev);
- /*
* Try to enable the clock producer.
*/
- retval = clk_prepare_enable(uap->clk);
- if (retval)
return retval;
- uap->port.uartclk = clk_get_rate(uap->clk);
- /* Clear pending error and receive interrupts */
- writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS |
UART011_RTIS | UART011_RXIS, uap->port.membase + UART011_ICR);
- /*
* Save interrupts enable mask, and enable RX interrupts in case if
* the interrupt is used for NMI entry.
*/
- uap->im = readw(uap->port.membase + UART011_IMSC);
- writew(UART011_RTIM | UART011_RXIM, uap->port.membase + UART011_IMSC);
- if (dev_get_platdata(uap->port.dev)) {
struct amba_pl011_data *plat;
plat = dev_get_platdata(uap->port.dev);
if (plat->init)
plat->init();
- }
- return 0;
+}
#ifdef CONFIG_CONSOLE_POLL +static int pl011_poll_init(struct uart_port *port) +{
- struct uart_amba_port *uap = (struct uart_amba_port *)port;
Please use container_of() in new code.
Ok.
Personally I dislike a file that mixes casts and conatiner_of but I guess I can make both of us happy by switching the whole driver to container_of. Separate patch again?
- int retval;
- retval = pl011_hwinit(port);
+#ifdef CONFIG_KGDB_FIQ
- if (retval == 0)
kgdb_register_fiq(uap->port.irq);
The uap->port dereference is unnecessary since the port parameter is the same thing.
kgdb_register_fiq(port->irq);
Ok.
On 08/19/2014 05:08 AM, Daniel Thompson wrote:
On 18/08/14 19:30, Peter Hurley wrote:
[cut]
+static int pl011_poll_init(struct uart_port *port) +{
- struct uart_amba_port *uap = (struct uart_amba_port *)port;
Please use container_of() in new code.
Ok.
Personally I dislike a file that mixes casts and conatiner_of but I guess I can make both of us happy by switching the whole driver to container_of. Separate patch again?
The change below makes the uap local unnecessary, so you can skip the container_of() change, if you'd prefer.
- int retval;
- retval = pl011_hwinit(port);
+#ifdef CONFIG_KGDB_FIQ
- if (retval == 0)
kgdb_register_fiq(uap->port.irq);
The uap->port dereference is unnecessary since the port parameter is the same thing.
kgdb_register_fiq(port->irq);
Ok.
Thanks, Peter Hurley
On 19/08/14 12:58, Peter Hurley wrote:
On 08/19/2014 05:08 AM, Daniel Thompson wrote:
On 18/08/14 19:30, Peter Hurley wrote:
[cut]
+static int pl011_poll_init(struct uart_port *port) +{
- struct uart_amba_port *uap = (struct uart_amba_port *)port;
Please use container_of() in new code.
Ok.
Personally I dislike a file that mixes casts and conatiner_of but I guess I can make both of us happy by switching the whole driver to container_of. Separate patch again?
The change below makes the uap local unnecessary, so you can skip the container_of() change, if you'd prefer.
I realized that myself although not until after I'd replaced all the casts with container_of()...
So I'll keep the patch for now but can drop it if anyone takes against it.
Thanks
Daniel.
- int retval;
- retval = pl011_hwinit(port);
+#ifdef CONFIG_KGDB_FIQ
- if (retval == 0)
kgdb_register_fiq(uap->port.irq);
The uap->port dereference is unnecessary since the port parameter is the same thing.
kgdb_register_fiq(port->irq);
Ok.
Thanks, Peter Hurley
Add a .poll_init() function that enables UART RX and registers the UART's irq with KGDB. By providing this information to KGDB the serial driver offers "permission" for KGDB to route the UART interrupt signal from the drivers own handler to KGDBs FIQ handler (which will eventually use the UART's polled I/O callbacks to interact with the user).
Note that the RX is not only enabled but also unmasked. This is required because otherwise the FIQ handler could never trigger. This unmask is copied from similar code in amba-pl011.c .
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/st-asc.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 8b2d735..2b5eb6e 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -30,6 +30,7 @@ #include <linux/of_platform.h> #include <linux/serial_core.h> #include <linux/clk.h> +#include <linux/kgdb.h>
#define DRIVER_NAME "st-asc" #define ASC_SERIAL_NAME "ttyAS" @@ -607,6 +608,25 @@ asc_verify_port(struct uart_port *port, struct serial_struct *ser) }
#ifdef CONFIG_CONSOLE_POLL + +#ifdef CONFIG_KGDB_FIQ +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int asc_poll_init(struct uart_port *port) +{ + struct asc_port *ascport = container_of(port, struct asc_port, port); + + /* register the FIQ with kgdb */ + kgdb_register_fiq(ascport->port.irq); + + /* enable RX interrupts in case the interrupt is used for NMI entry. */ + asc_enable_rx_interrupts(port); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + /* * Console polling routines for writing and reading from the uart while * in an interrupt or debug context (i.e. kgdb). @@ -649,6 +669,9 @@ static struct uart_ops asc_uart_ops = { .verify_port = asc_verify_port, .pm = asc_pm, #ifdef CONFIG_CONSOLE_POLL +#ifdef CONFIG_KGDB_FIQ + .poll_init = asc_poll_init, +#endif /* CONFIG_KGDB_FIQ */ .poll_get_char = asc_get_poll_char, .poll_put_char = asc_put_poll_char, #endif /* CONFIG_CONSOLE_POLL */
The architectures where this peripheral exists (ARM and SH) have expensive implementations of writel(), reliant on spin locks and explicit L2 cache management. These architectures provide a cheaper writel_relaxed() which is much better suited to peripherals that do not perform DMA. The situation with readl()/readl_relaxed()is similar although less acute.
This driver does not use DMA and will be more power efficient and more robust (due to absense of spin locks during console I/O) if it uses the relaxed variants.
This change means the driver is no longer portable and therefore no longer suitable for compile testing.
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Srinivas Kandagatla srinivas.kandagatla@gmail.com Cc: Maxime Coquelin maxime.coquelin@st.com Cc: Patrice Chotard patrice.chotard@st.com Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: kernel@stlinux.com Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/st-asc.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 26cec64..e9b1735 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1527,7 +1527,7 @@ config SERIAL_FSL_LPUART_CONSOLE config SERIAL_ST_ASC tristate "ST ASC serial port support" select SERIAL_CORE - depends on ARM || COMPILE_TEST + depends on ARM help This driver is for the on-chip Asychronous Serial Controller on STMicroelectronics STi SoCs. diff --git a/drivers/tty/serial/st-asc.c b/drivers/tty/serial/st-asc.c index 2b5eb6e..df709ee 100644 --- a/drivers/tty/serial/st-asc.c +++ b/drivers/tty/serial/st-asc.c @@ -152,12 +152,12 @@ static inline struct asc_port *to_asc_port(struct uart_port *port)
static inline u32 asc_in(struct uart_port *port, u32 offset) { - return readl(port->membase + offset); + return readl_relaxed(port->membase + offset); }
static inline void asc_out(struct uart_port *port, u32 offset, u32 value) { - writel(value, port->membase + offset); + writel_relaxed(value, port->membase + offset); }
/*
From: Dirk Behme dirk.behme@de.bosch.com
Looking at the get_poll_char() function of the 8250.c serial driver, we learn:
* poll_get_char() doesn't have to save/disable/restore the interrupt registers. No interrupt handling is needed in this function at all. Remove it.
* Don't block in case there is no data available. So instead blocking in the do {} while loop, just return with NO_POLL_CHAR, immediately .
Additionally, while the i.MX6 register URXD[7-0] contain the RX_DATA, the upper bits of this register (URXD[15-10]) might contain some control flags. To ensure that these are not returned with the data read, just mask out URXD[7-0].
These changes fix the 'hang' working with kdb:
$ echo ttymxc3 > /sys/module/kgdboc/parameters/kgdboc $ echo g >/proc/sysrq-trigger [0]kdb> help ... <hang>
Signed-off-by: Dirk Behme dirk.behme@de.bosch.com Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/imx.c | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 044e86d..983668a 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -80,6 +80,7 @@ #define URXD_FRMERR (1<<12) #define URXD_BRK (1<<11) #define URXD_PRERR (1<<10) +#define URXD_RX_DATA (0xFF<<0) #define UCR1_ADEN (1<<15) /* Auto detect interrupt */ #define UCR1_ADBR (1<<14) /* Auto detect baud rate */ #define UCR1_TRDYEN (1<<13) /* Transmitter ready interrupt enable */ @@ -1506,32 +1507,10 @@ imx_verify_port(struct uart_port *port, struct serial_struct *ser) #if defined(CONFIG_CONSOLE_POLL) static int imx_poll_get_char(struct uart_port *port) { - struct imx_port_ucrs old_ucr; - unsigned int status; - unsigned char c; - - /* save control registers */ - imx_port_ucrs_save(port, &old_ucr); - - /* disable interrupts */ - writel(UCR1_UARTEN, port->membase + UCR1); - writel(old_ucr.ucr2 & ~(UCR2_ATEN | UCR2_RTSEN | UCR2_ESCI), - port->membase + UCR2); - writel(old_ucr.ucr3 & ~(UCR3_DCD | UCR3_RI | UCR3_DTREN), - port->membase + UCR3); - - /* poll */ - do { - status = readl(port->membase + USR2); - } while (~status & USR2_RDR); - - /* read */ - c = readl(port->membase + URXD0); - - /* restore control registers */ - imx_port_ucrs_restore(port, &old_ucr); + if (!(readl(port->membase + USR2) & USR2_RDR)) + return NO_POLL_CHAR;
- return c; + return readl(port->membase + URXD0) & URXD_RX_DATA; }
static void imx_poll_put_char(struct uart_port *port, unsigned char c)
This patch makes it possible to use the imx uart with KGDB's FIQ/NMI mode.
Main changes are:
.poll_init() will, if KGDB+FIQ are enabled, perform deeper hardware initialization to ensure the serial port is always active (required otherwise FIQ is not triggered by UART activity). This has an impact on power usage so it is conservatively enabled.
imx_put_poll_char() has been simplified to remove the code to disable interrupts. The present code can corrupt register state when re-entered from FIQ handler.
Both imx_put_poll_char() and imx_get_poll_char() adopt _relaxed() MMIO functions (which are safe for polled I/O and needed to avoid taking spin locks).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org --- drivers/tty/serial/imx.c | 71 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 19 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 983668a..a201c61 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -49,6 +49,7 @@ #include <linux/of_device.h> #include <linux/io.h> #include <linux/dma-mapping.h> +#include <linux/kgdb.h>
#include <asm/irq.h> #include <linux/platform_data/serial-imx.h> @@ -1505,44 +1506,73 @@ imx_verify_port(struct uart_port *port, struct serial_struct *ser) }
#if defined(CONFIG_CONSOLE_POLL) + +#if defined(CONFIG_KGDB_FIQ) +/* + * Prepare the UART to be used from kgdb's NMI support. + */ +static int imx_poll_init(struct uart_port *port) +{ + struct imx_port *sport = (struct imx_port *)port; + unsigned long flags; + unsigned long temp; + int retval; + + retval = clk_prepare_enable(sport->clk_ipg); + if (retval) + return retval; + retval = clk_prepare_enable(sport->clk_per); + if (retval) + clk_disable_unprepare(sport->clk_ipg); + + imx_setup_ufcr(sport, 0); + + spin_lock_irqsave(&sport->port.lock, flags); + + temp = readl(sport->port.membase + UCR1); + if (is_imx1_uart(sport)) + temp |= IMX1_UCR1_UARTCLKEN; + temp |= UCR1_UARTEN | UCR1_RRDYEN; + temp &= ~(UCR1_TXMPTYEN | UCR1_RTSDEN); + writel(temp, sport->port.membase + UCR1); + + temp = readl(sport->port.membase + UCR2); + temp |= UCR2_RXEN; + writel(temp, sport->port.membase + UCR2); + + spin_unlock_irqrestore(&sport->port.lock, flags); + + /* register the FIQ with kgdb */ + kgdb_register_fiq(sport->port.irq); + + return 0; +} +#endif /* CONFIG_KGDB_FIQ */ + static int imx_poll_get_char(struct uart_port *port) { - if (!(readl(port->membase + USR2) & USR2_RDR)) + if (!(readl_relaxed(port->membase + USR2) & USR2_RDR)) return NO_POLL_CHAR;
- return readl(port->membase + URXD0) & URXD_RX_DATA; + return readl_relaxed(port->membase + URXD0) & URXD_RX_DATA; }
static void imx_poll_put_char(struct uart_port *port, unsigned char c) { - struct imx_port_ucrs old_ucr; unsigned int status;
- /* save control registers */ - imx_port_ucrs_save(port, &old_ucr); - - /* disable interrupts */ - writel(UCR1_UARTEN, port->membase + UCR1); - writel(old_ucr.ucr2 & ~(UCR2_ATEN | UCR2_RTSEN | UCR2_ESCI), - port->membase + UCR2); - writel(old_ucr.ucr3 & ~(UCR3_DCD | UCR3_RI | UCR3_DTREN), - port->membase + UCR3); - /* drain */ do { - status = readl(port->membase + USR1); + status = readl_relaxed(port->membase + USR1); } while (~status & USR1_TRDY);
/* write */ - writel(c, port->membase + URTX0); + writel_relaxed(c, port->membase + URTX0);
/* flush */ do { - status = readl(port->membase + USR2); + status = readl_relaxed(port->membase + USR2); } while (~status & USR2_TXDC); - - /* restore control registers */ - imx_port_ucrs_restore(port, &old_ucr); } #endif
@@ -1563,6 +1593,9 @@ static struct uart_ops imx_pops = { .config_port = imx_config_port, .verify_port = imx_verify_port, #if defined(CONFIG_CONSOLE_POLL) +#if defined(CONFIG_KGDB_FIQ) + .poll_init = imx_poll_init, +#endif .poll_get_char = imx_poll_get_char, .poll_put_char = imx_poll_put_char, #endif
On 18.08.2014 16:28, Daniel Thompson wrote:
This patch makes it possible to use the imx uart with KGDB's FIQ/NMI mode.
Main changes are:
.poll_init() will, if KGDB+FIQ are enabled, perform deeper hardware initialization to ensure the serial port is always active (required otherwise FIQ is not triggered by UART activity). This has an impact on power usage so it is conservatively enabled.
imx_put_poll_char() has been simplified to remove the code to disable interrupts. The present code can corrupt register state when re-entered from FIQ handler.
Both imx_put_poll_char() and imx_get_poll_char() adopt _relaxed() MMIO functions (which are safe for polled I/O and needed to avoid taking spin locks).
Signed-off-by: Daniel Thompson daniel.thompson@linaro.org Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Jiri Slaby jslaby@suse.cz Cc: linux-serial@vger.kernel.org
Acked-by: Dirk Behme dirk.behme@de.bosch.com
Thanks
Dirk
drivers/tty/serial/imx.c | 71 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 19 deletions(-)
diff --git a/drivers/tty/serial/imx.c b/drivers/tty/serial/imx.c index 983668a..a201c61 100644 --- a/drivers/tty/serial/imx.c +++ b/drivers/tty/serial/imx.c @@ -49,6 +49,7 @@ #include <linux/of_device.h> #include <linux/io.h> #include <linux/dma-mapping.h> +#include <linux/kgdb.h>
#include <asm/irq.h> #include <linux/platform_data/serial-imx.h> @@ -1505,44 +1506,73 @@ imx_verify_port(struct uart_port *port, struct serial_struct *ser) }
#if defined(CONFIG_CONSOLE_POLL)
+#if defined(CONFIG_KGDB_FIQ) +/*
- Prepare the UART to be used from kgdb's NMI support.
- */
+static int imx_poll_init(struct uart_port *port) +{
- struct imx_port *sport = (struct imx_port *)port;
- unsigned long flags;
- unsigned long temp;
- int retval;
- retval = clk_prepare_enable(sport->clk_ipg);
- if (retval)
return retval;
- retval = clk_prepare_enable(sport->clk_per);
- if (retval)
clk_disable_unprepare(sport->clk_ipg);
- imx_setup_ufcr(sport, 0);
- spin_lock_irqsave(&sport->port.lock, flags);
- temp = readl(sport->port.membase + UCR1);
- if (is_imx1_uart(sport))
temp |= IMX1_UCR1_UARTCLKEN;
- temp |= UCR1_UARTEN | UCR1_RRDYEN;
- temp &= ~(UCR1_TXMPTYEN | UCR1_RTSDEN);
- writel(temp, sport->port.membase + UCR1);
- temp = readl(sport->port.membase + UCR2);
- temp |= UCR2_RXEN;
- writel(temp, sport->port.membase + UCR2);
- spin_unlock_irqrestore(&sport->port.lock, flags);
- /* register the FIQ with kgdb */
- kgdb_register_fiq(sport->port.irq);
- return 0;
+} +#endif /* CONFIG_KGDB_FIQ */
- static int imx_poll_get_char(struct uart_port *port) {
- if (!(readl(port->membase + USR2) & USR2_RDR))
- if (!(readl_relaxed(port->membase + USR2) & USR2_RDR)) return NO_POLL_CHAR;
- return readl(port->membase + URXD0) & URXD_RX_DATA;
return readl_relaxed(port->membase + URXD0) & URXD_RX_DATA; }
static void imx_poll_put_char(struct uart_port *port, unsigned char c) {
struct imx_port_ucrs old_ucr; unsigned int status;
/* save control registers */
imx_port_ucrs_save(port, &old_ucr);
/* disable interrupts */
writel(UCR1_UARTEN, port->membase + UCR1);
writel(old_ucr.ucr2 & ~(UCR2_ATEN | UCR2_RTSEN | UCR2_ESCI),
port->membase + UCR2);
writel(old_ucr.ucr3 & ~(UCR3_DCD | UCR3_RI | UCR3_DTREN),
port->membase + UCR3);
/* drain */ do {
status = readl(port->membase + USR1);
status = readl_relaxed(port->membase + USR1);
} while (~status & USR1_TRDY);
/* write */
- writel(c, port->membase + URTX0);
writel_relaxed(c, port->membase + URTX0);
/* flush */ do {
status = readl(port->membase + USR2);
} while (~status & USR2_TXDC);status = readl_relaxed(port->membase + USR2);
- /* restore control registers */
- imx_port_ucrs_restore(port, &old_ucr); } #endif
@@ -1563,6 +1593,9 @@ static struct uart_ops imx_pops = { .config_port = imx_config_port, .verify_port = imx_verify_port, #if defined(CONFIG_CONSOLE_POLL) +#if defined(CONFIG_KGDB_FIQ)
- .poll_init = imx_poll_init,
+#endif .poll_get_char = imx_poll_get_char, .poll_put_char = imx_poll_put_char, #endif
linaro-kernel@lists.linaro.org