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