Commit | Line | Data |
---|---|---|
6fa3eb70 S |
1 | /* |
2 | * Copyright (C) 2004-2010 Nigel Cunningham (nigel at tuxonice net) | |
3 | * | |
4 | * This file is released under the GPLv2. | |
5 | */ | |
6 | #include <linux/resume-trace.h> | |
7 | #include <linux/kernel.h> | |
8 | #include <linux/swap.h> | |
9 | #include <linux/syscalls.h> | |
10 | #include <linux/bio.h> | |
11 | #include <linux/root_dev.h> | |
12 | #include <linux/freezer.h> | |
13 | #include <linux/reboot.h> | |
14 | #include <linux/writeback.h> | |
15 | #include <linux/tty.h> | |
16 | #include <linux/crypto.h> | |
17 | #include <linux/cpu.h> | |
18 | #include <linux/ctype.h> | |
19 | #include "tuxonice_io.h" | |
20 | #include "tuxonice.h" | |
21 | #include "tuxonice_extent.h" | |
22 | #include "tuxonice_netlink.h" | |
23 | #include "tuxonice_prepare_image.h" | |
24 | #include "tuxonice_ui.h" | |
25 | #include "tuxonice_sysfs.h" | |
26 | #include "tuxonice_pagedir.h" | |
27 | #include "tuxonice_modules.h" | |
28 | #include "tuxonice_builtin.h" | |
29 | #include "tuxonice_power_off.h" | |
30 | #include "tuxonice_alloc.h" | |
31 | ||
32 | unsigned long toi_bootflags_mask; | |
33 | EXPORT_SYMBOL_GPL(toi_bootflags_mask); | |
34 | ||
35 | /* | |
36 | * Highmem related functions (x86 only). | |
37 | */ | |
38 | ||
39 | #ifdef CONFIG_HIGHMEM | |
40 | ||
41 | /** | |
42 | * copyback_high: Restore highmem pages. | |
43 | * | |
44 | * Highmem data and pbe lists are/can be stored in highmem. | |
45 | * The format is slightly different to the lowmem pbe lists | |
46 | * used for the assembly code: the last pbe in each page is | |
47 | * a struct page * instead of struct pbe *, pointing to the | |
48 | * next page where pbes are stored (or NULL if happens to be | |
49 | * the end of the list). Since we don't want to generate | |
50 | * unnecessary deltas against swsusp code, we use a cast | |
51 | * instead of a union. | |
52 | **/ | |
53 | ||
54 | static void copyback_high(void) | |
55 | { | |
56 | struct page *pbe_page = (struct page *)restore_highmem_pblist; | |
57 | struct pbe *this_pbe, *first_pbe; | |
58 | unsigned long *origpage, *copypage; | |
59 | int pbe_index = 1; | |
60 | ||
61 | if (!pbe_page) | |
62 | return; | |
63 | ||
64 | this_pbe = (struct pbe *)kmap_atomic(pbe_page); | |
65 | first_pbe = this_pbe; | |
66 | ||
67 | while (this_pbe) { | |
68 | int loop = (PAGE_SIZE / sizeof(unsigned long)) - 1; | |
69 | ||
70 | origpage = kmap_atomic(pfn_to_page((unsigned long)this_pbe->orig_address)); | |
71 | copypage = kmap_atomic((struct page *)this_pbe->address); | |
72 | ||
73 | while (loop >= 0) { | |
74 | *(origpage + loop) = *(copypage + loop); | |
75 | loop--; | |
76 | } | |
77 | ||
78 | kunmap_atomic(origpage); | |
79 | kunmap_atomic(copypage); | |
80 | ||
81 | if (!this_pbe->next) | |
82 | break; | |
83 | ||
84 | if (pbe_index < PBES_PER_PAGE) { | |
85 | this_pbe++; | |
86 | pbe_index++; | |
87 | } else { | |
88 | pbe_page = (struct page *)this_pbe->next; | |
89 | kunmap_atomic(first_pbe); | |
90 | if (!pbe_page) | |
91 | return; | |
92 | this_pbe = (struct pbe *)kmap_atomic(pbe_page); | |
93 | first_pbe = this_pbe; | |
94 | pbe_index = 1; | |
95 | } | |
96 | } | |
97 | kunmap_atomic(first_pbe); | |
98 | } | |
99 | ||
100 | #else /* CONFIG_HIGHMEM */ | |
101 | static void copyback_high(void) | |
102 | { | |
103 | } | |
104 | #endif | |
105 | ||
106 | char toi_wait_for_keypress_dev_console(int timeout) | |
107 | { | |
108 | int fd, this_timeout = 255; | |
109 | char key = '\0'; | |
110 | struct termios t, t_backup; | |
111 | ||
112 | /* We should be guaranteed /dev/console exists after populate_rootfs() | |
113 | * in init/main.c. | |
114 | */ | |
115 | fd = sys_open("/dev/console", O_RDONLY, 0); | |
116 | if (fd < 0) { | |
117 | printk(KERN_INFO "Couldn't open /dev/console.\n"); | |
118 | return key; | |
119 | } | |
120 | ||
121 | if (sys_ioctl(fd, TCGETS, (long)&t) < 0) | |
122 | goto out_close; | |
123 | ||
124 | memcpy(&t_backup, &t, sizeof(t)); | |
125 | ||
126 | t.c_lflag &= ~(ISIG | ICANON | ECHO); | |
127 | t.c_cc[VMIN] = 0; | |
128 | ||
129 | new_timeout: | |
130 | if (timeout > 0) { | |
131 | this_timeout = timeout < 26 ? timeout : 25; | |
132 | timeout -= this_timeout; | |
133 | this_timeout *= 10; | |
134 | } | |
135 | ||
136 | t.c_cc[VTIME] = this_timeout; | |
137 | ||
138 | if (sys_ioctl(fd, TCSETS, (long)&t) < 0) | |
139 | goto out_restore; | |
140 | ||
141 | while (1) { | |
142 | if (sys_read(fd, &key, 1) <= 0) { | |
143 | if (timeout) | |
144 | goto new_timeout; | |
145 | key = '\0'; | |
146 | break; | |
147 | } | |
148 | key = tolower(key); | |
149 | if (test_toi_state(TOI_SANITY_CHECK_PROMPT)) { | |
150 | if (key == 'c') { | |
151 | set_toi_state(TOI_CONTINUE_REQ); | |
152 | break; | |
153 | } else if (key == ' ') | |
154 | break; | |
155 | } else | |
156 | break; | |
157 | } | |
158 | ||
159 | out_restore: | |
160 | sys_ioctl(fd, TCSETS, (long)&t_backup); | |
161 | out_close: | |
162 | sys_close(fd); | |
163 | ||
164 | return key; | |
165 | } | |
166 | EXPORT_SYMBOL_GPL(toi_wait_for_keypress_dev_console); | |
167 | ||
168 | struct toi_boot_kernel_data toi_bkd __nosavedata __attribute__ ((aligned(PAGE_SIZE))) = { | |
169 | MY_BOOT_KERNEL_DATA_VERSION, 0, | |
170 | #ifdef CONFIG_TOI_REPLACE_SWSUSP | |
171 | (1 << TOI_REPLACE_SWSUSP) | | |
172 | #endif | |
173 | (1 << TOI_NO_FLUSHER_THREAD) | | |
174 | /* (1 << TOI_NO_MULTITHREADED_IO) | */ | |
175 | (1 << TOI_LATE_CPU_HOTPLUG) | (1 << TOI_PAGESET2_FULL),}; | |
176 | ||
177 | EXPORT_SYMBOL_GPL(toi_bkd); | |
178 | ||
179 | struct block_device *toi_open_by_devnum(dev_t dev) | |
180 | { | |
181 | struct block_device *bdev = bdget(dev); | |
182 | int err = -ENOMEM; | |
183 | if (bdev) | |
184 | err = blkdev_get(bdev, FMODE_READ | FMODE_NDELAY, NULL); | |
185 | return err ? ERR_PTR(err) : bdev; | |
186 | } | |
187 | EXPORT_SYMBOL_GPL(toi_open_by_devnum); | |
188 | ||
189 | /** | |
190 | * toi_close_bdev: Close a swap bdev. | |
191 | * | |
192 | * int: The swap entry number to close. | |
193 | */ | |
194 | void toi_close_bdev(struct block_device *bdev) | |
195 | { | |
196 | blkdev_put(bdev, FMODE_READ | FMODE_NDELAY); | |
197 | } | |
198 | EXPORT_SYMBOL_GPL(toi_close_bdev); | |
199 | ||
200 | int toi_wait = CONFIG_TOI_DEFAULT_WAIT; | |
201 | EXPORT_SYMBOL_GPL(toi_wait); | |
202 | ||
203 | struct toi_core_fns *toi_core_fns; | |
204 | EXPORT_SYMBOL_GPL(toi_core_fns); | |
205 | ||
206 | unsigned long toi_result; | |
207 | EXPORT_SYMBOL_GPL(toi_result); | |
208 | ||
209 | struct pagedir pagedir1 = { 1 }; | |
210 | ||
211 | EXPORT_SYMBOL_GPL(pagedir1); | |
212 | ||
213 | unsigned long toi_get_nonconflicting_page(void) | |
214 | { | |
215 | return toi_core_fns->get_nonconflicting_page(); | |
216 | } | |
217 | ||
218 | int toi_post_context_save(void) | |
219 | { | |
220 | return toi_core_fns->post_context_save(); | |
221 | } | |
222 | ||
223 | int try_tuxonice_hibernate(void) | |
224 | { | |
225 | if (!toi_core_fns) | |
226 | return -ENODEV; | |
227 | ||
228 | return toi_core_fns->try_hibernate(); | |
229 | } | |
230 | ||
231 | static int num_resume_calls; | |
232 | #ifdef CONFIG_TOI_IGNORE_LATE_INITCALL | |
233 | static int ignore_late_initcall = 1; | |
234 | #else | |
235 | static int ignore_late_initcall; | |
236 | #endif | |
237 | ||
238 | #ifdef CONFIG_TOI_ENHANCE | |
239 | int toi_ignore_late_initcall(void) | |
240 | { | |
241 | return ignore_late_initcall; | |
242 | } | |
243 | EXPORT_SYMBOL_GPL(toi_ignore_late_initcall); | |
244 | #endif | |
245 | ||
246 | int toi_translate_err_default = TOI_CONTINUE_REQ; | |
247 | EXPORT_SYMBOL_GPL(toi_translate_err_default); | |
248 | ||
249 | void try_tuxonice_resume(void) | |
250 | { | |
251 | /* Don't let it wrap around eventually */ | |
252 | if (num_resume_calls < 2) | |
253 | num_resume_calls++; | |
254 | ||
255 | if (num_resume_calls == 1 && ignore_late_initcall) { | |
256 | printk(KERN_INFO "TuxOnIce: Ignoring late initcall, as requested.\n"); | |
257 | return; | |
258 | } | |
259 | ||
260 | if (toi_core_fns) | |
261 | toi_core_fns->try_resume(); | |
262 | else | |
263 | printk(KERN_INFO "TuxOnIce core not loaded yet.\n"); | |
264 | } | |
265 | ||
266 | int toi_lowlevel_builtin(void) | |
267 | { | |
268 | int error = 0; | |
269 | ||
270 | save_processor_state(); | |
271 | error = swsusp_arch_suspend(); | |
272 | if (error) | |
273 | printk(KERN_ERR "Error %d hibernating\n", error); | |
274 | ||
275 | #ifdef CONFIG_TOI_ENHANCE | |
276 | if (test_result_state(TOI_ARCH_PREPARE_FAILED)) { | |
277 | hib_err("CAUTION: error(%d/0x%08x)\n", error, (unsigned int)toi_result); | |
278 | } | |
279 | #endif | |
280 | ||
281 | /* Restore control flow appears here */ | |
282 | if (!toi_in_hibernate) { | |
283 | copyback_high(); | |
284 | set_toi_state(TOI_NOW_RESUMING); | |
285 | } | |
286 | ||
287 | restore_processor_state(); | |
288 | return error; | |
289 | } | |
290 | EXPORT_SYMBOL_GPL(toi_lowlevel_builtin); | |
291 | ||
292 | unsigned long toi_compress_bytes_in; | |
293 | EXPORT_SYMBOL_GPL(toi_compress_bytes_in); | |
294 | ||
295 | unsigned long toi_compress_bytes_out; | |
296 | EXPORT_SYMBOL_GPL(toi_compress_bytes_out); | |
297 | ||
298 | int toi_in_suspend(void) | |
299 | { | |
300 | return in_suspend; | |
301 | } | |
302 | EXPORT_SYMBOL_GPL(toi_in_suspend); | |
303 | ||
304 | unsigned long toi_state = ((1 << TOI_BOOT_TIME) | | |
305 | (1 << TOI_IGNORE_LOGLEVEL) | (1 << TOI_IO_STOPPED)); | |
306 | EXPORT_SYMBOL_GPL(toi_state); | |
307 | ||
308 | /* The number of hibernates we have started (some may have been cancelled) */ | |
309 | unsigned int nr_hibernates; | |
310 | EXPORT_SYMBOL_GPL(nr_hibernates); | |
311 | ||
312 | int toi_running; | |
313 | EXPORT_SYMBOL_GPL(toi_running); | |
314 | ||
315 | __nosavedata int toi_in_hibernate; | |
316 | EXPORT_SYMBOL_GPL(toi_in_hibernate); | |
317 | ||
318 | __nosavedata struct pbe *restore_highmem_pblist; | |
319 | EXPORT_SYMBOL_GPL(restore_highmem_pblist); | |
320 | ||
321 | int toi_trace_allocs; | |
322 | EXPORT_SYMBOL_GPL(toi_trace_allocs); | |
323 | ||
324 | void toi_read_lock_tasklist(void) | |
325 | { | |
326 | read_lock(&tasklist_lock); | |
327 | } | |
328 | EXPORT_SYMBOL_GPL(toi_read_lock_tasklist); | |
329 | ||
330 | void toi_read_unlock_tasklist(void) | |
331 | { | |
332 | read_unlock(&tasklist_lock); | |
333 | } | |
334 | EXPORT_SYMBOL_GPL(toi_read_unlock_tasklist); | |
335 | ||
336 | #ifdef CONFIG_TOI_ZRAM_SUPPORT | |
337 | int (*toi_flag_zram_disks) (void); | |
338 | EXPORT_SYMBOL_GPL(toi_flag_zram_disks); | |
339 | ||
340 | int toi_do_flag_zram_disks(void) | |
341 | { | |
342 | return toi_flag_zram_disks ? (*toi_flag_zram_disks) () : 0; | |
343 | } | |
344 | EXPORT_SYMBOL_GPL(toi_do_flag_zram_disks); | |
345 | #endif | |
346 | ||
347 | static int __init toi_wait_setup(char *str) | |
348 | { | |
349 | int value; | |
350 | ||
351 | if (sscanf(str, "=%d", &value)) { | |
352 | if (value < -1 || value > 255) | |
353 | printk(KERN_INFO "TuxOnIce_wait outside range -1 to " "255.\n"); | |
354 | else | |
355 | toi_wait = value; | |
356 | } | |
357 | ||
358 | return 1; | |
359 | } | |
360 | ||
361 | __setup("toi_wait", toi_wait_setup); | |
362 | ||
363 | static int __init toi_translate_retry_setup(char *str) | |
364 | { | |
365 | toi_translate_err_default = 0; | |
366 | return 1; | |
367 | } | |
368 | ||
369 | __setup("toi_translate_retry", toi_translate_retry_setup); | |
370 | ||
371 | static int __init toi_debug_setup(char *str) | |
372 | { | |
373 | toi_bkd.toi_action |= (1 << TOI_LOGALL); | |
374 | toi_bootflags_mask |= (1 << TOI_LOGALL); | |
375 | toi_bkd.toi_debug_state = 255; | |
376 | toi_bkd.toi_default_console_level = 7; | |
377 | return 1; | |
378 | } | |
379 | ||
380 | __setup("toi_debug_setup", toi_debug_setup); | |
381 | ||
382 | static int __init toi_pause_setup(char *str) | |
383 | { | |
384 | toi_bkd.toi_action |= (1 << TOI_PAUSE); | |
385 | toi_bootflags_mask |= (1 << TOI_PAUSE); | |
386 | return 1; | |
387 | } | |
388 | ||
389 | __setup("toi_pause", toi_pause_setup); | |
390 | ||
391 | #ifdef CONFIG_PM_DEBUG | |
392 | static int __init toi_trace_allocs_setup(char *str) | |
393 | { | |
394 | int value; | |
395 | ||
396 | if (sscanf(str, "=%d", &value)) | |
397 | toi_trace_allocs = value; | |
398 | ||
399 | return 1; | |
400 | } | |
401 | ||
402 | __setup("toi_trace_allocs", toi_trace_allocs_setup); | |
403 | #endif | |
404 | ||
405 | static int __init toi_ignore_late_initcall_setup(char *str) | |
406 | { | |
407 | int value; | |
408 | ||
409 | if (sscanf(str, "=%d", &value)) | |
410 | ignore_late_initcall = value; | |
411 | ||
412 | return 1; | |
413 | } | |
414 | ||
415 | __setup("toi_initramfs_resume_only", toi_ignore_late_initcall_setup); | |
416 | ||
417 | static int __init toi_force_no_multithreaded_setup(char *str) | |
418 | { | |
419 | int value; | |
420 | ||
421 | toi_bkd.toi_action &= ~(1 << TOI_NO_MULTITHREADED_IO); | |
422 | toi_bootflags_mask |= (1 << TOI_NO_MULTITHREADED_IO); | |
423 | ||
424 | if (sscanf(str, "=%d", &value) && value) | |
425 | toi_bkd.toi_action |= (1 << TOI_NO_MULTITHREADED_IO); | |
426 | ||
427 | return 1; | |
428 | } | |
429 | ||
430 | __setup("toi_no_multithreaded", toi_force_no_multithreaded_setup); | |
431 | ||
432 | #ifdef CONFIG_KGDB | |
433 | static int __init toi_post_resume_breakpoint_setup(char *str) | |
434 | { | |
435 | int value; | |
436 | ||
437 | toi_bkd.toi_action &= ~(1 << TOI_POST_RESUME_BREAKPOINT); | |
438 | toi_bootflags_mask |= (1 << TOI_POST_RESUME_BREAKPOINT); | |
439 | if (sscanf(str, "=%d", &value) && value) | |
440 | toi_bkd.toi_action |= (1 << TOI_POST_RESUME_BREAKPOINT); | |
441 | ||
442 | return 1; | |
443 | } | |
444 | ||
445 | __setup("toi_post_resume_break", toi_post_resume_breakpoint_setup); | |
446 | #endif | |
447 | ||
448 | static int __init toi_disable_readahead_setup(char *str) | |
449 | { | |
450 | int value; | |
451 | ||
452 | toi_bkd.toi_action &= ~(1 << TOI_NO_READAHEAD); | |
453 | toi_bootflags_mask |= (1 << TOI_NO_READAHEAD); | |
454 | if (sscanf(str, "=%d", &value) && value) | |
455 | toi_bkd.toi_action |= (1 << TOI_NO_READAHEAD); | |
456 | ||
457 | return 1; | |
458 | } | |
459 | ||
460 | __setup("toi_no_readahead", toi_disable_readahead_setup); |