diff options
Diffstat (limited to 'arch/mips/kernel/traps.c')
-rw-r--r-- | arch/mips/kernel/traps.c | 164 |
1 files changed, 86 insertions, 78 deletions
diff --git a/arch/mips/kernel/traps.c b/arch/mips/kernel/traps.c index 9c0c478d71ac..bbf01b81a4ff 100644 --- a/arch/mips/kernel/traps.c +++ b/arch/mips/kernel/traps.c | |||
@@ -9,9 +9,10 @@ | |||
9 | * Copyright (C) 1999 Silicon Graphics, Inc. | 9 | * Copyright (C) 1999 Silicon Graphics, Inc. |
10 | * Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com | 10 | * Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com |
11 | * Copyright (C) 2000, 01 MIPS Technologies, Inc. | 11 | * Copyright (C) 2000, 01 MIPS Technologies, Inc. |
12 | * Copyright (C) 2002, 2003, 2004, 2005 Maciej W. Rozycki | 12 | * Copyright (C) 2002, 2003, 2004, 2005, 2007 Maciej W. Rozycki |
13 | */ | 13 | */ |
14 | #include <linux/bug.h> | 14 | #include <linux/bug.h> |
15 | #include <linux/compiler.h> | ||
15 | #include <linux/init.h> | 16 | #include <linux/init.h> |
16 | #include <linux/mm.h> | 17 | #include <linux/mm.h> |
17 | #include <linux/module.h> | 18 | #include <linux/module.h> |
@@ -410,7 +411,7 @@ asmlinkage void do_be(struct pt_regs *regs) | |||
410 | } | 411 | } |
411 | 412 | ||
412 | /* | 413 | /* |
413 | * ll/sc emulation | 414 | * ll/sc, rdhwr, sync emulation |
414 | */ | 415 | */ |
415 | 416 | ||
416 | #define OPCODE 0xfc000000 | 417 | #define OPCODE 0xfc000000 |
@@ -419,9 +420,11 @@ asmlinkage void do_be(struct pt_regs *regs) | |||
419 | #define OFFSET 0x0000ffff | 420 | #define OFFSET 0x0000ffff |
420 | #define LL 0xc0000000 | 421 | #define LL 0xc0000000 |
421 | #define SC 0xe0000000 | 422 | #define SC 0xe0000000 |
423 | #define SPEC0 0x00000000 | ||
422 | #define SPEC3 0x7c000000 | 424 | #define SPEC3 0x7c000000 |
423 | #define RD 0x0000f800 | 425 | #define RD 0x0000f800 |
424 | #define FUNC 0x0000003f | 426 | #define FUNC 0x0000003f |
427 | #define SYNC 0x0000000f | ||
425 | #define RDHWR 0x0000003b | 428 | #define RDHWR 0x0000003b |
426 | 429 | ||
427 | /* | 430 | /* |
@@ -432,11 +435,10 @@ unsigned long ll_bit; | |||
432 | 435 | ||
433 | static struct task_struct *ll_task = NULL; | 436 | static struct task_struct *ll_task = NULL; |
434 | 437 | ||
435 | static inline void simulate_ll(struct pt_regs *regs, unsigned int opcode) | 438 | static inline int simulate_ll(struct pt_regs *regs, unsigned int opcode) |
436 | { | 439 | { |
437 | unsigned long value, __user *vaddr; | 440 | unsigned long value, __user *vaddr; |
438 | long offset; | 441 | long offset; |
439 | int signal = 0; | ||
440 | 442 | ||
441 | /* | 443 | /* |
442 | * analyse the ll instruction that just caused a ri exception | 444 | * analyse the ll instruction that just caused a ri exception |
@@ -451,14 +453,10 @@ static inline void simulate_ll(struct pt_regs *regs, unsigned int opcode) | |||
451 | vaddr = (unsigned long __user *) | 453 | vaddr = (unsigned long __user *) |
452 | ((unsigned long)(regs->regs[(opcode & BASE) >> 21]) + offset); | 454 | ((unsigned long)(regs->regs[(opcode & BASE) >> 21]) + offset); |
453 | 455 | ||
454 | if ((unsigned long)vaddr & 3) { | 456 | if ((unsigned long)vaddr & 3) |
455 | signal = SIGBUS; | 457 | return SIGBUS; |
456 | goto sig; | 458 | if (get_user(value, vaddr)) |
457 | } | 459 | return SIGSEGV; |
458 | if (get_user(value, vaddr)) { | ||
459 | signal = SIGSEGV; | ||
460 | goto sig; | ||
461 | } | ||
462 | 460 | ||
463 | preempt_disable(); | 461 | preempt_disable(); |
464 | 462 | ||
@@ -471,22 +469,16 @@ static inline void simulate_ll(struct pt_regs *regs, unsigned int opcode) | |||
471 | 469 | ||
472 | preempt_enable(); | 470 | preempt_enable(); |
473 | 471 | ||
474 | compute_return_epc(regs); | ||
475 | |||
476 | regs->regs[(opcode & RT) >> 16] = value; | 472 | regs->regs[(opcode & RT) >> 16] = value; |
477 | 473 | ||
478 | return; | 474 | return 0; |
479 | |||
480 | sig: | ||
481 | force_sig(signal, current); | ||
482 | } | 475 | } |
483 | 476 | ||
484 | static inline void simulate_sc(struct pt_regs *regs, unsigned int opcode) | 477 | static inline int simulate_sc(struct pt_regs *regs, unsigned int opcode) |
485 | { | 478 | { |
486 | unsigned long __user *vaddr; | 479 | unsigned long __user *vaddr; |
487 | unsigned long reg; | 480 | unsigned long reg; |
488 | long offset; | 481 | long offset; |
489 | int signal = 0; | ||
490 | 482 | ||
491 | /* | 483 | /* |
492 | * analyse the sc instruction that just caused a ri exception | 484 | * analyse the sc instruction that just caused a ri exception |
@@ -502,34 +494,25 @@ static inline void simulate_sc(struct pt_regs *regs, unsigned int opcode) | |||
502 | ((unsigned long)(regs->regs[(opcode & BASE) >> 21]) + offset); | 494 | ((unsigned long)(regs->regs[(opcode & BASE) >> 21]) + offset); |
503 | reg = (opcode & RT) >> 16; | 495 | reg = (opcode & RT) >> 16; |
504 | 496 | ||
505 | if ((unsigned long)vaddr & 3) { | 497 | if ((unsigned long)vaddr & 3) |
506 | signal = SIGBUS; | 498 | return SIGBUS; |
507 | goto sig; | ||
508 | } | ||
509 | 499 | ||
510 | preempt_disable(); | 500 | preempt_disable(); |
511 | 501 | ||
512 | if (ll_bit == 0 || ll_task != current) { | 502 | if (ll_bit == 0 || ll_task != current) { |
513 | compute_return_epc(regs); | ||
514 | regs->regs[reg] = 0; | 503 | regs->regs[reg] = 0; |
515 | preempt_enable(); | 504 | preempt_enable(); |
516 | return; | 505 | return 0; |
517 | } | 506 | } |
518 | 507 | ||
519 | preempt_enable(); | 508 | preempt_enable(); |
520 | 509 | ||
521 | if (put_user(regs->regs[reg], vaddr)) { | 510 | if (put_user(regs->regs[reg], vaddr)) |
522 | signal = SIGSEGV; | 511 | return SIGSEGV; |
523 | goto sig; | ||
524 | } | ||
525 | 512 | ||
526 | compute_return_epc(regs); | ||
527 | regs->regs[reg] = 1; | 513 | regs->regs[reg] = 1; |
528 | 514 | ||
529 | return; | 515 | return 0; |
530 | |||
531 | sig: | ||
532 | force_sig(signal, current); | ||
533 | } | 516 | } |
534 | 517 | ||
535 | /* | 518 | /* |
@@ -539,27 +522,14 @@ sig: | |||
539 | * few processors such as NEC's VR4100 throw reserved instruction exceptions | 522 | * few processors such as NEC's VR4100 throw reserved instruction exceptions |
540 | * instead, so we're doing the emulation thing in both exception handlers. | 523 | * instead, so we're doing the emulation thing in both exception handlers. |
541 | */ | 524 | */ |
542 | static inline int simulate_llsc(struct pt_regs *regs) | 525 | static int simulate_llsc(struct pt_regs *regs, unsigned int opcode) |
543 | { | 526 | { |
544 | unsigned int opcode; | 527 | if ((opcode & OPCODE) == LL) |
545 | 528 | return simulate_ll(regs, opcode); | |
546 | if (get_user(opcode, (unsigned int __user *) exception_epc(regs))) | 529 | if ((opcode & OPCODE) == SC) |
547 | goto out_sigsegv; | 530 | return simulate_sc(regs, opcode); |
548 | |||
549 | if ((opcode & OPCODE) == LL) { | ||
550 | simulate_ll(regs, opcode); | ||
551 | return 0; | ||
552 | } | ||
553 | if ((opcode & OPCODE) == SC) { | ||
554 | simulate_sc(regs, opcode); | ||
555 | return 0; | ||
556 | } | ||
557 | |||
558 | return -EFAULT; /* Strange things going on ... */ | ||
559 | 531 | ||
560 | out_sigsegv: | 532 | return -1; /* Must be something else ... */ |
561 | force_sig(SIGSEGV, current); | ||
562 | return -EFAULT; | ||
563 | } | 533 | } |
564 | 534 | ||
565 | /* | 535 | /* |
@@ -567,16 +537,9 @@ out_sigsegv: | |||
567 | * registers not implemented in hardware. The only current use of this | 537 | * registers not implemented in hardware. The only current use of this |
568 | * is the thread area pointer. | 538 | * is the thread area pointer. |
569 | */ | 539 | */ |
570 | static inline int simulate_rdhwr(struct pt_regs *regs) | 540 | static int simulate_rdhwr(struct pt_regs *regs, unsigned int opcode) |
571 | { | 541 | { |
572 | struct thread_info *ti = task_thread_info(current); | 542 | struct thread_info *ti = task_thread_info(current); |
573 | unsigned int opcode; | ||
574 | |||
575 | if (get_user(opcode, (unsigned int __user *) exception_epc(regs))) | ||
576 | goto out_sigsegv; | ||
577 | |||
578 | if (unlikely(compute_return_epc(regs))) | ||
579 | return -EFAULT; | ||
580 | 543 | ||
581 | if ((opcode & OPCODE) == SPEC3 && (opcode & FUNC) == RDHWR) { | 544 | if ((opcode & OPCODE) == SPEC3 && (opcode & FUNC) == RDHWR) { |
582 | int rd = (opcode & RD) >> 11; | 545 | int rd = (opcode & RD) >> 11; |
@@ -586,16 +549,20 @@ static inline int simulate_rdhwr(struct pt_regs *regs) | |||
586 | regs->regs[rt] = ti->tp_value; | 549 | regs->regs[rt] = ti->tp_value; |
587 | return 0; | 550 | return 0; |
588 | default: | 551 | default: |
589 | return -EFAULT; | 552 | return -1; |
590 | } | 553 | } |
591 | } | 554 | } |
592 | 555 | ||
593 | /* Not ours. */ | 556 | /* Not ours. */ |
594 | return -EFAULT; | 557 | return -1; |
558 | } | ||
595 | 559 | ||
596 | out_sigsegv: | 560 | static int simulate_sync(struct pt_regs *regs, unsigned int opcode) |
597 | force_sig(SIGSEGV, current); | 561 | { |
598 | return -EFAULT; | 562 | if ((opcode & OPCODE) == SPEC0 && (opcode & FUNC) == SYNC) |
563 | return 0; | ||
564 | |||
565 | return -1; /* Must be something else ... */ | ||
599 | } | 566 | } |
600 | 567 | ||
601 | asmlinkage void do_ov(struct pt_regs *regs) | 568 | asmlinkage void do_ov(struct pt_regs *regs) |
@@ -767,16 +734,35 @@ out_sigsegv: | |||
767 | 734 | ||
768 | asmlinkage void do_ri(struct pt_regs *regs) | 735 | asmlinkage void do_ri(struct pt_regs *regs) |
769 | { | 736 | { |
770 | die_if_kernel("Reserved instruction in kernel code", regs); | 737 | unsigned int __user *epc = (unsigned int __user *)exception_epc(regs); |
738 | unsigned long old_epc = regs->cp0_epc; | ||
739 | unsigned int opcode = 0; | ||
740 | int status = -1; | ||
771 | 741 | ||
772 | if (!cpu_has_llsc) | 742 | die_if_kernel("Reserved instruction in kernel code", regs); |
773 | if (!simulate_llsc(regs)) | ||
774 | return; | ||
775 | 743 | ||
776 | if (!simulate_rdhwr(regs)) | 744 | if (unlikely(compute_return_epc(regs) < 0)) |
777 | return; | 745 | return; |
778 | 746 | ||
779 | force_sig(SIGILL, current); | 747 | if (unlikely(get_user(opcode, epc) < 0)) |
748 | status = SIGSEGV; | ||
749 | |||
750 | if (!cpu_has_llsc && status < 0) | ||
751 | status = simulate_llsc(regs, opcode); | ||
752 | |||
753 | if (status < 0) | ||
754 | status = simulate_rdhwr(regs, opcode); | ||
755 | |||
756 | if (status < 0) | ||
757 | status = simulate_sync(regs, opcode); | ||
758 | |||
759 | if (status < 0) | ||
760 | status = SIGILL; | ||
761 | |||
762 | if (unlikely(status > 0)) { | ||
763 | regs->cp0_epc = old_epc; /* Undo skip-over. */ | ||
764 | force_sig(status, current); | ||
765 | } | ||
780 | } | 766 | } |
781 | 767 | ||
782 | /* | 768 | /* |
@@ -808,7 +794,11 @@ static void mt_ase_fp_affinity(void) | |||
808 | 794 | ||
809 | asmlinkage void do_cpu(struct pt_regs *regs) | 795 | asmlinkage void do_cpu(struct pt_regs *regs) |
810 | { | 796 | { |
797 | unsigned int __user *epc; | ||
798 | unsigned long old_epc; | ||
799 | unsigned int opcode; | ||
811 | unsigned int cpid; | 800 | unsigned int cpid; |
801 | int status; | ||
812 | 802 | ||
813 | die_if_kernel("do_cpu invoked from kernel context!", regs); | 803 | die_if_kernel("do_cpu invoked from kernel context!", regs); |
814 | 804 | ||
@@ -816,14 +806,32 @@ asmlinkage void do_cpu(struct pt_regs *regs) | |||
816 | 806 | ||
817 | switch (cpid) { | 807 | switch (cpid) { |
818 | case 0: | 808 | case 0: |
819 | if (!cpu_has_llsc) | 809 | epc = (unsigned int __user *)exception_epc(regs); |
820 | if (!simulate_llsc(regs)) | 810 | old_epc = regs->cp0_epc; |
821 | return; | 811 | opcode = 0; |
812 | status = -1; | ||
822 | 813 | ||
823 | if (!simulate_rdhwr(regs)) | 814 | if (unlikely(compute_return_epc(regs) < 0)) |
824 | return; | 815 | return; |
825 | 816 | ||
826 | break; | 817 | if (unlikely(get_user(opcode, epc) < 0)) |
818 | status = SIGSEGV; | ||
819 | |||
820 | if (!cpu_has_llsc && status < 0) | ||
821 | status = simulate_llsc(regs, opcode); | ||
822 | |||
823 | if (status < 0) | ||
824 | status = simulate_rdhwr(regs, opcode); | ||
825 | |||
826 | if (status < 0) | ||
827 | status = SIGILL; | ||
828 | |||
829 | if (unlikely(status > 0)) { | ||
830 | regs->cp0_epc = old_epc; /* Undo skip-over. */ | ||
831 | force_sig(status, current); | ||
832 | } | ||
833 | |||
834 | return; | ||
827 | 835 | ||
828 | case 1: | 836 | case 1: |
829 | if (used_math()) /* Using the FPU again. */ | 837 | if (used_math()) /* Using the FPU again. */ |