diff options
author | Maciej W. Rozycki <macro@linux-mips.org> | 2007-10-16 13:43:26 -0400 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2007-10-17 13:28:47 -0400 |
commit | 60b0d65541b581955279221e060f8a0a221151b4 (patch) | |
tree | 9ed6abf374cb5004ab626927b96e1ba6eb4eb2ee /arch/mips/kernel/traps.c | |
parent | 396a2ae08e5080b140330645743ab2567f6bc426 (diff) |
[MIPS] SYNC emulation for MIPS I processors
Userland, including the C library and the dynamic linker, is keen to use
the SYNC instruction, even for "generic" MIPS I binaries these days.
Which makes it less than useful on MIPS I processors.
This change adds the emulation, but as our do_ri() infrastructure was not
really prepared to take yet another instruction, I have rewritten it and
its callees slightly as follows.
Now there is only a single place a possible signal is thrown from. The
place is at the end of do_ri(). The instruction word is fetched in
do_ri() and passed down to handlers. The handlers are called in sequence
and return a result that lets the caller decide upon further processing.
If the result is positive, then the handler has picked the instruction,
but a signal should be thrown and the result is the signal number. If the
result is zero, then the handler has successfully simulated the
instruction. If the result is negative, then the handler did not handle
the instruction; to make it more obvious the calls do not follow the usual
0/-Exxx result convention they now return -1 instead of -EFAULT.
The calculation of the return EPC is now at the beginning. The reason is
it is easier to handle it there as emulation callees may modify a register
and an instruction may be located in delay slot of a branch whose result
depends on the register. It has to be undone if a signal is to be raised,
but it is not a problem as this is the slow-path case, and both actions
are done in single places now rather than the former being scattered
through emulation handlers.
The part of do_cpu() being covered follows the changes to do_ri().
Signed-off-by: Maciej W. Rozycki <macro@linux-mips.org>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
---
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. */ |