aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/acpi/sleep/main.c53
-rw-r--r--include/linux/suspend.h13
-rw-r--r--kernel/power/swsusp.c122
3 files changed, 180 insertions, 8 deletions
diff --git a/drivers/acpi/sleep/main.c b/drivers/acpi/sleep/main.c
index 28a691cc625e..45a8015e4217 100644
--- a/drivers/acpi/sleep/main.c
+++ b/drivers/acpi/sleep/main.c
@@ -394,9 +394,25 @@ void __init acpi_no_s4_hw_signature(void)
394 394
395static int acpi_hibernation_begin(void) 395static int acpi_hibernation_begin(void)
396{ 396{
397 acpi_target_sleep_state = ACPI_STATE_S4; 397 int error;
398 acpi_sleep_tts_switch(acpi_target_sleep_state); 398
399 return 0; 399 error = hibernate_nvs_alloc();
400 if (!error) {
401 acpi_target_sleep_state = ACPI_STATE_S4;
402 acpi_sleep_tts_switch(acpi_target_sleep_state);
403 }
404
405 return error;
406}
407
408static int acpi_hibernation_pre_snapshot(void)
409{
410 int error = acpi_pm_prepare();
411
412 if (!error)
413 hibernate_nvs_save();
414
415 return error;
400} 416}
401 417
402static int acpi_hibernation_enter(void) 418static int acpi_hibernation_enter(void)
@@ -417,6 +433,12 @@ static int acpi_hibernation_enter(void)
417 return ACPI_SUCCESS(status) ? 0 : -EFAULT; 433 return ACPI_SUCCESS(status) ? 0 : -EFAULT;
418} 434}
419 435
436static void acpi_hibernation_finish(void)
437{
438 hibernate_nvs_free();
439 acpi_pm_finish();
440}
441
420static void acpi_hibernation_leave(void) 442static void acpi_hibernation_leave(void)
421{ 443{
422 /* 444 /*
@@ -432,6 +454,8 @@ static void acpi_hibernation_leave(void)
432 "cannot resume!\n"); 454 "cannot resume!\n");
433 panic("ACPI S4 hardware signature mismatch"); 455 panic("ACPI S4 hardware signature mismatch");
434 } 456 }
457 /* Restore the NVS memory area */
458 hibernate_nvs_restore();
435} 459}
436 460
437static void acpi_pm_enable_gpes(void) 461static void acpi_pm_enable_gpes(void)
@@ -442,8 +466,8 @@ static void acpi_pm_enable_gpes(void)
442static struct platform_hibernation_ops acpi_hibernation_ops = { 466static struct platform_hibernation_ops acpi_hibernation_ops = {
443 .begin = acpi_hibernation_begin, 467 .begin = acpi_hibernation_begin,
444 .end = acpi_pm_end, 468 .end = acpi_pm_end,
445 .pre_snapshot = acpi_pm_prepare, 469 .pre_snapshot = acpi_hibernation_pre_snapshot,
446 .finish = acpi_pm_finish, 470 .finish = acpi_hibernation_finish,
447 .prepare = acpi_pm_prepare, 471 .prepare = acpi_pm_prepare,
448 .enter = acpi_hibernation_enter, 472 .enter = acpi_hibernation_enter,
449 .leave = acpi_hibernation_leave, 473 .leave = acpi_hibernation_leave,
@@ -469,8 +493,21 @@ static int acpi_hibernation_begin_old(void)
469 493
470 error = acpi_sleep_prepare(ACPI_STATE_S4); 494 error = acpi_sleep_prepare(ACPI_STATE_S4);
471 495
496 if (!error) {
497 error = hibernate_nvs_alloc();
498 if (!error)
499 acpi_target_sleep_state = ACPI_STATE_S4;
500 }
501 return error;
502}
503
504static int acpi_hibernation_pre_snapshot_old(void)
505{
506 int error = acpi_pm_disable_gpes();
507
472 if (!error) 508 if (!error)
473 acpi_target_sleep_state = ACPI_STATE_S4; 509 hibernate_nvs_save();
510
474 return error; 511 return error;
475} 512}
476 513
@@ -481,8 +518,8 @@ static int acpi_hibernation_begin_old(void)
481static struct platform_hibernation_ops acpi_hibernation_ops_old = { 518static struct platform_hibernation_ops acpi_hibernation_ops_old = {
482 .begin = acpi_hibernation_begin_old, 519 .begin = acpi_hibernation_begin_old,
483 .end = acpi_pm_end, 520 .end = acpi_pm_end,
484 .pre_snapshot = acpi_pm_disable_gpes, 521 .pre_snapshot = acpi_hibernation_pre_snapshot_old,
485 .finish = acpi_pm_finish, 522 .finish = acpi_hibernation_finish,
486 .prepare = acpi_pm_disable_gpes, 523 .prepare = acpi_pm_disable_gpes,
487 .enter = acpi_hibernation_enter, 524 .enter = acpi_hibernation_enter,
488 .leave = acpi_hibernation_leave, 525 .leave = acpi_hibernation_leave,
diff --git a/include/linux/suspend.h b/include/linux/suspend.h
index 2ce8207686e2..2b409c44db83 100644
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -232,6 +232,11 @@ extern unsigned long get_safe_page(gfp_t gfp_mask);
232 232
233extern void hibernation_set_ops(struct platform_hibernation_ops *ops); 233extern void hibernation_set_ops(struct platform_hibernation_ops *ops);
234extern int hibernate(void); 234extern int hibernate(void);
235extern int hibernate_nvs_register(unsigned long start, unsigned long size);
236extern int hibernate_nvs_alloc(void);
237extern void hibernate_nvs_free(void);
238extern void hibernate_nvs_save(void);
239extern void hibernate_nvs_restore(void);
235#else /* CONFIG_HIBERNATION */ 240#else /* CONFIG_HIBERNATION */
236static inline int swsusp_page_is_forbidden(struct page *p) { return 0; } 241static inline int swsusp_page_is_forbidden(struct page *p) { return 0; }
237static inline void swsusp_set_page_free(struct page *p) {} 242static inline void swsusp_set_page_free(struct page *p) {}
@@ -239,6 +244,14 @@ static inline void swsusp_unset_page_free(struct page *p) {}
239 244
240static inline void hibernation_set_ops(struct platform_hibernation_ops *ops) {} 245static inline void hibernation_set_ops(struct platform_hibernation_ops *ops) {}
241static inline int hibernate(void) { return -ENOSYS; } 246static inline int hibernate(void) { return -ENOSYS; }
247static inline int hibernate_nvs_register(unsigned long a, unsigned long b)
248{
249 return 0;
250}
251static inline int hibernate_nvs_alloc(void) { return 0; }
252static inline void hibernate_nvs_free(void) {}
253static inline void hibernate_nvs_save(void) {}
254static inline void hibernate_nvs_restore(void) {}
242#endif /* CONFIG_HIBERNATION */ 255#endif /* CONFIG_HIBERNATION */
243 256
244#ifdef CONFIG_PM_SLEEP 257#ifdef CONFIG_PM_SLEEP
diff --git a/kernel/power/swsusp.c b/kernel/power/swsusp.c
index 023ff2a31d89..a92c91451559 100644
--- a/kernel/power/swsusp.c
+++ b/kernel/power/swsusp.c
@@ -262,3 +262,125 @@ int swsusp_shrink_memory(void)
262 262
263 return 0; 263 return 0;
264} 264}
265
266/*
267 * Platforms, like ACPI, may want us to save some memory used by them during
268 * hibernation and to restore the contents of this memory during the subsequent
269 * resume. The code below implements a mechanism allowing us to do that.
270 */
271
272struct nvs_page {
273 unsigned long phys_start;
274 unsigned int size;
275 void *kaddr;
276 void *data;
277 struct list_head node;
278};
279
280static LIST_HEAD(nvs_list);
281
282/**
283 * hibernate_nvs_register - register platform NVS memory region to save
284 * @start - physical address of the region
285 * @size - size of the region
286 *
287 * The NVS region need not be page-aligned (both ends) and we arrange
288 * things so that the data from page-aligned addresses in this region will
289 * be copied into separate RAM pages.
290 */
291int hibernate_nvs_register(unsigned long start, unsigned long size)
292{
293 struct nvs_page *entry, *next;
294
295 while (size > 0) {
296 unsigned int nr_bytes;
297
298 entry = kzalloc(sizeof(struct nvs_page), GFP_KERNEL);
299 if (!entry)
300 goto Error;
301
302 list_add_tail(&entry->node, &nvs_list);
303 entry->phys_start = start;
304 nr_bytes = PAGE_SIZE - (start & ~PAGE_MASK);
305 entry->size = (size < nr_bytes) ? size : nr_bytes;
306
307 start += entry->size;
308 size -= entry->size;
309 }
310 return 0;
311
312 Error:
313 list_for_each_entry_safe(entry, next, &nvs_list, node) {
314 list_del(&entry->node);
315 kfree(entry);
316 }
317 return -ENOMEM;
318}
319
320/**
321 * hibernate_nvs_free - free data pages allocated for saving NVS regions
322 */
323void hibernate_nvs_free(void)
324{
325 struct nvs_page *entry;
326
327 list_for_each_entry(entry, &nvs_list, node)
328 if (entry->data) {
329 free_page((unsigned long)entry->data);
330 entry->data = NULL;
331 if (entry->kaddr) {
332 iounmap(entry->kaddr);
333 entry->kaddr = NULL;
334 }
335 }
336}
337
338/**
339 * hibernate_nvs_alloc - allocate memory necessary for saving NVS regions
340 */
341int hibernate_nvs_alloc(void)
342{
343 struct nvs_page *entry;
344
345 list_for_each_entry(entry, &nvs_list, node) {
346 entry->data = (void *)__get_free_page(GFP_KERNEL);
347 if (!entry->data) {
348 hibernate_nvs_free();
349 return -ENOMEM;
350 }
351 }
352 return 0;
353}
354
355/**
356 * hibernate_nvs_save - save NVS memory regions
357 */
358void hibernate_nvs_save(void)
359{
360 struct nvs_page *entry;
361
362 printk(KERN_INFO "PM: Saving platform NVS memory\n");
363
364 list_for_each_entry(entry, &nvs_list, node)
365 if (entry->data) {
366 entry->kaddr = ioremap(entry->phys_start, entry->size);
367 memcpy(entry->data, entry->kaddr, entry->size);
368 }
369}
370
371/**
372 * hibernate_nvs_restore - restore NVS memory regions
373 *
374 * This function is going to be called with interrupts disabled, so it
375 * cannot iounmap the virtual addresses used to access the NVS region.
376 */
377void hibernate_nvs_restore(void)
378{
379 struct nvs_page *entry;
380
381 printk(KERN_INFO "PM: Restoring platform NVS memory\n");
382
383 list_for_each_entry(entry, &nvs_list, node)
384 if (entry->data)
385 memcpy(entry->kaddr, entry->data, entry->size);
386}