Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Neil Campbell and committed by
Benjamin Herrenschmidt
bb7f20b1 f04b10cd

+46 -17
+46 -17
arch/powerpc/kernel/align.c
··· 642 642 */ 643 643 static int emulate_vsx(unsigned char __user *addr, unsigned int reg, 644 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 648 char *ptr; 649 + unsigned long *lptr; 648 650 int ret = 0; 651 + int sw = 0; 652 + int i, j; 649 653 650 654 flush_vsx_to_thread(current); 651 655 ··· 658 654 else 659 655 ptr = (char *) &current->thread.vr[reg - 32]; 660 656 661 - if (flags & ST) 662 - ret = __copy_to_user(addr, ptr, length); 663 - else { 664 - if (flags & SPLT){ 665 - ret = __copy_from_user(ptr, addr, length); 666 - ptr += length; 657 + lptr = (unsigned long *) ptr; 658 + 659 + if (flags & SW) 660 + sw = elsize-1; 661 + 662 + for (j = 0; j < length; j += elsize) { 663 + for (i = 0; i < elsize; ++i) { 664 + if (flags & ST) 665 + ret |= __put_user(ptr[i^sw], addr + i); 666 + else 667 + ret |= __get_user(ptr[i^sw], addr + i); 667 668 } 668 - ret |= __copy_from_user(ptr, addr, length); 669 + ptr += elsize; 670 + addr += elsize; 669 671 } 670 - if (flags & U) 671 - regs->gpr[areg] = regs->dar; 672 - if (ret) 672 + 673 + if (!ret) { 674 + if (flags & U) 675 + regs->gpr[areg] = regs->dar; 676 + 677 + /* Splat load copies the same data to top and bottom 8 bytes */ 678 + if (flags & SPLT) 679 + lptr[1] = lptr[0]; 680 + /* For 8 byte loads, zero the top 8 bytes */ 681 + else if (!(flags & ST) && (8 == length)) 682 + lptr[1] = 0; 683 + } else 673 684 return -EFAULT; 685 + 674 686 return 1; 675 687 } 676 688 #endif ··· 787 767 788 768 #ifdef CONFIG_VSX 789 769 if ((instruction & 0xfc00003e) == 0x7c000018) { 790 - /* Additional register addressing bit (64 VSX vs 32 FPR/GPR */ 770 + unsigned int elsize; 771 + 772 + /* Additional register addressing bit (64 VSX vs 32 FPR/GPR) */ 791 773 reg |= (instruction & 0x1) << 5; 792 774 /* Simple inline decoder instead of a table */ 775 + /* VSX has only 8 and 16 byte memory accesses */ 776 + nb = 8; 793 777 if (instruction & 0x200) 794 778 nb = 16; 795 - else if (instruction & 0x080) 796 - nb = 8; 797 - else 798 - nb = 4; 779 + 780 + /* Vector stores in little-endian mode swap individual 781 + elements, so process them separately */ 782 + elsize = 4; 783 + if (instruction & 0x80) 784 + elsize = 8; 785 + 799 786 flags = 0; 787 + if (regs->msr & MSR_LE) 788 + flags |= SW; 800 789 if (instruction & 0x100) 801 790 flags |= ST; 802 791 if (instruction & 0x040) ··· 816 787 nb = 8; 817 788 } 818 789 PPC_WARN_ALIGNMENT(vsx, regs); 819 - return emulate_vsx(addr, reg, areg, regs, flags, nb); 790 + return emulate_vsx(addr, reg, areg, regs, flags, nb, elsize); 820 791 } 821 792 #endif 822 793 /* A size of 0 indicates an instruction we don't support, with