xref: /openbsd-src/sys/arch/m88k/m88k/m88100_fp.c (revision 37d02ff3fae3fe9be2e3ed207dfb940914780271)
1 /*	$OpenBSD: m88100_fp.c,v 1.7 2024/03/03 11:14:34 miod Exp $	*/
2 
3 /*
4  * Copyright (c) 2007, 2014, Miodrag Vallat.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice, this permission notice, and the disclaimer below
9  * appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/param.h>
21 #include <sys/proc.h>
22 #include <sys/signalvar.h>
23 #include <sys/systm.h>
24 
25 #include <machine/fpu.h>
26 #include <machine/frame.h>
27 #include <machine/ieee.h>
28 #include <machine/ieeefp.h>
29 #include <machine/trap.h>
30 #include <machine/m88100.h>
31 
32 #include <lib/libkern/softfloat.h>
33 #include <lib/libkern/milieu.h>
34 float32 normalizeRoundAndPackFloat32(int, int16, bits32);
35 float64 normalizeRoundAndPackFloat64(flag, int16, bits64);
36 
37 #include <m88k/m88k/fpu.h>
38 
39 int	m88100_fpu_emulate(struct trapframe *);
40 void	m88100_fpu_fetch(struct trapframe *, u_int, u_int, u_int, fparg *);
41 void	m88100_fpu_checksig(struct trapframe *, int, int);
42 
43 /*
44  * All 88100 precise floating-point exceptions are handled there.
45  *
46  * We ignore the exception cause register completely, except for the
47  * `privilege violation' bit, and attempt to perform the computation in
48  * software if needed.
49  */
50 
51 void
m88100_fpu_precise_exception(struct trapframe * frame)52 m88100_fpu_precise_exception(struct trapframe *frame)
53 {
54 	int fault_type;
55 	int sig;
56 
57 	/* if FPECR_FUNIMP is set, all other bits are undefined, ignore them */
58 	if (ISSET(frame->tf_fpecr, FPECR_FUNIMP))
59 		frame->tf_fpecr = FPECR_FUNIMP;
60 
61 	/* Reset the exception cause register */
62 	__asm__ volatile ("fstcr %r0, %fcr0");
63 
64 	if (ISSET(frame->tf_fpecr, FPECR_FPRV)) {
65 		sig = SIGILL;
66 		fault_type = ILL_PRVREG;
67 	} else {
68 		sig = m88100_fpu_emulate(frame);
69 		fault_type = SI_NOINFO;
70 	}
71 
72 	/*
73 	 * Update the floating point status register regardless of
74 	 * whether we'll deliver a signal or not.
75 	 */
76 	__asm__ volatile ("fstcr %0, %%fcr62" :: "r"(frame->tf_fpsr));
77 
78 	m88100_fpu_checksig(frame, sig, fault_type);
79 }
80 
81 /*
82  * Convert a single floating-point argument with its exponent sign-extended
83  * to 11 bits in an fphs/fpls pair to a correct 32-bit single precision
84  * number.
85  */
86 static inline uint32_t
m88100_fpu_parse_single(uint32_t hs,uint32_t ls)87 m88100_fpu_parse_single(uint32_t hs, uint32_t ls)
88 {
89 	uint32_t result;
90 
91 	result = hs << (DBL_EXPBITS - SNG_EXPBITS);
92 	result &= ~(1U << 31);		/* clear carry into sign bit */
93 	result |= ls >> (DBL_FRACBITS - SNG_FRACBITS);
94 	result |= hs & (1U << 31);	/* sign bit */
95 
96 	return result;
97 }
98 
99 /*
100  * Load a floating-point argument into a fparg union, then convert it to
101  * the required format if it is of larger precision.
102  *
103  * This assumes the final format (width) is not FTYPE_INT, and the original
104  * format (orig_width) <= width.
105  */
106 void
m88100_fpu_fetch(struct trapframe * frame,u_int operandno,u_int orig_width,u_int width,fparg * dest)107 m88100_fpu_fetch(struct trapframe *frame, u_int operandno, u_int orig_width,
108     u_int width, fparg *dest)
109 {
110 	u_int32_t tmp;
111 
112 	switch (orig_width) {
113 	case FTYPE_INT:
114 		tmp = operandno == 1 ? frame->tf_fpls1 : frame->tf_fpls2;
115 		switch (width) {
116 		case FTYPE_SNG:
117 			dest->sng = int32_to_float32(tmp);
118 			break;
119 		case FTYPE_DBL:
120 			dest->dbl = int32_to_float64(tmp);
121 			break;
122 		}
123 		break;
124 	case FTYPE_SNG:
125 		tmp = operandno == 1 ?
126 		    m88100_fpu_parse_single(frame->tf_fphs1, frame->tf_fpls1) :
127 		    m88100_fpu_parse_single(frame->tf_fphs2, frame->tf_fpls2);
128 		switch (width) {
129 		case FTYPE_SNG:
130 			dest->sng = tmp;
131 			break;
132 		case FTYPE_DBL:
133 			dest->dbl = float32_to_float64(tmp);
134 			break;
135 		}
136 		break;
137 	case FTYPE_DBL:
138 		tmp = operandno == 1 ? frame->tf_fphs1 : frame->tf_fphs2;
139 		dest->dbl = ((float64)tmp) << 32;
140 		tmp = operandno == 1 ? frame->tf_fpls1 : frame->tf_fpls2;
141 		dest->dbl |= (float64)tmp;
142 		break;
143 	}
144 }
145 
146 /*
147  * Emulate an FPU instruction.  On return, the trapframe registers
148  * will be modified to reflect the settings the hardware would have left.
149  */
150 int
m88100_fpu_emulate(struct trapframe * frame)151 m88100_fpu_emulate(struct trapframe *frame)
152 {
153 	u_int rd, t1, t2, td, tmax, opcode;
154 	u_int32_t old_fpsr, old_fpcr;
155 	int rc;
156 
157 	fparg arg1, arg2, dest;
158 
159 	/*
160 	 * Crack the instruction.
161 	 */
162 	rd = frame->tf_fppt & 0x1f;
163 	opcode = (frame->tf_fppt >> 11) & 0x1f;
164 	t1 = (frame->tf_fppt >> 9) & 0x03;
165 	t2 = (frame->tf_fppt >> 7) & 0x03;
166 	td = (frame->tf_fppt >> 5) & 0x03;
167 
168 	if (rd == 0)	/* r0 not allowed as destination */
169 		return (SIGILL);
170 
171 	switch (opcode) {
172 	case 0x00:	/* fmul */
173 	case 0x05:	/* fadd */
174 	case 0x06:	/* fsub */
175 	case 0x0e:	/* fdiv */
176 		if ((t1 != FTYPE_SNG && t1 != FTYPE_DBL) ||
177 		    (t2 != FTYPE_SNG && t2 != FTYPE_DBL) ||
178 		    (td != FTYPE_SNG && td != FTYPE_DBL))
179 			return (SIGILL);
180 		break;
181 	case 0x04:	/* flt */
182 		if ((td != FTYPE_SNG && td != FTYPE_DBL) ||
183 		    t2 != 0x00 || t1 != 0x00)
184 			return (SIGILL);
185 		break;
186 	case 0x07:	/* fcmp */
187 		if ((t1 != FTYPE_SNG && t1 != FTYPE_DBL) ||
188 		    (t2 != FTYPE_SNG && t2 != FTYPE_DBL) ||
189 		    td != 0x00)
190 			return (SIGILL);
191 		break;
192 	case 0x09:	/* int */
193 	case 0x0a:	/* nint */
194 	case 0x0b:	/* trnc */
195 		if ((t2 != FTYPE_SNG && t2 != FTYPE_DBL) ||
196 		    t1 != 0x00 || td != 0x00)
197 			return (SIGILL);
198 		break;
199 	default:
200 		return (SIGILL);
201 	}
202 
203 	/*
204 	 * Temporarily reset the status register, so that we can tell
205 	 * which exceptions are new after processing the opcode.
206 	 */
207 	old_fpsr = frame->tf_fpsr;
208 	frame->tf_fpsr = 0;
209 
210 	/*
211 	 * Save fpcr as well, since we might need to change rounding mode
212 	 * temporarily.
213 	 */
214 	old_fpcr = frame->tf_fpcr;
215 
216 	/*
217 	 * The logic for instruction emulation is:
218 	 *
219 	 * - the computation precision is the largest one of all the operands.
220 	 * - all source operands are converted to this precision if needed.
221 	 * - computation is performed.
222 	 * - the result is stored into the destination operand, converting it
223 	 *   to the destination precision if lower.
224 	 */
225 
226 	switch (opcode) {
227 	case 0x00:	/* fmul */
228 		tmax = fpu_precision(t1, t2, td);
229 		m88100_fpu_fetch(frame, 1, t1, tmax, &arg1);
230 		m88100_fpu_fetch(frame, 2, t2, tmax, &arg2);
231 		switch (tmax) {
232 		case FTYPE_SNG:
233 			dest.sng = float32_mul(arg1.sng, arg2.sng);
234 			break;
235 		case FTYPE_DBL:
236 			dest.dbl = float64_mul(arg1.dbl, arg2.dbl);
237 			break;
238 		}
239 		fpu_store(frame, rd, tmax, td, &dest);
240 		break;
241 
242 	case 0x04:	/* flt */
243 		m88100_fpu_fetch(frame, 2, FTYPE_INT, td, &dest);
244 		fpu_store(frame, rd, td, td, &dest);
245 		break;
246 
247 	case 0x05:	/* fadd */
248 		tmax = fpu_precision(t1, t2, td);
249 		m88100_fpu_fetch(frame, 1, t1, tmax, &arg1);
250 		m88100_fpu_fetch(frame, 2, t2, tmax, &arg2);
251 		switch (tmax) {
252 		case FTYPE_SNG:
253 			dest.sng = float32_add(arg1.sng, arg2.sng);
254 			break;
255 		case FTYPE_DBL:
256 			dest.dbl = float64_add(arg1.dbl, arg2.dbl);
257 			break;
258 		}
259 		fpu_store(frame, rd, tmax, td, &dest);
260 		break;
261 
262 	case 0x06:	/* fsub */
263 		tmax = fpu_precision(t1, t2, td);
264 		m88100_fpu_fetch(frame, 1, t1, tmax, &arg1);
265 		m88100_fpu_fetch(frame, 2, t2, tmax, &arg2);
266 		switch (tmax) {
267 		case FTYPE_SNG:
268 			dest.sng = float32_sub(arg1.sng, arg2.sng);
269 			break;
270 		case FTYPE_DBL:
271 			dest.dbl = float64_sub(arg1.dbl, arg2.dbl);
272 			break;
273 		}
274 		fpu_store(frame, rd, tmax, td, &dest);
275 		break;
276 
277 	case 0x07:	/* fcmp */
278 		tmax = fpu_precision(t1, t2, IGNORE_PRECISION);
279 		m88100_fpu_fetch(frame, 1, t1, tmax, &arg1);
280 		m88100_fpu_fetch(frame, 2, t2, tmax, &arg2);
281 		fpu_compare(frame, &arg1, &arg2, tmax, rd, 0);
282 		break;
283 
284 	case 0x09:	/* int */
285 do_int:
286 		m88100_fpu_fetch(frame, 2, t2, t2, &dest);
287 		fpu_store(frame, rd, t2, FTYPE_INT, &dest);
288 		break;
289 
290 	case 0x0a:	/* nint */
291 		/* round to nearest */
292 		frame->tf_fpcr = (old_fpcr & ~(FPCR_RD_MASK << FPCR_RD_SHIFT)) |
293 		    (FP_RN << FPCR_RD_SHIFT);
294 		goto do_int;
295 
296 	case 0x0b:	/* trnc */
297 		/* round towards zero */
298 		frame->tf_fpcr = (old_fpcr & ~(FPCR_RD_MASK << FPCR_RD_SHIFT)) |
299 		    (FP_RZ << FPCR_RD_SHIFT);
300 		goto do_int;
301 
302 	case 0x0e:	/* fdiv */
303 		tmax = fpu_precision(t1, t2, td);
304 		m88100_fpu_fetch(frame, 1, t1, tmax, &arg1);
305 		m88100_fpu_fetch(frame, 2, t2, tmax, &arg2);
306 		switch (tmax) {
307 		case FTYPE_SNG:
308 			dest.sng = float32_div(arg1.sng, arg2.sng);
309 			break;
310 		case FTYPE_DBL:
311 			dest.dbl = float64_div(arg1.dbl, arg2.dbl);
312 			break;
313 		}
314 		fpu_store(frame, rd, tmax, td, &dest);
315 		break;
316 	}
317 
318 	/*
319 	 * Mark new exceptions, if any, in the fpsr, and decide whether
320 	 * to send a signal or not.
321 	 */
322 
323 	if (frame->tf_fpsr & old_fpcr)
324 		rc = SIGFPE;
325 	else
326 		rc = 0;
327 	frame->tf_fpsr |= old_fpsr;
328 
329 	/*
330 	 * Restore fpcr as well.
331 	 */
332 	frame->tf_fpcr = old_fpcr;
333 
334 	return (rc);
335 }
336 
337 /*
338  * All 88100 imprecise floating-point exceptions are handled there.
339  *
340  * We ignore the exception condition bits and simply round the intermediate
341  * result according to the current rounding mode, raising whichever exception
342  * conditions are necessary in the process.
343  */
344 
345 void
m88100_fpu_imprecise_exception(struct trapframe * frame)346 m88100_fpu_imprecise_exception(struct trapframe *frame)
347 {
348 	flag sign;
349 	int16 exp;
350 	bits32 mant32;
351 	bits64 mant64;
352 	fparg res;
353 	u_int fmt;
354 
355 	/* Reset the exception cause register */
356 	__asm__ volatile ("fstcr %r0, %fcr0");
357 
358 	/*
359 	 * The 88100 errata for mask C82N (rev 0x0a) documents that an
360 	 * imprecise exception may be raised for integer instructions
361 	 * returning an inexact result.
362 	 * However, there is nothing to do in this case, since the result
363 	 * is not a floating-point value, and has been correctly put in
364 	 * the destination register; we simply need to to ignore that
365 	 * exception.
366 	 */
367 	switch ((frame->tf_fpit >> 11) & 0x1f) {
368 	case 0x09:	/* int */
369 	case 0x0a:	/* nint */
370 	case 0x0b:	/* trnc */
371 		return;
372 	default:
373 		break;
374 	}
375 
376 	/*
377 	 * Pick the inexact result, build a float32 or a float64 out of it, and
378 	 * normalize it to the destination width.
379 	 */
380 
381 	if (frame->tf_fpit & FPIT_DBL)
382 		fmt = FTYPE_DBL;
383 	else
384 		fmt = FTYPE_SNG;
385 
386 	sign = (frame->tf_fprh & FPRH_SIGN) != 0;
387 	exp = ((int32_t)frame->tf_fpit) >> 20; /* signed, unbiaised exponent */
388 
389 	if (fmt == FTYPE_SNG) {
390 		exp += SNG_EXP_BIAS;
391 		mant32 = (frame->tf_fprh & FPRH_MANTH_MASK);
392 	       	mant32 <<= (SNG_FRACBITS - FPRH_MANTH_BITS + 1);
393 		mant32 |= frame->tf_fprl >> (DBL_FRACBITS - SNG_FRACBITS);
394 
395 		/*
396 		 * If the mantissa has been incremented, revert this; but
397 		 * if doing so causes the mantissa hidden bit to clear,
398 		 * the exponent needs to be decremented and the hidden bit
399 		 * restored.
400 		 */
401 		if (frame->tf_fprh & FPRH_ADDONE) {
402 			mant32--;
403 			if ((mant32 & (1 << SNG_FRACBITS)) == 0) {
404 				exp--;
405 				mant32 |= 1 << SNG_FRACBITS;
406 			}
407 		}
408 
409 		/* normalizeRoundAndPackFloat32() requirement */
410 		mant32 <<= (31 - SNG_FRACBITS - 1);
411 		res.sng = normalizeRoundAndPackFloat32(sign, exp, mant32);
412 	} else {
413 		exp += DBL_EXP_BIAS;
414 		mant64 = frame->tf_fprh & FPRH_MANTH_MASK;
415 		mant64 <<= (DBL_FRACBITS - FPRH_MANTH_BITS + 1);
416 		mant64 |= frame->tf_fprl;
417 
418 		/*
419 		 * If the mantissa has been incremented, revert this; but
420 		 * if doing so causes the mantissa hidden bit to clear,
421 		 * the exponent needs to be decremented and the hidden bit
422 		 * restored.
423 		 */
424 		if (frame->tf_fprh & FPRH_ADDONE) {
425 			mant64--;
426 			if ((mant64 & (1LL << DBL_FRACBITS)) == 0) {
427 				exp--;
428 				mant64 |= 1LL << DBL_FRACBITS;
429 			}
430 		}
431 
432 		/* normalizeRoundAndPackFloat64() requirement */
433 		mant64 <<= (63 - DBL_FRACBITS - 1);
434 		res.dbl = normalizeRoundAndPackFloat64(sign, exp, mant64);
435 	}
436 
437 	fpu_store(frame, frame->tf_fpit & 0x1f, fmt, fmt, &res);
438 
439 	/*
440 	 * Update the floating point status register regardless of
441 	 * whether we'll deliver a signal or not.
442 	 */
443 	__asm__ volatile ("fstcr %0, %%fcr62" :: "r"(frame->tf_fpsr));
444 
445 	/*
446 	 * Check for a SIGFPE condition.
447 	 *
448 	 * XXX If the exception was caught while in kernel mode, we can't
449 	 * XXX send a signal at this point... what to do?
450 	 */
451 	if ((frame->tf_fpsr & PSR_MODE) == 0) {
452 		if (frame->tf_fpsr & frame->tf_fpcr)
453 			m88100_fpu_checksig(frame, SIGFPE, 0 /* SI_NOINFO */);
454 	}
455 }
456 
457 /*
458  * Check if a signal needs to be delivered, and send it.
459  */
460 void
m88100_fpu_checksig(struct trapframe * frame,int sig,int fault_type)461 m88100_fpu_checksig(struct trapframe *frame, int sig, int fault_type)
462 {
463 	struct proc *p = curproc;
464 	union sigval sv;
465 
466 	if (sig != 0) {
467 		if (sig == SIGILL) {
468 			if (fault_type == SI_NOINFO)
469 				fault_type = ILL_ILLOPC;
470 		} else {
471 			if (frame->tf_fpecr & FPECR_FIOV)
472 				fault_type = FPE_FLTSUB;
473 			else if (frame->tf_fpecr & FPECR_FROP)
474 				fault_type = FPE_FLTINV;
475 			else if (frame->tf_fpecr & FPECR_FDVZ)
476 				fault_type = FPE_INTDIV;
477 			else if (frame->tf_fpecr & FPECR_FUNF) {
478 				if (frame->tf_fpsr & FPSR_EFUNF)
479 					fault_type = FPE_FLTUND;
480 				else if (frame->tf_fpsr & FPSR_EFINX)
481 					fault_type = FPE_FLTRES;
482 			} else if (frame->tf_fpecr & FPECR_FOVF) {
483 				if (frame->tf_fpsr & FPSR_EFOVF)
484 					fault_type = FPE_FLTOVF;
485 				else if (frame->tf_fpsr & FPSR_EFINX)
486 					fault_type = FPE_FLTRES;
487 			} else if (frame->tf_fpecr & FPECR_FINX)
488 				fault_type = FPE_FLTRES;
489 		}
490 
491 		sv.sival_ptr = (void *)(frame->tf_sxip & XIP_ADDR);
492 		trapsignal(p, sig, 0, fault_type, sv);
493 	}
494 }
495