Skip to content

Commit c0e7f7e

Browse files
Daniel ThompsonRussell King
authored andcommitted
ARM: 8150/3: fiq: Replace default FIQ handler
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. Thanks also to Russell King for spoonfeeding me a variety of fixes during the review cycle. Signed-off-by: Daniel Thompson <[email protected]> Cc: Catalin Marinas <[email protected]> Acked-by: Nicolas Pitre <[email protected]> Signed-off-by: Russell King <[email protected]>
1 parent 9e82bf0 commit c0e7f7e

File tree

5 files changed

+180
-16
lines changed

5 files changed

+180
-16
lines changed

arch/arm/kernel/entry-armv.S

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ ENDPROC(__und_invalid)
146146
#define SPFIX(code...)
147147
#endif
148148

149-
.macro svc_entry, stack_hole=0
149+
.macro svc_entry, stack_hole=0, trace=1
150150
UNWIND(.fnstart )
151151
UNWIND(.save {r0 - pc} )
152152
sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
@@ -182,9 +182,11 @@ ENDPROC(__und_invalid)
182182
@
183183
stmia r7, {r2 - r6}
184184

185+
.if \trace
185186
#ifdef CONFIG_TRACE_IRQFLAGS
186187
bl trace_hardirqs_off
187188
#endif
189+
.endif
188190
.endm
189191

190192
.align 5
@@ -294,6 +296,15 @@ __pabt_svc:
294296
UNWIND(.fnend )
295297
ENDPROC(__pabt_svc)
296298

299+
.align 5
300+
__fiq_svc:
301+
svc_entry trace=0
302+
mov r0, sp @ struct pt_regs *regs
303+
bl handle_fiq_as_nmi
304+
svc_exit_via_fiq
305+
UNWIND(.fnend )
306+
ENDPROC(__fiq_svc)
307+
297308
.align 5
298309
.LCcralign:
299310
.word cr_alignment
@@ -304,6 +315,46 @@ ENDPROC(__pabt_svc)
304315
.LCfp:
305316
.word fp_enter
306317

318+
/*
319+
* Abort mode handlers
320+
*/
321+
322+
@
323+
@ Taking a FIQ in abort mode is similar to taking a FIQ in SVC mode
324+
@ and reuses the same macros. However in abort mode we must also
325+
@ save/restore lr_abt and spsr_abt to make nested aborts safe.
326+
@
327+
.align 5
328+
__fiq_abt:
329+
svc_entry trace=0
330+
331+
ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT )
332+
THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT )
333+
THUMB( msr cpsr_c, r0 )
334+
mov r1, lr @ Save lr_abt
335+
mrs r2, spsr @ Save spsr_abt, abort is now safe
336+
ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT )
337+
THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT )
338+
THUMB( msr cpsr_c, r0 )
339+
stmfd sp!, {r1 - r2}
340+
341+
add r0, sp, #8 @ struct pt_regs *regs
342+
bl handle_fiq_as_nmi
343+
344+
ldmfd sp!, {r1 - r2}
345+
ARM( msr cpsr_c, #ABT_MODE | PSR_I_BIT | PSR_F_BIT )
346+
THUMB( mov r0, #ABT_MODE | PSR_I_BIT | PSR_F_BIT )
347+
THUMB( msr cpsr_c, r0 )
348+
mov lr, r1 @ Restore lr_abt, abort is unsafe
349+
msr spsr_cxsf, r2 @ Restore spsr_abt
350+
ARM( msr cpsr_c, #SVC_MODE | PSR_I_BIT | PSR_F_BIT )
351+
THUMB( mov r0, #SVC_MODE | PSR_I_BIT | PSR_F_BIT )
352+
THUMB( msr cpsr_c, r0 )
353+
354+
svc_exit_via_fiq
355+
UNWIND(.fnend )
356+
ENDPROC(__fiq_abt)
357+
307358
/*
308359
* User mode handlers
309360
*
@@ -314,7 +365,7 @@ ENDPROC(__pabt_svc)
314365
#error "sizeof(struct pt_regs) must be a multiple of 8"
315366
#endif
316367

317-
.macro usr_entry
368+
.macro usr_entry, trace=1
318369
UNWIND(.fnstart )
319370
UNWIND(.cantunwind ) @ don't unwind the user space
320371
sub sp, sp, #S_FRAME_SIZE
@@ -351,10 +402,12 @@ ENDPROC(__pabt_svc)
351402
@
352403
zero_fp
353404

405+
.if \trace
354406
#ifdef CONFIG_IRQSOFF_TRACER
355407
bl trace_hardirqs_off
356408
#endif
357409
ct_user_exit save = 0
410+
.endif
358411
.endm
359412

360413
.macro kuser_cmpxchg_check
@@ -683,6 +736,17 @@ ENTRY(ret_from_exception)
683736
ENDPROC(__pabt_usr)
684737
ENDPROC(ret_from_exception)
685738

739+
.align 5
740+
__fiq_usr:
741+
usr_entry trace=0
742+
kuser_cmpxchg_check
743+
mov r0, sp @ struct pt_regs *regs
744+
bl handle_fiq_as_nmi
745+
get_thread_info tsk
746+
restore_user_regs fast = 0, offset = 0
747+
UNWIND(.fnend )
748+
ENDPROC(__fiq_usr)
749+
686750
/*
687751
* Register switch for ARMv3 and ARMv4 processors
688752
* r0 = previous task_struct, r1 = previous thread_info, r2 = next thread_info
@@ -1118,17 +1182,29 @@ vector_addrexcptn:
11181182
b vector_addrexcptn
11191183

11201184
/*=============================================================================
1121-
* Undefined FIQs
1185+
* FIQ "NMI" handler
11221186
*-----------------------------------------------------------------------------
1123-
* Enter in FIQ mode, spsr = ANY CPSR, lr = ANY PC
1124-
* MUST PRESERVE SVC SPSR, but need to switch to SVC mode to show our msg.
1125-
* Basically to switch modes, we *HAVE* to clobber one register... brain
1126-
* damage alert! I don't think that we can execute any code in here in any
1127-
* other mode than FIQ... Ok you can switch to another mode, but you can't
1128-
* get out of that mode without clobbering one register.
1187+
* Handle a FIQ using the SVC stack allowing FIQ act like NMI on x86
1188+
* systems.
11291189
*/
1130-
vector_fiq:
1131-
subs pc, lr, #4
1190+
vector_stub fiq, FIQ_MODE, 4
1191+
1192+
.long __fiq_usr @ 0 (USR_26 / USR_32)
1193+
.long __fiq_svc @ 1 (FIQ_26 / FIQ_32)
1194+
.long __fiq_svc @ 2 (IRQ_26 / IRQ_32)
1195+
.long __fiq_svc @ 3 (SVC_26 / SVC_32)
1196+
.long __fiq_svc @ 4
1197+
.long __fiq_svc @ 5
1198+
.long __fiq_svc @ 6
1199+
.long __fiq_abt @ 7
1200+
.long __fiq_svc @ 8
1201+
.long __fiq_svc @ 9
1202+
.long __fiq_svc @ a
1203+
.long __fiq_svc @ b
1204+
.long __fiq_svc @ c
1205+
.long __fiq_svc @ d
1206+
.long __fiq_svc @ e
1207+
.long __fiq_svc @ f
11321208

11331209
.globl vector_fiq_offset
11341210
.equ vector_fiq_offset, vector_fiq

arch/arm/kernel/entry-header.S

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,34 @@
216216
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
217217
.endm
218218

219+
@
220+
@ svc_exit_via_fiq - like svc_exit but switches to FIQ mode before exit
221+
@
222+
@ This macro acts in a similar manner to svc_exit but switches to FIQ
223+
@ mode to restore the final part of the register state.
224+
@
225+
@ We cannot use the normal svc_exit procedure because that would
226+
@ clobber spsr_svc (FIQ could be delivered during the first few
227+
@ instructions of vector_swi meaning its contents have not been
228+
@ saved anywhere).
229+
@
230+
@ Note that, unlike svc_exit, this macro also does not allow a caller
231+
@ supplied rpsr. This is because the FIQ exceptions are not re-entrant
232+
@ and the handlers cannot call into the scheduler (meaning the value
233+
@ on the stack remains correct).
234+
@
235+
.macro svc_exit_via_fiq
236+
mov r0, sp
237+
ldmib r0, {r1 - r14} @ abort is deadly from here onward (it will
238+
@ clobber state restored below)
239+
msr cpsr_c, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT
240+
add r8, r0, #S_PC
241+
ldr r9, [r0, #S_PSR]
242+
msr spsr_cxsf, r9
243+
ldr r0, [r0, #S_R0]
244+
ldmia r8, {pc}^
245+
.endm
246+
219247
.macro restore_user_regs, fast = 0, offset = 0
220248
ldr r1, [sp, #\offset + S_PSR] @ get calling cpsr
221249
ldr lr, [sp, #\offset + S_PC]! @ get pc
@@ -267,6 +295,25 @@
267295
rfeia sp!
268296
.endm
269297

298+
@
299+
@ svc_exit_via_fiq - like svc_exit but switches to FIQ mode before exit
300+
@
301+
@ For full details see non-Thumb implementation above.
302+
@
303+
.macro svc_exit_via_fiq
304+
add r0, sp, #S_R2
305+
ldr lr, [sp, #S_LR]
306+
ldr sp, [sp, #S_SP] @ abort is deadly from here onward (it will
307+
@ clobber state restored below)
308+
ldmia r0, {r2 - r12}
309+
mov r1, #FIQ_MODE | PSR_I_BIT | PSR_F_BIT
310+
msr cpsr_c, r1
311+
sub r0, #S_R2
312+
add r8, r0, #S_PC
313+
ldmia r0, {r0 - r1}
314+
rfeia r8
315+
.endm
316+
270317
#ifdef CONFIG_CPU_V7M
271318
/*
272319
* Note we don't need to do clrex here as clearing the local monitor is

arch/arm/kernel/fiq.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,24 @@
5252
(unsigned)&vector_fiq_offset; \
5353
})
5454

55-
static unsigned long no_fiq_insn;
55+
static unsigned long dfl_fiq_insn;
56+
static struct pt_regs dfl_fiq_regs;
5657

5758
/* Default reacquire function
5859
* - we always relinquish FIQ control
5960
* - we always reacquire FIQ control
6061
*/
6162
static int fiq_def_op(void *ref, int relinquish)
6263
{
63-
if (!relinquish)
64-
set_fiq_handler(&no_fiq_insn, sizeof(no_fiq_insn));
64+
if (!relinquish) {
65+
/* Restore default handler and registers */
66+
local_fiq_disable();
67+
set_fiq_regs(&dfl_fiq_regs);
68+
set_fiq_handler(&dfl_fiq_insn, sizeof(dfl_fiq_insn));
69+
local_fiq_enable();
70+
71+
/* FIXME: notify irq controller to standard enable FIQs */
72+
}
6573

6674
return 0;
6775
}
@@ -150,6 +158,7 @@ EXPORT_SYMBOL(disable_fiq);
150158
void __init init_FIQ(int start)
151159
{
152160
unsigned offset = FIQ_OFFSET;
153-
no_fiq_insn = *(unsigned long *)(0xffff0000 + offset);
161+
dfl_fiq_insn = *(unsigned long *)(0xffff0000 + offset);
162+
get_fiq_regs(&dfl_fiq_regs);
154163
fiq_start = start;
155164
}

arch/arm/kernel/setup.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ struct stack {
133133
u32 irq[3];
134134
u32 abt[3];
135135
u32 und[3];
136+
u32 fiq[3];
136137
} ____cacheline_aligned;
137138

138139
#ifndef CONFIG_CPU_V7M
@@ -470,7 +471,10 @@ void notrace cpu_init(void)
470471
"msr cpsr_c, %5\n\t"
471472
"add r14, %0, %6\n\t"
472473
"mov sp, r14\n\t"
473-
"msr cpsr_c, %7"
474+
"msr cpsr_c, %7\n\t"
475+
"add r14, %0, %8\n\t"
476+
"mov sp, r14\n\t"
477+
"msr cpsr_c, %9"
474478
:
475479
: "r" (stk),
476480
PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
@@ -479,6 +483,8 @@ void notrace cpu_init(void)
479483
"I" (offsetof(struct stack, abt[0])),
480484
PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
481485
"I" (offsetof(struct stack, und[0])),
486+
PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE),
487+
"I" (offsetof(struct stack, fiq[0])),
482488
PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
483489
: "r14");
484490
#endif

arch/arm/kernel/traps.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <linux/delay.h>
2626
#include <linux/init.h>
2727
#include <linux/sched.h>
28+
#include <linux/irq.h>
2829

2930
#include <linux/atomic.h>
3031
#include <asm/cacheflush.h>
@@ -466,6 +467,31 @@ asmlinkage void do_unexp_fiq (struct pt_regs *regs)
466467
printk("You may have a hardware problem...\n");
467468
}
468469

470+
/*
471+
* Handle FIQ similarly to NMI on x86 systems.
472+
*
473+
* The runtime environment for NMIs is extremely restrictive
474+
* (NMIs can pre-empt critical sections meaning almost all locking is
475+
* forbidden) meaning this default FIQ handling must only be used in
476+
* circumstances where non-maskability improves robustness, such as
477+
* watchdog or debug logic.
478+
*
479+
* This handler is not appropriate for general purpose use in drivers
480+
* platform code and can be overrideen using set_fiq_handler.
481+
*/
482+
asmlinkage void __exception_irq_entry handle_fiq_as_nmi(struct pt_regs *regs)
483+
{
484+
struct pt_regs *old_regs = set_irq_regs(regs);
485+
486+
nmi_enter();
487+
488+
/* nop. FIQ handlers for special arch/arm features can be added here. */
489+
490+
nmi_exit();
491+
492+
set_irq_regs(old_regs);
493+
}
494+
469495
/*
470496
* bad_mode handles the impossible case in the vectors. If you see one of
471497
* these, then it's extremely serious, and could mean you have buggy hardware.

0 commit comments

Comments
 (0)