Skip to content

Commit 313c8c1

Browse files
Alex Shirafaeljw
authored andcommitted
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 <[email protected]> Tested-by: Tony Lindgren <[email protected]> Acked-by: Sebastian Andrzej Siewior <[email protected]> [ rjw: Subject & changelog ] Signed-off-by: Rafael J. Wysocki <[email protected]>
1 parent 16f73eb commit 313c8c1

File tree

1 file changed

+13
-37
lines changed

1 file changed

+13
-37
lines changed

kernel/cpu_pm.c

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,21 @@
2222
#include <linux/spinlock.h>
2323
#include <linux/syscore_ops.h>
2424

25-
static DEFINE_RWLOCK(cpu_pm_notifier_lock);
26-
static RAW_NOTIFIER_HEAD(cpu_pm_notifier_chain);
25+
static ATOMIC_NOTIFIER_HEAD(cpu_pm_notifier_chain);
2726

2827
static int cpu_pm_notify(enum cpu_pm_event event, int nr_to_call, int *nr_calls)
2928
{
3029
int ret;
3130

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,
3338
nr_to_call, nr_calls);
39+
rcu_irq_exit_irqson();
3440

3541
return notifier_to_errno(ret);
3642
}
@@ -47,14 +53,7 @@ static int cpu_pm_notify(enum cpu_pm_event event, int nr_to_call, int *nr_calls)
4753
*/
4854
int cpu_pm_register_notifier(struct notifier_block *nb)
4955
{
50-
unsigned long flags;
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;
56+
return atomic_notifier_chain_register(&cpu_pm_notifier_chain, nb);
5857
}
5958
EXPORT_SYMBOL_GPL(cpu_pm_register_notifier);
6059

@@ -69,14 +68,7 @@ EXPORT_SYMBOL_GPL(cpu_pm_register_notifier);
6968
*/
7069
int cpu_pm_unregister_notifier(struct notifier_block *nb)
7170
{
72-
unsigned long flags;
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;
71+
return atomic_notifier_chain_unregister(&cpu_pm_notifier_chain, nb);
8072
}
8173
EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier);
8274

@@ -100,15 +92,13 @@ int cpu_pm_enter(void)
10092
int nr_calls;
10193
int ret = 0;
10294

103-
read_lock(&cpu_pm_notifier_lock);
10495
ret = cpu_pm_notify(CPU_PM_ENTER, -1, &nr_calls);
10596
if (ret)
10697
/*
10798
* Inform listeners (nr_calls - 1) about failure of CPU PM
10899
* PM entry who are notified earlier to prepare for it.
109100
*/
110101
cpu_pm_notify(CPU_PM_ENTER_FAILED, nr_calls - 1, NULL);
111-
read_unlock(&cpu_pm_notifier_lock);
112102

113103
return ret;
114104
}
@@ -128,13 +118,7 @@ EXPORT_SYMBOL_GPL(cpu_pm_enter);
128118
*/
129119
int cpu_pm_exit(void)
130120
{
131-
int ret;
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;
121+
return cpu_pm_notify(CPU_PM_EXIT, -1, NULL);
138122
}
139123
EXPORT_SYMBOL_GPL(cpu_pm_exit);
140124

@@ -159,15 +143,13 @@ int cpu_cluster_pm_enter(void)
159143
int nr_calls;
160144
int ret = 0;
161145

162-
read_lock(&cpu_pm_notifier_lock);
163146
ret = cpu_pm_notify(CPU_CLUSTER_PM_ENTER, -1, &nr_calls);
164147
if (ret)
165148
/*
166149
* Inform listeners (nr_calls - 1) about failure of CPU cluster
167150
* PM entry who are notified earlier to prepare for it.
168151
*/
169152
cpu_pm_notify(CPU_CLUSTER_PM_ENTER_FAILED, nr_calls - 1, NULL);
170-
read_unlock(&cpu_pm_notifier_lock);
171153

172154
return ret;
173155
}
@@ -190,13 +172,7 @@ EXPORT_SYMBOL_GPL(cpu_cluster_pm_enter);
190172
*/
191173
int cpu_cluster_pm_exit(void)
192174
{
193-
int ret;
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;
175+
return cpu_pm_notify(CPU_CLUSTER_PM_EXIT, -1, NULL);
200176
}
201177
EXPORT_SYMBOL_GPL(cpu_cluster_pm_exit);
202178

0 commit comments

Comments
 (0)