d94ffb4808
this simplifies the arm ports and keeps all the stuff in one place instead of spreading it thru notify(), trap() and syscall() functions and prevents useless fp state copying for kernel procs. also make sure to save fp in notify while still splhi().
514 lines
10 KiB
C
514 lines
10 KiB
C
/*
|
|
* VFPv2 or VFPv3 floating point unit
|
|
*/
|
|
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "ureg.h"
|
|
#include "arm.h"
|
|
|
|
/* subarchitecture code in m->havefp */
|
|
enum {
|
|
VFPv2 = 2,
|
|
VFPv3 = 3,
|
|
};
|
|
|
|
/* fp control regs. most are read-only */
|
|
enum {
|
|
Fpsid = 0,
|
|
Fpscr = 1, /* rw */
|
|
Mvfr1 = 6,
|
|
Mvfr0 = 7,
|
|
Fpexc = 8, /* rw */
|
|
Fpinst= 9, /* optional, for exceptions */
|
|
Fpinst2=10,
|
|
};
|
|
enum {
|
|
/* Fpexc bits */
|
|
Fpex = 1u << 31,
|
|
Fpenabled = 1 << 30,
|
|
Fpdex = 1 << 29, /* defined synch exception */
|
|
// Fp2v = 1 << 28, /* Fpinst2 reg is valid */
|
|
// Fpvv = 1 << 27, /* if Fpdex, vecitr is valid */
|
|
// Fptfv = 1 << 26, /* trapped fault is valid */
|
|
// Fpvecitr = MASK(3) << 8,
|
|
/* FSR bits appear here */
|
|
Fpmbc = Fpdex, /* bits exception handler must clear */
|
|
|
|
/* Fpscr bits; see u.h for more */
|
|
Stride = MASK(2) << 20,
|
|
Len = MASK(3) << 16,
|
|
Dn= 1 << 25,
|
|
Fz= 1 << 24,
|
|
/* trap exception enables (not allowed in vfp3) */
|
|
FPIDNRM = 1 << 15, /* input denormal */
|
|
Alltraps = FPIDNRM | FPINEX | FPUNFL | FPOVFL | FPZDIV | FPINVAL,
|
|
/* pending exceptions */
|
|
FPAIDNRM = 1 << 7, /* input denormal */
|
|
Allexc = FPAIDNRM | FPAINEX | FPAUNFL | FPAOVFL | FPAZDIV | FPAINVAL,
|
|
/* condition codes */
|
|
Allcc = MASK(4) << 28,
|
|
};
|
|
enum {
|
|
/* CpCPaccess bits */
|
|
Cpaccnosimd = 1u << 31,
|
|
Cpaccd16 = 1 << 30,
|
|
};
|
|
|
|
static char *
|
|
subarch(int impl, uint sa)
|
|
{
|
|
static char *armarchs[] = {
|
|
"VFPv1 (unsupported)",
|
|
"VFPv2",
|
|
"VFPv3+ with common VFP subarch v2",
|
|
"VFPv3+ with null subarch",
|
|
"VFPv3+ with common VFP subarch v3",
|
|
};
|
|
|
|
if (impl != 'A' || sa >= nelem(armarchs))
|
|
return "GOK";
|
|
else
|
|
return armarchs[sa];
|
|
}
|
|
|
|
static char *
|
|
implement(uchar impl)
|
|
{
|
|
if (impl == 'A')
|
|
return "arm";
|
|
else
|
|
return "unknown";
|
|
}
|
|
|
|
static int
|
|
havefp(void)
|
|
{
|
|
int gotfp;
|
|
ulong acc, sid;
|
|
|
|
if (m->havefpvalid)
|
|
return m->havefp;
|
|
|
|
m->havefp = 0;
|
|
gotfp = 1 << CpFP | 1 << CpDFP;
|
|
cpwrsc(0, CpCONTROL, 0, CpCPaccess, MASK(28));
|
|
acc = cprdsc(0, CpCONTROL, 0, CpCPaccess);
|
|
if ((acc & (MASK(2) << (2*CpFP))) == 0) {
|
|
gotfp &= ~(1 << CpFP);
|
|
print("fpon: no single FP coprocessor\n");
|
|
}
|
|
if ((acc & (MASK(2) << (2*CpDFP))) == 0) {
|
|
gotfp &= ~(1 << CpDFP);
|
|
print("fpon: no double FP coprocessor\n");
|
|
}
|
|
if (!gotfp) {
|
|
print("fpon: no FP coprocessors\n");
|
|
m->havefpvalid = 1;
|
|
return 0;
|
|
}
|
|
m->fpon = 1; /* don't panic */
|
|
sid = fprd(Fpsid);
|
|
m->fpon = 0;
|
|
switch((sid >> 16) & MASK(7)){
|
|
case 0: /* VFPv1 */
|
|
break;
|
|
case 1: /* VFPv2 */
|
|
m->havefp = VFPv2;
|
|
m->fpnregs = 16;
|
|
break;
|
|
default: /* VFPv3 or later */
|
|
m->havefp = VFPv3;
|
|
m->fpnregs = (acc & Cpaccd16) ? 16 : 32;
|
|
break;
|
|
}
|
|
if (m->machno == 0)
|
|
print("fp: %d registers, %s simd\n", m->fpnregs,
|
|
(acc & Cpaccnosimd? " no": ""));
|
|
m->havefpvalid = 1;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* these can be called to turn the fpu on or off for user procs,
|
|
* not just at system start up or shutdown.
|
|
*/
|
|
|
|
void
|
|
fpoff(void)
|
|
{
|
|
if (m->fpon) {
|
|
fpwr(Fpexc, 0);
|
|
m->fpon = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
fpononly(void)
|
|
{
|
|
if (!m->fpon && havefp()) {
|
|
/* enable fp. must be first operation on the FPUs. */
|
|
fpwr(Fpexc, Fpenabled);
|
|
m->fpon = 1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fpcfg(void)
|
|
{
|
|
int impl;
|
|
ulong sid;
|
|
static int printed;
|
|
|
|
/* clear pending exceptions; no traps in vfp3; all v7 ops are scalar */
|
|
m->fpscr = Dn | Fz | FPRNR | (FPINVAL | FPZDIV | FPOVFL) & ~Alltraps;
|
|
fpwr(Fpscr, m->fpscr);
|
|
m->fpconfiged = 1;
|
|
|
|
if (printed)
|
|
return;
|
|
sid = fprd(Fpsid);
|
|
impl = sid >> 24;
|
|
print("fp: %s arch %s; rev %ld\n", implement(impl),
|
|
subarch(impl, (sid >> 16) & MASK(7)), sid & MASK(4));
|
|
printed = 1;
|
|
}
|
|
|
|
void
|
|
fpinit(void)
|
|
{
|
|
if (havefp()) {
|
|
fpononly();
|
|
fpcfg();
|
|
}
|
|
}
|
|
|
|
void
|
|
fpon(void)
|
|
{
|
|
if (havefp()) {
|
|
fpononly();
|
|
if (m->fpconfiged)
|
|
fpwr(Fpscr, (fprd(Fpscr) & Allcc) | m->fpscr);
|
|
else
|
|
fpcfg(); /* 1st time on this fpu; configure it */
|
|
}
|
|
}
|
|
|
|
void
|
|
fpclear(void)
|
|
{
|
|
// ulong scr;
|
|
|
|
fpon();
|
|
// scr = fprd(Fpscr);
|
|
// m->fpscr = scr & ~Allexc;
|
|
// fpwr(Fpscr, m->fpscr);
|
|
|
|
fpwr(Fpexc, fprd(Fpexc) & ~Fpmbc);
|
|
}
|
|
|
|
|
|
/*
|
|
* Called when a note is about to be delivered to a
|
|
* user process, usually at the end of a system call.
|
|
* Note handlers are not allowed to use the FPU so
|
|
* the state is marked (after saving if necessary) and
|
|
* checked in the Device Not Available handler.
|
|
*/
|
|
void
|
|
fpunotify(Ureg*)
|
|
{
|
|
if(up->fpstate == FPactive){
|
|
fpsave(&up->fpsave);
|
|
up->fpstate = FPinactive;
|
|
}
|
|
up->fpstate |= FPillegal;
|
|
}
|
|
|
|
/*
|
|
* Called from sysnoted() via the machine-dependent
|
|
* noted() routine.
|
|
* Clear the flag set above in fpunotify().
|
|
*/
|
|
void
|
|
fpunoted(void)
|
|
{
|
|
up->fpstate &= ~FPillegal;
|
|
}
|
|
|
|
/* should only be called if p->fpstate == FPactive */
|
|
void
|
|
fpsave(FPsave *fps)
|
|
{
|
|
int n;
|
|
|
|
fpon();
|
|
fps->control = fps->status = fprd(Fpscr);
|
|
assert(m->fpnregs);
|
|
for (n = 0; n < m->fpnregs; n++)
|
|
fpsavereg(n, (uvlong *)fps->regs[n]);
|
|
fpoff();
|
|
}
|
|
|
|
static void
|
|
fprestore(Proc *p)
|
|
{
|
|
int n;
|
|
|
|
fpon();
|
|
fpwr(Fpscr, p->fpsave.control);
|
|
m->fpscr = fprd(Fpscr) & ~Allcc;
|
|
assert(m->fpnregs);
|
|
for (n = 0; n < m->fpnregs; n++)
|
|
fprestreg(n, *(uvlong *)p->fpsave.regs[n]);
|
|
}
|
|
|
|
/*
|
|
* Called from sched() and sleep() via the machine-dependent
|
|
* procsave() routine.
|
|
* About to go in to the scheduler.
|
|
* If the process wasn't using the FPU
|
|
* there's nothing to do.
|
|
*/
|
|
void
|
|
fpuprocsave(Proc *p)
|
|
{
|
|
if(p->fpstate == FPactive){
|
|
if(p->state == Moribund)
|
|
fpclear();
|
|
else{
|
|
/*
|
|
* Fpsave() stores without handling pending
|
|
* unmasked exeptions. Postnote() can't be called
|
|
* here as sleep() already has up->rlock, so
|
|
* the handling of pending exceptions is delayed
|
|
* until the process runs again and generates an
|
|
* emulation fault to activate the FPU.
|
|
*/
|
|
fpsave(&p->fpsave);
|
|
}
|
|
p->fpstate = FPinactive;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The process has been rescheduled and is about to run.
|
|
* Nothing to do here right now. If the process tries to use
|
|
* the FPU again it will cause a Device Not Available
|
|
* exception and the state will then be restored.
|
|
*/
|
|
void
|
|
fpuprocrestore(Proc *)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* The current process has been forked,
|
|
* save and copy neccesary state to child.
|
|
*/
|
|
void
|
|
fpuprocfork(Proc *p)
|
|
{
|
|
int s;
|
|
|
|
s = splhi();
|
|
switch(up->fpstate & ~FPillegal){
|
|
case FPactive:
|
|
fpsave(&up->fpsave);
|
|
up->fpstate = FPinactive;
|
|
/* no break */
|
|
case FPinactive:
|
|
p->fpsave = up->fpsave;
|
|
p->fpstate = FPinactive;
|
|
}
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* Disable the FPU.
|
|
* Called from sysexec() via sysprocsetup() to
|
|
* set the FPU for the new process.
|
|
*/
|
|
void
|
|
fpusysprocsetup(Proc *p)
|
|
{
|
|
p->fpstate = FPinit;
|
|
fpoff();
|
|
}
|
|
|
|
static void
|
|
mathnote(void)
|
|
{
|
|
ulong status;
|
|
char *msg, note[ERRMAX];
|
|
|
|
status = up->fpsave.status;
|
|
|
|
/*
|
|
* Some attention should probably be paid here to the
|
|
* exception masks and error summary.
|
|
*/
|
|
if (status & FPAINEX)
|
|
msg = "inexact";
|
|
else if (status & FPAOVFL)
|
|
msg = "overflow";
|
|
else if (status & FPAUNFL)
|
|
msg = "underflow";
|
|
else if (status & FPAZDIV)
|
|
msg = "divide by zero";
|
|
else if (status & FPAINVAL)
|
|
msg = "bad operation";
|
|
else
|
|
msg = "spurious";
|
|
snprint(note, sizeof note, "sys: fp: %s fppc=%#p status=%#lux",
|
|
msg, up->fpsave.pc, status);
|
|
postnote(up, 1, note, NDebug);
|
|
}
|
|
|
|
static void
|
|
mathemu(Ureg *)
|
|
{
|
|
if(m->havefp == VFPv3 && !(fprd(Fpexc) & (Fpex|Fpdex)))
|
|
iprint("mathemu: not an FP exception but an unknown FP opcode\n");
|
|
switch(up->fpstate){
|
|
case FPemu:
|
|
error("illegal instruction: VFP opcode in emulated mode");
|
|
case FPinit:
|
|
fpinit();
|
|
up->fpstate = FPactive;
|
|
break;
|
|
case FPinactive:
|
|
/*
|
|
* Before restoring the state, check for any pending
|
|
* exceptions. There's no way to restore the state without
|
|
* generating an unmasked exception.
|
|
* More attention should probably be paid here to the
|
|
* exception masks and error summary.
|
|
*/
|
|
if(up->fpsave.status & (FPAINEX|FPAUNFL|FPAOVFL|FPAZDIV|FPAINVAL)){
|
|
mathnote();
|
|
break;
|
|
}
|
|
fprestore(up);
|
|
up->fpstate = FPactive;
|
|
break;
|
|
case FPactive:
|
|
error("illegal instruction: bad vfp fpu opcode");
|
|
break;
|
|
}
|
|
fpclear();
|
|
}
|
|
|
|
void
|
|
fpstuck(uintptr pc)
|
|
{
|
|
if (m->fppc == pc && m->fppid == up->pid) {
|
|
m->fpcnt++;
|
|
if (m->fpcnt > 4)
|
|
panic("fpuemu: cpu%d stuck at pid %ld %s pc %#p "
|
|
"instr %#8.8lux", m->machno, up->pid, up->text,
|
|
pc, *(ulong *)pc);
|
|
} else {
|
|
m->fppid = up->pid;
|
|
m->fppc = pc;
|
|
m->fpcnt = 0;
|
|
}
|
|
}
|
|
|
|
enum {
|
|
N = 1<<31,
|
|
Z = 1<<30,
|
|
C = 1<<29,
|
|
V = 1<<28,
|
|
REGPC = 15,
|
|
};
|
|
|
|
static int
|
|
condok(int cc, int c)
|
|
{
|
|
switch(c){
|
|
case 0: /* Z set */
|
|
return cc&Z;
|
|
case 1: /* Z clear */
|
|
return (cc&Z) == 0;
|
|
case 2: /* C set */
|
|
return cc&C;
|
|
case 3: /* C clear */
|
|
return (cc&C) == 0;
|
|
case 4: /* N set */
|
|
return cc&N;
|
|
case 5: /* N clear */
|
|
return (cc&N) == 0;
|
|
case 6: /* V set */
|
|
return cc&V;
|
|
case 7: /* V clear */
|
|
return (cc&V) == 0;
|
|
case 8: /* C set and Z clear */
|
|
return cc&C && (cc&Z) == 0;
|
|
case 9: /* C clear or Z set */
|
|
return (cc&C) == 0 || cc&Z;
|
|
case 10: /* N set and V set, or N clear and V clear */
|
|
return (~cc&(N|V))==0 || (cc&(N|V)) == 0;
|
|
case 11: /* N set and V clear, or N clear and V set */
|
|
return (cc&(N|V))==N || (cc&(N|V))==V;
|
|
case 12: /* Z clear, and either N set and V set or N clear and V clear */
|
|
return (cc&Z) == 0 && ((~cc&(N|V))==0 || (cc&(N|V))==0);
|
|
case 13: /* Z set, or N set and V clear or N clear and V set */
|
|
return (cc&Z) || (cc&(N|V))==N || (cc&(N|V))==V;
|
|
case 14: /* always */
|
|
return 1;
|
|
case 15: /* never (reserved) */
|
|
return 0;
|
|
}
|
|
return 0; /* not reached */
|
|
}
|
|
|
|
/* only called to deal with user-mode instruction faults */
|
|
int
|
|
fpuemu(Ureg* ureg)
|
|
{
|
|
int s, nfp, cop, op;
|
|
uintptr pc;
|
|
|
|
if(waserror()){
|
|
postnote(up, 1, up->errstr, NDebug);
|
|
return 1;
|
|
}
|
|
|
|
if(up->fpstate & FPillegal)
|
|
error("floating point in note handler");
|
|
|
|
nfp = 0;
|
|
pc = ureg->pc;
|
|
validaddr(pc, 4, 0);
|
|
if(!condok(ureg->psr, *(ulong*)pc >> 28))
|
|
iprint("fpuemu: conditional instr shouldn't have got here\n");
|
|
op = (*(ulong *)pc >> 24) & MASK(4);
|
|
cop = (*(ulong *)pc >> 8) & MASK(4);
|
|
if(m->fpon)
|
|
fpstuck(pc); /* debugging; could move down 1 line */
|
|
if (ISFPAOP(cop, op)) { /* old arm 7500 fpa opcode? */
|
|
// iprint("fpuemu: fpa instr %#8.8lux at %#p\n", *(ulong *)pc, pc);
|
|
// error("illegal instruction: old arm 7500 fpa opcode");
|
|
s = spllo();
|
|
if(waserror()){
|
|
splx(s);
|
|
nexterror();
|
|
}
|
|
nfp = fpiarm(ureg); /* advances pc past emulated instr(s) */
|
|
if (nfp > 1) /* could adjust this threshold */
|
|
m->fppc = m->fpcnt = 0;
|
|
splx(s);
|
|
poperror();
|
|
} else if (ISVFPOP(cop, op)) { /* if vfp, fpu must be off */
|
|
mathemu(ureg); /* enable fpu & retry */
|
|
nfp = 1;
|
|
}
|
|
|
|
poperror();
|
|
return nfp;
|
|
}
|