int speround_handler()

in math-emu/math_efp.c [722:887]


int speround_handler(struct pt_regs *regs)
{
	union dw_union fgpr;
	int s_lo, s_hi;
	int lo_inexact, hi_inexact;
	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 */

	func = speinsn & 0x7ff;
	type = insn_type(func);
	if (type == XCR) return -ENOSYS;

	__FPU_FPSCR = mfspr(SPRN_SPEFSCR);
	pr_debug("speinsn:%08lx spefscr:%08lx\n", speinsn, __FPU_FPSCR);

	fptype = (speinsn >> 5) & 0x7;

	/* No need to round if the result is exact */
	lo_inexact = __FPU_FPSCR & (SPEFSCR_FG | SPEFSCR_FX);
	hi_inexact = __FPU_FPSCR & (SPEFSCR_FGH | SPEFSCR_FXH);
	if (!(lo_inexact || (hi_inexact && fptype == VCT)))
		return 0;

	fc = (speinsn >> 21) & 0x1f;
	s_lo = regs->gpr[fc] & SIGN_BIT_S;
	s_hi = current->thread.evr[fc] & SIGN_BIT_S;
	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) {
	/* Since SPE instructions on E500 core can handle round to nearest
	 * and round toward zero with IEEE-754 complied, we just need
	 * to handle round toward +Inf and round toward -Inf by software.
	 */
	case SPFP:
		if ((FP_ROUNDMODE) == FP_RND_PINF) {
			if (!s_lo) fgpr.wp[1]++; /* Z > 0, choose Z1 */
		} else { /* round to -Inf */
			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) {
				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) {
				if (fp_result)
					fgpr.dp[0]++; /* Z < 0, choose Z2 */
				else
					fgpr.wp[1]--; /* Z < 0, choose Z2 */
			}
		}
		break;

	case VCT:
		if (FP_ROUNDMODE == FP_RND_PINF) {
			if (lo_inexact && !s_lo)
				fgpr.wp[1]++; /* Z_low > 0, choose Z1 */
			if (hi_inexact && !s_hi)
				fgpr.wp[0]++; /* Z_high word > 0, choose Z1 */
		} else { /* round to -Inf */
			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;

	default:
		return -EINVAL;
	}

	current->thread.evr[fc] = fgpr.wp[0];
	regs->gpr[fc] = fgpr.wp[1];

	pr_debug("  to fgpr: %08x  %08x\n", fgpr.wp[0], fgpr.wp[1]);

	if (current->thread.fpexc_mode & PR_FP_EXC_SW_ENABLE)
		return (current->thread.fpexc_mode & PR_FP_EXC_RES) ? 1 : 0;
	return 0;
}