Skip to content

Commit b2f6662

Browse files
Valentin Schneiderrafaeljw
authored andcommitted
PM: cpu: Make notifier chain use a raw_spinlock_t
Invoking atomic_notifier_chain_notify() requires acquiring a spinlock_t, which can block under CONFIG_PREEMPT_RT. Notifications for members of the cpu_pm notification chain will be issued by the idle task, which can never block. Making *all* atomic_notifiers use a raw_spinlock is too big of a hammer, as only notifications issued by the idle task are problematic. Special-case cpu_pm_notifier_chain by kludging a raw_notifier and raw_spinlock_t together, matching the atomic_notifier behavior with a raw_spinlock_t. Fixes: 70d9329 ("notifier: Fix broken error handling pattern") Signed-off-by: Valentin Schneider <[email protected]> Acked-by: Sebastian Andrzej Siewior <[email protected]> Signed-off-by: Rafael J. Wysocki <[email protected]>
1 parent 7c60610 commit b2f6662

File tree

1 file changed

+38
-12
lines changed

1 file changed

+38
-12
lines changed

kernel/cpu_pm.c

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,46 @@
1313
#include <linux/spinlock.h>
1414
#include <linux/syscore_ops.h>
1515

16-
static ATOMIC_NOTIFIER_HEAD(cpu_pm_notifier_chain);
16+
/*
17+
* atomic_notifiers use a spinlock_t, which can block under PREEMPT_RT.
18+
* Notifications for cpu_pm will be issued by the idle task itself, which can
19+
* never block, IOW it requires using a raw_spinlock_t.
20+
*/
21+
static struct {
22+
struct raw_notifier_head chain;
23+
raw_spinlock_t lock;
24+
} cpu_pm_notifier = {
25+
.chain = RAW_NOTIFIER_INIT(cpu_pm_notifier.chain),
26+
.lock = __RAW_SPIN_LOCK_UNLOCKED(cpu_pm_notifier.lock),
27+
};
1728

1829
static int cpu_pm_notify(enum cpu_pm_event event)
1930
{
2031
int ret;
2132

2233
/*
23-
* atomic_notifier_call_chain has a RCU read critical section, which
24-
* could be disfunctional in cpu idle. Copy RCU_NONIDLE code to let
25-
* RCU know this.
34+
* This introduces a RCU read critical section, which could be
35+
* disfunctional in cpu idle. Copy RCU_NONIDLE code to let RCU know
36+
* this.
2637
*/
2738
rcu_irq_enter_irqson();
28-
ret = atomic_notifier_call_chain(&cpu_pm_notifier_chain, event, NULL);
39+
rcu_read_lock();
40+
ret = raw_notifier_call_chain(&cpu_pm_notifier.chain, event, NULL);
41+
rcu_read_unlock();
2942
rcu_irq_exit_irqson();
3043

3144
return notifier_to_errno(ret);
3245
}
3346

3447
static int cpu_pm_notify_robust(enum cpu_pm_event event_up, enum cpu_pm_event event_down)
3548
{
49+
unsigned long flags;
3650
int ret;
3751

3852
rcu_irq_enter_irqson();
39-
ret = atomic_notifier_call_chain_robust(&cpu_pm_notifier_chain, event_up, event_down, NULL);
53+
raw_spin_lock_irqsave(&cpu_pm_notifier.lock, flags);
54+
ret = raw_notifier_call_chain_robust(&cpu_pm_notifier.chain, event_up, event_down, NULL);
55+
raw_spin_unlock_irqrestore(&cpu_pm_notifier.lock, flags);
4056
rcu_irq_exit_irqson();
4157

4258
return notifier_to_errno(ret);
@@ -49,12 +65,17 @@ static int cpu_pm_notify_robust(enum cpu_pm_event event_up, enum cpu_pm_event ev
4965
* Add a driver to a list of drivers that are notified about
5066
* CPU and CPU cluster low power entry and exit.
5167
*
52-
* This function may sleep, and has the same return conditions as
53-
* raw_notifier_chain_register.
68+
* This function has the same return conditions as raw_notifier_chain_register.
5469
*/
5570
int cpu_pm_register_notifier(struct notifier_block *nb)
5671
{
57-
return atomic_notifier_chain_register(&cpu_pm_notifier_chain, nb);
72+
unsigned long flags;
73+
int ret;
74+
75+
raw_spin_lock_irqsave(&cpu_pm_notifier.lock, flags);
76+
ret = raw_notifier_chain_register(&cpu_pm_notifier.chain, nb);
77+
raw_spin_unlock_irqrestore(&cpu_pm_notifier.lock, flags);
78+
return ret;
5879
}
5980
EXPORT_SYMBOL_GPL(cpu_pm_register_notifier);
6081

@@ -64,12 +85,17 @@ EXPORT_SYMBOL_GPL(cpu_pm_register_notifier);
6485
*
6586
* Remove a driver from the CPU PM notifier list.
6687
*
67-
* This function may sleep, and has the same return conditions as
68-
* raw_notifier_chain_unregister.
88+
* This function has the same return conditions as raw_notifier_chain_unregister.
6989
*/
7090
int cpu_pm_unregister_notifier(struct notifier_block *nb)
7191
{
72-
return atomic_notifier_chain_unregister(&cpu_pm_notifier_chain, nb);
92+
unsigned long flags;
93+
int ret;
94+
95+
raw_spin_lock_irqsave(&cpu_pm_notifier.lock, flags);
96+
ret = raw_notifier_chain_unregister(&cpu_pm_notifier.chain, nb);
97+
raw_spin_unlock_irqrestore(&cpu_pm_notifier.lock, flags);
98+
return ret;
7399
}
74100
EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier);
75101

0 commit comments

Comments
 (0)