diff options
Diffstat (limited to 'arch/s390/mm/fault.c')
-rw-r--r-- | arch/s390/mm/fault.c | 77 |
1 files changed, 46 insertions, 31 deletions
diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c index 2505b2ea0ef1..fe5701e9efbf 100644 --- a/arch/s390/mm/fault.c +++ b/arch/s390/mm/fault.c | |||
@@ -52,6 +52,14 @@ | |||
52 | #define VM_FAULT_BADMAP 0x020000 | 52 | #define VM_FAULT_BADMAP 0x020000 |
53 | #define VM_FAULT_BADACCESS 0x040000 | 53 | #define VM_FAULT_BADACCESS 0x040000 |
54 | 54 | ||
55 | static unsigned long store_indication; | ||
56 | |||
57 | void fault_init(void) | ||
58 | { | ||
59 | if (test_facility(2) && test_facility(75)) | ||
60 | store_indication = 0xc00; | ||
61 | } | ||
62 | |||
55 | static inline int notify_page_fault(struct pt_regs *regs) | 63 | static inline int notify_page_fault(struct pt_regs *regs) |
56 | { | 64 | { |
57 | int ret = 0; | 65 | int ret = 0; |
@@ -199,14 +207,21 @@ static noinline void do_sigbus(struct pt_regs *regs, long int_code, | |||
199 | unsigned long trans_exc_code) | 207 | unsigned long trans_exc_code) |
200 | { | 208 | { |
201 | struct task_struct *tsk = current; | 209 | struct task_struct *tsk = current; |
210 | unsigned long address; | ||
211 | struct siginfo si; | ||
202 | 212 | ||
203 | /* | 213 | /* |
204 | * Send a sigbus, regardless of whether we were in kernel | 214 | * Send a sigbus, regardless of whether we were in kernel |
205 | * or user mode. | 215 | * or user mode. |
206 | */ | 216 | */ |
207 | tsk->thread.prot_addr = trans_exc_code & __FAIL_ADDR_MASK; | 217 | address = trans_exc_code & __FAIL_ADDR_MASK; |
218 | tsk->thread.prot_addr = address; | ||
208 | tsk->thread.trap_no = int_code; | 219 | tsk->thread.trap_no = int_code; |
209 | force_sig(SIGBUS, tsk); | 220 | si.si_signo = SIGBUS; |
221 | si.si_errno = 0; | ||
222 | si.si_code = BUS_ADRERR; | ||
223 | si.si_addr = (void __user *) address; | ||
224 | force_sig_info(SIGBUS, &si, tsk); | ||
210 | } | 225 | } |
211 | 226 | ||
212 | #ifdef CONFIG_S390_EXEC_PROTECT | 227 | #ifdef CONFIG_S390_EXEC_PROTECT |
@@ -266,10 +281,11 @@ static noinline void do_fault_error(struct pt_regs *regs, long int_code, | |||
266 | if (fault & VM_FAULT_OOM) | 281 | if (fault & VM_FAULT_OOM) |
267 | pagefault_out_of_memory(); | 282 | pagefault_out_of_memory(); |
268 | else if (fault & VM_FAULT_SIGBUS) { | 283 | else if (fault & VM_FAULT_SIGBUS) { |
269 | do_sigbus(regs, int_code, trans_exc_code); | ||
270 | /* Kernel mode? Handle exceptions or die */ | 284 | /* Kernel mode? Handle exceptions or die */ |
271 | if (!(regs->psw.mask & PSW_MASK_PSTATE)) | 285 | if (!(regs->psw.mask & PSW_MASK_PSTATE)) |
272 | do_no_context(regs, int_code, trans_exc_code); | 286 | do_no_context(regs, int_code, trans_exc_code); |
287 | else | ||
288 | do_sigbus(regs, int_code, trans_exc_code); | ||
273 | } else | 289 | } else |
274 | BUG(); | 290 | BUG(); |
275 | break; | 291 | break; |
@@ -294,7 +310,7 @@ static inline int do_exception(struct pt_regs *regs, int access, | |||
294 | struct mm_struct *mm; | 310 | struct mm_struct *mm; |
295 | struct vm_area_struct *vma; | 311 | struct vm_area_struct *vma; |
296 | unsigned long address; | 312 | unsigned long address; |
297 | int fault; | 313 | int fault, write; |
298 | 314 | ||
299 | if (notify_page_fault(regs)) | 315 | if (notify_page_fault(regs)) |
300 | return 0; | 316 | return 0; |
@@ -312,12 +328,6 @@ static inline int do_exception(struct pt_regs *regs, int access, | |||
312 | goto out; | 328 | goto out; |
313 | 329 | ||
314 | address = trans_exc_code & __FAIL_ADDR_MASK; | 330 | address = trans_exc_code & __FAIL_ADDR_MASK; |
315 | /* | ||
316 | * When we get here, the fault happened in the current | ||
317 | * task's user address space, so we can switch on the | ||
318 | * interrupts again and then search the VMAs | ||
319 | */ | ||
320 | local_irq_enable(); | ||
321 | perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, 0, regs, address); | 331 | perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, 0, regs, address); |
322 | down_read(&mm->mmap_sem); | 332 | down_read(&mm->mmap_sem); |
323 | 333 | ||
@@ -348,8 +358,10 @@ static inline int do_exception(struct pt_regs *regs, int access, | |||
348 | * make sure we exit gracefully rather than endlessly redo | 358 | * make sure we exit gracefully rather than endlessly redo |
349 | * the fault. | 359 | * the fault. |
350 | */ | 360 | */ |
351 | fault = handle_mm_fault(mm, vma, address, | 361 | write = (access == VM_WRITE || |
352 | (access == VM_WRITE) ? FAULT_FLAG_WRITE : 0); | 362 | (trans_exc_code & store_indication) == 0x400) ? |
363 | FAULT_FLAG_WRITE : 0; | ||
364 | fault = handle_mm_fault(mm, vma, address, write); | ||
353 | if (unlikely(fault & VM_FAULT_ERROR)) | 365 | if (unlikely(fault & VM_FAULT_ERROR)) |
354 | goto out_up; | 366 | goto out_up; |
355 | 367 | ||
@@ -374,20 +386,20 @@ out: | |||
374 | return fault; | 386 | return fault; |
375 | } | 387 | } |
376 | 388 | ||
377 | void __kprobes do_protection_exception(struct pt_regs *regs, long int_code) | 389 | void __kprobes do_protection_exception(struct pt_regs *regs, long pgm_int_code, |
390 | unsigned long trans_exc_code) | ||
378 | { | 391 | { |
379 | unsigned long trans_exc_code = S390_lowcore.trans_exc_code; | ||
380 | int fault; | 392 | int fault; |
381 | 393 | ||
382 | /* Protection exception is supressing, decrement psw address. */ | 394 | /* Protection exception is supressing, decrement psw address. */ |
383 | regs->psw.addr -= (int_code >> 16); | 395 | regs->psw.addr -= (pgm_int_code >> 16); |
384 | /* | 396 | /* |
385 | * Check for low-address protection. This needs to be treated | 397 | * Check for low-address protection. This needs to be treated |
386 | * as a special case because the translation exception code | 398 | * as a special case because the translation exception code |
387 | * field is not guaranteed to contain valid data in this case. | 399 | * field is not guaranteed to contain valid data in this case. |
388 | */ | 400 | */ |
389 | if (unlikely(!(trans_exc_code & 4))) { | 401 | if (unlikely(!(trans_exc_code & 4))) { |
390 | do_low_address(regs, int_code, trans_exc_code); | 402 | do_low_address(regs, pgm_int_code, trans_exc_code); |
391 | return; | 403 | return; |
392 | } | 404 | } |
393 | fault = do_exception(regs, VM_WRITE, trans_exc_code); | 405 | fault = do_exception(regs, VM_WRITE, trans_exc_code); |
@@ -395,9 +407,9 @@ void __kprobes do_protection_exception(struct pt_regs *regs, long int_code) | |||
395 | do_fault_error(regs, 4, trans_exc_code, fault); | 407 | do_fault_error(regs, 4, trans_exc_code, fault); |
396 | } | 408 | } |
397 | 409 | ||
398 | void __kprobes do_dat_exception(struct pt_regs *regs, long int_code) | 410 | void __kprobes do_dat_exception(struct pt_regs *regs, long pgm_int_code, |
411 | unsigned long trans_exc_code) | ||
399 | { | 412 | { |
400 | unsigned long trans_exc_code = S390_lowcore.trans_exc_code; | ||
401 | int access, fault; | 413 | int access, fault; |
402 | 414 | ||
403 | access = VM_READ | VM_EXEC | VM_WRITE; | 415 | access = VM_READ | VM_EXEC | VM_WRITE; |
@@ -408,21 +420,19 @@ void __kprobes do_dat_exception(struct pt_regs *regs, long int_code) | |||
408 | #endif | 420 | #endif |
409 | fault = do_exception(regs, access, trans_exc_code); | 421 | fault = do_exception(regs, access, trans_exc_code); |
410 | if (unlikely(fault)) | 422 | if (unlikely(fault)) |
411 | do_fault_error(regs, int_code & 255, trans_exc_code, fault); | 423 | do_fault_error(regs, pgm_int_code & 255, trans_exc_code, fault); |
412 | } | 424 | } |
413 | 425 | ||
414 | #ifdef CONFIG_64BIT | 426 | #ifdef CONFIG_64BIT |
415 | void __kprobes do_asce_exception(struct pt_regs *regs, long int_code) | 427 | void __kprobes do_asce_exception(struct pt_regs *regs, long pgm_int_code, |
428 | unsigned long trans_exc_code) | ||
416 | { | 429 | { |
417 | unsigned long trans_exc_code = S390_lowcore.trans_exc_code; | ||
418 | struct mm_struct *mm = current->mm; | 430 | struct mm_struct *mm = current->mm; |
419 | struct vm_area_struct *vma; | 431 | struct vm_area_struct *vma; |
420 | 432 | ||
421 | if (unlikely(!user_space_fault(trans_exc_code) || in_atomic() || !mm)) | 433 | if (unlikely(!user_space_fault(trans_exc_code) || in_atomic() || !mm)) |
422 | goto no_context; | 434 | goto no_context; |
423 | 435 | ||
424 | local_irq_enable(); | ||
425 | |||
426 | down_read(&mm->mmap_sem); | 436 | down_read(&mm->mmap_sem); |
427 | vma = find_vma(mm, trans_exc_code & __FAIL_ADDR_MASK); | 437 | vma = find_vma(mm, trans_exc_code & __FAIL_ADDR_MASK); |
428 | up_read(&mm->mmap_sem); | 438 | up_read(&mm->mmap_sem); |
@@ -434,16 +444,16 @@ void __kprobes do_asce_exception(struct pt_regs *regs, long int_code) | |||
434 | 444 | ||
435 | /* User mode accesses just cause a SIGSEGV */ | 445 | /* User mode accesses just cause a SIGSEGV */ |
436 | if (regs->psw.mask & PSW_MASK_PSTATE) { | 446 | if (regs->psw.mask & PSW_MASK_PSTATE) { |
437 | do_sigsegv(regs, int_code, SEGV_MAPERR, trans_exc_code); | 447 | do_sigsegv(regs, pgm_int_code, SEGV_MAPERR, trans_exc_code); |
438 | return; | 448 | return; |
439 | } | 449 | } |
440 | 450 | ||
441 | no_context: | 451 | no_context: |
442 | do_no_context(regs, int_code, trans_exc_code); | 452 | do_no_context(regs, pgm_int_code, trans_exc_code); |
443 | } | 453 | } |
444 | #endif | 454 | #endif |
445 | 455 | ||
446 | int __handle_fault(unsigned long uaddr, unsigned long int_code, int write_user) | 456 | int __handle_fault(unsigned long uaddr, unsigned long pgm_int_code, int write) |
447 | { | 457 | { |
448 | struct pt_regs regs; | 458 | struct pt_regs regs; |
449 | int access, fault; | 459 | int access, fault; |
@@ -454,14 +464,14 @@ int __handle_fault(unsigned long uaddr, unsigned long int_code, int write_user) | |||
454 | regs.psw.addr = (unsigned long) __builtin_return_address(0); | 464 | regs.psw.addr = (unsigned long) __builtin_return_address(0); |
455 | regs.psw.addr |= PSW_ADDR_AMODE; | 465 | regs.psw.addr |= PSW_ADDR_AMODE; |
456 | uaddr &= PAGE_MASK; | 466 | uaddr &= PAGE_MASK; |
457 | access = write_user ? VM_WRITE : VM_READ; | 467 | access = write ? VM_WRITE : VM_READ; |
458 | fault = do_exception(®s, access, uaddr | 2); | 468 | fault = do_exception(®s, access, uaddr | 2); |
459 | if (unlikely(fault)) { | 469 | if (unlikely(fault)) { |
460 | if (fault & VM_FAULT_OOM) { | 470 | if (fault & VM_FAULT_OOM) { |
461 | pagefault_out_of_memory(); | 471 | pagefault_out_of_memory(); |
462 | fault = 0; | 472 | fault = 0; |
463 | } else if (fault & VM_FAULT_SIGBUS) | 473 | } else if (fault & VM_FAULT_SIGBUS) |
464 | do_sigbus(®s, int_code, uaddr); | 474 | do_sigbus(®s, pgm_int_code, uaddr); |
465 | } | 475 | } |
466 | return fault ? -EFAULT : 0; | 476 | return fault ? -EFAULT : 0; |
467 | } | 477 | } |
@@ -527,7 +537,8 @@ void pfault_fini(void) | |||
527 | : : "a" (&refbk), "m" (refbk) : "cc"); | 537 | : : "a" (&refbk), "m" (refbk) : "cc"); |
528 | } | 538 | } |
529 | 539 | ||
530 | static void pfault_interrupt(__u16 int_code) | 540 | static void pfault_interrupt(unsigned int ext_int_code, |
541 | unsigned int param32, unsigned long param64) | ||
531 | { | 542 | { |
532 | struct task_struct *tsk; | 543 | struct task_struct *tsk; |
533 | __u16 subcode; | 544 | __u16 subcode; |
@@ -538,14 +549,18 @@ static void pfault_interrupt(__u16 int_code) | |||
538 | * in the 'cpu address' field associated with the | 549 | * in the 'cpu address' field associated with the |
539 | * external interrupt. | 550 | * external interrupt. |
540 | */ | 551 | */ |
541 | subcode = S390_lowcore.cpu_addr; | 552 | subcode = ext_int_code >> 16; |
542 | if ((subcode & 0xff00) != __SUBCODE_MASK) | 553 | if ((subcode & 0xff00) != __SUBCODE_MASK) |
543 | return; | 554 | return; |
544 | 555 | ||
545 | /* | 556 | /* |
546 | * Get the token (= address of the task structure of the affected task). | 557 | * Get the token (= address of the task structure of the affected task). |
547 | */ | 558 | */ |
548 | tsk = *(struct task_struct **) __LC_PFAULT_INTPARM; | 559 | #ifdef CONFIG_64BIT |
560 | tsk = *(struct task_struct **) param64; | ||
561 | #else | ||
562 | tsk = *(struct task_struct **) param32; | ||
563 | #endif | ||
549 | 564 | ||
550 | if (subcode & 0x0080) { | 565 | if (subcode & 0x0080) { |
551 | /* signal bit is set -> a page has been swapped in by VM */ | 566 | /* signal bit is set -> a page has been swapped in by VM */ |