diff options
author | Steven Rostedt <srostedt@redhat.com> | 2009-01-22 19:01:40 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-01-23 05:10:57 -0500 |
commit | 7e49fcce1bdadd723ae6a0b3b324c4daced61563 (patch) | |
tree | a2bf1a143ed33ca01612dfab1fb7c993c467cdb0 /kernel/softirq.c | |
parent | b06a830183b610c0a88c29a92feb7991a867ab46 (diff) |
trace, lockdep: manual preempt count adding for local_bh_disable
Impact: fix to preempt trace triggering lockdep check_flag failure
In local_bh_disable, the use of add_preempt_count causes the
preempt tracer to start recording the time preemption is off.
But because it already modified the preempt_count to show
softirqs disabled, and before it called the lockdep code to
handle this, it causes a state that lockdep can not handle.
The preempt tracer will reset the ring buffer on start of a trace,
and the ring buffer reset code does a spin_lock_irqsave. This
calls into lockdep and lockdep will fail when it detects the
invalid state of having softirqs disabled but the internal
current->softirqs_enabled is still set.
The fix is to manually add the SOFTIRQ_OFFSET to preempt count
and call the preempt tracer code outside the lockdep critical
area.
Thanks to Peter Zijlstra for suggesting this solution.
Signed-off-by: Steven Rostedt <srostedt@redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'kernel/softirq.c')
-rw-r--r-- | kernel/softirq.c | 13 |
1 files changed, 12 insertions, 1 deletions
diff --git a/kernel/softirq.c b/kernel/softirq.c index bdbe9de9cd8d..6edfc2c11d99 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c | |||
@@ -21,6 +21,7 @@ | |||
21 | #include <linux/freezer.h> | 21 | #include <linux/freezer.h> |
22 | #include <linux/kthread.h> | 22 | #include <linux/kthread.h> |
23 | #include <linux/rcupdate.h> | 23 | #include <linux/rcupdate.h> |
24 | #include <linux/ftrace.h> | ||
24 | #include <linux/smp.h> | 25 | #include <linux/smp.h> |
25 | #include <linux/tick.h> | 26 | #include <linux/tick.h> |
26 | 27 | ||
@@ -79,13 +80,23 @@ static void __local_bh_disable(unsigned long ip) | |||
79 | WARN_ON_ONCE(in_irq()); | 80 | WARN_ON_ONCE(in_irq()); |
80 | 81 | ||
81 | raw_local_irq_save(flags); | 82 | raw_local_irq_save(flags); |
82 | add_preempt_count(SOFTIRQ_OFFSET); | 83 | /* |
84 | * The preempt tracer hooks into add_preempt_count and will break | ||
85 | * lockdep because it calls back into lockdep after SOFTIRQ_OFFSET | ||
86 | * is set and before current->softirq_enabled is cleared. | ||
87 | * We must manually increment preempt_count here and manually | ||
88 | * call the trace_preempt_off later. | ||
89 | */ | ||
90 | preempt_count() += SOFTIRQ_OFFSET; | ||
83 | /* | 91 | /* |
84 | * Were softirqs turned off above: | 92 | * Were softirqs turned off above: |
85 | */ | 93 | */ |
86 | if (softirq_count() == SOFTIRQ_OFFSET) | 94 | if (softirq_count() == SOFTIRQ_OFFSET) |
87 | trace_softirqs_off(ip); | 95 | trace_softirqs_off(ip); |
88 | raw_local_irq_restore(flags); | 96 | raw_local_irq_restore(flags); |
97 | |||
98 | if (preempt_count() == SOFTIRQ_OFFSET) | ||
99 | trace_preempt_off(CALLER_ADDR0, get_parent_ip(CALLER_ADDR1)); | ||
89 | } | 100 | } |
90 | #else /* !CONFIG_TRACE_IRQFLAGS */ | 101 | #else /* !CONFIG_TRACE_IRQFLAGS */ |
91 | static inline void __local_bh_disable(unsigned long ip) | 102 | static inline void __local_bh_disable(unsigned long ip) |