/*
 * Copyright (c) 2003 Linuxant inc.
 * Copyright (c) 2001-2003 Conexant Systems, Inc.
 *
 * NOTE: The use and distribution of this software is governed by the terms in
 * the file LICENSE, which is included in the package. You must read this and
 * agree to these terms before using or distributing this software.
 *
 */
#include "oscompat.h"

#include "framewrk.h"
#include "ossysenv.h"

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
#include <asm/i387.h>
#endif

#if defined(cpu_has_xmm) && !defined(load_mxcsr)
#define load_mxcsr( val ) do { \
	unsigned long __mxcsr = ((unsigned long)(val) & 0xffbf); \
	asm volatile( "ldmxcsr %0" : : "m" (__mxcsr) ); \
} while (0)
#endif

#define NFPSTATES 10

typedef struct {
	unsigned long used;
	union i387_union i387;
	unsigned int cr0;
} fpstate_t;

static fpstate_t fpstates[NFPSTATES];

static inline void
hsf_kernel_fpu_begin(fpstate_t *fpstate)
{
	// XXX if CONFIG_PREEMPT we might have to disable preemption here
	fpstate->cr0 = read_cr0();
	clts();
#ifdef cpu_has_fxsr
	if ( cpu_has_fxsr ) {
		asm volatile( "fxsave %0 ; fnclex"
		      	: "=m" (fpstate->i387.fxsave) );
	} else {
		asm volatile( "fnsave %0 ; fwait"
		      	: "=m" (fpstate->i387.fsave) );
	}
#else
	asm volatile( "fnsave %0 ; fwait"
	      	: "=m" (fpstate->i387) );
#endif

    __asm__("fninit");
#ifdef cpu_has_xmm
	if ( cpu_has_xmm )
		load_mxcsr(0x1f80);
#endif
}

static inline void
hsf_kernel_fpu_end(fpstate_t *fpstate)
{
	clts();
#ifdef cpu_has_fxsr
	if ( cpu_has_fxsr ) {
		asm volatile( "fxrstor %0; fwait"
			      : : "m" (fpstate->i387.fxsave) );
	} else {
		asm volatile( "frstor %0; fwait"
			      : : "m" (fpstate->i387.fsave) );
	}
#else
	asm volatile( "frstor %0; fwait"
		      : : "m" (fpstate->i387) );
#endif
	write_cr0(fpstate->cr0);
}

/***************************************************************************
Function name :	OSFloatPrefix
Parameters :	None
Return value :	None
functionality :	Treats the FPU in order to enable float operations.
***************************************************************************/
 
GLOBAL	int OSFloatPrefix(void)
{
	static int Id;

	ASSERT(in_interrupt());

	while(test_and_set_bit(0, &fpstates[Id].used)) {
		Id++;
		if(Id == NFPSTATES)
			Id = 0;
	}

	hsf_kernel_fpu_begin(&fpstates[Id]);

	/* not allowed to sleep until OSFloatSuffix().
	 * moot point, since we're in interrupt context anyway..
	 * */
	return Id;
}

/***************************************************************************
Function name :	OSFloatPrefix
Parameters :	None
Return value :	None
functionality :	Restores the FPU after float operations.
***************************************************************************/
GLOBAL	BOOL OSFloatSuffix(int Id)
{
	hsf_kernel_fpu_end(&fpstates[Id]);
	if(!test_and_clear_bit(0, &fpstates[Id].used)) {
		printk(KERN_ERR "%s: Id=%d used bit was FALSE !\n", __FUNCTION__, Id);
		return FALSE;
	}

	return TRUE;
}

