1 /*
2 * VFPv2 or VFPv3 floating point unit
3 */
4 #include "u.h"
5 #include "../port/lib.h"
6 #include "mem.h"
7 #include "dat.h"
8 #include "fns.h"
9 #include "ureg.h"
10 #include "arm.h"
11
12 /* subarchitecture code in m->havefp */
13 enum {
14 VFPv2 = 2,
15 VFPv3 = 3,
16 };
17
18 /* fp control regs. most are read-only */
19 enum {
20 Fpsid = 0,
21 Fpscr = 1, /* rw */
22 Mvfr1 = 6,
23 Mvfr0 = 7,
24 Fpexc = 8, /* rw */
25 Fpinst= 9, /* optional, for exceptions */
26 Fpinst2=10,
27 };
28 enum {
29 /* Fpexc bits */
30 Fpex = 1u << 31,
31 Fpenabled = 1 << 30,
32 Fpdex = 1 << 29, /* defined synch exception */
33 // Fp2v = 1 << 28, /* Fpinst2 reg is valid */
34 // Fpvv = 1 << 27, /* if Fpdex, vecitr is valid */
35 // Fptfv = 1 << 26, /* trapped fault is valid */
36 // Fpvecitr = MASK(3) << 8,
37 /* FSR bits appear here */
38 Fpmbc = Fpdex, /* bits exception handler must clear */
39
40 /* Fpscr bits; see u.h for more */
41 Stride = MASK(2) << 20,
42 Len = MASK(3) << 16,
43 Dn= 1 << 25,
44 Fz= 1 << 24,
45 /* trap exception enables (not allowed in vfp3) */
46 FPIDNRM = 1 << 15, /* input denormal */
47 Alltraps = FPIDNRM | FPINEX | FPUNFL | FPOVFL | FPZDIV | FPINVAL,
48 /* pending exceptions */
49 FPAIDNRM = 1 << 7, /* input denormal */
50 Allexc = FPAIDNRM | FPAINEX | FPAUNFL | FPAOVFL | FPAZDIV | FPAINVAL,
51 /* condition codes */
52 Allcc = MASK(4) << 28,
53 };
54 enum {
55 /* CpCPaccess bits */
56 Cpaccnosimd = 1u << 31,
57 Cpaccd16 = 1 << 30,
58 };
59
60 static char *
subarch(int impl,uint sa)61 subarch(int impl, uint sa)
62 {
63 static char *armarchs[] = {
64 "VFPv1 (unsupported)",
65 "VFPv2",
66 "VFPv3+ with common VFP subarch v2",
67 "VFPv3+ with null subarch",
68 "VFPv3+ with common VFP subarch v3",
69 };
70
71 if (impl != 'A' || sa >= nelem(armarchs))
72 return "GOK";
73 else
74 return armarchs[sa];
75 }
76
77 static char *
implement(uchar impl)78 implement(uchar impl)
79 {
80 if (impl == 'A')
81 return "arm";
82 else
83 return "unknown";
84 }
85
86 static int
havefp(void)87 havefp(void)
88 {
89 int gotfp;
90 ulong acc, sid;
91
92 if (m->havefpvalid)
93 return m->havefp;
94
95 m->havefp = 0;
96 gotfp = 1 << CpFP | 1 << CpDFP;
97 cpwrsc(0, CpCONTROL, 0, CpCPaccess, MASK(28));
98 acc = cprdsc(0, CpCONTROL, 0, CpCPaccess);
99 if ((acc & (MASK(2) << (2*CpFP))) == 0) {
100 gotfp &= ~(1 << CpFP);
101 print("fpon: no single FP coprocessor\n");
102 }
103 if ((acc & (MASK(2) << (2*CpDFP))) == 0) {
104 gotfp &= ~(1 << CpDFP);
105 print("fpon: no double FP coprocessor\n");
106 }
107 if (!gotfp) {
108 print("fpon: no FP coprocessors\n");
109 m->havefpvalid = 1;
110 return 0;
111 }
112 m->fpon = 1; /* don't panic */
113 sid = fprd(Fpsid);
114 m->fpon = 0;
115 switch((sid >> 16) & MASK(7)){
116 case 0: /* VFPv1 */
117 break;
118 case 1: /* VFPv2 */
119 m->havefp = VFPv2;
120 m->fpnregs = 16;
121 break;
122 default: /* VFPv3 or later */
123 m->havefp = VFPv3;
124 m->fpnregs = (acc & Cpaccd16) ? 16 : 32;
125 break;
126 }
127 if (m->machno == 0)
128 print("fp: %d registers, %s simd\n", m->fpnregs,
129 (acc & Cpaccnosimd? " no": ""));
130 m->havefpvalid = 1;
131 return 1;
132 }
133
134 /*
135 * these can be called to turn the fpu on or off for user procs,
136 * not just at system start up or shutdown.
137 */
138
139 void
fpoff(void)140 fpoff(void)
141 {
142 if (m->fpon) {
143 fpwr(Fpexc, 0);
144 m->fpon = 0;
145 }
146 }
147
148 void
fpononly(void)149 fpononly(void)
150 {
151 if (!m->fpon && havefp()) {
152 /* enable fp. must be first operation on the FPUs. */
153 fpwr(Fpexc, Fpenabled);
154 m->fpon = 1;
155 }
156 }
157
158 static void
fpcfg(void)159 fpcfg(void)
160 {
161 int impl;
162 ulong sid;
163 static int printed;
164
165 /* clear pending exceptions; no traps in vfp3; all v7 ops are scalar */
166 m->fpscr = Dn | FPRNR | (FPINVAL | FPZDIV | FPOVFL) & ~Alltraps;
167 /* VFPv2 needs software support for underflows, so force them to zero */
168 if(m->havefp == VFPv2)
169 m->fpscr |= Fz;
170 fpwr(Fpscr, m->fpscr);
171 m->fpconfiged = 1;
172
173 if (printed)
174 return;
175 sid = fprd(Fpsid);
176 impl = sid >> 24;
177 print("fp: %s arch %s; rev %ld\n", implement(impl),
178 subarch(impl, (sid >> 16) & MASK(7)), sid & MASK(4));
179 printed = 1;
180 }
181
182 void
fpinit(void)183 fpinit(void)
184 {
185 if (havefp()) {
186 fpononly();
187 fpcfg();
188 }
189 }
190
191 void
fpon(void)192 fpon(void)
193 {
194 if (havefp()) {
195 fpononly();
196 if (m->fpconfiged)
197 fpwr(Fpscr, (fprd(Fpscr) & Allcc) | m->fpscr);
198 else
199 fpcfg(); /* 1st time on this fpu; configure it */
200 }
201 }
202
203 void
fpclear(void)204 fpclear(void)
205 {
206 // ulong scr;
207
208 fpon();
209 // scr = fprd(Fpscr);
210 // m->fpscr = scr & ~Allexc;
211 // fpwr(Fpscr, m->fpscr);
212
213 fpwr(Fpexc, fprd(Fpexc) & ~Fpmbc);
214 }
215
216
217 /*
218 * Called when a note is about to be delivered to a
219 * user process, usually at the end of a system call.
220 * Note handlers are not allowed to use the FPU so
221 * the state is marked (after saving if necessary) and
222 * checked in the Device Not Available handler.
223 */
224 void
fpunotify(Ureg *)225 fpunotify(Ureg*)
226 {
227 if(up->fpstate == FPactive){
228 fpsave(&up->fpsave);
229 up->fpstate = FPinactive;
230 }
231 up->fpstate |= FPillegal;
232 }
233
234 /*
235 * Called from sysnoted() via the machine-dependent
236 * noted() routine.
237 * Clear the flag set above in fpunotify().
238 */
239 void
fpunoted(void)240 fpunoted(void)
241 {
242 up->fpstate &= ~FPillegal;
243 }
244
245 /*
246 * Called early in the non-interruptible path of
247 * sysrfork() via the machine-dependent syscall() routine.
248 * Save the state so that it can be easily copied
249 * to the child process later.
250 */
251 void
fpusysrfork(Ureg *)252 fpusysrfork(Ureg*)
253 {
254 if(up->fpstate == FPactive){
255 fpsave(&up->fpsave);
256 up->fpstate = FPinactive;
257 }
258 }
259
260 /*
261 * Called later in sysrfork() via the machine-dependent
262 * sysrforkchild() routine.
263 * Copy the parent FPU state to the child.
264 */
265 void
fpusysrforkchild(Proc * p,Ureg *,Proc * up)266 fpusysrforkchild(Proc *p, Ureg *, Proc *up)
267 {
268 /* don't penalize the child, it hasn't done FP in a note handler. */
269 p->fpstate = up->fpstate & ~FPillegal;
270 }
271
272 /* should only be called if p->fpstate == FPactive */
273 void
fpsave(FPsave * fps)274 fpsave(FPsave *fps)
275 {
276 int n;
277
278 fpon();
279 fps->control = fps->status = fprd(Fpscr);
280 assert(m->fpnregs);
281 for (n = 0; n < m->fpnregs; n++)
282 fpsavereg(n, (uvlong *)fps->regs[n]);
283 fpoff();
284 }
285
286 static void
fprestore(Proc * p)287 fprestore(Proc *p)
288 {
289 int n;
290
291 fpon();
292 fpwr(Fpscr, p->fpsave.control);
293 m->fpscr = fprd(Fpscr) & ~Allcc;
294 assert(m->fpnregs);
295 for (n = 0; n < m->fpnregs; n++)
296 fprestreg(n, *(uvlong *)p->fpsave.regs[n]);
297 }
298
299 /*
300 * Called from sched() and sleep() via the machine-dependent
301 * procsave() routine.
302 * About to go in to the scheduler.
303 * If the process wasn't using the FPU
304 * there's nothing to do.
305 */
306 void
fpuprocsave(Proc * p)307 fpuprocsave(Proc *p)
308 {
309 if(p->fpstate == FPactive){
310 if(p->state == Moribund)
311 fpoff();
312 else{
313 /*
314 * Fpsave() stores without handling pending
315 * unmasked exeptions. Postnote() can't be called
316 * here as sleep() already has up->rlock, so
317 * the handling of pending exceptions is delayed
318 * until the process runs again and generates an
319 * emulation fault to activate the FPU.
320 */
321 fpsave(&p->fpsave);
322 }
323 p->fpstate = FPinactive;
324 }
325 }
326
327 /*
328 * The process has been rescheduled and is about to run.
329 * Nothing to do here right now. If the process tries to use
330 * the FPU again it will cause a Device Not Available
331 * exception and the state will then be restored.
332 */
333 void
fpuprocrestore(Proc *)334 fpuprocrestore(Proc *)
335 {
336 }
337
338 /*
339 * Disable the FPU.
340 * Called from sysexec() via sysprocsetup() to
341 * set the FPU for the new process.
342 */
343 void
fpusysprocsetup(Proc * p)344 fpusysprocsetup(Proc *p)
345 {
346 p->fpstate = FPinit;
347 fpoff();
348 }
349
350 static void
mathnote(void)351 mathnote(void)
352 {
353 ulong status;
354 char *msg, note[ERRMAX];
355
356 status = up->fpsave.status;
357
358 /*
359 * Some attention should probably be paid here to the
360 * exception masks and error summary.
361 */
362 if (status & FPAINEX)
363 msg = "inexact";
364 else if (status & FPAOVFL)
365 msg = "overflow";
366 else if (status & FPAUNFL)
367 msg = "underflow";
368 else if (status & FPAZDIV)
369 msg = "divide by zero";
370 else if (status & FPAINVAL)
371 msg = "bad operation";
372 else
373 msg = "spurious";
374 snprint(note, sizeof note, "sys: fp: %s fppc=%#p status=%#lux",
375 msg, up->fpsave.pc, status);
376 postnote(up, 1, note, NDebug);
377 }
378
379 static void
mathemu(Ureg *)380 mathemu(Ureg *)
381 {
382 switch(up->fpstate){
383 case FPemu:
384 error("illegal instruction: VFP opcode in emulated mode");
385 case FPinit:
386 fpinit();
387 up->fpstate = FPactive;
388 break;
389 case FPinactive:
390 /*
391 * Before restoring the state, check for any pending
392 * exceptions. There's no way to restore the state without
393 * generating an unmasked exception.
394 * More attention should probably be paid here to the
395 * exception masks and error summary.
396 */
397 if(up->fpsave.status & (FPAINEX|FPAUNFL|FPAOVFL|FPAZDIV|FPAINVAL)){
398 mathnote();
399 break;
400 }
401 fprestore(up);
402 up->fpstate = FPactive;
403 break;
404 case FPactive:
405 error("sys: illegal instruction: bad vfp fpu opcode");
406 break;
407 }
408 fpclear();
409 }
410
411 void
fpstuck(uintptr pc)412 fpstuck(uintptr pc)
413 {
414 if (m->fppc == pc && m->fppid == up->pid) {
415 m->fpcnt++;
416 if (m->fpcnt > 4)
417 panic("fpuemu: cpu%d stuck at pid %ld %s pc %#p "
418 "instr %#8.8lux", m->machno, up->pid, up->text,
419 pc, *(ulong *)pc);
420 } else {
421 m->fppid = up->pid;
422 m->fppc = pc;
423 m->fpcnt = 0;
424 }
425 }
426
427 enum {
428 N = 1<<31,
429 Z = 1<<30,
430 C = 1<<29,
431 V = 1<<28,
432 REGPC = 15,
433 };
434
435 static int
condok(int cc,int c)436 condok(int cc, int c)
437 {
438 switch(c){
439 case 0: /* Z set */
440 return cc&Z;
441 case 1: /* Z clear */
442 return (cc&Z) == 0;
443 case 2: /* C set */
444 return cc&C;
445 case 3: /* C clear */
446 return (cc&C) == 0;
447 case 4: /* N set */
448 return cc&N;
449 case 5: /* N clear */
450 return (cc&N) == 0;
451 case 6: /* V set */
452 return cc&V;
453 case 7: /* V clear */
454 return (cc&V) == 0;
455 case 8: /* C set and Z clear */
456 return cc&C && (cc&Z) == 0;
457 case 9: /* C clear or Z set */
458 return (cc&C) == 0 || cc&Z;
459 case 10: /* N set and V set, or N clear and V clear */
460 return (~cc&(N|V))==0 || (cc&(N|V)) == 0;
461 case 11: /* N set and V clear, or N clear and V set */
462 return (cc&(N|V))==N || (cc&(N|V))==V;
463 case 12: /* Z clear, and either N set and V set or N clear and V clear */
464 return (cc&Z) == 0 && ((~cc&(N|V))==0 || (cc&(N|V))==0);
465 case 13: /* Z set, or N set and V clear or N clear and V set */
466 return (cc&Z) || (cc&(N|V))==N || (cc&(N|V))==V;
467 case 14: /* always */
468 return 1;
469 case 15: /* never (reserved) */
470 return 0;
471 }
472 return 0; /* not reached */
473 }
474
475 /* only called to deal with user-mode instruction faults */
476 int
fpuemu(Ureg * ureg)477 fpuemu(Ureg* ureg)
478 {
479 int s, nfp, cop, op;
480 uintptr pc;
481 static int already;
482
483 if(waserror()){
484 postnote(up, 1, up->errstr, NDebug);
485 return 1;
486 }
487
488 if(up->fpstate & FPillegal)
489 error("floating point in note handler");
490
491 nfp = 0;
492 pc = ureg->pc;
493 validaddr(pc, 4, 0);
494 op = (*(ulong *)pc >> 24) & MASK(4);
495 cop = (*(ulong *)pc >> 8) & MASK(4);
496 if(m->fpon)
497 fpstuck(pc); /* debugging; could move down 1 line */
498 if (ISFPAOP(cop, op)) { /* old arm 7500 fpa opcode? */
499 s = spllo();
500 if(!already++)
501 pprint("warning: emulated arm7500 fpa instr %#8.8lux at %#p\n", *(ulong *)pc, pc);
502 if(waserror()){
503 splx(s);
504 nexterror();
505 }
506 nfp = fpiarm(ureg); /* advances pc past emulated instr(s) */
507 if (nfp > 1) /* could adjust this threshold */
508 m->fppc = m->fpcnt = 0;
509 splx(s);
510 poperror();
511 } else if (ISVFPOP(cop, op)) { /* if vfp, fpu off or unsupported instruction */
512 mathemu(ureg); /* enable fpu & retry */
513 nfp = 1;
514 }
515
516 poperror();
517 return nfp;
518 }
519