diff options
author | Neil Campbell <neilc@linux.vnet.ibm.com> | 2009-12-13 23:08:57 -0500 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2009-12-17 22:55:43 -0500 |
commit | bb7f20b1c639606def3b91f4e4aca6daeee5d80a (patch) | |
tree | 9ccfe5acbbb9750ac5aae7048fd86b26cdf90c53 /arch/powerpc | |
parent | f04b10cddb0fbceadbad7af38c31543298948d8f (diff) |
powerpc: Handle VSX alignment faults correctly in little-endian mode
This patch fixes the handling of VSX alignment faults in little-endian
mode (the current code assumes the processor is in big-endian mode).
The patch also makes the handlers clear the top 8 bytes of the register
when handling an 8 byte VSX load.
This is based on 2.6.32.
Signed-off-by: Neil Campbell <neilc@linux.vnet.ibm.com>
Cc: <stable@kernel.org>
Acked-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc')
-rw-r--r-- | arch/powerpc/kernel/align.c | 63 |
1 files changed, 46 insertions, 17 deletions
diff --git a/arch/powerpc/kernel/align.c b/arch/powerpc/kernel/align.c index 3839839f83c7..b876e989220b 100644 --- a/arch/powerpc/kernel/align.c +++ b/arch/powerpc/kernel/align.c | |||
@@ -642,10 +642,14 @@ static int emulate_spe(struct pt_regs *regs, unsigned int reg, | |||
642 | */ | 642 | */ |
643 | static int emulate_vsx(unsigned char __user *addr, unsigned int reg, | 643 | static int emulate_vsx(unsigned char __user *addr, unsigned int reg, |
644 | unsigned int areg, struct pt_regs *regs, | 644 | unsigned int areg, struct pt_regs *regs, |
645 | unsigned int flags, unsigned int length) | 645 | unsigned int flags, unsigned int length, |
646 | unsigned int elsize) | ||
646 | { | 647 | { |
647 | char *ptr; | 648 | char *ptr; |
649 | unsigned long *lptr; | ||
648 | int ret = 0; | 650 | int ret = 0; |
651 | int sw = 0; | ||
652 | int i, j; | ||
649 | 653 | ||
650 | flush_vsx_to_thread(current); | 654 | flush_vsx_to_thread(current); |
651 | 655 | ||
@@ -654,19 +658,35 @@ static int emulate_vsx(unsigned char __user *addr, unsigned int reg, | |||
654 | else | 658 | else |
655 | ptr = (char *) ¤t->thread.vr[reg - 32]; | 659 | ptr = (char *) ¤t->thread.vr[reg - 32]; |
656 | 660 | ||
657 | if (flags & ST) | 661 | lptr = (unsigned long *) ptr; |
658 | ret = __copy_to_user(addr, ptr, length); | 662 | |
659 | else { | 663 | if (flags & SW) |
660 | if (flags & SPLT){ | 664 | sw = elsize-1; |
661 | ret = __copy_from_user(ptr, addr, length); | 665 | |
662 | ptr += length; | 666 | for (j = 0; j < length; j += elsize) { |
667 | for (i = 0; i < elsize; ++i) { | ||
668 | if (flags & ST) | ||
669 | ret |= __put_user(ptr[i^sw], addr + i); | ||
670 | else | ||
671 | ret |= __get_user(ptr[i^sw], addr + i); | ||
663 | } | 672 | } |
664 | ret |= __copy_from_user(ptr, addr, length); | 673 | ptr += elsize; |
674 | addr += elsize; | ||
665 | } | 675 | } |
666 | if (flags & U) | 676 | |
667 | regs->gpr[areg] = regs->dar; | 677 | if (!ret) { |
668 | if (ret) | 678 | if (flags & U) |
679 | regs->gpr[areg] = regs->dar; | ||
680 | |||
681 | /* Splat load copies the same data to top and bottom 8 bytes */ | ||
682 | if (flags & SPLT) | ||
683 | lptr[1] = lptr[0]; | ||
684 | /* For 8 byte loads, zero the top 8 bytes */ | ||
685 | else if (!(flags & ST) && (8 == length)) | ||
686 | lptr[1] = 0; | ||
687 | } else | ||
669 | return -EFAULT; | 688 | return -EFAULT; |
689 | |||
670 | return 1; | 690 | return 1; |
671 | } | 691 | } |
672 | #endif | 692 | #endif |
@@ -767,16 +787,25 @@ int fix_alignment(struct pt_regs *regs) | |||
767 | 787 | ||
768 | #ifdef CONFIG_VSX | 788 | #ifdef CONFIG_VSX |
769 | if ((instruction & 0xfc00003e) == 0x7c000018) { | 789 | if ((instruction & 0xfc00003e) == 0x7c000018) { |
770 | /* Additional register addressing bit (64 VSX vs 32 FPR/GPR */ | 790 | unsigned int elsize; |
791 | |||
792 | /* Additional register addressing bit (64 VSX vs 32 FPR/GPR) */ | ||
771 | reg |= (instruction & 0x1) << 5; | 793 | reg |= (instruction & 0x1) << 5; |
772 | /* Simple inline decoder instead of a table */ | 794 | /* Simple inline decoder instead of a table */ |
795 | /* VSX has only 8 and 16 byte memory accesses */ | ||
796 | nb = 8; | ||
773 | if (instruction & 0x200) | 797 | if (instruction & 0x200) |
774 | nb = 16; | 798 | nb = 16; |
775 | else if (instruction & 0x080) | 799 | |
776 | nb = 8; | 800 | /* Vector stores in little-endian mode swap individual |
777 | else | 801 | elements, so process them separately */ |
778 | nb = 4; | 802 | elsize = 4; |
803 | if (instruction & 0x80) | ||
804 | elsize = 8; | ||
805 | |||
779 | flags = 0; | 806 | flags = 0; |
807 | if (regs->msr & MSR_LE) | ||
808 | flags |= SW; | ||
780 | if (instruction & 0x100) | 809 | if (instruction & 0x100) |
781 | flags |= ST; | 810 | flags |= ST; |
782 | if (instruction & 0x040) | 811 | if (instruction & 0x040) |
@@ -787,7 +816,7 @@ int fix_alignment(struct pt_regs *regs) | |||
787 | nb = 8; | 816 | nb = 8; |
788 | } | 817 | } |
789 | PPC_WARN_ALIGNMENT(vsx, regs); | 818 | PPC_WARN_ALIGNMENT(vsx, regs); |
790 | return emulate_vsx(addr, reg, areg, regs, flags, nb); | 819 | return emulate_vsx(addr, reg, areg, regs, flags, nb, elsize); |
791 | } | 820 | } |
792 | #endif | 821 | #endif |
793 | /* A size of 0 indicates an instruction we don't support, with | 822 | /* A size of 0 indicates an instruction we don't support, with |