| 1 | /* |
| 2 | * Hibernation support specific for ARM |
| 3 | * |
| 4 | * Derived from work on ARM hibernation support by: |
| 5 | * |
| 6 | * Ubuntu project, hibernation support for mach-dove |
| 7 | * Copyright (C) 2010 Nokia Corporation (Hiroshi Doyu) |
| 8 | * Copyright (C) 2010 Texas Instruments, Inc. (Teerth Reddy et al.) |
| 9 | * https://lkml.org/lkml/2010/6/18/4 |
| 10 | * https://lists.linux-foundation.org/pipermail/linux-pm/2010-June/027422.html |
| 11 | * https://patchwork.kernel.org/patch/96442/ |
| 12 | * |
| 13 | * Copyright (C) 2006 Rafael J. Wysocki <rjw@sisk.pl> |
| 14 | * |
| 15 | * License terms: GNU General Public License (GPL) version 2 |
| 16 | */ |
| 17 | |
| 18 | #include <linux/mm.h> |
| 19 | #include <linux/suspend.h> |
| 20 | #include <asm/system_misc.h> |
| 21 | #include <asm/idmap.h> |
| 22 | #include <asm/suspend.h> |
| 23 | #include <asm/memory.h> |
| 24 | #include <asm/sections.h> |
| 25 | |
| 26 | int pfn_is_nosave(unsigned long pfn) |
| 27 | { |
| 28 | unsigned long nosave_begin_pfn = virt_to_pfn(&__nosave_begin); |
| 29 | unsigned long nosave_end_pfn = virt_to_pfn(&__nosave_end - 1); |
| 30 | |
| 31 | return (pfn >= nosave_begin_pfn) && (pfn <= nosave_end_pfn); |
| 32 | } |
| 33 | |
| 34 | void notrace save_processor_state(void) |
| 35 | { |
| 36 | WARN_ON(num_online_cpus() != 1); |
| 37 | local_fiq_disable(); |
| 38 | } |
| 39 | |
| 40 | void notrace restore_processor_state(void) |
| 41 | { |
| 42 | local_fiq_enable(); |
| 43 | } |
| 44 | |
| 45 | /* |
| 46 | * Snapshot kernel memory and reset the system. |
| 47 | * |
| 48 | * swsusp_save() is executed in the suspend finisher so that the CPU |
| 49 | * context pointer and memory are part of the saved image, which is |
| 50 | * required by the resume kernel image to restart execution from |
| 51 | * swsusp_arch_suspend(). |
| 52 | * |
| 53 | * soft_restart is not technically needed, but is used to get success |
| 54 | * returned from cpu_suspend. |
| 55 | * |
| 56 | * When soft reboot completes, the hibernation snapshot is written out. |
| 57 | */ |
| 58 | static int notrace arch_save_image(unsigned long unused) |
| 59 | { |
| 60 | int ret; |
| 61 | |
| 62 | ret = swsusp_save(); |
| 63 | if (ret == 0) |
| 64 | soft_restart(virt_to_phys(cpu_resume)); |
| 65 | return ret; |
| 66 | } |
| 67 | |
| 68 | /* |
| 69 | * Save the current CPU state before suspend / poweroff. |
| 70 | */ |
| 71 | int notrace swsusp_arch_suspend(void) |
| 72 | { |
| 73 | return cpu_suspend(0, arch_save_image); |
| 74 | } |
| 75 | |
| 76 | /* |
| 77 | * Restore page contents for physical pages that were in use during loading |
| 78 | * hibernation image. Switch to idmap_pgd so the physical page tables |
| 79 | * are overwritten with the same contents. |
| 80 | */ |
| 81 | static void notrace arch_restore_image(void *unused) |
| 82 | { |
| 83 | struct pbe *pbe; |
| 84 | |
| 85 | cpu_switch_mm(idmap_pgd, &init_mm); |
| 86 | for (pbe = restore_pblist; pbe; pbe = pbe->next) |
| 87 | copy_page(pbe->orig_address, pbe->address); |
| 88 | |
| 89 | soft_restart(virt_to_phys(cpu_resume)); |
| 90 | } |
| 91 | |
| 92 | static u64 resume_stack[PAGE_SIZE/2/sizeof(u64)] __nosavedata; |
| 93 | |
| 94 | /* |
| 95 | * Resume from the hibernation image. |
| 96 | * Due to the kernel heap / data restore, stack contents change underneath |
| 97 | * and that would make function calls impossible; switch to a temporary |
| 98 | * stack within the nosave region to avoid that problem. |
| 99 | */ |
| 100 | int swsusp_arch_resume(void) |
| 101 | { |
| 102 | extern void call_with_stack(void (*fn)(void *), void *arg, void *sp); |
| 103 | call_with_stack(arch_restore_image, 0, |
| 104 | resume_stack + ARRAY_SIZE(resume_stack)); |
| 105 | return 0; |
| 106 | } |