aboutsummaryrefslogtreecommitdiffstats
path: root/arch/mips/kernel
diff options
context:
space:
mode:
authorMaciej W. Rozycki <macro@linux-mips.org>2007-10-16 13:43:26 -0400
committerRalf Baechle <ralf@linux-mips.org>2007-10-17 13:28:47 -0400
commit60b0d65541b581955279221e060f8a0a221151b4 (patch)
tree9ed6abf374cb5004ab626927b96e1ba6eb4eb2ee /arch/mips/kernel
parent396a2ae08e5080b140330645743ab2567f6bc426 (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')
-rw-r--r--arch/mips/kernel/traps.c164
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
433static struct task_struct *ll_task = NULL; 436static struct task_struct *ll_task = NULL;
434 437
435static inline void simulate_ll(struct pt_regs *regs, unsigned int opcode) 438static 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
480sig:
481 force_sig(signal, current);
482} 475}
483 476
484static inline void simulate_sc(struct pt_regs *regs, unsigned int opcode) 477static 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
531sig:
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 */
542static inline int simulate_llsc(struct pt_regs *regs) 525static 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
560out_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 */
570static inline int simulate_rdhwr(struct pt_regs *regs) 540static 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
596out_sigsegv: 560static 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
601asmlinkage void do_ov(struct pt_regs *regs) 568asmlinkage void do_ov(struct pt_regs *regs)
@@ -767,16 +734,35 @@ out_sigsegv:
767 734
768asmlinkage void do_ri(struct pt_regs *regs) 735asmlinkage 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
809asmlinkage void do_cpu(struct pt_regs *regs) 795asmlinkage 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. */