/*	@(#)fpu.c 1.1 92/07/30 SMI;	*/
#include <sys/types.h>
#include <machine/reg.h>
#include <machine/psl.h>
#include <machine/buserr.h>
#include <machine/trap.h>
#include <sun/fault.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/proc.h>
#include <sys/map.h>
#include <sys/user.h>
#include <sys/core.h>
#include <sys/ptrace.h>
#include <machine/fpu/fpu_simulator.h>
#include <machine/fpu/globals.h>

/*
 * the global pointer to the current floating point context
 */

#ifdef MULTIPROCESSOR
extern struct fpu *fp_ctxp;
#else MULTIPROCESSOR
struct fpu *fp_ctxp = (struct fpu *)0;
#endif MULTIPROCESSOR

struct fpu *
fpu_ctxalloc()
{
	register struct fpu *fp;
	register int i;

	fp = (struct fpu *)new_kmem_alloc(sizeof (*fp), KMEM_SLEEP);
	fp->fpu_fsr = 0;	/* zero fsr */
	i = -1;
	fp->fpu_regs[0] = i;	/* initialize registers to NAN */
	fp->fpu_regs[1] = i;
	fp->fpu_regs[2] = i;
	fp->fpu_regs[3] = i;
	fp->fpu_regs[4] = i;
	fp->fpu_regs[5] = i;
	fp->fpu_regs[6] = i;
	fp->fpu_regs[7] = i;
	fp->fpu_regs[8] = i;
	fp->fpu_regs[9] = i;
	fp->fpu_regs[10] = i;
	fp->fpu_regs[11] = i;
	fp->fpu_regs[12] = i;
	fp->fpu_regs[13] = i;
	fp->fpu_regs[14] = i;
	fp->fpu_regs[15] = i;
	fp->fpu_regs[16] = i;
	fp->fpu_regs[17] = i;
	fp->fpu_regs[18] = i;
	fp->fpu_regs[19] = i;
	fp->fpu_regs[20] = i;
	fp->fpu_regs[21] = i;
	fp->fpu_regs[22] = i;
	fp->fpu_regs[23] = i;
	fp->fpu_regs[24] = i;
	fp->fpu_regs[25] = i;
	fp->fpu_regs[26] = i;
	fp->fpu_regs[27] = i;
	fp->fpu_regs[28] = i;
	fp->fpu_regs[29] = i;
	fp->fpu_regs[30] = i;
	fp->fpu_regs[31] = i;
	return (fp);
}

fpu_ctxfree()
{
	register struct pcb *pcb;

	pcb = &u.u_pcb;
	if (pcb->pcb_fpctxp) {
		if (fp_ctxp == pcb->pcb_fpctxp)
			fp_ctxp = 0;
		kmem_free((caddr_t)pcb->pcb_fpctxp, sizeof (struct fpu));
		pcb->pcb_fpctxp = (struct fpu *) 0;
	}
}

/* ARGSUSED */
fpu_fork_context(p2, nfp, new_context)
struct proc *p2;
struct file **nfp;
int *new_context;
{
	register struct fpu *fp;
	register struct pcb *pcb;

	/*
	 * if the current process is using the fpu,
	 * allocate a new fp context and initialize it
	 * with the current fp context state.
	 *
	 */
	pcb = &u.u_pcb;

	if (pcb->pcb_fpctxp) 
	{
	 	/* we are forking fpu context due to users fork() call.
		 * At this time the fpu chip registers belong to the 
		 * parent. First dump the registers to the fpu context.
		 * then copy the context to the child's context.
		 * 
	 	 * Child runs first and therefore the fp_ctxp should
		 * point to childs fp context. However, the child may not
		 * started due to other errors during the rest of the fork.
		 * therefore shutoff PSR_EF for the parent and this will be
		 * copied to the child in fork(). THen make fp_ctxp 0.
		 * This will ensure the child and parent have different 
		 * fpu contexts. 
		 */

		fp_dumpregs((caddr_t)pcb->pcb_fpctxp);	
		fp = fpu_ctxalloc();
		*new_context = (int)fp;
		bcopy((caddr_t)pcb->pcb_fpctxp, (caddr_t)fp, sizeof *fp);
		u.u_ar0[PS] = u.u_ar0[PS] & ~PSR_EF;
		fp_ctxp = 0;	/* current loaded context does not belong
				 * to anyone.
				 */
	}
	return (1);
}

fpu_ofile()
{
}

/* ARGSUSED */
fpu_newproc(nfp, new_context, childu)
struct file *nfp;
int new_context;
struct user *childu;
{
	/*
	 * initialize the fp context pointer in the new procs
	 * uarea, it was allocated above in fpu_fork_context
	 */
	childu->u_pcb.pcb_fpctxp = (struct fpu *)new_context;
}

fpu_core(corep)
struct core *corep;
{
	if (u.u_pcb.pcb_fpctxp)
		bcopy((caddr_t)u.u_pcb.pcb_fpctxp, (caddr_t)&corep->c_fpu,
			sizeof (struct fpu));
}

fpu_ptrace(req, p, ipc)
enum ptracereq req;
struct proc *p;
struct ipc *ipc;
{

	switch (req) {
	case PTRACE_GETFPREGS:
		runchild(p);
		if (copyout((caddr_t)&(ipc->ip_fpu), ipc->ip_addr,
		    sizeof (struct fpu)) != 0) {
			ipc->ip_error = 1;
		}
		break;

	case PTRACE_SETFPREGS:
		if (copyin(ipc->ip_addr, (caddr_t)&ipc->ip_fpu,
		    sizeof (struct fpu)) != 0) {
			ipc->ip_error = 1;
		}
		runchild(p);
		break;
	}
}

fpu_procxmt(req, ipc)
enum ptracereq req;
struct ipc *ipc;
{
	register struct pcb *pcb = &u.u_pcb;
	extern int fpu_exists;
	extern struct fpu *fpu_ctxalloc();

	switch (req) {
	case PTRACE_GETFPREGS:
		if (pcb->pcb_fpctxp)
			bcopy((caddr_t)pcb->pcb_fpctxp, (caddr_t)&ipc->ip_fpu,
				sizeof (struct fpu));
		break;

	case PTRACE_SETFPREGS:
		if (fpu_exists && pcb->pcb_fpctxp) {
			register u_int i;
			for (i = 0; i < 32; i++) {
				_fp_write_pfreg((FPU_REGS_TYPE *)&ipc->ip_fpu.fpu_regs[i], i);
			}
			_fp_write_pfsr(&ipc->ip_fpu.fpu_fsr);
		} else {
			if (pcb->pcb_fpctxp == (struct fpu *) 0) {
				pcb->pcb_fpctxp = fpu_ctxalloc();
			}
			bcopy((caddr_t)&ipc->ip_fpu, (caddr_t)pcb->pcb_fpctxp,
				sizeof (struct fpu));
			pcb->pcb_fpflags &= ~FP_UNINITIALIZED;
			if (!fpu_exists)
				pcb->pcb_fpflags |= FP_DISABLE;
		}
		break;
	default:
		return (-1);
	}
	return (1);
}
/*
 * Handle floaing point traps generated by simulation/emulation.
 */
void
fp_traps(pfpsd, ftt, rp)
	fp_simd_type	*pfpsd;		/* Pointer to simulator data */
	register enum ftt_type ftt;	/* trap type */
	register struct regs *rp;	/* ptr to regs fro trap */
{
	extern void trap();
	extern u_int beval;		/* beval is set in trap.c for 
					   specific architectures  */

	struct regs *saved_fptraprp = 0;

	/*
	 * If we take a user's exception in kernel mode, we want to trap
	 * with the user's registers.  Clear fptraprp in case we don't
	 * return from trap.
	 */
	saved_fptraprp = fptraprp;
	if (fptraprp != 0) {
		rp = fptraprp;
		fptraprp = 0;
	}

	switch (ftt) {
	case ftt_ieee:
		trap(T_FP_EXCEPTION, rp, pfpsd->fp_trapaddr,
		    pfpsd->fp_trapcode, 0);
		break;
	case ftt_fault:
		trap(T_DATA_FAULT, rp, pfpsd->fp_trapaddr, beval, pfpsd->fp_traprw);
		break;
	case ftt_alignment:
		trap(T_ALIGNMENT, rp, pfpsd->fp_trapaddr, 0, 0);
		break;
	case ftt_unimplemented:
		trap(T_UNIMP_INSTR, rp, pfpsd->fp_trapaddr, 0, 0);
		break;
	default:
		/*
		 * We don't expect any of the other types here.
		 */
		panic("fp_traps: bad ftt");
	}
	fptraprp = saved_fptraprp;

}

/*
 * floating point unit disabled:
 * One of two possibilities
 *	1. There is no fpu in the confguration. If so, emulate in SW.
 * 	2. THere is a FPU in config., however, this is the first time the
 *   	   process doing a fp op. If so, allocate a fpu context and 
 *	   turn on the EF bit in the PSR and let the proc go.
 */

fp_is_disabled(rp)

struct regs *rp;
{
     struct fpu *fp;
     enum ftt_type ftt;
     
     if (u.u_pcb.pcb_fpctxp == 0) {
	  fp = fpu_ctxalloc();	       /* allocate a fpu context */
	  uunix->u_pcb.pcb_fpctxp = fp;
     }
     else 
	fp = uunix->u_pcb.pcb_fpctxp;

     fp_ctxp = fp;
     
     if (fpu_exists) {	/* there is a fpu, go ahead and enable the fpu */
	  if (rp->r_psr&PSR_EF) {
	       panic("fp_disabled: no FPU but EF bit set");
	       /* NOT REACHED */
	  }
	  else  {	
		/* turn on enable fpu bit in the PSR and let the proc go */
		rp->r_psr |= PSR_EF;
		fp_enable(fp);
	  }
     } else {		/* no fpu, emulate the instruction */
	  fp_simd_type fpsd;
	  
	  flush_user_windows_to_stack();

	  if (ftt = fp_emulator(&fpsd, (fp_inst_type *)rp->r_pc,
				rp, (struct rwindow *)rp->r_sp,
				(struct fpu *) &fp->fpu_regs[0]))
	       fp_traps(&fpsd, ftt, rp);
     }
}


/*
 * Process the floating point queue
 *
 * Each entry in the floating point queue is processed in turn.
 * If processing an entry results in an exception fp_traps() is called to
 * handle the exception - this usually results in the generation of a signal
 * to be delivered to the user. There are 2 possible outcomes to this (note
 * that hardware generated signals cannot be held!):
 *
 *   1. If the signal is being ignored we continue to process the rest
 *	of the entries in the queue.
 *
 *   2. If arrangements have been made for return to a user signal handler,
 *	sendsig() will have copied the floating point queue onto the user's
 *	signal stack and zero'ed the queue count in the u_pcb. Note that
 *	this has the side effect of terminating fp_runq's processing loop.
 *	We will re-run the floating point queue on return from the user
 *	signal handler if necessary as part of normal setcontext processing.
 */
void
fp_runq(rp)
	register struct regs *rp;	/* ptr to regs for trap */
{
	register struct fpu *fp =	u.u_pcb.pcb_fpctxp;
	register struct fq *fqp =	fp->fpu_q;
	fp_simd_type			fpsd;
	extern int 			fpu_exists;

	/*
	 * don't preempt while manipulating the queue
	 */

	while (fp->fpu_qcnt) {
		enum ftt_type fptrap;

		fptrap = fpu_simulator((fp_simd_type *)&fpsd,
					(fp_inst_type *)fqp->FQu.fpq.addr,
					(fsr_type *)&fp->fpu_fsr,
					fqp->FQu.fpq.instr);
		if (fptrap) {
			/*
			 * fpu_simulator uses the fp registers directly but it
			 * uses the software copy of the fsr. We need to write
			 * that back to fpu so that fpu's state is current for
			 * ucontext.
			 */
			if (fpu_exists)
				_fp_write_pfsr(&fp->fpu_fsr);

			/* post signal */
			fp_traps(&fpsd, fptrap, rp);

			/*
			 * Break from loop to allow signal to be sent.
			 */
			break;
		}
		fp->fpu_qcnt--;
		fqp++;
	}

	/*
	 * fpu_simulator uses the fp registers directly, so we have
	 * to update the pcb copies to keep current, but it uses the
	 * software copy of the fsr, so we write that back to fpu
	 */
	if (fpu_exists) {
		register u_int i;

		for (i = 0; i < 32; i++)
			_fp_read_pfreg((FPU_REGS_TYPE *)&fp->fpu_regs[i], i);
		_fp_write_pfsr((FPU_FSR_TYPE *)&fp->fpu_fsr);
	}

}

