Skip to content

Commit 7cd1ea1

Browse files
mrutland-armwilldeacon
authored andcommitted
arm64: entry: fix non-NMI kernel<->kernel transitions
There are periods in kernel mode when RCU is not watching and/or the scheduler tick is disabled, but we can still take exceptions such as interrupts. The arm64 exception handlers do not account for this, and it's possible that RCU is not watching while an exception handler runs. The x86/generic entry code handles this by ensuring that all (non-NMI) kernel exception handlers call irqentry_enter() and irqentry_exit(), which handle RCU, lockdep, and IRQ flag tracing. We can't yet move to the generic entry code, and already hadnle the user<->kernel transitions elsewhere, so we add new kernel<->kernel transition helpers alog the lines of the generic entry code. Since we now track interrupts becoming masked when an exception is taken, local_daif_inherit() is modified to track interrupts becoming re-enabled when the original context is inherited. To balance the entry/exit paths, each handler masks all DAIF exceptions before exit_to_kernel_mode(). Signed-off-by: Mark Rutland <[email protected]> Cc: Catalin Marinas <[email protected]> Cc: James Morse <[email protected]> Cc: Will Deacon <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Will Deacon <[email protected]>
1 parent 1ec2f2c commit 7cd1ea1

File tree

2 files changed

+67
-3
lines changed

2 files changed

+67
-3
lines changed

arch/arm64/include/asm/daifflags.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ static inline void local_daif_inherit(struct pt_regs *regs)
128128
{
129129
unsigned long flags = regs->pstate & DAIF_MASK;
130130

131+
if (interrupts_enabled(regs))
132+
trace_hardirqs_on();
133+
131134
/*
132135
* We can't use local_daif_restore(regs->pstate) here as
133136
* system_has_prio_mask_debugging() won't restore the I bit if it can

arch/arm64/kernel/entry-common.c

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,49 +17,107 @@
1717
#include <asm/mmu.h>
1818
#include <asm/sysreg.h>
1919

20+
/*
21+
* This is intended to match the logic in irqentry_enter(), handling the kernel
22+
* mode transitions only.
23+
*/
24+
static void noinstr enter_from_kernel_mode(struct pt_regs *regs)
25+
{
26+
regs->exit_rcu = false;
27+
28+
if (!IS_ENABLED(CONFIG_TINY_RCU) && is_idle_task(current)) {
29+
lockdep_hardirqs_off(CALLER_ADDR0);
30+
rcu_irq_enter();
31+
trace_hardirqs_off_finish();
32+
33+
regs->exit_rcu = true;
34+
return;
35+
}
36+
37+
lockdep_hardirqs_off(CALLER_ADDR0);
38+
rcu_irq_enter_check_tick();
39+
trace_hardirqs_off_finish();
40+
}
41+
42+
/*
43+
* This is intended to match the logic in irqentry_exit(), handling the kernel
44+
* mode transitions only, and with preemption handled elsewhere.
45+
*/
46+
static void noinstr exit_to_kernel_mode(struct pt_regs *regs)
47+
{
48+
lockdep_assert_irqs_disabled();
49+
50+
if (interrupts_enabled(regs)) {
51+
if (regs->exit_rcu) {
52+
trace_hardirqs_on_prepare();
53+
lockdep_hardirqs_on_prepare(CALLER_ADDR0);
54+
rcu_irq_exit();
55+
lockdep_hardirqs_on(CALLER_ADDR0);
56+
return;
57+
}
58+
59+
trace_hardirqs_on();
60+
} else {
61+
if (regs->exit_rcu)
62+
rcu_irq_exit();
63+
}
64+
}
65+
2066
asmlinkage void noinstr enter_el1_irq_or_nmi(struct pt_regs *regs)
2167
{
2268
if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && !interrupts_enabled(regs))
2369
nmi_enter();
24-
25-
trace_hardirqs_off();
70+
else
71+
enter_from_kernel_mode(regs);
2672
}
2773

2874
asmlinkage void noinstr exit_el1_irq_or_nmi(struct pt_regs *regs)
2975
{
3076
if (IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) && !interrupts_enabled(regs))
3177
nmi_exit();
3278
else
33-
trace_hardirqs_on();
79+
exit_to_kernel_mode(regs);
3480
}
3581

3682
static void noinstr el1_abort(struct pt_regs *regs, unsigned long esr)
3783
{
3884
unsigned long far = read_sysreg(far_el1);
3985

86+
enter_from_kernel_mode(regs);
4087
local_daif_inherit(regs);
4188
far = untagged_addr(far);
4289
do_mem_abort(far, esr, regs);
90+
local_daif_mask();
91+
exit_to_kernel_mode(regs);
4392
}
4493

4594
static void noinstr el1_pc(struct pt_regs *regs, unsigned long esr)
4695
{
4796
unsigned long far = read_sysreg(far_el1);
4897

98+
enter_from_kernel_mode(regs);
4999
local_daif_inherit(regs);
50100
do_sp_pc_abort(far, esr, regs);
101+
local_daif_mask();
102+
exit_to_kernel_mode(regs);
51103
}
52104

53105
static void noinstr el1_undef(struct pt_regs *regs)
54106
{
107+
enter_from_kernel_mode(regs);
55108
local_daif_inherit(regs);
56109
do_undefinstr(regs);
110+
local_daif_mask();
111+
exit_to_kernel_mode(regs);
57112
}
58113

59114
static void noinstr el1_inv(struct pt_regs *regs, unsigned long esr)
60115
{
116+
enter_from_kernel_mode(regs);
61117
local_daif_inherit(regs);
62118
bad_mode(regs, 0, esr);
119+
local_daif_mask();
120+
exit_to_kernel_mode(regs);
63121
}
64122

65123
static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
@@ -79,8 +137,11 @@ static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
79137

80138
static void noinstr el1_fpac(struct pt_regs *regs, unsigned long esr)
81139
{
140+
enter_from_kernel_mode(regs);
82141
local_daif_inherit(regs);
83142
do_ptrauth_fault(regs, esr);
143+
local_daif_mask();
144+
exit_to_kernel_mode(regs);
84145
}
85146

86147
asmlinkage void noinstr el1_sync_handler(struct pt_regs *regs)

0 commit comments

Comments
 (0)