aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2005-10-09 15:19:40 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2005-10-10 11:36:46 -0400
commit3dd083255ddcfa87751fa8e32f61a9547a15a541 (patch)
tree9767ee9d882e57037d8423ea06205f6f0139bfea
parent52a2d3e45e06012a662f627177729d3196ba8903 (diff)
[PATCH] x86_64: Set up safe page tables during resume
The following patch makes swsusp avoid the possible temporary corruption of page translation tables during resume on x86-64. This is achieved by creating a copy of the relevant page tables that will not be modified by swsusp and can be safely used by it on resume. The problem is that during resume on x86-64 swsusp may temporarily corrupt the page tables used for the direct mapping of RAM. If that happens, a page fault occurs and cannot be handled properly, which leads to the solid hang of the affected system. This leads to the loss of the system's state from before suspend and may result in the loss of data or the corruption of filesystems, so it is a serious issue. Also, it appears to happen quite often (for me, as often as 50% of the time). The problem is related to the fact that (at least) one of the PMD entries used in the direct memory mapping (starting at PAGE_OFFSET) points to a page table the physical address of which is much greater than the physical address of the PMD entry itself. Moreover, unfortunately, the physical address of the page table before suspend (i.e. the one stored in the suspend image) happens to be different to the physical address of the corresponding page table used during resume (i.e. the one that is valid right before swsusp_arch_resume() in arch/x86_64/kernel/suspend_asm.S is executed). Thus while the image is restored, the "offending" PMD entry gets overwritten, so it does not point to the right physical address any more (i.e. there's no page table at the address pointed to by it, because it points to the address the page table has been at during suspend). Consequently, if the PMD entry is used later on, and it _is_ used in the process of copying the image pages, a page fault occurs, but it cannot be handled in the normal way and the system hangs. In principle we can call create_resume_mapping() from swsusp_arch_resume() (ie. from suspend_asm.S), but then the memory allocations in create_resume_mapping(), resume_pud_mapping(), and resume_pmd_mapping() must be made carefully so that we use _only_ NosaveFree pages in them (the other pages are overwritten by the loop in swsusp_arch_resume()). Additionally, we are in atomic context at that time, so we cannot use GFP_KERNEL. Moreover, if one of the allocations fails, we should free all of the allocated pages, so we need to trace them somehow. All of this is done in the appended patch, except that the functions populating the page tables are located in arch/x86_64/kernel/suspend.c rather than in init.c. It may be done in a more elegan way in the future, with the help of some swsusp patches that are in the works now. [AK: move some externs into headers, renamed a function] Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Signed-off-by: Andi Kleen <ak@suse.de> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
-rw-r--r--arch/x86_64/kernel/suspend.c127
-rw-r--r--arch/x86_64/kernel/suspend_asm.S17
-rw-r--r--include/linux/suspend.h2
-rw-r--r--kernel/power/swsusp.c7
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
15struct saved_context saved_context; 17struct 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 */
147extern int restore_image(void);
143 148
149pgd_t *temp_level4_pgt;
150
151static void **pages;
152
153static 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
165static 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 */
181static 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
201static 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
229static 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
254int 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
42ENTRY(swsusp_arch_resume) 42ENTRY(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
71done: 72done:
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);
71struct saved_context; 71struct saved_context;
72void __save_processor_state(struct saved_context *ctxt); 72void __save_processor_state(struct saved_context *ctxt);
73void __restore_processor_state(struct saved_context *ctxt); 73void __restore_processor_state(struct saved_context *ctxt);
74extern unsigned long get_usable_page(unsigned gfp_mask);
75extern 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
1098static unsigned long get_usable_page(unsigned gfp_mask) 1098unsigned 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
1112static void free_eaten_memory(void) 1112void 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);