]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - arch/powerpc/kernel/align.c
[POWERPC] Timer interrupt: use a struct for two per_cpu varables
[linux-2.6-omap-h63xx.git] / arch / powerpc / kernel / align.c
index 4734b5de599dd516fc612a72d9a1b3258edb544d..e06f75daeba368ca231ec3d8af43db2285e68aba 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 */
@@ -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 */
@@ -87,9 +89,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 +169,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 */
@@ -241,7 +243,7 @@ static int emulate_dcbz(struct pt_regs *regs, unsigned char __user *addr)
        if (user_mode(regs) && !access_ok(VERIFY_WRITE, p, size))
                return -EFAULT;
        for (i = 0; i < size / sizeof(long); ++i)
-               if (__put_user(0, p+i))
+               if (__put_user_inatomic(0, p+i))
                        return -EFAULT;
        return 1;
 }
@@ -288,7 +290,8 @@ static int emulate_multiple(struct pt_regs *regs, unsigned char __user *addr,
                } else {
                        unsigned long pc = regs->nip ^ (swiz & 4);
 
-                       if (__get_user(instr, (unsigned int __user *)pc))
+                       if (__get_user_inatomic(instr,
+                                               (unsigned int __user *)pc))
                                return -EFAULT;
                        if (swiz == 0 && (flags & SW))
                                instr = cpu_to_le32(instr);
@@ -324,33 +327,315 @@ static int emulate_multiple(struct pt_regs *regs, unsigned char __user *addr,
                               ((nb0 + 3) / 4) * sizeof(unsigned long));
 
                for (i = 0; i < nb; ++i, ++p)
-                       if (__get_user(REG_BYTE(rptr, i ^ bswiz), SWIZ_PTR(p)))
+                       if (__get_user_inatomic(REG_BYTE(rptr, i ^ bswiz),
+                                               SWIZ_PTR(p)))
                                return -EFAULT;
                if (nb0 > 0) {
                        rptr = &regs->gpr[0];
                        addr += nb;
                        for (i = 0; i < nb0; ++i, ++p)
-                               if (__get_user(REG_BYTE(rptr, i ^ bswiz),
-                                              SWIZ_PTR(p)))
+                               if (__get_user_inatomic(REG_BYTE(rptr,
+                                                                i ^ bswiz),
+                                                       SWIZ_PTR(p)))
                                        return -EFAULT;
                }
 
        } else {
                for (i = 0; i < nb; ++i, ++p)
-                       if (__put_user(REG_BYTE(rptr, i ^ bswiz), SWIZ_PTR(p)))
+                       if (__put_user_inatomic(REG_BYTE(rptr, i ^ bswiz),
+                                               SWIZ_PTR(p)))
                                return -EFAULT;
                if (nb0 > 0) {
                        rptr = &regs->gpr[0];
                        addr += nb;
                        for (i = 0; i < nb0; ++i, ++p)
-                               if (__put_user(REG_BYTE(rptr, i ^ bswiz),
-                                              SWIZ_PTR(p)))
+                               if (__put_user_inatomic(REG_BYTE(rptr,
+                                                                i ^ bswiz),
+                                                       SWIZ_PTR(p)))
                                        return -EFAULT;
                }
        }
        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 */
+}
+
+#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
@@ -398,7 +683,8 @@ int fix_alignment(struct pt_regs *regs)
 
                if (cpu_has_feature(CPU_FTR_PPC_LE) && (regs->msr & MSR_LE))
                        pc ^= 4;
-               if (unlikely(__get_user(instr, (unsigned int __user *)pc)))
+               if (unlikely(__get_user_inatomic(instr,
+                                                (unsigned int __user *)pc)))
                        return -EFAULT;
                if (cpu_has_feature(CPU_FTR_REAL_LE) && (regs->msr & MSR_LE))
                        instr = cpu_to_le32(instr);
@@ -408,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;
 
@@ -465,6 +757,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
         */
@@ -474,16 +770,16 @@ int fix_alignment(struct pt_regs *regs)
                p = (unsigned long) addr;
                switch (nb) {
                case 8:
-                       ret |= __get_user(data.v[0], SWIZ_PTR(p++));
-                       ret |= __get_user(data.v[1], SWIZ_PTR(p++));
-                       ret |= __get_user(data.v[2], SWIZ_PTR(p++));
-                       ret |= __get_user(data.v[3], SWIZ_PTR(p++));
+                       ret |= __get_user_inatomic(data.v[0], SWIZ_PTR(p++));
+                       ret |= __get_user_inatomic(data.v[1], SWIZ_PTR(p++));
+                       ret |= __get_user_inatomic(data.v[2], SWIZ_PTR(p++));
+                       ret |= __get_user_inatomic(data.v[3], SWIZ_PTR(p++));
                case 4:
-                       ret |= __get_user(data.v[4], SWIZ_PTR(p++));
-                       ret |= __get_user(data.v[5], SWIZ_PTR(p++));
+                       ret |= __get_user_inatomic(data.v[4], SWIZ_PTR(p++));
+                       ret |= __get_user_inatomic(data.v[5], SWIZ_PTR(p++));
                case 2:
-                       ret |= __get_user(data.v[6], SWIZ_PTR(p++));
-                       ret |= __get_user(data.v[7], SWIZ_PTR(p++));
+                       ret |= __get_user_inatomic(data.v[6], SWIZ_PTR(p++));
+                       ret |= __get_user_inatomic(data.v[7], SWIZ_PTR(p++));
                        if (unlikely(ret))
                                return -EFAULT;
                }
@@ -525,7 +821,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 */
@@ -551,16 +848,16 @@ int fix_alignment(struct pt_regs *regs)
                p = (unsigned long) addr;
                switch (nb) {
                case 8:
-                       ret |= __put_user(data.v[0], SWIZ_PTR(p++));
-                       ret |= __put_user(data.v[1], SWIZ_PTR(p++));
-                       ret |= __put_user(data.v[2], SWIZ_PTR(p++));
-                       ret |= __put_user(data.v[3], SWIZ_PTR(p++));
+                       ret |= __put_user_inatomic(data.v[0], SWIZ_PTR(p++));
+                       ret |= __put_user_inatomic(data.v[1], SWIZ_PTR(p++));
+                       ret |= __put_user_inatomic(data.v[2], SWIZ_PTR(p++));
+                       ret |= __put_user_inatomic(data.v[3], SWIZ_PTR(p++));
                case 4:
-                       ret |= __put_user(data.v[4], SWIZ_PTR(p++));
-                       ret |= __put_user(data.v[5], SWIZ_PTR(p++));
+                       ret |= __put_user_inatomic(data.v[4], SWIZ_PTR(p++));
+                       ret |= __put_user_inatomic(data.v[5], SWIZ_PTR(p++));
                case 2:
-                       ret |= __put_user(data.v[6], SWIZ_PTR(p++));
-                       ret |= __put_user(data.v[7], SWIZ_PTR(p++));
+                       ret |= __put_user_inatomic(data.v[6], SWIZ_PTR(p++));
+                       ret |= __put_user_inatomic(data.v[7], SWIZ_PTR(p++));
                }
                if (unlikely(ret))
                        return -EFAULT;