diff options
Diffstat (limited to 'kernel/power/main.c')
-rw-r--r-- | kernel/power/main.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/kernel/power/main.c b/kernel/power/main.c new file mode 100644 index 000000000000..7960ddf04a57 --- /dev/null +++ b/kernel/power/main.c | |||
@@ -0,0 +1,269 @@ | |||
1 | /* | ||
2 | * kernel/power/main.c - PM subsystem core functionality. | ||
3 | * | ||
4 | * Copyright (c) 2003 Patrick Mochel | ||
5 | * Copyright (c) 2003 Open Source Development Lab | ||
6 | * | ||
7 | * This file is released under the GPLv2 | ||
8 | * | ||
9 | */ | ||
10 | |||
11 | #include <linux/suspend.h> | ||
12 | #include <linux/kobject.h> | ||
13 | #include <linux/string.h> | ||
14 | #include <linux/delay.h> | ||
15 | #include <linux/errno.h> | ||
16 | #include <linux/init.h> | ||
17 | #include <linux/pm.h> | ||
18 | |||
19 | |||
20 | #include "power.h" | ||
21 | |||
22 | DECLARE_MUTEX(pm_sem); | ||
23 | |||
24 | struct pm_ops * pm_ops = NULL; | ||
25 | suspend_disk_method_t pm_disk_mode = PM_DISK_SHUTDOWN; | ||
26 | |||
27 | /** | ||
28 | * pm_set_ops - Set the global power method table. | ||
29 | * @ops: Pointer to ops structure. | ||
30 | */ | ||
31 | |||
32 | void pm_set_ops(struct pm_ops * ops) | ||
33 | { | ||
34 | down(&pm_sem); | ||
35 | pm_ops = ops; | ||
36 | up(&pm_sem); | ||
37 | } | ||
38 | |||
39 | |||
40 | /** | ||
41 | * suspend_prepare - Do prep work before entering low-power state. | ||
42 | * @state: State we're entering. | ||
43 | * | ||
44 | * This is common code that is called for each state that we're | ||
45 | * entering. Allocate a console, stop all processes, then make sure | ||
46 | * the platform can enter the requested state. | ||
47 | */ | ||
48 | |||
49 | static int suspend_prepare(suspend_state_t state) | ||
50 | { | ||
51 | int error = 0; | ||
52 | |||
53 | if (!pm_ops || !pm_ops->enter) | ||
54 | return -EPERM; | ||
55 | |||
56 | pm_prepare_console(); | ||
57 | |||
58 | if (freeze_processes()) { | ||
59 | error = -EAGAIN; | ||
60 | goto Thaw; | ||
61 | } | ||
62 | |||
63 | if (pm_ops->prepare) { | ||
64 | if ((error = pm_ops->prepare(state))) | ||
65 | goto Thaw; | ||
66 | } | ||
67 | |||
68 | if ((error = device_suspend(PMSG_SUSPEND))) { | ||
69 | printk(KERN_ERR "Some devices failed to suspend\n"); | ||
70 | goto Finish; | ||
71 | } | ||
72 | return 0; | ||
73 | Finish: | ||
74 | if (pm_ops->finish) | ||
75 | pm_ops->finish(state); | ||
76 | Thaw: | ||
77 | thaw_processes(); | ||
78 | pm_restore_console(); | ||
79 | return error; | ||
80 | } | ||
81 | |||
82 | |||
83 | static int suspend_enter(suspend_state_t state) | ||
84 | { | ||
85 | int error = 0; | ||
86 | unsigned long flags; | ||
87 | |||
88 | local_irq_save(flags); | ||
89 | |||
90 | if ((error = device_power_down(PMSG_SUSPEND))) { | ||
91 | printk(KERN_ERR "Some devices failed to power down\n"); | ||
92 | goto Done; | ||
93 | } | ||
94 | error = pm_ops->enter(state); | ||
95 | device_power_up(); | ||
96 | Done: | ||
97 | local_irq_restore(flags); | ||
98 | return error; | ||
99 | } | ||
100 | |||
101 | |||
102 | /** | ||
103 | * suspend_finish - Do final work before exiting suspend sequence. | ||
104 | * @state: State we're coming out of. | ||
105 | * | ||
106 | * Call platform code to clean up, restart processes, and free the | ||
107 | * console that we've allocated. This is not called for suspend-to-disk. | ||
108 | */ | ||
109 | |||
110 | static void suspend_finish(suspend_state_t state) | ||
111 | { | ||
112 | device_resume(); | ||
113 | if (pm_ops && pm_ops->finish) | ||
114 | pm_ops->finish(state); | ||
115 | thaw_processes(); | ||
116 | pm_restore_console(); | ||
117 | } | ||
118 | |||
119 | |||
120 | |||
121 | |||
122 | static char * pm_states[] = { | ||
123 | [PM_SUSPEND_STANDBY] = "standby", | ||
124 | [PM_SUSPEND_MEM] = "mem", | ||
125 | [PM_SUSPEND_DISK] = "disk", | ||
126 | NULL, | ||
127 | }; | ||
128 | |||
129 | |||
130 | /** | ||
131 | * enter_state - Do common work of entering low-power state. | ||
132 | * @state: pm_state structure for state we're entering. | ||
133 | * | ||
134 | * Make sure we're the only ones trying to enter a sleep state. Fail | ||
135 | * if someone has beat us to it, since we don't want anything weird to | ||
136 | * happen when we wake up. | ||
137 | * Then, do the setup for suspend, enter the state, and cleaup (after | ||
138 | * we've woken up). | ||
139 | */ | ||
140 | |||
141 | static int enter_state(suspend_state_t state) | ||
142 | { | ||
143 | int error; | ||
144 | |||
145 | if (down_trylock(&pm_sem)) | ||
146 | return -EBUSY; | ||
147 | |||
148 | if (state == PM_SUSPEND_DISK) { | ||
149 | error = pm_suspend_disk(); | ||
150 | goto Unlock; | ||
151 | } | ||
152 | |||
153 | /* Suspend is hard to get right on SMP. */ | ||
154 | if (num_online_cpus() != 1) { | ||
155 | error = -EPERM; | ||
156 | goto Unlock; | ||
157 | } | ||
158 | |||
159 | pr_debug("PM: Preparing system for suspend\n"); | ||
160 | if ((error = suspend_prepare(state))) | ||
161 | goto Unlock; | ||
162 | |||
163 | pr_debug("PM: Entering state.\n"); | ||
164 | error = suspend_enter(state); | ||
165 | |||
166 | pr_debug("PM: Finishing up.\n"); | ||
167 | suspend_finish(state); | ||
168 | Unlock: | ||
169 | up(&pm_sem); | ||
170 | return error; | ||
171 | } | ||
172 | |||
173 | /* | ||
174 | * This is main interface to the outside world. It needs to be | ||
175 | * called from process context. | ||
176 | */ | ||
177 | int software_suspend(void) | ||
178 | { | ||
179 | return enter_state(PM_SUSPEND_DISK); | ||
180 | } | ||
181 | |||
182 | |||
183 | /** | ||
184 | * pm_suspend - Externally visible function for suspending system. | ||
185 | * @state: Enumarted value of state to enter. | ||
186 | * | ||
187 | * Determine whether or not value is within range, get state | ||
188 | * structure, and enter (above). | ||
189 | */ | ||
190 | |||
191 | int pm_suspend(suspend_state_t state) | ||
192 | { | ||
193 | if (state > PM_SUSPEND_ON && state < PM_SUSPEND_MAX) | ||
194 | return enter_state(state); | ||
195 | return -EINVAL; | ||
196 | } | ||
197 | |||
198 | |||
199 | |||
200 | decl_subsys(power,NULL,NULL); | ||
201 | |||
202 | |||
203 | /** | ||
204 | * state - control system power state. | ||
205 | * | ||
206 | * show() returns what states are supported, which is hard-coded to | ||
207 | * 'standby' (Power-On Suspend), 'mem' (Suspend-to-RAM), and | ||
208 | * 'disk' (Suspend-to-Disk). | ||
209 | * | ||
210 | * store() accepts one of those strings, translates it into the | ||
211 | * proper enumerated value, and initiates a suspend transition. | ||
212 | */ | ||
213 | |||
214 | static ssize_t state_show(struct subsystem * subsys, char * buf) | ||
215 | { | ||
216 | int i; | ||
217 | char * s = buf; | ||
218 | |||
219 | for (i = 0; i < PM_SUSPEND_MAX; i++) { | ||
220 | if (pm_states[i]) | ||
221 | s += sprintf(s,"%s ",pm_states[i]); | ||
222 | } | ||
223 | s += sprintf(s,"\n"); | ||
224 | return (s - buf); | ||
225 | } | ||
226 | |||
227 | static ssize_t state_store(struct subsystem * subsys, const char * buf, size_t n) | ||
228 | { | ||
229 | suspend_state_t state = PM_SUSPEND_STANDBY; | ||
230 | char ** s; | ||
231 | char *p; | ||
232 | int error; | ||
233 | int len; | ||
234 | |||
235 | p = memchr(buf, '\n', n); | ||
236 | len = p ? p - buf : n; | ||
237 | |||
238 | for (s = &pm_states[state]; state < PM_SUSPEND_MAX; s++, state++) { | ||
239 | if (*s && !strncmp(buf, *s, len)) | ||
240 | break; | ||
241 | } | ||
242 | if (*s) | ||
243 | error = enter_state(state); | ||
244 | else | ||
245 | error = -EINVAL; | ||
246 | return error ? error : n; | ||
247 | } | ||
248 | |||
249 | power_attr(state); | ||
250 | |||
251 | static struct attribute * g[] = { | ||
252 | &state_attr.attr, | ||
253 | NULL, | ||
254 | }; | ||
255 | |||
256 | static struct attribute_group attr_group = { | ||
257 | .attrs = g, | ||
258 | }; | ||
259 | |||
260 | |||
261 | static int __init pm_init(void) | ||
262 | { | ||
263 | int error = subsystem_register(&power_subsys); | ||
264 | if (!error) | ||
265 | error = sysfs_create_group(&power_subsys.kset.kobj,&attr_group); | ||
266 | return error; | ||
267 | } | ||
268 | |||
269 | core_initcall(pm_init); | ||