case EFSCTSF:
                case EFSCTUF:
-                       if (!((vb.wp[1] >> 23) == 0xff && ((vb.wp[1] & 0x7fffff) > 0))) {
-                               /* NaN */
-                               if (((vb.wp[1] >> 23) & 0xff) == 0) {
-                                       /* denorm */
-                                       vc.wp[1] = 0x0;
-                               } else if ((vb.wp[1] >> 31) == 0) {
-                                       /* positive normal */
-                                       vc.wp[1] = (func == EFSCTSF) ?
-                                               0x7fffffff : 0xffffffff;
-                               } else { /* negative normal */
-                                       vc.wp[1] = (func == EFSCTSF) ?
-                                               0x80000000 : 0x0;
-                               }
-                       } else { /* rB is NaN */
-                               vc.wp[1] = 0x0;
+                       if (SB_c == FP_CLS_NAN) {
+                               vc.wp[1] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               SB_e += (func == EFSCTSF ? 31 : 32);
+                               FP_TO_INT_ROUND_S(vc.wp[1], SB, 32,
+                                               (func == EFSCTSF));
                        }
                        goto update_regs;
 
                }
 
                case EFSCTSI:
-               case EFSCTSIZ:
                case EFSCTUI:
+                       if (SB_c == FP_CLS_NAN) {
+                               vc.wp[1] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               FP_TO_INT_ROUND_S(vc.wp[1], SB, 32,
+                                               ((func & 0x3) != 0));
+                       }
+                       goto update_regs;
+
+               case EFSCTSIZ:
                case EFSCTUIZ:
-                       if (func & 0x4) {
-                               _FP_ROUND(1, SB);
+                       if (SB_c == FP_CLS_NAN) {
+                               vc.wp[1] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
                        } else {
-                               _FP_ROUND_ZERO(1, SB);
+                               FP_TO_INT_S(vc.wp[1], SB, 32,
+                                               ((func & 0x3) != 0));
                        }
-                       FP_TO_INT_S(vc.wp[1], SB, 32,
-                                       (((func & 0x3) != 0) || SB_s));
                        goto update_regs;
 
                default:
 
                case EFDCTSF:
                case EFDCTUF:
-                       if (!((vb.wp[0] >> 20) == 0x7ff &&
-                          ((vb.wp[0] & 0xfffff) > 0 || (vb.wp[1] > 0)))) {
-                               /* not a NaN */
-                               if (((vb.wp[0] >> 20) & 0x7ff) == 0) {
-                                       /* denorm */
-                                       vc.wp[1] = 0x0;
-                               } else if ((vb.wp[0] >> 31) == 0) {
-                                       /* positive normal */
-                                       vc.wp[1] = (func == EFDCTSF) ?
-                                               0x7fffffff : 0xffffffff;
-                               } else { /* negative normal */
-                                       vc.wp[1] = (func == EFDCTSF) ?
-                                               0x80000000 : 0x0;
-                               }
-                       } else { /* NaN */
-                               vc.wp[1] = 0x0;
+                       if (DB_c == FP_CLS_NAN) {
+                               vc.wp[1] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               DB_e += (func == EFDCTSF ? 31 : 32);
+                               FP_TO_INT_ROUND_D(vc.wp[1], DB, 32,
+                                               (func == EFDCTSF));
                        }
                        goto update_regs;
 
 
                case EFDCTUIDZ:
                case EFDCTSIDZ:
-                       _FP_ROUND_ZERO(2, DB);
-                       FP_TO_INT_D(vc.dp[0], DB, 64, ((func & 0x1) == 0));
+                       if (DB_c == FP_CLS_NAN) {
+                               vc.dp[0] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               FP_TO_INT_D(vc.dp[0], DB, 64,
+                                               ((func & 0x1) == 0));
+                       }
                        goto update_regs;
 
                case EFDCTUI:
                case EFDCTSI:
+                       if (DB_c == FP_CLS_NAN) {
+                               vc.wp[1] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               FP_TO_INT_ROUND_D(vc.wp[1], DB, 32,
+                                               ((func & 0x3) != 0));
+                       }
+                       goto update_regs;
+
                case EFDCTUIZ:
                case EFDCTSIZ:
-                       if (func & 0x4) {
-                               _FP_ROUND(2, DB);
+                       if (DB_c == FP_CLS_NAN) {
+                               vc.wp[1] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
                        } else {
-                               _FP_ROUND_ZERO(2, DB);
+                               FP_TO_INT_D(vc.wp[1], DB, 32,
+                                               ((func & 0x3) != 0));
                        }
-                       FP_TO_INT_D(vc.wp[1], DB, 32,
-                                       (((func & 0x3) != 0) || DB_s));
                        goto update_regs;
 
                default:
                        cmp = -1;
                        goto cmp_vs;
 
-               case EVFSCTSF:
-                       __asm__ __volatile__ ("mtspr 512, %4\n"
-                               "efsctsf %0, %2\n"
-                               "efsctsf %1, %3\n"
-                               : "=r" (vc.wp[0]), "=r" (vc.wp[1])
-                               : "r" (vb.wp[0]), "r" (vb.wp[1]), "r" (0));
-                       goto update_regs;
-
                case EVFSCTUF:
-                       __asm__ __volatile__ ("mtspr 512, %4\n"
-                               "efsctuf %0, %2\n"
-                               "efsctuf %1, %3\n"
-                               : "=r" (vc.wp[0]), "=r" (vc.wp[1])
-                               : "r" (vb.wp[0]), "r" (vb.wp[1]), "r" (0));
+               case EVFSCTSF:
+                       if (SB0_c == FP_CLS_NAN) {
+                               vc.wp[0] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               SB0_e += (func == EVFSCTSF ? 31 : 32);
+                               FP_TO_INT_ROUND_S(vc.wp[0], SB0, 32,
+                                               (func == EVFSCTSF));
+                       }
+                       if (SB1_c == FP_CLS_NAN) {
+                               vc.wp[1] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               SB1_e += (func == EVFSCTSF ? 31 : 32);
+                               FP_TO_INT_ROUND_S(vc.wp[1], SB1, 32,
+                                               (func == EVFSCTSF));
+                       }
                        goto update_regs;
 
                case EVFSCTUI:
                case EVFSCTSI:
+                       if (SB0_c == FP_CLS_NAN) {
+                               vc.wp[0] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               FP_TO_INT_ROUND_S(vc.wp[0], SB0, 32,
+                                               ((func & 0x3) != 0));
+                       }
+                       if (SB1_c == FP_CLS_NAN) {
+                               vc.wp[1] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               FP_TO_INT_ROUND_S(vc.wp[1], SB1, 32,
+                                               ((func & 0x3) != 0));
+                       }
+                       goto update_regs;
+
                case EVFSCTUIZ:
                case EVFSCTSIZ:
-                       if (func & 0x4) {
-                               _FP_ROUND(1, SB0);
-                               _FP_ROUND(1, SB1);
+                       if (SB0_c == FP_CLS_NAN) {
+                               vc.wp[0] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
                        } else {
-                               _FP_ROUND_ZERO(1, SB0);
-                               _FP_ROUND_ZERO(1, SB1);
+                               FP_TO_INT_S(vc.wp[0], SB0, 32,
+                                               ((func & 0x3) != 0));
+                       }
+                       if (SB1_c == FP_CLS_NAN) {
+                               vc.wp[1] = 0;
+                               FP_SET_EXCEPTION(FP_EX_INVALID);
+                       } else {
+                               FP_TO_INT_S(vc.wp[1], SB1, 32,
+                                               ((func & 0x3) != 0));
                        }
-                       FP_TO_INT_S(vc.wp[0], SB0, 32,
-                                       (((func & 0x3) != 0) || SB0_s));
-                       FP_TO_INT_S(vc.wp[1], SB1, 32,
-                                       (((func & 0x3) != 0) || SB1_s));
                        goto update_regs;
 
                default:
        union dw_union fgpr;
        int s_lo, s_hi;
        int lo_inexact, hi_inexact;
-       unsigned long speinsn, type, fc, fptype;
+       int fp_result;
+       unsigned long speinsn, type, fb, fc, fptype, func;
 
        if (get_user(speinsn, (unsigned int __user *) regs->nip))
                return -EFAULT;
        if ((speinsn >> 26) != 4)
                return -EINVAL;         /* not an spe instruction */
 
-       type = insn_type(speinsn & 0x7ff);
+       func = speinsn & 0x7ff;
+       type = insn_type(func);
        if (type == XCR) return -ENOSYS;
 
        __FPU_FPSCR = mfspr(SPRN_SPEFSCR);
        fgpr.wp[0] = current->thread.evr[fc];
        fgpr.wp[1] = regs->gpr[fc];
 
+       fb = (speinsn >> 11) & 0x1f;
+       switch (func) {
+       case EFSCTUIZ:
+       case EFSCTSIZ:
+       case EVFSCTUIZ:
+       case EVFSCTSIZ:
+       case EFDCTUIDZ:
+       case EFDCTSIDZ:
+       case EFDCTUIZ:
+       case EFDCTSIZ:
+               /*
+                * These instructions always round to zero,
+                * independent of the rounding mode.
+                */
+               return 0;
+
+       case EFSCTUI:
+       case EFSCTUF:
+       case EVFSCTUI:
+       case EVFSCTUF:
+       case EFDCTUI:
+       case EFDCTUF:
+               fp_result = 0;
+               s_lo = 0;
+               s_hi = 0;
+               break;
+
+       case EFSCTSI:
+       case EFSCTSF:
+               fp_result = 0;
+               /* Recover the sign of a zero result if possible.  */
+               if (fgpr.wp[1] == 0)
+                       s_lo = regs->gpr[fb] & SIGN_BIT_S;
+               break;
+
+       case EVFSCTSI:
+       case EVFSCTSF:
+               fp_result = 0;
+               /* Recover the sign of a zero result if possible.  */
+               if (fgpr.wp[1] == 0)
+                       s_lo = regs->gpr[fb] & SIGN_BIT_S;
+               if (fgpr.wp[0] == 0)
+                       s_hi = current->thread.evr[fb] & SIGN_BIT_S;
+               break;
+
+       case EFDCTSI:
+       case EFDCTSF:
+               fp_result = 0;
+               s_hi = s_lo;
+               /* Recover the sign of a zero result if possible.  */
+               if (fgpr.wp[1] == 0)
+                       s_hi = current->thread.evr[fb] & SIGN_BIT_S;
+               break;
+
+       default:
+               fp_result = 1;
+               break;
+       }
+
        pr_debug("round fgpr: %08x  %08x\n", fgpr.wp[0], fgpr.wp[1]);
 
        switch (fptype) {
                if ((FP_ROUNDMODE) == FP_RND_PINF) {
                        if (!s_lo) fgpr.wp[1]++; /* Z > 0, choose Z1 */
                } else { /* round to -Inf */
-                       if (s_lo) fgpr.wp[1]++; /* Z < 0, choose Z2 */
+                       if (s_lo) {
+                               if (fp_result)
+                                       fgpr.wp[1]++; /* Z < 0, choose Z2 */
+                               else
+                                       fgpr.wp[1]--; /* Z < 0, choose Z2 */
+                       }
                }
                break;
 
        case DPFP:
                if (FP_ROUNDMODE == FP_RND_PINF) {
-                       if (!s_hi) fgpr.dp[0]++; /* Z > 0, choose Z1 */
+                       if (!s_hi) {
+                               if (fp_result)
+                                       fgpr.dp[0]++; /* Z > 0, choose Z1 */
+                               else
+                                       fgpr.wp[1]++; /* Z > 0, choose Z1 */
+                       }
                } else { /* round to -Inf */
-                       if (s_hi) fgpr.dp[0]++; /* Z < 0, choose Z2 */
+                       if (s_hi) {
+                               if (fp_result)
+                                       fgpr.dp[0]++; /* Z < 0, choose Z2 */
+                               else
+                                       fgpr.wp[1]--; /* Z < 0, choose Z2 */
+                       }
                }
                break;
 
                        if (hi_inexact && !s_hi)
                                fgpr.wp[0]++; /* Z_high word > 0, choose Z1 */
                } else { /* round to -Inf */
-                       if (lo_inexact && s_lo)
-                               fgpr.wp[1]++; /* Z_low < 0, choose Z2 */
-                       if (hi_inexact && s_hi)
-                               fgpr.wp[0]++; /* Z_high < 0, choose Z2 */
+                       if (lo_inexact && s_lo) {
+                               if (fp_result)
+                                       fgpr.wp[1]++; /* Z_low < 0, choose Z2 */
+                               else
+                                       fgpr.wp[1]--; /* Z_low < 0, choose Z2 */
+                       }
+                       if (hi_inexact && s_hi) {
+                               if (fp_result)
+                                       fgpr.wp[0]++; /* Z_high < 0, choose Z2 */
+                               else
+                                       fgpr.wp[0]--; /* Z_high < 0, choose Z2 */
+                       }
                }
                break;