/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/* Integer Unit simulator for Sparc FPU simulator. */

#include <sys/fpu/fpu_simulator.h>
#include <sys/fpu/globals.h>

#include <sys/privregs.h>
#include <sys/vis_simulator.h>
#include <sys/asi.h>
#include <sys/simulate.h>
#include <sys/model.h>

#define	FPU_REG_FIELD uint32_reg	/* Coordinate with FPU_REGS_TYPE. */
#define	FPU_DREG_FIELD uint64_reg	/* Coordinate with FPU_DREGS_TYPE. */
#define	FPU_FSR_FIELD uint64_reg	/* Coordinate with V9_FPU_FSR_TYPE. */

/*
 * Simulator for loads and stores between floating-point unit and memory.
 */
enum ftt_type
fldst(
	fp_simd_type	*pfpsd,	/* FPU simulator data. */
	fp_inst_type	pinst,	/* FPU instruction to simulate. */
	struct regs	*pregs,	/* Pointer to PCB image of registers. */
	void		*prw)	/* Pointer to locals and ins. */
{
	uint32_t sz_bits, asi = 0;
	uint64_t fea, tea;
	uint64_t *ea;
	enum ftt_type   ftt;
	char *badaddr = (caddr_t)(-1);
	union {
		fp_inst_type	inst;
		int32_t		i;
	} fp;

	fp.inst = pinst;
	if ((pinst.op3 >> 4) & 1) {
		if (pinst.ibit) {
			asi = (uint32_t)((pregs->r_tstate >> TSTATE_ASI_SHIFT) &
			    TSTATE_ASI_MASK);
		} else {
			asi = (fp.i >> 5) & 0xff;
		}
		/* check for ld/st alternate and highest defined V9 asi */
		if (((pinst.op3 & 0x30) == 0x30) && (asi > ASI_SNFL))
			return (vis_fldst(pfpsd, pinst, pregs, prw, asi));
	}

	if (pinst.ibit == 0) {	/* effective address = rs1 + rs2 */
		ftt = read_iureg(pfpsd, pinst.rs1, pregs, prw, &fea);
		if (ftt != ftt_none)
			return (ftt);
		ftt = read_iureg(pfpsd, pinst.rs2, pregs, prw, &tea);
		if (ftt != ftt_none)
			return (ftt);
		ea = (uint64_t *)(fea + tea);
	} else {		/* effective address = rs1 + imm13 */
				/* Extract simm13 field. */
		fea = (uint64_t)((fp.i << 19) >> 19);
		ftt = read_iureg(pfpsd, pinst.rs1, pregs, prw, &tea);
		if (ftt != ftt_none)
			return (ftt);
		ea = (uint64_t *)(fea + tea);
	}
	sz_bits = pinst.op3 & 0x3;
	switch (sz_bits) {		/* map size bits to a number */
	case 0:					/* ldf{a}/stf{a} */
		/* Must be word-aligned. */
		if (((uintptr_t)ea & 0x3) != 0)
			return (ftt_alignment);
		break;
	case 1: if (pinst.rd == 0) {		/* ldfsr/stfsr */
			/* Must be word-aligned. */
			if (((uintptr_t)ea & 0x3) != 0)
				return (ftt_alignment);
		} else {			/* ldxfsr/stxfsr */
			/* Must be extword-aligned. */
			if (((uintptr_t)ea & 0x7) != 0)
				return (ftt_alignment);
		}
		break;
	case 2:					/* ldqf{a}/stqf{a} */
		/* Require only word alignment. */
		if (((uintptr_t)ea & 0x3) != 0)
			return (ftt_alignment);
		break;
	case 3:					/* lddf{a}/stdf{a} */
		if (get_udatamodel() == DATAMODEL_ILP32) {
			/* Require 64 bit-alignment. */
			if (((uintptr_t)ea & 0x7) != 0)
				return (ftt_alignment);
		} else {
			if (((uintptr_t)ea & 0x3) != 0)
				return (ftt_alignment);
		}
	}

	pfpsd->fp_trapaddr = (caddr_t)ea; /* setup bad addr in case we trap */
	if ((pinst.op3 >> 2) & 1)	/* store */
		pfpsd->fp_traprw = S_READ;
	else
		pfpsd->fp_traprw = S_WRITE;

	switch (do_unaligned(pregs, &badaddr)) {
	case SIMU_FAULT:
		return (ftt_fault);
	case SIMU_ILLEGAL:
		return (ftt_unimplemented);
	case SIMU_SUCCESS:
		break;
	}
	pregs->r_pc = pregs->r_npc;	/* Do not retry emulated instruction. */
	pregs->r_npc += 4;
	return (ftt_none);
}

/*
 * Floating-point conditional moves between floating point unit registers.
 */
static enum ftt_type
fmovcc_fcc(
	fp_simd_type	*pfpsd,	/* Pointer to fpu simulator data */
	fp_inst_type	inst,	/* FPU instruction to simulate. */
	fsr_type	*pfsr,	/* Pointer to image of FSR to read and write. */
	enum cc_type	cc)	/* FSR condition code field from fcc[0-3] */
{
	uint32_t	moveit;
	fsr_type	fsr;
	enum fcc_type	fcc;
	enum icc_type {
		fmovn, fmovne, fmovlg, fmovul, fmovl, fmovug, fmovg, fmovu,
		fmova, fmove, fmovue, fmovge, fmovuge, fmovle, fmovule, fmovo
	} cond;

	fsr = *pfsr;
	switch (cc) {
	case fcc_0:
		fcc = fsr.fcc0;
		break;
	case fcc_1:
		fcc = fsr.fcc1;
		break;
	case fcc_2:
		fcc = fsr.fcc2;
		break;
	case fcc_3:
		fcc = fsr.fcc3;
		break;
	default:
		return (ftt_unimplemented);
	}

	cond = (enum icc_type) (inst.rs1 & 0xf);
	switch (cond) {
	case fmovn:
		moveit = 0;
		break;
	case fmovl:
		moveit = fcc == fcc_less;
		break;
	case fmovg:
		moveit = fcc == fcc_greater;
		break;
	case fmovu:
		moveit = fcc == fcc_unordered;
		break;
	case fmove:
		moveit = fcc == fcc_equal;
		break;
	case fmovlg:
		moveit = (fcc == fcc_less) || (fcc == fcc_greater);
		break;
	case fmovul:
		moveit = (fcc == fcc_unordered) || (fcc == fcc_less);
		break;
	case fmovug:
		moveit = (fcc == fcc_unordered) || (fcc == fcc_greater);
		break;
	case fmovue:
		moveit = (fcc == fcc_unordered) || (fcc == fcc_equal);
		break;
	case fmovge:
		moveit = (fcc == fcc_greater) || (fcc == fcc_equal);
		break;
	case fmovle:
		moveit = (fcc == fcc_less) || (fcc == fcc_equal);
		break;
	case fmovne:
		moveit = fcc != fcc_equal;
		break;
	case fmovuge:
		moveit = fcc != fcc_less;
		break;
	case fmovule:
		moveit = fcc != fcc_greater;
		break;
	case fmovo:
		moveit = fcc != fcc_unordered;
		break;
	case fmova:
		moveit = 1;
		break;
	default:
		return (ftt_unimplemented);
	}
	if (moveit) {		/* Move fpu register. */
		uint32_t nrs2, nrd;
		uint32_t usr;
		uint64_t lusr;

		nrs2 = inst.rs2;
		nrd = inst.rd;
		if (inst.prec < 2) {	/* fmovs */
			_fp_unpack_word(pfpsd, &usr, nrs2);
			_fp_pack_word(pfpsd, &usr, nrd);
		} else {		/* fmovd */
			/* fix register encoding */
			if ((nrs2 & 1) == 1)
				nrs2 = (nrs2 & 0x1e) | 0x20;
			_fp_unpack_extword(pfpsd, &lusr, nrs2);
			if ((nrd & 1) == 1)
				nrd = (nrd & 0x1e) | 0x20;
			_fp_pack_extword(pfpsd, &lusr, nrd);
			if (inst.prec > 2) {		/* fmovq */
				_fp_unpack_extword(pfpsd, &lusr, nrs2+2);
				_fp_pack_extword(pfpsd, &lusr, nrd+2);
			}
		}
	}
	return (ftt_none);
}

/*
 * Integer conditional moves between floating point unit registers.
 */
static enum ftt_type
fmovcc_icc(
	fp_simd_type	*pfpsd,	/* Pointer to fpu simulator data */
	fp_inst_type	inst,	/* FPU instruction to simulate. */
	enum cc_type	cc)	/* CCR condition code field from tstate */
{
	int 	moveit;
	enum icc_type {
		fmovn, fmove, fmovle, fmovl, fmovleu, fmovcs, fmovneg, fmovvs,
		fmova, fmovne, fmovg, fmovge, fmovgu, fmovcc, fmovpos, fmovvc
	} cond;

	struct regs *pregs;
	uint64_t tstate;
	union {
		uint32_t	i;
		ccr_type	cc;
	} ccr;

	pregs = lwptoregs(curthread->t_lwp);
	tstate = pregs->r_tstate;
	switch (cc) {
	case icc:
		ccr.i = (uint32_t)((tstate >> TSTATE_CCR_SHIFT) & 0xf);
		break;
	case xcc:
		ccr.i = (uint32_t)(((tstate >> TSTATE_CCR_SHIFT) & 0xf0) >> 4);
		break;
	}

	cond = (enum icc_type) (inst.rs1 & 0xf);
	switch (cond) {
	case fmovn:
		moveit = 0;
		break;
	case fmove:
		moveit = (int)(ccr.cc.z);
		break;
	case fmovle:
		moveit = (int)(ccr.cc.z | (ccr.cc.n ^ ccr.cc.v));
		break;
	case fmovl:
		moveit = (int)(ccr.cc.n ^ ccr.cc.v);
		break;
	case fmovleu:
		moveit = (int)(ccr.cc.c | ccr.cc.z);
		break;
	case fmovcs:
		moveit = (int)(ccr.cc.c);
		break;
	case fmovneg:
		moveit = (int)(ccr.cc.n);
		break;
	case fmovvs:
		moveit = (int)(ccr.cc.v);
		break;
	case fmova:
		moveit = 1;
		break;
	case fmovne:
		moveit = (int)(ccr.cc.z == 0);
		break;
	case fmovg:
		moveit = (int)((ccr.cc.z | (ccr.cc.n ^ ccr.cc.v)) == 0);
		break;
	case fmovge:
		moveit = (int)((ccr.cc.n ^ ccr.cc.v) == 0);
		break;
	case fmovgu:
		moveit = (int)((ccr.cc.c | ccr.cc.z) == 0);
		break;
	case fmovcc:
		moveit = (int)(ccr.cc.c == 0);
		break;
	case fmovpos:
		moveit = (int)(ccr.cc.n == 0);
		break;
	case fmovvc:
		moveit = (int)(ccr.cc.v == 0);
		break;
	default:
		return (ftt_unimplemented);
	}
	if (moveit) {		/* Move fpu register. */
		uint32_t nrs2, nrd;
		uint32_t usr;
		uint64_t lusr;

		nrs2 = inst.rs2;
		nrd = inst.rd;
		if (inst.prec < 2) {	/* fmovs */
			_fp_unpack_word(pfpsd, &usr, nrs2);
			_fp_pack_word(pfpsd, &usr, nrd);
		} else {		/* fmovd */
			/* fix register encoding */
			if ((nrs2 & 1) == 1)
				nrs2 = (nrs2 & 0x1e) | 0x20;
			_fp_unpack_extword(pfpsd, &lusr, nrs2);
			if ((nrd & 1) == 1)
				nrd = (nrd & 0x1e) | 0x20;
			_fp_pack_extword(pfpsd, &lusr, nrd);
			if (inst.prec > 2) {		/* fmovq */
				_fp_unpack_extword(pfpsd, &lusr, nrs2+2);
				_fp_pack_extword(pfpsd, &lusr, nrd+2);
			}
		}
	}
	return (ftt_none);
}

/*
 * Simulator for moving fp register on condition (FMOVcc).
 * FMOVccq (Quad version of instruction) not supported by Ultra-1, so this
 * code must always be present.
 */
enum ftt_type
fmovcc(
	fp_simd_type	*pfpsd,	/* Pointer to fpu simulator data */
	fp_inst_type	inst,	/* FPU instruction to simulate. */
	fsr_type	*pfsr)	/* Pointer to image of FSR to read and write. */
{
	enum cc_type	opf_cc;

	opf_cc = (enum cc_type) ((inst.ibit << 2) | (inst.opcode >> 4));
	if ((opf_cc == icc) || (opf_cc == xcc)) {
		return (fmovcc_icc(pfpsd, inst, opf_cc));
	} else {
		return (fmovcc_fcc(pfpsd, inst, pfsr, opf_cc));
	}
}

/*
 * Simulator for moving fp register on integer register condition (FMOVr).
 * FMOVrq (Quad version of instruction) not supported by Ultra-1, so this
 * code must always be present.
 */
enum ftt_type
fmovr(
	fp_simd_type	*pfpsd,	/* Pointer to fpu simulator data */
	fp_inst_type	inst)	/* FPU instruction to simulate. */
{
	struct regs	*pregs;
	ulong_t		*prw;
	uint32_t	nrs1;
	enum ftt_type	ftt;
	enum rcond_type {
		none, fmovre, fmovrlez, fmovrlz,
		nnone, fmovrne, fmovrgz, fmovrgez
	} rcond;
	int64_t moveit, r;

	nrs1 = inst.rs1;
	if (nrs1 > 15)		/* rs1 must be a global register */
		return (ftt_unimplemented);
	if (inst.ibit)		/* ibit must be unused */
		return (ftt_unimplemented);
	pregs = lwptoregs(curthread->t_lwp);
	prw = (ulong_t *)pregs->r_sp;
	ftt = read_iureg(pfpsd, nrs1, pregs, prw, (uint64_t *)&r);
	if (ftt != ftt_none)
		return (ftt);
	rcond = (enum rcond_type) (inst.opcode >> 3) & 7;
	switch (rcond) {
	case fmovre:
		moveit = r == 0;
		break;
	case fmovrlez:
		moveit = r <= 0;
		break;
	case fmovrlz:
		moveit = r < 0;
		break;
	case fmovrne:
		moveit = r != 0;
		break;
	case fmovrgz:
		moveit = r > 0;
		break;
	case fmovrgez:
		moveit = r >= 0;
		break;
	default:
		return (ftt_unimplemented);
	}
	if (moveit) {		/* Move fpu register. */
		uint32_t nrs2, nrd;
		uint32_t usr;
		uint64_t lusr;

		nrs2 = inst.rs2;
		nrd = inst.rd;
		if (inst.prec < 2) {	/* fmovs */
			_fp_unpack_word(pfpsd, &usr, nrs2);
			_fp_pack_word(pfpsd, &usr, nrd);
		} else {		/* fmovd */
			_fp_unpack_extword(pfpsd, &lusr, nrs2);
			_fp_pack_extword(pfpsd, &lusr, nrd);
			if (inst.prec > 2) {		/* fmovq */
				_fp_unpack_extword(pfpsd, &lusr, nrs2+2);
				_fp_pack_extword(pfpsd, &lusr, nrd+2);
			}
		}
	}
	return (ftt_none);
}

/*
 * Move integer register on condition (MOVcc).
 */
enum ftt_type
movcc(
	fp_simd_type	*pfpsd, /* Pointer to fpu simulator data */
	fp_inst_type    pinst,	/* FPU instruction to simulate. */
	struct regs	*pregs,	/* Pointer to PCB image of registers. */
	void		*prw,	/* Pointer to locals and ins. */
	kfpu_t		*pfpu)	/* Pointer to FPU register block. */

{
	fsr_type	fsr;
	enum cc_type	cc;
	enum fcc_type	fcc;
	enum icc_type {
		fmovn, fmovne, fmovlg, fmovul, fmovl, fmovug, fmovg, fmovu,
		fmova, fmove, fmovue, fmovge, fmovuge, fmovle, fmovule, fmovo
	} cond;
	uint32_t moveit;
	enum ftt_type ftt = ftt_none;

	cc = (enum cc_type) (pinst.opcode >> 0x4) & 3;
	fsr.ll = pfpu->fpu_fsr;
	cond = (enum icc_type) (pinst.rs1 & 0xf);
	switch (cc) {
	case fcc_0:
		fcc = fsr.fcc0;
		break;
	case fcc_1:
		fcc = fsr.fcc1;
		break;
	case fcc_2:
		fcc = fsr.fcc2;
		break;
	case fcc_3:
		fcc = fsr.fcc3;
		break;
	default:
		return (ftt_unimplemented);
	}

	switch (cond) {
	case fmovn:
		moveit = 0;
		break;
	case fmovl:
		moveit = fcc == fcc_less;
		break;
	case fmovg:
		moveit = fcc == fcc_greater;
		break;
	case fmovu:
		moveit = fcc == fcc_unordered;
		break;
	case fmove:
		moveit = fcc == fcc_equal;
		break;
	case fmovlg:
		moveit = (fcc == fcc_less) || (fcc == fcc_greater);
		break;
	case fmovul:
		moveit = (fcc == fcc_unordered) || (fcc == fcc_less);
		break;
	case fmovug:
		moveit = (fcc == fcc_unordered) || (fcc == fcc_greater);
		break;
	case fmovue:
		moveit = (fcc == fcc_unordered) || (fcc == fcc_equal);
		break;
	case fmovge:
		moveit = (fcc == fcc_greater) || (fcc == fcc_equal);
		break;
	case fmovle:
		moveit = (fcc == fcc_less) || (fcc == fcc_equal);
		break;
	case fmovne:
		moveit = fcc != fcc_equal;
		break;
	case fmovuge:
		moveit = fcc != fcc_less;
		break;
	case fmovule:
		moveit = fcc != fcc_greater;
		break;
	case fmovo:
		moveit = fcc != fcc_unordered;
		break;
	case fmova:
		moveit = 1;
		break;
	default:
		return (ftt_unimplemented);
	}
	if (moveit) {		/* Move fpu register. */
		uint32_t nrd;
		uint64_t r;

		nrd = pinst.rd;
		if (pinst.ibit == 0) {	/* copy the value in r[rs2] */
			uint32_t nrs2;

			nrs2 = pinst.rs2;
			ftt = read_iureg(pfpsd, nrs2, pregs, prw, &r);
			if (ftt != ftt_none)
				return (ftt);
			ftt = write_iureg(pfpsd, nrd, pregs, prw, &r);
		} else {		/* use sign_ext(simm11) */
			union {
				fp_inst_type	inst;
				int32_t		i;
			} fp;

			fp.inst = pinst;	/* Extract simm11 field */
			r = (fp.i << 21) >> 21;
			ftt = write_iureg(pfpsd, nrd, pregs, prw, &r);
		}
	}
	pregs->r_pc = pregs->r_npc;	/* Do not retry emulated instruction. */
	pregs->r_npc += 4;
	return (ftt);
}