diff options
| -rw-r--r-- | drivers/acpi/sleep/main.c | 53 | ||||
| -rw-r--r-- | include/linux/suspend.h | 13 | ||||
| -rw-r--r-- | kernel/power/swsusp.c | 122 |
3 files changed, 180 insertions, 8 deletions
diff --git a/drivers/acpi/sleep/main.c b/drivers/acpi/sleep/main.c index 28a691cc625..45a8015e421 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 | ||
| 395 | static int acpi_hibernation_begin(void) | 395 | static 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 | |||
| 408 | static 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 | ||
| 402 | static int acpi_hibernation_enter(void) | 418 | static 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 | ||
| 436 | static void acpi_hibernation_finish(void) | ||
| 437 | { | ||
| 438 | hibernate_nvs_free(); | ||
| 439 | acpi_pm_finish(); | ||
| 440 | } | ||
| 441 | |||
| 420 | static void acpi_hibernation_leave(void) | 442 | static 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 | ||
| 437 | static void acpi_pm_enable_gpes(void) | 461 | static void acpi_pm_enable_gpes(void) |
| @@ -442,8 +466,8 @@ static void acpi_pm_enable_gpes(void) | |||
| 442 | static struct platform_hibernation_ops acpi_hibernation_ops = { | 466 | static 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 | |||
| 504 | static 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) | |||
| 481 | static struct platform_hibernation_ops acpi_hibernation_ops_old = { | 518 | static 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 2ce8207686e..2b409c44db8 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 | ||
| 233 | extern void hibernation_set_ops(struct platform_hibernation_ops *ops); | 233 | extern void hibernation_set_ops(struct platform_hibernation_ops *ops); |
| 234 | extern int hibernate(void); | 234 | extern int hibernate(void); |
| 235 | extern int hibernate_nvs_register(unsigned long start, unsigned long size); | ||
| 236 | extern int hibernate_nvs_alloc(void); | ||
| 237 | extern void hibernate_nvs_free(void); | ||
| 238 | extern void hibernate_nvs_save(void); | ||
| 239 | extern void hibernate_nvs_restore(void); | ||
| 235 | #else /* CONFIG_HIBERNATION */ | 240 | #else /* CONFIG_HIBERNATION */ |
| 236 | static inline int swsusp_page_is_forbidden(struct page *p) { return 0; } | 241 | static inline int swsusp_page_is_forbidden(struct page *p) { return 0; } |
| 237 | static inline void swsusp_set_page_free(struct page *p) {} | 242 | static 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 | ||
| 240 | static inline void hibernation_set_ops(struct platform_hibernation_ops *ops) {} | 245 | static inline void hibernation_set_ops(struct platform_hibernation_ops *ops) {} |
| 241 | static inline int hibernate(void) { return -ENOSYS; } | 246 | static inline int hibernate(void) { return -ENOSYS; } |
| 247 | static inline int hibernate_nvs_register(unsigned long a, unsigned long b) | ||
| 248 | { | ||
| 249 | return 0; | ||
| 250 | } | ||
| 251 | static inline int hibernate_nvs_alloc(void) { return 0; } | ||
| 252 | static inline void hibernate_nvs_free(void) {} | ||
| 253 | static inline void hibernate_nvs_save(void) {} | ||
| 254 | static 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 023ff2a31d8..a92c9145155 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 | |||
| 272 | struct nvs_page { | ||
| 273 | unsigned long phys_start; | ||
| 274 | unsigned int size; | ||
| 275 | void *kaddr; | ||
| 276 | void *data; | ||
| 277 | struct list_head node; | ||
| 278 | }; | ||
| 279 | |||
| 280 | static 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 | */ | ||
| 291 | int 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 | */ | ||
| 323 | void 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 | */ | ||
| 341 | int 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 | */ | ||
| 358 | void 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 | */ | ||
| 377 | void 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 | } | ||
