xref: /plan9/sys/src/9/teg2/vfp3.c (revision 6397270f2f1353bc245f3005811da89a06033226)
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 | Fz | FPRNR | (FPINVAL | FPZDIV | FPOVFL) & ~Alltraps;
167 	fpwr(Fpscr, m->fpscr);
168 	m->fpconfiged = 1;
169 
170 	if (printed)
171 		return;
172 	sid = fprd(Fpsid);
173 	impl = sid >> 24;
174 	print("fp: %s arch %s; rev %ld\n", implement(impl),
175 		subarch(impl, (sid >> 16) & MASK(7)), sid & MASK(4));
176 	printed = 1;
177 }
178 
179 void
fpinit(void)180 fpinit(void)
181 {
182 	if (havefp()) {
183 		fpononly();
184 		fpcfg();
185 	}
186 }
187 
188 void
fpon(void)189 fpon(void)
190 {
191 	if (havefp()) {
192 	 	fpononly();
193 		if (m->fpconfiged)
194 			fpwr(Fpscr, (fprd(Fpscr) & Allcc) | m->fpscr);
195 		else
196 			fpcfg();	/* 1st time on this fpu; configure it */
197 	}
198 }
199 
200 void
fpclear(void)201 fpclear(void)
202 {
203 //	ulong scr;
204 
205 	fpon();
206 //	scr = fprd(Fpscr);
207 //	m->fpscr = scr & ~Allexc;
208 //	fpwr(Fpscr, m->fpscr);
209 
210 	fpwr(Fpexc, fprd(Fpexc) & ~Fpmbc);
211 }
212 
213 
214 /*
215  * Called when a note is about to be delivered to a
216  * user process, usually at the end of a system call.
217  * Note handlers are not allowed to use the FPU so
218  * the state is marked (after saving if necessary) and
219  * checked in the Device Not Available handler.
220  */
221 void
fpunotify(Ureg *)222 fpunotify(Ureg*)
223 {
224 	if(up->fpstate == FPactive){
225 		fpsave(&up->fpsave);
226 		up->fpstate = FPinactive;
227 	}
228 	up->fpstate |= FPillegal;
229 }
230 
231 /*
232  * Called from sysnoted() via the machine-dependent
233  * noted() routine.
234  * Clear the flag set above in fpunotify().
235  */
236 void
fpunoted(void)237 fpunoted(void)
238 {
239 	up->fpstate &= ~FPillegal;
240 }
241 
242 /*
243  * Called early in the non-interruptible path of
244  * sysrfork() via the machine-dependent syscall() routine.
245  * Save the state so that it can be easily copied
246  * to the child process later.
247  */
248 void
fpusysrfork(Ureg *)249 fpusysrfork(Ureg*)
250 {
251 	if(up->fpstate == FPactive){
252 		fpsave(&up->fpsave);
253 		up->fpstate = FPinactive;
254 	}
255 }
256 
257 /*
258  * Called later in sysrfork() via the machine-dependent
259  * sysrforkchild() routine.
260  * Copy the parent FPU state to the child.
261  */
262 void
fpusysrforkchild(Proc * p,Ureg *,Proc * up)263 fpusysrforkchild(Proc *p, Ureg *, Proc *up)
264 {
265 	/* don't penalize the child, it hasn't done FP in a note handler. */
266 	p->fpstate = up->fpstate & ~FPillegal;
267 }
268 
269 /* should only be called if p->fpstate == FPactive */
270 void
fpsave(FPsave * fps)271 fpsave(FPsave *fps)
272 {
273 	int n;
274 
275 	fpon();
276 	fps->control = fps->status = fprd(Fpscr);
277 	assert(m->fpnregs);
278 	for (n = 0; n < m->fpnregs; n++)
279 		fpsavereg(n, (uvlong *)fps->regs[n]);
280 	fpoff();
281 }
282 
283 static void
fprestore(Proc * p)284 fprestore(Proc *p)
285 {
286 	int n;
287 
288 	fpon();
289 	fpwr(Fpscr, p->fpsave.control);
290 	m->fpscr = fprd(Fpscr) & ~Allcc;
291 	assert(m->fpnregs);
292 	for (n = 0; n < m->fpnregs; n++)
293 		fprestreg(n, *(uvlong *)p->fpsave.regs[n]);
294 }
295 
296 /*
297  * Called from sched() and sleep() via the machine-dependent
298  * procsave() routine.
299  * About to go in to the scheduler.
300  * If the process wasn't using the FPU
301  * there's nothing to do.
302  */
303 void
fpuprocsave(Proc * p)304 fpuprocsave(Proc *p)
305 {
306 	if(p->fpstate == FPactive){
307 		if(p->state == Moribund)
308 			fpclear();
309 		else{
310 			/*
311 			 * Fpsave() stores without handling pending
312 			 * unmasked exeptions. Postnote() can't be called
313 			 * here as sleep() already has up->rlock, so
314 			 * the handling of pending exceptions is delayed
315 			 * until the process runs again and generates an
316 			 * emulation fault to activate the FPU.
317 			 */
318 			fpsave(&p->fpsave);
319 		}
320 		p->fpstate = FPinactive;
321 	}
322 }
323 
324 /*
325  * The process has been rescheduled and is about to run.
326  * Nothing to do here right now. If the process tries to use
327  * the FPU again it will cause a Device Not Available
328  * exception and the state will then be restored.
329  */
330 void
fpuprocrestore(Proc *)331 fpuprocrestore(Proc *)
332 {
333 }
334 
335 /*
336  * Disable the FPU.
337  * Called from sysexec() via sysprocsetup() to
338  * set the FPU for the new process.
339  */
340 void
fpusysprocsetup(Proc * p)341 fpusysprocsetup(Proc *p)
342 {
343 	p->fpstate = FPinit;
344 	fpoff();
345 }
346 
347 static void
mathnote(void)348 mathnote(void)
349 {
350 	ulong status;
351 	char *msg, note[ERRMAX];
352 
353 	status = up->fpsave.status;
354 
355 	/*
356 	 * Some attention should probably be paid here to the
357 	 * exception masks and error summary.
358 	 */
359 	if (status & FPAINEX)
360 		msg = "inexact";
361 	else if (status & FPAOVFL)
362 		msg = "overflow";
363 	else if (status & FPAUNFL)
364 		msg = "underflow";
365 	else if (status & FPAZDIV)
366 		msg = "divide by zero";
367 	else if (status & FPAINVAL)
368 		msg = "bad operation";
369 	else
370 		msg = "spurious";
371 	snprint(note, sizeof note, "sys: fp: %s fppc=%#p status=%#lux",
372 		msg, up->fpsave.pc, status);
373 	postnote(up, 1, note, NDebug);
374 }
375 
376 static void
mathemu(Ureg *)377 mathemu(Ureg *)
378 {
379 	switch(up->fpstate){
380 	case FPemu:
381 		error("illegal instruction: VFP opcode in emulated mode");
382 	case FPinit:
383 		fpinit();
384 		up->fpstate = FPactive;
385 		break;
386 	case FPinactive:
387 		/*
388 		 * Before restoring the state, check for any pending
389 		 * exceptions.  There's no way to restore the state without
390 		 * generating an unmasked exception.
391 		 * More attention should probably be paid here to the
392 		 * exception masks and error summary.
393 		 */
394 		if(up->fpsave.status & (FPAINEX|FPAUNFL|FPAOVFL|FPAZDIV|FPAINVAL)){
395 			mathnote();
396 			break;
397 		}
398 		fprestore(up);
399 		up->fpstate = FPactive;
400 		break;
401 	case FPactive:
402 		error("illegal instruction: bad vfp fpu opcode");
403 		break;
404 	}
405 	fpclear();
406 }
407 
408 void
fpstuck(uintptr pc)409 fpstuck(uintptr pc)
410 {
411 	if (m->fppc == pc && m->fppid == up->pid) {
412 		m->fpcnt++;
413 		if (m->fpcnt > 4)
414 			panic("fpuemu: cpu%d stuck at pid %ld %s pc %#p "
415 				"instr %#8.8lux", m->machno, up->pid, up->text,
416 				pc, *(ulong *)pc);
417 	} else {
418 		m->fppid = up->pid;
419 		m->fppc = pc;
420 		m->fpcnt = 0;
421 	}
422 }
423 
424 enum {
425 	N = 1<<31,
426 	Z = 1<<30,
427 	C = 1<<29,
428 	V = 1<<28,
429 	REGPC = 15,
430 };
431 
432 static int
condok(int cc,int c)433 condok(int cc, int c)
434 {
435 	switch(c){
436 	case 0:	/* Z set */
437 		return cc&Z;
438 	case 1:	/* Z clear */
439 		return (cc&Z) == 0;
440 	case 2:	/* C set */
441 		return cc&C;
442 	case 3:	/* C clear */
443 		return (cc&C) == 0;
444 	case 4:	/* N set */
445 		return cc&N;
446 	case 5:	/* N clear */
447 		return (cc&N) == 0;
448 	case 6:	/* V set */
449 		return cc&V;
450 	case 7:	/* V clear */
451 		return (cc&V) == 0;
452 	case 8:	/* C set and Z clear */
453 		return cc&C && (cc&Z) == 0;
454 	case 9:	/* C clear or Z set */
455 		return (cc&C) == 0 || cc&Z;
456 	case 10:	/* N set and V set, or N clear and V clear */
457 		return (~cc&(N|V))==0 || (cc&(N|V)) == 0;
458 	case 11:	/* N set and V clear, or N clear and V set */
459 		return (cc&(N|V))==N || (cc&(N|V))==V;
460 	case 12:	/* Z clear, and either N set and V set or N clear and V clear */
461 		return (cc&Z) == 0 && ((~cc&(N|V))==0 || (cc&(N|V))==0);
462 	case 13:	/* Z set, or N set and V clear or N clear and V set */
463 		return (cc&Z) || (cc&(N|V))==N || (cc&(N|V))==V;
464 	case 14:	/* always */
465 		return 1;
466 	case 15:	/* never (reserved) */
467 		return 0;
468 	}
469 	return 0;	/* not reached */
470 }
471 
472 /* only called to deal with user-mode instruction faults */
473 int
fpuemu(Ureg * ureg)474 fpuemu(Ureg* ureg)
475 {
476 	int s, nfp, cop, op;
477 	uintptr pc;
478 
479 	if(waserror()){
480 		postnote(up, 1, up->errstr, NDebug);
481 		return 1;
482 	}
483 
484 	if(up->fpstate & FPillegal)
485 		error("floating point in note handler");
486 
487 	nfp = 0;
488 	pc = ureg->pc;
489 	validaddr(pc, 4, 0);
490 	if(!condok(ureg->psr, *(ulong*)pc >> 28))
491 		iprint("fpuemu: conditional instr shouldn't have got here\n");
492 	op  = (*(ulong *)pc >> 24) & MASK(4);
493 	cop = (*(ulong *)pc >>  8) & MASK(4);
494 	if(m->fpon)
495 		fpstuck(pc);		/* debugging; could move down 1 line */
496 	if (ISFPAOP(cop, op)) {		/* old arm 7500 fpa opcode? */
497 //		iprint("fpuemu: fpa instr %#8.8lux at %#p\n", *(ulong *)pc, pc);
498 //		error("illegal instruction: old arm 7500 fpa opcode");
499 		s = spllo();
500 		if(waserror()){
501 			splx(s);
502 			nexterror();
503 		}
504 		nfp = fpiarm(ureg);	/* advances pc past emulated instr(s) */
505 		if (nfp > 1)		/* could adjust this threshold */
506 			m->fppc = m->fpcnt = 0;
507 		splx(s);
508 		poperror();
509 	} else if (ISVFPOP(cop, op)) {	/* if vfp, fpu must be off */
510 		mathemu(ureg);		/* enable fpu & retry */
511 		nfp = 1;
512 	}
513 
514 	poperror();
515 	return nfp;
516 }
517