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

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

/*
 * This file contains those routines which (should be) the only
 * routines the kernel needs to call in order to perform floating
 * point support.  All knowledge of the actual floating point
 * hardware configuration is confined to this file.  From this
 * file, we call routines in fpa.c, mc881.c, and (someday)
 * fpa_emul.c.
 *
 * The following, in alphabetical order, are the routines
 * which are called from the outside world.
 */

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

#include "sigframe.h"
#include "fp.h"
#include "float.h"

struct floatsave *floatsave;	/* NPROC of these allocated */
int	float_hardware;		/* What hardware is on the system */

/*
 * float_core() :
 *
 * Called to get the register set (if it exists)
 * into the user address space so that a debugger can look at it.
 */

void
float_core()
{
    int floatmask = u.u_floatmask;

#ifdef MC881
    if (float_has_881(floatmask)) {
	mc881_core();
    }
#endif MC881
#ifdef	FPA
    if (float_has_fpa_or_afpa(floatmask)) {
	fpa_core();
    }
#endif	/* FPA */
    if (float_has_emul(floatmask)) {
	emul_core();
    }
}

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

 
void
float_exit(p)
struct proc *p;			/* exiting proc */
{
    int floatmask = u.u_floatmask;

#ifdef MC881
    if (float_has_881(floatmask)) {
	mc881_exit(p);
    }
#endif MC881
#ifdef FPA
    if (float_has_fpa_or_afpa(floatmask)) {
	fpa_exit(p);
    }
#endif FPA
    if (float_has_emul(floatmask)) {
	emul_exit(p);
    }
}

/*
 * On a fork, make sure that floating point registers are copied down.
 *
 * We happen to "know" that we are called with the old u. in place.
 */

void
float_fork(oldp, newp)
struct proc	*oldp,		/* parent process */
		*newp;		/* child process */
{
    int floatmask = u.u_floatmask;

#ifdef MC881
    if (float_has_881(floatmask)) {
	mc881_fork(oldp, newp);
    }
#endif MC881

#ifdef FPA
    /*
     * The FPA code, which leaves stuff lying around in the proc struct,
     * needs to be called on EVERY fork.
     */
    fpa_fork(oldp, newp);
#endif FPA

    if (float_has_emul(floatmask)) {
	emul_fork(oldp, newp);
    }
}

/*
 * float_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
float_getreg(reg, value)
int	reg;			/* Which floating point register */
int	*value;			/* Where value should be placed */
{
    int floatmask = u.u_floatmask;
    int registers;

#ifdef MC881
    if (float_has_881(floatmask)) {
	if ((registers = mc881_getreg(reg, value)) == 0) {
	    return 0;
	} else {
	    reg -= registers;
	}
    }
#endif MC881
#ifdef	FPA
    if (float_has_fpa_or_afpa(floatmask)) {
	if ((registers = fpa_getreg(reg, value)) == 0) {
	    return 0;
	} else {
	    reg -= registers;
	}
    }
#endif	/* FPA */
    if (float_has_emul(floatmask)) {
	if ((registers = emul_getreg(reg, value)) == 0) {
	    return 0;
	} else {
	    reg -= registers;
	}
    }
    return 1;				/* BAD */
}

/*
 * 1. call each initialization routine in turn.
 * 2. if any FPA hardware is present allocate the floatsave structure.
 */
float_init()
{
#ifdef FPA
    fpa_init();
#endif FPA
#ifdef MC881
    mc881_init();
#endif MC881
    emul_init();
    /*
     * Get storage for the area which will receive all floating point
     * registers in the system.
     */
    if (float_has_any(float_hardware)
		|| float_has_afpa_hardware(float_hardware)) {
	floatsave = (struct floatsave *)
			    calloc(nproc * sizeof (struct floatsave));
	if (floatsave == 0) {
	    panic("floatsave calloc");
	}
    }
}

/*
 * 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
float_pci(mcs_pcs, info, locr0)
int	mcs_pcs;
int	info;
int	*locr0;		/* Where user registers are located */
{
    int result = 0;
    int oldmask = u.u_floatmask;

#ifdef MC881
    result |= mc881_pci(mcs_pcs, info, locr0);
#endif MC881
#ifdef	FPA
    result |= fpa_pci(mcs_pcs, info, locr0);
#endif	/* FPA */
    if (result == 0) {
	return 0;
    } else {
	result = 0;
	if (float_has_881(u.u_floatmask)) {
	    result++;
	}
	if (float_has_fpa_or_afpa(u.u_floatmask)) {
	    result++;
	}
	if (float_has_emul(u.u_floatmask)) {
	    result++;
	}
	if (result > 1) {
	    uprintf("float:  attempt to acquire %b when already using %b\n",
			u.u_floatmask&(~oldmask), FLOAT_FMT,
			oldmask, FLOAT_FMT);
	    return 0;
	} else {
	    return 1;
	}
    }
}

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

int
float_putreg(reg, value)
int	reg;
int	*value;
{
    int floatmask = u.u_floatmask;
    int registers;

#ifdef MC881
    if (float_has_881(floatmask)) {
	if ((registers = mc881_putreg(reg, value)) == 0) {
	    return 0;
	} else {
	    reg -= registers;
	}
    }
#endif MC881
#ifdef	FPA
    if (float_has_fpa_or_afpa(floatmask)) {
	if ((registers = fpa_putreg(reg, value)) == 0) {
	    return 0;
	} else {
	    reg -= registers;
	}
    }
#endif	/* FPA */
    if (float_has_emul(floatmask)) {
	if ((registers = emul_putreg(reg, value)) == 0) {
	    return 0;
	} else {
	    reg -= registers;
	}
    }
    return 1;			/* Bad return */
}


/*
 * float_sendsig
 *
 * We are sending a floating point signal to a process.  Add
 * the floatsave structure to the stack.
 *
 * Returns
 *	0	We didn't save anything (integer divide case)
 *	1	We saved a floatsave structure
 */

int
float_sendsig(fp)
struct sigframe *fp;
{
    int floatmask = u.u_floatmask;
    int result = 0;

    if (u.u_code != FP_INT_DIVIDE) {
#ifdef MC881
	if (float_has_881(floatmask)) {
	    result |= mc881_sendsig(&fp->sf_floatsave);
	}
#endif MC881
#ifdef FPA
	if (float_has_fpa_or_afpa(floatmask)) {
	    result |= fpa_sendsig(&fp->sf_floatsave);
	}
#endif FPA
	if (float_has_emul(floatmask)) {
	    result |= emul_sendsig(&fp->sf_floatsave);
	}
	return result;
    } else {
	return 0;
    }
}



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

void
float_sigcleanup(sf_scp)
struct sigcontext *sf_scp;
{
    int floatmask = u.u_floatmask;

#ifdef MC881
    if (float_has_881(floatmask)) {
	mc881_sigcleanup(sf_scp);
    }
#endif MC881
#ifdef	FPA
    if (float_has_fpa_or_afpa(floatmask)) {
	fpa_sigcleanup(sf_scp);
    }
#endif	/* FPA */
    if (float_has_emul(floatmask)) {
	emul_sigcleanup(sf_scp);
    }
}

/*
 * getfloatstate()
 *
 * This returns, to the user, information which can be used to determine
 * which floating point hardware exists on the system, and which floating
 * point hardware is in use by the current (invoking) process.
 */

getfloatstate()
{
    struct a {
	caddr_t	state;		/* address of structure to be filled in */
	caddr_t	size;		/* size of structure pointed to by 'state' */
    } *uap;
    struct floatstate ourstate;
    int usersize;
    extern void _FPemulate();		/* Floating point emulator */
    extern void fpgen();		/* Floating point code generator */

    /* Set up argument list */
    uap = (struct a *)u.u_ap;

    /* Fetch (and validate, by the way) the size */
    u.u_error = copyin(uap->size, (caddr_t) &usersize, sizeof usersize);
    if (u.u_error) {
	return;
    }

    /* We could access size; is it OK? */
    if (usersize != sizeof (struct floatstate)) {
		/* Aside from sizeof (struct floatstate),
		 * what are the legal sizes.
		 */
	static int legalsizes[] = { 16 };
	int i;

	u.u_error = EINVAL;
	for (i = 0; i < sizeof legalsizes/sizeof legalsizes[0]; i++) {
	    if (usersize == legalsizes[i]) {
		u.u_error = 0;		/* No error */
		break;
	    }
	}
	if (u.u_error) {
	    return;
	}
    }

    /* OK.  Now, set up our version of the structure. */
    ourstate.hardware_state = float_hardware;
    ourstate.process_state = u.u_floatmask;
    ourstate.emulator = _FPemulate;
    ourstate.code_generator = fpgen;
    ourstate.fpa_registerset = u.u_procp->p_fpareg;

    /* Now, copy our version up to the user process (validating the pointer) */
    u.u_error = copyout((caddr_t) &ourstate, uap->state, usersize);

    /* Done.  If an error on the above copyout, u.u_error is already set */
}

/*
 * system call to set return fpaemulator address
 */
getfpemulator()
{
    extern int fpa_valid;

#ifdef FPA
    if (float_has_fpa_or_afpa(float_hardware)) {
	u.u_r.r_val1 = (int) &fpa_valid;
    } else {
	if (float_has_881(u.u_floatmask)) {	/* illegal! */
	    psignal(u.u_procp, SIGBUS);
	} else {
	    u.u_r.r_val1 = (int) _FPemulate;
	    /* We assume if getfpemulator, and no fpa, then will use emul */
	    u.u_floatmask |= FLOAT_EMUL;		/* Using emulator */
	}
    }
#else FPA
    u.u_r.r_val1 = (int) _FPemulate;
    u.u_floatmask |= FLOAT_EMUL;		/* Using emulator */
#endif FPA
    u.u_r.r_val2 = (int) _FPemulate;
}


/*
 * Kernel call to get fp
 */
kgetfpemulator()
{
    return (int) _FPemulate;
}


/*
 * setfloatstate
 *
 * This allows a user process to set the state of the floating
 * point hardware in use by the invoking process.
 *
 * In addition, the super user is able to change the state of
 * the hardware configuration using this syscall.  This would
 * most typically be used to enable the afpa (advanced floating
 * point adapter) after a user-level process has loaded micro-
 * code into it.
 */

setfloatstate()
{
    struct a {
	caddr_t	mask;		/* Pointer to structure containing mask */
	caddr_t state;	/* Pointer to structure containing new value */
	caddr_t	size;		/* size of structure pointed to by 'state' */
    } *uap;
    struct floatstate newstate;
    struct floatstate newmask;
    int usersize;
    int hardware, user;
    extern void _FPemulate();		/* Floating point emulator */
    extern void fpgen();		/* Floating point code generator */

    /* Set up argument list */
    uap = (struct a *)u.u_ap;

    /* Fetch (and validate, by the way) the size */
    u.u_error = copyin(uap->size, (caddr_t) &usersize, sizeof usersize);
    if (u.u_error) {
	return;
    }

    /* We could access size; is it OK? */
    if (usersize != sizeof (struct floatstate)) {
	u.u_error = EINVAL;
	return;
    }

    /* Copyin the mask and new values */
    u.u_error = copyin(uap->mask, (caddr_t) &newmask, usersize);
    u.u_error = copyin(uap->state, (caddr_t) &newstate, usersize);

    /* Check for super user */
    if (!suser() && (newmask.hardware_state != 0)) {
	return;			/* suser set EPERM */
    }

    hardware = (float_hardware&~newmask.hardware_state) |
			    (newstate.hardware_state&newmask.hardware_state);
    user = (u.u_floatmask&~newmask.process_state) |
			    (newstate.process_state&newmask.process_state);
	    /*
	     * We are about to change the state of the hardware.
	     * So, to keep things cool, let the affected hardware
	     * handlers keep track of what is going on.
	     *
	     * In addition, the hardware handlers are allowed to
	     * change bits in the input (if, for example, the
	     * afpa handler decides that the afpa isn't really
	     * working very well; or if the particular handler
	     * doesn't allow the "dropping" of a piece of hardware
	     * on the fly).
	     */
#if	defined(MC881)
    mc881_setfloatstate(&newmask, &newstate, hardware, user);
#endif	/* defined(MC881) */
#if	defined(FPA)
    fpa_setfloatstate(&newmask, &newstate, hardware, user);
#endif	/* defined(FPA) */

    /* The hardware handlers may have modified the mask, so recompute */
    hardware = (float_hardware&~newmask.hardware_state) |
			    (newstate.hardware_state&newmask.hardware_state);
    user = (u.u_floatmask&~newmask.process_state) |
			    (newstate.process_state&newmask.process_state);

    /* Update the data areas */

    float_hardware = hardware;
    u.u_floatmask = user;

    /* OK.  Now, set up our version of the structure. */
    newstate.hardware_state = float_hardware;
    newstate.process_state = u.u_floatmask;
    newstate.emulator = _FPemulate;
    newstate.code_generator = fpgen;

    /* Now, copy our version up to the user process (validating the pointer) */
    u.u_error = copyout((caddr_t) &newstate, uap->state, usersize);

    /* Done.  If an error on the above copyout, u.u_error is already set */
}

