blob: 799d4af5be66381da440d545d94e0d71ebf4bac5 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*---------------------------------------------------------------------------+
2 | reg_ld_str.c |
3 | |
4 | All of the functions which transfer data between user memory and FPU_REGs.|
5 | |
6 | Copyright (C) 1992,1993,1994,1996,1997 |
7 | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, Australia |
8 | E-mail billm@suburbia.net |
9 | |
10 | |
11 +---------------------------------------------------------------------------*/
12
13/*---------------------------------------------------------------------------+
14 | Note: |
15 | The file contains code which accesses user memory. |
16 | Emulator static data may change when user memory is accessed, due to |
17 | other processes using the emulator while swapping is in progress. |
18 +---------------------------------------------------------------------------*/
19
20#include "fpu_emu.h"
21
22#include <asm/uaccess.h>
23
24#include "fpu_system.h"
25#include "exception.h"
26#include "reg_constant.h"
27#include "control_w.h"
28#include "status_w.h"
29
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010030#define DOUBLE_Emax 1023 /* largest valid exponent */
Linus Torvalds1da177e2005-04-16 15:20:36 -070031#define DOUBLE_Ebias 1023
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010032#define DOUBLE_Emin (-1022) /* smallest valid exponent */
Linus Torvalds1da177e2005-04-16 15:20:36 -070033
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010034#define SINGLE_Emax 127 /* largest valid exponent */
Linus Torvalds1da177e2005-04-16 15:20:36 -070035#define SINGLE_Ebias 127
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010036#define SINGLE_Emin (-126) /* smallest valid exponent */
Linus Torvalds1da177e2005-04-16 15:20:36 -070037
Ingo Molnare8d591d2008-01-30 13:30:12 +010038static u_char normalize_no_excep(FPU_REG *r, int exp, int sign)
Linus Torvalds1da177e2005-04-16 15:20:36 -070039{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010040 u_char tag;
Linus Torvalds1da177e2005-04-16 15:20:36 -070041
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010042 setexponent16(r, exp);
Linus Torvalds1da177e2005-04-16 15:20:36 -070043
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010044 tag = FPU_normalize_nuo(r);
45 stdexp(r);
46 if (sign)
47 setnegative(r);
Linus Torvalds1da177e2005-04-16 15:20:36 -070048
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010049 return tag;
Linus Torvalds1da177e2005-04-16 15:20:36 -070050}
51
Ingo Molnare8d591d2008-01-30 13:30:12 +010052int FPU_tagof(FPU_REG *ptr)
Linus Torvalds1da177e2005-04-16 15:20:36 -070053{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010054 int exp;
Linus Torvalds1da177e2005-04-16 15:20:36 -070055
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010056 exp = exponent16(ptr) & 0x7fff;
57 if (exp == 0) {
58 if (!(ptr->sigh | ptr->sigl)) {
59 return TAG_Zero;
60 }
61 /* The number is a de-normal or pseudodenormal. */
62 return TAG_Special;
Linus Torvalds1da177e2005-04-16 15:20:36 -070063 }
Linus Torvalds1da177e2005-04-16 15:20:36 -070064
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010065 if (exp == 0x7fff) {
66 /* Is an Infinity, a NaN, or an unsupported data type. */
67 return TAG_Special;
68 }
Linus Torvalds1da177e2005-04-16 15:20:36 -070069
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010070 if (!(ptr->sigh & 0x80000000)) {
71 /* Unsupported data type. */
72 /* Valid numbers have the ms bit set to 1. */
73 /* Unnormal. */
74 return TAG_Special;
75 }
Linus Torvalds1da177e2005-04-16 15:20:36 -070076
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010077 return TAG_Valid;
Linus Torvalds1da177e2005-04-16 15:20:36 -070078}
79
Linus Torvalds1da177e2005-04-16 15:20:36 -070080/* Get a long double from user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +010081int FPU_load_extended(long double __user *s, int stnr)
Linus Torvalds1da177e2005-04-16 15:20:36 -070082{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010083 FPU_REG *sti_ptr = &st(stnr);
Linus Torvalds1da177e2005-04-16 15:20:36 -070084
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010085 RE_ENTRANT_CHECK_OFF;
86 FPU_access_ok(VERIFY_READ, s, 10);
87 __copy_from_user(sti_ptr, s, 10);
88 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -070089
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010090 return FPU_tagof(sti_ptr);
Linus Torvalds1da177e2005-04-16 15:20:36 -070091}
92
Linus Torvalds1da177e2005-04-16 15:20:36 -070093/* Get a double from user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +010094int FPU_load_double(double __user *dfloat, FPU_REG *loaded_data)
Linus Torvalds1da177e2005-04-16 15:20:36 -070095{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010096 int exp, tag, negative;
97 unsigned m64, l64;
Linus Torvalds1da177e2005-04-16 15:20:36 -070098
Ingo Molnar3d0d14f2008-01-30 13:30:11 +010099 RE_ENTRANT_CHECK_OFF;
100 FPU_access_ok(VERIFY_READ, dfloat, 8);
101 FPU_get_user(m64, 1 + (unsigned long __user *)dfloat);
102 FPU_get_user(l64, (unsigned long __user *)dfloat);
103 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700104
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100105 negative = (m64 & 0x80000000) ? SIGN_Negative : SIGN_Positive;
106 exp = ((m64 & 0x7ff00000) >> 20) - DOUBLE_Ebias + EXTENDED_Ebias;
107 m64 &= 0xfffff;
108 if (exp > DOUBLE_Emax + EXTENDED_Ebias) {
109 /* Infinity or NaN */
110 if ((m64 == 0) && (l64 == 0)) {
111 /* +- infinity */
112 loaded_data->sigh = 0x80000000;
113 loaded_data->sigl = 0x00000000;
114 exp = EXP_Infinity + EXTENDED_Ebias;
115 tag = TAG_Special;
116 } else {
117 /* Must be a signaling or quiet NaN */
118 exp = EXP_NaN + EXTENDED_Ebias;
119 loaded_data->sigh = (m64 << 11) | 0x80000000;
120 loaded_data->sigh |= l64 >> 21;
121 loaded_data->sigl = l64 << 11;
122 tag = TAG_Special; /* The calling function must look for NaNs */
123 }
124 } else if (exp < DOUBLE_Emin + EXTENDED_Ebias) {
125 /* Zero or de-normal */
126 if ((m64 == 0) && (l64 == 0)) {
127 /* Zero */
128 reg_copy(&CONST_Z, loaded_data);
129 exp = 0;
130 tag = TAG_Zero;
131 } else {
132 /* De-normal */
133 loaded_data->sigh = m64 << 11;
134 loaded_data->sigh |= l64 >> 21;
135 loaded_data->sigl = l64 << 11;
136
137 return normalize_no_excep(loaded_data, DOUBLE_Emin,
138 negative)
139 | (denormal_operand() < 0 ? FPU_Exception : 0);
140 }
141 } else {
142 loaded_data->sigh = (m64 << 11) | 0x80000000;
143 loaded_data->sigh |= l64 >> 21;
144 loaded_data->sigl = l64 << 11;
145
146 tag = TAG_Valid;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700147 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700148
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100149 setexponent16(loaded_data, exp | negative);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700150
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100151 return tag;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700152}
153
Linus Torvalds1da177e2005-04-16 15:20:36 -0700154/* Get a float from user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100155int FPU_load_single(float __user *single, FPU_REG *loaded_data)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700156{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100157 unsigned m32;
158 int exp, tag, negative;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700159
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100160 RE_ENTRANT_CHECK_OFF;
161 FPU_access_ok(VERIFY_READ, single, 4);
162 FPU_get_user(m32, (unsigned long __user *)single);
163 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700164
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100165 negative = (m32 & 0x80000000) ? SIGN_Negative : SIGN_Positive;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700166
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100167 if (!(m32 & 0x7fffffff)) {
168 /* Zero */
169 reg_copy(&CONST_Z, loaded_data);
170 addexponent(loaded_data, negative);
171 return TAG_Zero;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700172 }
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100173 exp = ((m32 & 0x7f800000) >> 23) - SINGLE_Ebias + EXTENDED_Ebias;
174 m32 = (m32 & 0x7fffff) << 8;
175 if (exp < SINGLE_Emin + EXTENDED_Ebias) {
176 /* De-normals */
177 loaded_data->sigh = m32;
178 loaded_data->sigl = 0;
179
180 return normalize_no_excep(loaded_data, SINGLE_Emin, negative)
181 | (denormal_operand() < 0 ? FPU_Exception : 0);
182 } else if (exp > SINGLE_Emax + EXTENDED_Ebias) {
183 /* Infinity or NaN */
184 if (m32 == 0) {
185 /* +- infinity */
186 loaded_data->sigh = 0x80000000;
187 loaded_data->sigl = 0x00000000;
188 exp = EXP_Infinity + EXTENDED_Ebias;
189 tag = TAG_Special;
190 } else {
191 /* Must be a signaling or quiet NaN */
192 exp = EXP_NaN + EXTENDED_Ebias;
193 loaded_data->sigh = m32 | 0x80000000;
194 loaded_data->sigl = 0;
195 tag = TAG_Special; /* The calling function must look for NaNs */
196 }
197 } else {
198 loaded_data->sigh = m32 | 0x80000000;
199 loaded_data->sigl = 0;
200 tag = TAG_Valid;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700201 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700202
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100203 setexponent16(loaded_data, exp | negative); /* Set the sign. */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700204
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100205 return tag;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700206}
207
Linus Torvalds1da177e2005-04-16 15:20:36 -0700208/* Get a long long from user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100209int FPU_load_int64(long long __user *_s)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700210{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100211 long long s;
212 int sign;
213 FPU_REG *st0_ptr = &st(0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700214
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100215 RE_ENTRANT_CHECK_OFF;
216 FPU_access_ok(VERIFY_READ, _s, 8);
217 if (copy_from_user(&s, _s, 8))
218 FPU_abort;
219 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700220
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100221 if (s == 0) {
222 reg_copy(&CONST_Z, st0_ptr);
223 return TAG_Zero;
224 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700225
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100226 if (s > 0)
227 sign = SIGN_Positive;
228 else {
229 s = -s;
230 sign = SIGN_Negative;
231 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700232
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100233 significand(st0_ptr) = s;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700234
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100235 return normalize_no_excep(st0_ptr, 63, sign);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700236}
237
Linus Torvalds1da177e2005-04-16 15:20:36 -0700238/* Get a long from user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100239int FPU_load_int32(long __user *_s, FPU_REG *loaded_data)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700240{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100241 long s;
242 int negative;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700243
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100244 RE_ENTRANT_CHECK_OFF;
245 FPU_access_ok(VERIFY_READ, _s, 4);
246 FPU_get_user(s, _s);
247 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700248
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100249 if (s == 0) {
250 reg_copy(&CONST_Z, loaded_data);
251 return TAG_Zero;
252 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700253
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100254 if (s > 0)
255 negative = SIGN_Positive;
256 else {
257 s = -s;
258 negative = SIGN_Negative;
259 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700260
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100261 loaded_data->sigh = s;
262 loaded_data->sigl = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700263
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100264 return normalize_no_excep(loaded_data, 31, negative);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700265}
266
Linus Torvalds1da177e2005-04-16 15:20:36 -0700267/* Get a short from user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100268int FPU_load_int16(short __user *_s, FPU_REG *loaded_data)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700269{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100270 int s, negative;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700271
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100272 RE_ENTRANT_CHECK_OFF;
273 FPU_access_ok(VERIFY_READ, _s, 2);
274 /* Cast as short to get the sign extended. */
275 FPU_get_user(s, _s);
276 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700277
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100278 if (s == 0) {
279 reg_copy(&CONST_Z, loaded_data);
280 return TAG_Zero;
281 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700282
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100283 if (s > 0)
284 negative = SIGN_Positive;
285 else {
286 s = -s;
287 negative = SIGN_Negative;
288 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700289
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100290 loaded_data->sigh = s << 16;
291 loaded_data->sigl = 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700292
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100293 return normalize_no_excep(loaded_data, 15, negative);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700294}
295
Linus Torvalds1da177e2005-04-16 15:20:36 -0700296/* Get a packed bcd array from user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100297int FPU_load_bcd(u_char __user *s)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700298{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100299 FPU_REG *st0_ptr = &st(0);
300 int pos;
301 u_char bcd;
302 long long l = 0;
303 int sign;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700304
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100305 RE_ENTRANT_CHECK_OFF;
306 FPU_access_ok(VERIFY_READ, s, 10);
307 RE_ENTRANT_CHECK_ON;
308 for (pos = 8; pos >= 0; pos--) {
309 l *= 10;
310 RE_ENTRANT_CHECK_OFF;
311 FPU_get_user(bcd, s + pos);
312 RE_ENTRANT_CHECK_ON;
313 l += bcd >> 4;
314 l *= 10;
315 l += bcd & 0x0f;
316 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700317
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100318 RE_ENTRANT_CHECK_OFF;
319 FPU_get_user(sign, s + 9);
320 sign = sign & 0x80 ? SIGN_Negative : SIGN_Positive;
321 RE_ENTRANT_CHECK_ON;
322
323 if (l == 0) {
324 reg_copy(&CONST_Z, st0_ptr);
325 addexponent(st0_ptr, sign); /* Set the sign. */
326 return TAG_Zero;
327 } else {
328 significand(st0_ptr) = l;
329 return normalize_no_excep(st0_ptr, 63, sign);
330 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700331}
332
333/*===========================================================================*/
334
335/* Put a long double into user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100336int FPU_store_extended(FPU_REG *st0_ptr, u_char st0_tag,
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100337 long double __user * d)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700338{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100339 /*
340 The only exception raised by an attempt to store to an
341 extended format is the Invalid Stack exception, i.e.
342 attempting to store from an empty register.
343 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700344
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100345 if (st0_tag != TAG_Empty) {
346 RE_ENTRANT_CHECK_OFF;
347 FPU_access_ok(VERIFY_WRITE, d, 10);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700348
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100349 FPU_put_user(st0_ptr->sigl, (unsigned long __user *)d);
350 FPU_put_user(st0_ptr->sigh,
351 (unsigned long __user *)((u_char __user *) d + 4));
352 FPU_put_user(exponent16(st0_ptr),
353 (unsigned short __user *)((u_char __user *) d +
354 8));
355 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700356
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100357 return 1;
358 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700359
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100360 /* Empty register (stack underflow) */
361 EXCEPTION(EX_StackUnder);
362 if (control_word & CW_Invalid) {
363 /* The masked response */
364 /* Put out the QNaN indefinite */
365 RE_ENTRANT_CHECK_OFF;
366 FPU_access_ok(VERIFY_WRITE, d, 10);
367 FPU_put_user(0, (unsigned long __user *)d);
368 FPU_put_user(0xc0000000, 1 + (unsigned long __user *)d);
369 FPU_put_user(0xffff, 4 + (short __user *)d);
370 RE_ENTRANT_CHECK_ON;
371 return 1;
372 } else
373 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700374
375}
376
Linus Torvalds1da177e2005-04-16 15:20:36 -0700377/* Put a double into user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100378int FPU_store_double(FPU_REG *st0_ptr, u_char st0_tag, double __user *dfloat)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700379{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100380 unsigned long l[2];
381 unsigned long increment = 0; /* avoid gcc warnings */
382 int precision_loss;
383 int exp;
384 FPU_REG tmp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700385
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100386 if (st0_tag == TAG_Valid) {
387 reg_copy(st0_ptr, &tmp);
388 exp = exponent(&tmp);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700389
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100390 if (exp < DOUBLE_Emin) { /* It may be a denormal */
391 addexponent(&tmp, -DOUBLE_Emin + 52); /* largest exp to be 51 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700392
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100393 denormal_arg:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700394
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100395 if ((precision_loss = FPU_round_to_int(&tmp, st0_tag))) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700396#ifdef PECULIAR_486
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100397 /* Did it round to a non-denormal ? */
398 /* This behaviour might be regarded as peculiar, it appears
399 that the 80486 rounds to the dest precision, then
400 converts to decide underflow. */
401 if (!
402 ((tmp.sigh == 0x00100000) && (tmp.sigl == 0)
403 && (st0_ptr->sigl & 0x000007ff)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700404#endif /* PECULIAR_486 */
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100405 {
406 EXCEPTION(EX_Underflow);
407 /* This is a special case: see sec 16.2.5.1 of
408 the 80486 book */
409 if (!(control_word & CW_Underflow))
410 return 0;
411 }
412 EXCEPTION(precision_loss);
413 if (!(control_word & CW_Precision))
414 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700415 }
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100416 l[0] = tmp.sigl;
417 l[1] = tmp.sigh;
418 } else {
419 if (tmp.sigl & 0x000007ff) {
420 precision_loss = 1;
421 switch (control_word & CW_RC) {
422 case RC_RND:
423 /* Rounding can get a little messy.. */
424 increment = ((tmp.sigl & 0x7ff) > 0x400) | /* nearest */
425 ((tmp.sigl & 0xc00) == 0xc00); /* odd -> even */
426 break;
427 case RC_DOWN: /* towards -infinity */
428 increment =
429 signpositive(&tmp) ? 0 : tmp.
430 sigl & 0x7ff;
431 break;
432 case RC_UP: /* towards +infinity */
433 increment =
434 signpositive(&tmp) ? tmp.
435 sigl & 0x7ff : 0;
436 break;
437 case RC_CHOP:
438 increment = 0;
439 break;
440 }
441
442 /* Truncate the mantissa */
443 tmp.sigl &= 0xfffff800;
444
445 if (increment) {
446 if (tmp.sigl >= 0xfffff800) {
447 /* the sigl part overflows */
448 if (tmp.sigh == 0xffffffff) {
449 /* The sigh part overflows */
450 tmp.sigh = 0x80000000;
451 exp++;
452 if (exp >= EXP_OVER)
453 goto overflow;
454 } else {
455 tmp.sigh++;
456 }
457 tmp.sigl = 0x00000000;
458 } else {
459 /* We only need to increment sigl */
460 tmp.sigl += 0x00000800;
461 }
462 }
463 } else
464 precision_loss = 0;
465
466 l[0] = (tmp.sigl >> 11) | (tmp.sigh << 21);
467 l[1] = ((tmp.sigh >> 11) & 0xfffff);
468
469 if (exp > DOUBLE_Emax) {
470 overflow:
471 EXCEPTION(EX_Overflow);
472 if (!(control_word & CW_Overflow))
473 return 0;
474 set_precision_flag_up();
475 if (!(control_word & CW_Precision))
476 return 0;
477
478 /* This is a special case: see sec 16.2.5.1 of the 80486 book */
479 /* Overflow to infinity */
480 l[0] = 0x00000000; /* Set to */
481 l[1] = 0x7ff00000; /* + INF */
482 } else {
483 if (precision_loss) {
484 if (increment)
485 set_precision_flag_up();
486 else
487 set_precision_flag_down();
488 }
489 /* Add the exponent */
490 l[1] |= (((exp + DOUBLE_Ebias) & 0x7ff) << 20);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700491 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700492 }
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100493 } else if (st0_tag == TAG_Zero) {
494 /* Number is zero */
495 l[0] = 0;
496 l[1] = 0;
497 } else if (st0_tag == TAG_Special) {
498 st0_tag = FPU_Special(st0_ptr);
499 if (st0_tag == TW_Denormal) {
500 /* A denormal will always underflow. */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700501#ifndef PECULIAR_486
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100502 /* An 80486 is supposed to be able to generate
503 a denormal exception here, but... */
504 /* Underflow has priority. */
505 if (control_word & CW_Underflow)
506 denormal_operand();
Linus Torvalds1da177e2005-04-16 15:20:36 -0700507#endif /* PECULIAR_486 */
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100508 reg_copy(st0_ptr, &tmp);
509 goto denormal_arg;
510 } else if (st0_tag == TW_Infinity) {
511 l[0] = 0;
512 l[1] = 0x7ff00000;
513 } else if (st0_tag == TW_NaN) {
514 /* Is it really a NaN ? */
515 if ((exponent(st0_ptr) == EXP_OVER)
516 && (st0_ptr->sigh & 0x80000000)) {
517 /* See if we can get a valid NaN from the FPU_REG */
518 l[0] =
519 (st0_ptr->sigl >> 11) | (st0_ptr->
520 sigh << 21);
521 l[1] = ((st0_ptr->sigh >> 11) & 0xfffff);
522 if (!(st0_ptr->sigh & 0x40000000)) {
523 /* It is a signalling NaN */
524 EXCEPTION(EX_Invalid);
525 if (!(control_word & CW_Invalid))
526 return 0;
527 l[1] |= (0x40000000 >> 11);
528 }
529 l[1] |= 0x7ff00000;
530 } else {
531 /* It is an unsupported data type */
532 EXCEPTION(EX_Invalid);
533 if (!(control_word & CW_Invalid))
534 return 0;
535 l[0] = 0;
536 l[1] = 0xfff80000;
537 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700538 }
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100539 } else if (st0_tag == TAG_Empty) {
540 /* Empty register (stack underflow) */
541 EXCEPTION(EX_StackUnder);
542 if (control_word & CW_Invalid) {
543 /* The masked response */
544 /* Put out the QNaN indefinite */
545 RE_ENTRANT_CHECK_OFF;
546 FPU_access_ok(VERIFY_WRITE, dfloat, 8);
547 FPU_put_user(0, (unsigned long __user *)dfloat);
548 FPU_put_user(0xfff80000,
549 1 + (unsigned long __user *)dfloat);
550 RE_ENTRANT_CHECK_ON;
551 return 1;
552 } else
553 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700554 }
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100555 if (getsign(st0_ptr))
556 l[1] |= 0x80000000;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700557
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100558 RE_ENTRANT_CHECK_OFF;
559 FPU_access_ok(VERIFY_WRITE, dfloat, 8);
560 FPU_put_user(l[0], (unsigned long __user *)dfloat);
561 FPU_put_user(l[1], 1 + (unsigned long __user *)dfloat);
562 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700563
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100564 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700565}
566
Linus Torvalds1da177e2005-04-16 15:20:36 -0700567/* Put a float into user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100568int FPU_store_single(FPU_REG *st0_ptr, u_char st0_tag, float __user *single)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700569{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100570 long templ = 0;
571 unsigned long increment = 0; /* avoid gcc warnings */
572 int precision_loss;
573 int exp;
574 FPU_REG tmp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700575
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100576 if (st0_tag == TAG_Valid) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700577
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100578 reg_copy(st0_ptr, &tmp);
579 exp = exponent(&tmp);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700580
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100581 if (exp < SINGLE_Emin) {
582 addexponent(&tmp, -SINGLE_Emin + 23); /* largest exp to be 22 */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700583
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100584 denormal_arg:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700585
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100586 if ((precision_loss = FPU_round_to_int(&tmp, st0_tag))) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700587#ifdef PECULIAR_486
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100588 /* Did it round to a non-denormal ? */
589 /* This behaviour might be regarded as peculiar, it appears
590 that the 80486 rounds to the dest precision, then
591 converts to decide underflow. */
592 if (!((tmp.sigl == 0x00800000) &&
593 ((st0_ptr->sigh & 0x000000ff)
594 || st0_ptr->sigl)))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700595#endif /* PECULIAR_486 */
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100596 {
597 EXCEPTION(EX_Underflow);
598 /* This is a special case: see sec 16.2.5.1 of
599 the 80486 book */
600 if (!(control_word & CW_Underflow))
601 return 0;
602 }
603 EXCEPTION(precision_loss);
604 if (!(control_word & CW_Precision))
605 return 0;
606 }
607 templ = tmp.sigl;
608 } else {
609 if (tmp.sigl | (tmp.sigh & 0x000000ff)) {
610 unsigned long sigh = tmp.sigh;
611 unsigned long sigl = tmp.sigl;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700612
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100613 precision_loss = 1;
614 switch (control_word & CW_RC) {
615 case RC_RND:
616 increment = ((sigh & 0xff) > 0x80) /* more than half */
617 ||(((sigh & 0xff) == 0x80) && sigl) /* more than half */
618 ||((sigh & 0x180) == 0x180); /* round to even */
619 break;
620 case RC_DOWN: /* towards -infinity */
621 increment = signpositive(&tmp)
622 ? 0 : (sigl | (sigh & 0xff));
623 break;
624 case RC_UP: /* towards +infinity */
625 increment = signpositive(&tmp)
626 ? (sigl | (sigh & 0xff)) : 0;
627 break;
628 case RC_CHOP:
629 increment = 0;
630 break;
631 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700632
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100633 /* Truncate part of the mantissa */
634 tmp.sigl = 0;
635
636 if (increment) {
637 if (sigh >= 0xffffff00) {
638 /* The sigh part overflows */
639 tmp.sigh = 0x80000000;
640 exp++;
641 if (exp >= EXP_OVER)
642 goto overflow;
643 } else {
644 tmp.sigh &= 0xffffff00;
645 tmp.sigh += 0x100;
646 }
647 } else {
648 tmp.sigh &= 0xffffff00; /* Finish the truncation */
649 }
650 } else
651 precision_loss = 0;
652
653 templ = (tmp.sigh >> 8) & 0x007fffff;
654
655 if (exp > SINGLE_Emax) {
656 overflow:
657 EXCEPTION(EX_Overflow);
658 if (!(control_word & CW_Overflow))
659 return 0;
660 set_precision_flag_up();
661 if (!(control_word & CW_Precision))
662 return 0;
663
664 /* This is a special case: see sec 16.2.5.1 of the 80486 book. */
665 /* Masked response is overflow to infinity. */
666 templ = 0x7f800000;
667 } else {
668 if (precision_loss) {
669 if (increment)
670 set_precision_flag_up();
671 else
672 set_precision_flag_down();
673 }
674 /* Add the exponent */
675 templ |= ((exp + SINGLE_Ebias) & 0xff) << 23;
676 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700677 }
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100678 } else if (st0_tag == TAG_Zero) {
679 templ = 0;
680 } else if (st0_tag == TAG_Special) {
681 st0_tag = FPU_Special(st0_ptr);
682 if (st0_tag == TW_Denormal) {
683 reg_copy(st0_ptr, &tmp);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700684
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100685 /* A denormal will always underflow. */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700686#ifndef PECULIAR_486
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100687 /* An 80486 is supposed to be able to generate
688 a denormal exception here, but... */
689 /* Underflow has priority. */
690 if (control_word & CW_Underflow)
691 denormal_operand();
692#endif /* PECULIAR_486 */
693 goto denormal_arg;
694 } else if (st0_tag == TW_Infinity) {
695 templ = 0x7f800000;
696 } else if (st0_tag == TW_NaN) {
697 /* Is it really a NaN ? */
698 if ((exponent(st0_ptr) == EXP_OVER)
699 && (st0_ptr->sigh & 0x80000000)) {
700 /* See if we can get a valid NaN from the FPU_REG */
701 templ = st0_ptr->sigh >> 8;
702 if (!(st0_ptr->sigh & 0x40000000)) {
703 /* It is a signalling NaN */
704 EXCEPTION(EX_Invalid);
705 if (!(control_word & CW_Invalid))
706 return 0;
707 templ |= (0x40000000 >> 8);
708 }
709 templ |= 0x7f800000;
710 } else {
711 /* It is an unsupported data type */
712 EXCEPTION(EX_Invalid);
713 if (!(control_word & CW_Invalid))
714 return 0;
715 templ = 0xffc00000;
716 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700717 }
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100718#ifdef PARANOID
719 else {
720 EXCEPTION(EX_INTERNAL | 0x164);
721 return 0;
722 }
723#endif
724 } else if (st0_tag == TAG_Empty) {
725 /* Empty register (stack underflow) */
726 EXCEPTION(EX_StackUnder);
727 if (control_word & EX_Invalid) {
728 /* The masked response */
729 /* Put out the QNaN indefinite */
730 RE_ENTRANT_CHECK_OFF;
731 FPU_access_ok(VERIFY_WRITE, single, 4);
732 FPU_put_user(0xffc00000,
733 (unsigned long __user *)single);
734 RE_ENTRANT_CHECK_ON;
735 return 1;
736 } else
737 return 0;
738 }
739#ifdef PARANOID
740 else {
741 EXCEPTION(EX_INTERNAL | 0x163);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700742 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700743 }
744#endif
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100745 if (getsign(st0_ptr))
746 templ |= 0x80000000;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700747
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100748 RE_ENTRANT_CHECK_OFF;
749 FPU_access_ok(VERIFY_WRITE, single, 4);
750 FPU_put_user(templ, (unsigned long __user *)single);
751 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700752
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100753 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700754}
755
Linus Torvalds1da177e2005-04-16 15:20:36 -0700756/* Put a long long into user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100757int FPU_store_int64(FPU_REG *st0_ptr, u_char st0_tag, long long __user *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700758{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100759 FPU_REG t;
760 long long tll;
761 int precision_loss;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700762
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100763 if (st0_tag == TAG_Empty) {
764 /* Empty register (stack underflow) */
765 EXCEPTION(EX_StackUnder);
766 goto invalid_operand;
767 } else if (st0_tag == TAG_Special) {
768 st0_tag = FPU_Special(st0_ptr);
769 if ((st0_tag == TW_Infinity) || (st0_tag == TW_NaN)) {
770 EXCEPTION(EX_Invalid);
771 goto invalid_operand;
772 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700773 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700774
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100775 reg_copy(st0_ptr, &t);
776 precision_loss = FPU_round_to_int(&t, st0_tag);
777 ((long *)&tll)[0] = t.sigl;
778 ((long *)&tll)[1] = t.sigh;
779 if ((precision_loss == 1) ||
780 ((t.sigh & 0x80000000) &&
781 !((t.sigh == 0x80000000) && (t.sigl == 0) && signnegative(&t)))) {
782 EXCEPTION(EX_Invalid);
783 /* This is a special case: see sec 16.2.5.1 of the 80486 book */
784 invalid_operand:
785 if (control_word & EX_Invalid) {
786 /* Produce something like QNaN "indefinite" */
787 tll = 0x8000000000000000LL;
788 } else
789 return 0;
790 } else {
791 if (precision_loss)
792 set_precision_flag(precision_loss);
793 if (signnegative(&t))
794 tll = -tll;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700795 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700796
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100797 RE_ENTRANT_CHECK_OFF;
798 FPU_access_ok(VERIFY_WRITE, d, 8);
799 if (copy_to_user(d, &tll, 8))
800 FPU_abort;
801 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700802
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100803 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700804}
805
Linus Torvalds1da177e2005-04-16 15:20:36 -0700806/* Put a long into user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100807int FPU_store_int32(FPU_REG *st0_ptr, u_char st0_tag, long __user *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700808{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100809 FPU_REG t;
810 int precision_loss;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700811
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100812 if (st0_tag == TAG_Empty) {
813 /* Empty register (stack underflow) */
814 EXCEPTION(EX_StackUnder);
815 goto invalid_operand;
816 } else if (st0_tag == TAG_Special) {
817 st0_tag = FPU_Special(st0_ptr);
818 if ((st0_tag == TW_Infinity) || (st0_tag == TW_NaN)) {
819 EXCEPTION(EX_Invalid);
820 goto invalid_operand;
821 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700822 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700823
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100824 reg_copy(st0_ptr, &t);
825 precision_loss = FPU_round_to_int(&t, st0_tag);
826 if (t.sigh ||
827 ((t.sigl & 0x80000000) &&
828 !((t.sigl == 0x80000000) && signnegative(&t)))) {
829 EXCEPTION(EX_Invalid);
830 /* This is a special case: see sec 16.2.5.1 of the 80486 book */
831 invalid_operand:
832 if (control_word & EX_Invalid) {
833 /* Produce something like QNaN "indefinite" */
834 t.sigl = 0x80000000;
835 } else
836 return 0;
837 } else {
838 if (precision_loss)
839 set_precision_flag(precision_loss);
840 if (signnegative(&t))
841 t.sigl = -(long)t.sigl;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700842 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700843
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100844 RE_ENTRANT_CHECK_OFF;
845 FPU_access_ok(VERIFY_WRITE, d, 4);
846 FPU_put_user(t.sigl, (unsigned long __user *)d);
847 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700848
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100849 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700850}
851
Linus Torvalds1da177e2005-04-16 15:20:36 -0700852/* Put a short into user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100853int FPU_store_int16(FPU_REG *st0_ptr, u_char st0_tag, short __user *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700854{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100855 FPU_REG t;
856 int precision_loss;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700857
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100858 if (st0_tag == TAG_Empty) {
859 /* Empty register (stack underflow) */
860 EXCEPTION(EX_StackUnder);
861 goto invalid_operand;
862 } else if (st0_tag == TAG_Special) {
863 st0_tag = FPU_Special(st0_ptr);
864 if ((st0_tag == TW_Infinity) || (st0_tag == TW_NaN)) {
865 EXCEPTION(EX_Invalid);
866 goto invalid_operand;
867 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700868 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700869
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100870 reg_copy(st0_ptr, &t);
871 precision_loss = FPU_round_to_int(&t, st0_tag);
872 if (t.sigh ||
873 ((t.sigl & 0xffff8000) &&
874 !((t.sigl == 0x8000) && signnegative(&t)))) {
875 EXCEPTION(EX_Invalid);
876 /* This is a special case: see sec 16.2.5.1 of the 80486 book */
877 invalid_operand:
878 if (control_word & EX_Invalid) {
879 /* Produce something like QNaN "indefinite" */
880 t.sigl = 0x8000;
881 } else
882 return 0;
883 } else {
884 if (precision_loss)
885 set_precision_flag(precision_loss);
886 if (signnegative(&t))
887 t.sigl = -t.sigl;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700888 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700889
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100890 RE_ENTRANT_CHECK_OFF;
891 FPU_access_ok(VERIFY_WRITE, d, 2);
892 FPU_put_user((short)t.sigl, d);
893 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700894
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100895 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700896}
897
Linus Torvalds1da177e2005-04-16 15:20:36 -0700898/* Put a packed bcd array into user memory */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100899int FPU_store_bcd(FPU_REG *st0_ptr, u_char st0_tag, u_char __user *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700900{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100901 FPU_REG t;
902 unsigned long long ll;
903 u_char b;
904 int i, precision_loss;
905 u_char sign = (getsign(st0_ptr) == SIGN_NEG) ? 0x80 : 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700906
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100907 if (st0_tag == TAG_Empty) {
908 /* Empty register (stack underflow) */
909 EXCEPTION(EX_StackUnder);
910 goto invalid_operand;
911 } else if (st0_tag == TAG_Special) {
912 st0_tag = FPU_Special(st0_ptr);
913 if ((st0_tag == TW_Infinity) || (st0_tag == TW_NaN)) {
914 EXCEPTION(EX_Invalid);
915 goto invalid_operand;
916 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700917 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700918
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100919 reg_copy(st0_ptr, &t);
920 precision_loss = FPU_round_to_int(&t, st0_tag);
921 ll = significand(&t);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700922
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100923 /* Check for overflow, by comparing with 999999999999999999 decimal. */
924 if ((t.sigh > 0x0de0b6b3) ||
925 ((t.sigh == 0x0de0b6b3) && (t.sigl > 0xa763ffff))) {
926 EXCEPTION(EX_Invalid);
927 /* This is a special case: see sec 16.2.5.1 of the 80486 book */
928 invalid_operand:
929 if (control_word & CW_Invalid) {
930 /* Produce the QNaN "indefinite" */
931 RE_ENTRANT_CHECK_OFF;
932 FPU_access_ok(VERIFY_WRITE, d, 10);
933 for (i = 0; i < 7; i++)
934 FPU_put_user(0, d + i); /* These bytes "undefined" */
935 FPU_put_user(0xc0, d + 7); /* This byte "undefined" */
936 FPU_put_user(0xff, d + 8);
937 FPU_put_user(0xff, d + 9);
938 RE_ENTRANT_CHECK_ON;
939 return 1;
940 } else
941 return 0;
942 } else if (precision_loss) {
943 /* Precision loss doesn't stop the data transfer */
944 set_precision_flag(precision_loss);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700945 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700946
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100947 RE_ENTRANT_CHECK_OFF;
948 FPU_access_ok(VERIFY_WRITE, d, 10);
949 RE_ENTRANT_CHECK_ON;
950 for (i = 0; i < 9; i++) {
951 b = FPU_div_small(&ll, 10);
952 b |= (FPU_div_small(&ll, 10)) << 4;
953 RE_ENTRANT_CHECK_OFF;
954 FPU_put_user(b, d + i);
955 RE_ENTRANT_CHECK_ON;
956 }
957 RE_ENTRANT_CHECK_OFF;
958 FPU_put_user(sign, d + 9);
959 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700960
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100961 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700962}
963
964/*===========================================================================*/
965
966/* r gets mangled such that sig is int, sign:
967 it is NOT normalized */
968/* The return value (in eax) is zero if the result is exact,
969 if bits are changed due to rounding, truncation, etc, then
970 a non-zero value is returned */
971/* Overflow is signalled by a non-zero return value (in eax).
972 In the case of overflow, the returned significand always has the
973 largest possible value */
Ingo Molnare8d591d2008-01-30 13:30:12 +0100974int FPU_round_to_int(FPU_REG *r, u_char tag)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700975{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100976 u_char very_big;
977 unsigned eax;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700978
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100979 if (tag == TAG_Zero) {
980 /* Make sure that zero is returned */
981 significand(r) = 0;
982 return 0; /* o.k. */
983 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700984
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100985 if (exponent(r) > 63) {
986 r->sigl = r->sigh = ~0; /* The largest representable number */
987 return 1; /* overflow */
988 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700989
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100990 eax = FPU_shrxs(&r->sigl, 63 - exponent(r));
991 very_big = !(~(r->sigh) | ~(r->sigl)); /* test for 0xfff...fff */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700992#define half_or_more (eax & 0x80000000)
993#define frac_part (eax)
994#define more_than_half ((eax & 0x80000001) == 0x80000001)
Ingo Molnar3d0d14f2008-01-30 13:30:11 +0100995 switch (control_word & CW_RC) {
996 case RC_RND:
997 if (more_than_half /* nearest */
998 || (half_or_more && (r->sigl & 1))) { /* odd -> even */
999 if (very_big)
1000 return 1; /* overflow */
1001 significand(r)++;
1002 return PRECISION_LOST_UP;
1003 }
1004 break;
1005 case RC_DOWN:
1006 if (frac_part && getsign(r)) {
1007 if (very_big)
1008 return 1; /* overflow */
1009 significand(r)++;
1010 return PRECISION_LOST_UP;
1011 }
1012 break;
1013 case RC_UP:
1014 if (frac_part && !getsign(r)) {
1015 if (very_big)
1016 return 1; /* overflow */
1017 significand(r)++;
1018 return PRECISION_LOST_UP;
1019 }
1020 break;
1021 case RC_CHOP:
1022 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001023 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07001024
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001025 return eax ? PRECISION_LOST_DOWN : 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001026
1027}
1028
1029/*===========================================================================*/
1030
Ingo Molnare8d591d2008-01-30 13:30:12 +01001031u_char __user *fldenv(fpu_addr_modes addr_modes, u_char __user *s)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001032{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001033 unsigned short tag_word = 0;
1034 u_char tag;
1035 int i;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001036
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001037 if ((addr_modes.default_mode == VM86) ||
1038 ((addr_modes.default_mode == PM16)
1039 ^ (addr_modes.override.operand_size == OP_SIZE_PREFIX))) {
1040 RE_ENTRANT_CHECK_OFF;
1041 FPU_access_ok(VERIFY_READ, s, 0x0e);
1042 FPU_get_user(control_word, (unsigned short __user *)s);
1043 FPU_get_user(partial_status, (unsigned short __user *)(s + 2));
1044 FPU_get_user(tag_word, (unsigned short __user *)(s + 4));
1045 FPU_get_user(instruction_address.offset,
1046 (unsigned short __user *)(s + 6));
1047 FPU_get_user(instruction_address.selector,
1048 (unsigned short __user *)(s + 8));
1049 FPU_get_user(operand_address.offset,
1050 (unsigned short __user *)(s + 0x0a));
1051 FPU_get_user(operand_address.selector,
1052 (unsigned short __user *)(s + 0x0c));
1053 RE_ENTRANT_CHECK_ON;
1054 s += 0x0e;
1055 if (addr_modes.default_mode == VM86) {
1056 instruction_address.offset
1057 += (instruction_address.selector & 0xf000) << 4;
1058 operand_address.offset +=
1059 (operand_address.selector & 0xf000) << 4;
1060 }
1061 } else {
1062 RE_ENTRANT_CHECK_OFF;
1063 FPU_access_ok(VERIFY_READ, s, 0x1c);
1064 FPU_get_user(control_word, (unsigned short __user *)s);
1065 FPU_get_user(partial_status, (unsigned short __user *)(s + 4));
1066 FPU_get_user(tag_word, (unsigned short __user *)(s + 8));
1067 FPU_get_user(instruction_address.offset,
1068 (unsigned long __user *)(s + 0x0c));
1069 FPU_get_user(instruction_address.selector,
1070 (unsigned short __user *)(s + 0x10));
1071 FPU_get_user(instruction_address.opcode,
1072 (unsigned short __user *)(s + 0x12));
1073 FPU_get_user(operand_address.offset,
1074 (unsigned long __user *)(s + 0x14));
1075 FPU_get_user(operand_address.selector,
1076 (unsigned long __user *)(s + 0x18));
1077 RE_ENTRANT_CHECK_ON;
1078 s += 0x1c;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001079 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07001080
1081#ifdef PECULIAR_486
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001082 control_word &= ~0xe080;
1083#endif /* PECULIAR_486 */
Linus Torvalds1da177e2005-04-16 15:20:36 -07001084
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001085 top = (partial_status >> SW_Top_Shift) & 7;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001086
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001087 if (partial_status & ~control_word & CW_Exceptions)
1088 partial_status |= (SW_Summary | SW_Backward);
1089 else
1090 partial_status &= ~(SW_Summary | SW_Backward);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001091
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001092 for (i = 0; i < 8; i++) {
1093 tag = tag_word & 3;
1094 tag_word >>= 2;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001095
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001096 if (tag == TAG_Empty)
1097 /* New tag is empty. Accept it */
1098 FPU_settag(i, TAG_Empty);
1099 else if (FPU_gettag(i) == TAG_Empty) {
1100 /* Old tag is empty and new tag is not empty. New tag is determined
1101 by old reg contents */
1102 if (exponent(&fpu_register(i)) == -EXTENDED_Ebias) {
1103 if (!
1104 (fpu_register(i).sigl | fpu_register(i).
1105 sigh))
1106 FPU_settag(i, TAG_Zero);
1107 else
1108 FPU_settag(i, TAG_Special);
1109 } else if (exponent(&fpu_register(i)) ==
1110 0x7fff - EXTENDED_Ebias) {
1111 FPU_settag(i, TAG_Special);
1112 } else if (fpu_register(i).sigh & 0x80000000)
1113 FPU_settag(i, TAG_Valid);
1114 else
1115 FPU_settag(i, TAG_Special); /* An Un-normal */
1116 }
1117 /* Else old tag is not empty and new tag is not empty. Old tag
1118 remains correct */
1119 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07001120
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001121 return s;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001122}
1123
Ingo Molnare8d591d2008-01-30 13:30:12 +01001124void frstor(fpu_addr_modes addr_modes, u_char __user *data_address)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001125{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001126 int i, regnr;
1127 u_char __user *s = fldenv(addr_modes, data_address);
1128 int offset = (top & 7) * 10, other = 80 - offset;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001129
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001130 /* Copy all registers in stack order. */
1131 RE_ENTRANT_CHECK_OFF;
1132 FPU_access_ok(VERIFY_READ, s, 80);
1133 __copy_from_user(register_base + offset, s, other);
1134 if (offset)
1135 __copy_from_user(register_base, s + other, offset);
1136 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001137
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001138 for (i = 0; i < 8; i++) {
1139 regnr = (i + top) & 7;
1140 if (FPU_gettag(regnr) != TAG_Empty)
1141 /* The loaded data over-rides all other cases. */
1142 FPU_settag(regnr, FPU_tagof(&st(i)));
1143 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07001144
1145}
1146
Ingo Molnare8d591d2008-01-30 13:30:12 +01001147u_char __user *fstenv(fpu_addr_modes addr_modes, u_char __user *d)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001148{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001149 if ((addr_modes.default_mode == VM86) ||
1150 ((addr_modes.default_mode == PM16)
1151 ^ (addr_modes.override.operand_size == OP_SIZE_PREFIX))) {
1152 RE_ENTRANT_CHECK_OFF;
1153 FPU_access_ok(VERIFY_WRITE, d, 14);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001154#ifdef PECULIAR_486
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001155 FPU_put_user(control_word & ~0xe080, (unsigned long __user *)d);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001156#else
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001157 FPU_put_user(control_word, (unsigned short __user *)d);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001158#endif /* PECULIAR_486 */
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001159 FPU_put_user(status_word(), (unsigned short __user *)(d + 2));
1160 FPU_put_user(fpu_tag_word, (unsigned short __user *)(d + 4));
1161 FPU_put_user(instruction_address.offset,
1162 (unsigned short __user *)(d + 6));
1163 FPU_put_user(operand_address.offset,
1164 (unsigned short __user *)(d + 0x0a));
1165 if (addr_modes.default_mode == VM86) {
1166 FPU_put_user((instruction_address.
1167 offset & 0xf0000) >> 4,
1168 (unsigned short __user *)(d + 8));
1169 FPU_put_user((operand_address.offset & 0xf0000) >> 4,
1170 (unsigned short __user *)(d + 0x0c));
1171 } else {
1172 FPU_put_user(instruction_address.selector,
1173 (unsigned short __user *)(d + 8));
1174 FPU_put_user(operand_address.selector,
1175 (unsigned short __user *)(d + 0x0c));
1176 }
1177 RE_ENTRANT_CHECK_ON;
1178 d += 0x0e;
1179 } else {
1180 RE_ENTRANT_CHECK_OFF;
1181 FPU_access_ok(VERIFY_WRITE, d, 7 * 4);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001182#ifdef PECULIAR_486
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001183 control_word &= ~0xe080;
1184 /* An 80486 sets nearly all of the reserved bits to 1. */
1185 control_word |= 0xffff0040;
1186 partial_status = status_word() | 0xffff0000;
1187 fpu_tag_word |= 0xffff0000;
1188 I387.soft.fcs &= ~0xf8000000;
1189 I387.soft.fos |= 0xffff0000;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001190#endif /* PECULIAR_486 */
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001191 if (__copy_to_user(d, &control_word, 7 * 4))
1192 FPU_abort;
1193 RE_ENTRANT_CHECK_ON;
1194 d += 0x1c;
1195 }
Linus Torvalds1da177e2005-04-16 15:20:36 -07001196
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001197 control_word |= CW_Exceptions;
1198 partial_status &= ~(SW_Summary | SW_Backward);
1199
1200 return d;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001201}
1202
Ingo Molnare8d591d2008-01-30 13:30:12 +01001203void fsave(fpu_addr_modes addr_modes, u_char __user *data_address)
Linus Torvalds1da177e2005-04-16 15:20:36 -07001204{
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001205 u_char __user *d;
1206 int offset = (top & 7) * 10, other = 80 - offset;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001207
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001208 d = fstenv(addr_modes, data_address);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001209
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001210 RE_ENTRANT_CHECK_OFF;
1211 FPU_access_ok(VERIFY_WRITE, d, 80);
Linus Torvalds1da177e2005-04-16 15:20:36 -07001212
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001213 /* Copy all registers in stack order. */
1214 if (__copy_to_user(d, register_base + offset, other))
1215 FPU_abort;
1216 if (offset)
1217 if (__copy_to_user(d + other, register_base, offset))
1218 FPU_abort;
1219 RE_ENTRANT_CHECK_ON;
Linus Torvalds1da177e2005-04-16 15:20:36 -07001220
Ingo Molnar3d0d14f2008-01-30 13:30:11 +01001221 finit();
Linus Torvalds1da177e2005-04-16 15:20:36 -07001222}
1223
1224/*===========================================================================*/