/*
 * linux/kernel/math/get_put.c
 *
 * (C) 1991 Linus Torvalds
 */

/*
 * This file handles all accesses to user memory: getting and putting
 * ints/reals/BCD etc. This is the only part that concerns itself with
 * other than temporary real format. All other cals are strictly temp_real.
 */
#include <signal.h>

#include <linux/math_emu.h>
#include <linux/kernel.h>
#include <asm/segment.h>

void get_short_real(temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	short_real sr;

	addr = ea(info,code);
	sr = get_fs_long((unsigned long *) addr);
	short_to_temp(&sr,tmp);
}

void get_long_real(temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	long_real lr;

	addr = ea(info,code);
	lr.a = get_fs_long((unsigned long *) addr);
	lr.b = get_fs_long(1 + (unsigned long *) addr);
	long_to_temp(&lr,tmp);
}

void get_temp_real(temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;

	addr = ea(info,code);
	tmp->a = get_fs_long((unsigned long *) addr);
	tmp->b = get_fs_long(1 + (unsigned long *) addr);
	tmp->exponent = get_fs_word(4 + (unsigned short *) addr);
}

void get_short_int(temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	temp_int ti;

	addr = ea(info,code);
	ti.a = (signed short) get_fs_word((unsigned short *) addr);
	ti.b = 0;
	if (ti.sign = (ti.a < 0))
		ti.a = - ti.a;
	int_to_real(&ti,tmp);
}

void get_long_int(temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	temp_int ti;

	addr = ea(info,code);
	ti.a = get_fs_long((unsigned long *) addr);
	ti.b = 0;
	if (ti.sign = (ti.a < 0))
		ti.a = - ti.a;
	int_to_real(&ti,tmp);
}

void get_longlong_int(temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	temp_int ti;

	addr = ea(info,code);
	ti.a = get_fs_long((unsigned long *) addr);
	ti.b = get_fs_long(1 + (unsigned long *) addr);
	if (ti.sign = (ti.b < 0))
		__asm__("notl %0 ; notl %1\n\t"
			"addl $1,%0 ; adcl $0,%1"
			:"=r" (ti.a),"=r" (ti.b)
			:"0" (ti.a),"1" (ti.b));
	int_to_real(&ti,tmp);
}

#define MUL10(low,high) \
__asm__("addl %0,%0 ; adcl %1,%1\n\t" \
"movl %0,%%ecx ; movl %1,%%ebx\n\t" \
"addl %0,%0 ; adcl %1,%1\n\t" \
"addl %0,%0 ; adcl %1,%1\n\t" \
"addl %%ecx,%0 ; adcl %%ebx,%1" \
:"=a" (low),"=d" (high) \
:"0" (low),"1" (high):"cx","bx")

#define ADD64(val,low,high) \
__asm__("addl %4,%0 ; adcl $0,%1":"=r" (low),"=r" (high) \
:"0" (low),"1" (high),"r" ((unsigned long) (val)))

void get_BCD(temp_real * tmp, struct info * info, unsigned short code)
{
	int k;
	char * addr;
	temp_int i;
	unsigned char c;

	addr = ea(info,code);
	addr += 9;
	i.sign = 0x80 & get_fs_byte(addr--);
	i.a = i.b = 0;
	for (k = 0; k < 9; k++) {
		c = get_fs_byte(addr--);
		MUL10(i.a, i.b);
		ADD64((c>>4), i.a, i.b);
		MUL10(i.a, i.b);
		ADD64((c&0xf), i.a, i.b);
	}
	int_to_real(&i,tmp);
}

void put_short_real(const temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	short_real sr;

	addr = ea(info,code);
	verify_area(addr,4);
	temp_to_short(tmp,&sr);
	put_fs_long(sr,(unsigned long *) addr);
}

void put_long_real(const temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	long_real lr;

	addr = ea(info,code);
	verify_area(addr,8);
	temp_to_long(tmp,&lr);
	put_fs_long(lr.a, (unsigned long *) addr);
	put_fs_long(lr.b, 1 + (unsigned long *) addr);
}

void put_temp_real(const temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;

	addr = ea(info,code);
	verify_area(addr,10);
	put_fs_long(tmp->a, (unsigned long *) addr);
	put_fs_long(tmp->b, 1 + (unsigned long *) addr);
	put_fs_word(tmp->exponent, 4 + (short *) addr);
}

void put_short_int(const temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	temp_int ti;

	addr = ea(info,code);
	real_to_int(tmp,&ti);
	verify_area(addr,2);
	if (ti.sign)
		ti.a = -ti.a;
	put_fs_word(ti.a,(short *) addr);
}

void put_long_int(const temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	temp_int ti;

	addr = ea(info,code);
	real_to_int(tmp,&ti);
	verify_area(addr,4);
	if (ti.sign)
		ti.a = -ti.a;
	put_fs_long(ti.a,(unsigned long *) addr);
}

void put_longlong_int(const temp_real * tmp,
	struct info * info, unsigned short code)
{
	char * addr;
	temp_int ti;

	addr = ea(info,code);
	real_to_int(tmp,&ti);
	verify_area(addr,8);
	if (ti.sign)
		__asm__("notl %0 ; notl %1\n\t"
			"addl $1,%0 ; adcl $0,%1"
			:"=r" (ti.a),"=r" (ti.b)
			:"0" (ti.a),"1" (ti.b));
	put_fs_long(ti.a,(unsigned long *) addr);
	put_fs_long(ti.b,1 + (unsigned long *) addr);
}

#define DIV10(low,high,rem) \
__asm__("divl %6 ; xchgl %1,%2 ; divl %6" \
	:"=d" (rem),"=a" (low),"=r" (high) \
	:"0" (0),"1" (high),"2" (low),"c" (10))

void put_BCD(const temp_real * tmp,struct info * info, unsigned short code)
{
	int k,rem;
	char * addr;
	temp_int i;
	unsigned char c;

	addr = ea(info,code);
	verify_area(addr,10);
	real_to_int(tmp,&i);
	if (i.sign)
		put_fs_byte(0x80, addr+9);
	else
		put_fs_byte(0, addr+9);
	for (k = 0; k < 9; k++) {
		DIV10(i.a,i.b,rem);
		c = rem;
		DIV10(i.a,i.b,rem);
		c += rem<<4;
		put_fs_byte(c,addr++);
	}
}
