[POWERPC] Handle alignment faults on SPE load/store instructions
authorKumar Gala <galak@kernel.crashing.org>
Fri, 24 Aug 2007 21:42:53 +0000 (16:42 -0500)
committerKumar Gala <galak@kernel.crashing.org>
Fri, 14 Sep 2007 13:51:48 +0000 (08:51 -0500)
This adds code to handle alignment traps generated by the following
SPE (signal processing engine) load/store instructions, by emulating
the instruction in the kernel (as is done for other instructions that
generate alignment traps):

evldd[x]         Vector Load Double Word into Double Word [Indexed]
evldw[x]         Vector Load Double into Two Words [Indexed]
evldh[x]         Vector Load Double into Four Half Words [Indexed]
evlhhesplat[x]   Vector Load Half Word into Half Words Even and Splat [Indexed]
evlhhousplat[x]  Vector Load Half Word into Half Word Odd Unsigned and Splat [Indexed]
evlhhossplat[x]  Vector Load Half Word into Half Word Odd Signed and Splat [Indexed]
evlwhe[x]        Vector Load Word into Two Half Words Even [Indexed]
evlwhou[x]       Vector Load Word into Two Half Words Odd Unsigned (zero-extended) [Indexed]
evlwhos[x]       Vector Load Word into Two Half Words Odd Signed (with sign extension) [Indexed]
evlwwsplat[x]    Vector Load Word into Word and Splat [Indexed]
evlwhsplat[x]    Vector Load Word into Two Half Words and Splat [Indexed]
evstdd[x]        Vector Store Double of Double [Indexed]
evstdw[x]        Vector Store Double of Two Words [Indexed]
evstdh[x]        Vector Store Double of Four Half Words [Indexed]
evstwhe[x]       Vector Store Word of Two Half Words from Even [Indexed]
evstwho[x]       Vector Store Word of Two Half Words from Odd [Indexed]
evstwwe[x]       Vector Store Word of Word from Even [Indexed]
evstwwo[x]       Vector Store Word of Word from Odd [Indexed]

Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
arch/powerpc/kernel/align.c

index 4c47f9cc0d9acbcadcdf561bb3a015e4ce4fd0e4..e06f75daeba368ca231ec3d8af43db2285e68aba 100644 (file)
@@ -46,6 +46,8 @@ struct aligninfo {
 #define S      0x40    /* single-precision fp or... */
 #define SX     0x40    /* ... byte count in XER */
 #define HARD   0x80    /* string, stwcx. */
+#define E4     0x40    /* SPE endianness is word */
+#define E8     0x80    /* SPE endianness is double word */
 
 /* DSISR bits reported for a DCBZ instruction: */
 #define DCBZ   0x5f    /* 8xx/82xx dcbz faults when cache not enabled */
@@ -392,6 +394,248 @@ static int emulate_fp_pair(struct pt_regs *regs, unsigned char __user *addr,
        return 1;       /* exception handled and fixed up */
 }
 
+#ifdef CONFIG_SPE
+
+static struct aligninfo spe_aligninfo[32] = {
+       { 8, LD+E8 },           /* 0 00 00: evldd[x] */
+       { 8, LD+E4 },           /* 0 00 01: evldw[x] */
+       { 8, LD },              /* 0 00 10: evldh[x] */
+       INVALID,                /* 0 00 11 */
+       { 2, LD },              /* 0 01 00: evlhhesplat[x] */
+       INVALID,                /* 0 01 01 */
+       { 2, LD },              /* 0 01 10: evlhhousplat[x] */
+       { 2, LD+SE },           /* 0 01 11: evlhhossplat[x] */
+       { 4, LD },              /* 0 10 00: evlwhe[x] */
+       INVALID,                /* 0 10 01 */
+       { 4, LD },              /* 0 10 10: evlwhou[x] */
+       { 4, LD+SE },           /* 0 10 11: evlwhos[x] */
+       { 4, LD+E4 },           /* 0 11 00: evlwwsplat[x] */
+       INVALID,                /* 0 11 01 */
+       { 4, LD },              /* 0 11 10: evlwhsplat[x] */
+       INVALID,                /* 0 11 11 */
+
+       { 8, ST+E8 },           /* 1 00 00: evstdd[x] */
+       { 8, ST+E4 },           /* 1 00 01: evstdw[x] */
+       { 8, ST },              /* 1 00 10: evstdh[x] */
+       INVALID,                /* 1 00 11 */
+       INVALID,                /* 1 01 00 */
+       INVALID,                /* 1 01 01 */
+       INVALID,                /* 1 01 10 */
+       INVALID,                /* 1 01 11 */
+       { 4, ST },              /* 1 10 00: evstwhe[x] */
+       INVALID,                /* 1 10 01 */
+       { 4, ST },              /* 1 10 10: evstwho[x] */
+       INVALID,                /* 1 10 11 */
+       { 4, ST+E4 },           /* 1 11 00: evstwwe[x] */
+       INVALID,                /* 1 11 01 */
+       { 4, ST+E4 },           /* 1 11 10: evstwwo[x] */
+       INVALID,                /* 1 11 11 */
+};
+
+#define        EVLDD           0x00
+#define        EVLDW           0x01
+#define        EVLDH           0x02
+#define        EVLHHESPLAT     0x04
+#define        EVLHHOUSPLAT    0x06
+#define        EVLHHOSSPLAT    0x07
+#define        EVLWHE          0x08
+#define        EVLWHOU         0x0A
+#define        EVLWHOS         0x0B
+#define        EVLWWSPLAT      0x0C
+#define        EVLWHSPLAT      0x0E
+#define        EVSTDD          0x10
+#define        EVSTDW          0x11
+#define        EVSTDH          0x12
+#define        EVSTWHE         0x18
+#define        EVSTWHO         0x1A
+#define        EVSTWWE         0x1C
+#define        EVSTWWO         0x1E
+
+/*
+ * Emulate SPE loads and stores.
+ * Only Book-E has these instructions, and it does true little-endian,
+ * so we don't need the address swizzling.
+ */
+static int emulate_spe(struct pt_regs *regs, unsigned int reg,
+                      unsigned int instr)
+{
+       int t, ret;
+       union {
+               u64 ll;
+               u32 w[2];
+               u16 h[4];
+               u8 v[8];
+       } data, temp;
+       unsigned char __user *p, *addr;
+       unsigned long *evr = &current->thread.evr[reg];
+       unsigned int nb, flags;
+
+       instr = (instr >> 1) & 0x1f;
+
+       /* DAR has the operand effective address */
+       addr = (unsigned char __user *)regs->dar;
+
+       nb = spe_aligninfo[instr].len;
+       flags = spe_aligninfo[instr].flags;
+
+       /* Verify the address of the operand */
+       if (unlikely(user_mode(regs) &&
+                    !access_ok((flags & ST ? VERIFY_WRITE : VERIFY_READ),
+                               addr, nb)))
+               return -EFAULT;
+
+       /* userland only */
+       if (unlikely(!user_mode(regs)))
+               return 0;
+
+       flush_spe_to_thread(current);
+
+       /* If we are loading, get the data from user space, else
+        * get it from register values
+        */
+       if (flags & ST) {
+               data.ll = 0;
+               switch (instr) {
+               case EVSTDD:
+               case EVSTDW:
+               case EVSTDH:
+                       data.w[0] = *evr;
+                       data.w[1] = regs->gpr[reg];
+                       break;
+               case EVSTWHE:
+                       data.h[2] = *evr >> 16;
+                       data.h[3] = regs->gpr[reg] >> 16;
+                       break;
+               case EVSTWHO:
+                       data.h[2] = *evr & 0xffff;
+                       data.h[3] = regs->gpr[reg] & 0xffff;
+                       break;
+               case EVSTWWE:
+                       data.w[1] = *evr;
+                       break;
+               case EVSTWWO:
+                       data.w[1] = regs->gpr[reg];
+                       break;
+               default:
+                       return -EINVAL;
+               }
+       } else {
+               temp.ll = data.ll = 0;
+               ret = 0;
+               p = addr;
+
+               switch (nb) {
+               case 8:
+                       ret |= __get_user_inatomic(temp.v[0], p++);
+                       ret |= __get_user_inatomic(temp.v[1], p++);
+                       ret |= __get_user_inatomic(temp.v[2], p++);
+                       ret |= __get_user_inatomic(temp.v[3], p++);
+               case 4:
+                       ret |= __get_user_inatomic(temp.v[4], p++);
+                       ret |= __get_user_inatomic(temp.v[5], p++);
+               case 2:
+                       ret |= __get_user_inatomic(temp.v[6], p++);
+                       ret |= __get_user_inatomic(temp.v[7], p++);
+                       if (unlikely(ret))
+                               return -EFAULT;
+               }
+
+               switch (instr) {
+               case EVLDD:
+               case EVLDW:
+               case EVLDH:
+                       data.ll = temp.ll;
+                       break;
+               case EVLHHESPLAT:
+                       data.h[0] = temp.h[3];
+                       data.h[2] = temp.h[3];
+                       break;
+               case EVLHHOUSPLAT:
+               case EVLHHOSSPLAT:
+                       data.h[1] = temp.h[3];
+                       data.h[3] = temp.h[3];
+                       break;
+               case EVLWHE:
+                       data.h[0] = temp.h[2];
+                       data.h[2] = temp.h[3];
+                       break;
+               case EVLWHOU:
+               case EVLWHOS:
+                       data.h[1] = temp.h[2];
+                       data.h[3] = temp.h[3];
+                       break;
+               case EVLWWSPLAT:
+                       data.w[0] = temp.w[1];
+                       data.w[1] = temp.w[1];
+                       break;
+               case EVLWHSPLAT:
+                       data.h[0] = temp.h[2];
+                       data.h[1] = temp.h[2];
+                       data.h[2] = temp.h[3];
+                       data.h[3] = temp.h[3];
+                       break;
+               default:
+                       return -EINVAL;
+               }
+       }
+
+       if (flags & SW) {
+               switch (flags & 0xf0) {
+               case E8:
+                       SWAP(data.v[0], data.v[7]);
+                       SWAP(data.v[1], data.v[6]);
+                       SWAP(data.v[2], data.v[5]);
+                       SWAP(data.v[3], data.v[4]);
+                       break;
+               case E4:
+
+                       SWAP(data.v[0], data.v[3]);
+                       SWAP(data.v[1], data.v[2]);
+                       SWAP(data.v[4], data.v[7]);
+                       SWAP(data.v[5], data.v[6]);
+                       break;
+               /* Its half word endian */
+               default:
+                       SWAP(data.v[0], data.v[1]);
+                       SWAP(data.v[2], data.v[3]);
+                       SWAP(data.v[4], data.v[5]);
+                       SWAP(data.v[6], data.v[7]);
+                       break;
+               }
+       }
+
+       if (flags & SE) {
+               data.w[0] = (s16)data.h[1];
+               data.w[1] = (s16)data.h[3];
+       }
+
+       /* Store result to memory or update registers */
+       if (flags & ST) {
+               ret = 0;
+               p = addr;
+               switch (nb) {
+               case 8:
+                       ret |= __put_user_inatomic(data.v[0], p++);
+                       ret |= __put_user_inatomic(data.v[1], p++);
+                       ret |= __put_user_inatomic(data.v[2], p++);
+                       ret |= __put_user_inatomic(data.v[3], p++);
+               case 4:
+                       ret |= __put_user_inatomic(data.v[4], p++);
+                       ret |= __put_user_inatomic(data.v[5], p++);
+               case 2:
+                       ret |= __put_user_inatomic(data.v[6], p++);
+                       ret |= __put_user_inatomic(data.v[7], p++);
+               }
+               if (unlikely(ret))
+                       return -EFAULT;
+       } else {
+               *evr = data.w[0];
+               regs->gpr[reg] = data.w[1];
+       }
+
+       return 1;
+}
+#endif /* CONFIG_SPE */
 
 /*
  * Called on alignment exception. Attempts to fixup
@@ -450,6 +694,12 @@ int fix_alignment(struct pt_regs *regs)
        /* extract the operation and registers from the dsisr */
        reg = (dsisr >> 5) & 0x1f;      /* source/dest register */
        areg = dsisr & 0x1f;            /* register to update */
+
+#ifdef CONFIG_SPE
+       if ((instr >> 26) == 0x4)
+               return emulate_spe(regs, reg, instr);
+#endif
+
        instr = (dsisr >> 10) & 0x7f;
        instr |= (dsisr >> 13) & 0x60;