📄 cp1emu.c
字号:
/* * cp1emu.c: a MIPS coprocessor 1 (fpu) instruction emulator * * MIPS floating point support * Copyright (C) 1994-2000 Algorithmics Ltd. All rights reserved. * http://www.algor.co.uk * * Kevin D. Kissell, kevink@mips.com and Carsten Langgaard, carstenl@mips.com * Copyright (C) 2000 MIPS Technologies, Inc. * * This program is free software; you can distribute it and/or modify it * under the terms of the GNU General Public License (Version 2) as * published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. * * A complete emulator for MIPS coprocessor 1 instructions. This is * required for #float(switch) or #float(trap), where it catches all * COP1 instructions via the "CoProcessor Unusable" exception. * * More surprisingly it is also required for #float(ieee), to help out * the hardware fpu at the boundaries of the IEEE-754 representation * (denormalised values, infinities, underflow, etc). It is made * quite nasty because emulation of some non-COP1 instructions is * required, e.g. in branch delay slots. * * Note if you know that you won't have an fpu, then you'll get much * better performance by compiling with -msoft-float! */#include <linux/compiler.h>#include <linux/mm.h>#include <linux/signal.h>#include <linux/smp.h>#include <linux/smp_lock.h>#include <asm/asm.h>#include <asm/branch.h>#include <asm/bootinfo.h>#include <asm/byteorder.h>#include <asm/cpu.h>#include <asm/inst.h>#include <asm/uaccess.h>#include <asm/processor.h>#include <asm/mipsregs.h>#include <asm/system.h>#include <asm/pgtable.h>#include <asm/fpu_emulator.h>#include "ieee754.h"/* Strap kernel emulator for full MIPS IV emulation */#ifdef __mips#undef __mips#endif#define __mips 4typedef void *vaddr_t;/* Function which emulates the instruction in a branch delay slot. */static int mips_dsemul(struct pt_regs *, mips_instruction, unsigned long);/* Function which emulates a floating point instruction. */static int fpu_emu(struct pt_regs *, struct mips_fpu_soft_struct *, mips_instruction);#if __mips >= 4 && __mips != 32static int fpux_emu(struct pt_regs *, struct mips_fpu_soft_struct *, mips_instruction);#endif/* Further private data for which no space exists in mips_fpu_soft_struct */struct mips_fpu_emulator_private fpuemuprivate;/* Control registers */#define FPCREG_RID 0 /* $0 = revision id */#define FPCREG_CSR 31 /* $31 = csr *//* Convert Mips rounding mode (0..3) to IEEE library modes. */static const unsigned char ieee_rm[4] = { IEEE754_RN, IEEE754_RZ, IEEE754_RU, IEEE754_RD};#if __mips >= 4/* convert condition code register number to csr bit */static const unsigned int fpucondbit[8] = { FPU_CSR_COND0, FPU_CSR_COND1, FPU_CSR_COND2, FPU_CSR_COND3, FPU_CSR_COND4, FPU_CSR_COND5, FPU_CSR_COND6, FPU_CSR_COND7};#endif/* * Redundant with logic already in kernel/branch.c, * embedded in compute_return_epc. At some point, * a single subroutine should be used across both * modules. */static int isBranchInstr(mips_instruction * i){ switch (MIPSInst_OPCODE(*i)) { case spec_op: switch (MIPSInst_FUNC(*i)) { case jalr_op: case jr_op: return 1; } break; case bcond_op: switch (MIPSInst_RT(*i)) { case bltz_op: case bgez_op: case bltzl_op: case bgezl_op: case bltzal_op: case bgezal_op: case bltzall_op: case bgezall_op: return 1; } break; case j_op: case jal_op: case jalx_op: case beq_op: case bne_op: case blez_op: case bgtz_op: case beql_op: case bnel_op: case blezl_op: case bgtzl_op: return 1; case cop0_op: case cop1_op: case cop2_op: case cop1x_op: if (MIPSInst_RS(*i) == bc_op) return 1; break; } return 0;}#define REG_TO_VA (vaddr_t)#define VA_TO_REG (unsigned long)/* * In the Linux kernel, we support selection of FPR format on the * basis of the Status.FR bit. This does imply that, if a full 32 * FPRs are desired, there needs to be a flip-flop that can be written * to one at that bit position. In any case, normal MIPS ABI uses * only the even FPRs (Status.FR = 0). */#define CP0_STATUS_FR_SUPPORT/* * Emulate the single floating point instruction pointed at by EPC. * Two instructions if the instruction is in a branch delay slot. */static int cop1Emulate(struct pt_regs *regs, struct mips_fpu_soft_struct *ctx){ vaddr_t emulpc, contpc; mips_instruction ir; unsigned int cond; int err = 0; err = get_user(ir, (mips_instruction *) regs->cp0_epc); if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } /* XXX NEC Vr54xx bug workaround */ if ((regs->cp0_cause & CAUSEF_BD) && !isBranchInstr(&ir)) regs->cp0_cause &= ~CAUSEF_BD; if (regs->cp0_cause & CAUSEF_BD) { /* * The instruction to be emulated is in a branch delay slot * which means that we have to emulate the branch instruction * BEFORE we do the cop1 instruction. * * This branch could be a COP1 branch, but in that case we * would have had a trap for that instruction, and would not * come through this route. * * Linux MIPS branch emulator operates on context, updating the * cp0_epc. */ emulpc = REG_TO_VA(regs->cp0_epc + 4); /* Snapshot emulation target */ if (__compute_return_epc(regs)) {#ifdef CP1DBG printk("failed to emulate branch at %p\n", REG_TO_VA(regs->cp0_epc));#endif return SIGILL; } err = get_user(ir, (mips_instruction *) emulpc); if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } /* __computer_return_epc() will have updated cp0_epc */ contpc = REG_TO_VA regs->cp0_epc; /* In order not to confuse ptrace() et al, tweak context */ regs->cp0_epc = VA_TO_REG emulpc - 4; } else { emulpc = REG_TO_VA regs->cp0_epc; contpc = REG_TO_VA regs->cp0_epc + 4; }emul: fpuemuprivate.stats.emulated++; switch (MIPSInst_OPCODE(ir)) {#ifdef CP0_STATUS_FR_SUPPORT /* R4000+ 64-bit fpu registers */#ifndef SINGLE_ONLY_FPU case ldc1_op: { u64 *va = REG_TO_VA(regs->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); int ft = MIPSInst_RT(ir); if (!(regs->cp0_status & ST0_FR)) ft &= ~1; err = get_user(ctx->regs[ft], va); fpuemuprivate.stats.loads++; if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } } break; case sdc1_op: { fpureg_t *va = REG_TO_VA(regs->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); int ft = MIPSInst_RT(ir); if (!(regs->cp0_status & ST0_FR)) ft &= ~1; fpuemuprivate.stats.stores++; if (put_user(ctx->regs[ft], va)) { fpuemuprivate.stats.errors++; return SIGBUS; } } break;#endif case lwc1_op: { u32 *va = REG_TO_VA(regs->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); int ft = MIPSInst_RT(ir); u32 val; fpuemuprivate.stats.loads++; err = get_user(val, va); if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } if (regs->cp0_status & ST0_FR) { /* load whole register */ ctx->regs[ft] = (s64) val; } else if (ft & 1) { /* load to m.s. 32 bits */#ifdef SINGLE_ONLY_FPU /* illegal register in single-float mode */ return SIGILL;#else ctx->regs[(ft & ~1)] &= 0xffffffff; ctx->regs[(ft & ~1)] |= (fpureg_t) val << 32;#endif } else { /* load to l.s. 32 bits */ ctx->regs[ft] &= ~0xffffffffLL; ctx->regs[ft] |= val; } } break; case swc1_op: { u32 *va = REG_TO_VA(regs->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); unsigned int val; int ft = MIPSInst_RT(ir); fpuemuprivate.stats.stores++; if (regs->cp0_status & ST0_FR) { /* store whole register */ val = ctx->regs[ft]; } else if (ft & 1) {#ifdef SINGLE_ONLY_FPU /* illegal register in single-float mode */ return SIGILL;#else /* store from m.s. 32 bits */ val = ctx->regs[(ft & ~1)] >> 32;#endif } else { /* store from l.s. 32 bits */ val = ctx->regs[ft]; } if (put_user(val, va)) { fpuemuprivate.stats.errors++; return SIGBUS; } } break;#else /* old 32-bit fpu registers */ case lwc1_op: { u32 *va = REG_TO_VA(regs->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); err = get_user(ctx->regs[MIPSInst_RT(ir)], va); fpuemuprivate.stats.loads++; if (err) { fpuemuprivate.stats.errors++; return SIGBUS; } } break; case swc1_op: { u32 *va = REG_TO_VA(regs->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); fpuemuprivate.stats.stores++; if (put_user(ctx->regs[MIPSInst_RT(ir)], va)) { fpuemuprivate.stats.errors++; return SIGBUS; } } break; case ldc1_op: { u32 *va = REG_TO_VA(regs->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); unsigned int rt = MIPSInst_RT(ir) & ~1; int errs = 0; fpuemuprivate.stats.loads++;#if (defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) || defined(__MIPSEB__) err = get_user(ctx->regs[rt + 1], va + 0); err |= get_user(ctx->regs[rt + 0], va + 1);#else err = get_user(ctx->regs[rt + 0], va + 0); err |= get_user(ctx->regs[rt + 1], va + 1);#endif if (err) return SIGBUS; } break; case sdc1_op: { u32 *va = REG_TO_VA(regs->regs[MIPSInst_RS(ir)]) + MIPSInst_SIMM(ir); unsigned int rt = MIPSInst_RT(ir) & ~1; fpuemuprivate.stats.stores++;#if (defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) || defined(__MIPSEB__) if (put_user(ctx->regs[rt + 1], va + 0) || put_user(ctx->regs[rt + 0], va + 1)) return SIGBUS;#else if (put_user(ctx->regs[rt + 0], va + 0) || put_user(ctx->regs[rt + 1], va + 1)) return SIGBUS;#endif } break;#endif case cop1_op: switch (MIPSInst_RS(ir)) {#ifdef CP0_STATUS_FR_SUPPORT#if __mips64 && !defined(SINGLE_ONLY_FPU) case dmfc_op: /* copregister fs -> gpr[rt] */ if (MIPSInst_RT(ir) != 0) { int fs = MIPSInst_RD(ir); if (!(regs->cp0_status & ST0_FR)) fs &= ~1; regs->regs[MIPSInst_RT(ir)] = ctx->regs[fs]; } break; case dmtc_op: { /* copregister fs <- rt */ fpureg_t value; int fs = MIPSInst_RD(ir); if (!(regs->cp0_status & ST0_FR)) fs &= ~1; value = (MIPSInst_RT(ir) == 0) ? 0 : regs->regs[MIPSInst_RT(ir)]; ctx->regs[fs] = value; break; }#endif case mfc_op: /* copregister rd -> gpr[rt] */ if (MIPSInst_RT(ir) != 0) { /* default value from l.s. 32 bits */ int value = ctx->regs[MIPSInst_RD(ir)]; if (MIPSInst_RD(ir) & 1) {#ifdef SINGLE_ONLY_FPU /* illegal register in single-float mode */ return SIGILL;#else if (!(regs->cp0_status & ST0_FR)) { /* move from m.s. 32 bits */ value = ctx-> regs[MIPSInst_RD(ir) & ~1] >> 32; }#endif } regs->regs[MIPSInst_RT(ir)] = value; } break; case mtc_op: /* copregister rd <- rt */ { fpureg_t value; if (MIPSInst_RT(ir) == 0) value = 0; else value = (unsigned int) regs-> regs[MIPSInst_RT(ir)]; if (MIPSInst_RD(ir) & 1) {#ifdef SINGLE_ONLY_FPU /* illegal register in single-float mode */ return SIGILL;#else if (!(regs->cp0_status & ST0_FR)) { /* move to m.s. 32 bits */ ctx-> regs[ (MIPSInst_RD(ir) & ~1)] &= 0xffffffff; ctx-> regs[ (MIPSInst_RD(ir) & ~1)] |= value << 32; break; }#endif } /* move to l.s. 32 bits */ ctx->regs[MIPSInst_RD(ir)] &= ~0xffffffffLL; ctx->regs[MIPSInst_RD(ir)] |= value; } break;#else case mfc_op: /* copregister rd -> gpr[rt] */ if (MIPSInst_RT(ir) != 0) { unsigned value = ctx->regs[MIPSInst_RD(ir)]; regs->regs[MIPSInst_RT(ir)] = value; } break; case mtc_op: /* copregister rd <- rt */ { unsigned value; value = (MIPSInst_RT(ir) == 0) ? 0 : regs->regs[MIPSInst_RT(ir)]; ctx->regs[MIPSInst_RD(ir)] = value; } break;#endif case cfc_op: /* cop control register rd -> gpr[rt] */ { unsigned value; if (MIPSInst_RD(ir) == FPCREG_CSR) { value = ctx->sr;#ifdef CSRTRACE printk ("%p gpr[%d]<-csr=%08x\n", REG_TO_VA(regs->cp0_epc), MIPSInst_RT(ir), value);#endif } else if (MIPSInst_RD(ir) == FPCREG_RID) value = 0; else value = 0; if (MIPSInst_RT(ir)) regs->regs[MIPSInst_RT(ir)] = value; } break; case ctc_op: /* copregister rd <- rt */ { unsigned value; if (MIPSInst_RT(ir) == 0) value = 0; else value = regs->regs[MIPSInst_RT(ir)]; /* we only have one writable control reg */ if (MIPSInst_RD(ir) == FPCREG_CSR) {#ifdef CSRTRACE printk ("%p gpr[%d]->csr=%08x\n", REG_TO_VA(regs->cp0_epc), MIPSInst_RT(ir), value);#endif ctx->sr = value; /* copy new rounding mode and flush bit to ieee library state! */ ieee754_csr.nod = (ctx->sr & 0x1000000) != 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -