diff options
author | Miod Vallat <miod@cvs.openbsd.org> | 2010-11-24 21:16:29 +0000 |
---|---|---|
committer | Miod Vallat <miod@cvs.openbsd.org> | 2010-11-24 21:16:29 +0000 |
commit | 1a6ca4c1794c5f70d8e81d3515a8b2fb580e2c02 (patch) | |
tree | b8ab39d08cd8a362978c5f402aa6785fca618484 /sys/arch | |
parent | 410a2cfa8b7417b4d090af83fc0bc8a91f8ada14 (diff) |
Floating-point emulation code for systems lacking proper FPU (i.e. Octeon),
enabled by option FPUEMUL.
This is pretty straightforward, except for conditional branch on FPU condition
codes emulation (bc1f/bc1fl/bc1t/bc1tl instructions): unlike most
RISC-with-delay-slots designs (m88k, sparc), the branch pipeline is not exposed
to the kernel on Mips, therefore we can not resume a branch without losing the
delay slot instruction.
Some other operating systems work around this issue by emulating the delay
slot instruction, but this is error-prone (and requires the kernel code to
be aware of all supported instructions of the processor it is currently running
on), some use dedicated breakpoints to single-step through the delay slot and
then resume the branch as expected, but this causes a lot of copy-on-write
allocations.
This code chooses a third path, of copying the delay slot instructions to run toa special `magic' page, followed by a special trap instruction to give control
back to the kernel. This makes sure the instruction will actually be run by the
processor, and that no more than one page per process is wasted, regardless of
the number of branches to emulate.
Tested on octeon (big-endian) by syuu@ and on loongson (little-endian) by me.
Note that enabling option FPUEMUL in the kernel will completely disable the
hardware FPU, if there is one; there is currently no way to build a kernel
supporting both hardware and software FPU, and there is no reason to change
this until there is a strong need to support both.
Diffstat (limited to 'sys/arch')
-rw-r--r-- | sys/arch/mips64/include/_types.h | 3 | ||||
-rw-r--r-- | sys/arch/mips64/include/cpu.h | 8 | ||||
-rw-r--r-- | sys/arch/mips64/include/proc.h | 15 | ||||
-rw-r--r-- | sys/arch/mips64/mips64/cpu.c | 6 | ||||
-rw-r--r-- | sys/arch/mips64/mips64/fp_emulate.c | 506 | ||||
-rw-r--r-- | sys/arch/mips64/mips64/mips64_machdep.c | 34 | ||||
-rw-r--r-- | sys/arch/mips64/mips64/trap.c | 175 | ||||
-rw-r--r-- | sys/arch/mips64/mips64/vm_machdep.c | 6 |
8 files changed, 725 insertions, 28 deletions
diff --git a/sys/arch/mips64/include/_types.h b/sys/arch/mips64/include/_types.h index bd59a97e6e8..e22ed5c01bf 100644 --- a/sys/arch/mips64/include/_types.h +++ b/sys/arch/mips64/include/_types.h @@ -1,4 +1,4 @@ -/* $OpenBSD: _types.h,v 1.7 2009/11/04 19:14:10 kettenis Exp $ */ +/* $OpenBSD: _types.h,v 1.8 2010/11/24 21:16:26 miod Exp $ */ /*- * Copyright (c) 1990, 1993 @@ -135,5 +135,6 @@ typedef struct label_t { /* Feature test macros */ #define __HAVE_TIMECOUNTER +#define __HAVE_EXEC_MD_MAP #endif /* _MIPS64__TYPES_H_ */ diff --git a/sys/arch/mips64/include/cpu.h b/sys/arch/mips64/include/cpu.h index a7843f21435..2ef12590d9d 100644 --- a/sys/arch/mips64/include/cpu.h +++ b/sys/arch/mips64/include/cpu.h @@ -1,4 +1,4 @@ -/* $OpenBSD: cpu.h,v 1.68 2010/10/24 15:40:03 miod Exp $ */ +/* $OpenBSD: cpu.h,v 1.69 2010/11/24 21:16:26 miod Exp $ */ /*- * Copyright (c) 1992, 1993 @@ -279,11 +279,13 @@ extern vaddr_t uncached_base; #define BREAK_BRKPT_VAL 514 #define BREAK_SOVER_VAL 515 #define BREAK_DDB_VAL 516 +#define BREAK_FPUEMUL_VAL 517 #define BREAK_KDB (BREAK_INSTR | (BREAK_KDB_VAL << BREAK_VAL_SHIFT)) #define BREAK_SSTEP (BREAK_INSTR | (BREAK_SSTEP_VAL << BREAK_VAL_SHIFT)) #define BREAK_BRKPT (BREAK_INSTR | (BREAK_BRKPT_VAL << BREAK_VAL_SHIFT)) #define BREAK_SOVER (BREAK_INSTR | (BREAK_SOVER_VAL << BREAK_VAL_SHIFT)) #define BREAK_DDB (BREAK_INSTR | (BREAK_DDB_VAL << BREAK_VAL_SHIFT)) +#define BREAK_FPUEMUL (BREAK_INSTR | (BREAK_FPUEMUL_VAL << BREAK_VAL_SHIFT)) /* * The floating point version and status registers. @@ -550,6 +552,7 @@ void cpu_startclock(struct cpu_info *); extern vaddr_t CpuCacheAliasMask; +struct exec_package; struct tlb_entry; struct user; @@ -604,10 +607,13 @@ int tlb_update(vaddr_t, unsigned); void tlb_read(int, struct tlb_entry *); void build_trampoline(vaddr_t, vaddr_t); +int exec_md_map(struct proc *, struct exec_package *); void savectx(struct user *, int); void enable_fpu(struct proc *); void save_fpu(void); +int fpe_branch_emulate(struct proc *, struct trap_frame *, uint32_t, + vaddr_t); int guarded_read_4(paddr_t, uint32_t *); int guarded_write_4(paddr_t, uint32_t); diff --git a/sys/arch/mips64/include/proc.h b/sys/arch/mips64/include/proc.h index b78d8f109e8..f4d31e7201a 100644 --- a/sys/arch/mips64/include/proc.h +++ b/sys/arch/mips64/include/proc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: proc.h,v 1.6 2010/11/24 21:01:02 miod Exp $ */ +/* $OpenBSD: proc.h,v 1.7 2010/11/24 21:16:26 miod Exp $ */ /* * Copyright (c) 1992, 1993 @@ -50,6 +50,11 @@ struct mdproc { vaddr_t md_ss_addr; /* single step address */ uint32_t md_ss_instr; /* saved single step instruction */ + /* fpu emulation fields */ + vaddr_t md_fppgva; /* vaddr of the branch emulation page */ + vaddr_t md_fpbranchva; /* vaddr of fp branch destination */ + vaddr_t md_fpslotva; /* initial vaddr of delay slot */ + /* The following is RM7000 dependent, but kept in for compatibility */ int md_pc_ctrl; /* performance counter control */ int md_pc_count; /* performance counter */ @@ -59,7 +64,13 @@ struct mdproc { int md_watch_m; }; -/* md_flags */ +/* + * Values for md_flags. + * MDP_FPUSED has two meanings: if the floating point hardware (coprocessor + * #1) is available, it means it has been used; if there is no floating + * point hardware, it means the process is currently running a duplicated + * delay slot, created by the branch emulation logic. + */ #define MDP_FPUSED 0x00000001 /* floating point coprocessor used */ #define MDP_PERF 0x00010000 /* Performance counter used */ #define MDP_WATCH1 0x00020000 /* Watch register 1 used */ diff --git a/sys/arch/mips64/mips64/cpu.c b/sys/arch/mips64/mips64/cpu.c index 640bd9796e9..fe8fe1a4e52 100644 --- a/sys/arch/mips64/mips64/cpu.c +++ b/sys/arch/mips64/mips64/cpu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cpu.c,v 1.35 2010/09/13 21:59:07 syuu Exp $ */ +/* $OpenBSD: cpu.c,v 1.36 2010/11/24 21:16:28 miod Exp $ */ /* * Copyright (c) 1997-2004 Opsycon AB (www.opsycon.se) @@ -335,6 +335,7 @@ cpu_switchto(struct proc *oldproc, struct proc *newproc) void enable_fpu(struct proc *p) { +#ifndef FPUEMUL struct cpu_info *ci = curcpu(); if (p->p_md.md_regs->sr & SR_FR_32) @@ -345,11 +346,13 @@ enable_fpu(struct proc *p) ci->ci_fpuproc = p; p->p_md.md_regs->sr |= SR_COP_1_BIT; p->p_md.md_flags |= MDP_FPUSED; +#endif } void save_fpu(void) { +#ifndef FPUEMUL struct cpu_info *ci = curcpu(); struct proc *p; @@ -359,6 +362,7 @@ save_fpu(void) MipsSaveCurFPState(p); else MipsSaveCurFPState16(p); +#endif } #ifdef MULTIPROCESSOR diff --git a/sys/arch/mips64/mips64/fp_emulate.c b/sys/arch/mips64/mips64/fp_emulate.c index a1de323ac48..aaec0c96f20 100644 --- a/sys/arch/mips64/mips64/fp_emulate.c +++ b/sys/arch/mips64/mips64/fp_emulate.c @@ -1,4 +1,4 @@ -/* $OpenBSD: fp_emulate.c,v 1.2 2010/10/27 20:05:12 miod Exp $ */ +/* $OpenBSD: fp_emulate.c,v 1.3 2010/11/24 21:16:28 miod Exp $ */ /* * Copyright (c) 2010 Miodrag Vallat. @@ -17,11 +17,9 @@ */ /* - * Floating Point completion code (MI softfloat code control engine). + * Floating Point completion/emulation code (MI softfloat code control engine). * * Supports all MIPS IV COP1 and COP1X floating-point instructions. - * Floating-point load and store instructions, as well as branch instructions, - * are not handled, as they should not require completion code. */ #include <sys/param.h> @@ -48,6 +46,12 @@ int fpu_emulate_cop1x(struct trap_frame *, uint32_t); uint64_t fpu_load(struct trap_frame *, uint, uint); void fpu_store(struct trap_frame *, uint, uint, uint64_t); +#ifdef FPUEMUL +int nofpu_emulate_cop1(struct trap_frame *, uint32_t, union sigval *); +int nofpu_emulate_cop1x(struct trap_frame *, uint32_t, union sigval *); +int nofpu_emulate_loadstore(struct trap_frame *, uint32_t, union sigval *); +int nofpu_emulate_movci(struct trap_frame *, uint32_t); +#endif typedef int (fpu_fn3)(struct trap_frame *, uint, uint, uint, uint); typedef int (fpu_fn4)(struct trap_frame *, uint, uint, uint, uint, uint); @@ -125,10 +129,15 @@ MipsFPTrap(struct trap_frame *tf) int fault_type = SI_NOINFO; int update_pcb = 0; int emulate = 0; +#ifdef FPUEMUL + int skip_insn = 1; +#else uint32_t sr; +#endif KDASSERT(tf == p->p_md.md_regs); +#ifndef FPUEMUL /* * Enable FPU, and read its status register. */ @@ -148,6 +157,15 @@ MipsFPTrap(struct trap_frame *tf) sig = SIGFPE; goto deliver; } +#else +#ifdef CPU_OCTEON + /* + * SR_FR_32 is hardwired to zero on Octeon; make sure it is + * set in the emulation view of the FPU state. + */ + tf->sr |= SR_FR_32; +#endif +#endif /* FPUEMUL */ /* * Get the faulting instruction. This should not fail, and @@ -185,9 +203,40 @@ MipsFPTrap(struct trap_frame *tf) * Not a FPU instruction. */ break; +#ifdef FPUEMUL + case OP_SPECIAL: + switch (inst.FRType.func) { + default: + /* + * Not a FPU instruction. + */ + break; + case OP_MOVCI: + /* + * This instruction should not require emulation, + * unless there is no FPU. + */ + emulate = 1; + break; + } + break; + case OP_LDC1: + case OP_LWC1: + case OP_SDC1: + case OP_SWC1: + /* + * These instructions should not require emulation, + * unless there is no FPU. + */ + emulate = 1; + break; +#endif case OP_COP1: switch (inst.RType.rs) { case OP_BC: +#ifdef FPUEMUL + skip_insn = 0; +#endif case OP_MF: case OP_DMF: case OP_CF: @@ -198,6 +247,9 @@ MipsFPTrap(struct trap_frame *tf) * These instructions should not require emulation, * unless there is no FPU. */ +#ifdef FPUEMUL + emulate = 1; +#endif break; default: emulate = 1; @@ -207,6 +259,25 @@ MipsFPTrap(struct trap_frame *tf) case OP_COP1X: switch (inst.FQType.op4) { default: + switch (inst.FRType.func) { +#ifdef FPUEMUL + case OP_LDXC1: + case OP_LWXC1: + case OP_SDXC1: + case OP_SWXC1: + case OP_PREFX: + /* + * These instructions should not require + * emulation, unless there is no FPU. + */ + emulate = 1; +#endif + default: + /* + * Not a valid instruction. + */ + break; + } break; case OP_MADD: case OP_MSUB: @@ -219,8 +290,10 @@ MipsFPTrap(struct trap_frame *tf) } if (emulate) { +#ifndef FPUEMUL KASSERT(p == ci->ci_fpuproc); save_fpu(); +#endif update_pcb = 1; sig = fpu_emulate(tf, insn, &sv); @@ -253,25 +326,39 @@ deliver: fault_type = FPE_FLTOVF; else /* if (excbits & FP_X_IMP) */ fault_type = FPE_FLTRES; + + break; +#ifdef FPUEMUL + case SIGBUS: + fault_type = BUS_ADRALN; + break; + case SIGSEGV: + fault_type = SEGV_MAPERR; break; +#endif } /* * Skip the instruction, unless we are delivering SIGILL. */ - - if (sig != SIGILL) { - if (tf->cause & CR_BR_DELAY) { - /* - * Note that it doesn't matter, at this point, - * that we pass the updated FSR value, as it is - * only used to decide whether to branch or not - * if the faulting instruction was BC1[FT]. - */ - tf->pc = MipsEmulateBranch(tf, tf->pc, fsr, 0); - } else - tf->pc += 4; +#ifdef FPUEMUL + if (skip_insn) { +#endif + if (sig != SIGILL) { + if (tf->cause & CR_BR_DELAY) { + /* + * Note that it doesn't matter, at this point, + * that we pass the updated FSR value, as it is + * only used to decide whether to branch or not + * if the faulting instruction was BC1[FT]. + */ + tf->pc = MipsEmulateBranch(tf, tf->pc, fsr, 0); + } else + tf->pc += 4; + } +#ifdef FPUEMUL } +#endif /* * Update the FPU status register. @@ -287,12 +374,17 @@ deliver: fsr &= ~FPCSR_C_MASK; if (update_pcb) tf->fsr = fsr; +#ifndef FPUEMUL __asm__ __volatile__ ("ctc1 %0, $31" :: "r" (fsr)); /* disable fpu before returning to trap() */ setsr(sr); +#endif if (sig != 0) { - sv.sival_ptr = (void *)pc; +#ifdef FPUEMUL + if (sig != SIGBUS && sig != SIGSEGV) +#endif + sv.sival_ptr = (void *)pc; KERNEL_PROC_LOCK(p); trapsignal(p, sig, 0, fault_type, sv); KERNEL_PROC_UNLOCK(p); @@ -314,10 +406,56 @@ fpu_emulate(struct trap_frame *tf, uint32_t insn, union sigval *sv) switch (inst.FRType.op) { default: break; +#ifdef FPUEMUL + case OP_SPECIAL: + return nofpu_emulate_movci(tf, insn); + case OP_LDC1: + case OP_LWC1: + case OP_SDC1: + case OP_SWC1: + return nofpu_emulate_loadstore(tf, insn, sv); +#endif case OP_COP1: - return fpu_emulate_cop1(tf, insn); + switch (inst.RType.rs) { +#ifdef FPUEMUL + case OP_MF: + case OP_DMF: + case OP_CF: + case OP_MT: + case OP_DMT: + case OP_CT: + case OP_BC: + return nofpu_emulate_cop1(tf, insn, sv); +#endif + default: + return fpu_emulate_cop1(tf, insn); + } + break; case OP_COP1X: - return fpu_emulate_cop1x(tf, insn); + switch (inst.FQType.op4) { +#ifdef FPUEMUL + default: + switch (inst.FRType.func) { + case OP_LDXC1: + case OP_LWXC1: + case OP_SDXC1: + case OP_SWXC1: + case OP_PREFX: + return nofpu_emulate_cop1x(tf, insn, sv); + default: + break; + } + break; + case OP_MADD: + case OP_MSUB: + case OP_NMADD: + case OP_NMSUB: + return fpu_emulate_cop1x(tf, insn); +#else + default: + return fpu_emulate_cop1x(tf, insn); +#endif + } } return SIGILL; @@ -1312,3 +1450,333 @@ fpu_trunc_w(struct trap_frame *tf, uint fmt, uint ft, uint fs, uint fd) /* round towards zero */ return fpu_int_w(tf, fmt, ft, fs, fd, FP_RZ); } + +#ifdef FPUEMUL + +/* + * Emulate a COP1 non-FPU instruction. + */ +int +nofpu_emulate_cop1(struct trap_frame *tf, uint32_t insn, union sigval *sv) +{ + register_t *regs = (register_t *)tf; + InstFmt inst; + int32_t cval; + + inst = *(InstFmt *)&insn; + + switch (inst.RType.rs) { + case OP_MF: + if (inst.FRType.fd != 0 || inst.FRType.func != 0) + return SIGILL; + if (inst.FRType.ft != ZERO) + regs[inst.FRType.ft] = + (int32_t)regs[FPBASE + inst.FRType.fs]; + break; + case OP_DMF: + if (inst.FRType.fd != 0 || inst.FRType.func != 0) + return SIGILL; + if ((tf->sr & SR_FR_32) != 0 || (inst.FRType.fs & 1) == 0) { + if (inst.FRType.ft != ZERO) + regs[inst.FRType.ft] = + fpu_load(tf, FMT_L, inst.FRType.fs); + } + break; + case OP_CF: + if (inst.FRType.fd != 0 || inst.FRType.func != 0) + return SIGILL; + if (inst.FRType.ft != ZERO) { + switch (inst.FRType.fs) { + case 0: /* FPC_ID */ + cval = MIPS_SOFT << 8; + break; + case 31: /* FPC_CSR */ + cval = (int32_t)tf->fsr; + break; + default: + cval = 0; + break; + } + regs[inst.FRType.ft] = (int64_t)cval; + } + break; + case OP_MT: + if (inst.FRType.fd != 0 || inst.FRType.func != 0) + return SIGILL; + regs[FPBASE + inst.FRType.fs] = (int32_t)regs[inst.FRType.ft]; + break; + case OP_DMT: + if (inst.FRType.fd != 0 || inst.FRType.func != 0) + return SIGILL; + if ((tf->sr & SR_FR_32) != 0 || (inst.FRType.fs & 1) == 0) { + fpu_store(tf, FMT_L, inst.FRType.fs, + regs[inst.FRType.ft]); + } + break; + case OP_CT: + if (inst.FRType.fd != 0 || inst.FRType.func != 0) + return SIGILL; + cval = (int32_t)regs[inst.FRType.ft]; + switch (inst.FRType.fs) { + case 31: /* FPC_CSR */ + cval &= ~FPCSR_C_E; + tf->fsr = cval; + break; + case 0: /* FPC_ID */ + default: + break; + } + break; + case OP_BC: + { + uint cc, nd, istf; + int condition; + vaddr_t dest; + uint32_t dinsn; + + cc = (inst.RType.rt & COPz_BC_CC_MASK) >> COPz_BC_CC_SHIFT; + nd = inst.RType.rt & COPz_BCL_TF_MASK; + istf = inst.RType.rt & COPz_BC_TF_MASK; + condition = tf->fsr & FPCSR_CONDVAL(cc); + if ((!condition && !istf) /*bc1f*/ || + (condition && istf) /*bc1t*/) { + /* + * Branch taken: if the delay slot is not a nop, + * copy the delay slot instruction to the dedicated + * relocation page, in order to be able to have the + * cpu process it and give control back to the + * kernel, for us to redirect to the branch + * destination. + */ + /* inline MipsEmulateBranch(tf, tf->pc, tf->fsr, insn)*/ + dest = tf->pc + 4 + ((short)inst.IType.imm << 2); + if (copyin((const void *)(tf->pc + 4), &dinsn, + sizeof dinsn)) { + sv->sival_ptr = (void *)(tf->pc + 4); + return SIGSEGV; + } + if (dinsn == 0x00000000 /* nop */ || + dinsn == 0x00000040 /* ssnop */) { + tf->pc = dest; + } else { + if (fpe_branch_emulate(curproc, tf, dinsn, + dest) != 0) + return SIGILL; + } + } else { + /* + * Branch not taken: skip the instruction, and + * skip the delay slot if it was a `branch likely' + * instruction. + */ + tf->pc += 4; + if (nd) + tf->pc += 4; + } + } + break; + } + + return 0; +} + +/* + * Emulate a COP1X non-FPU instruction. + */ +int +nofpu_emulate_cop1x(struct trap_frame *tf, uint32_t insn, union sigval *sv) +{ + register_t *regs = (register_t *)tf; + InstFmt inst; + vaddr_t va; + uint64_t ddata; + uint32_t wdata; + + inst = *(InstFmt *)&insn; + switch (inst.FRType.func) { + case OP_LDXC1: + if (inst.FQType.fs != 0) + return SIGILL; + va = (vaddr_t)regs[inst.FQType.fr] + + (vaddr_t)regs[inst.FQType.ft]; + if ((va & 0x07) != 0) { + sv->sival_ptr = (void *)va; + return SIGBUS; + } + if (copyin((const void *)va, &ddata, sizeof ddata) != 0) { + sv->sival_ptr = (void *)va; + return SIGSEGV; + } + if ((tf->sr & SR_FR_32) != 0 || (inst.FQType.fd & 1) == 0) + fpu_store(tf, FMT_L, inst.FQType.fd, ddata); + break; + case OP_LWXC1: + if (inst.FQType.fs != 0) + return SIGILL; + va = (vaddr_t)regs[inst.FQType.fr] + + (vaddr_t)regs[inst.FQType.ft]; + if ((va & 0x03) != 0) { + sv->sival_ptr = (void *)va; + return SIGBUS; + } +#ifdef __MIPSEB__ + va ^= 4; +#endif + if (copyin((const void *)va, &wdata, sizeof wdata) != 0) { + sv->sival_ptr = (void *)va; + return SIGSEGV; + } + regs[FPBASE + inst.FQType.fd] = wdata; + break; + case OP_SDXC1: + if (inst.FQType.fd != 0) + return SIGILL; + va = (vaddr_t)regs[inst.FQType.fr] + + (vaddr_t)regs[inst.FQType.ft]; + if ((va & 0x07) != 0) { + sv->sival_ptr = (void *)va; + return SIGBUS; + } + if ((tf->sr & SR_FR_32) != 0 || (inst.FQType.fs & 1) == 0) + ddata = fpu_load(tf, FMT_L, inst.FQType.fs); + else { + /* undefined behaviour, don't expose stack content */ + ddata = 0; + } + if (copyout(&ddata, (void *)va, sizeof ddata) != 0) { + sv->sival_ptr = (void *)va; + return SIGSEGV; + } + break; + case OP_SWXC1: + if (inst.FQType.fd != 0) + return SIGILL; + va = (vaddr_t)regs[inst.FQType.fr] + + (vaddr_t)regs[inst.FQType.ft]; + if ((va & 0x03) != 0) { + sv->sival_ptr = (void *)va; + return SIGBUS; + } +#ifdef __MIPSEB__ + va ^= 4; +#endif + wdata = regs[FPBASE + inst.FQType.fs]; + if (copyout(&wdata, (void *)va, sizeof wdata) != 0) { + sv->sival_ptr = (void *)va; + return SIGSEGV; + } + break; + case OP_PREFX: + /* nothing to do */ + break; + } + + return 0; +} + +/* + * Emulate a load/store instruction on FPU registers. + */ +int +nofpu_emulate_loadstore(struct trap_frame *tf, uint32_t insn, union sigval *sv) +{ + register_t *regs = (register_t *)tf; + InstFmt inst; + vaddr_t va; + uint64_t ddata; + uint32_t wdata; + + inst = *(InstFmt *)&insn; + switch (inst.IType.op) { + case OP_LDC1: + va = (vaddr_t)regs[inst.IType.rs] + (int16_t)inst.IType.imm; + if ((va & 0x07) != 0) { + sv->sival_ptr = (void *)va; + return SIGBUS; + } + if (copyin((const void *)va, &ddata, sizeof ddata) != 0) { + sv->sival_ptr = (void *)va; + return SIGSEGV; + } + if ((tf->sr & SR_FR_32) != 0 || (inst.IType.rt & 1) == 0) + fpu_store(tf, FMT_L, inst.IType.rt, ddata); + break; + case OP_LWC1: + va = (vaddr_t)regs[inst.IType.rs] + (int16_t)inst.IType.imm; + if ((va & 0x03) != 0) { + sv->sival_ptr = (void *)va; + return SIGBUS; + } +#ifdef __MIPSEB__ + va ^= 4; +#endif + if (copyin((const void *)va, &wdata, sizeof wdata) != 0) { + sv->sival_ptr = (void *)va; + return SIGSEGV; + } + regs[FPBASE + inst.IType.rt] = wdata; + break; + case OP_SDC1: + va = (vaddr_t)regs[inst.IType.rs] + (int16_t)inst.IType.imm; + if ((va & 0x07) != 0) { + sv->sival_ptr = (void *)va; + return SIGBUS; + } + if ((tf->sr & SR_FR_32) != 0 || (inst.IType.rt & 1) == 0) + ddata = fpu_load(tf, FMT_L, inst.IType.rt); + else { + /* undefined behaviour, don't expose stack content */ + ddata = 0; + } + if (copyout(&ddata, (void *)va, sizeof ddata) != 0) { + sv->sival_ptr = (void *)va; + return SIGSEGV; + } + break; + case OP_SWC1: + va = (vaddr_t)regs[inst.IType.rs] + (int16_t)inst.IType.imm; + if ((va & 0x03) != 0) { + sv->sival_ptr = (void *)va; + return SIGBUS; + } +#ifdef __MIPSEB__ + va ^= 4; +#endif + wdata = regs[FPBASE + inst.IType.rt]; + if (copyout(&wdata, (void *)va, sizeof wdata) != 0) { + sv->sival_ptr = (void *)va; + return SIGSEGV; + } + break; + } + + return 0; +} + +/* + * Emulate MOVF and MOVT. + */ +int +nofpu_emulate_movci(struct trap_frame *tf, uint32_t insn) +{ + register_t *regs = (register_t *)tf; + InstFmt inst; + uint cc, istf; + int condition; + + inst = *(InstFmt *)&insn; + if ((inst.RType.rt & 0x02) != 0 || inst.RType.shamt != 0) + return SIGILL; + + cc = inst.RType.rt >> 2; + istf = inst.RType.rt & COPz_BC_TF_MASK; + condition = tf->fsr & FPCSR_CONDVAL(cc); + if ((!condition && !istf) /*movf*/ || (condition && istf) /*movt*/) { + if (inst.RType.rd != ZERO) + regs[inst.RType.rd] = regs[inst.RType.rs]; + } + + return 0; +} + +#endif /* FPUEMUL */ diff --git a/sys/arch/mips64/mips64/mips64_machdep.c b/sys/arch/mips64/mips64/mips64_machdep.c index 7c322022a25..3cb514bf63c 100644 --- a/sys/arch/mips64/mips64/mips64_machdep.c +++ b/sys/arch/mips64/mips64/mips64_machdep.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mips64_machdep.c,v 1.1 2010/10/24 15:40:03 miod Exp $ */ +/* $OpenBSD: mips64_machdep.c,v 1.2 2010/11/24 21:16:28 miod Exp $ */ /* * Copyright (c) 2009, 2010 Miodrag Vallat. @@ -141,7 +141,9 @@ setregs(p, pack, stack, retval) #endif p->p_md.md_regs->sr |= idle_mask & SR_INT_MASK; p->p_md.md_regs->ic = (idle_mask << 8) & IC_INT_MASK; +#ifndef FPUEMUL p->p_md.md_flags &= ~MDP_FPUSED; +#endif if (ci->ci_fpuproc == p) ci->ci_fpuproc = NULL; p->p_md.md_ss_addr = 0; @@ -151,3 +153,33 @@ setregs(p, pack, stack, retval) retval[1] = 0; } + +int +exec_md_map(struct proc *p, struct exec_package *pack) +{ +#ifdef FPUEMUL + int rc; + vaddr_t va; + + /* + * If we are running with FPU instruction emulation, we need + * to allocate a special page in the process' address space, + * in order to be able to emulate delay slot instructions of + * successful conditional branches. + */ + + va = uvm_map_hint(p, UVM_PROT_RX); + rc = uvm_map(&p->p_vmspace->vm_map, &va, PAGE_SIZE, NULL, + UVM_UNKNOWN_OFFSET, 0, + UVM_MAPFLAG(UVM_PROT_NONE, UVM_PROT_ALL, UVM_INH_COPY, + UVM_ADV_NORMAL, UVM_FLAG_COPYONW)); + if (rc != 0) + return rc; +#ifdef DEBUG + printf("%s: p %p fppgva %p\n", __func__, p, va); +#endif + p->p_md.md_fppgva = va; +#endif + + return 0; +} diff --git a/sys/arch/mips64/mips64/trap.c b/sys/arch/mips64/mips64/trap.c index 0cf070b140b..bc4c0d7290f 100644 --- a/sys/arch/mips64/mips64/trap.c +++ b/sys/arch/mips64/mips64/trap.c @@ -1,4 +1,4 @@ -/* $OpenBSD: trap.c,v 1.71 2010/11/24 21:01:03 miod Exp $ */ +/* $OpenBSD: trap.c,v 1.72 2010/11/24 21:16:28 miod Exp $ */ /* * Copyright (c) 1988 University of Utah. @@ -640,6 +640,32 @@ printf("SIG-BUSB @%p pc %p, ra %p\n", trapframe->badvaddr, trapframe->pc, trapfr i = SIGTRAP; break; #endif +#ifdef FPUEMUL + case BREAK_FPUEMUL_VAL: + /* + * If this is a genuine FP emulation break, + * resume execution to our branch destination. + */ + if ((p->p_md.md_flags & MDP_FPUSED) != 0 && + p->p_md.md_fppgva + 4 == (vaddr_t)va) { + struct vm_map *map = &p->p_vmspace->vm_map; + + p->p_md.md_flags &= ~MDP_FPUSED; + locr0->pc = p->p_md.md_fpbranchva; + + /* + * Prevent access to the relocation page. + * XXX needs to be fixed to work with rthreads + */ + uvm_fault_unwire(map, p->p_md.md_fppgva, + p->p_md.md_fppgva + PAGE_SIZE); + (void)uvm_map_protect(map, p->p_md.md_fppgva, + p->p_md.md_fppgva + PAGE_SIZE, + UVM_PROT_NONE, FALSE); + goto out; + } + /* FALLTHROUGH */ +#endif default: typ = TRAP_TRACE; i = SIGTRAP; @@ -730,8 +756,11 @@ printf("SIG-BUSB @%p pc %p, ra %p\n", trapframe->badvaddr, trapframe->pc, trapfr typ = ILL_ILLOPC; break; } - +#ifdef FPUEMUL + MipsFPTrap(trapframe); +#else enable_fpu(p); +#endif goto out; case T_FPE: @@ -773,6 +802,16 @@ printf("SIG-BUSB @%p pc %p, ra %p\n", trapframe->badvaddr, trapframe->pc, trapfr #endif panic("trap"); } +#ifdef FPUEMUL + /* + * If a relocated delay slot causes an exception, blame the + * original delay slot address - userland is not supposed to + * know anything about emulation bowels. + */ + if ((p->p_md.md_flags & MDP_FPUSED) != 0 && + trapframe->badvaddr == p->p_md.md_fppgva) + trapframe->badvaddr = p->p_md.md_fpslotva; +#endif p->p_md.md_regs->pc = trapframe->pc; p->p_md.md_regs->cause = trapframe->cause; p->p_md.md_regs->badvaddr = trapframe->badvaddr; @@ -1361,3 +1400,135 @@ fn_name(vaddr_t addr) #endif /* !DDB */ #endif /* DDB || DEBUG */ + +#ifdef FPUEMUL +/* + * Set up a successful branch emulation. + * The delay slot instruction is copied to a reserved page, followed by a + * trap instruction to get control back, and resume at the branch + * destination. + */ +int +fpe_branch_emulate(struct proc *p, struct trap_frame *tf, uint32_t insn, + vaddr_t dest) +{ + struct vm_map *map = &p->p_vmspace->vm_map; + InstFmt inst; + int rc; + + /* + * Check the delay slot instruction: since it will run as a + * non-delay slot instruction, we want to reject branch instructions + * (which behaviour, when in a delay slot, is undefined anyway). + */ + + inst = *(InstFmt *)&insn; + rc = 0; + switch ((int)inst.JType.op) { + case OP_SPECIAL: + switch ((int)inst.RType.func) { + case OP_JR: + case OP_JALR: + rc = EINVAL; + break; + } + break; + case OP_BCOND: + switch ((int)inst.IType.rt) { + case OP_BLTZ: + case OP_BLTZL: + case OP_BLTZAL: + case OP_BLTZALL: + case OP_BGEZ: + case OP_BGEZL: + case OP_BGEZAL: + case OP_BGEZALL: + rc = EINVAL; + break; + } + break; + case OP_J: + case OP_JAL: + case OP_BEQ: + case OP_BEQL: + case OP_BNE: + case OP_BNEL: + case OP_BLEZ: + case OP_BLEZL: + case OP_BGTZ: + case OP_BGTZL: + rc = EINVAL; + break; + case OP_COP1: + if (inst.RType.rs == OP_BC) /* oh the irony */ + rc = EINVAL; + break; + } + + if (rc != 0) { +#ifdef DEBUG + printf("%s: bogus delay slot insn %08x\n", __func__, insn); +#endif + return rc; + } + + /* + * Temporarily change protection over the page used to relocate + * the delay slot, and fault it in. + */ + + rc = uvm_map_protect(map, p->p_md.md_fppgva, + p->p_md.md_fppgva + PAGE_SIZE, UVM_PROT_RWX, FALSE); + if (rc != 0) { +#ifdef DEBUG + printf("%s: uvm_map_protect on %p failed: %d\n", + __func__, p->p_md.md_fppgva, rc); +#endif + return rc; + } + rc = uvm_fault_wire(map, p->p_md.md_fppgva, + p->p_md.md_fppgva + PAGE_SIZE, UVM_PROT_RWX); + if (rc != 0) { +#ifdef DEBUG + printf("%s: uvm_fault_wire on %p failed: %d\n", + __func__, p->p_md.md_fppgva, rc); +#endif + goto err2; + } + + rc = copyout(&insn, (void *)p->p_md.md_fppgva, sizeof insn); + if (rc != 0) { +#ifdef DEBUG + printf("%s: copyout %p failed %d\n", + __func__, p->p_md.md_fppgva, rc); +#endif + goto err; + } + insn = BREAK_FPUEMUL; + rc = copyout(&insn, (void *)(p->p_md.md_fppgva + 4), sizeof insn); + if (rc != 0) { +#ifdef DEBUG + printf("%s: copyout %p failed %d\n", + __func__, p->p_md.md_fppgva + 4, rc); +#endif + goto err; + } + + (void)uvm_map_protect(map, p->p_md.md_fppgva, + p->p_md.md_fppgva + PAGE_SIZE, UVM_PROT_RX, FALSE); + p->p_md.md_fpbranchva = dest; + p->p_md.md_fpslotva = (vaddr_t)tf->pc + 4; + p->p_md.md_flags |= MDP_FPUSED; + tf->pc = p->p_md.md_fppgva; + pmap_proc_iflush(p, tf->pc, 2 * 4); + + return 0; + +err: + uvm_fault_unwire(map, p->p_md.md_fppgva, p->p_md.md_fppgva + PAGE_SIZE); +err2: + (void)uvm_map_protect(map, p->p_md.md_fppgva, + p->p_md.md_fppgva + PAGE_SIZE, UVM_PROT_NONE, FALSE); + return rc; +} +#endif diff --git a/sys/arch/mips64/mips64/vm_machdep.c b/sys/arch/mips64/mips64/vm_machdep.c index eedf19fc2e2..7ed194dea94 100644 --- a/sys/arch/mips64/mips64/vm_machdep.c +++ b/sys/arch/mips64/mips64/vm_machdep.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vm_machdep.c,v 1.23 2010/02/13 14:04:45 miod Exp $ */ +/* $OpenBSD: vm_machdep.c,v 1.24 2010/11/24 21:16:28 miod Exp $ */ /* * Copyright (c) 1988 University of Utah. * Copyright (c) 1992, 1993 @@ -87,6 +87,10 @@ cpu_fork(p1, p2, stack, stacksize, func, arg) save_fpu(); p2->p_md.md_flags = p1->p_md.md_flags & MDP_FORKSAVE; +#ifdef FPUEMUL + p2->p_md.md_fppgva = p1->p_md.md_fppgva; + KASSERT((p2->p_md.md_flags & MDP_FPUSED) == 0); +#endif /* Copy pcb from p1 to p2 */ if (p1 == curproc) { |