x86: Rework IST interrupts
Subsystems (5)
v4 cover x86: Rework IST interrupts
Following Linus' feedback, this series has grown. When I say "IST interrupt" below, I'm excluding NMIs. They are their own thing. This patch does *not* change NMI handling.
This adds new functions ist_enter and ist_exit to handle entry to and exit from ist handlers. This is, I think, a bugfix for RCU handling, and I've cc'd some RCU people on that patch. It will also catch bugs involving scheduling on the IST stack.
This series then changes the entry code to switch to the normal stack if an IST interrupt came from userspace and wasn't a double-fault. This simplifies the exit logic.
Building on that, I add new functions ist_begin_non_atomic and ist_end_non_atomic. They next inside ist_enter and ist_exit, and they allow scheduling if the right conditions are met and they BUG if not. They're meant to be used in do_machine_check to handle userspace memory failures.
NB: Tony has seen odd behavior when stress-testing injected machine checks with earlier versions of this series applied. I suspect that it's a bug in something else, possibly his BIOS. Bugs in this series shouldn't be ruled out, though.
Changes from v3:
- Reworked everything except the asm.
Changes from v2:
- double_fault is no longer affected.
- Other changes, but those are obsolete now.
Changes from RFC/v1: I forgot. Sorry.
Andy Lutomirski (5):
uprobes, x86: Fix _TIF_UPROBE vs _TIF_NOTIFY_RESUME x86, traps: Track entry into and exit from IST context x86, entry: Switch stacks on a paranoid entry from userspace
x86: Clean up current_stack_pointer
x86, traps: Add ist_begin_non_atomic and ist_end_non_atomic
Documentation/x86/entry_64.txt | 18 ++++-- Documentation/x86/x86_64/kernel-stacks | 8 ++- arch/x86/include/asm/thread_info.h | 13 +++- arch/x86/include/asm/traps.h | 6 ++ arch/x86/kernel/cpu/mcheck/mce.c | 5 ++ arch/x86/kernel/cpu/mcheck/p5.c | 6 ++ arch/x86/kernel/cpu/mcheck/winchip.c | 5 ++ arch/x86/kernel/entry_64.S | 86 ++++++++++++++------------ arch/x86/kernel/irq_32.c | 13 +--- arch/x86/kernel/traps.c | 110 +++++++++++++++++++++++++-------- kernel/events/uprobes.c | 1 - 11 files changed, 183 insertions(+), 88 deletions(-)
Signature · 1 line
1.9.3
PATCH v4 1/5 uprobes, x86: Fix _TIF_UPROBE vs _TIF_NOTIFY_RESUME
x86 call do_notify_resume on paranoid returns if TIF_UPROBE is set but not on non-paranoid returns. I suspect that this is a mistake and that the code only works because int3 is paranoid.
Setting _TIF_NOTIFY_RESUME in the uprobe code was probably a workaround for the x86 bug. With that bug fixed, we can remove _TIF_NOTIFY_RESUME from the uprobes code.
--- arch/x86/include/asm/thread_info.h | 2 +- kernel/events/uprobes.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-)
arch/x86/include/asm/thread_info.h +1 -1
@@ -141,7 +141,7 @@ struct thread_info {/* Only used for 64 bit */ #define _TIF_DO_NOTIFY_MASK \ (_TIF_SIGPENDING | _TIF_MCE_NOTIFY | _TIF_NOTIFY_RESUME | \ - _TIF_USER_RETURN_NOTIFY) + _TIF_USER_RETURN_NOTIFY | _TIF_UPROBE)/* flags to check in __switch_to() */ #define _TIF_WORK_CTXSW \
kernel/events/uprobes.c +0 -1
@@ -1640,7 +1640,6 @@ bool uprobe_deny_signal(void)if (__fatal_signal_pending (t) ||arch_uprobe_xol_was_trapped (t)) { utask->state =UTASK_SSTEP_TRAPPED ;set_tsk_thread_flag (t,TIF_UPROBE ); -set_tsk_thread_flag (t,TIF_NOTIFY_RESUME ); } }
Signature · 1 line
1.9.3
Re: [PATCH v4 1/5] uprobes, x86: Fix _TIF_UPROBE vs _TIF_NOTIFY_RESUME
Quoted 11 lines · Andy Lutomirski
x86 call
do_notify_resumeon paranoid returns ifTIF_UPROBEis set but not on non-paranoid returns. I suspect that this is a mistake and that the code only works because int3 is paranoid.Setting
_TIF_NOTIFY_RESUMEin the uprobe code was probably a workaround for the x86 bug. With that bug fixed, we can remove_TIF_NOTIFY_RESUMEfrom the uprobes code.Reported-by Oleg Nesterov <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>
Signature · 5 lines
Regards/Gruss,
Boris.
Sent from a fat crate under my desk. Formatting is fine.
--Re: [PATCH v4 1/5] uprobes, x86: Fix _TIF_UPROBE vs _TIF_NOTIFY_RESUME
Quoted 9 lines · Borislav Petkov
Quoted 8 lines · Andy Lutomirski
x86 call
do_notify_resumeon paranoid returns ifTIF_UPROBEis set but not on non-paranoid returns. I suspect that this is a mistake and that the code only works because int3 is paranoid.Setting
_TIF_NOTIFY_RESUMEin the uprobe code was probably a workaround for the x86 bug. With that bug fixed, we can remove_TIF_NOTIFY_RESUMEfrom the uprobes code.
Quoted 6 lines
Quoted 3 lines
Reported-by Oleg Nesterov <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>Acked-by Borislav Petkov <[email protected]>
This one is now upstream, so I'm dropping it.
--Andy
Quoted 6 lines
--
Regards/Gruss,
Boris.Sent from a fat crate under my desk. Formatting is fine. --
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
PATCH v4 2/5 x86, traps: Track entry into and exit from IST context
In reply to Andy LutomirskiWe currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use ist_enter and ist_exit to track IST context. Even though x86_32 has no IST stacks, we track these interrupts the same way.
This fixes two issues:
- Scheduling from an IST interrupt handler will now warn. It would previously appear to work as long as we got lucky and nothing overwrote the stack frame. (I don't know of any bugs in this that would trigger the warning, but it's good to be on the safe side.)
- RCU handling in IST context was dangerous. As far as I know, only machine checks were likely to trigger this, but it's good to be on the safe side.
Note that the machine check handlers appears to have been missing any context tracking at all before this patch.
--- arch/x86/include/asm/traps.h | 4 +++ arch/x86/kernel/cpu/mcheck/mce.c | 5 ++++ arch/x86/kernel/cpu/mcheck/p5.c | 6 +++++ arch/x86/kernel/cpu/mcheck/winchip.c | 5 ++++ arch/x86/kernel/traps.c | 49 ++++++++++++++++++++++++++++++------ 5 files changed, 61 insertions(+), 8 deletions(-)
arch/x86/include/asm/traps.h +4 -0
@@ -1,6 +1,7 @@#ifndef _ASM_X86_TRAPS_H#define _ASM_X86_TRAPS_H +#include <linux/context_tracking_state.h> #include <linux/kprobes.h> #include <asm/debugreg.h>
@@ -109,6 +110,9 @@ asmlinkage void smp_thermal_interrupt(void); asmlinkage void mce_threshold_interrupt(void);#endif +extern enum ctx_state ist_enter (struct pt_regs *regs); +extern void ist_exit (struct pt_regs *regs,enum ctx_state prev_state); +/* Interrupts/Exceptions */ enum {X86_TRAP_DE =0 ,/* 0, Divide-by-zero */
arch/x86/kernel/cpu/mcheck/mce.c +5 -0
@@ -43,6 +43,7 @@#include <linux/export.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h>
@@ -1016,6 +1017,7 @@ void do_machine_check(struct pt_regs *regs, long error_code) {struct mca_config *cfg = &mca_cfg;struct mce m, *final; +enum ctx_state prev_state;int i;int worst =0 ;int severity;
@@ -1038,6 +1040,8 @@ void do_machine_check(struct pt_regs *regs, long error_code)DECLARE_BITMAP (valid_banks,MAX_NR_BANKS );char *msg ="Unknown" ; + prev_state =ist_enter (regs); +this_cpu_inc (mce_exception_count);if (!cfg->banks)
@@ -1168,6 +1172,7 @@ void do_machine_check(struct pt_regs *regs, long error_code)mce_wrmsrl (MSR_IA32_MCG_STATUS ,0 ); out:sync_core (); +ist_exit (regs, prev_state); }EXPORT_SYMBOL_GPL (do_machine_check);
arch/x86/kernel/cpu/mcheck/p5.c +6 -0
@@ -8,6 +8,7 @@#include <linux/smp.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h>
@@ -17,8 +18,11 @@ int mce_p5_enabled __read_mostly;/* Machine check handler for Pentium class Intel CPUs: */ static void pentium_machine_check (struct pt_regs *regs,long error_code) { +enum ctx_state prev_state;u32 loaddr, hi, lotype; + prev_state =ist_enter (regs); +rdmsr (MSR_IA32_P5_MC_ADDR , loaddr, hi);rdmsr (MSR_IA32_P5_MC_TYPE , lotype, hi);
@@ -33,6 +37,8 @@ static void pentium_machine_check(struct pt_regs *regs, long error_code) }add_taint (TAINT_MACHINE_CHECK ,LOCKDEP_NOW_UNRELIABLE ); + +ist_exit (regs, prev_state); }/* Set up machine check reporting for processors with Intel style MCE: */
arch/x86/kernel/cpu/mcheck/winchip.c +5 -0
@@ -7,14 +7,19 @@#include <linux/types.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> /* Machine check handler for WinChip C6: */ static void winchip_machine_check (struct pt_regs *regs,long error_code) { +enum ctx_state prev_state =ist_enter (regs); +printk (KERN_EMERG "CPU0: Machine Check Exception.\n" );add_taint (TAINT_MACHINE_CHECK ,LOCKDEP_NOW_UNRELIABLE ); + +ist_exit (regs, prev_state); }/* Set up machine check reporting on the Winchip C6 series */
arch/x86/kernel/traps.c +41 -8
97 lines not on this page · raw message
Signature · 1 line
1.9.3
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.
I should add:
I have no idea why RCU read-side critical sections are safe inside __do_page_fault today. It's guarded by exception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?
--Andy
Quoted 255 lines
This fixes two issues:
- Scheduling from an IST interrupt handler will now warn. It would previously appear to work as long as we got lucky and nothing overwrote the stack frame. (I don't know of any bugs in this that would trigger the warning, but it's good to be on the safe side.)
- RCU handling in IST context was dangerous. As far as I know, only machine checks were likely to trigger this, but it's good to be on the safe side.
Note that the machine check handlers appears to have been missing any context tracking at all before this patch.
Cc "Paul E. McKenney" <[email protected]>Cc Josh Triplett <[email protected]>Cc Frédéric Weisbecker <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/include/asm/traps.h | 4 +++ arch/x86/kernel/cpu/mcheck/mce.c | 5 ++++ arch/x86/kernel/cpu/mcheck/p5.c | 6 +++++ arch/x86/kernel/cpu/mcheck/winchip.c | 5 ++++ arch/x86/kernel/traps.c | 49 ++++++++++++++++++++++++++++++------ 5 files changed, 61 insertions(+), 8 deletions(-)arch/x86/include/asm/traps.h +4
-0@@ -1,6 +1,7 @@#ifndef _ASM_X86_TRAPS_H#define _ASM_X86_TRAPS_H +#include <linux/context_tracking_state.h> #include <linux/kprobes.h> #include <asm/debugreg.h> @@ -109,6 +110,9 @@ asmlinkage void smp_thermal_interrupt(void); asmlinkage void mce_threshold_interrupt(void);#endif +extern enum ctx_state ist_enter (struct pt_regs *regs); +extern void ist_exit (struct pt_regs *regs,enum ctx_state prev_state); +/* Interrupts/Exceptions */ enum {X86_TRAP_DE =0 ,/* 0, Divide-by-zero */ arch/x86/kernel/cpu/mcheck/mce.c +5
-0@@ -43,6 +43,7 @@#include <linux/export.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> @@ -1016,6 +1017,7 @@ void do_machine_check(struct pt_regs *regs, long error_code) {struct mca_config *cfg = &mca_cfg;struct mce m, *final; +enum ctx_state prev_state;int i;int worst =0 ;int severity;@@ -1038,6 +1040,8 @@ void do_machine_check(struct pt_regs *regs, long error_code)DECLARE_BITMAP (valid_banks,MAX_NR_BANKS );char *msg ="Unknown" ; + prev_state =ist_enter (regs); +this_cpu_inc (mce_exception_count);if (!cfg->banks)@@ -1168,6 +1172,7 @@ void do_machine_check(struct pt_regs *regs, long error_code)mce_wrmsrl (MSR_IA32_MCG_STATUS ,0 ); out:sync_core (); +ist_exit (regs, prev_state); }EXPORT_SYMBOL_GPL (do_machine_check);arch/x86/kernel/cpu/mcheck/p5.c +6
-0@@ -8,6 +8,7 @@#include <linux/smp.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> @@ -17,8 +18,11 @@ int mce_p5_enabled __read_mostly;/* Machine check handler for Pentium class Intel CPUs: */ static void pentium_machine_check (struct pt_regs *regs,long error_code) { +enum ctx_state prev_state;u32 loaddr, hi, lotype; + prev_state =ist_enter (regs); +rdmsr (MSR_IA32_P5_MC_ADDR , loaddr, hi);rdmsr (MSR_IA32_P5_MC_TYPE , lotype, hi);@@ -33,6 +37,8 @@ static void pentium_machine_check(struct pt_regs *regs, long error_code) }add_taint (TAINT_MACHINE_CHECK ,LOCKDEP_NOW_UNRELIABLE ); + +ist_exit (regs, prev_state); }/* Set up machine check reporting for processors with Intel style MCE: */ arch/x86/kernel/cpu/mcheck/winchip.c +5
-0@@ -7,14 +7,19 @@#include <linux/types.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> /* Machine check handler for WinChip C6: */ static void winchip_machine_check (struct pt_regs *regs,long error_code) { +enum ctx_state prev_state =ist_enter (regs); +printk (KERN_EMERG "CPU0: Machine Check Exception.\n" );add_taint (TAINT_MACHINE_CHECK ,LOCKDEP_NOW_UNRELIABLE ); + +ist_exit (regs, prev_state); }/* Set up machine check reporting on the Winchip C6 series */ arch/x86/kernel/traps.c +41
-9@@ -107,6 +107,39 @@ static inline void preempt_conditional_cli(struct pt_regs *regs)preempt_count_dec (); } +enum ctx_state ist_enter (struct pt_regs *regs) +{ +/* +* We are atomic because we're on the IST stack (or we're on x86_32, +* in which case we still shouldn't schedule. +*/ +preempt_count_add (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) { +/* Other than that, we're just an exception. */ +return exception_enter (); + }else { +/* +* We might have interrupted pretty much anything. In +* fact, if we're a machine check, we can even interrupt +* NMI processing. We don't want in_nmi() to return true, +* but we need to notify RCU. +*/ +rcu_nmi_enter (); +return IN_KERNEL ;/* the value is irrelevant. */ + } +} + +void ist_exit (struct pt_regs *regs,enum ctx_state prev_state) +{ +preempt_count_sub (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) +return exception_exit (prev_state); +else +rcu_nmi_exit (); +} + static nokprobe_inline intdo_trap_no_signal (struct task_struct *tsk,int trapnr,char *str,struct pt_regs *regs,long error_code)@@ -244,14 +277,14 @@ dotraplinkage void do_stack_segment(struct pt_regs *regs, long error_code) {enum ctx_state prev_state; - prev_state =exception_enter (); + prev_state =ist_enter (regs);if (notify_die (DIE_TRAP ,"stack segment" , regs, error_code,X86_TRAP_SS ,SIGBUS ) !=NOTIFY_STOP ) {preempt_conditional_sti (regs);do_trap (X86_TRAP_SS ,SIGBUS ,"stack segment" , regs, error_code,NULL );preempt_conditional_cli (regs); } -exception_exit (prev_state); +ist_exit (regs, prev_state); } dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code)@@ -259,8 +292,8 @@ dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code)static const char str[] ="double fault" ;struct task_struct *tsk = current; -exception_enter (); -/* Return not checked because double check cannot be ignored */ +ist_enter (regs); +/* Return not checked because double fault cannot be ignored */ notify_die (DIE_TRAP , str, regs, error_code,X86_TRAP_DF ,SIGSEGV ); tsk->thread.error_code = error_code;@@ -343,7 +376,7 @@ dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)if (poke_int3_handler (regs))return ; - prev_state =exception_enter (); + prev_state =ist_enter (regs);#ifdef CONFIG_KGDB_LOW_LEVEL_TRAP if (kgdb_ll_trap (DIE_INT3 ,"int3" , regs, error_code,X86_TRAP_BP ,SIGTRAP ) ==NOTIFY_STOP )@@ -369,7 +402,7 @@ dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)preempt_conditional_cli (regs);debug_stack_usage_dec (); exit: -exception_exit (prev_state); +ist_exit (regs, prev_state); }NOKPROBE_SYMBOL (do_int3);@@ -433,7 +466,7 @@ dotraplinkage void do_debug(struct pt_regs *regs, long error_code)unsigned long dr6;int si_code; - prev_state =exception_enter (); + prev_state =ist_enter (regs);get_debugreg (dr6,6 );@@ -508,7 +541,7 @@ dotraplinkage void do_debug(struct pt_regs *regs, long error_code)debug_stack_usage_dec (); exit: -exception_exit (prev_state); +ist_exit (regs, prev_state); }NOKPROBE_SYMBOL (do_debug); --1.9.3
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.
Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?
Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.
2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution: NO_HZ_FULL and all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.
Make sense?
Thanx, Paul
Quoted 264 lines
--Andy
Quoted 255 lines
This fixes two issues:
- Scheduling from an IST interrupt handler will now warn. It would previously appear to work as long as we got lucky and nothing overwrote the stack frame. (I don't know of any bugs in this that would trigger the warning, but it's good to be on the safe side.)
- RCU handling in IST context was dangerous. As far as I know, only machine checks were likely to trigger this, but it's good to be on the safe side.
Note that the machine check handlers appears to have been missing any context tracking at all before this patch.
Cc "Paul E. McKenney" <[email protected]>Cc Josh Triplett <[email protected]>Cc Frédéric Weisbecker <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/include/asm/traps.h | 4 +++ arch/x86/kernel/cpu/mcheck/mce.c | 5 ++++ arch/x86/kernel/cpu/mcheck/p5.c | 6 +++++ arch/x86/kernel/cpu/mcheck/winchip.c | 5 ++++ arch/x86/kernel/traps.c | 49 ++++++++++++++++++++++++++++++------ 5 files changed, 61 insertions(+), 8 deletions(-)arch/x86/include/asm/traps.h +4
-0@@ -1,6 +1,7 @@#ifndef _ASM_X86_TRAPS_H#define _ASM_X86_TRAPS_H +#include <linux/context_tracking_state.h> #include <linux/kprobes.h> #include <asm/debugreg.h> @@ -109,6 +110,9 @@ asmlinkage void smp_thermal_interrupt(void); asmlinkage void mce_threshold_interrupt(void);#endif +extern enum ctx_state ist_enter (struct pt_regs *regs); +extern void ist_exit (struct pt_regs *regs,enum ctx_state prev_state); +/* Interrupts/Exceptions */ enum {X86_TRAP_DE =0 ,/* 0, Divide-by-zero */ arch/x86/kernel/cpu/mcheck/mce.c +5
-0@@ -43,6 +43,7 @@#include <linux/export.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> @@ -1016,6 +1017,7 @@ void do_machine_check(struct pt_regs *regs, long error_code) {struct mca_config *cfg = &mca_cfg;struct mce m, *final; +enum ctx_state prev_state;int i;int worst =0 ;int severity;@@ -1038,6 +1040,8 @@ void do_machine_check(struct pt_regs *regs, long error_code)DECLARE_BITMAP (valid_banks,MAX_NR_BANKS );char *msg ="Unknown" ; + prev_state =ist_enter (regs); +this_cpu_inc (mce_exception_count);if (!cfg->banks)@@ -1168,6 +1172,7 @@ void do_machine_check(struct pt_regs *regs, long error_code)mce_wrmsrl (MSR_IA32_MCG_STATUS ,0 ); out:sync_core (); +ist_exit (regs, prev_state); }EXPORT_SYMBOL_GPL (do_machine_check);arch/x86/kernel/cpu/mcheck/p5.c +6
-0@@ -8,6 +8,7 @@#include <linux/smp.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> @@ -17,8 +18,11 @@ int mce_p5_enabled __read_mostly;/* Machine check handler for Pentium class Intel CPUs: */ static void pentium_machine_check (struct pt_regs *regs,long error_code) { +enum ctx_state prev_state;u32 loaddr, hi, lotype; + prev_state =ist_enter (regs); +rdmsr (MSR_IA32_P5_MC_ADDR , loaddr, hi);rdmsr (MSR_IA32_P5_MC_TYPE , lotype, hi);@@ -33,6 +37,8 @@ static void pentium_machine_check(struct pt_regs *regs, long error_code) }add_taint (TAINT_MACHINE_CHECK ,LOCKDEP_NOW_UNRELIABLE ); + +ist_exit (regs, prev_state); }/* Set up machine check reporting for processors with Intel style MCE: */ arch/x86/kernel/cpu/mcheck/winchip.c +5
-0@@ -7,14 +7,19 @@#include <linux/types.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> /* Machine check handler for WinChip C6: */ static void winchip_machine_check (struct pt_regs *regs,long error_code) { +enum ctx_state prev_state =ist_enter (regs); +printk (KERN_EMERG "CPU0: Machine Check Exception.\n" );add_taint (TAINT_MACHINE_CHECK ,LOCKDEP_NOW_UNRELIABLE ); + +ist_exit (regs, prev_state); }/* Set up machine check reporting on the Winchip C6 series */ arch/x86/kernel/traps.c +41
-9@@ -107,6 +107,39 @@ static inline void preempt_conditional_cli(struct pt_regs *regs)preempt_count_dec (); } +enum ctx_state ist_enter (struct pt_regs *regs) +{ +/* +* We are atomic because we're on the IST stack (or we're on x86_32, +* in which case we still shouldn't schedule. +*/ +preempt_count_add (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) { +/* Other than that, we're just an exception. */ +return exception_enter (); + }else { +/* +* We might have interrupted pretty much anything. In +* fact, if we're a machine check, we can even interrupt +* NMI processing. We don't want in_nmi() to return true, +* but we need to notify RCU. +*/ +rcu_nmi_enter (); +return IN_KERNEL ;/* the value is irrelevant. */ + } +} + +void ist_exit (struct pt_regs *regs,enum ctx_state prev_state) +{ +preempt_count_sub (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) +return exception_exit (prev_state); +else +rcu_nmi_exit (); +} + static nokprobe_inline intdo_trap_no_signal (struct task_struct *tsk,int trapnr,char *str,struct pt_regs *regs,long error_code)@@ -244,14 +277,14 @@ dotraplinkage void do_stack_segment(struct pt_regs *regs, long error_code) {enum ctx_state prev_state; - prev_state =exception_enter (); + prev_state =ist_enter (regs);if (notify_die (DIE_TRAP ,"stack segment" , regs, error_code,X86_TRAP_SS ,SIGBUS ) !=NOTIFY_STOP ) {preempt_conditional_sti (regs);do_trap (X86_TRAP_SS ,SIGBUS ,"stack segment" , regs, error_code,NULL );preempt_conditional_cli (regs); } -exception_exit (prev_state); +ist_exit (regs, prev_state); } dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code)@@ -259,8 +292,8 @@ dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code)static const char str[] ="double fault" ;struct task_struct *tsk = current; -exception_enter (); -/* Return not checked because double check cannot be ignored */ +ist_enter (regs); +/* Return not checked because double fault cannot be ignored */ notify_die (DIE_TRAP , str, regs, error_code,X86_TRAP_DF ,SIGSEGV ); tsk->thread.error_code = error_code;@@ -343,7 +376,7 @@ dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)if (poke_int3_handler (regs))return ; - prev_state =exception_enter (); + prev_state =ist_enter (regs);#ifdef CONFIG_KGDB_LOW_LEVEL_TRAP if (kgdb_ll_trap (DIE_INT3 ,"int3" , regs, error_code,X86_TRAP_BP ,SIGTRAP ) ==NOTIFY_STOP )@@ -369,7 +402,7 @@ dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)preempt_conditional_cli (regs);debug_stack_usage_dec (); exit: -exception_exit (prev_state); +ist_exit (regs, prev_state); }NOKPROBE_SYMBOL (do_int3);@@ -433,7 +466,7 @@ dotraplinkage void do_debug(struct pt_regs *regs, long error_code)unsigned long dr6;int si_code; - prev_state =exception_enter (); + prev_state =ist_enter (regs);get_debugreg (dr6,6 );@@ -508,7 +541,7 @@ dotraplinkage void do_debug(struct pt_regs *regs, long error_code)debug_stack_usage_dec (); exit: -exception_exit (prev_state); +ist_exit (regs, prev_state); }NOKPROBE_SYMBOL (do_debug); --1.9.3
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 34 lines · Paul E. McKenney
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution:NO_HZ_FULLand all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.
Quoted 5 lines
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.
I guess so, as long as there are really no page faults in the idle loop.
There are, however, machine checks in the idle loop, and maybe kprobes (haven't checked), so I think this patch might fix real bugs.
Quoted 3 lines
Just out of curiosity... Can an NMI occur in IST context? If it can, I need to make
rcu_nmi_enter()andrcu_nmi_exit()deal properly with nested calls.
Yes, and vice versa. That code looked like it handled nesting correctly, but I wasn't entirely sure.
Also, just to make sure: are we okay if rcu_nmi_enter() is called before exception_enter if context tracking is on and we came directly from userspace?
--Andy
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 36 lines · Andy Lutomirski
Quoted 34 lines · Paul E. McKenney
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution:NO_HZ_FULLand all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.
Quoted 6 lines
Quoted 4 lines
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.I guess so, as long as there are really no page faults in the idle loop.
As far as I know, there are not. If there are, someone needs to let me know! ;-)
Quoted 2 lines
There are, however, machine checks in the idle loop, and maybe kprobes (haven't checked), so I think this patch might fix real bugs.
If you can get ISTs from the idle loop, then the patch is needed.
Quoted 6 lines
Quoted 3 lines
Just out of curiosity... Can an NMI occur in IST context? If it can, I need to make
rcu_nmi_enter()andrcu_nmi_exit()deal properly with nested calls.Yes, and vice versa. That code looked like it handled nesting correctly, but I wasn't entirely sure.
It currently does not, please see below patch. Are you able to test nesting? It would be really cool if you could do so -- I have no way to test this patch.
Quoted 3 lines
Also, just to make sure: are we okay if
rcu_nmi_enter()is called beforeexception_enterif context tracking is on and we came directly from userspace?
If I understand correctly, this will result in context tracking invoking rcu_user_enter(), which will result in the rcu_dynticks counter having an odd value. In that case, rcu_nmi_enter() will notice that RCU is already paying attention to this CPU via its check of atomic_read(&rdtp->dynticks) & 0x1), and will thus just return. The matching rcu_nmi_exit() will notice that the nesting count is zero, and will also just return.
Thus, everything works in that case.
In contrast, if rcu_nmi_enter() was invoked from the idle loop, it would see that RCU is not paying attention to this CPU and that the NMI nesting depth (which rcu_nmi_enter() increments) used to be zero. It would then atomically increment rtdp->dynticks, forcing RCU to start paying attention to this CPU. The matching rcu_nmi_exit() will see that the nesting count was non-zero, but became zero when decremented. This will cause rcu_nmi_exit() to atomically increment rtdp->dynticks, which will tell RCU to stop paying attention to this CPU.
Thanx, Paul
------------------------------------------------------------------------
rcu: Make rcu_nmi_enter() handle nesting
Andy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest, rcu_nmi_enter() and rcu_nmi_exit() must now correctly handle nesting. As luck would have it, rcu_nmi_exit() handles nesting but rcu_nmi_enter() does not. This patch therefore makes rcu_nmi_enter() handle nesting.
kernel/rcu/tree.c +2 -1
@@ -770,7 +770,8 @@ void rcu_nmi_enter(void)if (rdtp->dynticks_nmi_nesting ==0 && (atomic_read (&rdtp->dynticks) &0x1 ))return ; - rdtp->dynticks_nmi_nesting++; +if (rdtp->dynticks_nmi_nesting++ !=0 ) +return ;/* Nested NMI/IST/whatever. */ smp_mb__before_atomic ();/* Force delay from prior write. */ atomic_inc (&rdtp->dynticks);/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 56 lines · Paul E. McKenney
Quoted 36 lines · Andy Lutomirski
Quoted 34 lines · Paul E. McKenney
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution:NO_HZ_FULLand all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.Quoted 6 lines
Quoted 4 lines
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.I guess so, as long as there are really no page faults in the idle loop.
As far as I know, there are not. If there are, someone needs to let me know! ;-)
Quoted 2 lines
There are, however, machine checks in the idle loop, and maybe kprobes (haven't checked), so I think this patch might fix real bugs.
If you can get ISTs from the idle loop, then the patch is needed.
Quoted 4 lines
Quoted 3 lines
Just out of curiosity... Can an NMI occur in IST context? If it can, I need to make
rcu_nmi_enter()andrcu_nmi_exit()deal properly with nested calls.
Quoted 6 lines
Quoted 2 lines
Yes, and vice versa. That code looked like it handled nesting correctly, but I wasn't entirely sure.
It currently does not, please see below patch. Are you able to test nesting? It would be really cool if you could do so -- I have no way to test this patch.
I can try. It's sort of easy -- I'll put an int3 into do_nmi and add a fixup to avoid crashing.
What should I look for? Should I try to force full nohz on and assert something? I don't really know how to make full nohz work.
Quoted 27 lines
Quoted 4 lines
Also, just to make sure: are we okay if
rcu_nmi_enter()is called beforeexception_enterif context tracking is on and we came directly from userspace?If I understand correctly, this will result in context tracking invoking
rcu_user_enter(), which will result in thercu_dyntickscounter having an odd value. In that case,rcu_nmi_enter()will notice that RCU is already paying attention to this CPU via its check ofatomic_read(&rdtp->dynticks)&0x1), and will thus just return. The matchingrcu_nmi_exit()will notice that the nesting count is zero, and will also just return.Thus, everything works in that case.
In contrast, if
rcu_nmi_enter()was invoked from the idle loop, it would see that RCU is not paying attention to this CPU and that the NMI nesting depth (whichrcu_nmi_enter()increments) used to be zero. It would then atomically incrementrtdp->dynticks, forcing RCU to start paying attention to this CPU. The matchingrcu_nmi_exit()will see that the nesting count was non-zero, but became zero when decremented. This will causercu_nmi_exit()to atomically incrementrtdp->dynticks, which will tell RCU to stop paying attention to this CPU.Thanx, Paul
------------------------------------------------------------------------
Quoted 7 lines
rcu: Make
rcu_nmi_enter()handle nestingAndy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest,
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting. As luck would have it,rcu_nmi_exit()handles nesting butrcu_nmi_enter()does not. This patch therefore makesrcu_nmi_enter()handle nesting.
Thanks. Should I add this to v5 of my series?
--Andy
Quoted 18 lines
Signed-off-by Paul E. McKenney <[email protected]>kernel/rcu/tree.c +2
-1@@ -770,7 +770,8 @@ void rcu_nmi_enter(void)if (rdtp->dynticks_nmi_nesting ==0 && (atomic_read (&rdtp->dynticks) &0x1 ))return ; - rdtp->dynticks_nmi_nesting++; +if (rdtp->dynticks_nmi_nesting++ !=0 ) +return ;/* Nested NMI/IST/whatever. */ smp_mb__before_atomic ();/* Force delay from prior write. */ atomic_inc (&rdtp->dynticks);/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 65 lines · Andy Lutomirski
Quoted 56 lines · Paul E. McKenney
Quoted 36 lines · Andy Lutomirski
Quoted 34 lines · Paul E. McKenney
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution:NO_HZ_FULLand all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.Quoted 6 lines
Quoted 4 lines
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.I guess so, as long as there are really no page faults in the idle loop.
As far as I know, there are not. If there are, someone needs to let me know! ;-)
Quoted 2 lines
There are, however, machine checks in the idle loop, and maybe kprobes (haven't checked), so I think this patch might fix real bugs.
If you can get ISTs from the idle loop, then the patch is needed.
Quoted 4 lines
Quoted 3 lines
Just out of curiosity... Can an NMI occur in IST context? If it can, I need to make
rcu_nmi_enter()andrcu_nmi_exit()deal properly with nested calls.Quoted 6 lines
Quoted 2 lines
Yes, and vice versa. That code looked like it handled nesting correctly, but I wasn't entirely sure.
It currently does not, please see below patch. Are you able to test nesting? It would be really cool if you could do so -- I have no way to test this patch.
Quoted 5 lines
I can try. It's sort of easy -- I'll put an int3 into
do_nmiand add a fixup to avoid crashing.What should I look for? Should I try to force full nohz on and assert something? I don't really know how to make full nohz work.
You should look for the WARN_ON_ONCE() calls in rcu_nmi_enter() and rcu_nmi_exit() to fire.
Thanx, Paul
Quoted 63 lines
Quoted 26 lines
Quoted 3 lines
Also, just to make sure: are we okay if
rcu_nmi_enter()is called beforeexception_enterif context tracking is on and we came directly from userspace?If I understand correctly, this will result in context tracking invoking
rcu_user_enter(), which will result in thercu_dyntickscounter having an odd value. In that case,rcu_nmi_enter()will notice that RCU is already paying attention to this CPU via its check ofatomic_read(&rdtp->dynticks)&0x1), and will thus just return. The matchingrcu_nmi_exit()will notice that the nesting count is zero, and will also just return.Thus, everything works in that case.
In contrast, if
rcu_nmi_enter()was invoked from the idle loop, it would see that RCU is not paying attention to this CPU and that the NMI nesting depth (whichrcu_nmi_enter()increments) used to be zero. It would then atomically incrementrtdp->dynticks, forcing RCU to start paying attention to this CPU. The matchingrcu_nmi_exit()will see that the nesting count was non-zero, but became zero when decremented. This will causercu_nmi_exit()to atomically incrementrtdp->dynticks, which will tell RCU to stop paying attention to this CPU.Thanx, Paul
------------------------------------------------------------------------
Quoted 7 lines
rcu: Make
rcu_nmi_enter()handle nestingAndy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest,
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting. As luck would have it,rcu_nmi_exit()handles nesting butrcu_nmi_enter()does not. This patch therefore makesrcu_nmi_enter()handle nesting.Thanks. Should I add this to v5 of my series?
--Andy
Quoted 18 lines
Signed-off-by Paul E. McKenney <[email protected]>kernel/rcu/tree.c +2
-1@@ -770,7 +770,8 @@ void rcu_nmi_enter(void)if (rdtp->dynticks_nmi_nesting ==0 && (atomic_read (&rdtp->dynticks) &0x1 ))return ; - rdtp->dynticks_nmi_nesting++; +if (rdtp->dynticks_nmi_nesting++ !=0 ) +return ;/* Nested NMI/IST/whatever. */ smp_mb__before_atomic ();/* Force delay from prior write. */ atomic_inc (&rdtp->dynticks);/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 66 lines · Paul E. McKenney
Quoted 65 lines · Andy Lutomirski
Quoted 62 lines · Paul E. McKenney
Quoted 36 lines · Andy Lutomirski
Quoted 34 lines · Paul E. McKenney
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution:NO_HZ_FULLand all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.Quoted 6 lines
Quoted 4 lines
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.I guess so, as long as there are really no page faults in the idle loop.
As far as I know, there are not. If there are, someone needs to let me know! ;-)
Quoted 2 lines
There are, however, machine checks in the idle loop, and maybe kprobes (haven't checked), so I think this patch might fix real bugs.
If you can get ISTs from the idle loop, then the patch is needed.
Quoted 6 lines
Quoted 3 lines
Just out of curiosity... Can an NMI occur in IST context? If it can, I need to make
rcu_nmi_enter()andrcu_nmi_exit()deal properly with nested calls.Yes, and vice versa. That code looked like it handled nesting correctly, but I wasn't entirely sure.
It currently does not, please see below patch. Are you able to test nesting? It would be really cool if you could do so -- I have no way to test this patch.
Quoted 8 lines
Quoted 5 lines
I can try. It's sort of easy -- I'll put an int3 into
do_nmiand add a fixup to avoid crashing.What should I look for? Should I try to force full nohz on and assert something? I don't really know how to make full nohz work.
You should look for the
WARN_ON_ONCE()calls inrcu_nmi_enter()andrcu_nmi_exit()to fire.
No warning with or without your patch, maybe because all of those returns skip the labels.
Also, an NMI can happen *during* rcu_nmi_enter or rcu_nmi_exit. Is that okay? Should those dynticks_nmi_nesting++ things be local_inc and local_dec_and_test?
That dynticks_nmi_nesting thing seems scary to me. Shouldn't the code unconditionally increment dynticks_nmi_nesting in rcu_nmi_enter and unconditionally decrement it in rcu_nmi_exit?
--Andy
Quoted 67 lines
Thanx, Paul
Quoted 63 lines
Quoted 26 lines
Quoted 3 lines
Also, just to make sure: are we okay if
rcu_nmi_enter()is called beforeexception_enterif context tracking is on and we came directly from userspace?If I understand correctly, this will result in context tracking invoking
rcu_user_enter(), which will result in thercu_dyntickscounter having an odd value. In that case,rcu_nmi_enter()will notice that RCU is already paying attention to this CPU via its check ofatomic_read(&rdtp->dynticks)&0x1), and will thus just return. The matchingrcu_nmi_exit()will notice that the nesting count is zero, and will also just return.Thus, everything works in that case.
In contrast, if
rcu_nmi_enter()was invoked from the idle loop, it would see that RCU is not paying attention to this CPU and that the NMI nesting depth (whichrcu_nmi_enter()increments) used to be zero. It would then atomically incrementrtdp->dynticks, forcing RCU to start paying attention to this CPU. The matchingrcu_nmi_exit()will see that the nesting count was non-zero, but became zero when decremented. This will causercu_nmi_exit()to atomically incrementrtdp->dynticks, which will tell RCU to stop paying attention to this CPU.Thanx, Paul
------------------------------------------------------------------------
Quoted 7 lines
rcu: Make
rcu_nmi_enter()handle nestingAndy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest,
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting. As luck would have it,rcu_nmi_exit()handles nesting butrcu_nmi_enter()does not. This patch therefore makesrcu_nmi_enter()handle nesting.Thanks. Should I add this to v5 of my series?
--Andy
Quoted 9 lines
Signed-off-by Paul E. McKenney <[email protected]>kernel/rcu/tree.c +0
-0kernel/rcu/tree.c void rcu_nmi_enter(void) · line 777
Quoted 9 lines
kernel/rcu/tree.c +2
-1@@ -770,7 +770,8 @@ void rcu_nmi_enter(void)if (rdtp->dynticks_nmi_nesting ==0 && (atomic_read (&rdtp->dynticks) &0x1 ))return ; - rdtp->dynticks_nmi_nesting++; +if (rdtp->dynticks_nmi_nesting++ !=0 ) +return ;/* Nested NMI/IST/whatever. */ smp_mb__before_atomic ();/* Force delay from prior write. */ atomic_inc (&rdtp->dynticks);/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ --
Andy Lutomirski
AMA Capital Management, LLC
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 71 lines · Andy Lutomirski
Quoted 69 lines · Paul E. McKenney
Quoted 68 lines · Andy Lutomirski
Quoted 56 lines · Paul E. McKenney
Quoted 36 lines · Andy Lutomirski
Quoted 34 lines · Paul E. McKenney
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution:NO_HZ_FULLand all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.Quoted 6 lines
Quoted 4 lines
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.I guess so, as long as there are really no page faults in the idle loop.
As far as I know, there are not. If there are, someone needs to let me know! ;-)
Quoted 2 lines
There are, however, machine checks in the idle loop, and maybe kprobes (haven't checked), so I think this patch might fix real bugs.
If you can get ISTs from the idle loop, then the patch is needed.
Quoted 4 lines
Quoted 3 lines
Just out of curiosity... Can an NMI occur in IST context? If it can, I need to make
rcu_nmi_enter()andrcu_nmi_exit()deal properly with nested calls.Quoted 6 lines
Quoted 2 lines
Yes, and vice versa. That code looked like it handled nesting correctly, but I wasn't entirely sure.
It currently does not, please see below patch. Are you able to test nesting? It would be really cool if you could do so -- I have no way to test this patch.
I can try. It's sort of easy -- I'll put an int3 into
do_nmiand add a fixup to avoid crashing.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
What should I look for? Should I try to force full nohz on and assert something? I don't really know how to make full nohz work.
You should look for the
WARN_ON_ONCE()calls inrcu_nmi_enter()andrcu_nmi_exit()to fire.No warning with or without your patch, maybe because all of those returns skip the labels.
I will be guardedly optimistic and take this as a good sign. ;-)
Quoted 3 lines
Also, an NMI can happen *during*
rcu_nmi_enterorrcu_nmi_exit. Is that okay? Should those dynticks_nmi_nesting++ things belocal_incandlocal_dec_and_test?
Yep, it is OK during rcu_nmi_enter() or rcu_nmi_exit(). The nested NMI will put the dynticks_nmi_nesting counter back where it was, so no chance of confusion.
Quoted 3 lines
That
dynticks_nmi_nestingthing seems scary to me. Shouldn't the code unconditionally incrementdynticks_nmi_nestinginrcu_nmi_enterand unconditionally decrement it inrcu_nmi_exit?
You might be able to get that to work, but the reason it is not done that way is because we might get an NMI while not in dyntick-idle state. In that case, it would be very bad to atomically increment rcu_dynticks, because that would tell RCU to ignore the CPU while it was in the NMI handler, which is the opposite of what we want.
But what did you have in mind?
Thanx, Paul
Quoted 76 lines
--Andy
Quoted 67 lines
Thanx, Paul
Quoted 63 lines
Quoted 26 lines
Quoted 3 lines
Also, just to make sure: are we okay if
rcu_nmi_enter()is called beforeexception_enterif context tracking is on and we came directly from userspace?If I understand correctly, this will result in context tracking invoking
rcu_user_enter(), which will result in thercu_dyntickscounter having an odd value. In that case,rcu_nmi_enter()will notice that RCU is already paying attention to this CPU via its check ofatomic_read(&rdtp->dynticks)&0x1), and will thus just return. The matchingrcu_nmi_exit()will notice that the nesting count is zero, and will also just return.Thus, everything works in that case.
In contrast, if
rcu_nmi_enter()was invoked from the idle loop, it would see that RCU is not paying attention to this CPU and that the NMI nesting depth (whichrcu_nmi_enter()increments) used to be zero. It would then atomically incrementrtdp->dynticks, forcing RCU to start paying attention to this CPU. The matchingrcu_nmi_exit()will see that the nesting count was non-zero, but became zero when decremented. This will causercu_nmi_exit()to atomically incrementrtdp->dynticks, which will tell RCU to stop paying attention to this CPU.Thanx, Paul
------------------------------------------------------------------------
Quoted 7 lines
rcu: Make
rcu_nmi_enter()handle nestingAndy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest,
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting. As luck would have it,rcu_nmi_exit()handles nesting butrcu_nmi_enter()does not. This patch therefore makesrcu_nmi_enter()handle nesting.Thanks. Should I add this to v5 of my series?
--Andy
Quoted 9 lines
Signed-off-by Paul E. McKenney <[email protected]>kernel/rcu/tree.c +0
-0kernel/rcu/tree.c void rcu_nmi_enter(void) · line 777
Quoted 9 lines
kernel/rcu/tree.c +2
-1@@ -770,7 +770,8 @@ void rcu_nmi_enter(void)if (rdtp->dynticks_nmi_nesting ==0 && (atomic_read (&rdtp->dynticks) &0x1 ))return ; - rdtp->dynticks_nmi_nesting++; +if (rdtp->dynticks_nmi_nesting++ !=0 ) +return ;/* Nested NMI/IST/whatever. */ smp_mb__before_atomic ();/* Force delay from prior write. */ atomic_inc (&rdtp->dynticks);/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ --
Andy Lutomirski
AMA Capital Management, LLCSignature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 83 lines · Paul E. McKenney
Quoted 71 lines · Andy Lutomirski
Quoted 69 lines · Paul E. McKenney
Quoted 68 lines · Andy Lutomirski
Quoted 56 lines · Paul E. McKenney
Quoted 36 lines · Andy Lutomirski
Quoted 34 lines · Paul E. McKenney
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution:NO_HZ_FULLand all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.Quoted 6 lines
Quoted 4 lines
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.I guess so, as long as there are really no page faults in the idle loop.
As far as I know, there are not. If there are, someone needs to let me know! ;-)
Quoted 2 lines
There are, however, machine checks in the idle loop, and maybe kprobes (haven't checked), so I think this patch might fix real bugs.
If you can get ISTs from the idle loop, then the patch is needed.
Quoted 4 lines
Quoted 3 lines
Just out of curiosity... Can an NMI occur in IST context? If it can, I need to make
rcu_nmi_enter()andrcu_nmi_exit()deal properly with nested calls.Quoted 6 lines
Quoted 2 lines
Yes, and vice versa. That code looked like it handled nesting correctly, but I wasn't entirely sure.
It currently does not, please see below patch. Are you able to test nesting? It would be really cool if you could do so -- I have no way to test this patch.
I can try. It's sort of easy -- I'll put an int3 into
do_nmiand add a fixup to avoid crashing.Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
What should I look for? Should I try to force full nohz on and assert something? I don't really know how to make full nohz work.
You should look for the
WARN_ON_ONCE()calls inrcu_nmi_enter()andrcu_nmi_exit()to fire.No warning with or without your patch, maybe because all of those returns skip the labels.
I will be guardedly optimistic and take this as a good sign. ;-)
Quoted 8 lines
Quoted 3 lines
Also, an NMI can happen *during*
rcu_nmi_enterorrcu_nmi_exit. Is that okay? Should those dynticks_nmi_nesting++ things belocal_incandlocal_dec_and_test?Yep, it is OK during
rcu_nmi_enter()orrcu_nmi_exit(). The nested NMI will put thedynticks_nmi_nestingcounter back where it was, so no chance of confusion.
That sounds like it's making a scary assumption about the code generated by the ++ operator.
Quoted 11 lines
Quoted 3 lines
That
dynticks_nmi_nestingthing seems scary to me. Shouldn't the code unconditionally incrementdynticks_nmi_nestinginrcu_nmi_enterand unconditionally decrement it inrcu_nmi_exit?You might be able to get that to work, but the reason it is not done that way is because we might get an NMI while not in dyntick-idle state. In that case, it would be very bad to atomically increment
rcu_dynticks, because that would tell RCU to ignore the CPU while it was in the NMI handler, which is the opposite of what we want.But what did you have in mind?
If you're willing to have rcu_nmi_enter return a value and pass it back to rcu_nmi_exit, then it could be like:
int rcu_nmi_enter()
{
if (rdtp->dynticks & 1) {
return 0;
} else {
rdtp->dynticks++;
return 1; }
}
but wrapped in a cmpxchg loop to make it atomic, and
void rcu_nmi_exit(int state)
{
if (state)
rdtp->dynticks++;
}
Otherwise, I think that there may need to be enough state somewhere so that the outermost nested rcu_nmi_enter knows whether to increment dynticks. For example, dynticks_nmi_nesting could store the nesting count * 2 - (1 if the outermost nested user needs to increment dynticks). Something like:
void rcu_nmi_enter(void)
{
/* Be very careful -- this function may be called reentrently on the
same CPU. */
atomically: increment dynticks if it's even.
/* If an rcu_nmi_enter/rcu_nmi_exit pair happens here, then it will not change
* the state. */
local_inc(&dynticks_nmi_nesting, (we incremented dynticks ? 1 : 2));
WARN_ON(we incremented dynticks and dynticks_nmi_nesting was nonzero);
}
void rcu_nmi_exit(void)
{
WARN_ON(!(dynticks & 1));
locally atomically: dynticks_nmi_nesting -= 2, unlessdynticks_nmi_nesting == 1, in which case set it to zero
if (dynticks_nmi_nesting was 1)
atomic_inc(&dynticks); }
The invariant here is that, for a single unnested enter/exit, if dynticks_nmi_nesting != 0, then dynticks is odd. As a result, an rcu_nmi_enter/rcu_nmi_exit pair at any time when dynticks_nmi_nesting != 0 *or* dynticks is odd will have no net effect, so the invariant, in fact, holds for all invocations, nested or otherwise.
At least one of those conditions is true at all times during the execution of outermost pair, starting with the first atomic operation and ending with the final atomic_inc. So they nest properly no matter what else happens (unless, of course, someone else pokes dynticks in the middle).
Thoughts?
Quoted 80 lines
Thanx, Paul
Quoted 76 lines
--Andy
Quoted 67 lines
Thanx, Paul
Quoted 63 lines
Quoted 26 lines
Quoted 3 lines
Also, just to make sure: are we okay if
rcu_nmi_enter()is called beforeexception_enterif context tracking is on and we came directly from userspace?If I understand correctly, this will result in context tracking invoking
rcu_user_enter(), which will result in thercu_dyntickscounter having an odd value. In that case,rcu_nmi_enter()will notice that RCU is already paying attention to this CPU via its check ofatomic_read(&rdtp->dynticks)&0x1), and will thus just return. The matchingrcu_nmi_exit()will notice that the nesting count is zero, and will also just return.Thus, everything works in that case.
In contrast, if
rcu_nmi_enter()was invoked from the idle loop, it would see that RCU is not paying attention to this CPU and that the NMI nesting depth (whichrcu_nmi_enter()increments) used to be zero. It would then atomically incrementrtdp->dynticks, forcing RCU to start paying attention to this CPU. The matchingrcu_nmi_exit()will see that the nesting count was non-zero, but became zero when decremented. This will causercu_nmi_exit()to atomically incrementrtdp->dynticks, which will tell RCU to stop paying attention to this CPU.Thanx, Paul
------------------------------------------------------------------------
Quoted 7 lines
rcu: Make
rcu_nmi_enter()handle nestingAndy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest,
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting. As luck would have it,rcu_nmi_exit()handles nesting butrcu_nmi_enter()does not. This patch therefore makesrcu_nmi_enter()handle nesting.Thanks. Should I add this to v5 of my series?
--Andy
Quoted 9 lines
Signed-off-by Paul E. McKenney <[email protected]>kernel/rcu/tree.c +0
-0kernel/rcu/tree.c void rcu_nmi_enter(void) · line 777
Quoted 9 lines
kernel/rcu/tree.c +2
-1@@ -770,7 +770,8 @@ void rcu_nmi_enter(void)if (rdtp->dynticks_nmi_nesting ==0 && (atomic_read (&rdtp->dynticks) &0x1 ))return ; - rdtp->dynticks_nmi_nesting++; +if (rdtp->dynticks_nmi_nesting++ !=0 ) +return ;/* Nested NMI/IST/whatever. */ smp_mb__before_atomic ();/* Force delay from prior write. */ atomic_inc (&rdtp->dynticks);/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ --
Andy Lutomirski
AMA Capital Management, LLC--
Andy Lutomirski
AMA Capital Management, LLC
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 89 lines · Andy Lutomirski
Quoted 87 lines · Paul E. McKenney
Quoted 71 lines · Andy Lutomirski
Quoted 69 lines · Paul E. McKenney
Quoted 68 lines · Andy Lutomirski
Quoted 56 lines · Paul E. McKenney
Quoted 36 lines · Andy Lutomirski
Quoted 34 lines · Paul E. McKenney
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution:NO_HZ_FULLand all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.Quoted 6 lines
Quoted 4 lines
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.I guess so, as long as there are really no page faults in the idle loop.
As far as I know, there are not. If there are, someone needs to let me know! ;-)
Quoted 2 lines
There are, however, machine checks in the idle loop, and maybe kprobes (haven't checked), so I think this patch might fix real bugs.
If you can get ISTs from the idle loop, then the patch is needed.
Quoted 4 lines
Quoted 3 lines
Just out of curiosity... Can an NMI occur in IST context? If it can, I need to make
rcu_nmi_enter()andrcu_nmi_exit()deal properly with nested calls.Quoted 6 lines
Quoted 2 lines
Yes, and vice versa. That code looked like it handled nesting correctly, but I wasn't entirely sure.
It currently does not, please see below patch. Are you able to test nesting? It would be really cool if you could do so -- I have no way to test this patch.
I can try. It's sort of easy -- I'll put an int3 into
do_nmiand add a fixup to avoid crashing.Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
What should I look for? Should I try to force full nohz on and assert something? I don't really know how to make full nohz work.
You should look for the
WARN_ON_ONCE()calls inrcu_nmi_enter()andrcu_nmi_exit()to fire.No warning with or without your patch, maybe because all of those returns skip the labels.
I will be guardedly optimistic and take this as a good sign. ;-)
Quoted 3 lines
Also, an NMI can happen *during*
rcu_nmi_enterorrcu_nmi_exit. Is that okay? Should those dynticks_nmi_nesting++ things belocal_incandlocal_dec_and_test?
Quoted 6 lines
Quoted 3 lines
Yep, it is OK during
rcu_nmi_enter()orrcu_nmi_exit(). The nested NMI will put thedynticks_nmi_nestingcounter back where it was, so no chance of confusion.That sounds like it's making a scary assumption about the code generated by the ++ operator.
Yeah, well, local_inc becomes the ++ operator on many systems. ;-)
I could perhaps be convinced to use ACCESS_ONCE(), will give it some thought.
Quoted 25 lines
Quoted 11 lines
Quoted 3 lines
That
dynticks_nmi_nestingthing seems scary to me. Shouldn't the code unconditionally incrementdynticks_nmi_nestinginrcu_nmi_enterand unconditionally decrement it inrcu_nmi_exit?You might be able to get that to work, but the reason it is not done that way is because we might get an NMI while not in dyntick-idle state. In that case, it would be very bad to atomically increment
rcu_dynticks, because that would tell RCU to ignore the CPU while it was in the NMI handler, which is the opposite of what we want.But what did you have in mind?
If you're willing to have
rcu_nmi_enterreturn a value and pass it back torcu_nmi_exit, then it could be like:int
rcu_nmi_enter()
{if (rdtp->dynticks & 1) { return 0; } else { rdtp->dynticks++; return 1;}
}
Quoted 7 lines
but wrapped in a cmpxchg loop to make it atomic, and
void
rcu_nmi_exit(int state)
{
if (state)
rdtp->dynticks++;
}
Returning state sounds like a bad idea, if we can reasonably avoid it.
And I think I finally see what you are pointing out about my code: If another NMI comes in between the time I increment ->dynticks_nmi_nesting and the time I atomically increment ->dynticks, the nested NMI handler will incorrectly believe that RCU is already paying attention to this CPU. Which would indeed not be at all good, so good catch!!!
Quoted 36 lines
Otherwise, I think that there may need to be enough state somewhere so that the outermost nested
rcu_nmi_enterknows whether to increment dynticks. For example,dynticks_nmi_nestingcould store the nesting count * 2 - (1 if the outermost nested user needs to increment dynticks). Something like:void
rcu_nmi_enter(void)
{
/* Be very careful -- this function may be called reentrently on the
same CPU. */
atomically: increment dynticks if it's even./* If an rcu_nmi_enter/rcu_nmi_exit pair happens here, then it will not change
* the state. */local_inc(&dynticks_nmi_nesting, (we incremented dynticks ? 1 : 2));
WARN_ON(we incremented dynticks and dynticks_nmi_nesting was nonzero);
}void
rcu_nmi_exit(void)
{
WARN_ON(!(dynticks & 1));
locally atomically:dynticks_nmi_nesting-= 2, unlessdynticks_nmi_nesting== 1, in which case set it to zeroif (
dynticks_nmi_nestingwas 1)atomic_inc(&dynticks); }The invariant here is that, for a single unnested enter/exit, if
dynticks_nmi_nesting!= 0, then dynticks is odd. As a result, an rcu_nmi_enter/rcu_nmi_exit pair at any time whendynticks_nmi_nesting!= 0 *or* dynticks is odd will have no net effect, so the invariant, in fact, holds for all invocations, nested or otherwise.
Quoted 7 lines
At least one of those conditions is true at all times during the execution of outermost pair, starting with the first atomic operation and ending with the final
atomic_inc. So they nest properly no matter what else happens (unless, of course, someone else pokes dynticks in the middle).Thoughts?
Let's see... The evenness of ->dynticks should be preserved by nested NMI handlers, so the check and increment need not be atomic. We don't have any way (other than atomic operations) to do local atomic modifications on all architectures, because we cannot mask NMIs. (Yes, it can work on x86, but this is common code that needs to work everywhere.) On the other hand, presumably NMIs are rare, so atomic modification of the NMI nesting counter should be OK, at least if it proves absolutely necessary. And I am thinking that a mechanical proof will be needed here. :-/
But first, let me try generating the code and informally evaluating it:
1 struct rcu_dynticks {
2 long long dynticks_nesting;
3 int dynticks_nmi_nesting;
4 atomic_t dynticks;
5 };
6
7 void rcu_nmi_enter(void)
8 {
9 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);
10 int incby = 2;
11
12 if (!(atomic_read(&rdtp->dynticks) & 0x1)) {
13 smp_mb__before_atomic();
14 atomic_inc(&rdtp->dynticks);
15 smp_mb__after_atomic();
16 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1));
17 incby = 1;
18 }
19 rdtp->dynticks_nmi_nesting += incby;
20 barrier();
21 }
22
23 void rcu_nmi_exit(void)
24 {
25 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);
26
27 WARN_ON_ONCE(!rdtp->dynticks_nmi_nesting);
28 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1));
29 if (rdtp->dynticks_nmi_nesting != 1) {
30 rdtp->dynticks_nmi_nesting -= 2;
31 return;
32 }
33 rdtp->dynticks_nmi_nesting = 0;
34 smp_mb__before_atomic();
35 atomic_inc(&rdtp->dynticks);
36 smp_mb__after_atomic();
37 WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1);
38 }Line 9 picks up a pointer to this CPU's rcu_dynticks structure and line 10 assumes that we don't need to increment ->dynticks.
Line 12 checks to see if ->dynticks is even. Note that this check is
stable: If there are nested NMIs, they will increment ->dynticks twice or not at all, and either way preserves the evenness (to be proven, of course, but that is the plan). If ->dynticks is even, lines 13-15 atomically increment it, line 16 complains if still even, and line 17 says we will increment ->dynticks_nmi_nesting by only 1.
Either way, line 19 increments ->dynticks_nmi_nesting as needed and line 20 keeps the compiler from getting too cute.
For rcu_nmi_exit(), line 25 again picks up this CPUs rcu_dynticks structure. Lines 27 and 28 complain bitterly if invariants are violated. If line 29 finds that the value of ->dynticks_nmi_nesting is not 1, then line 30 subtracts 2 from ->dynticks_nmi_nesting and line 31 returns.
Otherwise, line 33 sets ->dynticks_nmi_nesting to zero, lines 34-36 atomically increment ->dynticks with full ordering, and line 37 complains bitterly if ->dynticks is not even.
So, if an NMI occurs before rcu_nmi_enter's atomic increment, then the nested NMI's rcu_nmi_enter() and rcu_nmi_exit() will think that they are not nested, which is the correct thing for them to think in that case. They will increment ->dynticks twice and restore ->dynticks_nmi_nesting to zero (adding and then subtracting 1). If the NMI happens after the atomic increment, then the nested rcu_nmi_enter() and rcu_nmi_exit() will leave ->dynticks alone, and will restore ->dynticks_nmi_nesting to zero (adding and subtracting two again). If the NMI happens after the increment of ->dynticks_nmi_nesting, the nested NMI's rcu_nmi_enter() and rcu_nmi_exit() will again restore ->dynticks_nmi_nesting, but this time to one (again adding and subtracting two).
In rcu_nmi_exit(), ->dynticks_nmi_nesting of zero had better not happen, one means we need to atomically increment ->dynticks, and other values mean that we are partially or fully nested. Reasoning proceeds as for rcu_nmi_enter(), but in the opposite direction.
Whew! That might even work.
But how about taking a different approach. Assuming that there can never be more than (say) 14 nesting NMI-like things, use the lower four bits of ->dynticks to represent the NMI nesting and the upper 28 bits as the counter. This of course requires modifying lots of places in RCU that check the counter, but it is probably time to abstract the check anyway.
This would allow my earlier attempted logic to work and (maybe) simplify the reasoning a bit (and yes, the "magic" constants need macros):
void rcu_nmi_enter(void)
{
struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);
int nesting = atomic_read(&rdtp->dynticks) & 0xf;
int incby = 0x01;
WARN_ON_ONCE(nexting == 0xf);
if (nesting == 0) {
if (atomic_read(&rdtp->dynticks) & 0x10)
return;
incby = 0x11;
}
smp_mb__before_atomic();
atomic_add(&rdtp->dynticks, incby);
smp_mb__after_atomic();
WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1));
}
void rcu_nmi_exit(void)
{
struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);
int nesting = atomic_read(&rdtp->dynticks) & 0xf;
int incby = 0x0f;
if (nesting == 0)
return;
if (nesting > 1)
incby = -1;
smp_mb__before_atomic();
atomic_add(&rdtp->dynticks, incby);
smp_mb__after_atomic();
WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1);
}Over to you! ;-)
Thanx, Paul
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 8 lines · Paul E. McKenney
Quoted 5 lines · Andy Lutomirski
Quoted 3 lines · Paul E. McKenney
Quoted 2 lines · Andy Lutomirski
On Fri, Nov 21, 2014 at 3:38 PM, Paul E. McKenney
<[email protected]> wrote:Returning state sounds like a bad idea, if we can reasonably avoid it.
I agree, except that we already do it for exception_enter(), etc. But yes, changing fewer things is nice.
Quoted 71 lines
And I think I finally see what you are pointing out about my code: If another NMI comes in between the time I increment ->dynticks_nmi_nesting and the time I atomically increment ->dynticks, the nested NMI handler will incorrectly believe that RCU is already paying attention to this CPU. Which would indeed not be at all good, so good catch!!!
Quoted 36 lines
Otherwise, I think that there may need to be enough state somewhere so that the outermost nested
rcu_nmi_enterknows whether to increment dynticks. For example,dynticks_nmi_nestingcould store the nesting count * 2 - (1 if the outermost nested user needs to increment dynticks). Something like:void
rcu_nmi_enter(void)
{
/* Be very careful -- this function may be called reentrently on the
same CPU. */
atomically: increment dynticks if it's even./* If an rcu_nmi_enter/rcu_nmi_exit pair happens here, then it will not change
* the state. */local_inc(&dynticks_nmi_nesting, (we incremented dynticks ? 1 : 2));
WARN_ON(we incremented dynticks and dynticks_nmi_nesting was nonzero);
}void
rcu_nmi_exit(void)
{
WARN_ON(!(dynticks & 1));
locally atomically:dynticks_nmi_nesting-= 2, unlessdynticks_nmi_nesting== 1, in which case set it to zeroif (
dynticks_nmi_nestingwas 1)atomic_inc(&dynticks); }The invariant here is that, for a single unnested enter/exit, if
dynticks_nmi_nesting!= 0, then dynticks is odd. As a result, an rcu_nmi_enter/rcu_nmi_exit pair at any time whendynticks_nmi_nesting!= 0 *or* dynticks is odd will have no net effect, so the invariant, in fact, holds for all invocations, nested or otherwise.Quoted 7 lines
At least one of those conditions is true at all times during the execution of outermost pair, starting with the first atomic operation and ending with the final
atomic_inc. So they nest properly no matter what else happens (unless, of course, someone else pokes dynticks in the middle).Thoughts?
Let's see... The evenness of ->dynticks should be preserved by nested NMI handlers, so the check and increment need not be atomic. We don't have any way (other than atomic operations) to do local atomic modifications on all architectures, because we cannot mask NMIs. (Yes, it can work on x86, but this is common code that needs to work everywhere.) On the other hand, presumably NMIs are rare, so atomic modification of the NMI nesting counter should be OK, at least if it proves absolutely necessary. And I am thinking that a mechanical proof will be needed here. :-/
But first, let me try generating the code and informally evaluating it:
1 struct rcu_dynticks { 2 long long dynticks_nesting; 3 int dynticks_nmi_nesting; 4 atomic_t dynticks; 5 }; 6 7 void rcu_nmi_enter(void) 8 { 9 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);
Quoted 8 lines
10 int incby = 2;
11
12 if (!(atomic_read(&rdtp->dynticks) & 0x1)) {
13 smp_mb__before_atomic();
14 atomic_inc(&rdtp->dynticks);
15 smp_mb__after_atomic();
16 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1));
17 incby = 1;WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 1) here, perhaps?
Quoted 2 lines
18 }
19 rdtp->dynticks_nmi_nesting += incby;Oh, I see why you don't need local_add -- it's because an nmi in the middle of this increment won't have any effect on the interrupted code, so even a software RMW will be okay.
Quoted 10 lines
20 barrier();
21 }
22
23 void rcu_nmi_exit(void)
24 {
25 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);
26
27 WARN_ON_ONCE(!rdtp->dynticks_nmi_nesting);
28 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1));
29 if (rdtp->dynticks_nmi_nesting != 1) {WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 2), perhaps?
Quoted 5 lines
30 rdtp->dynticks_nmi_nesting -= 2;
31 return;
32 }
33 rdtp->dynticks_nmi_nesting = 0;
34 smp_mb__before_atomic();This implies barrier(), right?
Quoted 39 lines
35 atomic_inc(&rdtp->dynticks); 36 smp_mb__after_atomic(); 37 WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); 38 }Line 9 picks up a pointer to this CPU's
rcu_dynticksstructure and line 10 assumes that we don't need to increment ->dynticks.Line 12 checks to see if ->dynticks is even. Note that this check is
stable: If there are nested NMIs, they will increment ->dynticks twice or not at all, and either way preserves the evenness (to be proven, of course, but that is the plan). If ->dynticks is even, lines 13-15 atomically increment it, line 16 complains if still even, and line 17 says we will increment ->dynticks_nmi_nesting by only 1.Either way, line 19 increments ->dynticks_nmi_nesting as needed and line 20 keeps the compiler from getting too cute.
For
rcu_nmi_exit(), line 25 again picks up this CPUsrcu_dynticksstructure. Lines 27 and 28 complain bitterly if invariants are violated. If line 29 finds that the value of ->dynticks_nmi_nesting is not 1, then line 30 subtracts 2 from ->dynticks_nmi_nesting and line 31 returns.Otherwise, line 33 sets ->dynticks_nmi_nesting to zero, lines 34-36 atomically increment ->dynticks with full ordering, and line 37 complains bitterly if ->dynticks is not even.
So, if an NMI occurs before rcu_nmi_enter's atomic increment, then the nested NMI's
rcu_nmi_enter()andrcu_nmi_exit()will think that they are not nested, which is the correct thing for them to think in that case. They will increment ->dynticks twice and restore ->dynticks_nmi_nesting to zero (adding and then subtracting 1). If the NMI happens after the atomic increment, then the nestedrcu_nmi_enter()andrcu_nmi_exit()will leave ->dynticks alone, and will restore ->dynticks_nmi_nesting to zero (adding and subtracting two again). If the NMI happens after the increment of ->dynticks_nmi_nesting, the nested NMI'srcu_nmi_enter()andrcu_nmi_exit()will again restore ->dynticks_nmi_nesting, but this time to one (again adding and subtracting two).
Quoted 6 lines
In
rcu_nmi_exit(), ->dynticks_nmi_nesting of zero had better not happen, one means we need to atomically increment ->dynticks, and other values mean that we are partially or fully nested. Reasoning proceeds as forrcu_nmi_enter(), but in the opposite direction.Whew! That might even work.
I think I like this, with the warnings above.
Quoted 45 lines
But how about taking a different approach. Assuming that there can never be more than (say) 14 nesting NMI-like things, use the lower four bits of ->dynticks to represent the NMI nesting and the upper 28 bits as the counter. This of course requires modifying lots of places in RCU that check the counter, but it is probably time to abstract the check anyway.
This would allow my earlier attempted logic to work and (maybe) simplify the reasoning a bit (and yes, the "magic" constants need macros):
void rcu_nmi_enter(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x01; WARN_ON_ONCE(nexting == 0xf); if (nesting == 0) { if (atomic_read(&rdtp->dynticks) & 0x10) return; incby = 0x11; } smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); } void rcu_nmi_exit(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x0f; if (nesting == 0) return; if (nesting > 1) incby = -1; smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); }
Quoted 1 line
Over to you! ;-)
This latter one is all you :)
--Andy
Quoted 3 lines
Thanx, Paul
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 94 lines · Andy Lutomirski
Quoted 8 lines · Paul E. McKenney
Quoted 5 lines · Andy Lutomirski
Quoted 3 lines · Paul E. McKenney
Quoted 2 lines · Andy Lutomirski
On Fri, Nov 21, 2014 at 3:38 PM, Paul E. McKenney
<[email protected]> wrote:Returning state sounds like a bad idea, if we can reasonably avoid it.
I agree, except that we already do it for
exception_enter(), etc. But yes, changing fewer things is nice.Quoted 71 lines
And I think I finally see what you are pointing out about my code: If another NMI comes in between the time I increment ->dynticks_nmi_nesting and the time I atomically increment ->dynticks, the nested NMI handler will incorrectly believe that RCU is already paying attention to this CPU. Which would indeed not be at all good, so good catch!!!
Quoted 36 lines
Otherwise, I think that there may need to be enough state somewhere so that the outermost nested
rcu_nmi_enterknows whether to increment dynticks. For example,dynticks_nmi_nestingcould store the nesting count * 2 - (1 if the outermost nested user needs to increment dynticks). Something like:void
rcu_nmi_enter(void)
{
/* Be very careful -- this function may be called reentrently on the
same CPU. */
atomically: increment dynticks if it's even./* If an rcu_nmi_enter/rcu_nmi_exit pair happens here, then it will not change
* the state. */local_inc(&dynticks_nmi_nesting, (we incremented dynticks ? 1 : 2));
WARN_ON(we incremented dynticks and dynticks_nmi_nesting was nonzero);
}void
rcu_nmi_exit(void)
{
WARN_ON(!(dynticks & 1));
locally atomically:dynticks_nmi_nesting-= 2, unlessdynticks_nmi_nesting== 1, in which case set it to zeroif (
dynticks_nmi_nestingwas 1)atomic_inc(&dynticks); }The invariant here is that, for a single unnested enter/exit, if
dynticks_nmi_nesting!= 0, then dynticks is odd. As a result, an rcu_nmi_enter/rcu_nmi_exit pair at any time whendynticks_nmi_nesting!= 0 *or* dynticks is odd will have no net effect, so the invariant, in fact, holds for all invocations, nested or otherwise.Quoted 7 lines
At least one of those conditions is true at all times during the execution of outermost pair, starting with the first atomic operation and ending with the final
atomic_inc. So they nest properly no matter what else happens (unless, of course, someone else pokes dynticks in the middle).Thoughts?
Let's see... The evenness of ->dynticks should be preserved by nested NMI handlers, so the check and increment need not be atomic. We don't have any way (other than atomic operations) to do local atomic modifications on all architectures, because we cannot mask NMIs. (Yes, it can work on x86, but this is common code that needs to work everywhere.) On the other hand, presumably NMIs are rare, so atomic modification of the NMI nesting counter should be OK, at least if it proves absolutely necessary. And I am thinking that a mechanical proof will be needed here. :-/
But first, let me try generating the code and informally evaluating it:
1 struct rcu_dynticks { 2 long long dynticks_nesting; 3 int dynticks_nmi_nesting; 4 atomic_t dynticks; 5 }; 6 7 void rcu_nmi_enter(void) 8 { 9 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);Quoted 8 lines
10 int incby = 2; 11 12 if (!(atomic_read(&rdtp->dynticks) & 0x1)) { 13 smp_mb__before_atomic(); 14 atomic_inc(&rdtp->dynticks); 15 smp_mb__after_atomic(); 16 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 17 incby = 1;
Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 1)here, perhaps?
That would make sense.
Quoted 6 lines
Quoted 2 lines
18 } 19 rdtp->dynticks_nmi_nesting += incby;Oh, I see why you don't need
local_add-- it's because an nmi in the middle of this increment won't have any effect on the interrupted code, so even a software RMW will be okay.
Yep! ;-)
Quoted 11 lines
Quoted 10 lines
20 barrier(); 21 } 22 23 void rcu_nmi_exit(void) 24 { 25 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); 26 27 WARN_ON_ONCE(!rdtp->dynticks_nmi_nesting); 28 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 29 if (rdtp->dynticks_nmi_nesting != 1) {
Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 2), perhaps?
This is already implied by the WARN_ON_ONCE() on line 27 and the check on line 29.
Quoted 7 lines
Quoted 5 lines
30 rdtp->dynticks_nmi_nesting -= 2; 31 return; 32 } 33 rdtp->dynticks_nmi_nesting = 0; 34 smp_mb__before_atomic();This implies
barrier(), right?
Yep!
Quoted 39 lines
Quoted 39 lines
35 atomic_inc(&rdtp->dynticks); 36 smp_mb__after_atomic(); 37 WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); 38 }Line 9 picks up a pointer to this CPU's
rcu_dynticksstructure and line 10 assumes that we don't need to increment ->dynticks.Line 12 checks to see if ->dynticks is even. Note that this check is
stable: If there are nested NMIs, they will increment ->dynticks twice or not at all, and either way preserves the evenness (to be proven, of course, but that is the plan). If ->dynticks is even, lines 13-15 atomically increment it, line 16 complains if still even, and line 17 says we will increment ->dynticks_nmi_nesting by only 1.Either way, line 19 increments ->dynticks_nmi_nesting as needed and line 20 keeps the compiler from getting too cute.
For
rcu_nmi_exit(), line 25 again picks up this CPUsrcu_dynticksstructure. Lines 27 and 28 complain bitterly if invariants are violated. If line 29 finds that the value of ->dynticks_nmi_nesting is not 1, then line 30 subtracts 2 from ->dynticks_nmi_nesting and line 31 returns.Otherwise, line 33 sets ->dynticks_nmi_nesting to zero, lines 34-36 atomically increment ->dynticks with full ordering, and line 37 complains bitterly if ->dynticks is not even.
So, if an NMI occurs before rcu_nmi_enter's atomic increment, then the nested NMI's
rcu_nmi_enter()andrcu_nmi_exit()will think that they are not nested, which is the correct thing for them to think in that case. They will increment ->dynticks twice and restore ->dynticks_nmi_nesting to zero (adding and then subtracting 1). If the NMI happens after the atomic increment, then the nestedrcu_nmi_enter()andrcu_nmi_exit()will leave ->dynticks alone, and will restore ->dynticks_nmi_nesting to zero (adding and subtracting two again). If the NMI happens after the increment of ->dynticks_nmi_nesting, the nested NMI'srcu_nmi_enter()andrcu_nmi_exit()will again restore ->dynticks_nmi_nesting, but this time to one (again adding and subtracting two).
Quoted 8 lines
Quoted 6 lines
In
rcu_nmi_exit(), ->dynticks_nmi_nesting of zero had better not happen, one means we need to atomically increment ->dynticks, and other values mean that we are partially or fully nested. Reasoning proceeds as forrcu_nmi_enter(), but in the opposite direction.Whew! That might even work.
I think I like this, with the warnings above.
OK with dropping the one that I called out as redundant?
Quoted 44 lines
Quoted 44 lines
But how about taking a different approach. Assuming that there can never be more than (say) 14 nesting NMI-like things, use the lower four bits of ->dynticks to represent the NMI nesting and the upper 28 bits as the counter. This of course requires modifying lots of places in RCU that check the counter, but it is probably time to abstract the check anyway.
This would allow my earlier attempted logic to work and (maybe) simplify the reasoning a bit (and yes, the "magic" constants need macros):
void rcu_nmi_enter(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x01; WARN_ON_ONCE(nexting == 0xf); if (nesting == 0) { if (atomic_read(&rdtp->dynticks) & 0x10) return; incby = 0x11; } smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); } void rcu_nmi_exit(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x0f; if (nesting == 0) return; if (nesting > 1) incby = -1; smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); }
Quoted 3 lines
Quoted 1 line
Over to you! ;-)
This latter one is all you :)
Well, let's see how I feel about it after trying a Promela model of the first code sequence. ;-)
Thanx, Paul
Quoted 12 lines
--Andy
Quoted 3 lines
Thanx, Paul
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 119 lines · Paul E. McKenney
Quoted 94 lines · Andy Lutomirski
Quoted 8 lines · Paul E. McKenney
Quoted 5 lines · Andy Lutomirski
Quoted 3 lines · Paul E. McKenney
Quoted 2 lines · Andy Lutomirski
On Fri, Nov 21, 2014 at 3:38 PM, Paul E. McKenney
<[email protected]> wrote:Returning state sounds like a bad idea, if we can reasonably avoid it.
I agree, except that we already do it for
exception_enter(), etc. But yes, changing fewer things is nice.Quoted 71 lines
And I think I finally see what you are pointing out about my code: If another NMI comes in between the time I increment ->dynticks_nmi_nesting and the time I atomically increment ->dynticks, the nested NMI handler will incorrectly believe that RCU is already paying attention to this CPU. Which would indeed not be at all good, so good catch!!!
Quoted 36 lines
Otherwise, I think that there may need to be enough state somewhere so that the outermost nested
rcu_nmi_enterknows whether to increment dynticks. For example,dynticks_nmi_nestingcould store the nesting count * 2 - (1 if the outermost nested user needs to increment dynticks). Something like:void
rcu_nmi_enter(void)
{
/* Be very careful -- this function may be called reentrently on the
same CPU. */
atomically: increment dynticks if it's even./* If an rcu_nmi_enter/rcu_nmi_exit pair happens here, then it will not change
* the state. */local_inc(&dynticks_nmi_nesting, (we incremented dynticks ? 1 : 2));
WARN_ON(we incremented dynticks and dynticks_nmi_nesting was nonzero);
}void
rcu_nmi_exit(void)
{
WARN_ON(!(dynticks & 1));
locally atomically:dynticks_nmi_nesting-= 2, unlessdynticks_nmi_nesting== 1, in which case set it to zeroif (
dynticks_nmi_nestingwas 1)atomic_inc(&dynticks); }The invariant here is that, for a single unnested enter/exit, if
dynticks_nmi_nesting!= 0, then dynticks is odd. As a result, an rcu_nmi_enter/rcu_nmi_exit pair at any time whendynticks_nmi_nesting!= 0 *or* dynticks is odd will have no net effect, so the invariant, in fact, holds for all invocations, nested or otherwise.Quoted 7 lines
At least one of those conditions is true at all times during the execution of outermost pair, starting with the first atomic operation and ending with the final
atomic_inc. So they nest properly no matter what else happens (unless, of course, someone else pokes dynticks in the middle).Thoughts?
Let's see... The evenness of ->dynticks should be preserved by nested NMI handlers, so the check and increment need not be atomic. We don't have any way (other than atomic operations) to do local atomic modifications on all architectures, because we cannot mask NMIs. (Yes, it can work on x86, but this is common code that needs to work everywhere.) On the other hand, presumably NMIs are rare, so atomic modification of the NMI nesting counter should be OK, at least if it proves absolutely necessary. And I am thinking that a mechanical proof will be needed here. :-/
But first, let me try generating the code and informally evaluating it:
1 struct rcu_dynticks { 2 long long dynticks_nesting; 3 int dynticks_nmi_nesting; 4 atomic_t dynticks; 5 }; 6 7 void rcu_nmi_enter(void) 8 { 9 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);Quoted 8 lines
10 int incby = 2; 11 12 if (!(atomic_read(&rdtp->dynticks) & 0x1)) { 13 smp_mb__before_atomic(); 14 atomic_inc(&rdtp->dynticks); 15 smp_mb__after_atomic(); 16 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 17 incby = 1;Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 1)here, perhaps?That would make sense.
Quoted 6 lines
Quoted 2 lines
18 } 19 rdtp->dynticks_nmi_nesting += incby;Oh, I see why you don't need
local_add-- it's because an nmi in the middle of this increment won't have any effect on the interrupted code, so even a software RMW will be okay.Yep! ;-)
Quoted 11 lines
Quoted 10 lines
20 barrier(); 21 } 22 23 void rcu_nmi_exit(void) 24 { 25 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); 26 27 WARN_ON_ONCE(!rdtp->dynticks_nmi_nesting); 28 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 29 if (rdtp->dynticks_nmi_nesting != 1) {
Quoted 4 lines
Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 2), perhaps?This is already implied by the
WARN_ON_ONCE()on line 27 and the check on line 29.
I was worried about negative numbers. Maybe change line 27 to WARN_ON_ONCE(rdtp->dynticks_nmi_nesting <= 0), then? (Or is it unsigned? If so, let's make to signed to catch this type of error.)
Quoted 55 lines
Quoted 8 lines
Quoted 6 lines
30 rdtp->dynticks_nmi_nesting -= 2; 31 return; 32 } 33 rdtp->dynticks_nmi_nesting = 0; 34 smp_mb__before_atomic();This implies
barrier(), right?Yep!
Quoted 44 lines
Quoted 44 lines
35 atomic_inc(&rdtp->dynticks); 36 smp_mb__after_atomic(); 37 WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); 38 }Line 9 picks up a pointer to this CPU's
rcu_dynticksstructure and line 10 assumes that we don't need to increment ->dynticks.Line 12 checks to see if ->dynticks is even. Note that this check is
stable: If there are nested NMIs, they will increment ->dynticks twice or not at all, and either way preserves the evenness (to be proven, of course, but that is the plan). If ->dynticks is even, lines 13-15 atomically increment it, line 16 complains if still even, and line 17 says we will increment ->dynticks_nmi_nesting by only 1.Either way, line 19 increments ->dynticks_nmi_nesting as needed and line 20 keeps the compiler from getting too cute.
For
rcu_nmi_exit(), line 25 again picks up this CPUsrcu_dynticksstructure. Lines 27 and 28 complain bitterly if invariants are violated. If line 29 finds that the value of ->dynticks_nmi_nesting is not 1, then line 30 subtracts 2 from ->dynticks_nmi_nesting and line 31 returns.Otherwise, line 33 sets ->dynticks_nmi_nesting to zero, lines 34-36 atomically increment ->dynticks with full ordering, and line 37 complains bitterly if ->dynticks is not even.
So, if an NMI occurs before rcu_nmi_enter's atomic increment, then the nested NMI's
rcu_nmi_enter()andrcu_nmi_exit()will think that they are not nested, which is the correct thing for them to think in that case. They will increment ->dynticks twice and restore ->dynticks_nmi_nesting to zero (adding and then subtracting 1). If the NMI happens after the atomic increment, then the nestedrcu_nmi_enter()andrcu_nmi_exit()will leave ->dynticks alone, and will restore ->dynticks_nmi_nesting to zero (adding and subtracting two again). If the NMI happens after the increment of ->dynticks_nmi_nesting, the nested NMI'srcu_nmi_enter()andrcu_nmi_exit()will again restore ->dynticks_nmi_nesting, but this time to one (again adding and subtracting two).In
rcu_nmi_exit(), ->dynticks_nmi_nesting of zero had better not happen, one means we need to atomically increment ->dynticks, and other values mean that we are partially or fully nested. Reasoning proceeds as forrcu_nmi_enter(), but in the opposite direction.
Quoted 5 lines
Quoted 3 lines
Quoted 1 line
Whew! That might even work.
I think I like this, with the warnings above.
OK with dropping the one that I called out as redundant?
Sure, but see about.
Quoted 45 lines
Quoted 45 lines
Quoted 45 lines
But how about taking a different approach. Assuming that there can never be more than (say) 14 nesting NMI-like things, use the lower four bits of ->dynticks to represent the NMI nesting and the upper 28 bits as the counter. This of course requires modifying lots of places in RCU that check the counter, but it is probably time to abstract the check anyway.
This would allow my earlier attempted logic to work and (maybe) simplify the reasoning a bit (and yes, the "magic" constants need macros):
void rcu_nmi_enter(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x01; WARN_ON_ONCE(nexting == 0xf); if (nesting == 0) { if (atomic_read(&rdtp->dynticks) & 0x10) return; incby = 0x11; } smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); } void rcu_nmi_exit(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x0f; if (nesting == 0) return; if (nesting > 1) incby = -1; smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); }
Quoted 6 lines
Quoted 3 lines
Quoted 1 line
Over to you! ;-)
This latter one is all you :)
Well, let's see how I feel about it after trying a Promela model of the first code sequence. ;-)
:)
Does Promela understand the differences between this type of reentrancy and real threading?
--Andy
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 121 lines · Andy Lutomirski
Quoted 119 lines · Paul E. McKenney
Quoted 94 lines · Andy Lutomirski
Quoted 8 lines · Paul E. McKenney
Quoted 5 lines · Andy Lutomirski
Quoted 3 lines · Paul E. McKenney
Quoted 2 lines · Andy Lutomirski
On Fri, Nov 21, 2014 at 3:38 PM, Paul E. McKenney
<[email protected]> wrote:Returning state sounds like a bad idea, if we can reasonably avoid it.
I agree, except that we already do it for
exception_enter(), etc. But yes, changing fewer things is nice.Quoted 71 lines
And I think I finally see what you are pointing out about my code: If another NMI comes in between the time I increment ->dynticks_nmi_nesting and the time I atomically increment ->dynticks, the nested NMI handler will incorrectly believe that RCU is already paying attention to this CPU. Which would indeed not be at all good, so good catch!!!
Quoted 36 lines
Otherwise, I think that there may need to be enough state somewhere so that the outermost nested
rcu_nmi_enterknows whether to increment dynticks. For example,dynticks_nmi_nestingcould store the nesting count * 2 - (1 if the outermost nested user needs to increment dynticks). Something like:void
rcu_nmi_enter(void)
{
/* Be very careful -- this function may be called reentrently on the
same CPU. */
atomically: increment dynticks if it's even./* If an rcu_nmi_enter/rcu_nmi_exit pair happens here, then it will not change
* the state. */local_inc(&dynticks_nmi_nesting, (we incremented dynticks ? 1 : 2));
WARN_ON(we incremented dynticks and dynticks_nmi_nesting was nonzero);
}void
rcu_nmi_exit(void)
{
WARN_ON(!(dynticks & 1));
locally atomically:dynticks_nmi_nesting-= 2, unlessdynticks_nmi_nesting== 1, in which case set it to zeroif (
dynticks_nmi_nestingwas 1)atomic_inc(&dynticks); }The invariant here is that, for a single unnested enter/exit, if
dynticks_nmi_nesting!= 0, then dynticks is odd. As a result, an rcu_nmi_enter/rcu_nmi_exit pair at any time whendynticks_nmi_nesting!= 0 *or* dynticks is odd will have no net effect, so the invariant, in fact, holds for all invocations, nested or otherwise.Quoted 7 lines
At least one of those conditions is true at all times during the execution of outermost pair, starting with the first atomic operation and ending with the final
atomic_inc. So they nest properly no matter what else happens (unless, of course, someone else pokes dynticks in the middle).Thoughts?
Let's see... The evenness of ->dynticks should be preserved by nested NMI handlers, so the check and increment need not be atomic. We don't have any way (other than atomic operations) to do local atomic modifications on all architectures, because we cannot mask NMIs. (Yes, it can work on x86, but this is common code that needs to work everywhere.) On the other hand, presumably NMIs are rare, so atomic modification of the NMI nesting counter should be OK, at least if it proves absolutely necessary. And I am thinking that a mechanical proof will be needed here. :-/
But first, let me try generating the code and informally evaluating it:
1 struct rcu_dynticks { 2 long long dynticks_nesting; 3 int dynticks_nmi_nesting; 4 atomic_t dynticks; 5 }; 6 7 void rcu_nmi_enter(void) 8 { 9 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);Quoted 8 lines
10 int incby = 2; 11 12 if (!(atomic_read(&rdtp->dynticks) & 0x1)) { 13 smp_mb__before_atomic(); 14 atomic_inc(&rdtp->dynticks); 15 smp_mb__after_atomic(); 16 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 17 incby = 1;Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 1)here, perhaps?That would make sense.
Quoted 6 lines
Quoted 2 lines
18 } 19 rdtp->dynticks_nmi_nesting += incby;Oh, I see why you don't need
local_add-- it's because an nmi in the middle of this increment won't have any effect on the interrupted code, so even a software RMW will be okay.Yep! ;-)
Quoted 11 lines
Quoted 10 lines
20 barrier(); 21 } 22 23 void rcu_nmi_exit(void) 24 { 25 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); 26 27 WARN_ON_ONCE(!rdtp->dynticks_nmi_nesting); 28 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 29 if (rdtp->dynticks_nmi_nesting != 1) {
Quoted 8 lines
Quoted 4 lines
Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 2), perhaps?This is already implied by the
WARN_ON_ONCE()on line 27 and the check on line 29.I was worried about negative numbers. Maybe change line 27 to
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting <= 0), then? (Or is it unsigned? If so, let's make to signed to catch this type of error.)
Good point, they are signed, so your WARN_ON_ONCE() would work.
Quoted 111 lines
Quoted 54 lines
Quoted 7 lines
Quoted 5 lines
30 rdtp->dynticks_nmi_nesting -= 2; 31 return; 32 } 33 rdtp->dynticks_nmi_nesting = 0; 34 smp_mb__before_atomic();This implies
barrier(), right?Yep!
Quoted 44 lines
Quoted 44 lines
35 atomic_inc(&rdtp->dynticks); 36 smp_mb__after_atomic(); 37 WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); 38 }Line 9 picks up a pointer to this CPU's
rcu_dynticksstructure and line 10 assumes that we don't need to increment ->dynticks.Line 12 checks to see if ->dynticks is even. Note that this check is
stable: If there are nested NMIs, they will increment ->dynticks twice or not at all, and either way preserves the evenness (to be proven, of course, but that is the plan). If ->dynticks is even, lines 13-15 atomically increment it, line 16 complains if still even, and line 17 says we will increment ->dynticks_nmi_nesting by only 1.Either way, line 19 increments ->dynticks_nmi_nesting as needed and line 20 keeps the compiler from getting too cute.
For
rcu_nmi_exit(), line 25 again picks up this CPUsrcu_dynticksstructure. Lines 27 and 28 complain bitterly if invariants are violated. If line 29 finds that the value of ->dynticks_nmi_nesting is not 1, then line 30 subtracts 2 from ->dynticks_nmi_nesting and line 31 returns.Otherwise, line 33 sets ->dynticks_nmi_nesting to zero, lines 34-36 atomically increment ->dynticks with full ordering, and line 37 complains bitterly if ->dynticks is not even.
So, if an NMI occurs before rcu_nmi_enter's atomic increment, then the nested NMI's
rcu_nmi_enter()andrcu_nmi_exit()will think that they are not nested, which is the correct thing for them to think in that case. They will increment ->dynticks twice and restore ->dynticks_nmi_nesting to zero (adding and then subtracting 1). If the NMI happens after the atomic increment, then the nestedrcu_nmi_enter()andrcu_nmi_exit()will leave ->dynticks alone, and will restore ->dynticks_nmi_nesting to zero (adding and subtracting two again). If the NMI happens after the increment of ->dynticks_nmi_nesting, the nested NMI'srcu_nmi_enter()andrcu_nmi_exit()will again restore ->dynticks_nmi_nesting, but this time to one (again adding and subtracting two).In
rcu_nmi_exit(), ->dynticks_nmi_nesting of zero had better not happen, one means we need to atomically increment ->dynticks, and other values mean that we are partially or fully nested. Reasoning proceeds as forrcu_nmi_enter(), but in the opposite direction.Quoted 5 lines
Quoted 3 lines
Quoted 1 line
Whew! That might even work.
I think I like this, with the warnings above.
OK with dropping the one that I called out as redundant?
Sure, but see about.
Quoted 49 lines
Quoted 48 lines
Quoted 45 lines
But how about taking a different approach. Assuming that there can never be more than (say) 14 nesting NMI-like things, use the lower four bits of ->dynticks to represent the NMI nesting and the upper 28 bits as the counter. This of course requires modifying lots of places in RCU that check the counter, but it is probably time to abstract the check anyway.
This would allow my earlier attempted logic to work and (maybe) simplify the reasoning a bit (and yes, the "magic" constants need macros):
void rcu_nmi_enter(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x01; WARN_ON_ONCE(nexting == 0xf); if (nesting == 0) { if (atomic_read(&rdtp->dynticks) & 0x10) return; incby = 0x11; } smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); } void rcu_nmi_exit(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x0f; if (nesting == 0) return; if (nesting > 1) incby = -1; smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); }Quoted 1 line
Over to you! ;-)
This latter one is all you :)
Quoted 7 lines
Quoted 2 lines
Well, let's see how I feel about it after trying a Promela model of the first code sequence. ;-)
:)
Does Promela understand the differences between this type of reentrancy and real threading?
Not as far as I know. But it can be tricked into making this distinction. One thread just has the Promela code as is, and the other thread has the same Promela code entirely contained in an atomic block. This means that the entire second thread must executed at one point in the first thread, just like an NMI would.
Thanx, Paul
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 249 lines · Paul E. McKenney
Quoted 121 lines · Andy Lutomirski
Quoted 119 lines · Paul E. McKenney
Quoted 94 lines · Andy Lutomirski
Quoted 8 lines · Paul E. McKenney
Quoted 5 lines · Andy Lutomirski
Quoted 3 lines · Paul E. McKenney
Quoted 2 lines · Andy Lutomirski
On Fri, Nov 21, 2014 at 3:38 PM, Paul E. McKenney
<[email protected]> wrote:Returning state sounds like a bad idea, if we can reasonably avoid it.
I agree, except that we already do it for
exception_enter(), etc. But yes, changing fewer things is nice.Quoted 71 lines
And I think I finally see what you are pointing out about my code: If another NMI comes in between the time I increment ->dynticks_nmi_nesting and the time I atomically increment ->dynticks, the nested NMI handler will incorrectly believe that RCU is already paying attention to this CPU. Which would indeed not be at all good, so good catch!!!
Quoted 36 lines
Otherwise, I think that there may need to be enough state somewhere so that the outermost nested
rcu_nmi_enterknows whether to increment dynticks. For example,dynticks_nmi_nestingcould store the nesting count * 2 - (1 if the outermost nested user needs to increment dynticks). Something like:void
rcu_nmi_enter(void)
{
/* Be very careful -- this function may be called reentrently on the
same CPU. */
atomically: increment dynticks if it's even./* If an rcu_nmi_enter/rcu_nmi_exit pair happens here, then it will not change
* the state. */local_inc(&dynticks_nmi_nesting, (we incremented dynticks ? 1 : 2));
WARN_ON(we incremented dynticks and dynticks_nmi_nesting was nonzero);
}void
rcu_nmi_exit(void)
{
WARN_ON(!(dynticks & 1));
locally atomically:dynticks_nmi_nesting-= 2, unlessdynticks_nmi_nesting== 1, in which case set it to zeroif (
dynticks_nmi_nestingwas 1)atomic_inc(&dynticks); }The invariant here is that, for a single unnested enter/exit, if
dynticks_nmi_nesting!= 0, then dynticks is odd. As a result, an rcu_nmi_enter/rcu_nmi_exit pair at any time whendynticks_nmi_nesting!= 0 *or* dynticks is odd will have no net effect, so the invariant, in fact, holds for all invocations, nested or otherwise.Quoted 7 lines
At least one of those conditions is true at all times during the execution of outermost pair, starting with the first atomic operation and ending with the final
atomic_inc. So they nest properly no matter what else happens (unless, of course, someone else pokes dynticks in the middle).Thoughts?
Let's see... The evenness of ->dynticks should be preserved by nested NMI handlers, so the check and increment need not be atomic. We don't have any way (other than atomic operations) to do local atomic modifications on all architectures, because we cannot mask NMIs. (Yes, it can work on x86, but this is common code that needs to work everywhere.) On the other hand, presumably NMIs are rare, so atomic modification of the NMI nesting counter should be OK, at least if it proves absolutely necessary. And I am thinking that a mechanical proof will be needed here. :-/
But first, let me try generating the code and informally evaluating it:
1 struct rcu_dynticks { 2 long long dynticks_nesting; 3 int dynticks_nmi_nesting; 4 atomic_t dynticks; 5 }; 6 7 void rcu_nmi_enter(void) 8 { 9 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);Quoted 8 lines
10 int incby = 2; 11 12 if (!(atomic_read(&rdtp->dynticks) & 0x1)) { 13 smp_mb__before_atomic(); 14 atomic_inc(&rdtp->dynticks); 15 smp_mb__after_atomic(); 16 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 17 incby = 1;Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 1)here, perhaps?That would make sense.
Quoted 6 lines
Quoted 2 lines
18 } 19 rdtp->dynticks_nmi_nesting += incby;Oh, I see why you don't need
local_add-- it's because an nmi in the middle of this increment won't have any effect on the interrupted code, so even a software RMW will be okay.Yep! ;-)
Quoted 11 lines
Quoted 10 lines
20 barrier(); 21 } 22 23 void rcu_nmi_exit(void) 24 { 25 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); 26 27 WARN_ON_ONCE(!rdtp->dynticks_nmi_nesting); 28 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 29 if (rdtp->dynticks_nmi_nesting != 1) {Quoted 8 lines
Quoted 4 lines
Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 2), perhaps?This is already implied by the
WARN_ON_ONCE()on line 27 and the check on line 29.I was worried about negative numbers. Maybe change line 27 to
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting <= 0), then? (Or is it unsigned? If so, let's make to signed to catch this type of error.)Good point, they are signed, so your
WARN_ON_ONCE()would work.Quoted 116 lines
Quoted 54 lines
Quoted 7 lines
Quoted 5 lines
30 rdtp->dynticks_nmi_nesting -= 2; 31 return; 32 } 33 rdtp->dynticks_nmi_nesting = 0; 34 smp_mb__before_atomic();This implies
barrier(), right?Yep!
Quoted 44 lines
Quoted 44 lines
35 atomic_inc(&rdtp->dynticks); 36 smp_mb__after_atomic(); 37 WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); 38 }Line 9 picks up a pointer to this CPU's
rcu_dynticksstructure and line 10 assumes that we don't need to increment ->dynticks.Line 12 checks to see if ->dynticks is even. Note that this check is
stable: If there are nested NMIs, they will increment ->dynticks twice or not at all, and either way preserves the evenness (to be proven, of course, but that is the plan). If ->dynticks is even, lines 13-15 atomically increment it, line 16 complains if still even, and line 17 says we will increment ->dynticks_nmi_nesting by only 1.Either way, line 19 increments ->dynticks_nmi_nesting as needed and line 20 keeps the compiler from getting too cute.
For
rcu_nmi_exit(), line 25 again picks up this CPUsrcu_dynticksstructure. Lines 27 and 28 complain bitterly if invariants are violated. If line 29 finds that the value of ->dynticks_nmi_nesting is not 1, then line 30 subtracts 2 from ->dynticks_nmi_nesting and line 31 returns.Otherwise, line 33 sets ->dynticks_nmi_nesting to zero, lines 34-36 atomically increment ->dynticks with full ordering, and line 37 complains bitterly if ->dynticks is not even.
So, if an NMI occurs before rcu_nmi_enter's atomic increment, then the nested NMI's
rcu_nmi_enter()andrcu_nmi_exit()will think that they are not nested, which is the correct thing for them to think in that case. They will increment ->dynticks twice and restore ->dynticks_nmi_nesting to zero (adding and then subtracting 1). If the NMI happens after the atomic increment, then the nestedrcu_nmi_enter()andrcu_nmi_exit()will leave ->dynticks alone, and will restore ->dynticks_nmi_nesting to zero (adding and subtracting two again). If the NMI happens after the increment of ->dynticks_nmi_nesting, the nested NMI'srcu_nmi_enter()andrcu_nmi_exit()will again restore ->dynticks_nmi_nesting, but this time to one (again adding and subtracting two).In
rcu_nmi_exit(), ->dynticks_nmi_nesting of zero had better not happen, one means we need to atomically increment ->dynticks, and other values mean that we are partially or fully nested. Reasoning proceeds as forrcu_nmi_enter(), but in the opposite direction.Quoted 5 lines
Quoted 3 lines
Quoted 1 line
Whew! That might even work.
I think I like this, with the warnings above.
OK with dropping the one that I called out as redundant?
Sure, but see about.
Quoted 45 lines
Quoted 45 lines
Quoted 45 lines
But how about taking a different approach. Assuming that there can never be more than (say) 14 nesting NMI-like things, use the lower four bits of ->dynticks to represent the NMI nesting and the upper 28 bits as the counter. This of course requires modifying lots of places in RCU that check the counter, but it is probably time to abstract the check anyway.
This would allow my earlier attempted logic to work and (maybe) simplify the reasoning a bit (and yes, the "magic" constants need macros):
void rcu_nmi_enter(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x01; WARN_ON_ONCE(nexting == 0xf); if (nesting == 0) { if (atomic_read(&rdtp->dynticks) & 0x10) return; incby = 0x11; } smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); } void rcu_nmi_exit(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x0f; if (nesting == 0) return; if (nesting > 1) incby = -1; smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); }Quoted 6 lines
Quoted 3 lines
Quoted 1 line
Over to you! ;-)
This latter one is all you :)
Well, let's see how I feel about it after trying a Promela model of the first code sequence. ;-)
:)
Quoted 8 lines
Quoted 2 lines
Does Promela understand the differences between this type of reentrancy and real threading?
Not as far as I know. But it can be tricked into making this distinction. One thread just has the Promela code as is, and the other thread has the same Promela code entirely contained in an atomic block. This means that the entire second thread must executed at one point in the first thread, just like an NMI would.
And the following Promela model claims that your approach works. Should I trust it? ;-)
Thanx, Paul
------------------------------------------------------------------------
/*
* Promela model for Andy Lutomirski's suggested change to rcu_nmi_enter()
* that allows nesting.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright IBM Corporation, 2014
*
* Author: Paul E. McKenney <[email protected]>
*/
byte dynticks_nesting;
byte dynticks_nmi_nesting;
byte dynticks;
byte busy;
/*
* Promela verision of rcu_nmi_enter().
*/
inline rcu_nmi_enter()
{
assert(dynticks_nmi_nesting >= 0);
if
:: (dynticks & 1) == 0 ->
atomic {
dynticks = dynticks + 1;
}
assert((dynticks & 1) == 1);
dynticks_nmi_nesting = dynticks_nmi_nesting + 1;
assert(dynticks_nmi_nesting >= 1);
:: else ->
dynticks_nmi_nesting = dynticks_nmi_nesting + 2;
fi;
}/*
* Promela verision of rcu_nmi_exit().
*/
inline rcu_nmi_exit()
{
assert(dynticks_nmi_nesting > 0);
assert((dynticks & 1) != 0);
if
:: dynticks_nmi_nesting != 1 ->
dynticks_nmi_nesting = dynticks_nmi_nesting - 2;
:: else ->
dynticks_nmi_nesting = 0;
atomic {
dynticks = dynticks + 1;
}
assert((dynticks & 1) == 0);
fi;
}/*
* Base-level NMI runs non-atomically. Crudely emulates process-level
* dynticks-idle entry/exit.
*/
proctype base_NMI()
{
do
:: if
:: 1 -> atomic {
dynticks = dynticks + 1;
}
busy = 0;
:: 1 -> skip;
fi;
rcu_nmi_enter();
assert((dynticks & 1) == 1);
rcu_nmi_exit();
if
:: busy -> skip;
:: !busy ->
atomic {
dynticks = dynticks + 1;
}
busy = 1;
fi;
od;
}/*
* Nested NMI runs atomically to emulate interrupting base_level(). */
proctype nested_NMI()
{
do
:: atomic {
rcu_nmi_enter();
assert((dynticks & 1) == 1);
rcu_nmi_exit();
}
od;
}init {
dynticks_nesting = 0;
dynticks_nmi_nesting = 0;
dynticks = 0;
busy = 0;
run base_NMI();
run nested_NMI();
}Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 253 lines · Paul E. McKenney
Quoted 252 lines · Paul E. McKenney
Quoted 121 lines · Andy Lutomirski
Quoted 119 lines · Paul E. McKenney
Quoted 94 lines · Andy Lutomirski
Quoted 8 lines · Paul E. McKenney
Quoted 5 lines · Andy Lutomirski
Quoted 3 lines · Paul E. McKenney
Quoted 2 lines · Andy Lutomirski
On Fri, Nov 21, 2014 at 3:38 PM, Paul E. McKenney
<[email protected]> wrote:Returning state sounds like a bad idea, if we can reasonably avoid it.
I agree, except that we already do it for
exception_enter(), etc. But yes, changing fewer things is nice.Quoted 71 lines
And I think I finally see what you are pointing out about my code: If another NMI comes in between the time I increment ->dynticks_nmi_nesting and the time I atomically increment ->dynticks, the nested NMI handler will incorrectly believe that RCU is already paying attention to this CPU. Which would indeed not be at all good, so good catch!!!
Quoted 36 lines
Otherwise, I think that there may need to be enough state somewhere so that the outermost nested
rcu_nmi_enterknows whether to increment dynticks. For example,dynticks_nmi_nestingcould store the nesting count * 2 - (1 if the outermost nested user needs to increment dynticks). Something like:void
rcu_nmi_enter(void)
{
/* Be very careful -- this function may be called reentrently on the
same CPU. */
atomically: increment dynticks if it's even./* If an rcu_nmi_enter/rcu_nmi_exit pair happens here, then it will not change
* the state. */local_inc(&dynticks_nmi_nesting, (we incremented dynticks ? 1 : 2));
WARN_ON(we incremented dynticks and dynticks_nmi_nesting was nonzero);
}void
rcu_nmi_exit(void)
{
WARN_ON(!(dynticks & 1));
locally atomically:dynticks_nmi_nesting-= 2, unlessdynticks_nmi_nesting== 1, in which case set it to zeroif (
dynticks_nmi_nestingwas 1)atomic_inc(&dynticks); }The invariant here is that, for a single unnested enter/exit, if
dynticks_nmi_nesting!= 0, then dynticks is odd. As a result, an rcu_nmi_enter/rcu_nmi_exit pair at any time whendynticks_nmi_nesting!= 0 *or* dynticks is odd will have no net effect, so the invariant, in fact, holds for all invocations, nested or otherwise.Quoted 7 lines
At least one of those conditions is true at all times during the execution of outermost pair, starting with the first atomic operation and ending with the final
atomic_inc. So they nest properly no matter what else happens (unless, of course, someone else pokes dynticks in the middle).Thoughts?
Let's see... The evenness of ->dynticks should be preserved by nested NMI handlers, so the check and increment need not be atomic. We don't have any way (other than atomic operations) to do local atomic modifications on all architectures, because we cannot mask NMIs. (Yes, it can work on x86, but this is common code that needs to work everywhere.) On the other hand, presumably NMIs are rare, so atomic modification of the NMI nesting counter should be OK, at least if it proves absolutely necessary. And I am thinking that a mechanical proof will be needed here. :-/
But first, let me try generating the code and informally evaluating it:
1 struct rcu_dynticks { 2 long long dynticks_nesting; 3 int dynticks_nmi_nesting; 4 atomic_t dynticks; 5 }; 6 7 void rcu_nmi_enter(void) 8 { 9 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks);Quoted 8 lines
10 int incby = 2; 11 12 if (!(atomic_read(&rdtp->dynticks) & 0x1)) { 13 smp_mb__before_atomic(); 14 atomic_inc(&rdtp->dynticks); 15 smp_mb__after_atomic(); 16 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 17 incby = 1;Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 1)here, perhaps?That would make sense.
Quoted 6 lines
Quoted 2 lines
18 } 19 rdtp->dynticks_nmi_nesting += incby;Oh, I see why you don't need
local_add-- it's because an nmi in the middle of this increment won't have any effect on the interrupted code, so even a software RMW will be okay.Yep! ;-)
Quoted 11 lines
Quoted 10 lines
20 barrier(); 21 } 22 23 void rcu_nmi_exit(void) 24 { 25 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); 26 27 WARN_ON_ONCE(!rdtp->dynticks_nmi_nesting); 28 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 29 if (rdtp->dynticks_nmi_nesting != 1) {Quoted 8 lines
Quoted 4 lines
Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 2), perhaps?This is already implied by the
WARN_ON_ONCE()on line 27 and the check on line 29.I was worried about negative numbers. Maybe change line 27 to
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting <= 0), then? (Or is it unsigned? If so, let's make to signed to catch this type of error.)Good point, they are signed, so your
WARN_ON_ONCE()would work.Quoted 118 lines
Quoted 54 lines
Quoted 7 lines
Quoted 5 lines
30 rdtp->dynticks_nmi_nesting -= 2; 31 return; 32 } 33 rdtp->dynticks_nmi_nesting = 0; 34 smp_mb__before_atomic();This implies
barrier(), right?Yep!
Quoted 44 lines
Quoted 44 lines
35 atomic_inc(&rdtp->dynticks); 36 smp_mb__after_atomic(); 37 WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); 38 }Line 9 picks up a pointer to this CPU's
rcu_dynticksstructure and line 10 assumes that we don't need to increment ->dynticks.Line 12 checks to see if ->dynticks is even. Note that this check is
stable: If there are nested NMIs, they will increment ->dynticks twice or not at all, and either way preserves the evenness (to be proven, of course, but that is the plan). If ->dynticks is even, lines 13-15 atomically increment it, line 16 complains if still even, and line 17 says we will increment ->dynticks_nmi_nesting by only 1.Either way, line 19 increments ->dynticks_nmi_nesting as needed and line 20 keeps the compiler from getting too cute.
For
rcu_nmi_exit(), line 25 again picks up this CPUsrcu_dynticksstructure. Lines 27 and 28 complain bitterly if invariants are violated. If line 29 finds that the value of ->dynticks_nmi_nesting is not 1, then line 30 subtracts 2 from ->dynticks_nmi_nesting and line 31 returns.Otherwise, line 33 sets ->dynticks_nmi_nesting to zero, lines 34-36 atomically increment ->dynticks with full ordering, and line 37 complains bitterly if ->dynticks is not even.
So, if an NMI occurs before rcu_nmi_enter's atomic increment, then the nested NMI's
rcu_nmi_enter()andrcu_nmi_exit()will think that they are not nested, which is the correct thing for them to think in that case. They will increment ->dynticks twice and restore ->dynticks_nmi_nesting to zero (adding and then subtracting 1). If the NMI happens after the atomic increment, then the nestedrcu_nmi_enter()andrcu_nmi_exit()will leave ->dynticks alone, and will restore ->dynticks_nmi_nesting to zero (adding and subtracting two again). If the NMI happens after the increment of ->dynticks_nmi_nesting, the nested NMI'srcu_nmi_enter()andrcu_nmi_exit()will again restore ->dynticks_nmi_nesting, but this time to one (again adding and subtracting two).In
rcu_nmi_exit(), ->dynticks_nmi_nesting of zero had better not happen, one means we need to atomically increment ->dynticks, and other values mean that we are partially or fully nested. Reasoning proceeds as forrcu_nmi_enter(), but in the opposite direction.Quoted 5 lines
Quoted 3 lines
Quoted 1 line
Whew! That might even work.
I think I like this, with the warnings above.
OK with dropping the one that I called out as redundant?
Sure, but see about.
Quoted 45 lines
Quoted 45 lines
Quoted 45 lines
But how about taking a different approach. Assuming that there can never be more than (say) 14 nesting NMI-like things, use the lower four bits of ->dynticks to represent the NMI nesting and the upper 28 bits as the counter. This of course requires modifying lots of places in RCU that check the counter, but it is probably time to abstract the check anyway.
This would allow my earlier attempted logic to work and (maybe) simplify the reasoning a bit (and yes, the "magic" constants need macros):
void rcu_nmi_enter(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x01; WARN_ON_ONCE(nexting == 0xf); if (nesting == 0) { if (atomic_read(&rdtp->dynticks) & 0x10) return; incby = 0x11; } smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); } void rcu_nmi_exit(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x0f; if (nesting == 0) return; if (nesting > 1) incby = -1; smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); }Quoted 6 lines
Quoted 3 lines
Quoted 1 line
Over to you! ;-)
This latter one is all you :)
Well, let's see how I feel about it after trying a Promela model of the first code sequence. ;-)
:)
Does Promela understand the differences between this type of reentrancy and real threading?
Quoted 9 lines
Quoted 5 lines
Not as far as I know. But it can be tricked into making this distinction. One thread just has the Promela code as is, and the other thread has the same Promela code entirely contained in an atomic block. This means that the entire second thread must executed at one point in the first thread, just like an NMI would.
And the following Promela model claims that your approach works. Should I trust it? ;-)
I think so.
Want to write a patch? If so, whose tree should it go in? I can add it to my IST series, but that seems a bit odd.
--Andy
Quoted 121 lines
Thanx, Paul
------------------------------------------------------------------------
/*
* Promela model for Andy Lutomirski's suggested change torcu_nmi_enter()
* that allows nesting.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright IBM Corporation, 2014
*
* Author: Paul E. McKenney <[email protected]>
*/byte
dynticks_nesting;
bytedynticks_nmi_nesting;
byte dynticks;
byte busy;/*
* Promela verision ofrcu_nmi_enter().
*/
inlinercu_nmi_enter(){ assert(dynticks_nmi_nesting >= 0); if :: (dynticks & 1) == 0 -> atomic { dynticks = dynticks + 1; } assert((dynticks & 1) == 1); dynticks_nmi_nesting = dynticks_nmi_nesting + 1; assert(dynticks_nmi_nesting >= 1); :: else -> dynticks_nmi_nesting = dynticks_nmi_nesting + 2; fi; }/*
* Promela verision ofrcu_nmi_exit().
*/
inlinercu_nmi_exit(){ assert(dynticks_nmi_nesting > 0); assert((dynticks & 1) != 0); if :: dynticks_nmi_nesting != 1 -> dynticks_nmi_nesting = dynticks_nmi_nesting - 2; :: else -> dynticks_nmi_nesting = 0; atomic { dynticks = dynticks + 1; } assert((dynticks & 1) == 0); fi; }/*
* Base-level NMI runs non-atomically. Crudely emulates process-level
* dynticks-idle entry/exit.
*/
proctypebase_NMI(){ do :: if :: 1 -> atomic { dynticks = dynticks + 1; } busy = 0; :: 1 -> skip; fi; rcu_nmi_enter(); assert((dynticks & 1) == 1); rcu_nmi_exit(); if :: busy -> skip; :: !busy -> atomic { dynticks = dynticks + 1; } busy = 1; fi; od; }/*
* Nested NMI runs atomically to emulate interruptingbase_level(). */
proctypenested_NMI(){ do :: atomic { rcu_nmi_enter(); assert((dynticks & 1) == 1); rcu_nmi_exit(); } od; }init { dynticks_nesting = 0; dynticks_nmi_nesting = 0; dynticks = 0; busy = 0; run base_NMI(); run nested_NMI(); }
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 3 lines · Andy Lutomirski
Quoted 1 line · Paul E. McKenney
On Mon, Nov 24, 2014 at 01:35:01PM -0800, Paul E. McKenney wrote:
[ . . . ]
Quoted 8 lines
Quoted 3 lines
And the following Promela model claims that your approach works. Should I trust it? ;-)
I think so.
Want to write a patch? If so, whose tree should it go in? I can add it to my IST series, but that seems a bit odd.
Working on it. ;-)
Thanx, Paul
Quoted 130 lines
--Andy
Quoted 121 lines
Thanx, Paul
------------------------------------------------------------------------
/*
* Promela model for Andy Lutomirski's suggested change torcu_nmi_enter()
* that allows nesting.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright IBM Corporation, 2014
*
* Author: Paul E. McKenney <[email protected]>
*/byte
dynticks_nesting;
bytedynticks_nmi_nesting;
byte dynticks;
byte busy;/*
* Promela verision ofrcu_nmi_enter().
*/
inlinercu_nmi_enter(){ assert(dynticks_nmi_nesting >= 0); if :: (dynticks & 1) == 0 -> atomic { dynticks = dynticks + 1; } assert((dynticks & 1) == 1); dynticks_nmi_nesting = dynticks_nmi_nesting + 1; assert(dynticks_nmi_nesting >= 1); :: else -> dynticks_nmi_nesting = dynticks_nmi_nesting + 2; fi; }/*
* Promela verision ofrcu_nmi_exit().
*/
inlinercu_nmi_exit(){ assert(dynticks_nmi_nesting > 0); assert((dynticks & 1) != 0); if :: dynticks_nmi_nesting != 1 -> dynticks_nmi_nesting = dynticks_nmi_nesting - 2; :: else -> dynticks_nmi_nesting = 0; atomic { dynticks = dynticks + 1; } assert((dynticks & 1) == 0); fi; }/*
* Base-level NMI runs non-atomically. Crudely emulates process-level
* dynticks-idle entry/exit.
*/
proctypebase_NMI(){ do :: if :: 1 -> atomic { dynticks = dynticks + 1; } busy = 0; :: 1 -> skip; fi; rcu_nmi_enter(); assert((dynticks & 1) == 1); rcu_nmi_exit(); if :: busy -> skip; :: !busy -> atomic { dynticks = dynticks + 1; } busy = 1; fi; od; }/*
* Nested NMI runs atomically to emulate interruptingbase_level(). */
proctypenested_NMI(){ do :: atomic { rcu_nmi_enter(); assert((dynticks & 1) == 1); rcu_nmi_exit(); } od; }init { dynticks_nesting = 0; dynticks_nmi_nesting = 0; dynticks = 0; busy = 0; run base_NMI(); run nested_NMI(); }Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 11 lines · Paul E. McKenney
Quoted 3 lines · Andy Lutomirski
Quoted 1 line · Paul E. McKenney
On Mon, Nov 24, 2014 at 01:35:01PM -0800, Paul E. McKenney wrote:
[ . . . ]
Quoted 4 lines
Quoted 3 lines
And the following Promela model claims that your approach works. Should I trust it? ;-)
Quoted 6 lines
Quoted 4 lines
I think so.
Want to write a patch? If so, whose tree should it go in? I can add it to my IST series, but that seems a bit odd.
Working on it. ;-)
And here is a sneak preview of the patch. Thoughts?
Thanx, Paul
------------------------------------------------------------------------
rcu: Make rcu_nmi_enter() handle nesting
Andy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest, rcu_nmi_enter() and rcu_nmi_exit() must now correctly handle nesting. This commit therefore introduces nesting, using a clever NMI-coordination algorithm suggested by Andy. The trick is to atomically increment ->dynticks (if needed) before manipulating ->dynticks_nmi_nesting on entry (and, accordingly, after on exit). In addition, ->dynticks_nmi_nesting is incremented by one if ->dynticks was incremented and by two otherwise. This means that when rcu_nmi_exit() sees ->dynticks_nmi_nesting equal to one, it knows that ->dynticks must be atomically incremented.
This NMI-coordination algorithms has been validated by the following Promela model, for whatever that might be worth:
/*
* Promela model for Andy Lutomirski's suggested change to rcu_nmi_enter()
* that allows nesting.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright IBM Corporation, 2014
*
* Author: Paul E. McKenney <[email protected]>
*/
byte dynticks_nesting = 0;
byte dynticks_nmi_nesting = 0;
byte dynticks = 0;
/*
* Promela verision of rcu_nmi_enter().
*/
inline rcu_nmi_enter()
{
byte incby;
incby = 2;
assert(dynticks_nmi_nesting >= 0);
if
:: (dynticks & 1) == 0 ->
atomic {
dynticks = dynticks + 1;
}
assert((dynticks & 1) == 1);
incby = 1;
:: else ->
skip;
fi;
dynticks_nmi_nesting = dynticks_nmi_nesting + incby;
assert(dynticks_nmi_nesting >= 1);
}/*
* Promela verision of rcu_nmi_exit().
*/
inline rcu_nmi_exit()
{
assert(dynticks_nmi_nesting > 0);
assert((dynticks & 1) != 0);
if
:: dynticks_nmi_nesting != 1 ->
dynticks_nmi_nesting = dynticks_nmi_nesting - 2;
:: else ->
dynticks_nmi_nesting = 0;
atomic {
dynticks = dynticks + 1;
}
assert((dynticks & 1) == 0);
fi;
}/*
* Base-level NMI runs non-atomically. Crudely emulates process-level
* dynticks-idle entry/exit.
*/
proctype base_NMI()
{
byte busy;
busy = 0;
do
:: if
:: 1 -> atomic {
dynticks = dynticks + 1;
}
busy = 0;
:: 1 -> skip;
fi;
rcu_nmi_enter();
assert((dynticks & 1) == 1);
rcu_nmi_exit();
if
:: busy -> skip;
:: !busy ->
atomic {
dynticks = dynticks + 1;
}
busy = 1;
fi;
od;
}/*
* Nested NMI runs atomically to emulate interrupting base_level(). */
proctype nested_NMI()
{
do
:: atomic {
rcu_nmi_enter();
assert((dynticks & 1) == 1);
rcu_nmi_exit();
}
od;
}init {
run base_NMI();
run nested_NMI();
}kernel/rcu/tree.c +49 -17
@@ -759,39 +759,71 @@ void rcu_irq_enter(void)/** * rcu_nmi_enter - inform RCU of entry to NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is active. +* If the CPU was idle from RCU's viewpoint, update rdtp->dynticks and +* rdtp->dynticks_nmi_nesting to let the RCU grace-period handling know +* that the CPU is active. This implementation permits nested NMIs, as +* long as the nesting level does not overflow an int. (You will probably +* run out of stack space first.) */ void rcu_nmi_enter (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); +int incby =2 ; -if (rdtp->dynticks_nmi_nesting ==0 && - (atomic_read (&rdtp->dynticks) &0x1 )) -return ; - rdtp->dynticks_nmi_nesting++; -smp_mb__before_atomic ();/* Force delay from prior write. */ -atomic_inc (&rdtp->dynticks); -/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ -smp_mb__after_atomic ();/* See above. */ -WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); +/* Complain about underflow. */ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <0 ); + +/* +* If idle from RCU viewpoint, atomically increment ->dynticks +* to mark non-idle and increment ->dynticks_nmi_nesting by one. +* Otherwise, increment ->dynticks_nmi_nesting by two. This means +* if ->dynticks_nmi_nesting is equal to one, we are guaranteed +* to be in the outermost NMI handler that interrupted an RCU-idle +* period (observation due to Andy Lutomirski). +*/ +if (!(atomic_read (&rdtp->dynticks) &0x1 )) { +smp_mb__before_atomic ();/* Force delay from prior write. */ +atomic_inc (&rdtp->dynticks); +/* atomic_inc() before later RCU read-side crit sects */ +smp_mb__after_atomic ();/* See above. */ +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + incby =1 ; + } + rdtp->dynticks_nmi_nesting += incby; +barrier (); }/** * rcu_nmi_exit - inform RCU of exit from NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is no longer active. +* If we are returning from the outermost NMI handler that interrupted an +* RCU-idle period, update rdtp->dynticks and rdtp->dynticks_nmi_nesting +* to let the RCU grace-period handling know that the CPU is back to +* being RCU-idle. */ void rcu_nmi_exit (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); -if (rdtp->dynticks_nmi_nesting ==0 || - --rdtp->dynticks_nmi_nesting !=0 ) +/* +* Check for ->dynticks_nmi_nesting underflow and bad ->dynticks. +* (We are exiting an NMI handler, so RCU better be paying attention +* to us!) +*/ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <=0 ); +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + +/* +* If the nesting level is not 1, the CPU wasn't RCU-idle, so +* leave it in non-RCU-idle state. +*/ +if (rdtp->dynticks_nmi_nesting !=1 ) { + rdtp->dynticks_nmi_nesting -=2 ;return ; + } + +/* This NMI interrupted an RCU-idle CPU, restore RCU-idleness. */ + rdtp->dynticks_nmi_nesting =0 ;/* CPUs seeing atomic_inc() must see prior RCU read-side crit sects */ smp_mb__before_atomic ();/* See above. */ atomic_inc (&rdtp->dynticks);
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 23 lines · Paul E. McKenney
Quoted 11 lines · Paul E. McKenney
Quoted 3 lines · Andy Lutomirski
Quoted 1 line · Paul E. McKenney
On Mon, Nov 24, 2014 at 01:35:01PM -0800, Paul E. McKenney wrote:
[ . . . ]
Quoted 4 lines
Quoted 3 lines
And the following Promela model claims that your approach works. Should I trust it? ;-)
Quoted 6 lines
Quoted 4 lines
I think so.
Want to write a patch? If so, whose tree should it go in? I can add it to my IST series, but that seems a bit odd.
Working on it. ;-)
And here is a sneak preview of the patch. Thoughts?
Thanx, Paul
Quoted 7 lines
------------------------------------------------------------------------
rcu: Make
rcu_nmi_enter()handle nestingAndy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest,
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting.
You must not be a frequent reader of entry_64.S and the Intel SDM :) IOW, IST is just a stack switching mechanism, and these interrupts have been around forever -- they're just buggy right now.
How about:
x86 has multiple types of NMI-like interrupts: real NMIs, machine checks, and, for some values of NMI-like, debugging and breakpoint interrupts. These interrupts can nest inside each other. Andy Lutomirski is adding RCU support to these interrupts, so rcu_nmi_enter() and rcu_nmi_exit() must now correctly handle nesting.
Other than that, I like it.
Quoted 227 lines
This commit therefore
introduces nesting, using a clever NMI-coordination algorithm suggested by Andy. The trick is to atomically increment ->dynticks (if needed) before manipulating ->dynticks_nmi_nesting on entry (and, accordingly, after on exit). In addition, ->dynticks_nmi_nesting is incremented by one if ->dynticks was incremented and by two otherwise. This means that whenrcu_nmi_exit()sees ->dynticks_nmi_nesting equal to one, it knows that ->dynticks must be atomically incremented.This NMI-coordination algorithms has been validated by the following Promela model, for whatever that might be worth:
/*
* Promela model for Andy Lutomirski's suggested change torcu_nmi_enter()
* that allows nesting.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright IBM Corporation, 2014
*
* Author: Paul E. McKenney <[email protected]>
*/byte
dynticks_nesting= 0;
bytedynticks_nmi_nesting= 0;
byte dynticks = 0;/*
* Promela verision ofrcu_nmi_enter().
*/
inlinercu_nmi_enter(){ byte incby; incby = 2; assert(dynticks_nmi_nesting >= 0); if :: (dynticks & 1) == 0 -> atomic { dynticks = dynticks + 1; } assert((dynticks & 1) == 1); incby = 1; :: else -> skip; fi; dynticks_nmi_nesting = dynticks_nmi_nesting + incby; assert(dynticks_nmi_nesting >= 1); }/*
* Promela verision ofrcu_nmi_exit().
*/
inlinercu_nmi_exit(){ assert(dynticks_nmi_nesting > 0); assert((dynticks & 1) != 0); if :: dynticks_nmi_nesting != 1 -> dynticks_nmi_nesting = dynticks_nmi_nesting - 2; :: else -> dynticks_nmi_nesting = 0; atomic { dynticks = dynticks + 1; } assert((dynticks & 1) == 0); fi; }/*
* Base-level NMI runs non-atomically. Crudely emulates process-level
* dynticks-idle entry/exit.
*/
proctypebase_NMI(){ byte busy; busy = 0; do :: if :: 1 -> atomic { dynticks = dynticks + 1; } busy = 0; :: 1 -> skip; fi; rcu_nmi_enter(); assert((dynticks & 1) == 1); rcu_nmi_exit(); if :: busy -> skip; :: !busy -> atomic { dynticks = dynticks + 1; } busy = 1; fi; od; }/*
* Nested NMI runs atomically to emulate interruptingbase_level(). */
proctypenested_NMI(){ do :: atomic { rcu_nmi_enter(); assert((dynticks & 1) == 1); rcu_nmi_exit(); } od; }init { run base_NMI(); run nested_NMI(); }Signed-off-by Paul E. McKenney <[email protected]>kernel/rcu/tree.c +49
-17@@ -759,39 +759,71 @@ void rcu_irq_enter(void)/** * rcu_nmi_enter - inform RCU of entry to NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is active. +* If the CPU was idle from RCU's viewpoint, update rdtp->dynticks and +* rdtp->dynticks_nmi_nesting to let the RCU grace-period handling know +* that the CPU is active. This implementation permits nested NMIs, as +* long as the nesting level does not overflow an int. (You will probably +* run out of stack space first.) */ void rcu_nmi_enter (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); +int incby =2 ; -if (rdtp->dynticks_nmi_nesting ==0 && - (atomic_read (&rdtp->dynticks) &0x1 )) -return ; - rdtp->dynticks_nmi_nesting++; -smp_mb__before_atomic ();/* Force delay from prior write. */ -atomic_inc (&rdtp->dynticks); -/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ -smp_mb__after_atomic ();/* See above. */ -WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); +/* Complain about underflow. */ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <0 ); + +/* +* If idle from RCU viewpoint, atomically increment ->dynticks +* to mark non-idle and increment ->dynticks_nmi_nesting by one. +* Otherwise, increment ->dynticks_nmi_nesting by two. This means +* if ->dynticks_nmi_nesting is equal to one, we are guaranteed +* to be in the outermost NMI handler that interrupted an RCU-idle +* period (observation due to Andy Lutomirski). +*/ +if (!(atomic_read (&rdtp->dynticks) &0x1 )) { +smp_mb__before_atomic ();/* Force delay from prior write. */ +atomic_inc (&rdtp->dynticks); +/* atomic_inc() before later RCU read-side crit sects */ +smp_mb__after_atomic ();/* See above. */ +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + incby =1 ; + } + rdtp->dynticks_nmi_nesting += incby; +barrier (); }/** * rcu_nmi_exit - inform RCU of exit from NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is no longer active. +* If we are returning from the outermost NMI handler that interrupted an +* RCU-idle period, update rdtp->dynticks and rdtp->dynticks_nmi_nesting +* to let the RCU grace-period handling know that the CPU is back to +* being RCU-idle. */ void rcu_nmi_exit (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); -if (rdtp->dynticks_nmi_nesting ==0 || - --rdtp->dynticks_nmi_nesting !=0 ) +/* +* Check for ->dynticks_nmi_nesting underflow and bad ->dynticks. +* (We are exiting an NMI handler, so RCU better be paying attention +* to us!) +*/ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <=0 ); +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + +/* +* If the nesting level is not 1, the CPU wasn't RCU-idle, so +* leave it in non-RCU-idle state. +*/ +if (rdtp->dynticks_nmi_nesting !=1 ) { + rdtp->dynticks_nmi_nesting -=2 ;return ; + } + +/* This NMI interrupted an RCU-idle CPU, restore RCU-idleness. */ + rdtp->dynticks_nmi_nesting =0 ;/* CPUs seeing atomic_inc() must see prior RCU read-side crit sects */ smp_mb__before_atomic ();/* See above. */ atomic_inc (&rdtp->dynticks);
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 39 lines · Andy Lutomirski
Quoted 23 lines · Paul E. McKenney
Quoted 11 lines · Paul E. McKenney
Quoted 3 lines · Andy Lutomirski
Quoted 1 line · Paul E. McKenney
On Mon, Nov 24, 2014 at 01:35:01PM -0800, Paul E. McKenney wrote:
[ . . . ]
Quoted 4 lines
Quoted 3 lines
And the following Promela model claims that your approach works. Should I trust it? ;-)
Quoted 6 lines
Quoted 4 lines
I think so.
Want to write a patch? If so, whose tree should it go in? I can add it to my IST series, but that seems a bit odd.
Working on it. ;-)
And here is a sneak preview of the patch. Thoughts?
Thanx, Paul
Quoted 7 lines
------------------------------------------------------------------------
rcu: Make
rcu_nmi_enter()handle nestingAndy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest,
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting.You must not be a frequent reader of
entry_64.Sand the Intel SDM :) IOW, IST is just a stack switching mechanism, and these interrupts have been around forever -- they're just buggy right now.How about:
Quoted 7 lines
x86 has multiple types of NMI-like interrupts: real NMIs, machine checks, and, for some values of NMI-like, debugging and breakpoint interrupts. These interrupts can nest inside each other. Andy Lutomirski is adding RCU support to these interrupts, so
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting.Other than that, I like it.
And here is the updated version. Left to my normal workflow, this would go into 3.20. Please let me know if you need it earlier, but in that case, I will need you to test it fairly soon. (I don't have any way to test nested NMI-like things.)
Thanx, Paul
------------------------------------------------------------------------
rcu: Make rcu_nmi_enter() handle nesting
The x86 architecture has multiple types of NMI-like interrupts: real NMIs, machine checks, and, for some values of NMI-like, debugging and breakpoint interrupts. These interrupts can nest inside each other. Andy Lutomirski is adding RCU support to these interrupts, so rcu_nmi_enter() and rcu_nmi_exit() must now correctly handle nesting.
This commit therefore introduces nesting, using a clever NMI-coordination algorithm suggested by Andy. The trick is to atomically increment ->dynticks (if needed) before manipulating ->dynticks_nmi_nesting on entry (and, accordingly, after on exit). In addition, ->dynticks_nmi_nesting is incremented by one if ->dynticks was incremented and by two otherwise. This means that when rcu_nmi_exit() sees ->dynticks_nmi_nesting equal to one, it knows that ->dynticks must be atomically incremented.
This NMI-coordination algorithms has been validated by the following Promela model, for whatever that might be worth:
/*
* Promela model for Andy Lutomirski's suggested change to rcu_nmi_enter()
* that allows nesting.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright IBM Corporation, 2014
*
* Author: Paul E. McKenney <[email protected]>
*/
byte dynticks_nesting = 0;
byte dynticks_nmi_nesting = 0;
byte dynticks = 0;
/*
* Promela verision of rcu_nmi_enter().
*/
inline rcu_nmi_enter()
{
byte incby;
incby = 2;
assert(dynticks_nmi_nesting >= 0);
if
:: (dynticks & 1) == 0 ->
atomic {
dynticks = dynticks + 1;
}
assert((dynticks & 1) == 1);
incby = 1;
:: else ->
skip;
fi;
dynticks_nmi_nesting = dynticks_nmi_nesting + incby;
assert(dynticks_nmi_nesting >= 1);
}/*
* Promela verision of rcu_nmi_exit().
*/
inline rcu_nmi_exit()
{
assert(dynticks_nmi_nesting > 0);
assert((dynticks & 1) != 0);
if
:: dynticks_nmi_nesting != 1 ->
dynticks_nmi_nesting = dynticks_nmi_nesting - 2;
:: else ->
dynticks_nmi_nesting = 0;
atomic {
dynticks = dynticks + 1;
}
assert((dynticks & 1) == 0);
fi;
}/*
* Base-level NMI runs non-atomically. Crudely emulates process-level
* dynticks-idle entry/exit.
*/
proctype base_NMI()
{
byte busy;
busy = 0;
do
:: if
:: 1 -> atomic {
dynticks = dynticks + 1;
}
busy = 0;
:: 1 -> skip;
fi;
rcu_nmi_enter();
assert((dynticks & 1) == 1);
rcu_nmi_exit();
if
:: busy -> skip;
:: !busy ->
atomic {
dynticks = dynticks + 1;
}
busy = 1;
fi;
od;
}/*
* Nested NMI runs atomically to emulate interrupting base_level(). */
proctype nested_NMI()
{
do
:: atomic {
rcu_nmi_enter();
assert((dynticks & 1) == 1);
rcu_nmi_exit();
}
od;
}init {
run base_NMI();
run nested_NMI();
}kernel/rcu/tree.c +49 -17
@@ -759,39 +759,71 @@ void rcu_irq_enter(void)/** * rcu_nmi_enter - inform RCU of entry to NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is active. +* If the CPU was idle from RCU's viewpoint, update rdtp->dynticks and +* rdtp->dynticks_nmi_nesting to let the RCU grace-period handling know +* that the CPU is active. This implementation permits nested NMIs, as +* long as the nesting level does not overflow an int. (You will probably +* run out of stack space first.) */ void rcu_nmi_enter (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); +int incby =2 ; -if (rdtp->dynticks_nmi_nesting ==0 && - (atomic_read (&rdtp->dynticks) &0x1 )) -return ; - rdtp->dynticks_nmi_nesting++; -smp_mb__before_atomic ();/* Force delay from prior write. */ -atomic_inc (&rdtp->dynticks); -/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ -smp_mb__after_atomic ();/* See above. */ -WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); +/* Complain about underflow. */ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <0 ); + +/* +* If idle from RCU viewpoint, atomically increment ->dynticks +* to mark non-idle and increment ->dynticks_nmi_nesting by one. +* Otherwise, increment ->dynticks_nmi_nesting by two. This means +* if ->dynticks_nmi_nesting is equal to one, we are guaranteed +* to be in the outermost NMI handler that interrupted an RCU-idle +* period (observation due to Andy Lutomirski). +*/ +if (!(atomic_read (&rdtp->dynticks) &0x1 )) { +smp_mb__before_atomic ();/* Force delay from prior write. */ +atomic_inc (&rdtp->dynticks); +/* atomic_inc() before later RCU read-side crit sects */ +smp_mb__after_atomic ();/* See above. */ +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + incby =1 ; + } + rdtp->dynticks_nmi_nesting += incby; +barrier (); }/** * rcu_nmi_exit - inform RCU of exit from NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is no longer active. +* If we are returning from the outermost NMI handler that interrupted an +* RCU-idle period, update rdtp->dynticks and rdtp->dynticks_nmi_nesting +* to let the RCU grace-period handling know that the CPU is back to +* being RCU-idle. */ void rcu_nmi_exit (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); -if (rdtp->dynticks_nmi_nesting ==0 || - --rdtp->dynticks_nmi_nesting !=0 ) +/* +* Check for ->dynticks_nmi_nesting underflow and bad ->dynticks. +* (We are exiting an NMI handler, so RCU better be paying attention +* to us!) +*/ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <=0 ); +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + +/* +* If the nesting level is not 1, the CPU wasn't RCU-idle, so +* leave it in non-RCU-idle state. +*/ +if (rdtp->dynticks_nmi_nesting !=1 ) { + rdtp->dynticks_nmi_nesting -=2 ;return ; + } + +/* This NMI interrupted an RCU-idle CPU, restore RCU-idleness. */ + rdtp->dynticks_nmi_nesting =0 ;/* CPUs seeing atomic_inc() must see prior RCU read-side crit sects */ smp_mb__before_atomic ();/* See above. */ atomic_inc (&rdtp->dynticks);
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 46 lines · Paul E. McKenney
Quoted 45 lines · Andy Lutomirski
Quoted 23 lines · Paul E. McKenney
Quoted 11 lines · Paul E. McKenney
Quoted 3 lines · Andy Lutomirski
Quoted 1 line · Paul E. McKenney
On Mon, Nov 24, 2014 at 01:35:01PM -0800, Paul E. McKenney wrote:
[ . . . ]
Quoted 4 lines
Quoted 3 lines
And the following Promela model claims that your approach works. Should I trust it? ;-)
Quoted 6 lines
Quoted 4 lines
I think so.
Want to write a patch? If so, whose tree should it go in? I can add it to my IST series, but that seems a bit odd.
Working on it. ;-)
And here is a sneak preview of the patch. Thoughts?
Thanx, Paul
Quoted 7 lines
------------------------------------------------------------------------
rcu: Make
rcu_nmi_enter()handle nestingAndy Lutomirski is introducing ISTs into x86, which from RCU's viewpoint are NMIs. Because ISTs and NMIs can nest,
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting.You must not be a frequent reader of
entry_64.Sand the Intel SDM :) IOW, IST is just a stack switching mechanism, and these interrupts have been around forever -- they're just buggy right now.How about:
x86 has multiple types of NMI-like interrupts: real NMIs, machine checks, and, for some values of NMI-like, debugging and breakpoint interrupts. These interrupts can nest inside each other. Andy Lutomirski is adding RCU support to these interrupts, so
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting.
Quoted 6 lines
Quoted 1 line
Other than that, I like it.
And here is the updated version. Left to my normal workflow, this would go into 3.20. Please let me know if you need it earlier, but in that case, I will need you to test it fairly soon. (I don't have any way to test nested NMI-like things.)
Dunno. Tony and Borislav -- when do you want the IST stack switching stuff?
--Andy
Quoted 239 lines
Thanx, Paul
------------------------------------------------------------------------
rcu: Make
rcu_nmi_enter()handle nestingThe x86 architecture has multiple types of NMI-like interrupts: real NMIs, machine checks, and, for some values of NMI-like, debugging and breakpoint interrupts. These interrupts can nest inside each other. Andy Lutomirski is adding RCU support to these interrupts, so
rcu_nmi_enter()andrcu_nmi_exit()must now correctly handle nesting.This commit therefore introduces nesting, using a clever NMI-coordination algorithm suggested by Andy. The trick is to atomically increment ->dynticks (if needed) before manipulating ->dynticks_nmi_nesting on entry (and, accordingly, after on exit). In addition, ->dynticks_nmi_nesting is incremented by one if ->dynticks was incremented and by two otherwise. This means that when
rcu_nmi_exit()sees ->dynticks_nmi_nesting equal to one, it knows that ->dynticks must be atomically incremented.This NMI-coordination algorithms has been validated by the following Promela model, for whatever that might be worth:
/*
* Promela model for Andy Lutomirski's suggested change torcu_nmi_enter()
* that allows nesting.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright IBM Corporation, 2014
*
* Author: Paul E. McKenney <[email protected]>
*/byte
dynticks_nesting= 0;
bytedynticks_nmi_nesting= 0;
byte dynticks = 0;/*
* Promela verision ofrcu_nmi_enter().
*/
inlinercu_nmi_enter(){ byte incby; incby = 2; assert(dynticks_nmi_nesting >= 0); if :: (dynticks & 1) == 0 -> atomic { dynticks = dynticks + 1; } assert((dynticks & 1) == 1); incby = 1; :: else -> skip; fi; dynticks_nmi_nesting = dynticks_nmi_nesting + incby; assert(dynticks_nmi_nesting >= 1); }/*
* Promela verision ofrcu_nmi_exit().
*/
inlinercu_nmi_exit(){ assert(dynticks_nmi_nesting > 0); assert((dynticks & 1) != 0); if :: dynticks_nmi_nesting != 1 -> dynticks_nmi_nesting = dynticks_nmi_nesting - 2; :: else -> dynticks_nmi_nesting = 0; atomic { dynticks = dynticks + 1; } assert((dynticks & 1) == 0); fi; }/*
* Base-level NMI runs non-atomically. Crudely emulates process-level
* dynticks-idle entry/exit.
*/
proctypebase_NMI(){ byte busy; busy = 0; do :: if :: 1 -> atomic { dynticks = dynticks + 1; } busy = 0; :: 1 -> skip; fi; rcu_nmi_enter(); assert((dynticks & 1) == 1); rcu_nmi_exit(); if :: busy -> skip; :: !busy -> atomic { dynticks = dynticks + 1; } busy = 1; fi; od; }/*
* Nested NMI runs atomically to emulate interruptingbase_level(). */
proctypenested_NMI(){ do :: atomic { rcu_nmi_enter(); assert((dynticks & 1) == 1); rcu_nmi_exit(); } od; }init { run base_NMI(); run nested_NMI(); }Signed-off-by Paul E. McKenney <[email protected]>kernel/rcu/tree.c +49
-17@@ -759,39 +759,71 @@ void rcu_irq_enter(void)/** * rcu_nmi_enter - inform RCU of entry to NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is active. +* If the CPU was idle from RCU's viewpoint, update rdtp->dynticks and +* rdtp->dynticks_nmi_nesting to let the RCU grace-period handling know +* that the CPU is active. This implementation permits nested NMIs, as +* long as the nesting level does not overflow an int. (You will probably +* run out of stack space first.) */ void rcu_nmi_enter (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); +int incby =2 ; -if (rdtp->dynticks_nmi_nesting ==0 && - (atomic_read (&rdtp->dynticks) &0x1 )) -return ; - rdtp->dynticks_nmi_nesting++; -smp_mb__before_atomic ();/* Force delay from prior write. */ -atomic_inc (&rdtp->dynticks); -/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ -smp_mb__after_atomic ();/* See above. */ -WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); +/* Complain about underflow. */ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <0 ); + +/* +* If idle from RCU viewpoint, atomically increment ->dynticks +* to mark non-idle and increment ->dynticks_nmi_nesting by one. +* Otherwise, increment ->dynticks_nmi_nesting by two. This means +* if ->dynticks_nmi_nesting is equal to one, we are guaranteed +* to be in the outermost NMI handler that interrupted an RCU-idle +* period (observation due to Andy Lutomirski). +*/ +if (!(atomic_read (&rdtp->dynticks) &0x1 )) { +smp_mb__before_atomic ();/* Force delay from prior write. */ +atomic_inc (&rdtp->dynticks); +/* atomic_inc() before later RCU read-side crit sects */ +smp_mb__after_atomic ();/* See above. */ +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + incby =1 ; + } + rdtp->dynticks_nmi_nesting += incby; +barrier (); }/** * rcu_nmi_exit - inform RCU of exit from NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is no longer active. +* If we are returning from the outermost NMI handler that interrupted an +* RCU-idle period, update rdtp->dynticks and rdtp->dynticks_nmi_nesting +* to let the RCU grace-period handling know that the CPU is back to +* being RCU-idle. */ void rcu_nmi_exit (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); -if (rdtp->dynticks_nmi_nesting ==0 || - --rdtp->dynticks_nmi_nesting !=0 ) +/* +* Check for ->dynticks_nmi_nesting underflow and bad ->dynticks. +* (We are exiting an NMI handler, so RCU better be paying attention +* to us!) +*/ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <=0 ); +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + +/* +* If the nesting level is not 1, the CPU wasn't RCU-idle, so +* leave it in non-RCU-idle state. +*/ +if (rdtp->dynticks_nmi_nesting !=1 ) { + rdtp->dynticks_nmi_nesting -=2 ;return ; + } + +/* This NMI interrupted an RCU-idle CPU, restore RCU-idleness. */ + rdtp->dynticks_nmi_nesting =0 ;/* CPUs seeing atomic_inc() must see prior RCU read-side crit sects */ smp_mb__before_atomic ();/* See above. */ atomic_inc (&rdtp->dynticks);
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Borislav Petkov
maintainerRe: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 2 lines · Andy Lutomirski
Dunno. Tony and Borislav -- when do you want the IST stack switching stuff?
I'd leave that up to Tony and his testbench. I mean, we can hammer on it as much as we can and it can pass all testing locally but the real fun starts once it hits upstream and for that it doesn't matter which release... IMHO.
Signature · 5 lines
Regards/Gruss,
Boris.
Sent from a fat crate under my desk. Formatting is fine.
--Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 8 lines · Borislav Petkov
Quoted 2 lines · Andy Lutomirski
Dunno. Tony and Borislav -- when do you want the IST stack switching stuff?
I'd leave that up to Tony and his testbench. I mean, we can hammer on it as much as we can and it can pass all testing locally but the real fun starts once it hits upstream and for that it doesn't matter which release... IMHO.
Nothing quite like oddball hardware to make test planning a challenge, no two ways about it! ;-)
Thanx, Paul
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
In reply to Borislav PetkovQuoted 8 lines · Borislav Petkov
Quoted 2 lines · Andy Lutomirski
Dunno. Tony and Borislav -- when do you want the IST stack switching stuff?
I'd leave that up to Tony and his testbench. I mean, we can hammer on it as much as we can and it can pass all testing locally but the real fun starts once it hits upstream and for that it doesn't matter which release... IMHO.
So what was the net result of all the mode/RCU discussions?
Do I need some extra magic incantations in the final version of do_machine_check() beyond what was in this patch:
https://lkml.kernel.org/r/[email protected]
to make everything happy?
-Tony
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 10 lines · Tony Luck
Quoted 8 lines · Borislav Petkov
Quoted 2 lines · Andy Lutomirski
Dunno. Tony and Borislav -- when do you want the IST stack switching stuff?
I'd leave that up to Tony and his testbench. I mean, we can hammer on it as much as we can and it can pass all testing locally but the real fun starts once it hits upstream and for that it doesn't matter which release... IMHO.
Quoted 9 lines
So what was the net result of all the mode/RCU discussions?
Do I need some extra magic incantations in the final version of
do_machine_check()beyond what was in this patch:https://lkml.kernel.org/r/[email protected]
to make everything happy?
I think you need ist_begin_non_atomic() before local_irq_enable() and ist_end_non_atomic() after local_irq_disable(). Otherwise it should be good.
--Andy
Quoted 1 line
-Tony
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 3 lines · Andy Lutomirski
I think you need
ist_begin_non_atomic()beforelocal_irq_enable()andist_end_non_atomic()afterlocal_irq_disable(). Otherwise it should be good.
In your x86/paranoid branch you added:
prev_state = ist_enter(regs);
.... body of do_machine_check() here ...
ist_exit(regs, prev_state);Does that override the previous advice? Or do I still need something before I call local_irq_enable() and after local_irq_disable()?
-Tony
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 5 lines · Tony Luck
Quoted 3 lines · Andy Lutomirski
I think you need
ist_begin_non_atomic()beforelocal_irq_enable()andist_end_non_atomic()afterlocal_irq_disable(). Otherwise it should be good.
Quoted 8 lines
In your x86/paranoid branch you added:
prev_state = ist_enter(regs); .... body of do_machine_check() here ... ist_exit(regs, prev_state);Does that override the previous advice? Or do I still need something before I call
local_irq_enable()and afterlocal_irq_disable()?
I think I was just being vague. It would be:
prev_state = ist_enter(regs);
.... beginning of do_machine_check() here ...
if (whatever condition) {
ist_begin_non_atomic();
local_irq_enable();
...
local_irq_disable();
ist_end_non_atomic();
ist_exit(regs, prev_state);--Andy
Quoted 2 lines
-Tony
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
PATCH 1/1 x86, mce: Get rid of TIF_MCE_NOTIFY and associated mce tricks
We now switch to the kernel stack when a machine check interrupts during user mode. This means that we can perform recovery actions in the tail of do_machine_check()
---
On top of Andy's x86/paranoid branch
Andy: Should I really move that:
pr_err("Uncorrected hardware memory error ...
inside the ist_begin_non_atomic() section?
arch/x86/include/asm/mce.h +0 -1
@@ -190,7 +190,6 @@ enum mcp_flags {void machine_check_poll (enum mcp_flags flags,mce_banks_t *b);int mce_notify_irq (void ); -void mce_notify_process (void ); DECLARE_PER_CPU(struct mce, injectm);
arch/x86/include/asm/thread_info.h +1 -3
@@ -75,7 +75,6 @@ struct thread_info {#define TIF_SYSCALL_EMU 6/* syscall emulation active */ #define TIF_SYSCALL_AUDIT 7/* syscall auditing active */ #define TIF_SECCOMP 8/* secure computing */ -#define TIF_MCE_NOTIFY 10/* notify userspace of an MCE */ #define TIF_USER_RETURN_NOTIFY 11/* notify kernel of userspace return */ #define TIF_UPROBE 12/* breakpointed or singlestepping */ #define TIF_NOTSC 16/* TSC is not accessible in userland */
@@ -100,7 +99,6 @@ struct thread_info {#define _TIF_SYSCALL_EMU (1 << TIF_SYSCALL_EMU)#define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT)#define _TIF_SECCOMP (1 << TIF_SECCOMP) -#define _TIF_MCE_NOTIFY (1 << TIF_MCE_NOTIFY)#define _TIF_USER_RETURN_NOTIFY (1 << TIF_USER_RETURN_NOTIFY)#define _TIF_UPROBE (1 << TIF_UPROBE)#define _TIF_NOTSC (1 << TIF_NOTSC)
@@ -140,7 +138,7 @@ struct thread_info {/* Only used for 64 bit */ #define _TIF_DO_NOTIFY_MASK \ - (_TIF_SIGPENDING | _TIF_MCE_NOTIFY | _TIF_NOTIFY_RESUME | \ + (_TIF_SIGPENDING | _TIF_NOTIFY_RESUME | \ _TIF_USER_RETURN_NOTIFY | _TIF_UPROBE)/* flags to check in __switch_to() */
arch/x86/kernel/cpu/mcheck/mce.c +25 -84
139 lines not on this page · raw message
arch/x86/kernel/signal.c +0 -6
@@ -740,12 +740,6 @@ do_notify_resume(struct pt_regs *regs, void *unused, __u32 thread_info_flags) {user_exit (); -#ifdef CONFIG_X86_MCE -/* notify userspace of pending MCEs */ -if (thread_info_flags & _TIF_MCE_NOTIFY) -mce_notify_process (); -#endif /* CONFIG_X86_64 && CONFIG_X86_MCE */ -if (thread_info_flags & _TIF_UPROBE)uprobe_notify_resume (regs);
Re: [PATCH] x86, mce: Get rid of TIF_MCE_NOTIFY and associated mce tricks
Quoted 4 lines · Tony
We now switch to the kernel stack when a machine check interrupts during user mode. This means that we can perform recovery actions in the tail of
do_machine_check()
Quoted 8 lines
Signed-off-by Tony Luck <[email protected]>---
On top of Andy's x86/paranoid branch
Andy: Should I really move that:
pr_err("Uncorrected hardware memory error ...
inside theist_begin_non_atomic()section?
I think I like it as is.
[...]
arch/x86/kernel/cpu/mcheck/mce.c void do_machine_check(struct pt_regs *regs, long error_code) · line 1185
Quoted 10 lines
arch/x86/kernel/cpu/mcheck/mce.c +6
-0@@ -1220,6 +1177,26 @@ void do_machine_check(struct pt_regs *regs, long error_code)mce_wrmsrl (MSR_IA32_MCG_STATUS ,0 ); out:sync_core (); + +if (recover_paddr == ~0ull ) + goto done; + +pr_err ("Uncorrected hardware memory error in user-access at %llx" , + recover_paddr);
printk is safe from IRQ context, so this should be okay unless we've totally screwed up. And, if we totally screwed up, seeing this before the BUGs in ist_begin_non_atomic would be nice.
Quoted 6 lines
arch/x86/kernel/cpu/mcheck/mce.c +6
-0+/* +* We must call memory_failure() here even if the current process is +* doomed. We still need to mark the page as poisoned and alert any +* other users of the page. +*/ +ist_begin_non_atomic (regs);
arch/x86/kernel/cpu/mcheck/mce.c void do_machine_check(struct pt_regs *regs, long error_code) · line 1201
Quoted 10 lines
arch/x86/kernel/cpu/mcheck/mce.c +8
-0+local_irq_enable (); +if (memory_failure (recover_paddr >>PAGE_SHIFT ,MCE_VECTOR , flags) <0 ) { +pr_err ("Memory error not recovered" ); +force_sig (SIGBUS , current); + } +local_irq_disable (); +ist_end_non_atomic (); +done:ist_exit (regs, prev_state); }
For the context-related bits:
Should I stick this in my -next branch so it can stew?
--Andy
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
RE: [PATCH] x86, mce: Get rid of TIF_MCE_NOTIFY and associated mce tricks
Quoted 3 lines
For the context-related bits:
Reviewed-by Andy Lutomirski <[email protected]>
Thanks.
Quoted 1 line
Should I stick this in my -next branch so it can stew?
That's fine with me. Boris ... any comments?
-Tony ���{.n�+�������+%�����ݶ��w��{.n�+����{��G�����{ay�ʇڙ�,j��f���h��������z_�(�階�ݢj"���m������G������?����&���~��iO��z��v�^�m����������?�I�
Re: [PATCH] x86, mce: Get rid of TIF_MCE_NOTIFY and associated mce tricks
Quoted 1 line · Tony
That's fine with me. Boris ... any comments?
Same here:
I like negative diffstats:
include/asm/mce.h | 1 include/asm/thread_info.h | 4 - kernel/cpu/mcheck/mce.c | 109 ++++++++++------------------------------------ kernel/signal.c | 6 -- 4 files changed, 26 insertions(+), 94 deletions(-)
Procedural question: so this is going in, in the next merge window, aimed at 3.20, right?
IMO that is a good plan as we have max time to deal with fallout, should there be any.
Thanks.
Signature · 5 lines
Regards/Gruss,
Boris.
Sent from a fat crate under my desk. Formatting is fine.
--Re: [PATCH] x86, mce: Get rid of TIF_MCE_NOTIFY and associated mce tricks
Quoted 9 lines · Borislav Petkov
Quoted 1 line · Tony
That's fine with me. Boris ... any comments?
Same here:
Acked-by Borislav Petkov <[email protected]>I like negative diffstats:
Quoted 8 lines
include/asm/mce.h | 1 include/asm/thread_info.h | 4 - kernel/cpu/mcheck/mce.c | 109 ++++++++++------------------------------------ kernel/signal.c | 6 -- 4 files changed, 26 insertions(+), 94 deletions(-)Procedural question: so this is going in, in the next merge window, aimed at 3.20, right?
Yes, assuming that Ingo and Thomas agree.
When does Paul usually send the rcu pull request? I can send these as a pull request or an emailed series after that happens.
--Andy
Quoted 12 lines
IMO that is a good plan as we have max time to deal with fallout, should there be any.
Thanks.
--
Regards/Gruss,
Boris.Sent from a fat crate under my desk. Formatting is fine. --
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Borislav Petkov
maintainerRe: [PATCH] x86, mce: Get rid of TIF_MCE_NOTIFY and associated mce tricks
Quoted 2 lines · Andy Lutomirski
When does Paul usually send the rcu pull request? I can send these as a pull request or an emailed series after that happens.
I'd put a note in the pull request for tip guys to pull after Paul's pull req., for example.
Thanks.
Signature · 5 lines
Regards/Gruss,
Boris.
Sent from a fat crate under my desk. Formatting is fine.
--Re: [PATCH] x86, mce: Get rid of TIF_MCE_NOTIFY and associated mce tricks
In reply to Andy LutomirskiQuoted 16 lines · Andy Lutomirski
Quoted 15 lines · Borislav Petkov
Quoted 1 line · Tony
That's fine with me. Boris ... any comments?
Same here:
Acked-by Borislav Petkov <[email protected]>I like negative diffstats:
include/asm/mce.h | 1 include/asm/thread_info.h | 4 - kernel/cpu/mcheck/mce.c | 109 ++++++++++------------------------------------ kernel/signal.c | 6 -- 4 files changed, 26 insertions(+), 94 deletions(-)
Quoted 7 lines
Quoted 2 lines
Procedural question: so this is going in, in the next merge window, aimed at 3.20, right?
Yes, assuming that Ingo and Thomas agree.
When does Paul usually send the rcu pull request? I can send these as a pull request or an emailed series after that happens.
I normally send it to -tip at about -rc5. Which is coming right up, isn't it? Guess I had better get my tree organized. ;-)
Thanx, Paul
Quoted 21 lines
--Andy
Quoted 12 lines
IMO that is a good plan as we have max time to deal with fallout, should there be any.
Thanks.
--
Regards/Gruss,
Boris.Sent from a fat crate under my desk. Formatting is fine. --
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
In reply to Paul E. McKenneyQuoted 9 lines · Paul E. McKenney
Quoted 8 lines · Andy Lutomirski
Quoted 6 lines · Paul E. McKenney
Quoted 5 lines · Paul E. McKenney
Quoted 3 lines · Andy Lutomirski
Quoted 1 line · Paul E. McKenney
On Mon, Nov 24, 2014 at 01:35:01PM -0800, Paul E. McKenney wrote:
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
[ . . . ]
Quoted 5 lines
Quoted 3 lines
And the following Promela model claims that your approach works. Should I trust it? ;-)
I think so.
OK, I added some coverage checks and also injected bugs, all of which it detected, so I am feeling at least a bit more confident in the model, the updated version of which is included below, along with the script that runs it.
Thanx, Paul
------------------------------------------------------------------------
rcu_nmi.spin
------------------------------------------------------------------------
/*
* Promela model for Andy Lutomirski's suggested change to rcu_nmi_enter()
* that allows nesting.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright IBM Corporation, 2014
*
* Author: Paul E. McKenney <[email protected]>
*/
byte dynticks_nmi_nesting = 0;
byte dynticks = 0;
#define BUSY_INCBY 2 /* set to 1 to force failure. */
/*
* Promela verision of rcu_nmi_enter().
*/
inline rcu_nmi_enter()
{
byte incby;
byte tmp;
incby = BUSY_INCBY;
assert(dynticks_nmi_nesting >= 0);
if
:: (dynticks & 1) == 0 ->
atomic {
dynticks = dynticks + 1;
}
assert((dynticks & 1) == 1);
incby = 1;
:: else ->
skip;
fi;
tmp = dynticks_nmi_nesting;
tmp = tmp + incby;
dynticks_nmi_nesting = tmp;
assert(dynticks_nmi_nesting >= 1);
}/*
* Promela verision of rcu_nmi_exit().
*/
inline rcu_nmi_exit()
{
byte tmp;
assert(dynticks_nmi_nesting > 0);
assert((dynticks & 1) != 0);
if
:: dynticks_nmi_nesting != 1 ->
tmp = dynticks_nmi_nesting;
tmp = tmp - BUSY_INCBY;
dynticks_nmi_nesting = tmp;
:: else ->
dynticks_nmi_nesting = 0;
atomic {
dynticks = dynticks + 1;
}
assert((dynticks & 1) == 0);
fi;
}/*
* Base-level NMI runs non-atomically. Crudely emulates process-level
* dynticks-idle entry/exit.
*/
proctype base_NMI()
{
byte busy;
busy = 0;
do
:: /* Emulate base-level dynticks and not. */
if
:: 1 -> atomic {
dynticks = dynticks + 1;
}
busy = 1;
:: 1 -> skip;
fi;
/* Verify that we only sometimes have base-level dynticks. */
if
:: busy == 0 -> skip;
:: busy == 1 -> skip;
fi;
/* Model RCU's NMI entry and exit actions. */
rcu_nmi_enter();
assert((dynticks & 1) == 1);
rcu_nmi_exit();
/* Emulated re-entering base-level dynticks and not. */
if
:: !busy -> skip;
:: busy ->
atomic {
dynticks = dynticks + 1;
}
busy = 0;
fi;
/* We had better now be in dyntick-idle mode. */
assert((dynticks & 1) == 0);
od;
}/*
* Nested NMI runs atomically to emulate interrupting base_level(). */
proctype nested_NMI()
{
do
:: /*
* Use an atomic section to model a nested NMI. This is
* guaranteed to interleave into base_NMI() between a pair
* of base_NMI() statements, just as a nested NMI would.
*/
atomic {
/* Verify that we only sometimes are in dynticks. */
if
:: (dynticks & 1) == 0 -> skip;
:: (dynticks & 1) == 1 -> skip;
fi;
/* Model RCU's NMI entry and exit actions. */
rcu_nmi_enter();
assert((dynticks & 1) == 1);
rcu_nmi_exit();
}
od;
}init {
run base_NMI();
run nested_NMI();
}------------------------------------------------------------------------rcu_nmi.sh
------------------------------------------------------------------------
if ! spin -a rcu_nmi.spin
then
echo Spin errors!!!
exit 1
fi
if ! cc -DSAFETY -o pan pan.c
then
echo Compilation errors!!!
exit 1
fi
./pan -m100000
# For errors: spin -p -l -g -t rcu_nmi.spin < rcu_nmi.spin.trail
Lai Jiangshan
Reviewed-byRe: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
In reply to Paul E. McKenneyQuoted 4 lines
Signed-off-by Paul E. McKenney <[email protected]>
Quoted 41 lines
kernel/rcu/tree.c +16
-12@@ -759,39 +759,71 @@ void rcu_irq_enter(void)/** * rcu_nmi_enter - inform RCU of entry to NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is active. +* If the CPU was idle from RCU's viewpoint, update rdtp->dynticks and +* rdtp->dynticks_nmi_nesting to let the RCU grace-period handling know +* that the CPU is active. This implementation permits nested NMIs, as +* long as the nesting level does not overflow an int. (You will probably +* run out of stack space first.) */ void rcu_nmi_enter (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); +int incby =2 ; -if (rdtp->dynticks_nmi_nesting ==0 && - (atomic_read (&rdtp->dynticks) &0x1 )) -return ; - rdtp->dynticks_nmi_nesting++; -smp_mb__before_atomic ();/* Force delay from prior write. */ -atomic_inc (&rdtp->dynticks); -/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ -smp_mb__after_atomic ();/* See above. */ -WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); +/* Complain about underflow. */ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <0 ); + +/* +* If idle from RCU viewpoint, atomically increment ->dynticks +* to mark non-idle and increment ->dynticks_nmi_nesting by one. +* Otherwise, increment ->dynticks_nmi_nesting by two. This means +* if ->dynticks_nmi_nesting is equal to one, we are guaranteed +* to be in the outermost NMI handler that interrupted an RCU-idle +* period (observation due to Andy Lutomirski).
kernel/rcu/tree.c void rcu_irq_enter(void) · line 792
Quoted 10 lines
kernel/rcu/tree.c +10
-0+*/ +if (!(atomic_read (&rdtp->dynticks) &0x1 )) { +smp_mb__before_atomic ();/* Force delay from prior write. */ +atomic_inc (&rdtp->dynticks); +/* atomic_inc() before later RCU read-side crit sects */ +smp_mb__after_atomic ();/* See above. */ +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + incby =1 ; + } + rdtp->dynticks_nmi_nesting += incby;
I prefer a "else" branch here.
if (!(atomic_read(&rdtp->dynticks) & 0x1)) {
...
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting); /* paired with "rdtp->dynticks_nmi_nesting = 0" */
rdtp->dynticks_nmi_nesting = 1; /* paired with "if (rdtp->dynticks_nmi_nesting != 1) {" */
} else {
rdtp->dynticks_nmi_nesting += 2;
}Quoted 50 lines
kernel/rcu/tree.c +23
-6+barrier (); }/** * rcu_nmi_exit - inform RCU of exit from NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is no longer active. +* If we are returning from the outermost NMI handler that interrupted an +* RCU-idle period, update rdtp->dynticks and rdtp->dynticks_nmi_nesting +* to let the RCU grace-period handling know that the CPU is back to +* being RCU-idle. */ void rcu_nmi_exit (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); -if (rdtp->dynticks_nmi_nesting ==0 || - --rdtp->dynticks_nmi_nesting !=0 ) +/* +* Check for ->dynticks_nmi_nesting underflow and bad ->dynticks. +* (We are exiting an NMI handler, so RCU better be paying attention +* to us!) +*/ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <=0 ); +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + +/* +* If the nesting level is not 1, the CPU wasn't RCU-idle, so +* leave it in non-RCU-idle state. +*/ +if (rdtp->dynticks_nmi_nesting !=1 ) { + rdtp->dynticks_nmi_nesting -=2 ;return ; + } + +/* This NMI interrupted an RCU-idle CPU, restore RCU-idleness. */ + rdtp->dynticks_nmi_nesting =0 ;/* CPUs seeing atomic_inc() must see prior RCU read-side crit sects */ smp_mb__before_atomic ();/* See above. */ atomic_inc (&rdtp->dynticks); --To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
.
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 5 lines · Lai Jiangshan
Quoted 3 lines
Signed-off-by Paul E. McKenney <[email protected]>Reviewed-by Lai Jiangshan <[email protected]>
Thank you, recorded!
Quoted 54 lines
Quoted 41 lines
kernel/rcu/tree.c +16
-12@@ -759,39 +759,71 @@ void rcu_irq_enter(void)/** * rcu_nmi_enter - inform RCU of entry to NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is active. +* If the CPU was idle from RCU's viewpoint, update rdtp->dynticks and +* rdtp->dynticks_nmi_nesting to let the RCU grace-period handling know +* that the CPU is active. This implementation permits nested NMIs, as +* long as the nesting level does not overflow an int. (You will probably +* run out of stack space first.) */ void rcu_nmi_enter (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); +int incby =2 ; -if (rdtp->dynticks_nmi_nesting ==0 && - (atomic_read (&rdtp->dynticks) &0x1 )) -return ; - rdtp->dynticks_nmi_nesting++; -smp_mb__before_atomic ();/* Force delay from prior write. */ -atomic_inc (&rdtp->dynticks); -/* CPUs seeing atomic_inc() must see later RCU read-side crit sects */ -smp_mb__after_atomic ();/* See above. */ -WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); +/* Complain about underflow. */ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <0 ); + +/* +* If idle from RCU viewpoint, atomically increment ->dynticks +* to mark non-idle and increment ->dynticks_nmi_nesting by one. +* Otherwise, increment ->dynticks_nmi_nesting by two. This means +* if ->dynticks_nmi_nesting is equal to one, we are guaranteed +* to be in the outermost NMI handler that interrupted an RCU-idle +* period (observation due to Andy Lutomirski). kernel/rcu/tree.c void rcu_irq_enter(void) · line 792
Quoted 10 lines
kernel/rcu/tree.c +10
-0+*/ +if (!(atomic_read (&rdtp->dynticks) &0x1 )) { +smp_mb__before_atomic ();/* Force delay from prior write. */ +atomic_inc (&rdtp->dynticks); +/* atomic_inc() before later RCU read-side crit sects */ +smp_mb__after_atomic ();/* See above. */ +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + incby =1 ; + } + rdtp->dynticks_nmi_nesting += incby;I prefer a "else" branch here.
Quoted 7 lines
if (!(atomic_read(&rdtp->dynticks) & 0x1)) {
...
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting); /* paired with "rdtp->dynticks_nmi_nesting = 0" */
rdtp->dynticks_nmi_nesting = 1; /* paired with "if (rdtp->dynticks_nmi_nesting != 1) {" */
} else {
rdtp->dynticks_nmi_nesting += 2;
}I avoided this approach due to the extra line.
Thanx, Paul
Quoted 51 lines
Quoted 50 lines
kernel/rcu/tree.c +23
-6+barrier (); }/** * rcu_nmi_exit - inform RCU of exit from NMI context * -* If the CPU was idle with dynamic ticks active, and there is no -* irq handler running, this updates rdtp->dynticks_nmi to let the -* RCU grace-period handling know that the CPU is no longer active. +* If we are returning from the outermost NMI handler that interrupted an +* RCU-idle period, update rdtp->dynticks and rdtp->dynticks_nmi_nesting +* to let the RCU grace-period handling know that the CPU is back to +* being RCU-idle. */ void rcu_nmi_exit (void ) {struct rcu_dynticks *rdtp =this_cpu_ptr (&rcu_dynticks); -if (rdtp->dynticks_nmi_nesting ==0 || - --rdtp->dynticks_nmi_nesting !=0 ) +/* +* Check for ->dynticks_nmi_nesting underflow and bad ->dynticks. +* (We are exiting an NMI handler, so RCU better be paying attention +* to us!) +*/ +WARN_ON_ONCE (rdtp->dynticks_nmi_nesting <=0 ); +WARN_ON_ONCE (!(atomic_read (&rdtp->dynticks) &0x1 )); + +/* +* If the nesting level is not 1, the CPU wasn't RCU-idle, so +* leave it in non-RCU-idle state. +*/ +if (rdtp->dynticks_nmi_nesting !=1 ) { + rdtp->dynticks_nmi_nesting -=2 ;return ; + } + +/* This NMI interrupted an RCU-idle CPU, restore RCU-idleness. */ + rdtp->dynticks_nmi_nesting =0 ;/* CPUs seeing atomic_inc() must see prior RCU read-side crit sects */ smp_mb__before_atomic ();/* See above. */ atomic_inc (&rdtp->dynticks); --To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
.
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
In reply to Paul E. McKenneyQuoted 95 lines · Paul E. McKenney
Quoted 94 lines · Andy Lutomirski
Quoted 8 lines · Paul E. McKenney
Quoted 5 lines · Andy Lutomirski
Quoted 3 lines · Paul E. McKenney
Quoted 2 lines · Andy Lutomirski
On Fri, Nov 21, 2014 at 3:38 PM, Paul E. McKenney
<[email protected]> wrote:Returning state sounds like a bad idea, if we can reasonably avoid it.
I agree, except that we already do it for
exception_enter(), etc. But yes, changing fewer things is nice.Quoted 79 lines
And I think I finally see what you are pointing out about my code: If another NMI comes in between the time I increment ->dynticks_nmi_nesting and the time I atomically increment ->dynticks, the nested NMI handler will incorrectly believe that RCU is already paying attention to this CPU. Which would indeed not be at all good, so good catch!!!
Quoted 36 lines
Otherwise, I think that there may need to be enough state somewhere so that the outermost nested
rcu_nmi_enterknows whether to increment dynticks. For example,dynticks_nmi_nestingcould store the nesting count * 2 - (1 if the outermost nested user needs to increment dynticks). Something like:void
rcu_nmi_enter(void)
{
/* Be very careful -- this function may be called reentrently on the
same CPU. */
atomically: increment dynticks if it's even./* If an rcu_nmi_enter/rcu_nmi_exit pair happens here, then it will not change
* the state. */local_inc(&dynticks_nmi_nesting, (we incremented dynticks ? 1 : 2));
WARN_ON(we incremented dynticks and dynticks_nmi_nesting was nonzero);
}void
rcu_nmi_exit(void)
{
WARN_ON(!(dynticks & 1));
locally atomically:dynticks_nmi_nesting-= 2, unlessdynticks_nmi_nesting== 1, in which case set it to zeroif (
dynticks_nmi_nestingwas 1)atomic_inc(&dynticks); }The invariant here is that, for a single unnested enter/exit, if
dynticks_nmi_nesting!= 0, then dynticks is odd. As a result, an rcu_nmi_enter/rcu_nmi_exit pair at any time whendynticks_nmi_nesting!= 0 *or* dynticks is odd will have no net effect, so the invariant, in fact, holds for all invocations, nested or otherwise.Quoted 7 lines
At least one of those conditions is true at all times during the execution of outermost pair, starting with the first atomic operation and ending with the final
atomic_inc. So they nest properly no matter what else happens (unless, of course, someone else pokes dynticks in the middle).Thoughts?
Let's see... The evenness of ->dynticks should be preserved by nested NMI handlers, so the check and increment need not be atomic. We don't have any way (other than atomic operations) to do local atomic modifications on all architectures, because we cannot mask NMIs. (Yes, it can work on x86, but this is common code that needs to work everywhere.) On the other hand, presumably NMIs are rare, so atomic modification of the NMI nesting counter should be OK, at least if it proves absolutely necessary. And I am thinking that a mechanical proof will be needed here. :-/
But first, let me try generating the code and informally evaluating it:
1 struct rcu_dynticks { 2 long long dynticks_nesting; 3 int dynticks_nmi_nesting; 4 atomic_t dynticks; 5 }; 6 7 void rcu_nmi_enter(void) 8 { 9 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); 10 int incby = 2; 11 12 if (!(atomic_read(&rdtp->dynticks) & 0x1)) { 13 smp_mb__before_atomic(); 14 atomic_inc(&rdtp->dynticks); 15 smp_mb__after_atomic(); 16 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 17 incby = 1;
Quoted 3 lines
Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 1)here, perhaps?That would make sense.
I take it back. We get here only if there is no lower-level NMI in effect. I could do WARN_ON_ONCE(rdtp->dynticks_nmi_nesting > 0), though.
Thanx, Paul
Quoted 150 lines
Quoted 6 lines
Quoted 2 lines
18 } 19 rdtp->dynticks_nmi_nesting += incby;Oh, I see why you don't need
local_add-- it's because an nmi in the middle of this increment won't have any effect on the interrupted code, so even a software RMW will be okay.Yep! ;-)
Quoted 11 lines
Quoted 10 lines
20 barrier(); 21 } 22 23 void rcu_nmi_exit(void) 24 { 25 struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); 26 27 WARN_ON_ONCE(!rdtp->dynticks_nmi_nesting); 28 WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); 29 if (rdtp->dynticks_nmi_nesting != 1) {Quoted 1 line
WARN_ON_ONCE(rdtp->dynticks_nmi_nesting < 2), perhaps?This is already implied by the
WARN_ON_ONCE()on line 27 and the check on line 29.Quoted 7 lines
Quoted 5 lines
30 rdtp->dynticks_nmi_nesting -= 2; 31 return; 32 } 33 rdtp->dynticks_nmi_nesting = 0; 34 smp_mb__before_atomic();This implies
barrier(), right?Yep!
Quoted 39 lines
Quoted 39 lines
35 atomic_inc(&rdtp->dynticks); 36 smp_mb__after_atomic(); 37 WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); 38 }Line 9 picks up a pointer to this CPU's
rcu_dynticksstructure and line 10 assumes that we don't need to increment ->dynticks.Line 12 checks to see if ->dynticks is even. Note that this check is
stable: If there are nested NMIs, they will increment ->dynticks twice or not at all, and either way preserves the evenness (to be proven, of course, but that is the plan). If ->dynticks is even, lines 13-15 atomically increment it, line 16 complains if still even, and line 17 says we will increment ->dynticks_nmi_nesting by only 1.Either way, line 19 increments ->dynticks_nmi_nesting as needed and line 20 keeps the compiler from getting too cute.
For
rcu_nmi_exit(), line 25 again picks up this CPUsrcu_dynticksstructure. Lines 27 and 28 complain bitterly if invariants are violated. If line 29 finds that the value of ->dynticks_nmi_nesting is not 1, then line 30 subtracts 2 from ->dynticks_nmi_nesting and line 31 returns.Otherwise, line 33 sets ->dynticks_nmi_nesting to zero, lines 34-36 atomically increment ->dynticks with full ordering, and line 37 complains bitterly if ->dynticks is not even.
So, if an NMI occurs before rcu_nmi_enter's atomic increment, then the nested NMI's
rcu_nmi_enter()andrcu_nmi_exit()will think that they are not nested, which is the correct thing for them to think in that case. They will increment ->dynticks twice and restore ->dynticks_nmi_nesting to zero (adding and then subtracting 1). If the NMI happens after the atomic increment, then the nestedrcu_nmi_enter()andrcu_nmi_exit()will leave ->dynticks alone, and will restore ->dynticks_nmi_nesting to zero (adding and subtracting two again). If the NMI happens after the increment of ->dynticks_nmi_nesting, the nested NMI'srcu_nmi_enter()andrcu_nmi_exit()will again restore ->dynticks_nmi_nesting, but this time to one (again adding and subtracting two).Quoted 8 lines
Quoted 6 lines
In
rcu_nmi_exit(), ->dynticks_nmi_nesting of zero had better not happen, one means we need to atomically increment ->dynticks, and other values mean that we are partially or fully nested. Reasoning proceeds as forrcu_nmi_enter(), but in the opposite direction.Whew! That might even work.
I think I like this, with the warnings above.
OK with dropping the one that I called out as redundant?
Quoted 44 lines
Quoted 44 lines
But how about taking a different approach. Assuming that there can never be more than (say) 14 nesting NMI-like things, use the lower four bits of ->dynticks to represent the NMI nesting and the upper 28 bits as the counter. This of course requires modifying lots of places in RCU that check the counter, but it is probably time to abstract the check anyway.
This would allow my earlier attempted logic to work and (maybe) simplify the reasoning a bit (and yes, the "magic" constants need macros):
void rcu_nmi_enter(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x01; WARN_ON_ONCE(nexting == 0xf); if (nesting == 0) { if (atomic_read(&rdtp->dynticks) & 0x10) return; incby = 0x11; } smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(!(atomic_read(&rdtp->dynticks) & 0x1)); } void rcu_nmi_exit(void) { struct rcu_dynticks *rdtp = this_cpu_ptr(&rcu_dynticks); int nesting = atomic_read(&rdtp->dynticks) & 0xf; int incby = 0x0f; if (nesting == 0) return; if (nesting > 1) incby = -1; smp_mb__before_atomic(); atomic_add(&rdtp->dynticks, incby); smp_mb__after_atomic(); WARN_ON_ONCE(atomic_read(&rdtp->dynticks) & 0x1); }Quoted 3 lines
Quoted 1 line
Over to you! ;-)
This latter one is all you :)
Well, let's see how I feel about it after trying a Promela model of the first code sequence. ;-)
Thanx, Paul
Quoted 12 lines
--Andy
Quoted 3 lines
Thanx, Paul
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
In reply to Paul E. McKenneyQuoted 34 lines · Paul E. McKenney
Quoted 12 lines · Andy Lutomirski
Quoted 10 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.Quoted 6 lines
I should add:
I have no idea why RCU read-side critical sections are safe inside
__do_page_faulttoday. It's guarded byexception_enter(), but that doesn't do anything if context tracking is off, and context tracking is usually off. What am I missing here?Ah! There are three cases:
1. Context tracking is off on a non-idle CPU. In this case, RCU is
still paying attention to CPUs running in both userspace and in the kernel. So if a page fault happens, RCU will be set up to notice any RCU read-side critical sections.2. Context tracking is on on a non-idle CPU. In this case, RCU
might well be ignoring userspace execution:NO_HZ_FULLand all that. However, as you pointed out, in this case the context-tracking code lets RCU know that we have entered the kernel, which means that RCU will again be paying attention to RCU read-side critical sections.
Quoted 6 lines
3. The CPU is idle. In this case, RCU is ignoring the CPU, so
if we take a page fault when context tracking is off, life will be hard. But the kernel is not supposed to take page faults in the idle loop, so this is not a problem.Make sense?
To zoom out the picture for Andy, context tracking is never used 99% of all workloads. It's only used for NO_HZ_FULL. RCU needs to tick to poll on RCU uses. But when we run in userspace, RCU isn't used and thus doesn't need the tick which we can stop. So context tracking is there to tell RCU about CPUs crossing user/kernel boundaries.
Also these hooks account the cputime spent in userspace and kernelspace in the absence of a tick.
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
In reply to Andy LutomirskiQuoted 23 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.This fixes two issues:
- Scheduling from an IST interrupt handler will now warn. It would previously appear to work as long as we got lucky and nothing overwrote the stack frame. (I don't know of any bugs in this that would trigger the warning, but it's good to be on the safe side.)
- RCU handling in IST context was dangerous. As far as I know, only machine checks were likely to trigger this, but it's good to be on the safe side.
Quoted 7 lines
Note that the machine check handlers appears to have been missing any context tracking at all before this patch.
Cc "Paul E. McKenney" <[email protected]>Cc Josh Triplett <[email protected]>Cc Frédéric Weisbecker <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>
Just out of curiosity... Can an NMI occur in IST context? If it can, I need to make rcu_nmi_enter() and rcu_nmi_exit() deal properly with nested calls.
Thanx, Paul
Quoted 235 lines
--- arch/x86/include/asm/traps.h | 4 +++ arch/x86/kernel/cpu/mcheck/mce.c | 5 ++++ arch/x86/kernel/cpu/mcheck/p5.c | 6 +++++ arch/x86/kernel/cpu/mcheck/winchip.c | 5 ++++ arch/x86/kernel/traps.c | 49 ++++++++++++++++++++++++++++++------ 5 files changed, 61 insertions(+), 8 deletions(-)arch/x86/include/asm/traps.h +4
-0@@ -1,6 +1,7 @@#ifndef _ASM_X86_TRAPS_H#define _ASM_X86_TRAPS_H +#include <linux/context_tracking_state.h> #include <linux/kprobes.h> #include <asm/debugreg.h> @@ -109,6 +110,9 @@ asmlinkage void smp_thermal_interrupt(void); asmlinkage void mce_threshold_interrupt(void);#endif +extern enum ctx_state ist_enter (struct pt_regs *regs); +extern void ist_exit (struct pt_regs *regs,enum ctx_state prev_state); +/* Interrupts/Exceptions */ enum {X86_TRAP_DE =0 ,/* 0, Divide-by-zero */ arch/x86/kernel/cpu/mcheck/mce.c +5
-0@@ -43,6 +43,7 @@#include <linux/export.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> @@ -1016,6 +1017,7 @@ void do_machine_check(struct pt_regs *regs, long error_code) {struct mca_config *cfg = &mca_cfg;struct mce m, *final; +enum ctx_state prev_state;int i;int worst =0 ;int severity;@@ -1038,6 +1040,8 @@ void do_machine_check(struct pt_regs *regs, long error_code)DECLARE_BITMAP (valid_banks,MAX_NR_BANKS );char *msg ="Unknown" ; + prev_state =ist_enter (regs); +this_cpu_inc (mce_exception_count);if (!cfg->banks)@@ -1168,6 +1172,7 @@ void do_machine_check(struct pt_regs *regs, long error_code)mce_wrmsrl (MSR_IA32_MCG_STATUS ,0 ); out:sync_core (); +ist_exit (regs, prev_state); }EXPORT_SYMBOL_GPL (do_machine_check);arch/x86/kernel/cpu/mcheck/p5.c +6
-0@@ -8,6 +8,7 @@#include <linux/smp.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> @@ -17,8 +18,11 @@ int mce_p5_enabled __read_mostly;/* Machine check handler for Pentium class Intel CPUs: */ static void pentium_machine_check (struct pt_regs *regs,long error_code) { +enum ctx_state prev_state;u32 loaddr, hi, lotype; + prev_state =ist_enter (regs); +rdmsr (MSR_IA32_P5_MC_ADDR , loaddr, hi);rdmsr (MSR_IA32_P5_MC_TYPE , lotype, hi);@@ -33,6 +37,8 @@ static void pentium_machine_check(struct pt_regs *regs, long error_code) }add_taint (TAINT_MACHINE_CHECK ,LOCKDEP_NOW_UNRELIABLE ); + +ist_exit (regs, prev_state); }/* Set up machine check reporting for processors with Intel style MCE: */ arch/x86/kernel/cpu/mcheck/winchip.c +5
-0@@ -7,14 +7,19 @@#include <linux/types.h> #include <asm/processor.h> +#include <asm/traps.h> #include <asm/mce.h> #include <asm/msr.h> /* Machine check handler for WinChip C6: */ static void winchip_machine_check (struct pt_regs *regs,long error_code) { +enum ctx_state prev_state =ist_enter (regs); +printk (KERN_EMERG "CPU0: Machine Check Exception.\n" );add_taint (TAINT_MACHINE_CHECK ,LOCKDEP_NOW_UNRELIABLE ); + +ist_exit (regs, prev_state); }/* Set up machine check reporting on the Winchip C6 series */ arch/x86/kernel/traps.c +41
-8@@ -107,6 +107,39 @@ static inline void preempt_conditional_cli(struct pt_regs *regs)preempt_count_dec (); } +enum ctx_state ist_enter (struct pt_regs *regs) +{ +/* +* We are atomic because we're on the IST stack (or we're on x86_32, +* in which case we still shouldn't schedule. +*/ +preempt_count_add (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) { +/* Other than that, we're just an exception. */ +return exception_enter (); + }else { +/* +* We might have interrupted pretty much anything. In +* fact, if we're a machine check, we can even interrupt +* NMI processing. We don't want in_nmi() to return true, +* but we need to notify RCU. +*/ +rcu_nmi_enter (); +return IN_KERNEL ;/* the value is irrelevant. */ + } +} + +void ist_exit (struct pt_regs *regs,enum ctx_state prev_state) +{ +preempt_count_sub (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) +return exception_exit (prev_state); +else +rcu_nmi_exit (); +} + static nokprobe_inline intdo_trap_no_signal (struct task_struct *tsk,int trapnr,char *str,struct pt_regs *regs,long error_code)@@ -244,14 +277,14 @@ dotraplinkage void do_stack_segment(struct pt_regs *regs, long error_code) {enum ctx_state prev_state; - prev_state =exception_enter (); + prev_state =ist_enter (regs);if (notify_die (DIE_TRAP ,"stack segment" , regs, error_code,X86_TRAP_SS ,SIGBUS ) !=NOTIFY_STOP ) {preempt_conditional_sti (regs);do_trap (X86_TRAP_SS ,SIGBUS ,"stack segment" , regs, error_code,NULL );preempt_conditional_cli (regs); } -exception_exit (prev_state); +ist_exit (regs, prev_state); } dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code)@@ -259,8 +292,8 @@ dotraplinkage void do_double_fault(struct pt_regs *regs, long error_code)static const char str[] ="double fault" ;struct task_struct *tsk = current; -exception_enter (); -/* Return not checked because double check cannot be ignored */ +ist_enter (regs); +/* Return not checked because double fault cannot be ignored */ notify_die (DIE_TRAP , str, regs, error_code,X86_TRAP_DF ,SIGSEGV ); tsk->thread.error_code = error_code;@@ -343,7 +376,7 @@ dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)if (poke_int3_handler (regs))return ; - prev_state =exception_enter (); + prev_state =ist_enter (regs);#ifdef CONFIG_KGDB_LOW_LEVEL_TRAP if (kgdb_ll_trap (DIE_INT3 ,"int3" , regs, error_code,X86_TRAP_BP ,SIGTRAP ) ==NOTIFY_STOP )@@ -369,7 +402,7 @@ dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)preempt_conditional_cli (regs);debug_stack_usage_dec (); exit: -exception_exit (prev_state); +ist_exit (regs, prev_state); }NOKPROBE_SYMBOL (do_int3);@@ -433,7 +466,7 @@ dotraplinkage void do_debug(struct pt_regs *regs, long error_code)unsigned long dr6;int si_code; - prev_state =exception_enter (); + prev_state =ist_enter (regs);get_debugreg (dr6,6 );@@ -508,7 +541,7 @@ dotraplinkage void do_debug(struct pt_regs *regs, long error_code)debug_stack_usage_dec (); exit: -exception_exit (prev_state); +ist_exit (regs, prev_state); }NOKPROBE_SYMBOL (do_debug);Signature · 1 line
1.9.3
Borislav Petkov
maintainerRe: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
In reply to Andy LutomirskiQuoted 37 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.This fixes two issues:
- Scheduling from an IST interrupt handler will now warn. It would previously appear to work as long as we got lucky and nothing overwrote the stack frame. (I don't know of any bugs in this that would trigger the warning, but it's good to be on the safe side.)
- RCU handling in IST context was dangerous. As far as I know, only machine checks were likely to trigger this, but it's good to be on the safe side.
Note that the machine check handlers appears to have been missing any context tracking at all before this patch.
Cc "Paul E. McKenney" <[email protected]>Cc Josh Triplett <[email protected]>Cc Frédéric Weisbecker <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/include/asm/traps.h | 4 +++ arch/x86/kernel/cpu/mcheck/mce.c | 5 ++++ arch/x86/kernel/cpu/mcheck/p5.c | 6 +++++ arch/x86/kernel/cpu/mcheck/winchip.c | 5 ++++ arch/x86/kernel/traps.c | 49 ++++++++++++++++++++++++++++++------ 5 files changed, 61 insertions(+), 8 deletions(-)
...
Quoted 19 lines
arch/x86/kernel/traps.c +11
-0@@ -107,6 +107,39 @@ static inline void preempt_conditional_cli(struct pt_regs *regs)preempt_count_dec (); } +enum ctx_state ist_enter (struct pt_regs *regs) +{ +/* +* We are atomic because we're on the IST stack (or we're on x86_32, +* in which case we still shouldn't schedule. +*/ +preempt_count_add (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) { +/* Other than that, we're just an exception. */ +return exception_enter ();
arch/x86/kernel/traps.c static inline void preempt_conditional_cli(struct pt_regs *regs) · line 130
Quoted 10 lines
arch/x86/kernel/traps.c +10
-0+ }else { +/* +* We might have interrupted pretty much anything. In +* fact, if we're a machine check, we can even interrupt +* NMI processing. We don't want in_nmi() to return true, +* but we need to notify RCU. +*/ +rcu_nmi_enter (); +return IN_KERNEL ;/* the value is irrelevant. */ + }
I guess dropping the explicit else-branch could make it look a bit nicer with the curly braces gone and all...
enum ctx_state ist_enter(struct pt_regs *regs)
{
/*
* We are atomic because we're on the IST stack (or we're on x86_32,
* in which case we still shouldn't schedule.
*/
preempt_count_add(HARDIRQ_OFFSET);
if (user_mode_vm(regs))
/* Other than that, we're just an exception. */
return exception_enter();
/*
* We might have interrupted pretty much anything. In fact, if we're a
* machine check, we can even interrupt NMI processing. We don't want
* in_nmi() to return true, but we need to notify RCU.
*/
rcu_nmi_enter();
return IN_KERNEL; /* the value is irrelevant. */
} arch/x86/kernel/traps.c static inline void preempt_conditional_cli(struct pt_regs *regs) · line 141
Quoted 11 lines
arch/x86/kernel/traps.c +11
-0+} + +void ist_exit (struct pt_regs *regs,enum ctx_state prev_state) +{ +preempt_count_sub (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) +return exception_exit (prev_state); +else +rcu_nmi_exit (); +}
Ditto here.
Quoted 4 lines
arch/x86/kernel/traps.c +1
-0+ static nokprobe_inline intdo_trap_no_signal (struct task_struct *tsk,int trapnr,char *str,struct pt_regs *regs,long error_code)
Signature · 5 lines
Regards/Gruss,
Boris.
Sent from a fat crate under my desk. Formatting is fine.
--Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 75 lines · Borislav Petkov
Quoted 37 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.This fixes two issues:
- Scheduling from an IST interrupt handler will now warn. It would previously appear to work as long as we got lucky and nothing overwrote the stack frame. (I don't know of any bugs in this that would trigger the warning, but it's good to be on the safe side.)
- RCU handling in IST context was dangerous. As far as I know, only machine checks were likely to trigger this, but it's good to be on the safe side.
Note that the machine check handlers appears to have been missing any context tracking at all before this patch.
Cc "Paul E. McKenney" <[email protected]>Cc Josh Triplett <[email protected]>Cc Frédéric Weisbecker <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/include/asm/traps.h | 4 +++ arch/x86/kernel/cpu/mcheck/mce.c | 5 ++++ arch/x86/kernel/cpu/mcheck/p5.c | 6 +++++ arch/x86/kernel/cpu/mcheck/winchip.c | 5 ++++ arch/x86/kernel/traps.c | 49 ++++++++++++++++++++++++++++++------ 5 files changed, 61 insertions(+), 8 deletions(-)...
Quoted 19 lines
arch/x86/kernel/traps.c +11
-0@@ -107,6 +107,39 @@ static inline void preempt_conditional_cli(struct pt_regs *regs)preempt_count_dec (); } +enum ctx_state ist_enter (struct pt_regs *regs) +{ +/* +* We are atomic because we're on the IST stack (or we're on x86_32, +* in which case we still shouldn't schedule. +*/ +preempt_count_add (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) { +/* Other than that, we're just an exception. */ +return exception_enter ();arch/x86/kernel/traps.c static inline void preempt_conditional_cli(struct pt_regs *regs) · line 130
Quoted 10 lines
arch/x86/kernel/traps.c +10
-0+ }else { +/* +* We might have interrupted pretty much anything. In +* fact, if we're a machine check, we can even interrupt +* NMI processing. We don't want in_nmi() to return true, +* but we need to notify RCU. +*/ +rcu_nmi_enter (); +return IN_KERNEL ;/* the value is irrelevant. */ + }I guess dropping the explicit else-branch could make it look a bit nicer with the curly braces gone and all...
enum
ctx_stateist_enter(struct pt_regs *regs)
Quoted 11 lines
{
/*
* We are atomic because we're on the IST stack (or we're on x86_32,
* in which case we still shouldn't schedule.
*/
preempt_count_add(HARDIRQ_OFFSET);
if (user_mode_vm(regs))
/* Other than that, we're just an exception. */
return exception_enter();Two indented lines w/o curly braces makes me think of goto fail; :-/
TBH, when there are clearly two options, I tend to prefer the braces that make it very obvious what's going on. I had some memorable bugs several years ago that would have been impossible if I has used braces more liberally.
--Andy
Quoted 34 lines
/*
* We might have interrupted pretty much anything. In fact, if we're a
* machine check, we can even interrupt NMI processing. We don't want
*in_nmi()to return true, but we need to notify RCU.
*/
rcu_nmi_enter();
returnIN_KERNEL; /* the value is irrelevant. */
}arch/x86/kernel/traps.c static inline void preempt_conditional_cli(struct pt_regs *regs) · line 141
Quoted 11 lines
arch/x86/kernel/traps.c +11
-0+} + +void ist_exit (struct pt_regs *regs,enum ctx_state prev_state) +{ +preempt_count_sub (HARDIRQ_OFFSET ); + +if (user_mode_vm (regs)) +return exception_exit (prev_state); +else +rcu_nmi_exit (); +}Ditto here.
arch/x86/kernel/traps.c static inline void preempt_conditional_cli(struct pt_regs *regs) · line 145
Quoted 4 lines
arch/x86/kernel/traps.c +1
-0+ static nokprobe_inline intdo_trap_no_signal (struct task_struct *tsk,int trapnr,char *str,struct pt_regs *regs,long error_code)--
Regards/Gruss,
Boris.Sent from a fat crate under my desk. Formatting is fine. --
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
In reply to Andy LutomirskiQuoted 19 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.This fixes two issues:
- Scheduling from an IST interrupt handler will now warn. It would previously appear to work as long as we got lucky and nothing overwrote the stack frame. (I don't know of any bugs in this that would trigger the warning, but it's good to be on the safe side.)
Quoted 6 lines
- RCU handling in IST context was dangerous. As far as I know, only machine checks were likely to trigger this, but it's good to be on the safe side.
Note that the machine check handlers appears to have been missing any context tracking at all before this patch.
Hi Andy, Paul,
I *suspect* that the following is a result of this commit:
[ 543.999079] ===============================
[ 543.999079] [ INFO: suspicious RCU usage. ]
[ 543.999079] 3.19.0-rc5-next-20150121-sasha-00064-g3c37e35-dirty #1809 Not tainted
[ 543.999079] -------------------------------
[ 543.999079] include/linux/rcupdate.h:892 rcu_read_lock() used illegally while idle!
[ 543.999079]
[ 543.999079] other info that might help us debug this:
[ 543.999079]
[ 543.999079]
[ 543.999079] RCU used illegally from idle CPU!
[ 543.999079] rcu_scheduler_active = 1, debug_locks = 1
[ 543.999079] RCU used illegally from extended quiescent state!
[ 543.999079] 1 lock held by trinity-main/15058:
[ 543.999079] #0: (rcu_read_lock){......}, at: atomic_notifier_call_chain (kernel/notifier.c:192)
[ 543.999079]
[ 543.999079] stack backtrace:
[ 543.999079] CPU: 16 PID: 15058 Comm: trinity-main Not tainted 3.19.0-rc5-next-20150121-sasha-00064-g3c37e35-dirty #1809
[ 543.999079] 0000000000000000 0000000000000000 0000000000000001 ffff8801af907d88
[ 543.999079] ffffffff92e9e917 0000000000000011 ffff8801afcf8000 ffff8801af907db8
[ 543.999079] ffffffff815f5613 ffffffff9654d4a0 0000000000000003 ffff8801af907e28
[ 543.999079] Call Trace:
[ 543.999079] dump_stack (lib/dump_stack.c:52)
[ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259)
[ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193)
[ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192)
[ 543.999079] notify_die (kernel/notifier.c:538)
[ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538)
[ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57)
[ 543.999079] do_debug (arch/x86/kernel/traps.c:652)
[ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609)
[ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610)
[ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601)
[ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Thanks,
Sasha
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 56 lines · Sasha Levin
Quoted 19 lines · Andy Lutomirski
We currently pretend that IST context is like standard exception context, but this is incorrect. IST entries from userspace are like standard exceptions except that they use per-cpu stacks, so they are atomic. IST entries from kernel space are like NMIs from RCU's perspective -- they are not quiescent states even if they interrupted the kernel during a quiescent state.
Add and use
ist_enterandist_exitto track IST context. Even thoughx86_32has no IST stacks, we track these interrupts the same way.This fixes two issues:
- Scheduling from an IST interrupt handler will now warn. It would previously appear to work as long as we got lucky and nothing overwrote the stack frame. (I don't know of any bugs in this that would trigger the warning, but it's good to be on the safe side.)
Quoted 6 lines
- RCU handling in IST context was dangerous. As far as I know, only machine checks were likely to trigger this, but it's good to be on the safe side.
Note that the machine check handlers appears to have been missing any context tracking at all before this patch.
Hi Andy, Paul,
I *suspect* that the following is a result of this commit:
[ 543.999079] =============================== [ 543.999079] [ INFO: suspicious RCU usage. ] [ 543.999079] 3.19.0-rc5-next-20150121-sasha-00064-g3c37e35-dirty #1809 Not tainted [ 543.999079] ------------------------------- [ 543.999079] include/linux/rcupdate.h:892 rcu_read_lock() used illegally while idle! [ 543.999079] [ 543.999079] other info that might help us debug this: [ 543.999079] [ 543.999079] [ 543.999079] RCU used illegally from idle CPU! [ 543.999079] rcu_scheduler_active = 1, debug_locks = 1 [ 543.999079] RCU used illegally from extended quiescent state! [ 543.999079] 1 lock held by trinity-main/15058: [ 543.999079] #0: (rcu_read_lock){......}, at: atomic_notifier_call_chain (kernel/notifier.c:192) [ 543.999079] [ 543.999079] stack backtrace: [ 543.999079] CPU: 16 PID: 15058 Comm: trinity-main Not tainted 3.19.0-rc5-next-20150121-sasha-00064-g3c37e35-dirty #1809 [ 543.999079] 0000000000000000 0000000000000000 0000000000000001 ffff8801af907d88 [ 543.999079] ffffffff92e9e917 0000000000000011 ffff8801afcf8000 ffff8801af907db8 [ 543.999079] ffffffff815f5613 ffffffff9654d4a0 0000000000000003 ffff8801af907e28 [ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192)
Quoted 8 lines
[ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)
I don't know how to read this stack trace. Are we in do_int3, do_debug, or both? I didn't change do_debug at all.
I think that nesting exception_enter inside rcu_nmi_enter should be okay (and it had better be, even in old kernels, because I think perf does that).
So you have any idea what you (or trinity) did to trigger this?
--Andy
Borislav Petkov
maintainerRe: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 14 lines · Andy Lutomirski
Quoted 5 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192)Quoted 8 lines
[ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)
Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.
It looks like we're in do_debug. do_int3 is only on the stack but not part of the current frame if I can trust the '?' ...
Signature · 5 lines
Regards/Gruss,
Boris.
ECO tip #101: Trim your mails when you reply.
--Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 15 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 13 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192) [ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)
Quoted 6 lines
Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...
It's possible that an int3 happened and I did something wrong on return that caused a subsequent do_debug to screw up, but I don't see how my patch would have caused that.
Were there any earlier log messages?
--Andy
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 23 lines · Andy Lutomirski
Quoted 15 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 13 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192) [ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Quoted 6 lines
Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...
Quoted 5 lines
It's possible that an int3 happened and I did something wrong on return that caused a subsequent
do_debugto screw up, but I don't see how my patch would have caused that.Were there any earlier log messages?
Nope, nothing odd before or after.
Thanks,
Sasha
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 24 lines · Sasha Levin
Quoted 23 lines · Andy Lutomirski
Quoted 21 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 5 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192)Quoted 8 lines
[ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...
Quoted 7 lines
Quoted 5 lines
It's possible that an int3 happened and I did something wrong on return that caused a subsequent
do_debugto screw up, but I don't see how my patch would have caused that.Were there any earlier log messages?
Nope, nothing odd before or after.
Trinity just survived for a decent amount of time for me with my patches, other than a bunch of apparently expected OOM kills. I have no idea how to tell trinity how much memory to use.
--Andy
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 29 lines · Andy Lutomirski
Quoted 28 lines · Sasha Levin
Quoted 27 lines · Andy Lutomirski
Quoted 15 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 13 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192) [ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Quoted 6 lines
Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...It's possible that an int3 happened and I did something wrong on return that caused a subsequent
do_debugto screw up, but I don't see how my patch would have caused that.
Quoted 8 lines
Quoted 3 lines
Quoted 1 line
Were there any earlier log messages?
Nope, nothing odd before or after.
Trinity just survived for a decent amount of time for me with my patches, other than a bunch of apparently expected OOM kills. I have no idea how to tell trinity how much memory to use.
A longer trinity run on a larger VM survived (still with some OOM kills, but no taint) with these patches. I suspect that it's a regression somewhere else in the RCU changes. I have CONFIG_PROVE_RCU=y, so I should have seen the failure if it was there, I think.
--Andy
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 38 lines · Andy Lutomirski
Quoted 29 lines · Andy Lutomirski
Quoted 28 lines · Sasha Levin
Quoted 27 lines · Andy Lutomirski
Quoted 15 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 13 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192) [ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Quoted 6 lines
Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...It's possible that an int3 happened and I did something wrong on return that caused a subsequent
do_debugto screw up, but I don't see how my patch would have caused that.Quoted 7 lines
Quoted 3 lines
Quoted 1 line
Were there any earlier log messages?
Nope, nothing odd before or after.
Trinity just survived for a decent amount of time for me with my patches, other than a bunch of apparently expected OOM kills. I have no idea how to tell trinity how much memory to use.
Quoted 5 lines
A longer trinity run on a larger VM survived (still with some OOM kills, but no taint) with these patches. I suspect that it's a regression somewhere else in the RCU changes. I have
CONFIG_PROVE_RCU=y, so I should have seen the failure if it was there, I think.
If by "RCU changes" you mean my changes to the RCU infrastructure, I am going to need more of a hint than I see in this thread thus far. ;-)
Thanx, Paul
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 39 lines · Paul E. McKenney
Quoted 38 lines · Andy Lutomirski
Quoted 36 lines · Andy Lutomirski
Quoted 24 lines · Sasha Levin
Quoted 23 lines · Andy Lutomirski
Quoted 21 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 5 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192)Quoted 8 lines
[ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...Quoted 7 lines
Quoted 5 lines
It's possible that an int3 happened and I did something wrong on return that caused a subsequent
do_debugto screw up, but I don't see how my patch would have caused that.Were there any earlier log messages?
Nope, nothing odd before or after.
Trinity just survived for a decent amount of time for me with my patches, other than a bunch of apparently expected OOM kills. I have no idea how to tell trinity how much memory to use.
Quoted 9 lines
Quoted 5 lines
A longer trinity run on a larger VM survived (still with some OOM kills, but no taint) with these patches. I suspect that it's a regression somewhere else in the RCU changes. I have
CONFIG_PROVE_RCU=y, so I should have seen the failure if it was there, I think.If by "RCU changes" you mean my changes to the RCU infrastructure, I am going to need more of a hint than I see in this thread thus far. ;-)
I can't help much, since I can't reproduce the problem. Presumably if it's a bug in -tip, someone else will trigger it, too.
--Andy
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 47 lines · Andy Lutomirski
Quoted 45 lines · Paul E. McKenney
Quoted 43 lines · Andy Lutomirski
Quoted 29 lines · Andy Lutomirski
Quoted 28 lines · Sasha Levin
Quoted 27 lines · Andy Lutomirski
Quoted 15 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 13 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192) [ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Quoted 6 lines
Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...It's possible that an int3 happened and I did something wrong on return that caused a subsequent
do_debugto screw up, but I don't see how my patch would have caused that.Quoted 7 lines
Quoted 3 lines
Quoted 1 line
Were there any earlier log messages?
Nope, nothing odd before or after.
Trinity just survived for a decent amount of time for me with my patches, other than a bunch of apparently expected OOM kills. I have no idea how to tell trinity how much memory to use.
A longer trinity run on a larger VM survived (still with some OOM kills, but no taint) with these patches. I suspect that it's a regression somewhere else in the RCU changes. I have
CONFIG_PROVE_RCU=y, so I should have seen the failure if it was there, I think.
Quoted 6 lines
Quoted 3 lines
If by "RCU changes" you mean my changes to the RCU infrastructure, I am going to need more of a hint than I see in this thread thus far. ;-)
I can't help much, since I can't reproduce the problem. Presumably if it's a bug in -tip, someone else will trigger it, too.
I'm not sure what to tell you here, I'm not using any weird options for trinity to reproduce it.
It doesn't happen to frequently, but I still see it happening.
Would you like me to try a debug patch or something similar?
Thanks,
Sasha
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 55 lines · Sasha Levin
Quoted 47 lines · Andy Lutomirski
Quoted 45 lines · Paul E. McKenney
Quoted 43 lines · Andy Lutomirski
Quoted 29 lines · Andy Lutomirski
Quoted 28 lines · Sasha Levin
Quoted 27 lines · Andy Lutomirski
Quoted 15 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 13 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192) [ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Quoted 6 lines
Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...It's possible that an int3 happened and I did something wrong on return that caused a subsequent
do_debugto screw up, but I don't see how my patch would have caused that.Quoted 7 lines
Quoted 3 lines
Quoted 1 line
Were there any earlier log messages?
Nope, nothing odd before or after.
Trinity just survived for a decent amount of time for me with my patches, other than a bunch of apparently expected OOM kills. I have no idea how to tell trinity how much memory to use.
A longer trinity run on a larger VM survived (still with some OOM kills, but no taint) with these patches. I suspect that it's a regression somewhere else in the RCU changes. I have
CONFIG_PROVE_RCU=y, so I should have seen the failure if it was there, I think.Quoted 6 lines
Quoted 3 lines
If by "RCU changes" you mean my changes to the RCU infrastructure, I am going to need more of a hint than I see in this thread thus far. ;-)
I can't help much, since I can't reproduce the problem. Presumably if it's a bug in -tip, someone else will trigger it, too.
Quoted 6 lines
I'm not sure what to tell you here, I'm not using any weird options for trinity to reproduce it.
It doesn't happen to frequently, but I still see it happening.
Would you like me to try a debug patch or something similar?
After talking with Paul we know what's going on here:
do_debug() calls ist_enter() to indicate we're running on the interrupt stack. The first think ist_enter() does is:
preempt_count_add(HARDIRQ_OFFSET);
After this, as far as the kernel is concerned, we're in interrupt mode so in_interrupt() will return true.
Next, we'll call exception_enter() which won't do anything since:
void context_tracking_user_exit(void)
{
unsigned long flags;
if (!context_tracking_is_enabled())
return;
if (in_interrupt()) <=== This returns true, so nothing else gets done
return;At this stage we never tell RCU that we exited user mode, but then we try to use it calling the notifiers, which explains the warnings I'm seeing.
Thanks,
Sasha
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 59 lines · Sasha Levin
Quoted 58 lines · Sasha Levin
Quoted 47 lines · Andy Lutomirski
Quoted 45 lines · Paul E. McKenney
Quoted 43 lines · Andy Lutomirski
Quoted 29 lines · Andy Lutomirski
Quoted 28 lines · Sasha Levin
Quoted 27 lines · Andy Lutomirski
Quoted 15 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 13 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192) [ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Quoted 6 lines
Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...It's possible that an int3 happened and I did something wrong on return that caused a subsequent
do_debugto screw up, but I don't see how my patch would have caused that.Quoted 7 lines
Quoted 3 lines
Quoted 1 line
Were there any earlier log messages?
Nope, nothing odd before or after.
Trinity just survived for a decent amount of time for me with my patches, other than a bunch of apparently expected OOM kills. I have no idea how to tell trinity how much memory to use.
A longer trinity run on a larger VM survived (still with some OOM kills, but no taint) with these patches. I suspect that it's a regression somewhere else in the RCU changes. I have
CONFIG_PROVE_RCU=y, so I should have seen the failure if it was there, I think.Quoted 6 lines
Quoted 3 lines
If by "RCU changes" you mean my changes to the RCU infrastructure, I am going to need more of a hint than I see in this thread thus far. ;-)
I can't help much, since I can't reproduce the problem. Presumably if it's a bug in -tip, someone else will trigger it, too.
I'm not sure what to tell you here, I'm not using any weird options for trinity to reproduce it.
Quoted 8 lines
Quoted 3 lines
It doesn't happen to frequently, but I still see it happening.
Would you like me to try a debug patch or something similar?
After talking with Paul we know what's going on here:
do_debug()callsist_enter()to indicate we're running on the interrupt stack. The first thinkist_enter()does is:
I wonder whether there's an easy way to trigger this. Probably a watchpoint on the user stack would do the trick.
Quoted 18 lines
preempt_count_add(HARDIRQ_OFFSET);After this, as far as the kernel is concerned, we're in interrupt mode so
in_interrupt()will return true.Next, we'll call
exception_enter()which won't do anything since:void context_tracking_user_exit(void) { unsigned long flags; if (!context_tracking_is_enabled()) return; if (in_interrupt()) <=== This returns true, so nothing else gets done return;
Quoted 3 lines
At this stage we never tell RCU that we exited user mode, but then we try to use it calling the notifiers, which explains the warnings I'm seeing.
Is fixing this as simple as calling exception_enter before incrementing the preempt count? I'll try to have a tested patch tomorrow.
Thanks for tracking this down! I've been out of town since you reported this, so I haven't had enough time to track it down myself.
--Andy
Re: [PATCH v4 2/5] x86, traps: Track entry into and exit from IST context
Quoted 64 lines · Andy Lutomirski
Quoted 63 lines · Sasha Levin
Quoted 61 lines · Sasha Levin
Quoted 47 lines · Andy Lutomirski
Quoted 45 lines · Paul E. McKenney
Quoted 43 lines · Andy Lutomirski
Quoted 29 lines · Andy Lutomirski
Quoted 28 lines · Sasha Levin
Quoted 27 lines · Andy Lutomirski
Quoted 15 lines · Borislav Petkov
Quoted 14 lines · Andy Lutomirski
Quoted 13 lines
[ 543.999079] Call Trace: [ 543.999079] dump_stack (lib/dump_stack.c:52) [ 543.999079] lockdep_rcu_suspicious (kernel/locking/lockdep.c:4259) [ 543.999079] atomic_notifier_call_chain (include/linux/rcupdate.h:892 kernel/notifier.c:182 kernel/notifier.c:193) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:192) [ 543.999079] notify_die (kernel/notifier.c:538) [ 543.999079] ? atomic_notifier_call_chain (kernel/notifier.c:538) [ 543.999079] ? debug_smp_processor_id (lib/smp_processor_id.c:57) [ 543.999079] do_debug (arch/x86/kernel/traps.c:652) [ 543.999079] ? trace_hardirqs_on (kernel/locking/lockdep.c:2609) [ 543.999079] ? do_int3 (arch/x86/kernel/traps.c:610) [ 543.999079] ? trace_hardirqs_on_caller (kernel/locking/lockdep.c:2554 kernel/locking/lockdep.c:2601) [ 543.999079] debug (arch/x86/kernel/entry_64.S:1310)Quoted 6 lines
Quoted 2 lines
I don't know how to read this stack trace. Are we in
do_int3,do_debug, or both? I didn't changedo_debugat all.It looks like we're in
do_debug.do_int3is only on the stack but not part of the current frame if I can trust the '?' ...It's possible that an int3 happened and I did something wrong on return that caused a subsequent
do_debugto screw up, but I don't see how my patch would have caused that.Quoted 7 lines
Quoted 3 lines
Quoted 1 line
Were there any earlier log messages?
Nope, nothing odd before or after.
Trinity just survived for a decent amount of time for me with my patches, other than a bunch of apparently expected OOM kills. I have no idea how to tell trinity how much memory to use.
A longer trinity run on a larger VM survived (still with some OOM kills, but no taint) with these patches. I suspect that it's a regression somewhere else in the RCU changes. I have
CONFIG_PROVE_RCU=y, so I should have seen the failure if it was there, I think.Quoted 6 lines
Quoted 3 lines
If by "RCU changes" you mean my changes to the RCU infrastructure, I am going to need more of a hint than I see in this thread thus far. ;-)
I can't help much, since I can't reproduce the problem. Presumably if it's a bug in -tip, someone else will trigger it, too.
I'm not sure what to tell you here, I'm not using any weird options for trinity to reproduce it.
It doesn't happen to frequently, but I still see it happening.
Would you like me to try a debug patch or something similar?
Quoted 7 lines
Quoted 4 lines
After talking with Paul we know what's going on here:
do_debug()callsist_enter()to indicate we're running on the interrupt stack. The first thinkist_enter()does is:I wonder whether there's an easy way to trigger this. Probably a watchpoint on the user stack would do the trick.
This is embarrassing. I just stuck an assertion in do_int3 and I can reproduce it with int3 from user space. Patch coming.
Quoted 31 lines
Quoted 19 lines
preempt_count_add(HARDIRQ_OFFSET);After this, as far as the kernel is concerned, we're in interrupt mode so
in_interrupt()will return true.Next, we'll call
exception_enter()which won't do anything since:void context_tracking_user_exit(void) { unsigned long flags; if (!context_tracking_is_enabled()) return; if (in_interrupt()) <=== This returns true, so nothing else gets done return;Quoted 3 lines
At this stage we never tell RCU that we exited user mode, but then we try to use it calling the notifiers, which explains the warnings I'm seeing.
Is fixing this as simple as calling
exception_enterbefore incrementing the preempt count? I'll try to have a tested patch tomorrow.Thanks for tracking this down! I've been out of town since you reported this, so I haven't had enough time to track it down myself.
--Andy
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
PATCH 1/1 x86, traps: Fix ist_enter from userspace
In reply to Sasha Levincontext_tracking_user_exit() has no effect if in_interrupt() returns true, so ist_enter() didn't work. Fix it by calling exception_enter(), and thus context_tracking_user_exit(), before incrementing the preempt count.
This also adds an assertion that will catch the problem reliably if CONFIG_PROVE_RCU=y to help prevent the bug from being reintroduced.
--- arch/x86/kernel/traps.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-)
arch/x86/kernel/traps.c +18 -7
@@ -110,15 +110,11 @@ static inline void preempt_conditional_cli(struct pt_regs *regs)enum ctx_state ist_enter (struct pt_regs *regs) { -/* -* We are atomic because we're on the IST stack (or we're on x86_32, -* in which case we still shouldn't schedule. -*/ -preempt_count_add (HARDIRQ_OFFSET ); +enum ctx_state prev_state;if (user_mode_vm (regs)) {/* Other than that, we're just an exception. */ -return exception_enter (); + prev_state =exception_enter (); }else {/* * We might have interrupted pretty much anything. In
@@ -127,12 +123,27 @@ enum ctx_state ist_enter(struct pt_regs *regs)* but we need to notify RCU. */ rcu_nmi_enter (); -return IN_KERNEL ;/* the value is irrelevant. */ + prev_state =IN_KERNEL ;/* the value is irrelevant. */ } + +/* +* We are atomic because we're on the IST stack (or we're on x86_32, +* in which case we still shouldn't schedule). +* +* This must be after exception_enter(), because exception_enter() +* won't do anything if in_interrupt() returns true. +*/ +preempt_count_add (HARDIRQ_OFFSET ); + +/* This code is a bit fragile. Test it. */ +rcu_lockdep_assert (rcu_is_watching (),"ist_enter didn't work" ); + +return prev_state; }void ist_exit (struct pt_regs *regs,enum ctx_state prev_state) { +/* Must be before exception_exit. */ preempt_count_sub (HARDIRQ_OFFSET );if (user_mode_vm (regs))
Signature · 1 line
2.1.0
Re: [PATCH] x86, traps: Fix ist_enter from userspace
Quoted 10 lines · Andy Lutomirski
context_tracking_user_exit()has no effect ifin_interrupt()returns true, soist_enter()didn't work. Fix it by callingexception_enter(), and thuscontext_tracking_user_exit(), before incrementing the preempt count.This also adds an assertion that will catch the problem reliably if
CONFIG_PROVE_RCU=yto help prevent the bug from being reintroduced.Fixes 959274753857 x86, traps: Track entry into and exit from IST contextReported-by Sasha Levin <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>
I was running something like that overnight, looks like it fixes the problem.
Thanks,
Sasha
Re: [PATCH] x86, traps: Fix ist_enter from userspace
In reply to Andy LutomirskiQuoted 42 lines · Andy Lutomirski
context_tracking_user_exit()has no effect ifin_interrupt()returns true, soist_enter()didn't work. Fix it by callingexception_enter(), and thuscontext_tracking_user_exit(), before incrementing the preempt count.This also adds an assertion that will catch the problem reliably if
CONFIG_PROVE_RCU=yto help prevent the bug from being reintroduced.Fixes 959274753857 x86, traps: Track entry into and exit from IST contextReported-by Sasha Levin <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/kernel/traps.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-)arch/x86/kernel/traps.c +3
-7@@ -110,15 +110,11 @@ static inline void preempt_conditional_cli(struct pt_regs *regs)enum ctx_state ist_enter (struct pt_regs *regs) { -/* -* We are atomic because we're on the IST stack (or we're on x86_32, -* in which case we still shouldn't schedule. -*/ -preempt_count_add (HARDIRQ_OFFSET ); +enum ctx_state prev_state;if (user_mode_vm (regs)) {/* Other than that, we're just an exception. */ -return exception_enter (); + prev_state =exception_enter (); }else {/* * We might have interrupted pretty much anything. In @@ -127,12 +123,27 @@ enum ctx_state ist_enter(struct pt_regs *regs)* but we need to notify RCU. */ rcu_nmi_enter (); -return IN_KERNEL ;/* the value is irrelevant. */ + prev_state =IN_KERNEL ;/* the value is irrelevant. */
arch/x86/kernel/traps.c enum ctx_state ist_enter(struct pt_regs *regs) · line 136
Quoted 10 lines
arch/x86/kernel/traps.c +9
-0} + +/* +* We are atomic because we're on the IST stack (or we're on x86_32, +* in which case we still shouldn't schedule). +* +* This must be after exception_enter(), because exception_enter() +* won't do anything if in_interrupt() returns true. +*/ +preempt_count_add (HARDIRQ_OFFSET );
So the reason we need this here is that ist_exit() does not have access to the bits tested by user_mode_vm()? Or is the idea to avoid an additional test in ist_exit()? (Wondering why this isn't in the above "else" clause.)
Thanx, Paul
Quoted 16 lines
arch/x86/kernel/traps.c +6
-0+ +/* This code is a bit fragile. Test it. */ +rcu_lockdep_assert (rcu_is_watching (),"ist_enter didn't work" ); + +return prev_state; }void ist_exit (struct pt_regs *regs,enum ctx_state prev_state) { +/* Must be before exception_exit. */ preempt_count_sub (HARDIRQ_OFFSET );if (user_mode_vm (regs))Signature · 1 line
2.1.0
Re: [PATCH] x86, traps: Fix ist_enter from userspace
Quoted 51 lines · Paul E. McKenney
Quoted 49 lines · Andy Lutomirski
context_tracking_user_exit()has no effect ifin_interrupt()returns true, soist_enter()didn't work. Fix it by callingexception_enter(), and thuscontext_tracking_user_exit(), before incrementing the preempt count.This also adds an assertion that will catch the problem reliably if
CONFIG_PROVE_RCU=yto help prevent the bug from being reintroduced.Fixes 959274753857 x86, traps: Track entry into and exit from IST contextReported-by Sasha Levin <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/kernel/traps.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-)arch/x86/kernel/traps.c +9
-7@@ -110,15 +110,11 @@ static inline void preempt_conditional_cli(struct pt_regs *regs)enum ctx_state ist_enter (struct pt_regs *regs) { -/* -* We are atomic because we're on the IST stack (or we're on x86_32, -* in which case we still shouldn't schedule. -*/ -preempt_count_add (HARDIRQ_OFFSET ); +enum ctx_state prev_state;if (user_mode_vm (regs)) {/* Other than that, we're just an exception. */ -return exception_enter (); + prev_state =exception_enter (); }else {/* * We might have interrupted pretty much anything. In @@ -127,12 +123,27 @@ enum ctx_state ist_enter(struct pt_regs *regs)* but we need to notify RCU. */ rcu_nmi_enter (); -return IN_KERNEL ;/* the value is irrelevant. */ + prev_state =IN_KERNEL ;/* the value is irrelevant. */ } + +/* +* We are atomic because we're on the IST stack (or we're on x86_32, +* in which case we still shouldn't schedule). +* +* This must be after exception_enter(), because exception_enter()
Quoted 8 lines
arch/x86/kernel/traps.c enum ctx_state ist_enter(struct pt_regs *regs) · line 136
Quoted 3 lines
arch/x86/kernel/traps.c +3
-0+* won't do anything if in_interrupt() returns true. +*/ +preempt_count_add (HARDIRQ_OFFSET );So the reason we need this here is that
ist_exit()does not have access to the bits tested byuser_mode_vm()? Or is the idea to avoid an additional test inist_exit()? (Wondering why this isn't in the above "else" clause.)
It's result of Linus's suggestion here:
https://lkml.org/lkml/2014/11/19/1131
The idea is that scheduling in IST context is generally bad, and, before this change, we wouldn't have warned. By bumping the preempt count unconditionally, we make it clear that we can't schedule.
For the one exceptional special case, we have ist_begin_non_atomic.
--Andy
tip-bot for Andy Lutomirski
merge bot[tip:x86/asm] x86, traps: Fix ist_enter from userspace
In reply to Andy Lutomirskix86, traps: Fix ist_enter from userspace
Commit-ID: b926e6f61a26036ee9eabe6761483954d481ad25 Gitweb: http://git.kernel.org/tip/b926e6f61a26036ee9eabe6761483954d481ad25 Author: Andy Lutomirski <[email protected]> AuthorDate: Sat, 31 Jan 2015 04:53:53 -0800 Committer: Andy Lutomirski <[email protected]> CommitDate: Sun, 1 Feb 2015 04:02:53 -0800
x86, traps: Fix ist_enter from userspace
context_tracking_user_exit() has no effect if in_interrupt() returns true, so ist_enter() didn't work. Fix it by calling exception_enter(), and thus context_tracking_user_exit(), before incrementing the preempt count.
This also adds an assertion that will catch the problem reliably if CONFIG_PROVE_RCU=y to help prevent the bug from being reintroduced.
--- arch/x86/kernel/traps.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-)
arch/x86/kernel/traps.c +18 -7
@@ -110,15 +110,11 @@ static inline void preempt_conditional_cli(struct pt_regs *regs)enum ctx_state ist_enter (struct pt_regs *regs) { -/* -* We are atomic because we're on the IST stack (or we're on x86_32, -* in which case we still shouldn't schedule. -*/ -preempt_count_add (HARDIRQ_OFFSET ); +enum ctx_state prev_state;if (user_mode_vm (regs)) {/* Other than that, we're just an exception. */ -return exception_enter (); + prev_state =exception_enter (); }else {/* * We might have interrupted pretty much anything. In
@@ -127,12 +123,27 @@ enum ctx_state ist_enter(struct pt_regs *regs)* but we need to notify RCU. */ rcu_nmi_enter (); -return IN_KERNEL ;/* the value is irrelevant. */ + prev_state =IN_KERNEL ;/* the value is irrelevant. */ } + +/* +* We are atomic because we're on the IST stack (or we're on x86_32, +* in which case we still shouldn't schedule). +* +* This must be after exception_enter(), because exception_enter() +* won't do anything if in_interrupt() returns true. +*/ +preempt_count_add (HARDIRQ_OFFSET ); + +/* This code is a bit fragile. Test it. */ +rcu_lockdep_assert (rcu_is_watching (),"ist_enter didn't work" ); + +return prev_state; }void ist_exit (struct pt_regs *regs,enum ctx_state prev_state) { +/* Must be before exception_exit. */ preempt_count_sub (HARDIRQ_OFFSET );if (user_mode_vm (regs))
PATCH v4 3/5 x86, entry: Switch stacks on a paranoid entry from userspace
In reply to Andy LutomirskiThis causes all non-NMI, non-double-fault kernel entries from userspace to run on the normal kernel stack. Double-fault is exempt to minimize confusion if we double-fault directly from userspace due to a bad kernel stack.
This is, suprisingly, simpler and shorter than the current code. It removes the IMO rather frightening paranoid_userspace path, and it make sync_regs much simpler.
There is no risk of stack overflow due to this change -- the kernel stack that we switch to is empty.
This will also enable us to create non-atomic sections within machine checks from userspace, which will simplify memory failure handling. It will also allow the upcoming fsgsbase code to be simplified, because it doesn't need to worry about usergs when scheduling in paranoid_exit, as that code no longer exists.
--- Documentation/x86/entry_64.txt | 18 ++++--- Documentation/x86/x86_64/kernel-stacks | 8 ++-- arch/x86/kernel/entry_64.S | 86 ++++++++++++++++++---------------- arch/x86/kernel/traps.c | 23 ++------- 4 files changed, 67 insertions(+), 68 deletions(-)
Documentation/x86/entry_64.txt +12 -6
@@ -75,9 +75,6 @@ The expensive (paranoid) way is to read back the MSR_GS_BASE value xorl %ebx,%ebx 1: ret -and the whole paranoid non-paranoid macro complexity is about whether -to suffer that RDMSR cost. - If we are at an interrupt or user-trap/gate-alike boundary then we can use the faster check: the stack will be a reliable indicator of whether SWAPGS was already done: if we see that we are a secondary
@@ -90,6 +87,15 @@ which might have triggered right after a normal entry wrote CS to the stack but before we executed SWAPGS, then the only safe way to check for GS is the slower method: the RDMSR. -So we try only to mark those entry methods 'paranoid' that absolutely -need the more expensive check for the GS base - and we generate all -'normal' entry points with the regular (faster) entry macros. +Therefore, super-atomic entries (except NMI, which is handled separately) +must use idtentry with paranoid=1 to handle gsbase correctly. This +triggers three main behavior changes: + + - Interrupt entry will use the slower gsbase check. + - Interrupt entry from user mode will switch off the IST stack. + - Interrupt exit to kernel mode will not attempt to reschedule. + +We try to only use IST entries and the paranoid entry code for vectors +that absolutely need the more expensive check for the GS base - and we +generate all 'normal' entry points with the regular (faster) paranoid=0 +variant.
Documentation/x86/x86_64/kernel-stacks +5 -3
@@ -40,9 +40,11 @@ An IST is selected by a non-zero value in the IST field of an interrupt-gate descriptor. When an interrupt occurs and the hardware loads such a descriptor, the hardware automatically sets the new stack pointer based on the IST value, then invokes the interrupt handler. If -software wants to allow nested IST interrupts then the handler must -adjust the IST values on entry to and exit from the interrupt handler. -(This is occasionally done, e.g. for debug exceptions.) +the interrupt came from user mode, then the interrupt handler prologue +will switch back to the per-thread stack. If software wants to allow +nested IST interrupts then the handler must adjust the IST values on +entry to and exit from the interrupt handler. (This is occasionally +done, e.g. for debug exceptions.) Events with different IST codes (i.e. with different stacks) can be nested. For example, a debug interrupt can safely be interrupted by an
arch/x86/kernel/entry_64.S +45 -41
123 lines not on this page · raw message
arch/x86/kernel/traps.c +5 -18
@@ -408,27 +408,14 @@ NOKPROBE_SYMBOL(do_int3);#ifdef CONFIG_X86_64 /* -* Help handler running on IST stack to switch back to user stack -* for scheduling or signal handling. The actual stack switch is done in -* entry.S +* Help handler running on IST stack to switch off the IST stack if the +* interrupted code was in user mode. The actual stack switch is done in +* entry_64.S */ asmlinkage __visible struct pt_regs *sync_regs(struct pt_regs *eregs) { -struct pt_regs *regs = eregs; -/* Did already sync */ -if (eregs == (struct pt_regs *)eregs->sp) - ; -/* Exception from user space */ -else if (user_mode (eregs)) - regs =task_pt_regs (current); -/* -* Exception from kernel and interrupts are enabled. Move to -* kernel process stack. -*/ -else if (eregs->flags &X86_EFLAGS_IF ) - regs = (struct pt_regs *)(eregs->sp -=sizeof (struct pt_regs )); -if (eregs != regs) - *regs = *eregs; +struct pt_regs *regs =task_pt_regs (current); + *regs = *eregs;return regs; }NOKPROBE_SYMBOL (sync_regs);
Signature · 1 line
1.9.3
Re: [PATCH v4 3/5] x86, entry: Switch stacks on a paranoid entry from userspace
Quoted 18 lines · Andy Lutomirski
This causes all non-NMI, non-double-fault kernel entries from userspace to run on the normal kernel stack. Double-fault is exempt to minimize confusion if we double-fault directly from userspace due to a bad kernel stack.
This is, suprisingly, simpler and shorter than the current code. It removes the IMO rather frightening
paranoid_userspacepath, and it makesync_regsmuch simpler.There is no risk of stack overflow due to this change -- the kernel stack that we switch to is empty.
This will also enable us to create non-atomic sections within machine checks from userspace, which will simplify memory failure handling. It will also allow the upcoming fsgsbase code to be simplified, because it doesn't need to worry about usergs when scheduling in
paranoid_exit, as that code no longer exists.
Quoted 5 lines
Cc Oleg Nesterov <[email protected]>Cc Andi Kleen <[email protected]>Cc Tony Luck <[email protected]>Cc Borislav Petkov <[email protected]>Signed-off-by Andy Lutomirski <[email protected]>
Signature · 5 lines
Regards/Gruss,
Boris.
Sent from a fat crate under my desk. Formatting is fine.
--PATCH v4 4/5 x86: Clean up current_stack_pointer
In reply to Andy LutomirskiThere's no good reason for it to be a macro, and x86_64 will want to use it, so it should be in a header.
--- arch/x86/include/asm/thread_info.h | 11 +++++++++++ arch/x86/kernel/irq_32.c | 13 +++---------- 2 files changed, 14 insertions(+), 10 deletions(-)
arch/x86/include/asm/thread_info.h +11 -0
@@ -170,6 +170,17 @@ static inline struct thread_info *current_thread_info(void) return ti; } +static inline unsigned long current_stack_pointer (void ) +{ +unsigned long sp; +#ifdef CONFIG_X86_64 + asm("mov %%rsp,%0" :"=g" (sp)); +#else + asm("mov %%esp,%0" :"=g" (sp)); +#endif +return sp; +} +#else /* !__ASSEMBLY__ */ /* how to get the thread information struct from ASM */
arch/x86/kernel/irq_32.c +3 -10
@@ -69,16 +69,9 @@ static void call_on_stack(void *func, void *stack) : "memory", "cc", "edx", "ecx", "eax"); } -/* how to get the current stack pointer from C */ -#define current_stack_pointer ({ \ - unsigned long sp; \ - asm("mov %%esp,%0" : "=g" (sp)); \ - sp; \ -}) -static inline void *current_stack (void ) { -return (void *)(current_stack_pointer & ~(THREAD_SIZE -1 )); +return (void *)(current_stack_pointer () & ~(THREAD_SIZE -1 )); } static inline int
@@ -103,7 +96,7 @@ execute_on_irq_stack(int overflow, struct irq_desc *desc, int irq)/* Save the next esp at the bottom of the stack */ prev_esp = (u32 *)irqstk; - *prev_esp = current_stack_pointer; + *prev_esp =current_stack_pointer ();if (unlikely (overflow))call_on_stack (print_stack_overflow, isp);
@@ -156,7 +149,7 @@ void do_softirq_own_stack(void)/* Push the previous esp onto the stack */ prev_esp = (u32 *)irqstk; - *prev_esp = current_stack_pointer; + *prev_esp =current_stack_pointer ();call_on_stack (__do_softirq, isp); }
Signature · 1 line
1.9.3
Re: [PATCH v4 4/5] x86: Clean up current_stack_pointer
Quoted 4 lines · Andy Lutomirski
There's no good reason for it to be a macro, and
x86_64will want to use it, so it should be in a header.Signed-off-by Andy Lutomirski <[email protected]>
Signature · 5 lines
Regards/Gruss,
Boris.
Sent from a fat crate under my desk. Formatting is fine.
--PATCH v4 5/5 x86, traps: Add ist_begin_non_atomic and ist_end_non_atomic
In reply to Andy LutomirskiIn some IST handlers, if the interrupt came from user mode, we can safely enable preemption. Add helpers to do it safely.
This is intended to be used my the memory failure code in do_machine_check.
--- arch/x86/include/asm/traps.h | 2 ++ arch/x86/kernel/traps.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+)
arch/x86/include/asm/traps.h +2 -0
@@ -112,6 +112,8 @@ asmlinkage void mce_threshold_interrupt(void);extern enum ctx_state ist_enter (struct pt_regs *regs);extern void ist_exit (struct pt_regs *regs,enum ctx_state prev_state); +extern void ist_begin_non_atomic (struct pt_regs *regs); +extern void ist_end_non_atomic (void );/* Interrupts/Exceptions */ enum {
arch/x86/kernel/traps.c +38 -0
@@ -140,6 +140,44 @@ void ist_exit(struct pt_regs *regs, enum ctx_state prev_state)rcu_nmi_exit (); } +/** +* ist_begin_non_atomic() - begin a non-atomic section in an IST exception +* @regs: regs passed to the IST exception handler +* +* IST exception handlers normally cannot schedule. As a special +* exception, if the exception interrupted userspace code (i.e. +* user_mode_vm(regs) would return true) and the exception was not +* a double fault, it can be safe to schedule. ist_begin_non_atomic() +* begins a non-atomic section within an ist_enter()/ist_exit() region. +* Callers are responsible for enabling interrupts themselves inside +* the non-atomic section, and callers must call is_end_non_atomic() +* before ist_exit(). +*/ +void ist_begin_non_atomic (struct pt_regs *regs) +{ +BUG_ON (!user_mode_vm (regs)); + +/* +* Sanity check: we need to be on the normal thread stack. This +* will catch asm bugs and any attempt to use ist_preempt_enable +* from double_fault. +*/ +BUG_ON (((current_stack_pointer () ^this_cpu_read_stable (kernel_stack)) + & ~(THREAD_SIZE -1 )) !=0 ); + +preempt_count_sub (HARDIRQ_OFFSET ); +} + +/** +* ist_end_non_atomic() - begin a non-atomic section in an IST exception +* +* Ends a non-atomic section started with ist_begin_non_atomic(). +*/ +void ist_end_non_atomic (void ) +{ +preempt_count_add (HARDIRQ_OFFSET ); +} + static nokprobe_inline intdo_trap_no_signal (struct task_struct *tsk,int trapnr,char *str,struct pt_regs *regs,long error_code)
Signature · 1 line
1.9.3
Re: [PATCH v4 5/5] x86, traps: Add ist_begin_non_atomic and ist_end_non_atomic
Quoted 36 lines · Andy Lutomirski
In some IST handlers, if the interrupt came from user mode, we can safely enable preemption. Add helpers to do it safely.
This is intended to be used my the memory failure code in
do_machine_check.Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/include/asm/traps.h | 2 ++ arch/x86/kernel/traps.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+)arch/x86/include/asm/traps.h +2
-0@@ -112,6 +112,8 @@ asmlinkage void mce_threshold_interrupt(void);extern enum ctx_state ist_enter (struct pt_regs *regs);extern void ist_exit (struct pt_regs *regs,enum ctx_state prev_state); +extern void ist_begin_non_atomic (struct pt_regs *regs); +extern void ist_end_non_atomic (void );/* Interrupts/Exceptions */ enum {arch/x86/kernel/traps.c +3
-0@@ -140,6 +140,44 @@ void ist_exit(struct pt_regs *regs, enum ctx_state prev_state)rcu_nmi_exit (); } +/** +* ist_begin_non_atomic() - begin a non-atomic section in an IST exception +* @regs: regs passed to the IST exception handler
arch/x86/kernel/traps.c void ist_exit(struct pt_regs *regs, enum ctx_state prev_state) · line 155
Quoted 10 lines
arch/x86/kernel/traps.c +10
-0+* +* IST exception handlers normally cannot schedule. As a special +* exception, if the exception interrupted userspace code (i.e. +* user_mode_vm(regs) would return true) and the exception was not +* a double fault, it can be safe to schedule. ist_begin_non_atomic() +* begins a non-atomic section within an ist_enter()/ist_exit() region. +* Callers are responsible for enabling interrupts themselves inside +* the non-atomic section, and callers must call is_end_non_atomic() +* before ist_exit(). +*/
Ok, I guess this is is fine, albeit a bit circumstantial:
I need to do
ist_enter() ist_begin_non_atomic()
In here, I still need to do
if (user_mode_vm()) {
# do non-atomic stuff
}AFAICT, right?
ist_end_non_atomic() ist_exit()
and this whole fun for the context tracking exception_enter/_exit() calls.
Signature · 5 lines
Regards/Gruss,
Boris.
Sent from a fat crate under my desk. Formatting is fine.
--Re: [PATCH v4 5/5] x86, traps: Add ist_begin_non_atomic and ist_end_non_atomic
Quoted 61 lines · Borislav Petkov
Quoted 36 lines · Andy Lutomirski
In some IST handlers, if the interrupt came from user mode, we can safely enable preemption. Add helpers to do it safely.
This is intended to be used my the memory failure code in
do_machine_check.Signed-off-by Andy Lutomirski <[email protected]>--- arch/x86/include/asm/traps.h | 2 ++ arch/x86/kernel/traps.c | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+)arch/x86/include/asm/traps.h +2
-0@@ -112,6 +112,8 @@ asmlinkage void mce_threshold_interrupt(void);extern enum ctx_state ist_enter (struct pt_regs *regs);extern void ist_exit (struct pt_regs *regs,enum ctx_state prev_state); +extern void ist_begin_non_atomic (struct pt_regs *regs); +extern void ist_end_non_atomic (void );/* Interrupts/Exceptions */ enum {arch/x86/kernel/traps.c +3
-0@@ -140,6 +140,44 @@ void ist_exit(struct pt_regs *regs, enum ctx_state prev_state)rcu_nmi_exit (); } +/** +* ist_begin_non_atomic() - begin a non-atomic section in an IST exception +* @regs: regs passed to the IST exception handler arch/x86/kernel/traps.c void ist_exit(struct pt_regs *regs, enum ctx_state prev_state) · line 155
Quoted 10 lines
arch/x86/kernel/traps.c +10
-0+* +* IST exception handlers normally cannot schedule. As a special +* exception, if the exception interrupted userspace code (i.e. +* user_mode_vm(regs) would return true) and the exception was not +* a double fault, it can be safe to schedule. ist_begin_non_atomic() +* begins a non-atomic section within an ist_enter()/ist_exit() region. +* Callers are responsible for enabling interrupts themselves inside +* the non-atomic section, and callers must call is_end_non_atomic() +* before ist_exit(). +*/ Ok, I guess this is is fine, albeit a bit circumstantial:
I need to do
ist_enter() ist_begin_non_atomic()In here, I still need to do
if (user_mode_vm()) { # do non-atomic stuff }
Quoted 7 lines
AFAICT, right?
ist_end_non_atomic() ist_exit()and this whole fun for the context tracking exception_enter/_exit() calls.
Not quite. This fun stuff is to satisfy Linus' request to make the atomicness of the IST entries unconditional instead of depending on the weird user_space_vm thing and to make the escape to non-atomicness be clearly a special case.
So your code above should OOPS instead of working. The code should be:
ist_enter()
if (user_mode_vm()) {
ist_begin_non_atomic();
local_irq_enable();
# do non-atomic stuff local_irq_disable(); ist_end_non_atomic(); }
ist_exit().
Although that user_mode_vm() check might be subsumed by the mce_severity stuff.
NB: This series is going to look a bit different once I rebase it. This part is the same, but Linus merged some patches that change some of the same code over the weekend.
--Andy
Quoted 9 lines
Acked-by Borislav Petkov <[email protected]>--
Regards/Gruss,
Boris.Sent from a fat crate under my desk. Formatting is fine. --
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC