1 | // SPDX-License-Identifier: GPL-2.0 |
---|---|
2 | /* |
3 | * kernel/power/autosleep.c |
4 | * |
5 | * Opportunistic sleep support. |
6 | * |
7 | * Copyright (C) 2012 Rafael J. Wysocki <rjw@sisk.pl> |
8 | */ |
9 | |
10 | #include <linux/device.h> |
11 | #include <linux/mutex.h> |
12 | #include <linux/pm_wakeup.h> |
13 | |
14 | #include "power.h" |
15 | |
16 | static suspend_state_t autosleep_state; |
17 | static struct workqueue_struct *autosleep_wq; |
18 | /* |
19 | * Note: it is only safe to mutex_lock(&autosleep_lock) if a wakeup_source |
20 | * is active, otherwise a deadlock with try_to_suspend() is possible. |
21 | * Alternatively mutex_lock_interruptible() can be used. This will then fail |
22 | * if an auto_sleep cycle tries to freeze processes. |
23 | */ |
24 | static DEFINE_MUTEX(autosleep_lock); |
25 | static struct wakeup_source *autosleep_ws; |
26 | |
27 | static void try_to_suspend(struct work_struct *work) |
28 | { |
29 | unsigned int initial_count, final_count; |
30 | |
31 | if (!pm_get_wakeup_count(count: &initial_count, block: true)) |
32 | goto out; |
33 | |
34 | mutex_lock(&autosleep_lock); |
35 | |
36 | if (!pm_save_wakeup_count(count: initial_count) || |
37 | system_state != SYSTEM_RUNNING) { |
38 | mutex_unlock(lock: &autosleep_lock); |
39 | goto out; |
40 | } |
41 | |
42 | if (autosleep_state == PM_SUSPEND_ON) { |
43 | mutex_unlock(lock: &autosleep_lock); |
44 | return; |
45 | } |
46 | if (autosleep_state >= PM_SUSPEND_MAX) |
47 | hibernate(); |
48 | else |
49 | pm_suspend(state: autosleep_state); |
50 | |
51 | mutex_unlock(lock: &autosleep_lock); |
52 | |
53 | if (!pm_get_wakeup_count(count: &final_count, block: false)) |
54 | goto out; |
55 | |
56 | /* |
57 | * If the wakeup occurred for an unknown reason, wait to prevent the |
58 | * system from trying to suspend and waking up in a tight loop. |
59 | */ |
60 | if (final_count == initial_count) |
61 | schedule_timeout_uninterruptible(HZ / 2); |
62 | |
63 | out: |
64 | queue_up_suspend_work(); |
65 | } |
66 | |
67 | static DECLARE_WORK(suspend_work, try_to_suspend); |
68 | |
69 | void queue_up_suspend_work(void) |
70 | { |
71 | if (autosleep_state > PM_SUSPEND_ON) |
72 | queue_work(wq: autosleep_wq, work: &suspend_work); |
73 | } |
74 | |
75 | suspend_state_t pm_autosleep_state(void) |
76 | { |
77 | return autosleep_state; |
78 | } |
79 | |
80 | int pm_autosleep_lock(void) |
81 | { |
82 | return mutex_lock_interruptible(&autosleep_lock); |
83 | } |
84 | |
85 | void pm_autosleep_unlock(void) |
86 | { |
87 | mutex_unlock(lock: &autosleep_lock); |
88 | } |
89 | |
90 | int pm_autosleep_set_state(suspend_state_t state) |
91 | { |
92 | |
93 | #ifndef CONFIG_HIBERNATION |
94 | if (state >= PM_SUSPEND_MAX) |
95 | return -EINVAL; |
96 | #endif |
97 | |
98 | __pm_stay_awake(ws: autosleep_ws); |
99 | |
100 | mutex_lock(&autosleep_lock); |
101 | |
102 | autosleep_state = state; |
103 | |
104 | __pm_relax(ws: autosleep_ws); |
105 | |
106 | if (state > PM_SUSPEND_ON) { |
107 | pm_wakep_autosleep_enabled(set: true); |
108 | queue_up_suspend_work(); |
109 | } else { |
110 | pm_wakep_autosleep_enabled(set: false); |
111 | } |
112 | |
113 | mutex_unlock(lock: &autosleep_lock); |
114 | return 0; |
115 | } |
116 | |
117 | int __init pm_autosleep_init(void) |
118 | { |
119 | autosleep_ws = wakeup_source_register(NULL, name: "autosleep"); |
120 | if (!autosleep_ws) |
121 | return -ENOMEM; |
122 | |
123 | autosleep_wq = alloc_ordered_workqueue("autosleep", 0); |
124 | if (autosleep_wq) |
125 | return 0; |
126 | |
127 | wakeup_source_unregister(ws: autosleep_ws); |
128 | return -ENOMEM; |
129 | } |
130 |