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

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


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

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

#include "ioim.h"
#include "scr.h"
#include "mmu.h"
#include "mc881.h"
#include "mc881var.h"

#include "cpu.h"
#include "reg.h"
#include "frame.h"
#include "debug.h"

#if	defined(MC881)

#define MC881_ABORT()	MC_CIR_WRITE_16(MC_CIR_CONTROL, MC_CIR_CONTROL_AB)

extern struct proc *mc881_proc;		/* Who owns mc881 registers */
int mc881_status;			/* What last error status was */
int nomc881 = 0;			/* set to diable use of mc881 */

static void
mc881_busy()
{
    /* Loop waiting for the 68881 to finish after a DMA operation */
    while (ior(IOIM2+IOIM_CPS)&IOIM_CPS_68881_BUSY) {
	;
    }
}


static void
mc881_save(statep)
struct mc881_state *statep;
{
    u_int i, *ptr;

    /* First, save ioim length register */
    statep->ioim_length_register = IOIM_881_READ_LENGTH_REGISTER();
    do {
	i = MC_CIR_READ_16(MC_CIR_SAVE);
    } while ((i&MC_STATE_MASK) == MC_STATE_NOT_READY);

    statep->internal.format = i;
    switch ((i&MC_STATE_MASK)) {
    case MC_STATE_NULL:
	i = MC_NULL_FRAME_SIZE;
	break;
    case MC_STATE_NOT_READY:
	panic("mc881_save: Should not occur!\n");
	/*NOTREACHED*/
    case MC_STATE_ILLEGAL:
	panic("mc881_save: Illegal!\n");
	/*NOTREACHED*/
    default:
	i &= MC_NON_NULL_FRAME_SIZE_MASK;
    }
    i /= 4;
    ptr = &statep->internal.data[0];
    while (i-- != 0) {
	*ptr = MC_CIR_READ_32(MC_CIR_OPERAND);
	ptr++;
    }

    /* Now, save the programmer's model */

    IOIM_881_WRITE_LENGTH_REGISTER(96/32*8);		/* How much to save */
    IOIM_881_WRITE(IOIM_881_XFER_PATH_FROM_881,
				MC_SAVE_DATA_REGS, IOIM_881_LENGTH_N_WORDS,
			(u_int) &statep->programmer.data[0]);
    mc881_busy();				/* Wait for op to complete */
    IOIM_881_WRITE_LENGTH_REGISTER(32/32*3);		/* How much to save */
    IOIM_881_WRITE(IOIM_881_XFER_PATH_FROM_881,
			MC_SAVE_CONTROL_REGS, IOIM_881_LENGTH_N_WORDS,
			(u_int) &statep->programmer.control[0]);
    mc881_busy();				/* Wait for op to complete */
}

static void
mc881_restore(statep)
struct mc881_state *statep;
{
    u_int i, *ptr;

    /* First, restore the programmer's model */

    if (statep->internal.format != MC_STATE_NULL) {
	IOIM_881_WRITE_LENGTH_REGISTER(96/32*8);	/* How much to save */
	IOIM_881_WRITE(IOIM_881_XFER_PATH_TO_881, MC_RESTORE_DATA_REGS,
	    IOIM_881_LENGTH_N_WORDS, (u_int) &statep->programmer.data[0]);
	mc881_busy();				/* Wait for op to complete */
	IOIM_881_WRITE_LENGTH_REGISTER(32/32*3);	/* How much to save */
	IOIM_881_WRITE(IOIM_881_XFER_PATH_TO_881, MC_RESTORE_CONTROL_REGS,
	    IOIM_881_LENGTH_N_WORDS, (u_int) &statep->programmer.control[0]);
	mc881_busy();				/* Wait for op to complete */
    }

    /* Next, restore the internal state */

    MC_CIR_WRITE_16(MC_CIR_RESTORE, statep->internal.format);

    do {
	i = MC_CIR_READ_16(MC_CIR_RESTORE);
    } while ((i&MC_STATE_MASK) == MC_STATE_NOT_READY);

    switch ((i&MC_STATE_MASK)) {
    case MC_STATE_NULL:
	i = MC_NULL_FRAME_SIZE;
	break;
    case MC_STATE_NOT_READY:
	panic("mc881_restore: Should not occur!\n");
	/*NOTREACHED*/
    case MC_STATE_ILLEGAL:
	panic("mc881_restore: Illegal!\n");
	/*NOTREACHED*/
    default:
	i &= MC_NON_NULL_FRAME_SIZE_MASK;
    }
    i /= 4;

    /*
     * We need to "pop" these off the "stack" (ie: we restore in the
     * opposite order they were saved).
     */
    ptr = &statep->internal.data[i-1];
    while (i-- != 0) {
	MC_CIR_WRITE_32(MC_CIR_OPERAND, *ptr);
	ptr--;
    }

    /* Last, restore ioim length register */
    IOIM_881_WRITE_LENGTH_REGISTER(statep->ioim_length_register);
}

static label_t mc881_label;
void mc881_trap();
/*
 * mc881_test(): called to do a unit test to make sure that the 881 
 * is operational.
 */
static int
mc881_test()
{
    static double x = 1.0, y = 2.0;	/* static to avoid use of floating point code */
    static double z;
    static double result = 3.0;
    int n;
    struct mc881_state state;
    extern void (*_trap)();			/* Pointer to trap slih */
    void (*oldtrap)() = _trap;

    _trap = mc881_trap;
    if (setjmp(&mc881_label)) {
	_trap = oldtrap;
	return(0);				/* program check - not operational */
    }
    bzero((char *)&state, sizeof state);	/* Zero registers XXX */
    state.internal.format = MC_STATE_NULL;
    mc881_restore(&state);			/* intialize 881 */

    *(int *)(IOIM_881_HW_BASE + 0x015202) = (int) (&x);	/* transfer x to fr1 */
    *(int *)(IOIM_881_HW_BASE + 0x01528a) = (int) (&y);	/* add y to fr1 */
    *(int *)(IOIM_881_HW_BASE + 0x3dd202) = (int) (&z);	/* transfer fr1 to z */

    n = IOIM_881_READ_LENGTH_REGISTER();	/* get in sync */
    if (n == 3) {
	n = 4;
    }
    mc881_save(&state);				/* see if we can do a save! */
    _trap = oldtrap;
    return(*(int *) (&z) == * (int *) (&result));
}

/*
 * trap intercept function in case mc881 causes a program or
 * machine check.
 */
static void
mc881_trap(mcs_pcs, info, ics_cs, regs)
	register mcs_pcs, info;
	int ics_cs;			/* must not be a register variable */
	int regs;			/* must not be a register variable */
{
	register int *locr0 = &regs;

	DEBUGF(mc881debug&SHOW_INIT,
	    prstate("mc881_trap", mcs_pcs, info, locr0[IAR], ics_cs, (int *)0));
	printf("68881 program check - ");	/* give reason not operational */
	longjmp(&mc881_label);
}


/*
 * The following, in alphabetical order, are the routines
 * which are called from the outside world.
 */

/*
 * This is very hardware specific.  What has happened is that the second
 * exception in the exception stack is for the 68881, and the exception
 * sequence is not store/load (in the store/load case, when the store gives
 * an exception, the CPU always gives an exception to the load, even if
 * it succeeds).
 *
 * The 68881 (or IOIM2) has given the exception for one of two reasons:
 *	First, the 68881 decided that there was a problem and issued a
 *		"take exception" primitive.  This has two cases:
 *		1. mid-instruction exception - issue abort unless the 
 *			exception was a protocol violation which does 
 *			regenerate.
 *		2. pre-instruction exception - reset BIU "instruction-pending bit.
 *	Second, otherwise, issue an 881 abort.
 */

void
mc881_abort(locr0)
int *locr0;			/* Registers */
{
    int i = MC_CIR_READ_16(MC_CIR_RESPONSE);	/* In case IOIM didn't do it */

    if (ior(IOIM2+IOIM_CPS)&IOIM_CPS_68881_BUSY) {	/* If the 881 is busy */
	do {
	    i = MC_CIR_READ_16(MC_CIR_RESPONSE);
	} while ((i&MC_RESPONSE_CA)
		&& ((i&MC_RESPONSE_NULL_MASK) == MC_RESPONSE_NULL_VALUE));
    }

    if ((i&MC_RESPONSE_TAKE_EXCEPT_MASK) == MC_RESPONSE_TAKE_MID_EXCEPT) {
	if ((i&MC_RESPONSE_PARAMETER_MASK) == MC_RESPONSE_PROTOCOL_VIOLATION)
		return;		/* protocol violations regenerate */
	/* otherwise fall thru to do abort */
    } else if ((i&MC_RESPONSE_TAKE_EXCEPT_MASK) == MC_RESPONSE_TAKE_PRE_EXCEPT) {
	/* reset BIU "instruction pending flag" */
	struct mc881_state *state = (struct mc881_state *)
				    floatsave[u.u_procp-proc].mc881_intreg;

	mc881_save(state);
	if ((state->internal.format&MC_NON_NULL_FRAME_SIZE_MASK) != MC_IDLE_FRAME_SIZE)
		panic("mc881_abort: not in idle state");
	if ((state->internal.biu_flags & MC_BIU_INST_PENDING) != 0)
		panic("mc881_abort: instn not pending");
	state->internal.biu_flags |= MC_BIU_INST_PENDING;	/* reset instruction pending */
	mc881_restore(state);
	DEBUGF(mc881debug&SHOW_PCI,printf("mc881_abort:cleared BIU instn pending\n"));
	return;
    }
    MC881_ABORT();		/* abort current function */
    DEBUGF(mc881debug&SHOW_PCI,printf("mc881_abort:abort command issued\n"));
}


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

void
mc881_core()
{
    struct proc *p = u.u_procp;
    struct mc881_state *state = 
			(struct mc881_state *) floatsave[p-proc].mc881_intreg;

    if (mc881_proc == p) {
	mc881_save(state);
	iow(IOIM2+IOIM_SUB_PROB, IOIM2_PROB_DISABLE);
	mc881_proc = 0;			/* Not using now */
    }

    bcopy((caddr_t) state, (caddr_t) (USER_FPM), sizeof *state);
}

/*
 * A process is giving up ownership of the '881.  Indicate
 * this in the u.u_floatmask, and clean up.
 */

void
mc881_exit(p)
struct proc *p;
{
    if (mc881_proc == p) {
	/*
	 * We do this save at this point to leave things in a clean
	 * state.  If we don't, and the '881 is in a funny state,
	 * our next mc881_restore() can cause a trap.
	 */
	mc881_save((struct mc881_state *) floatsave[p-proc].mc881_intreg);
	iow(IOIM2+IOIM_SUB_PROB, IOIM2_PROB_DISABLE);
	mc881_proc = 0;			/* No more process */
    }
    u.u_floatmask &= ~FLOAT_MC881;	/* We are not using 68881 */
    DEBUGF(mc881debug&SHOW_REGS,
	printf("mc881_exit: release pid %d\n", p->p_pid));
}

/*
 * Duplicate the process's floating point register into the next process.
 */

void
mc881_fork(oldp, newp)
struct proc	*oldp,		/* parent process */
		*newp;		/* child process */
{
    if (mc881_proc == oldp) {
	mc881_save((struct mc881_state *)
			floatsave[oldp-proc].mc881_intreg);
	iow(IOIM2+IOIM_SUB_PROB, IOIM2_PROB_DISABLE);
	mc881_proc = 0;			/* Force reload next time referenced */
    }
    floatsave[newp-proc] = floatsave[oldp-proc];	/* copy it */
}

int
mc881_getreg(reg, value)
int reg;			/* register number to get */
int *value;			/* Where to return it */
{
    struct mc881_state *state = (struct mc881_state *)
				floatsave[u.u_procp-proc].mc881_intreg;

    if (reg >= MC881_NUM_REG) {
	return MC881_NUM_REG;
    }
    if (mc881_proc == u.u_procp) {
	mc881_save(state);
	iow(IOIM2+IOIM_SUB_PROB, IOIM2_PROB_DISABLE);
	mc881_proc = 0;			/* Force reload next time referenced */
    }
    *value = state->programmer.data[reg];
    return 0;
}

/*
 * Check to see if the mc881 exists.  This simply involves the null
 * case if we are "ROMPC" compiled, but not "SGP" compiled.  If we
 * are both "ROMPC" and "SGP" compiled, then we check the CPU type.
 * ROMPC cpu's "always" have the 68881.
 *
 * If the 68881 does exist, enable it's use.
 * If it doesn't exist, disable the use of it (by setting mc881_proc
 * to "-1").
 */

void
mc881_init()
{
#if	(defined(SGP) || defined(MOD135))
    if (cpu_model != CPU_ROMPC) {
	mc881_proc = (struct proc *) -1;
    } else
#endif	/* defined(SGP) || defined(MOD135) */
    if (nomc881) {
	printf("68881 disabled.\n");
    } else {
        mc881_proc = 0;
        float_hardware |= FLOAT_MC881;
    }
    if (float_hardware&FLOAT_MC881) {
        if (GETSEG(IOIM_881_SW_BASE) != GETSEG(IOIM_881_HW_BASE))
		panic("mc881_init: HW_BASE != SW_BASE");
	if (sizeof (struct floatsave) < sizeof (struct mc881_state)) {
	    printf("Kernel inconsistency (mc881_init): 68881 not enabled.\n");
	    float_hardware &= ~FLOAT_MC881;
	} else if (!mc881_test()){
	    printf("68881 not operational.\n");
	    float_hardware &= ~FLOAT_MC881;
	} else {
	    printf("68881 enabled.\n");
	}
    }
}

/*
 * Is the interrupt from the 881?
 *
 * If so, then why did we get it?

 * If we got it because the 881 was locked, then allocate a register set
 * to this process, possibly store the old process's registers, and
 * then unlock the IOIM.
 */

int
mc881_pci(mcs_pcs, info, locr0)
int	mcs_pcs;
int	info;
int	*locr0;				/* Registers at interrupt time */
{
    if ((GETSEG(info) == GETSEG(IOIM_881_HW_BASE)) && 
		    ((GETSUBSEG(info) == GETSUBSEG(IOIM_881_HW_BASE)) ||
		    (GETSUBSEG(info) == GETSUBSEG(IOIM_881_SW_BASE)))) {
	struct proc *p = u.u_procp;
	struct mc881_state *statep = (struct mc881_state *)
					floatsave[p-proc].mc881_intreg;

	DEBUGF(mc881debug&SHOW_PCI,
	    prstate("mc881_pci", mcs_pcs, info, locr0[IAR], locr0[ICSCS],locr0));

	if (mc881_proc == p) {			/* 68881 exception */
	    return(mc881_exception(mcs_pcs,info,locr0,p));
	} 

	if (!float_has_881(float_hardware)) {
	    DEBUGF(mc881debug&SHOW_REGS,
		printf("mc881_pci: no 881 available pid %d\n", p->p_pid));
		return(0);			/* no 881 - give an exception */
	}
	if (!float_has_881(u.u_floatmask)) {
	    DEBUGF(mc881debug&SHOW_REGS,
		printf("mc881_pci: allocate 881 pid %d\n", p->p_pid));
	    bzero((char *)statep, sizeof *statep);	/* Zero registers XXX */
	    statep->internal.format = MC_STATE_NULL;
	    u.u_floatmask |= FLOAT_MC881;		/* We are using 68881 */
	}
	if (mc881_proc != 0) {
	    /* Save current registers */
	    mc881_save((struct mc881_state *)
				    floatsave[mc881_proc-proc].mc881_intreg);
	}
	mc881_proc = p;
	mc881_restore(statep);
	iow(IOIM2+IOIM_SUB_PROB, IOIM2_PROB_ENABLE);
	return 1;			/* handled */
    } else {
	return 0;			/* not ours */
    }
}

/*
 * handle actual exceptions reported from the 881 
 * these include:
 * 1. floating point exceptions
 * 2. DMA exceptions (page faults)
 * 3. other exceptions (parity errors) due to bad hardware.
 */

int
mc881_exception(mcs_pcs, info, locr0, p)
int	mcs_pcs;
int	info;
int	*locr0;				/* Registers at interrupt time */
struct proc *p;
{
    int status = ior(IOIM2 + IOIM_CPS);	/* co-processor status register */
    int i = SIGFPE;			/* assume numeric exception */
    struct mc881_state *state = (struct mc881_state *)
				floatsave[p-proc].mc881_intreg;


    mc881_status = status;		/* RAS */
    DEBUGF(mc881debug&SHOW_PCI,printf("CPS=%b ",status,CPS_FMT));

    MC881_ABORT();		/* abort 881 here for now; just in case */

    if ((locr0[ICSCS] & ICSCS_PROBSTATE) == 0) {
	return(0);			/* don't accept kernel mode traps */
    }

    switch (status & IOIM_CPS_MASK) {
    case IOIM_CPS_68881_EXCEPTION:
	i = SIGFPE;
	/* Save the last 68881 operation performed */
	state->lastop = ior(IOIM2+IOIM_LASTOP);
	break;
    case IOIM_CPS_PMUC_TIMEOUT+IOIM_CPS_PMUC_EXCEPTION:
    /*
     * The TIMEOUT bit can, because of hardware idiosyncracies,
     * come on in conjunction with the EXCEPTION bit.  So,
     * treat the combination as an EXCEPTION unless the
     * data packet was bound for segnemt F.
     */
	if (GETSUBSEG(info) == GETSUBSEG(IOIM_881_HW_BASE) &&
			GETSEG(locr0[EX1_DATA]) == GETSEG(IOIM_881_HW_BASE)) {
	    i = SIGBUS;
	    break;
	}	/* else: fall thru */
    case IOIM_CPS_PMUC_EXCEPTION:
	if (GETSUBSEG(info) == GETSUBSEG(IOIM_881_HW_BASE)) {
	    int addr = (locr0[EX1_DATA] & ~0xff) | ((status>>8) & 0xfc);
	    if (addr < locr0[EX1_DATA])
		addr += 0x100;		/* carry */
	    if (status & 0x100)
		addr -= 4;		/* address was ahead by 4 */
	    DEBUGF(mc881debug&SHOW_PCI,printf("dma_addr=%x ",addr));
#ifdef SIM_CHG_BIT
	    if ((info & MMU_EX_KEYS) &&
			protection(mcs_pcs, addr & (-NBPG), locr0) == 0)
		i = 0;		/* was handled protection */
	    else
#endif SIM_CHG_BIT
	    if (vtop(addr) < 0) {	/* if address could cause page fault */
		int error_save = u.u_error;

		if (pagein(addr, 0)) {
		    /* not valid fault - check if stack growth needed */
		    DEBUGF(mc881debug&SHOW_PCI,printf("pagein failed! "));
		    if (GROWSP(locr0[SP]))
			i = 0;		/* stack grew */
		    else
			i = SIGSEGV;
		    u.u_error = error_save;
		} else
		    i = 0;	/* was a handled page fault */
	    } else
		i = SIGSEGV;
	} else
	    i = SIGBUS;
	break;
    /*
     * The user has set up for some wayout DMA to occur.
     */
    case IOIM_CPS_PMUC_TIMEOUT:
	i = SIGBUS;
	break;
    /*
     * Other exceptions should only happen if there is a hardware failure
     */
    default:
	printf("CPS=%b ",status,CPS_FMT);
	panic("mc881_exception: fatal status");
    }
    DEBUGF(mc881debug&SHOW_PCI,printf("signal=%d\n",i));
    if (i)
	psignal(p, i);		/* Signal the user */
    return(1);			/* its ours */
}

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

int
mc881_putreg(reg, value)
int	reg;
int	*value;
{
    struct mc881_state *state = (struct mc881_state *)
				floatsave[u.u_procp-proc].mc881_intreg;

    if (reg >= MC881_NUM_REG) {
	return MC881_NUM_REG;
    }
    if (mc881_proc == u.u_procp) {
	mc881_save(state);
	iow(IOIM2+IOIM_SUB_PROB, IOIM2_PROB_DISABLE);
	mc881_proc = 0;			/* Force reload next time referenced */
	/* So, now in each case, we are using from save area */
    }
    state->programmer.data[reg] = *value;
    return 0;
}


/*
 * Copy programmer state into the sigframe structure.
 *
 * Return 1
 */


int
mc881_sendsig(fs)
struct floatsave *fs;
{
    struct mc881_state *state = (struct mc881_state *)
				floatsave[u.u_procp-proc].mc881_intreg;
    struct mc881_state *tostate = (struct mc881_state *) fs;
    int status, code;

    DEBUGF(mc881debug&SHOW_PCI,printf("mc881_sendsig: fs=%x state=%x\n",
		fs, state));
    if (mc881_proc == u.u_procp) {
	mc881_save(state);
	iow(IOIM2+IOIM_SUB_PROB, IOIM2_PROB_DISABLE);
	mc881_proc = 0;			/* Force reload next time referenced */
    }

    *tostate = *state;		/* Copy things over */

    /* Set u.u_code */

    status = state->programmer.control[1];	/* Get status register */
    if (status&MC_EXCEPT_BSUN) {
	code = FP_INV_OPER;
    } else if (status&MC_EXCEPT_SNAN) {
	code = FP_INV_OPER;
    } else if (status&MC_EXCEPT_OPERR) {
	code = FP_INV_OPER;
    } else if (status&MC_EXCEPT_OVFL) {
	code = FP_OVERFLOW;
    } else if (status&MC_EXCEPT_UNFL) {
	code = FP_UNDERFLOW;
    } else if (status&MC_EXCEPT_DZ) {
	code = FP_DIVIDE;
    } else if (status&MC_EXCEPT_INEX2) {
	code = FP_INEXACT;
    } else if (status&MC_EXCEPT_INEX1) {
	code = FP_INEXACT;
    } else {
	code = FP_INV_OPER;			/* ??? */
    }
    u.u_code = code;

    return 1;
}

/*
 * mc881_sigcleanup -
 *
 * After a signal handler returns from a SIGFPE, restore the
 * registers.
 *
 * On a signal, we send up the entire register set.  On a return,
 * we restore ONLY the programmer's model (and the length register).
 */

void
mc881_sigcleanup(sf_scp)
struct sigcontext *sf_scp;
{
    struct proc *p = u.u_procp;
    struct mc881_state
		*sigstate = (struct mc881_state *)sf_scp->sc_floatsave,
		*state = (struct mc881_state *) floatsave[p-proc].mc881_intreg;

    /* Ensure that the current state is saved */
    if (mc881_proc == p) {
	mc881_save(state);
	iow(IOIM2+IOIM_SUB_PROB, IOIM2_PROB_DISABLE);
	mc881_proc = 0;			/* Force reload next time referenced */
    }
    state->programmer = sigstate->programmer;
    state->ioim_length_register = sigstate->ioim_length_register;
}

void
mc881_setfloatstate(mask, state)
struct floatstate *mask, *state;
{
    mask->hardware_state &= ~FLOAT_MC881;	/* No changes in hardware */
    if ((mask->process_state&FLOAT_MC881) &&
		(float_has_881(u.u_floatmask^state->process_state))) {
	/* The user is changing state.  Handle this. */
	if (float_has_881(state->process_state)) {
	    /* If they want it, let them touch it (then mc881_pci) */
	    mask->process_state &= ~FLOAT_MC881;
	} else {			/* They want to let the 881 go */
	    mc881_exit(u.u_procp);	/* Release hardware */
	}
    }
}
#endif	/* defined(MC881) */
