/*
 * 5799-WZQ (C) COPYRIGHT IBM CORPORATION 1987,1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */
/* $Header:emul.c 12.0$ */
/* $ACIS:emul.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/ca/RCS/emul.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:emul.c 12.0$";
#endif

/*
 * This file contains all the routines which are needed to support the
 * floating point emulator.
 */

#include "debug.h"
#include "types.h"
#include "param.h"
#include "dir.h"
#include "signal.h"
#include "user.h"
#include "proc.h"
#include "vmparam.h"

#include "float.h"
#include "fp.h"

/*
 * emul_handle_cleanup
 *
 * This is a routine shared between the emulator handler and the
 * fpa handler.  It updates the USER_FPM data area with what should
 * be there (the new result, and the status register), and
 * then returns some values.  The values are:
 *
 *	Which register contains the updated result.
 *	The size of the changed result (0, 4, 8 bytes).
 *		o	For 0, no result should be updated.
 *	Whether the status should be updated.
 */

void
emul_handle_cleanup(sf_scp, pRegister, pResultSize, pUpdateStatus)
struct sigcontext *sf_scp;
int
	*pRegister,		/* Register which holds result */
	*pResultSize,		/* 0, 4, or 8 */
	*pUpdateStatus;		/* Whether to update status */
{
    fptrap *fpt;
    FP_STATUS fpstat;
    FP_MACH usermach;
    FP_MACH *sigmach;
    int destreg;
    FLOAT floatres;
    DOUBLE doubleres;

    copyin((caddr_t)USER_FPM,(caddr_t)(&usermach),sizeof(FP_MACH));

    sigmach = (FP_MACH *)sf_scp->sc_floatsave;
    fpt = &(sigmach->fptrap);
    fpstat = sigmach->status;

    destreg = *pRegister = fpt->fptrapinfo.dest;

    switch(fpt->fptrapinfo.operation) {
	    /* There should be a better way... */
	case FP_abf: case FP_adf: case FP_adfi:
	case FP_d2f: case FP_d2fi: case FP_dvf: case FP_dvfi:
	case FP_flf: case FP_flfi:
	case FP_i2f:
	case FP_mlf: case FP_mlfi:
	case FP_ngf: case FP_ntf: case FP_ntfi:
	case FP_rmf: case FP_rmfi:
	case FP_rnf: case FP_rnfi:
	case FP_sbf: case FP_sbfi:
	case FP_sqf: case FP_sqfi:
	case FP_trf: case FP_trfi:
	    /* floatres should be pulled from FP_MACH !! */
	    floatres = sigmach->dreg[destreg/2].dfracth;
	    usermach.dreg[destreg/2].dfracth = floatres;
	    *pResultSize = sizeof floatres;	/* "short" result */
	    usermach.status = fpstat;
	    *pUpdateStatus = 1;			/* Update status */
	    break;

	case FP_abd: case FP_add: case FP_addi:
	case FP_dvd: case FP_dvdi:
	case FP_f2d: case FP_f2di:
	case FP_fld: case FP_fldi:
	case FP_i2d:
	case FP_mld: case FP_mldi:
	case FP_ngd: case FP_ntd: case FP_ntdi:
	case FP_rmd: case FP_rmdi:
	case FP_rnd: case FP_rndi:
	case FP_sbd: case FP_sbdi:
	case FP_sqd: case FP_sqdi:
	case FP_trd: case FP_trdi:

	    /* doubleres should be pulled from FP_MACH !! */
	    doubleres.dfracth = sigmach->dreg[destreg/2].dfracth;
	    doubleres.dfractl = sigmach->dreg[destreg/2].dfractl;
	    usermach.dreg[destreg/2].dfracth = doubleres.dfracth;
	    usermach.dreg[destreg/2].dfractl = doubleres.dfractl;
	    *pResultSize = sizeof doubleres;	/* "long" result */
	    usermach.status = fpstat;
	    *pUpdateStatus = 1;			/* Update status */
	    break;

	    /* no result from compares */
	case FP_cmf:
	case FP_cmd:
	    *pResultSize = 0;		/* No result to update */
	    usermach.status = fpstat;
	    *pUpdateStatus = 1;		/* Update status */
	    break;

	default:	/* unused or undefined operation */
	    *pResultSize = 0;		/* No result to update */
	    *pUpdateStatus = 0;		/* Don't update status */
	    break;
    }
    copyout((caddr_t)(&usermach), (caddr_t)USER_FPM,sizeof(FP_MACH));
}

/* The following routines are in alphabetical order and called from float.c */

/*
 * emul_core() :
 *
 * Called to get the register set (if it exists)
 * into the user address space so that a debugger can look at it.
 *
 * In the emulator case, the registers are already in USER_FPM,
 * so we don't need to do anything.
 */

void
emul_core()
{
}

/*
 * emul_exit:
 *
 * When a process exits, we may have to do some clean up.
 */

 
void
emul_exit(p)
struct proc *p;			/* exiting proc */
{
    u.u_floatmask &= ~FLOAT_EMUL;	/* We are not using the emulator */
}

/*
 * A process which had been using the emulator is forking.  Copy
 * the emulator registers over.
 *
 * In our case, USER_FPM has already been copied, so we don't have
 * anything to do.
 */

void
emul_fork(oldp, newp)
struct proc	*oldp,		/* parent process */
		*newp;		/* child process */
{
}

/*
 * emul_getreg:
 *
 * Return the value of a floating point register.
 *
 * The value returned is through one of the arguments.  The actual
 * value of the function is:
 *	0		Good return
 *	non-0		Error value.
 */

int
emul_getreg(reg, value)
int	reg;			/* Which floating point register */
int	*value;			/* Where value should be placed */
{
    if ((unsigned) reg >= EMUL_NUM_REG) {
	return EMUL_NUM_REG;	/* invalid register number */
    }

    *value = ((int *)USER_FPM)[reg];
    return 0;
}

emul_init()
{
    float_hardware |= FLOAT_EMUL;		/* We have an emulator */
}

/*
 * trap has detected an invalid data reference that MIGHT be fpa related.
 * We return
 *	0	if it isn't ours (register set already allocated)
 *	1	if it is ours (we also allocate the appropriate register set)
 */

int
emul_pci(mcs_pcs, info, locr0)
int	mcs_pcs;
int	info;
int	*locr0;		/* Where user registers are located */
{
    return 0;
}

/*
 * emul_putreg :
 *
 * For a given floating point register number, set the value.
 */

int
emul_putreg(reg, value)
int	reg;
int	*value;
{
    if ((unsigned) reg >= EMUL_NUM_REG) {
	return EMUL_NUM_REG;			/* invalid register number */
    }

    ((int *)USER_FPM)[reg] = *value;
    return 0;
}



/*
 * On a floating point exception, return the structure
 * to the user defining the error.
 */

int
emul_sendsig(floatsave)
struct floatsave *floatsave;
{
    FP_MACH usermach;
    FP_MACH *fpvm = (FP_MACH *) floatsave;

    copyin((caddr_t)USER_FPM,(caddr_t)(&usermach),sizeof(FP_MACH));
    *fpvm = usermach;
    DEBUGF(fpadebug, printf("emul_sendsig: before u.u_code = %x\n",u.u_code));
    if (float_has_afpa(u.u_floatmask) && ((u.u_code & 0x7) == 3)) {
    	u.u_code = FP_DIVIDE;		/* tft == 3 == divided by zero */
    	fpvm->fptrap.fptrapinfo.except_type = FP_DIVIDE;
    } else {
    	u.u_code = usermach.fptrap.fptrapinfo.except_type;
    }
    DEBUGF(fpadebug, printf("emul_sendsig: after u.u_code = %x\n",u.u_code));
    DEBUGF(fpadebug, printf("except_type = %x\n",fpvm->fptrap.fptrapinfo.except_type));
    if(u.u_code == FP_INT_DIVIDE) {
	return 0;
    } else {
	return 1;
    }
}


/*
 * emul_sigcleanup:
 *
 * Do some very hardware specific things (at the
 * request of the user) during signal cleanup time.
 */

void
emul_sigcleanup(sf_scp)
struct sigcontext *sf_scp;
{
    int i, j, k;	/* Mostly unused variables */

    emul_handle_cleanup(sf_scp, &i, &j, &k);
}

/*
 * Initialize the USER_FPM.
 */

fp_mach_init()
{
	*(unsigned long *) &(((FP_MACH *)USER_FPM)->status) = FP_S_unsgd;
}
