diff options
author | Haavard Skinnemoen <haavard.skinnemoen@atmel.com> | 2009-10-06 11:36:55 -0400 |
---|---|---|
committer | Haavard Skinnemoen <haavard.skinnemoen@atmel.com> | 2009-10-06 11:36:55 -0400 |
commit | d94e5fcbf1420366dcb4102bafe04dbcfc0d0d4b (patch) | |
tree | a9b7de7df6da5c3132cc68169b9c47ba288ccd42 /arch/arm/mm/fault.c | |
parent | d55651168a20078a94597a297d5cdfd807bf07b6 (diff) | |
parent | 374576a8b6f865022c0fd1ca62396889b23d66dd (diff) |
Merge commit 'v2.6.32-rc3'
Diffstat (limited to 'arch/arm/mm/fault.c')
-rw-r--r-- | arch/arm/mm/fault.c | 185 |
1 files changed, 136 insertions, 49 deletions
diff --git a/arch/arm/mm/fault.c b/arch/arm/mm/fault.c index 6fdcbb709827..ae0e25f5a70e 100644 --- a/arch/arm/mm/fault.c +++ b/arch/arm/mm/fault.c | |||
@@ -16,6 +16,8 @@ | |||
16 | #include <linux/kprobes.h> | 16 | #include <linux/kprobes.h> |
17 | #include <linux/uaccess.h> | 17 | #include <linux/uaccess.h> |
18 | #include <linux/page-flags.h> | 18 | #include <linux/page-flags.h> |
19 | #include <linux/sched.h> | ||
20 | #include <linux/highmem.h> | ||
19 | 21 | ||
20 | #include <asm/system.h> | 22 | #include <asm/system.h> |
21 | #include <asm/pgtable.h> | 23 | #include <asm/pgtable.h> |
@@ -23,6 +25,20 @@ | |||
23 | 25 | ||
24 | #include "fault.h" | 26 | #include "fault.h" |
25 | 27 | ||
28 | /* | ||
29 | * Fault status register encodings. We steal bit 31 for our own purposes. | ||
30 | */ | ||
31 | #define FSR_LNX_PF (1 << 31) | ||
32 | #define FSR_WRITE (1 << 11) | ||
33 | #define FSR_FS4 (1 << 10) | ||
34 | #define FSR_FS3_0 (15) | ||
35 | |||
36 | static inline int fsr_fs(unsigned int fsr) | ||
37 | { | ||
38 | return (fsr & FSR_FS3_0) | (fsr & FSR_FS4) >> 6; | ||
39 | } | ||
40 | |||
41 | #ifdef CONFIG_MMU | ||
26 | 42 | ||
27 | #ifdef CONFIG_KPROBES | 43 | #ifdef CONFIG_KPROBES |
28 | static inline int notify_page_fault(struct pt_regs *regs, unsigned int fsr) | 44 | static inline int notify_page_fault(struct pt_regs *regs, unsigned int fsr) |
@@ -97,6 +113,10 @@ void show_pte(struct mm_struct *mm, unsigned long addr) | |||
97 | 113 | ||
98 | printk("\n"); | 114 | printk("\n"); |
99 | } | 115 | } |
116 | #else /* CONFIG_MMU */ | ||
117 | void show_pte(struct mm_struct *mm, unsigned long addr) | ||
118 | { } | ||
119 | #endif /* CONFIG_MMU */ | ||
100 | 120 | ||
101 | /* | 121 | /* |
102 | * Oops. The kernel tried to access some page that wasn't present. | 122 | * Oops. The kernel tried to access some page that wasn't present. |
@@ -171,21 +191,39 @@ void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
171 | __do_kernel_fault(mm, addr, fsr, regs); | 191 | __do_kernel_fault(mm, addr, fsr, regs); |
172 | } | 192 | } |
173 | 193 | ||
194 | #ifdef CONFIG_MMU | ||
174 | #define VM_FAULT_BADMAP 0x010000 | 195 | #define VM_FAULT_BADMAP 0x010000 |
175 | #define VM_FAULT_BADACCESS 0x020000 | 196 | #define VM_FAULT_BADACCESS 0x020000 |
176 | 197 | ||
177 | static int | 198 | /* |
199 | * Check that the permissions on the VMA allow for the fault which occurred. | ||
200 | * If we encountered a write fault, we must have write permission, otherwise | ||
201 | * we allow any permission. | ||
202 | */ | ||
203 | static inline bool access_error(unsigned int fsr, struct vm_area_struct *vma) | ||
204 | { | ||
205 | unsigned int mask = VM_READ | VM_WRITE | VM_EXEC; | ||
206 | |||
207 | if (fsr & FSR_WRITE) | ||
208 | mask = VM_WRITE; | ||
209 | if (fsr & FSR_LNX_PF) | ||
210 | mask = VM_EXEC; | ||
211 | |||
212 | return vma->vm_flags & mask ? false : true; | ||
213 | } | ||
214 | |||
215 | static int __kprobes | ||
178 | __do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr, | 216 | __do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr, |
179 | struct task_struct *tsk) | 217 | struct task_struct *tsk) |
180 | { | 218 | { |
181 | struct vm_area_struct *vma; | 219 | struct vm_area_struct *vma; |
182 | int fault, mask; | 220 | int fault; |
183 | 221 | ||
184 | vma = find_vma(mm, addr); | 222 | vma = find_vma(mm, addr); |
185 | fault = VM_FAULT_BADMAP; | 223 | fault = VM_FAULT_BADMAP; |
186 | if (!vma) | 224 | if (unlikely(!vma)) |
187 | goto out; | 225 | goto out; |
188 | if (vma->vm_start > addr) | 226 | if (unlikely(vma->vm_start > addr)) |
189 | goto check_stack; | 227 | goto check_stack; |
190 | 228 | ||
191 | /* | 229 | /* |
@@ -193,47 +231,24 @@ __do_page_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr, | |||
193 | * memory access, so we can handle it. | 231 | * memory access, so we can handle it. |
194 | */ | 232 | */ |
195 | good_area: | 233 | good_area: |
196 | if (fsr & (1 << 11)) /* write? */ | 234 | if (access_error(fsr, vma)) { |
197 | mask = VM_WRITE; | 235 | fault = VM_FAULT_BADACCESS; |
198 | else | ||
199 | mask = VM_READ|VM_EXEC|VM_WRITE; | ||
200 | |||
201 | fault = VM_FAULT_BADACCESS; | ||
202 | if (!(vma->vm_flags & mask)) | ||
203 | goto out; | 236 | goto out; |
237 | } | ||
204 | 238 | ||
205 | /* | 239 | /* |
206 | * If for any reason at all we couldn't handle | 240 | * If for any reason at all we couldn't handle the fault, make |
207 | * the fault, make sure we exit gracefully rather | 241 | * sure we exit gracefully rather than endlessly redo the fault. |
208 | * than endlessly redo the fault. | ||
209 | */ | 242 | */ |
210 | survive: | 243 | fault = handle_mm_fault(mm, vma, addr & PAGE_MASK, (fsr & FSR_WRITE) ? FAULT_FLAG_WRITE : 0); |
211 | fault = handle_mm_fault(mm, vma, addr & PAGE_MASK, (fsr & (1 << 11)) ? FAULT_FLAG_WRITE : 0); | 244 | if (unlikely(fault & VM_FAULT_ERROR)) |
212 | if (unlikely(fault & VM_FAULT_ERROR)) { | 245 | return fault; |
213 | if (fault & VM_FAULT_OOM) | ||
214 | goto out_of_memory; | ||
215 | else if (fault & VM_FAULT_SIGBUS) | ||
216 | return fault; | ||
217 | BUG(); | ||
218 | } | ||
219 | if (fault & VM_FAULT_MAJOR) | 246 | if (fault & VM_FAULT_MAJOR) |
220 | tsk->maj_flt++; | 247 | tsk->maj_flt++; |
221 | else | 248 | else |
222 | tsk->min_flt++; | 249 | tsk->min_flt++; |
223 | return fault; | 250 | return fault; |
224 | 251 | ||
225 | out_of_memory: | ||
226 | if (!is_global_init(tsk)) | ||
227 | goto out; | ||
228 | |||
229 | /* | ||
230 | * If we are out of memory for pid1, sleep for a while and retry | ||
231 | */ | ||
232 | up_read(&mm->mmap_sem); | ||
233 | yield(); | ||
234 | down_read(&mm->mmap_sem); | ||
235 | goto survive; | ||
236 | |||
237 | check_stack: | 252 | check_stack: |
238 | if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr)) | 253 | if (vma->vm_flags & VM_GROWSDOWN && !expand_stack(vma, addr)) |
239 | goto good_area; | 254 | goto good_area; |
@@ -270,6 +285,13 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
270 | if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc)) | 285 | if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc)) |
271 | goto no_context; | 286 | goto no_context; |
272 | down_read(&mm->mmap_sem); | 287 | down_read(&mm->mmap_sem); |
288 | } else { | ||
289 | /* | ||
290 | * The above down_read_trylock() might have succeeded in | ||
291 | * which case, we'll have missed the might_sleep() from | ||
292 | * down_read() | ||
293 | */ | ||
294 | might_sleep(); | ||
273 | } | 295 | } |
274 | 296 | ||
275 | fault = __do_page_fault(mm, addr, fsr, tsk); | 297 | fault = __do_page_fault(mm, addr, fsr, tsk); |
@@ -281,6 +303,16 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
281 | if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP | VM_FAULT_BADACCESS)))) | 303 | if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP | VM_FAULT_BADACCESS)))) |
282 | return 0; | 304 | return 0; |
283 | 305 | ||
306 | if (fault & VM_FAULT_OOM) { | ||
307 | /* | ||
308 | * We ran out of memory, call the OOM killer, and return to | ||
309 | * userspace (which will retry the fault, or kill us if we | ||
310 | * got oom-killed) | ||
311 | */ | ||
312 | pagefault_out_of_memory(); | ||
313 | return 0; | ||
314 | } | ||
315 | |||
284 | /* | 316 | /* |
285 | * If we are in kernel mode at this point, we | 317 | * If we are in kernel mode at this point, we |
286 | * have no context to handle this fault with. | 318 | * have no context to handle this fault with. |
@@ -288,16 +320,6 @@ do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
288 | if (!user_mode(regs)) | 320 | if (!user_mode(regs)) |
289 | goto no_context; | 321 | goto no_context; |
290 | 322 | ||
291 | if (fault & VM_FAULT_OOM) { | ||
292 | /* | ||
293 | * We ran out of memory, or some other thing | ||
294 | * happened to us that made us unable to handle | ||
295 | * the page fault gracefully. | ||
296 | */ | ||
297 | printk("VM: killing process %s\n", tsk->comm); | ||
298 | do_group_exit(SIGKILL); | ||
299 | return 0; | ||
300 | } | ||
301 | if (fault & VM_FAULT_SIGBUS) { | 323 | if (fault & VM_FAULT_SIGBUS) { |
302 | /* | 324 | /* |
303 | * We had some memory, but were unable to | 325 | * We had some memory, but were unable to |
@@ -322,6 +344,13 @@ no_context: | |||
322 | __do_kernel_fault(mm, addr, fsr, regs); | 344 | __do_kernel_fault(mm, addr, fsr, regs); |
323 | return 0; | 345 | return 0; |
324 | } | 346 | } |
347 | #else /* CONFIG_MMU */ | ||
348 | static int | ||
349 | do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | ||
350 | { | ||
351 | return 0; | ||
352 | } | ||
353 | #endif /* CONFIG_MMU */ | ||
325 | 354 | ||
326 | /* | 355 | /* |
327 | * First Level Translation Fault Handler | 356 | * First Level Translation Fault Handler |
@@ -340,6 +369,7 @@ no_context: | |||
340 | * interrupt or a critical region, and should only copy the information | 369 | * interrupt or a critical region, and should only copy the information |
341 | * from the master page table, nothing more. | 370 | * from the master page table, nothing more. |
342 | */ | 371 | */ |
372 | #ifdef CONFIG_MMU | ||
343 | static int __kprobes | 373 | static int __kprobes |
344 | do_translation_fault(unsigned long addr, unsigned int fsr, | 374 | do_translation_fault(unsigned long addr, unsigned int fsr, |
345 | struct pt_regs *regs) | 375 | struct pt_regs *regs) |
@@ -378,6 +408,14 @@ bad_area: | |||
378 | do_bad_area(addr, fsr, regs); | 408 | do_bad_area(addr, fsr, regs); |
379 | return 0; | 409 | return 0; |
380 | } | 410 | } |
411 | #else /* CONFIG_MMU */ | ||
412 | static int | ||
413 | do_translation_fault(unsigned long addr, unsigned int fsr, | ||
414 | struct pt_regs *regs) | ||
415 | { | ||
416 | return 0; | ||
417 | } | ||
418 | #endif /* CONFIG_MMU */ | ||
381 | 419 | ||
382 | /* | 420 | /* |
383 | * Some section permission faults need to be handled gracefully. | 421 | * Some section permission faults need to be handled gracefully. |
@@ -465,10 +503,10 @@ hook_fault_code(int nr, int (*fn)(unsigned long, unsigned int, struct pt_regs *) | |||
465 | asmlinkage void __exception | 503 | asmlinkage void __exception |
466 | do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | 504 | do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) |
467 | { | 505 | { |
468 | const struct fsr_info *inf = fsr_info + (fsr & 15) + ((fsr & (1 << 10)) >> 6); | 506 | const struct fsr_info *inf = fsr_info + fsr_fs(fsr); |
469 | struct siginfo info; | 507 | struct siginfo info; |
470 | 508 | ||
471 | if (!inf->fn(addr, fsr, regs)) | 509 | if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs)) |
472 | return; | 510 | return; |
473 | 511 | ||
474 | printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n", | 512 | printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n", |
@@ -481,9 +519,58 @@ do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs) | |||
481 | arm_notify_die("", regs, &info, fsr, 0); | 519 | arm_notify_die("", regs, &info, fsr, 0); |
482 | } | 520 | } |
483 | 521 | ||
522 | |||
523 | static struct fsr_info ifsr_info[] = { | ||
524 | { do_bad, SIGBUS, 0, "unknown 0" }, | ||
525 | { do_bad, SIGBUS, 0, "unknown 1" }, | ||
526 | { do_bad, SIGBUS, 0, "debug event" }, | ||
527 | { do_bad, SIGSEGV, SEGV_ACCERR, "section access flag fault" }, | ||
528 | { do_bad, SIGBUS, 0, "unknown 4" }, | ||
529 | { do_translation_fault, SIGSEGV, SEGV_MAPERR, "section translation fault" }, | ||
530 | { do_bad, SIGSEGV, SEGV_ACCERR, "page access flag fault" }, | ||
531 | { do_page_fault, SIGSEGV, SEGV_MAPERR, "page translation fault" }, | ||
532 | { do_bad, SIGBUS, 0, "external abort on non-linefetch" }, | ||
533 | { do_bad, SIGSEGV, SEGV_ACCERR, "section domain fault" }, | ||
534 | { do_bad, SIGBUS, 0, "unknown 10" }, | ||
535 | { do_bad, SIGSEGV, SEGV_ACCERR, "page domain fault" }, | ||
536 | { do_bad, SIGBUS, 0, "external abort on translation" }, | ||
537 | { do_sect_fault, SIGSEGV, SEGV_ACCERR, "section permission fault" }, | ||
538 | { do_bad, SIGBUS, 0, "external abort on translation" }, | ||
539 | { do_page_fault, SIGSEGV, SEGV_ACCERR, "page permission fault" }, | ||
540 | { do_bad, SIGBUS, 0, "unknown 16" }, | ||
541 | { do_bad, SIGBUS, 0, "unknown 17" }, | ||
542 | { do_bad, SIGBUS, 0, "unknown 18" }, | ||
543 | { do_bad, SIGBUS, 0, "unknown 19" }, | ||
544 | { do_bad, SIGBUS, 0, "unknown 20" }, | ||
545 | { do_bad, SIGBUS, 0, "unknown 21" }, | ||
546 | { do_bad, SIGBUS, 0, "unknown 22" }, | ||
547 | { do_bad, SIGBUS, 0, "unknown 23" }, | ||
548 | { do_bad, SIGBUS, 0, "unknown 24" }, | ||
549 | { do_bad, SIGBUS, 0, "unknown 25" }, | ||
550 | { do_bad, SIGBUS, 0, "unknown 26" }, | ||
551 | { do_bad, SIGBUS, 0, "unknown 27" }, | ||
552 | { do_bad, SIGBUS, 0, "unknown 28" }, | ||
553 | { do_bad, SIGBUS, 0, "unknown 29" }, | ||
554 | { do_bad, SIGBUS, 0, "unknown 30" }, | ||
555 | { do_bad, SIGBUS, 0, "unknown 31" }, | ||
556 | }; | ||
557 | |||
484 | asmlinkage void __exception | 558 | asmlinkage void __exception |
485 | do_PrefetchAbort(unsigned long addr, struct pt_regs *regs) | 559 | do_PrefetchAbort(unsigned long addr, unsigned int ifsr, struct pt_regs *regs) |
486 | { | 560 | { |
487 | do_translation_fault(addr, 0, regs); | 561 | const struct fsr_info *inf = ifsr_info + fsr_fs(ifsr); |
562 | struct siginfo info; | ||
563 | |||
564 | if (!inf->fn(addr, ifsr | FSR_LNX_PF, regs)) | ||
565 | return; | ||
566 | |||
567 | printk(KERN_ALERT "Unhandled prefetch abort: %s (0x%03x) at 0x%08lx\n", | ||
568 | inf->name, ifsr, addr); | ||
569 | |||
570 | info.si_signo = inf->sig; | ||
571 | info.si_errno = 0; | ||
572 | info.si_code = inf->code; | ||
573 | info.si_addr = (void __user *)addr; | ||
574 | arm_notify_die("", regs, &info, ifsr, 0); | ||
488 | } | 575 | } |
489 | 576 | ||