1
0
Files
irix-657m-src/irix/kern/ml/soft_fp/softfp_dispatch.c
2022-09-29 17:59:04 +03:00

813 lines
19 KiB
C

/**************************************************************************
* *
* Copyright (C) 1994-1996 Silicon Graphics, Inc. *
* *
* These coded instructions, statements, and computer programs contain *
* unpublished proprietary information of Silicon Graphics, Inc., and *
* are protected by Federal copyright law. They may not be disclosed *
* to third parties or copied or duplicated in any form, in whole or *
* in part, without the prior written consent of Silicon Graphics, Inc. *
* *
**************************************************************************/
#ident "$Revision: 1.20 $"
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/sbd.h>
#include <sys/pda.h>
#include <sys/fpu.h>
#include <sys/reg.h>
#include "fpparams.h"
#include "fpexterns.h"
#include <sys/uthread.h>
#include <ksys/exception.h>
#include <sys/proc.h> /* proxy definition */
#include <sys/debug.h>
#include <sys/kabi.h>
#include <sys/cmn_err.h>
#ifdef _R5000_CVT_WAR
extern int R5000_cvt_war;
#endif /* _R5000_CVT_WAR */
static int illfpinst(int, eframe_t *);
static int softfp_dispatch(struct eframe_s *, inst_t);
/*
* Floating-point coprocessor interrupt/exception handler
*/
/* SOME ASSUMPTIONS that this code makes:
* For R4000 and R10000 (or any processor that delivers precise
* fp exceptions): Since the exception is precise, curuthread must
* indicate the faulting process thread - hence we use CURUTHREAD through
* this code for those processors. (note that R8K in precise mode
* fits into this catagory.)
*
* For R8000 (or processors that deliver imprecise fp interrupts):
* p_fpowner is assumed to point to the faulting process uthread.
* curuthread is not usable - the interrupt could occur after a context
* switch - for example, as a result of calling checkfp() to save
* the fp context, since on R8K, reading the fp_csr causes all fp
* instructions to complete, and for any interrupts to occur.
*
* However, if the kernel preempts, then p_fpowner gets
* clobbered. R8K protects against this by entering this code
* with interrupts disabled - this is necessary also because R8K
* will immediately get another fp interrupt if interrupts are enabled.
* Other processors need to find their own solution here.
*/
static void
strayfp_intr(void)
{
/* got a floating point exception, but no process was using
* the unit.
*/
setsr(getsr() | SR_CU1);
set_fpc_csr(0); /* clear the exception/interrupt */
setsr(getsr() & ~SR_CU1);
cmn_err(CE_NOTE, "stray fp exception\n");
}
static void
sendSIGFPE(uthread_t *ut)
{
if (ut == curuthread) {
PCB(pcb_resched) = 1;
}
#ifdef IP26
/* Read comments for 636955 below. */
/* sigtouthread may call splhi() */
checkfp(ut, 0);
#endif
sigtouthread(ut, SIGFPE, 0);
/* We must clear the coprocessor interrupt without losing fp
* state, we do this by calling checkfp which will unload
* the fp to the pcb and clear the fp csr. A signal is
* pending, sendsig will clear the csr in the pcb after
* saving the fp state from the pcb into the sigcontext and
* before calling the signal handler
*/
#ifndef IP26
checkfp(ut, 0);
#endif
}
void
fp_intr(eframe_t *ep)
{
k_machreg_t epc;
uthread_t *ut;
int fp_sig;
inst_t flt_inst;
int junk;
#if TFP
k_machreg_t sr;
#endif
#if TFP
ut = private.p_fpowner;
#else
ut = curuthread;
#endif
if (ut == NULL) {
strayfp_intr();
return;
}
#if TFP
sr = ep->ef_sr;
if (!(sr & SR_DM)) {
int32_t csr;
/* TFP Performance Mode Floating Point
* If we get a denorm interrupt, we need to clear the E bit
* and return. The processor has already flushed the result
* to zero. Some users may want to be notified when this
* interrupt occurs, but for now we just ignore it.
*/
/* For TFP in performance mode, get the csr
* directly from the fpunit, not potentially from the
* pcb. This works because we are running at splhi.
*/
csr = get_fpc_csr();
if ((csr & FPCSR_FLUSH_ZERO) || ! (csr & FPCSR_UNIMP)) {
sendSIGFPE(ut);
return;
}
/* Clear the E bit, set that into the fpunit's fpc_csr,
* and return
*/
set_fpc_csr(csr & ~FPCSR_UNIMP);
return;
}
#ifdef IP26
/*
* Unload FP state for IP26 precise/debug mode. The rest
* of softfp is going to call splhi. splhi for IP26 will
* enable interrupts, and it will immedeately take the FP
* exception again. Note that checkfp is already fixed
* by not calling splhi for IP26. Eventhough the comment there
* does not make it too clear. PV # 636955
* This bug also has a change in sendSIGFPE
*/
checkfp(ut, 0) ;
#endif
#endif /* TFP */
/* The idiotic sysmips(MIPS_FPSIGINTR) interface:
* if ut->ut_pproxy->prxy_fp.pfp_fp is P_FP_SIGINTR1, then
* a SIGFPE is generated on every floating-point interrupt
* before each instruction and is then set to P_FP_SIGINTR2.
* If pfp_fp is P_FP_SIGINTR2, then no SIGFPE is generated
* but pfp_fp is set to P_FP_SIGINTR1.
*/
if ((fp_sig = ut->ut_pproxy->prxy_fp.pfp_fp) != 0) {
if (fp_sig == P_FP_SIGINTR1) {
ut->ut_pproxy->prxy_fp.pfp_fp = P_FP_SIGINTR2;
sendSIGFPE(ut);
return;
} else {
ut->ut_pproxy->prxy_fp.pfp_fp = P_FP_SIGINTR1;
}
}
#ifdef TFP
/* TFP may TLB fault loading the instruction at EPC since we may
* be executing from the icache. So we load it here where it's
* OK to fault (we think).
*/
/* Load instruction at epc */
ut->ut_epcinst = fuword((void *)ep->ef_epc);
if (ep->ef_cause & CAUSE_BD)
ut->ut_bdinst = fuword((void *)(ep->ef_epc + 4));
#endif
if (ep->ef_cause & CAUSE_BD) {
epc = emulate_branch(ep, ut->ut_epcinst, load_fpcsr(), &junk);
flt_inst = ut->ut_bdinst;
} else {
flt_inst = ut->ut_epcinst;
epc = ep->ef_epc + 4;
}
/*
* Check to see if the instruction to be emulated is a floating-point
* instruction. If it is not then this interrupt must have been caused
* by writing to the fpc_csr a value which caused the interrupt.
* It is possible however that when writing to the fpc_csr the
* instruction that is to be "emulated" when the interrupt is handled
* looks like a floating-point instruction and will incorrectly be
* emulated and a SIGFPE will not be sent. This is the user's problem
* because he shouldn't write a value into the fpc_csr which should
* cause an interrupt.
*/
if ( (flt_inst >> OPCODE_SHIFT == OPCODE_C1 &&
flt_inst >> OPCODE_25_SHIFT == OPCODE_C1_25)
||
flt_inst >> OPCODE_SHIFT == OPCODE_C1X) {
/* Looks like an fp instruction - call softfp
* to work on it.
* softfp returns non-zero if a signal is posted
* to the process.
*/
if (softfp_dispatch(ep, flt_inst) != 0) {
/*
* A modified checkfp protocol - an argument of 2
* indicates that the fpc_csr has already been
* stored into the PCB for this process, so don't
* do it again in checkfp. The reason is this: At
* the end of softfp, if a SIGFPE is being posted,
* softfp wants to put the cause of the SIGFPE
* into fpc_csr. Then, checkfp retrieves fpc_csr
* from the fpu and saves it in the PCB. This does
* not work for the R4000 and subsequent, since the
* ctc1 at the end of softfp would cause an immediate
* exception. So, if softfp is returning 1, it saves
* fpc_csr in the PCB rather than in the fpu.
* Similar problem with TFP, except it would cause
* an immediate interrupt. The only other way to
* avoid the FP interrupt is to either keep ALL
* interrupts disabled (IE bit in SR) or to clear
* the corresponding Enable bit in the fpc_csr. So
* we use the R4000 approach and don't reload the
* fpc_csr.
*/
checkfp(ut, 2);
} else {
/* The instruction was emulated by softfp
* without a signal being posted so now change
* the epc to the target pc.
*/
ep->ef_epc = epc;
ep->ef_exact_epc = epc;
}
} else {
sendSIGFPE(ut);
}
return;
}
#define DEBUG_SOFTFP_PREEMPTION
#ifdef DEBUG_SOFTFP_PREEMPTION
int force_checkfp;
#endif
static int
softfp_dispatch(struct eframe_s *ep, inst_t inst)
{
int extregmode;
int fp_csr;
int fp_enables;
int flush = 0;
inst_t opcode;
inst_t cop1_fmt;
inst_t cop1_func;
inst_t rs_num, rt_num, rd_num; /* register number for s,t and d reg */
int64_t rs, rt; /* s and t registers */
caddr_t epc; /* exception pc */
int iscop1x = 0;
#if MIPS4_ISA
inst_t rr_num; /* register number for r reg */
int64_t rr; /* r register */
#endif
#ifdef DEBUG_SOFTFP_PREEMPTION
if (force_checkfp)
checkfp(curuthread, 0);
#endif
ASSERT(curuthread != NULL);
extregmode = ABI_IS(ABI_IRIX5_64 | ABI_IRIX5_N32, get_current_abi());
fp_csr = load_fpcsr() & ~CSR_EXCEPT; /* All except the cause bits */
if (fp_csr & CSR_FSBITSET)
flush = FLUSH_INPUT_DENORMS|FLUSH_OUTPUT_DENORMS;
rd_num = (inst >> RD_SHIFT) & RD_FPRMASK;
/* Test for pc in range of u_nofpefrom/u_nofpeto */
epc = (caddr_t)(__psint_t)ep->ef_epc;
if (epc >= curuthread->ut_pproxy->prxy_fp.pfp_nofpefrom &&
epc <= curuthread->ut_pproxy->prxy_fp.pfp_nofpeto) {
/* The pc is in the range of addresses where
* exceptions will not deliver a sigfpe to
* the user. The algorithm for this is:
* 1. clear the enables, so we won't call psignal.
* 2. do the normal emulation.
* 3. Restore the enables at the end of the operation.
* be sure to mask off any cause bits that are also
* enabled, so we don't trigger an exception when
* storing back to the fp_csr register.
*/
fp_enables = fp_csr & CSR_ENABLE;
fp_csr &= ~CSR_ENABLE;
} else
fp_enables = 0;
fp_init_glob(ep, extregmode, fp_csr, fp_enables, flush, rd_num);
opcode = inst >> OPCODE_SHIFT;
#if MIPS4_ISA
if (opcode == COP1X)
iscop1x = 1;
else
#endif
if (opcode != COP1)
return illfpinst(fp_csr, ep);
cop1_fmt = (iscop1x == 0) ? ((inst >> C1_FMT_SHIFT) & C1_FMT_MASK)
: (inst & C1X_FMT_MASK);
rs_num = (inst >> RS_SHIFT) & RS_FPRMASK;
rt_num = (inst >> RT_SHIFT) & RT_FPRMASK;
/* check the 1st source register - if not in extended
* register mode, don't allow an odd register.
*/
if (!extregmode && (rs_num & 1))
return illfpinst(fp_csr, ep);
#ifdef _R5000_CVT_WAR
if( R5000_cvt_war &&
((cop1_fmt == (C1_FMT_WORD + 2)) ||
(cop1_fmt == (C1_FMT_LONG + 2))))
cop1_fmt -= 2;
#endif /* _R5000_CVT_WAR */
switch (cop1_fmt) {
default:
return illfpinst(fp_csr, ep);
case C1_FMT_SINGLE:
case C1_FMT_WORD:
rs = fpload_s(rs_num, extregmode);
break;
case C1_FMT_DOUBLE:
case C1_FMT_LONG:
rs = fpload_d(rs_num, extregmode);
break;
}
cop1_func = inst & C1_FUNC_MASK;
if (iscop1x)
goto cop1x;
if (cop1_func <= C1_FUNC_DIV ||
(cop1_func >= C1_FUNC_1stCMP && cop1_func <= C1_FUNC_lastCMP)) {
/* A binary op - check 2nd register specification */
if (!extregmode && (rt_num & 1))
return illfpinst(fp_csr, ep);
switch (cop1_fmt) {
case C1_FMT_SINGLE:
rt = fpload_s(rt_num, extregmode);
break;
case C1_FMT_DOUBLE:
rt = fpload_d(rt_num, extregmode);
break;
default:
return illfpinst(fp_csr, ep);
}
}
if ( ! (cop1_func >= C1_FUNC_1stCMP && cop1_func <= C1_FUNC_lastCMP)) {
/* Not a comparison opcode, so this op has a destination
* register - check for a legal specification.
*/
if (!extregmode && (rd_num & 1))
return illfpinst(fp_csr, ep);
}
switch (cop1_fmt) {
case C1_FMT_SINGLE:
if (cop1_func >= C1_FUNC_1stCMP &&
cop1_func <= C1_FUNC_lastCMP) {
#if MIPS4_ISA
_comp_sd(SINGLE, rs, rt, inst & COND_MASK,
(inst >> CC_SHIFT) & CC_MASK);
#else
_comp_sd(SINGLE, rs, rt, inst & COND_MASK, 0);
#endif
return ( GET_SOFTFP_FLAGS() & SIG_POSTED );
}
switch (cop1_func) {
case C1_FUNC_ADD:
_add_sd(SINGLE, rs, rt);
break;
case C1_FUNC_SUB:
_sub_sd(SINGLE, rs, rt);
break;
case C1_FUNC_MUL:
_mul_sd(SINGLE, rs, rt);
break;
case C1_FUNC_DIV:
_div_sd(SINGLE, rs, rt);
break;
case C1_FUNC_SQRT:
_sqrt_sd(SINGLE, rs);
break;
case C1_FUNC_ABS:
_abs_sd(SINGLE, rs);
break;
case C1_FUNC_NEG:
_neg_sd(SINGLE, rs);
break;
case C1_FUNC_MOV:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, MOV_SD );
#endif
FPSTORE_S(rs);
store_fpcsr();
break;
case C1_FUNC_ROUNDL:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, ROUNDL_SD );
#endif
/* set rounding mode to round-to-nearest */
SET_FP_CSR_RM(CSR_RM_RN);
_cvtl_sd( SINGLE, rs );
break;
case C1_FUNC_TRUNCL:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, TRUNCL_SD );
#endif
/* set rounding mode to round-to-zero */
SET_FP_CSR_RM(CSR_RM_RZ);
_cvtl_sd( SINGLE, rs );
break;
case C1_FUNC_CEILL:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, CEILL_SD );
#endif
/* set rounding mode to round-to-plus-infinity */
SET_FP_CSR_RM(CSR_RM_RPI);
_cvtl_sd( SINGLE, rs );
break;
case C1_FUNC_FLOORL:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, FLOORL_SD );
#endif
/* set rounding mode to round-to-minus-infinity */
SET_FP_CSR_RM(CSR_RM_RMI);
_cvtl_sd( SINGLE, rs );
break;
case C1_FUNC_ROUNDW:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, ROUNDW_SD );
#endif
/* set rounding mode to round-to-nearest */
SET_FP_CSR_RM(CSR_RM_RN);
_cvtw_sd( SINGLE, rs );
break;
case C1_FUNC_TRUNCW:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, TRUNCW_SD );
#endif
/* set rounding mode to round-to-zero */
SET_FP_CSR_RM(CSR_RM_RZ);
_cvtw_sd( SINGLE, rs );
break;
case C1_FUNC_CEILW:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, CEILW_SD );
#endif
/* set rounding mode to round-to-plus-infinity */
SET_FP_CSR_RM(CSR_RM_RPI);
_cvtw_sd( SINGLE, rs );
break;
case C1_FUNC_FLOORW:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, FLOORW_SD );
#endif
/* set rounding mode to round-to-minus-infinity */
SET_FP_CSR_RM(CSR_RM_RMI);
_cvtw_sd( SINGLE, rs );
break;
#if MIPS4_ISA
case C1_FUNC_RECIP:
#ifdef _FPTRACE
fp_trace1_sd( SINGLE, rs, RECIP_SD );
#endif
_div_sd(SINGLE, STWOP0, rs);
break;
case C1_FUNC_RSQRT:
_rsqrt_sd(SINGLE, rs);
break;
#endif
case C1_FUNC_CVTD:
_cvtd_s(rs);
break;
case C1_FUNC_CVTW:
_cvtw_sd(SINGLE, rs);
break;
case C1_FUNC_CVTL:
_cvtl_sd(SINGLE, rs);
break;
default:
return illfpinst(fp_csr, ep);
}
break;
case C1_FMT_DOUBLE:
if (cop1_func >= C1_FUNC_1stCMP &&
cop1_func <= C1_FUNC_lastCMP) {
#if MIPS4_ISA
_comp_sd(DOUBLE, rs, rt, inst & COND_MASK,
(inst >> CC_SHIFT) & CC_MASK);
#else
_comp_sd(DOUBLE, rs, rt, inst & COND_MASK, 0);
#endif
return ( GET_SOFTFP_FLAGS() & SIG_POSTED );
}
switch (cop1_func) {
case C1_FUNC_ADD:
_add_sd(DOUBLE, rs, rt);
break;
case C1_FUNC_SUB:
_sub_sd(DOUBLE, rs, rt);
break;
case C1_FUNC_MUL:
_mul_sd(DOUBLE, rs, rt);
break;
case C1_FUNC_DIV:
_div_sd(DOUBLE, rs, rt);
break;
case C1_FUNC_SQRT:
_sqrt_sd(DOUBLE, rs);
break;
case C1_FUNC_ABS:
_abs_sd(DOUBLE, rs);
break;
case C1_FUNC_NEG:
_neg_sd(DOUBLE, rs);
break;
case C1_FUNC_MOV:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, MOV_SD );
#endif
FPSTORE_D(rs);
store_fpcsr();
break;
case C1_FUNC_ROUNDL:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, ROUNDL_SD );
#endif
/* set rounding mode to round-to-nearest */
SET_FP_CSR_RM(CSR_RM_RN);
_cvtl_sd( DOUBLE, rs );
break;
case C1_FUNC_TRUNCL:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, TRUNCL_SD );
#endif
/* set rounding mode to round-to-zero */
SET_FP_CSR_RM(CSR_RM_RZ);
_cvtl_sd( DOUBLE, rs );
break;
case C1_FUNC_CEILL:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, CEILL_SD );
#endif
/* set rounding mode to round-to-plus-infinity */
SET_FP_CSR_RM(CSR_RM_RPI);
_cvtl_sd( DOUBLE, rs );
break;
case C1_FUNC_FLOORL:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, FLOORL_SD );
#endif
/* set rounding mode to round-to-minus-infinity */
SET_FP_CSR_RM(CSR_RM_RMI);
_cvtl_sd( DOUBLE, rs );
break;
case C1_FUNC_ROUNDW:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, ROUNDW_SD );
#endif
/* set rounding mode to round-to-nearest */
SET_FP_CSR_RM(CSR_RM_RN);
_cvtw_sd( DOUBLE, rs );
break;
case C1_FUNC_TRUNCW:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, TRUNCW_SD );
#endif
/* set rounding mode to round-to-zero */
SET_FP_CSR_RM(CSR_RM_RZ);
_cvtw_sd( DOUBLE, rs );
break;
case C1_FUNC_CEILW:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, CEILW_SD );
#endif
/* set rounding mode to round-to-plus-infinity */
SET_FP_CSR_RM(CSR_RM_RPI);
_cvtw_sd( DOUBLE, rs );
break;
case C1_FUNC_FLOORW:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, FLOORW_SD );
#endif
/* set rounding mode to round-to-minus-infinity */
SET_FP_CSR_RM(CSR_RM_RMI);
_cvtw_sd( DOUBLE, rs );
break;
#if MIPS4_ISA
case C1_FUNC_RECIP:
#ifdef _FPTRACE
fp_trace1_sd( DOUBLE, rs, RECIP_SD );
#endif
_div_sd(DOUBLE, DTWOP0, rs);
break;
case C1_FUNC_RSQRT:
_rsqrt_sd(DOUBLE, rs);
break;
#endif
case C1_FUNC_CVTS:
_cvts_d(rs);
break;
case C1_FUNC_CVTW:
_cvtw_sd(DOUBLE, rs);
break;
case C1_FUNC_CVTL:
_cvtl_sd(DOUBLE, rs);
break;
default:
return illfpinst(fp_csr, ep);
}
break;
case C1_FMT_WORD:
switch (cop1_func) {
case C1_FUNC_CVTS:
_cvtsd_w(SINGLE, rs);
break;
case C1_FUNC_CVTD:
_cvtsd_w(DOUBLE, rs);
break;
default:
return illfpinst(fp_csr, ep);
}
break;
case C1_FMT_LONG:
switch (cop1_func) {
case C1_FUNC_CVTS:
_cvtsd_l(SINGLE, rs);
break;
case C1_FUNC_CVTD:
_cvtsd_l(DOUBLE, rs);
break;
default:
return illfpinst(fp_csr, ep);
}
break;
default:
/* it is not a cop1x, so must be an illfpinst */
return illfpinst(fp_csr, ep);
}
return ( GET_SOFTFP_FLAGS() & SIG_POSTED );
cop1x:
#if MIPS4_ISA
rr_num = (inst >> RR_SHIFT) & RR_FPRMASK;
if (!extregmode && ((rt_num & 1) || (rr_num & 1) || (rd_num & 1)))
return illfpinst(fp_csr, ep);
switch (cop1_fmt) {
case C1_FMT_SINGLE:
rt = fpload_s(rt_num, extregmode);
rr = fpload_s(rr_num, extregmode);
break;
case C1_FMT_DOUBLE:
rt = fpload_d(rt_num, extregmode);
rr = fpload_d(rr_num, extregmode);
break;
default:
return illfpinst(fp_csr, ep);
}
switch (cop1_func) {
default:
return illfpinst(fp_csr, ep);
case C1X_FUNC_MADD_S:
_madd_sd(SINGLE, rs, rt, rr);
break;
case C1X_FUNC_MSUB_S:
_msub_sd(SINGLE, rs, rt, rr);
break;
case C1X_FUNC_NMADD_S:
_nmadd_sd(SINGLE, rs, rt, rr);
break;
case C1X_FUNC_NMSUB_S:
_nmsub_sd(SINGLE, rs, rt, rr);
break;
case C1X_FUNC_MADD_D:
_madd_sd(DOUBLE, rs, rt, rr);
break;
case C1X_FUNC_MSUB_D:
_msub_sd(DOUBLE, rs, rt, rr);
break;
case C1X_FUNC_NMADD_D:
_nmadd_sd(DOUBLE, rs, rt, rr);
break;
case C1X_FUNC_NMSUB_D:
_nmsub_sd(DOUBLE, rs, rt, rr);
break;
}
#endif /* MIPS4_ISA */
return ( GET_SOFTFP_FLAGS() & SIG_POSTED );
}
static int
illfpinst(int fp_csr, eframe_t *ep)
{
softfp_psignal(SIGILL, fp_csr, ep);
checkfp(curuthread, 0);
return 1;
}