NetBSD/sys/arch/m68k/fpe/fpu_emulate.c

649 lines
14 KiB
C

/* $NetBSD: fpu_emulate.c,v 1.2 1995/03/10 01:43:05 gwr Exp $ */
/*
* Copyright (c) 1995 Gordon W. Ross
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* 4. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Gordon Ross
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* mc68881 emulator
* XXX - Just a start at it for now...
*/
#include <sys/types.h>
#include <sys/signal.h>
#include <machine/frame.h>
#define DEBUG 1 /* XXX */
/*
* Internal info about a decoded effective address.
*/
struct insn_ea {
int regnum;
int immed;
int flags;
#define EA_DIRECT 0x01
#define EA_PREDECR 0x02
#define EA_POSTINCR 0x04
#define EA_OFFSET 0x08 /* mode 5: base+offset */
#define EA_INDEXED 0x10 /* mode 6: complicated */
#define EA_ABS 0x20 /* mode 7: reg 0 or 1 */
#define EA_PC_REL 0x40 /* mode 7: reg 2 or 3 */
#define EA_IMMED 0x80 /* mode 7: reg 4 */
};
struct instruction {
int advance; /* length of instruction */
int datasize; /* byte, word, long, float, double, ... */
int opcode;
int word1;
struct insn_ea ea0;
struct insn_ea ea1;
};
int fpu_emul_fmovm(struct frame *frame,
struct fpframe *fpf,
struct instruction *insn);
int fpu_emul_type0(struct frame *frame,
struct fpframe *fpf,
struct instruction *insn);
int fpu_emul_type1(struct frame *frame,
struct fpframe *fpf,
struct instruction *insn);
int fpu_emul_brcc(struct frame *frame,
struct fpframe *fpf,
struct instruction *insn);
static int decode_ea(struct frame *frame,
struct instruction *insn,
struct insn_ea *ea,
int modreg);
static int load_ea(struct frame *frame,
struct instruction *insn,
struct insn_ea *ea,
char *cpureg);
static int store_ea(struct frame *frame,
struct instruction *insn,
struct insn_ea *ea,
char *cpureg);
/*
* Emulate a floating-point instruction.
* Return zero for success, else signal number.
* (Typically: zero, SIGFPE, SIGILL, SIGSEGV)
*/
int fpu_emulate(struct frame *frame, struct fpframe *fpf)
{
struct instruction insn;
int word, optype, sig;
word = fusword(frame->f_pc);
if (word < 0) {
#ifdef DEBUG
printf("fpu_emulate: fault reading opcode\n");
#endif
return SIGSEGV;
}
if ((word & 0xF000) != 0xF000) {
#ifdef DEBUG
printf("fpu_emulate: not coproc. insn.: opcode=0x%x\n", word);
#endif
return SIGILL;
}
if ((word & 0x0E00) != 0x0200) {
#ifdef DEBUG
printf("fpu_emulate: bad coproc. id: opcode=0x%x\n", word);
#endif
return SIGILL;
}
insn.opcode = word;
optype = (word & 0x01C0);
word = fusword(frame->f_pc + 2);
if (word < 0) {
#ifdef DEBUG
printf("fpu_emulate: fault reading word1\n");
#endif
return SIGSEGV;
}
insn.word1 = word;
/*
* Which family (or type) of opcode is it?
* Tests ordered by likelihood (hopefully).
* Certainly, type 0 is the most common.
*/
if (optype == 0x0000) {
/* type=0: generic */
if (insn.word1 & 0x8000) {
sig = fpu_emul_fmovm(frame, fpf, &insn);
} else {
sig = fpu_emul_type0(frame, fpf, &insn);
}
}
else if (optype == 0x0080) {
/* type=2: fbcc, short disp. */
sig = fpu_emul_brcc(frame, fpf, &insn);
}
else if (optype == 0x00C0) {
/* type=3: fbcc, long disp. */
sig = fpu_emul_brcc(frame, fpf, &insn);
}
else if (optype == 0x0040) {
/* type=1: fdbcc, fscc, ftrapcc */
sig = fpu_emul_type1(frame, fpf, &insn);
}
else {
/* type=4: fsave (privileged) */
/* type=5: frestore (privileged) */
/* type=6: reserved */
/* type=7: reserved */
#ifdef DEBUG
printf("fpu_emulate: bad opcode type: opcode=0x%x\n", insn.opcode);
#endif
sig = SIGILL;
}
if (sig == 0) {
frame->f_pc += insn.advance;
}
#if defined(DDB) && defined(DEBUG)
else kdb_trap(-1, frame);
#endif
return (sig);
}
/*
* type 0: fmovem, fmove <cr>
* Separated out of fpu_emul_type0 for efficiency.
* In this function, we know:
* (opcode & 0x01C0) == 0
* (word1 & 0x8000) == 0x8000
*
* No conversion or rounding is done by this instruction,
* and the FPSR is not affected.
*/
int fpu_emul_fmovm(struct frame *frame,
struct fpframe *fpf,
struct instruction *insn)
{
int word1, sig;
int reglist, regmask, regnum;
int fpu_to_mem, order;
int w1_post_incr; /* XXX - FP regs order? */
int *fpregs;
insn->advance = 4;
insn->datasize = 12;
word1 = insn->word1;
/* Bit 14 selects FPn or FP control regs. */
if (word1 & 0x4000) {
/*
* Bits 12,11 select register list mode:
* 0,0: Static reg list, pre-decr.
* 0,1: Dynamic reg list, pre-decr.
* 1,0: Static reg list, post-incr.
* 1,1: Dynamic reg list, post-incr
*/
w1_post_incr = word1 & 0x1000;
if (word1 & 0x0800) {
/* dynamic reg list */
reglist = frame->f_regs[(word1 & 0x70) >> 4];
} else
reglist = word1;
reglist &= 0xFF;
} else {
/* XXX: move to/from control registers */
reglist = word1 & 0x1C00;
return SIGILL;
}
/* Bit 13 selects direction (FPU to/from Mem) */
fpu_to_mem = word1 & 0x2000;
/* Get effective address. (modreg=opcode&077) */
sig = decode_ea(frame, insn, &insn->ea0, insn->opcode);
if (sig) return sig;
/* Get address of soft coprocessor regs. */
fpregs = &fpf->fpf_regs[0];
if (insn->ea0.flags & EA_PREDECR) {
regnum = 7;
order = -1;
} else {
regnum = 0;
order = 1;
}
while ((0 <= regnum) && (regnum < 8)) {
regmask = 1 << regnum;
if (regmask & reglist) {
if (fpu_to_mem)
sig = store_ea(frame, insn, &insn->ea0,
(char*) &fpregs[regnum]);
else /* mem to fpu */
sig = load_ea(frame, insn, &insn->ea0,
(char*) &fpregs[regnum]);
if (sig) break;
}
regnum += order;
}
return 0;
}
int fpu_emul_type0(struct frame *frame,
struct fpframe *fpf,
struct instruction *insn)
{
int sig;
/* Get effective address */
/* XXX */
switch(insn->word1 & 0x3F) {
case 0x00: /* fmove */
case 0x01: /* fint */
case 0x02: /* fsinh */
case 0x03: /* fintrz */
case 0x04: /* fsqrt */
case 0x06: /* flognp1 */
case 0x09: /* ftanh */
case 0x0A: /* fatan */
case 0x0C: /* fasin */
case 0x0D: /* fatanh */
case 0x0E: /* fsin */
case 0x0F: /* ftan */
case 0x10: /* fetox */
case 0x11: /* ftwotox */
case 0x12: /* ftentox */
case 0x14: /* flogn */
case 0x15: /* flog10 */
case 0x16: /* flog2 */
case 0x18: /* fabs */
case 0x19: /* fcosh */
case 0x1A: /* fneg */
case 0x1C: /* facos */
case 0x1D: /* fcos */
case 0x1E: /* fgetexp */
case 0x1F: /* fgetman */
case 0x20: /* fdiv */
case 0x21: /* fmod */
case 0x22: /* fadd */
case 0x23: /* fmul */
case 0x24: /* fsgldiv */
case 0x25: /* frem */
case 0x26: /* fscale */
case 0x27: /* fsglmul */
case 0x28: /* fsub */
case 0x38: /* fcmp */
case 0x3A: /* ftst */
default:
#ifdef DEBUG
printf("fpu_emul_type0: unknown: opcode=0x%x, word1=0x%x\n",
insn->opcode, insn->word1);
#endif
sig = SIGILL;
} /* switch */
return (sig);
}
/*
* type 1: fdbcc, fscc, ftrapcc
* In this function, we know:
* (opcode & 0x01C0) == 0x0040
*/
int fpu_emul_type1(struct frame *frame,
struct fpframe *fpf,
struct instruction *insn)
{
int sig;
/* Get effective address */
/* XXX */
switch (insn->opcode & 070) {
case 010: /* fdbcc */
/* XXX: If not CC { Decrement Dn; if (Dn >= 0) branch; } */
case 070: /* fscc or ftrapcc */
if ((insn->opcode & 07) > 1) {
/* ftrapcc */
/* XXX: If CC, advance and return SIGFPE */
break;
}
/* fallthrough */
default: /* fscc */
/* XXX: If CC, store ones, else store zero */
sig = SIGILL;
break;
}
return (sig);
}
/*
* Type 2 or 3: fbcc (also fnop)
* In this function, we know:
* (opcode & 0x0180) == 0x0080
*/
int fpu_emul_brcc(struct frame *frame,
struct fpframe *fpf,
struct instruction *insn)
{
int displ, word2;
int sig, advance;
/*
* Get branch displacement.
*/
advance = 4;
displ = insn->word1;
if (displ & 0x8000)
displ |= 0xFFFF0000;
if (insn->opcode & 0x40) {
word2 = fusword(frame->f_pc + 4);
if (word2 < 0) {
#ifdef DEBUG
printf("fpu_emul_brcc: fault reading word2\n");
#endif
return SIGSEGV;
}
displ << 16;
displ |= word2;
advance += 2;
}
/* XXX: If CC, frame->f_pc += displ */
return SIGILL;
}
/*
* Helper routines for dealing with "effective address" values.
*/
/*
* Decode an effective address into internal form.
* Returns zero on success, else signal number.
*/
static int decode_ea(struct frame *frame,
struct instruction *insn,
struct insn_ea *ea,
int modreg)
{
int immed_bytes = 0;
int data;
/* Set the most common value here. */
ea->regnum = 8 + (modreg & 7);
switch (modreg & 070) {
case 0: /* Dn */
ea->regnum = (modreg & 7);
ea->flags = EA_DIRECT;
break;
case 010: /* An */
ea->flags = EA_DIRECT;
break;
case 020: /* (An) */
ea->flags = 0;
break;
case 030: /* (An)+ */
ea->flags = EA_POSTINCR;
break;
case 040: /* -(An) */
ea->flags = EA_PREDECR;
break;
case 050: /* (d16,An) */
ea->flags = EA_OFFSET;
immed_bytes = 2;
break;
case 060: /* (d8,An,Xn) */
ea->flags = EA_INDEXED;
immed_bytes = 2;
break;
case 070: /* misc. */
ea->regnum = (modreg & 7);
switch (modreg & 7) {
case 0: /* (xxxx).W */
ea->flags = EA_ABS;
immed_bytes = 2;
break;
case 1: /* (xxxxxxxx).L */
ea->flags = EA_ABS;
immed_bytes = 4;
break;
case 2: /* (d16,PC) */
ea->flags = EA_PC_REL | EA_OFFSET;
immed_bytes = 2;
break;
case 3: /* (d8,PC,Xn) */
ea->flags = EA_PC_REL | EA_INDEXED;
immed_bytes = 2;
break;
case 4: /* #data */
ea->flags = EA_IMMED;
immed_bytes = insn->datasize;
break;
default:
return SIGILL;
} /* switch for mode 7 */
break;
} /* switch mode */
/* Now fetch any immediate data and advance. */
if (immed_bytes > 0) {
data = fusword(frame->f_pc + insn->advance);
if (data < 0)
return SIGSEGV;
insn->advance += 2;
if (data & 0x8000)
data |= 0xFFFF0000;
ea->immed = data;
}
if (immed_bytes > 2) {
data = fusword(frame->f_pc + insn->advance);
if (data < 0)
return SIGSEGV;
insn->advance += 2;
ea->immed <<= 16;
ea->immed |= data;
}
return 0;
}
/*
* Load a value from an effective address.
* Returns zero on success, else signal number.
*/
static int load_ea(struct frame *frame,
struct instruction *insn,
struct insn_ea *ea,
char *dst)
{
int *reg;
char *src;
int len;
#ifdef DIAGNOSTIC
if (ea->regnum & ~0xF)
panic("load_ea: bad regnum");
#endif
/* The dst is always int or larger. */
len = insn->datasize;
if (len < 4)
dst += (4 - len);
/* point to the register */
if (ea->flags & EA_PC_REL)
reg = &frame->f_pc;
else
reg = &frame->f_regs[ea->regnum];
if (ea->flags & (EA_DIRECT | EA_IMMED)) {
if (ea->flags & EA_DIRECT)
src = (char*) reg;
if (ea->flags & EA_IMMED)
src = (char*) &ea->immed;
if (len > 4)
return SIGILL;
/* The source is an int. */
if (len < 4)
src += (4 - len);
bcopy(src, dst, len);
} else {
/* One of MANY indirect forms... */
/* do pre-decrement */
if (ea->flags & EA_PREDECR)
*reg -= len;
/* Grab the register contents. */
src = (char*) *reg;
/* apply the signed offset */
if (ea->flags & EA_OFFSET)
src += ea->immed;
/* XXX - Don't know how to handle this yet. */
if (ea->flags & EA_INDEXED)
return SIGILL;
copyin(src, dst, len);
/* do post-increment */
if (ea->flags & EA_POSTINCR)
*reg += len;
}
return 0;
}
/*
* Store a value at the effective address.
* Returns zero on success, else signal number.
*/
static int store_ea(struct frame *frame,
struct instruction *insn,
struct insn_ea *ea,
char *src)
{
int *reg;
char *dst;
int len;
#ifdef DIAGNOSTIC
if (ea->regnum & ~0xF)
panic("load_ea: bad regnum");
#endif
/* The src is always int or larger. */
len = insn->datasize;
if (len < 4)
src += (4 - len);
/* point to the register */
if (ea->flags & EA_PC_REL)
reg = &frame->f_pc;
else
reg = &frame->f_regs[ea->regnum];
if (ea->flags & EA_IMMED)
return SIGILL;
if (ea->flags & EA_DIRECT) {
dst = (char*) reg;
if (len > 4)
return SIGILL;
/* The destination is an int. */
if (len < 4)
dst += (4 - len);
bcopy(src, dst, len);
} else {
/* One of MANY indirect forms... */
/* do pre-decrement */
if (ea->flags & EA_PREDECR)
*reg -= len;
/* Grab the register contents. */
dst = (char*) *reg;
/* apply the signed offset */
if (ea->flags & EA_OFFSET)
dst += ea->immed;
/* XXX - Don't know how to handle this yet. */
if (ea->flags & EA_INDEXED)
return SIGILL;
copyout(src, dst, len);
/* do post-increment */
if (ea->flags & EA_POSTINCR)
*reg += len;
}
return 0;
}