aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2008-10-26 15:52:15 -0400
committerLen Brown <len.brown@intel.com>2008-12-19 04:40:34 -0500
commit3f4b0ef7f2899c91b1d6958779f084b44dd59d32 (patch)
treec026201981ecac9c575653c9a46e4c1dea1245a2
parent3fe0313e6ec572e6bb3f9d247316a834336db4be (diff)
ACPI hibernate: Add a mechanism to save/restore ACPI NVS memory
According to the ACPI Specification 3.0b, Section 15.3.2, "OSPM will call the _PTS control method some time before entering a sleeping state, to allow the platform's AML code to update this memory image before entering the sleeping state. After the system awakes from an S4 state, OSPM will restore this memory area and call the _WAK control method to enable the BIOS to reclaim its memory image." For this reason, implement a mechanism allowing us to save the NVS memory during hibernation and to restore it during the subsequent resume. Based on a patch by Zhang Rui. Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Acked-by: Nigel Cunningham <nigel@tuxonice.net> Cc: Zhang Rui <rui.zhang@intel.com> Signed-off-by: Len Brown <len.brown@intel.com>
-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}