/*
All of the functions which transfer data between user memory and FPU_REGs
*/
/*---------------------------------------------------------------------------+
| Note: |
| The file contains code which accesses user memory. |
| Emulator static data may change when user memory is accessed, due to |
| other processes using the emulator while swapping is in progress. |
+---------------------------------------------------------------------------*/
#include "fpu_emu.h"
#include <asm/uaccess.h>
#include "fpu_system.h"
#include "exception.h"
#include "reg_constant.h"
#include "control_w.h"
#include "status_w.h"
#define DOUBLE_Emax 1023 /* largest valid exponent */
#define DOUBLE_Ebias 1023
#define DOUBLE_Emin (-1022) /* smallest valid exponent */
#define SINGLE_Emax 127 /* largest valid exponent */
#define SINGLE_Ebias 127
#define SINGLE_Emin (-126) /* smallest valid exponent */
static u_char normalize_no_excep(FPU_REG *r, int exp, int sign)
{
u_char tag;
setexponent16(r, exp);
tag = FPU_normalize_nuo(r);
stdexp(r);
if (sign)
setnegative(r);
return tag;
}
int FPU_tagof(FPU_REG *ptr)
{
int exp;
exp = exponent16(ptr) & 0x7fff;
if (exp == 0) {
if (!(ptr->sigh | ptr->sigl)) {
return TAG_Zero;
}
/* The number is a de-normal or pseudodenormal. */
return TAG_Special;
}
if (exp == 0x7fff) {
/* Is an Infinity, a NaN, or an unsupported data type. */
return TAG_Special;
}
if (!(ptr->sigh & 0x80000000)) {
/* Unsupported data type. */
/* Valid numbers have the ms bit set to 1. */
/* Unnormal. */
return TAG_Special;
}
return TAG_Valid;
}
/* Get a long double from user memory */
int FPU_load_extended(long double __user *s, int stnr)
{
FPU_REG *sti_ptr = &st(stnr);
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(VERIFY_READ, s, 10);
__copy_from_user(sti_ptr, s, 10);
RE_ENTRANT_CHECK_ON;
return FPU_tagof(sti_ptr);
}
/* Get a double from user memory */
int FPU_load_double(double __user *dfloat, FPU_REG *loaded_data)
{
int exp, tag, negative;
unsigned m64, l64;
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(VERIFY_READ, dfloat, 8);
FPU_get_user(m64, 1 + (unsigned long __user *)dfloat);
FPU_get_user(l64, (unsigned long __user *)dfloat);
RE_ENTRANT_CHECK_ON;
negative = (m64 & 0x80000000) ? SIGN_Negative : SIGN_Positive;
exp = ((m64 & 0x7ff00000) >> 20) - DOUBLE_Ebias + EXTENDED_Ebias;
m64 &= 0xfffff;
if (exp > DOUBLE_Emax + EXTENDED_Ebias) {
/* Infinity or NaN */
if ((m64 == 0) && (l64 == 0)) {
/* +- infinity */
loaded_data->sigh = 0x80000000;
loaded_data->sigl = 0x00000000;
exp = EXP_Infinity + EXTENDED_Ebias;
tag = TAG_Special;
} else {
/* Must be a signaling or quiet NaN */
exp = EXP_NaN + EXTENDED_Ebias;
loaded_data->sigh = (m64 << 11) | 0x80000000;
loaded_data->sigh |= l64 >> 21;
loaded_data->sigl = l64 << 11;
tag = TAG_Special; /* The calling function must look for NaNs */
}
} else if (exp < DOUBLE_Emin + EXTENDED_Ebias) {
/* Zero or de-normal */
if ((m64 == 0) && (l64 == 0)) {
/* Zero */
reg_copy(&CONST_Z, loaded_data);
exp = 0;
tag = TAG_Zero;
} else {
/* De-normal */
loaded_data->sigh = m64 << 11;
loaded_data->sigh |= l64 >> 21;
loaded_data->sigl = l64 << 11;
return normalize_no_excep(loaded_data, DOUBLE_Emin,
negative)
| (denormal_operand() < 0 ? FPU_Exception : 0);
}
} else {
loaded_data->sigh = (m64 << 11) | 0x80000000;
loaded_data->sigh |= l64 >> 21;
loaded_data->sigl = l64 << 11;
tag = TAG_Valid;
}
setexponent16(loaded_data, exp | negative);
return tag;
}
/* Get a float from user memory */
int FPU_load_single(float __user *single, FPU_REG *loaded_data)
{
unsigned m32;
int exp, tag, negative;
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(VERIFY_READ, single, 4);
FPU_get_user(m32, (unsigned long __user *)single);
RE_ENTRANT_CHECK_ON;
negative = (m32 & 0x80000000) ? SIGN_Negative : SIGN_Positive;
if (!(m32 & 0x7fffffff)) {
/* Zero */
reg_copy(&CONST_Z, loaded_data);
addexponent(loaded_data, negative);
return TAG_Zero;
}
exp = ((m32 & 0x7f800000) >> 23) - SINGLE_Ebias + EXTENDED_Ebias;
m32 = (m32 & 0x7fffff) << 8;
if (exp < SINGLE_Emin + EXTENDED_Ebias) {
/* De-normals */
loaded_data->sigh = m32;
loaded_data->sigl = 0;
return normalize_no_excep(loaded_data, SINGLE_Emin, negative)
| (denormal_operand() < 0 ? FPU_Exception : 0);
} else if (exp > SINGLE_Emax + EXTENDED_Ebias) {
/* Infinity or NaN */
if (m32 == 0) {
/* +- infinity */
loaded_data->sigh = 0x80000000;
loaded_data->sigl = 0x00000000;
exp = EXP_Infinity + EXTENDED_Ebias;
tag = TAG_Special;
} else {
/* Must be a signaling or quiet NaN */
exp = EXP_NaN + EXTENDED_Ebias;
loaded_data->sigh = m32 | 0x80000000;
loaded_data->sigl = 0;
tag = TAG_Special; /* The calling function must look for NaNs */
}
} else {
loaded_data->sigh = m32 | 0x80000000;
loaded_data->sigl = 0;
tag = TAG_Valid;
}
setexponent16(loaded_data, exp | negative); /* Set the sign. */
return tag;
}
/* Get a long long from user memory */
int FPU_load_int64(long long __user *_s)
{
long long s;
int sign;
FPU_REG *st0_ptr = &st(0);
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(VERIFY_READ, _s, 8);
if (copy_from_user(&s, _s, 8))
FPU_abort;
RE_ENTRANT_CHECK_ON;
if (s == 0) {
reg_copy(&CONST_Z, st0_ptr);
return TAG_Zero;
}
if (s > 0)
sign = SIGN_Positive;
else {
s = -s;
sign = SIGN_Negative;
}
significand(st0_ptr) = s;
return normalize_no_excep(st0_ptr, 63, sign);
}
/* Get a long from user memory */
int FPU_load_int32(long __user *_s, FPU_REG *loaded_data)
{
long s;
int negative;
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(VERIFY_READ, _s, 4);
FPU_get_user(s, _s);
RE_ENTRANT_CHECK_ON;
if (s == 0) {
reg_copy(&CONST_Z, loaded_data);
return TAG_Zero;
}
if (s > 0)
negative = SIGN_Positive;
else {
s = -s;
negative = SIGN_Negative;
}
loaded_data->sigh = s;
loaded_data->sigl = 0;
return normalize_no_excep(loaded_data, 31, negative);
}
/* Get a short from user memory */
int FPU_load_int16(short __user *_s, FPU_REG *loaded_data)
{
int s, negative;
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(VERIFY_READ, _s, 2);
/* Cast as short to get the sign extended. */
FPU_get_user(s, _s);
RE_ENTRANT_CHECK_ON;
if (s == 0) {
reg_copy(&CONST_Z, loaded_data);
return TAG_Zero;
}
if (s > 0)
negative = SIGN_Positive;
else {
s = -s;
negative = SIGN_Negative;
}
loaded_data->sigh = s << 16;
loaded_data->sigl = 0;
return normalize_no_excep(loaded_data, 15, negative);
}
/* Get a packed bcd array from user memory */
int FPU_load_bcd(u_char __user *s)
{
FPU_REG *st0_ptr = &st(0);
int pos;
u_char bcd;
long long l = 0;
int sign;
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(VERIFY_READ, s, 10);
RE_ENTRANT_CHECK_ON;
for (pos = 8; pos >= 0; pos--) {
l *= 10;
RE_ENTRANT_CHECK_OFF;
FPU_get_user(bcd, s + pos);
RE_ENTRANT_CHECK_ON;
l += bcd >> 4;
l *= 10;
l += bcd & 0x0f;
}
RE_ENTRANT_CHECK_OFF;
FPU_get_user(sign, s + 9);
sign = sign & 0x80 ? SIGN_Negative : SIGN_Positive;
RE_ENTRANT_CHECK_ON;
if (l == 0) {
reg_copy(&CONST_Z, st0_ptr);
addexponent(st0_ptr, sign); /* Set the sign. */
return TAG_Zero;
} else {
significand(st0_ptr) = l;
return normalize_no_excep(st0_ptr, 63, sign);
}
}
/*===========================================================================*/
/* Put a long double into user memory */
int FPU_store_extended(FPU_REG *st0_ptr, u_char st0_tag,
long double __user * d)
{
/*
The only exception raised by an attempt to store to an
extended format is the Invalid Stack exception, i.e.
attempting to store from an empty register.
*/
if (st0_tag != TAG_Empty) {
RE_ENTRANT_CHECK_OFF;
FPU_access_ok(VERIFY_WRITE, d, 10);
FPU_put_user(st0_ptr->sigl, (unsigned long __user *)d);
FPU_put_user(st0_ptr->sigh,
(uns