diff options
Diffstat (limited to '')
-rw-r--r-- | arch/mips/include/asm/fpu.h | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/arch/mips/include/asm/fpu.h b/arch/mips/include/asm/fpu.h new file mode 100644 index 000000000..a2813fe38 --- /dev/null +++ b/arch/mips/include/asm/fpu.h @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2002 MontaVista Software Inc. + * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#ifndef _ASM_FPU_H +#define _ASM_FPU_H + +#include <linux/sched.h> +#include <linux/sched/task_stack.h> +#include <linux/ptrace.h> +#include <linux/thread_info.h> +#include <linux/bitops.h> + +#include <asm/mipsregs.h> +#include <asm/cpu.h> +#include <asm/cpu-features.h> +#include <asm/fpu_emulator.h> +#include <asm/hazards.h> +#include <asm/ptrace.h> +#include <asm/processor.h> +#include <asm/current.h> +#include <asm/msa.h> + +#ifdef CONFIG_MIPS_MT_FPAFF +#include <asm/mips_mt.h> +#endif + +struct sigcontext; +struct sigcontext32; + +extern void _init_fpu(unsigned int); +extern void _save_fp(struct task_struct *); +extern void _restore_fp(struct task_struct *); + +/* + * This enum specifies a mode in which we want the FPU to operate, for cores + * which implement the Status.FR bit. Note that the bottom bit of the value + * purposefully matches the desired value of the Status.FR bit. + */ +enum fpu_mode { + FPU_32BIT = 0, /* FR = 0 */ + FPU_64BIT, /* FR = 1, FRE = 0 */ + FPU_AS_IS, + FPU_HYBRID, /* FR = 1, FRE = 1 */ + +#define FPU_FR_MASK 0x1 +}; + +#define __disable_fpu() \ +do { \ + clear_c0_status(ST0_CU1); \ + disable_fpu_hazard(); \ +} while (0) + +static inline int __enable_fpu(enum fpu_mode mode) +{ + int fr; + + switch (mode) { + case FPU_AS_IS: + /* just enable the FPU in its current mode */ + set_c0_status(ST0_CU1); + enable_fpu_hazard(); + return 0; + + case FPU_HYBRID: + if (!cpu_has_fre) + return SIGFPE; + + /* set FRE */ + set_c0_config5(MIPS_CONF5_FRE); + goto fr_common; + + case FPU_64BIT: +#if !(defined(CONFIG_CPU_MIPSR2) || defined(CONFIG_CPU_MIPSR6) \ + || defined(CONFIG_64BIT)) + /* we only have a 32-bit FPU */ + return SIGFPE; +#endif + /* fall through */ + case FPU_32BIT: + if (cpu_has_fre) { + /* clear FRE */ + clear_c0_config5(MIPS_CONF5_FRE); + } +fr_common: + /* set CU1 & change FR appropriately */ + fr = (int)mode & FPU_FR_MASK; + change_c0_status(ST0_CU1 | ST0_FR, ST0_CU1 | (fr ? ST0_FR : 0)); + enable_fpu_hazard(); + + /* check FR has the desired value */ + if (!!(read_c0_status() & ST0_FR) == !!fr) + return 0; + + /* unsupported FR value */ + __disable_fpu(); + return SIGFPE; + + default: + BUG(); + } + + return SIGFPE; +} + +#define clear_fpu_owner() clear_thread_flag(TIF_USEDFPU) + +static inline int __is_fpu_owner(void) +{ + return test_thread_flag(TIF_USEDFPU); +} + +static inline int is_fpu_owner(void) +{ + return cpu_has_fpu && __is_fpu_owner(); +} + +static inline int __own_fpu(void) +{ + enum fpu_mode mode; + int ret; + + if (test_thread_flag(TIF_HYBRID_FPREGS)) + mode = FPU_HYBRID; + else + mode = !test_thread_flag(TIF_32BIT_FPREGS); + + ret = __enable_fpu(mode); + if (ret) + return ret; + + KSTK_STATUS(current) |= ST0_CU1; + if (mode == FPU_64BIT || mode == FPU_HYBRID) + KSTK_STATUS(current) |= ST0_FR; + else /* mode == FPU_32BIT */ + KSTK_STATUS(current) &= ~ST0_FR; + + set_thread_flag(TIF_USEDFPU); + return 0; +} + +static inline int own_fpu_inatomic(int restore) +{ + int ret = 0; + + if (cpu_has_fpu && !__is_fpu_owner()) { + ret = __own_fpu(); + if (restore && !ret) + _restore_fp(current); + } + return ret; +} + +static inline int own_fpu(int restore) +{ + int ret; + + preempt_disable(); + ret = own_fpu_inatomic(restore); + preempt_enable(); + return ret; +} + +static inline void lose_fpu_inatomic(int save, struct task_struct *tsk) +{ + if (is_msa_enabled()) { + if (save) { + save_msa(tsk); + tsk->thread.fpu.fcr31 = + read_32bit_cp1_register(CP1_STATUS); + } + disable_msa(); + clear_tsk_thread_flag(tsk, TIF_USEDMSA); + __disable_fpu(); + } else if (is_fpu_owner()) { + if (save) + _save_fp(tsk); + __disable_fpu(); + } else { + /* FPU should not have been left enabled with no owner */ + WARN(read_c0_status() & ST0_CU1, + "Orphaned FPU left enabled"); + } + KSTK_STATUS(tsk) &= ~ST0_CU1; + clear_tsk_thread_flag(tsk, TIF_USEDFPU); +} + +static inline void lose_fpu(int save) +{ + preempt_disable(); + lose_fpu_inatomic(save, current); + preempt_enable(); +} + +static inline int init_fpu(void) +{ + unsigned int fcr31 = current->thread.fpu.fcr31; + int ret = 0; + + if (cpu_has_fpu) { + unsigned int config5; + + ret = __own_fpu(); + if (ret) + return ret; + + if (!cpu_has_fre) { + _init_fpu(fcr31); + + return 0; + } + + /* + * Ensure FRE is clear whilst running _init_fpu, since + * single precision FP instructions are used. If FRE + * was set then we'll just end up initialising all 32 + * 64b registers. + */ + config5 = clear_c0_config5(MIPS_CONF5_FRE); + enable_fpu_hazard(); + + _init_fpu(fcr31); + + /* Restore FRE */ + write_c0_config5(config5); + enable_fpu_hazard(); + } else + fpu_emulator_init_fpu(); + + return ret; +} + +static inline void save_fp(struct task_struct *tsk) +{ + if (cpu_has_fpu) + _save_fp(tsk); +} + +static inline void restore_fp(struct task_struct *tsk) +{ + if (cpu_has_fpu) + _restore_fp(tsk); +} + +static inline union fpureg *get_fpu_regs(struct task_struct *tsk) +{ + if (tsk == current) { + preempt_disable(); + if (is_fpu_owner()) + _save_fp(current); + preempt_enable(); + } + + return tsk->thread.fpu.fpr; +} + +#endif /* _ASM_FPU_H */ |