diff options
author | Alex Shi <alex.shi@linaro.org> | 2017-07-28 03:09:25 -0400 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2017-07-31 07:09:49 -0400 |
commit | 313c8c16ee62b32b8b40c6b00637b401dc19050e (patch) | |
tree | 392a1491c74d888594a793bcec95ea7f893167b5 | |
parent | 16f73eb02d7e1765ccab3d2018e0bd98eb93d973 (diff) |
PM / CPU: replace raw_notifier with atomic_notifier
This patch replaces an rwlock and raw notifier by an atomic notifier
protected by a spin_lock and RCU.
The main reason for this change is due to a 'scheduling while atomic'
bug with RT kernels on ARM/ARM64. On ARM/ARM64, the rwlock
cpu_pm_notifier_lock in cpu_pm_enter/exit() causes a potential
schedule after IRQ disable in the idle call chain:
cpu_startup_entry
cpu_idle_loop
local_irq_disable()
cpuidle_idle_call
call_cpuidle
cpuidle_enter
cpuidle_enter_state
->enter :arm_enter_idle_state
cpu_pm_enter/exit
CPU_PM_CPU_IDLE_ENTER
read_lock(&cpu_pm_notifier_lock); <-- sleep in idle
__rt_spin_lock();
schedule();
The kernel panic is here:
[ 4.609601] BUG: scheduling while atomic: swapper/1/0/0x00000002
[ 4.609608] [<ffff0000086fae70>] arm_enter_idle_state+0x18/0x70
[ 4.609614] Modules linked in:
[ 4.609615] [<ffff0000086f9298>] cpuidle_enter_state+0xf0/0x218
[ 4.609620] [<ffff0000086f93f8>] cpuidle_enter+0x18/0x20
[ 4.609626] Preemption disabled at:
[ 4.609627] [<ffff0000080fa234>] call_cpuidle+0x24/0x40
[ 4.609635] [<ffff000008882fa4>] schedule_preempt_disabled+0x1c/0x28
[ 4.609639] [<ffff0000080fa49c>] cpu_startup_entry+0x154/0x1f8
[ 4.609645] [<ffff00000808e004>] secondary_start_kernel+0x15c/0x1a0
Daniel Lezcano said this notification is needed on arm/arm64 platforms.
Sebastian suggested using atomic_notifier instead of rwlock, which is not
only removing the sleeping in idle, but also improving latency.
Tony Lindgren found a miss use that rcu_read_lock used after rcu_idle_enter
Paul McKenney suggested trying RCU_NONIDLE.
Signed-off-by: Alex Shi <alex.shi@linaro.org>
Tested-by: Tony Lindgren <tony@atomide.com>
Acked-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
[ rjw: Subject & changelog ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
-rw-r--r-- | kernel/cpu_pm.c | 50 |
1 files changed, 13 insertions, 37 deletions
diff --git a/kernel/cpu_pm.c b/kernel/cpu_pm.c index 009cc9a17d95..67b02e138a47 100644 --- a/kernel/cpu_pm.c +++ b/kernel/cpu_pm.c | |||
@@ -22,15 +22,21 @@ | |||
22 | #include <linux/spinlock.h> | 22 | #include <linux/spinlock.h> |
23 | #include <linux/syscore_ops.h> | 23 | #include <linux/syscore_ops.h> |
24 | 24 | ||
25 | static DEFINE_RWLOCK(cpu_pm_notifier_lock); | 25 | static ATOMIC_NOTIFIER_HEAD(cpu_pm_notifier_chain); |
26 | static RAW_NOTIFIER_HEAD(cpu_pm_notifier_chain); | ||
27 | 26 | ||
28 | static int cpu_pm_notify(enum cpu_pm_event event, int nr_to_call, int *nr_calls) | 27 | static int cpu_pm_notify(enum cpu_pm_event event, int nr_to_call, int *nr_calls) |
29 | { | 28 | { |
30 | int ret; | 29 | int ret; |
31 | 30 | ||
32 | ret = __raw_notifier_call_chain(&cpu_pm_notifier_chain, event, NULL, | 31 | /* |
32 | * __atomic_notifier_call_chain has a RCU read critical section, which | ||
33 | * could be disfunctional in cpu idle. Copy RCU_NONIDLE code to let | ||
34 | * RCU know this. | ||
35 | */ | ||
36 | rcu_irq_enter_irqson(); | ||
37 | ret = __atomic_notifier_call_chain(&cpu_pm_notifier_chain, event, NULL, | ||
33 | nr_to_call, nr_calls); | 38 | nr_to_call, nr_calls); |
39 | rcu_irq_exit_irqson(); | ||
34 | 40 | ||
35 | return notifier_to_errno(ret); | 41 | return notifier_to_errno(ret); |
36 | } | 42 | } |
@@ -47,14 +53,7 @@ static int cpu_pm_notify(enum cpu_pm_event event, int nr_to_call, int *nr_calls) | |||
47 | */ | 53 | */ |
48 | int cpu_pm_register_notifier(struct notifier_block *nb) | 54 | int cpu_pm_register_notifier(struct notifier_block *nb) |
49 | { | 55 | { |
50 | unsigned long flags; | 56 | return atomic_notifier_chain_register(&cpu_pm_notifier_chain, nb); |
51 | int ret; | ||
52 | |||
53 | write_lock_irqsave(&cpu_pm_notifier_lock, flags); | ||
54 | ret = raw_notifier_chain_register(&cpu_pm_notifier_chain, nb); | ||
55 | write_unlock_irqrestore(&cpu_pm_notifier_lock, flags); | ||
56 | |||
57 | return ret; | ||
58 | } | 57 | } |
59 | EXPORT_SYMBOL_GPL(cpu_pm_register_notifier); | 58 | EXPORT_SYMBOL_GPL(cpu_pm_register_notifier); |
60 | 59 | ||
@@ -69,14 +68,7 @@ EXPORT_SYMBOL_GPL(cpu_pm_register_notifier); | |||
69 | */ | 68 | */ |
70 | int cpu_pm_unregister_notifier(struct notifier_block *nb) | 69 | int cpu_pm_unregister_notifier(struct notifier_block *nb) |
71 | { | 70 | { |
72 | unsigned long flags; | 71 | return atomic_notifier_chain_unregister(&cpu_pm_notifier_chain, nb); |
73 | int ret; | ||
74 | |||
75 | write_lock_irqsave(&cpu_pm_notifier_lock, flags); | ||
76 | ret = raw_notifier_chain_unregister(&cpu_pm_notifier_chain, nb); | ||
77 | write_unlock_irqrestore(&cpu_pm_notifier_lock, flags); | ||
78 | |||
79 | return ret; | ||
80 | } | 72 | } |
81 | EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier); | 73 | EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier); |
82 | 74 | ||
@@ -100,7 +92,6 @@ int cpu_pm_enter(void) | |||
100 | int nr_calls; | 92 | int nr_calls; |
101 | int ret = 0; | 93 | int ret = 0; |
102 | 94 | ||
103 | read_lock(&cpu_pm_notifier_lock); | ||
104 | ret = cpu_pm_notify(CPU_PM_ENTER, -1, &nr_calls); | 95 | ret = cpu_pm_notify(CPU_PM_ENTER, -1, &nr_calls); |
105 | if (ret) | 96 | if (ret) |
106 | /* | 97 | /* |
@@ -108,7 +99,6 @@ int cpu_pm_enter(void) | |||
108 | * PM entry who are notified earlier to prepare for it. | 99 | * PM entry who are notified earlier to prepare for it. |
109 | */ | 100 | */ |
110 | cpu_pm_notify(CPU_PM_ENTER_FAILED, nr_calls - 1, NULL); | 101 | cpu_pm_notify(CPU_PM_ENTER_FAILED, nr_calls - 1, NULL); |
111 | read_unlock(&cpu_pm_notifier_lock); | ||
112 | 102 | ||
113 | return ret; | 103 | return ret; |
114 | } | 104 | } |
@@ -128,13 +118,7 @@ EXPORT_SYMBOL_GPL(cpu_pm_enter); | |||
128 | */ | 118 | */ |
129 | int cpu_pm_exit(void) | 119 | int cpu_pm_exit(void) |
130 | { | 120 | { |
131 | int ret; | 121 | return cpu_pm_notify(CPU_PM_EXIT, -1, NULL); |
132 | |||
133 | read_lock(&cpu_pm_notifier_lock); | ||
134 | ret = cpu_pm_notify(CPU_PM_EXIT, -1, NULL); | ||
135 | read_unlock(&cpu_pm_notifier_lock); | ||
136 | |||
137 | return ret; | ||
138 | } | 122 | } |
139 | EXPORT_SYMBOL_GPL(cpu_pm_exit); | 123 | EXPORT_SYMBOL_GPL(cpu_pm_exit); |
140 | 124 | ||
@@ -159,7 +143,6 @@ int cpu_cluster_pm_enter(void) | |||
159 | int nr_calls; | 143 | int nr_calls; |
160 | int ret = 0; | 144 | int ret = 0; |
161 | 145 | ||
162 | read_lock(&cpu_pm_notifier_lock); | ||
163 | ret = cpu_pm_notify(CPU_CLUSTER_PM_ENTER, -1, &nr_calls); | 146 | ret = cpu_pm_notify(CPU_CLUSTER_PM_ENTER, -1, &nr_calls); |
164 | if (ret) | 147 | if (ret) |
165 | /* | 148 | /* |
@@ -167,7 +150,6 @@ int cpu_cluster_pm_enter(void) | |||
167 | * PM entry who are notified earlier to prepare for it. | 150 | * PM entry who are notified earlier to prepare for it. |
168 | */ | 151 | */ |
169 | cpu_pm_notify(CPU_CLUSTER_PM_ENTER_FAILED, nr_calls - 1, NULL); | 152 | cpu_pm_notify(CPU_CLUSTER_PM_ENTER_FAILED, nr_calls - 1, NULL); |
170 | read_unlock(&cpu_pm_notifier_lock); | ||
171 | 153 | ||
172 | return ret; | 154 | return ret; |
173 | } | 155 | } |
@@ -190,13 +172,7 @@ EXPORT_SYMBOL_GPL(cpu_cluster_pm_enter); | |||
190 | */ | 172 | */ |
191 | int cpu_cluster_pm_exit(void) | 173 | int cpu_cluster_pm_exit(void) |
192 | { | 174 | { |
193 | int ret; | 175 | return cpu_pm_notify(CPU_CLUSTER_PM_EXIT, -1, NULL); |
194 | |||
195 | read_lock(&cpu_pm_notifier_lock); | ||
196 | ret = cpu_pm_notify(CPU_CLUSTER_PM_EXIT, -1, NULL); | ||
197 | read_unlock(&cpu_pm_notifier_lock); | ||
198 | |||
199 | return ret; | ||
200 | } | 176 | } |
201 | EXPORT_SYMBOL_GPL(cpu_cluster_pm_exit); | 177 | EXPORT_SYMBOL_GPL(cpu_cluster_pm_exit); |
202 | 178 | ||