diff options
| -rw-r--r-- | arch/x86_64/kernel/suspend.c | 127 | ||||
| -rw-r--r-- | arch/x86_64/kernel/suspend_asm.S | 17 | ||||
| -rw-r--r-- | include/linux/suspend.h | 2 | ||||
| -rw-r--r-- | kernel/power/swsusp.c | 7 |
4 files changed, 144 insertions, 9 deletions
diff --git a/arch/x86_64/kernel/suspend.c b/arch/x86_64/kernel/suspend.c index ebb9abf3ce6d..f066c6ab3618 100644 --- a/arch/x86_64/kernel/suspend.c +++ b/arch/x86_64/kernel/suspend.c | |||
| @@ -11,6 +11,8 @@ | |||
| 11 | #include <linux/smp.h> | 11 | #include <linux/smp.h> |
| 12 | #include <linux/suspend.h> | 12 | #include <linux/suspend.h> |
| 13 | #include <asm/proto.h> | 13 | #include <asm/proto.h> |
| 14 | #include <asm/page.h> | ||
| 15 | #include <asm/pgtable.h> | ||
| 14 | 16 | ||
| 15 | struct saved_context saved_context; | 17 | struct saved_context saved_context; |
| 16 | 18 | ||
| @@ -140,4 +142,129 @@ void fix_processor_context(void) | |||
| 140 | 142 | ||
| 141 | } | 143 | } |
| 142 | 144 | ||
| 145 | #ifdef CONFIG_SOFTWARE_SUSPEND | ||
| 146 | /* Defined in arch/x86_64/kernel/suspend_asm.S */ | ||
| 147 | extern int restore_image(void); | ||
| 143 | 148 | ||
| 149 | pgd_t *temp_level4_pgt; | ||
| 150 | |||
| 151 | static void **pages; | ||
| 152 | |||
| 153 | static inline void *__add_page(void) | ||
| 154 | { | ||
| 155 | void **c; | ||
| 156 | |||
| 157 | c = (void **)get_usable_page(GFP_ATOMIC); | ||
| 158 | if (c) { | ||
| 159 | *c = pages; | ||
| 160 | pages = c; | ||
| 161 | } | ||
| 162 | return c; | ||
| 163 | } | ||
| 164 | |||
| 165 | static inline void *__next_page(void) | ||
| 166 | { | ||
| 167 | void **c; | ||
| 168 | |||
| 169 | c = pages; | ||
| 170 | if (c) { | ||
| 171 | pages = *c; | ||
| 172 | *c = NULL; | ||
| 173 | } | ||
| 174 | return c; | ||
| 175 | } | ||
| 176 | |||
| 177 | /* | ||
| 178 | * Try to allocate as many usable pages as needed and daisy chain them. | ||
| 179 | * If one allocation fails, free the pages allocated so far | ||
| 180 | */ | ||
| 181 | static int alloc_usable_pages(unsigned long n) | ||
| 182 | { | ||
| 183 | void *p; | ||
| 184 | |||
| 185 | pages = NULL; | ||
| 186 | do | ||
| 187 | if (!__add_page()) | ||
| 188 | break; | ||
| 189 | while (--n); | ||
| 190 | if (n) { | ||
| 191 | p = __next_page(); | ||
| 192 | while (p) { | ||
| 193 | free_page((unsigned long)p); | ||
| 194 | p = __next_page(); | ||
| 195 | } | ||
| 196 | return -ENOMEM; | ||
| 197 | } | ||
| 198 | return 0; | ||
| 199 | } | ||
| 200 | |||
| 201 | static void res_phys_pud_init(pud_t *pud, unsigned long address, unsigned long end) | ||
| 202 | { | ||
| 203 | long i, j; | ||
| 204 | |||
| 205 | i = pud_index(address); | ||
| 206 | pud = pud + i; | ||
| 207 | for (; i < PTRS_PER_PUD; pud++, i++) { | ||
| 208 | unsigned long paddr; | ||
| 209 | pmd_t *pmd; | ||
| 210 | |||
| 211 | paddr = address + i*PUD_SIZE; | ||
| 212 | if (paddr >= end) | ||
| 213 | break; | ||
| 214 | |||
| 215 | pmd = (pmd_t *)__next_page(); | ||
| 216 | set_pud(pud, __pud(__pa(pmd) | _KERNPG_TABLE)); | ||
| 217 | for (j = 0; j < PTRS_PER_PMD; pmd++, j++, paddr += PMD_SIZE) { | ||
| 218 | unsigned long pe; | ||
| 219 | |||
| 220 | if (paddr >= end) | ||
| 221 | break; | ||
| 222 | pe = _PAGE_NX | _PAGE_PSE | _KERNPG_TABLE | paddr; | ||
| 223 | pe &= __supported_pte_mask; | ||
| 224 | set_pmd(pmd, __pmd(pe)); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | static void set_up_temporary_mappings(void) | ||
| 230 | { | ||
| 231 | unsigned long start, end, next; | ||
| 232 | |||
| 233 | temp_level4_pgt = (pgd_t *)__next_page(); | ||
| 234 | |||
| 235 | /* It is safe to reuse the original kernel mapping */ | ||
| 236 | set_pgd(temp_level4_pgt + pgd_index(__START_KERNEL_map), | ||
| 237 | init_level4_pgt[pgd_index(__START_KERNEL_map)]); | ||
| 238 | |||
| 239 | /* Set up the direct mapping from scratch */ | ||
| 240 | start = (unsigned long)pfn_to_kaddr(0); | ||
| 241 | end = (unsigned long)pfn_to_kaddr(end_pfn); | ||
| 242 | |||
| 243 | for (; start < end; start = next) { | ||
| 244 | pud_t *pud = (pud_t *)__next_page(); | ||
| 245 | next = start + PGDIR_SIZE; | ||
| 246 | if (next > end) | ||
| 247 | next = end; | ||
| 248 | res_phys_pud_init(pud, __pa(start), __pa(next)); | ||
| 249 | set_pgd(temp_level4_pgt + pgd_index(start), | ||
| 250 | mk_kernel_pgd(__pa(pud))); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | |||
| 254 | int swsusp_arch_resume(void) | ||
| 255 | { | ||
| 256 | unsigned long n; | ||
| 257 | |||
| 258 | n = ((end_pfn << PAGE_SHIFT) + PUD_SIZE - 1) >> PUD_SHIFT; | ||
| 259 | n += (n + PTRS_PER_PUD - 1) / PTRS_PER_PUD + 1; | ||
| 260 | pr_debug("swsusp_arch_resume(): pages needed = %lu\n", n); | ||
| 261 | if (alloc_usable_pages(n)) { | ||
| 262 | free_eaten_memory(); | ||
| 263 | return -ENOMEM; | ||
| 264 | } | ||
| 265 | /* We have got enough memory and from now on we cannot recover */ | ||
| 266 | set_up_temporary_mappings(); | ||
| 267 | restore_image(); | ||
| 268 | return 0; | ||
| 269 | } | ||
| 270 | #endif /* CONFIG_SOFTWARE_SUSPEND */ | ||
diff --git a/arch/x86_64/kernel/suspend_asm.S b/arch/x86_64/kernel/suspend_asm.S index 4d659e97df10..320b6fb00cca 100644 --- a/arch/x86_64/kernel/suspend_asm.S +++ b/arch/x86_64/kernel/suspend_asm.S | |||
| @@ -39,12 +39,13 @@ ENTRY(swsusp_arch_suspend) | |||
| 39 | call swsusp_save | 39 | call swsusp_save |
| 40 | ret | 40 | ret |
| 41 | 41 | ||
| 42 | ENTRY(swsusp_arch_resume) | 42 | ENTRY(restore_image) |
| 43 | /* set up cr3 */ | 43 | /* switch to temporary page tables */ |
| 44 | leaq init_level4_pgt(%rip),%rax | 44 | movq $__PAGE_OFFSET, %rdx |
| 45 | subq $__START_KERNEL_map,%rax | 45 | movq temp_level4_pgt(%rip), %rax |
| 46 | movq %rax,%cr3 | 46 | subq %rdx, %rax |
| 47 | 47 | movq %rax, %cr3 | |
| 48 | /* Flush TLB */ | ||
| 48 | movq mmu_cr4_features(%rip), %rax | 49 | movq mmu_cr4_features(%rip), %rax |
| 49 | movq %rax, %rdx | 50 | movq %rax, %rdx |
| 50 | andq $~(1<<7), %rdx # PGE | 51 | andq $~(1<<7), %rdx # PGE |
| @@ -69,6 +70,10 @@ loop: | |||
| 69 | movq pbe_next(%rdx), %rdx | 70 | movq pbe_next(%rdx), %rdx |
| 70 | jmp loop | 71 | jmp loop |
| 71 | done: | 72 | done: |
| 73 | /* go back to the original page tables */ | ||
| 74 | leaq init_level4_pgt(%rip), %rax | ||
| 75 | subq $__START_KERNEL_map, %rax | ||
| 76 | movq %rax, %cr3 | ||
| 72 | /* Flush TLB, including "global" things (vmalloc) */ | 77 | /* Flush TLB, including "global" things (vmalloc) */ |
| 73 | movq mmu_cr4_features(%rip), %rax | 78 | movq mmu_cr4_features(%rip), %rax |
| 74 | movq %rax, %rdx | 79 | movq %rax, %rdx |
diff --git a/include/linux/suspend.h b/include/linux/suspend.h index f2e96fdfaae0..ad15a54806d8 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h | |||
| @@ -71,5 +71,7 @@ void restore_processor_state(void); | |||
| 71 | struct saved_context; | 71 | struct saved_context; |
| 72 | void __save_processor_state(struct saved_context *ctxt); | 72 | void __save_processor_state(struct saved_context *ctxt); |
| 73 | void __restore_processor_state(struct saved_context *ctxt); | 73 | void __restore_processor_state(struct saved_context *ctxt); |
| 74 | extern unsigned long get_usable_page(unsigned gfp_mask); | ||
| 75 | extern void free_eaten_memory(void); | ||
| 74 | 76 | ||
| 75 | #endif /* _LINUX_SWSUSP_H */ | 77 | #endif /* _LINUX_SWSUSP_H */ |
diff --git a/kernel/power/swsusp.c b/kernel/power/swsusp.c index acf79ac1cb6d..2d5c45676442 100644 --- a/kernel/power/swsusp.c +++ b/kernel/power/swsusp.c | |||
| @@ -1095,7 +1095,7 @@ static inline void eat_page(void *page) | |||
| 1095 | *eaten_memory = c; | 1095 | *eaten_memory = c; |
| 1096 | } | 1096 | } |
| 1097 | 1097 | ||
| 1098 | static unsigned long get_usable_page(unsigned gfp_mask) | 1098 | unsigned long get_usable_page(unsigned gfp_mask) |
| 1099 | { | 1099 | { |
| 1100 | unsigned long m; | 1100 | unsigned long m; |
| 1101 | 1101 | ||
| @@ -1109,7 +1109,7 @@ static unsigned long get_usable_page(unsigned gfp_mask) | |||
| 1109 | return m; | 1109 | return m; |
| 1110 | } | 1110 | } |
| 1111 | 1111 | ||
| 1112 | static void free_eaten_memory(void) | 1112 | void free_eaten_memory(void) |
| 1113 | { | 1113 | { |
| 1114 | unsigned long m; | 1114 | unsigned long m; |
| 1115 | void **c; | 1115 | void **c; |
| @@ -1481,11 +1481,12 @@ static int read_suspend_image(void) | |||
| 1481 | /* Allocate memory for the image and read the data from swap */ | 1481 | /* Allocate memory for the image and read the data from swap */ |
| 1482 | 1482 | ||
| 1483 | error = check_pagedir(pagedir_nosave); | 1483 | error = check_pagedir(pagedir_nosave); |
| 1484 | free_eaten_memory(); | 1484 | |
| 1485 | if (!error) | 1485 | if (!error) |
| 1486 | error = data_read(pagedir_nosave); | 1486 | error = data_read(pagedir_nosave); |
| 1487 | 1487 | ||
| 1488 | if (error) { /* We fail cleanly */ | 1488 | if (error) { /* We fail cleanly */ |
| 1489 | free_eaten_memory(); | ||
| 1489 | for_each_pbe (p, pagedir_nosave) | 1490 | for_each_pbe (p, pagedir_nosave) |
| 1490 | if (p->address) { | 1491 | if (p->address) { |
| 1491 | free_page(p->address); | 1492 | free_page(p->address); |
