Commit | Line | Data |
---|---|---|
6fa3eb70 S |
1 | /* |
2 | * kernel/power/tuxonice_power_off.c | |
3 | * | |
4 | * Copyright (C) 2006-2010 Nigel Cunningham (nigel at tuxonice net) | |
5 | * | |
6 | * This file is released under the GPLv2. | |
7 | * | |
8 | * Support for powering down. | |
9 | */ | |
10 | ||
11 | #include <linux/device.h> | |
12 | #include <linux/suspend.h> | |
13 | #include <linux/mm.h> | |
14 | #include <linux/pm.h> | |
15 | #include <linux/reboot.h> | |
16 | #include <linux/cpu.h> | |
17 | #include <linux/console.h> | |
18 | #include <linux/fs.h> | |
19 | #include "tuxonice.h" | |
20 | #include "tuxonice_ui.h" | |
21 | #include "tuxonice_power_off.h" | |
22 | #include "tuxonice_sysfs.h" | |
23 | #include "tuxonice_modules.h" | |
24 | #include "tuxonice_io.h" | |
25 | ||
26 | unsigned long toi_poweroff_method; /* 0 - Kernel power off */ | |
27 | EXPORT_SYMBOL_GPL(toi_poweroff_method); | |
28 | ||
29 | static int wake_delay; | |
30 | static char lid_state_file[256], wake_alarm_dir[256]; | |
31 | static struct file *lid_file, *alarm_file, *epoch_file; | |
32 | static int post_wake_state = -1; | |
33 | ||
34 | static int did_suspend_to_both; | |
35 | ||
36 | int hybrid_sleep_mode(void) | |
37 | { | |
38 | int retval = 0; | |
39 | if (toi_poweroff_method == 3 && did_suspend_to_both == 1) | |
40 | retval = 1; | |
41 | return retval; | |
42 | } | |
43 | EXPORT_SYMBOL_GPL(hybrid_sleep_mode); | |
44 | ||
45 | /* | |
46 | * __toi_power_down | |
47 | * Functionality : Powers down or reboots the computer once the image | |
48 | * has been written to disk. | |
49 | * Key Assumptions : Able to reboot/power down via code called or that | |
50 | * the warning emitted if the calls fail will be visible | |
51 | * to the user (ie printk resumes devices). | |
52 | */ | |
53 | ||
54 | static void __toi_power_down(int method) | |
55 | { | |
56 | int error; | |
57 | ||
58 | toi_cond_pause(1, test_action_state(TOI_REBOOT) ? "Ready to reboot." : "Powering down."); | |
59 | ||
60 | if (test_result_state(TOI_ABORTED)) | |
61 | goto out; | |
62 | ||
63 | if (test_action_state(TOI_REBOOT)) | |
64 | kernel_restart(NULL); | |
65 | ||
66 | switch (method) { | |
67 | case 0: | |
68 | break; | |
69 | case 3: | |
70 | /* | |
71 | * Re-read the overwritten part of pageset2 to make post-resume | |
72 | * faster. | |
73 | */ | |
74 | if (read_pageset2(1)) | |
75 | panic("Attempt to reload pagedir 2 failed. " "Try rebooting."); | |
76 | ||
77 | pm_prepare_console(); | |
78 | ||
79 | error = pm_notifier_call_chain(PM_SUSPEND_PREPARE); | |
80 | if (!error) { | |
81 | pm_restore_gfp_mask(); | |
82 | error = suspend_devices_and_enter(PM_SUSPEND_MEM); | |
83 | pm_restrict_gfp_mask(); | |
84 | if (!error) | |
85 | did_suspend_to_both = 1; | |
86 | } | |
87 | pm_notifier_call_chain(PM_POST_SUSPEND); | |
88 | pm_restore_console(); | |
89 | ||
90 | /* Success - we're now post-resume-from-ram */ | |
91 | if (did_suspend_to_both) | |
92 | return; | |
93 | ||
94 | /* Failed to suspend to ram - do normal power off */ | |
95 | break; | |
96 | case 4: | |
97 | /* | |
98 | * If succeeds, doesn't return. If fails, do a simple | |
99 | * powerdown. | |
100 | */ | |
101 | hibernation_platform_enter(); | |
102 | break; | |
103 | case 5: | |
104 | /* Historic entry only now */ | |
105 | break; | |
106 | } | |
107 | ||
108 | if (method && method != 5) | |
109 | toi_cond_pause(1, "Falling back to alternate power off method."); | |
110 | ||
111 | if (test_result_state(TOI_ABORTED)) | |
112 | goto out; | |
113 | ||
114 | kernel_power_off(); | |
115 | kernel_halt(); | |
116 | toi_cond_pause(1, "Powerdown failed."); | |
117 | while (1) | |
118 | cpu_relax(); | |
119 | ||
120 | out: | |
121 | if (read_pageset2(1)) | |
122 | panic("Attempt to reload pagedir 2 failed. Try rebooting."); | |
123 | return; | |
124 | } | |
125 | ||
126 | #define CLOSE_FILE(file) \ | |
127 | if (file) { \ | |
128 | filp_close(file, NULL); file = NULL; \ | |
129 | } | |
130 | ||
131 | static void powerdown_cleanup(int toi_or_resume) | |
132 | { | |
133 | if (!toi_or_resume) | |
134 | return; | |
135 | ||
136 | CLOSE_FILE(lid_file); | |
137 | CLOSE_FILE(alarm_file); | |
138 | CLOSE_FILE(epoch_file); | |
139 | } | |
140 | ||
141 | static void open_file(char *format, char *arg, struct file **var, int mode, char *desc) | |
142 | { | |
143 | char buf[256]; | |
144 | ||
145 | if (strlen(arg)) { | |
146 | sprintf(buf, format, arg); | |
147 | *var = filp_open(buf, mode, 0); | |
148 | if (IS_ERR(*var) || !*var) { | |
149 | printk(KERN_INFO "Failed to open %s file '%s' (%p).\n", desc, buf, *var); | |
150 | *var = NULL; | |
151 | } | |
152 | } | |
153 | } | |
154 | ||
155 | static int powerdown_init(int toi_or_resume) | |
156 | { | |
157 | if (!toi_or_resume) | |
158 | return 0; | |
159 | ||
160 | did_suspend_to_both = 0; | |
161 | ||
162 | open_file("/proc/acpi/button/%s/state", lid_state_file, &lid_file, O_RDONLY, "lid"); | |
163 | ||
164 | if (strlen(wake_alarm_dir)) { | |
165 | open_file("/sys/class/rtc/%s/wakealarm", wake_alarm_dir, | |
166 | &alarm_file, O_WRONLY, "alarm"); | |
167 | ||
168 | open_file("/sys/class/rtc/%s/since_epoch", wake_alarm_dir, | |
169 | &epoch_file, O_RDONLY, "epoch"); | |
170 | } | |
171 | ||
172 | return 0; | |
173 | } | |
174 | ||
175 | static int lid_closed(void) | |
176 | { | |
177 | char array[25]; | |
178 | ssize_t size; | |
179 | loff_t pos = 0; | |
180 | ||
181 | if (!lid_file) | |
182 | return 0; | |
183 | ||
184 | size = vfs_read(lid_file, (char __user *)array, 25, &pos); | |
185 | if ((int)size < 1) { | |
186 | printk(KERN_INFO "Failed to read lid state file (%d).\n", (int)size); | |
187 | return 0; | |
188 | } | |
189 | ||
190 | if (!strcmp(array, "state: closed\n")) | |
191 | return 1; | |
192 | ||
193 | return 0; | |
194 | } | |
195 | ||
196 | static void write_alarm_file(int value) | |
197 | { | |
198 | ssize_t size; | |
199 | char buf[40]; | |
200 | loff_t pos = 0; | |
201 | ||
202 | if (!alarm_file) | |
203 | return; | |
204 | ||
205 | sprintf(buf, "%d\n", value); | |
206 | ||
207 | size = vfs_write(alarm_file, (char __user *)buf, strlen(buf), &pos); | |
208 | ||
209 | if (size < 0) | |
210 | printk(KERN_INFO "Error %d writing alarm value %s.\n", (int)size, buf); | |
211 | } | |
212 | ||
213 | /** | |
214 | * toi_check_resleep: See whether to powerdown again after waking. | |
215 | * | |
216 | * After waking, check whether we should powerdown again in a (usually | |
217 | * different) way. We only do this if the lid switch is still closed. | |
218 | */ | |
219 | void toi_check_resleep(void) | |
220 | { | |
221 | /* We only return if we suspended to ram and woke. */ | |
222 | if (lid_closed() && post_wake_state >= 0) | |
223 | __toi_power_down(post_wake_state); | |
224 | } | |
225 | ||
226 | void toi_power_down(void) | |
227 | { | |
228 | if (alarm_file && wake_delay) { | |
229 | char array[25]; | |
230 | loff_t pos = 0; | |
231 | size_t size = vfs_read(epoch_file, (char __user *)array, 25, | |
232 | &pos); | |
233 | ||
234 | if (((int)size) < 1) | |
235 | printk(KERN_INFO "Failed to read epoch file (%d).\n", (int)size); | |
236 | else { | |
237 | unsigned long since_epoch; | |
238 | if (!strict_strtoul(array, 0, &since_epoch)) { | |
239 | /* Clear any wakeup time. */ | |
240 | write_alarm_file(0); | |
241 | ||
242 | /* Set new wakeup time. */ | |
243 | write_alarm_file(since_epoch + wake_delay); | |
244 | } | |
245 | } | |
246 | } | |
247 | ||
248 | __toi_power_down(toi_poweroff_method); | |
249 | ||
250 | toi_check_resleep(); | |
251 | } | |
252 | EXPORT_SYMBOL_GPL(toi_power_down); | |
253 | ||
254 | static struct toi_sysfs_data sysfs_params[] = { | |
255 | #if defined(CONFIG_ACPI) | |
256 | SYSFS_STRING("lid_file", SYSFS_RW, lid_state_file, 256, 0, NULL), | |
257 | SYSFS_INT("wake_delay", SYSFS_RW, &wake_delay, 0, INT_MAX, 0, NULL), | |
258 | SYSFS_STRING("wake_alarm_dir", SYSFS_RW, wake_alarm_dir, 256, 0, NULL), | |
259 | SYSFS_INT("post_wake_state", SYSFS_RW, &post_wake_state, -1, 5, 0, | |
260 | NULL), | |
261 | #endif | |
262 | SYSFS_UL("powerdown_method", SYSFS_RW, &toi_poweroff_method, 0, 5, 0), | |
263 | SYSFS_INT("did_suspend_to_both", SYSFS_READONLY, &did_suspend_to_both, | |
264 | 0, 0, 0, NULL) | |
265 | }; | |
266 | ||
267 | static struct toi_module_ops powerdown_ops = { | |
268 | .type = MISC_HIDDEN_MODULE, | |
269 | .name = "poweroff", | |
270 | .initialise = powerdown_init, | |
271 | .cleanup = powerdown_cleanup, | |
272 | .directory = "[ROOT]", | |
273 | .module = THIS_MODULE, | |
274 | .sysfs_data = sysfs_params, | |
275 | .num_sysfs_entries = sizeof(sysfs_params) / sizeof(struct toi_sysfs_data), | |
276 | }; | |
277 | ||
278 | int toi_poweroff_init(void) | |
279 | { | |
280 | return toi_register_module(&powerdown_ops); | |
281 | } | |
282 | ||
283 | void toi_poweroff_exit(void) | |
284 | { | |
285 | toi_unregister_module(&powerdown_ops); | |
286 | } |