[POWERPC] Handle alignment faults on new FP load/store instructions
authorPaul Mackerras <paulus@samba.org>
Fri, 10 Aug 2007 04:07:38 +0000 (14:07 +1000)
committerPaul Mackerras <paulus@samba.org>
Fri, 17 Aug 2007 01:01:55 +0000 (11:01 +1000)
This adds code to handle alignment traps generated by the following
new floating-point load/store instructions, by emulating the
instruction in the kernel (as is done for other instructions that
generate alignment traps):

lfiwax load floating-point as integer word algebraic indexed
stfiwx store floating-point as integer word indexed
lfdp load floating-point double pair
lfdpx load floating-point double pair indexed
stfdp store floating-point double pair
stfdpx store floating-point double pair indexed

All these except stfiwx are new in POWER6.

lfdp/lfdpx/stfdp/stfdpx load and store 16 bytes of memory into an
even/odd FP register pair.  In little-endian mode each 8-byte value is
byte-reversed separately (i.e. not as a 16-byte unit).  lfiwax/stfiwx
load or store the lower 4 bytes of a floating-point register from/to
memory; lfiwax sets the upper 4 bytes of the FP register to the sign
extension of the value loaded.

Signed-off-by: Paul Mackerras <paulus@samba.org>
arch/powerpc/kernel/align.c

index 5c9ff7f5c44ef903822ee04585e1d47789c6282c..4c47f9cc0d9acbcadcdf561bb3a015e4ce4fd0e4 100644 (file)
@@ -38,7 +38,7 @@ struct aligninfo {
 /* Bits in the flags field */
 #define LD     0       /* load */
 #define ST     1       /* store */
-#define        SE      2       /* sign-extend value */
+#define SE     2       /* sign-extend value, or FP ld/st as word */
 #define F      4       /* to/from fp regs */
 #define U      8       /* update index register */
 #define M      0x10    /* multiple load/store */
@@ -87,9 +87,9 @@ static struct aligninfo aligninfo[128] = {
        { 8, LD+F+U },          /* 00 1 1001: lfdu */
        { 4, ST+F+S+U },        /* 00 1 1010: stfsu */
        { 8, ST+F+U },          /* 00 1 1011: stfdu */
-       INVALID,                /* 00 1 1100 */
+       { 16, LD+F },           /* 00 1 1100: lfdp */
        INVALID,                /* 00 1 1101 */
-       INVALID,                /* 00 1 1110 */
+       { 16, ST+F },           /* 00 1 1110: stfdp */
        INVALID,                /* 00 1 1111 */
        { 8, LD },              /* 01 0 0000: ldx */
        INVALID,                /* 01 0 0001 */
@@ -167,10 +167,10 @@ static struct aligninfo aligninfo[128] = {
        { 8, LD+F },            /* 11 0 1001: lfdx */
        { 4, ST+F+S },          /* 11 0 1010: stfsx */
        { 8, ST+F },            /* 11 0 1011: stfdx */
-       INVALID,                /* 11 0 1100 */
-       { 8, LD+M },            /* 11 0 1101: lmd */
-       INVALID,                /* 11 0 1110 */
-       { 8, ST+M },            /* 11 0 1111: stmd */
+       { 16, LD+F },           /* 11 0 1100: lfdpx */
+       { 4, LD+F+SE },         /* 11 0 1101: lfiwax */
+       { 16, ST+F },           /* 11 0 1110: stfdpx */
+       { 4, ST+F },            /* 11 0 1111: stfiwx */
        { 4, LD+U },            /* 11 1 0000: lwzux */
        INVALID,                /* 11 1 0001 */
        { 4, ST+U },            /* 11 1 0010: stwux */
@@ -356,6 +356,42 @@ static int emulate_multiple(struct pt_regs *regs, unsigned char __user *addr,
        return 1;
 }
 
+/*
+ * Emulate floating-point pair loads and stores.
+ * Only POWER6 has these instructions, and it does true little-endian,
+ * so we don't need the address swizzling.
+ */
+static int emulate_fp_pair(struct pt_regs *regs, unsigned char __user *addr,
+                          unsigned int reg, unsigned int flags)
+{
+       char *ptr = (char *) &current->thread.fpr[reg];
+       int i, ret;
+
+       if (!(flags & F))
+               return 0;
+       if (reg & 1)
+               return 0;       /* invalid form: FRS/FRT must be even */
+       if (!(flags & SW)) {
+               /* not byte-swapped - easy */
+               if (!(flags & ST))
+                       ret = __copy_from_user(ptr, addr, 16);
+               else
+                       ret = __copy_to_user(addr, ptr, 16);
+       } else {
+               /* each FPR value is byte-swapped separately */
+               ret = 0;
+               for (i = 0; i < 16; ++i) {
+                       if (!(flags & ST))
+                               ret |= __get_user(ptr[i^7], addr + i);
+                       else
+                               ret |= __put_user(ptr[i^7], addr + i);
+               }
+       }
+       if (ret)
+               return -EFAULT;
+       return 1;       /* exception handled and fixed up */
+}
+
 
 /*
  * Called on alignment exception. Attempts to fixup
@@ -471,6 +507,10 @@ int fix_alignment(struct pt_regs *regs)
                flush_fp_to_thread(current);
        }
 
+       /* Special case for 16-byte FP loads and stores */
+       if (nb == 16)
+               return emulate_fp_pair(regs, addr, reg, flags);
+
        /* If we are loading, get the data from user space, else
         * get it from register values
         */
@@ -531,7 +571,8 @@ int fix_alignment(struct pt_regs *regs)
         * or floating point single precision conversion
         */
        switch (flags & ~(U|SW)) {
-       case LD+SE:     /* sign extend */
+       case LD+SE:     /* sign extending integer loads */
+       case LD+F+SE:   /* sign extend for lfiwax */
                if ( nb == 2 )
                        data.ll = data.x16.low16;
                else    /* nb must be 4 */