18c2ecf20Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0-or-later */
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2002 MontaVista Software Inc.
48c2ecf20Sopenharmony_ci * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci#ifndef _ASM_FPU_H
78c2ecf20Sopenharmony_ci#define _ASM_FPU_H
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/sched.h>
108c2ecf20Sopenharmony_ci#include <linux/sched/task_stack.h>
118c2ecf20Sopenharmony_ci#include <linux/ptrace.h>
128c2ecf20Sopenharmony_ci#include <linux/thread_info.h>
138c2ecf20Sopenharmony_ci#include <linux/bitops.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <asm/mipsregs.h>
168c2ecf20Sopenharmony_ci#include <asm/cpu.h>
178c2ecf20Sopenharmony_ci#include <asm/cpu-features.h>
188c2ecf20Sopenharmony_ci#include <asm/fpu_emulator.h>
198c2ecf20Sopenharmony_ci#include <asm/hazards.h>
208c2ecf20Sopenharmony_ci#include <asm/ptrace.h>
218c2ecf20Sopenharmony_ci#include <asm/processor.h>
228c2ecf20Sopenharmony_ci#include <asm/current.h>
238c2ecf20Sopenharmony_ci#include <asm/msa.h>
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#ifdef CONFIG_MIPS_MT_FPAFF
268c2ecf20Sopenharmony_ci#include <asm/mips_mt.h>
278c2ecf20Sopenharmony_ci#endif
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci/*
308c2ecf20Sopenharmony_ci * This enum specifies a mode in which we want the FPU to operate, for cores
318c2ecf20Sopenharmony_ci * which implement the Status.FR bit. Note that the bottom bit of the value
328c2ecf20Sopenharmony_ci * purposefully matches the desired value of the Status.FR bit.
338c2ecf20Sopenharmony_ci */
348c2ecf20Sopenharmony_cienum fpu_mode {
358c2ecf20Sopenharmony_ci	FPU_32BIT = 0,		/* FR = 0 */
368c2ecf20Sopenharmony_ci	FPU_64BIT,		/* FR = 1, FRE = 0 */
378c2ecf20Sopenharmony_ci	FPU_AS_IS,
388c2ecf20Sopenharmony_ci	FPU_HYBRID,		/* FR = 1, FRE = 1 */
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci#define FPU_FR_MASK		0x1
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci#ifdef CONFIG_MIPS_FP_SUPPORT
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ciextern void _save_fp(struct task_struct *);
468c2ecf20Sopenharmony_ciextern void _restore_fp(struct task_struct *);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci#define __disable_fpu()							\
498c2ecf20Sopenharmony_cido {									\
508c2ecf20Sopenharmony_ci	clear_c0_status(ST0_CU1);					\
518c2ecf20Sopenharmony_ci	disable_fpu_hazard();						\
528c2ecf20Sopenharmony_ci} while (0)
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistatic inline int __enable_fpu(enum fpu_mode mode)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	int fr;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	switch (mode) {
598c2ecf20Sopenharmony_ci	case FPU_AS_IS:
608c2ecf20Sopenharmony_ci		/* just enable the FPU in its current mode */
618c2ecf20Sopenharmony_ci		set_c0_status(ST0_CU1);
628c2ecf20Sopenharmony_ci		enable_fpu_hazard();
638c2ecf20Sopenharmony_ci		return 0;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	case FPU_HYBRID:
668c2ecf20Sopenharmony_ci		if (!cpu_has_fre)
678c2ecf20Sopenharmony_ci			return SIGFPE;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci		/* set FRE */
708c2ecf20Sopenharmony_ci		set_c0_config5(MIPS_CONF5_FRE);
718c2ecf20Sopenharmony_ci		goto fr_common;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	case FPU_64BIT:
748c2ecf20Sopenharmony_ci#if !(defined(CONFIG_CPU_MIPSR2) || defined(CONFIG_CPU_MIPSR5) || \
758c2ecf20Sopenharmony_ci      defined(CONFIG_CPU_MIPSR6) || defined(CONFIG_64BIT))
768c2ecf20Sopenharmony_ci		/* we only have a 32-bit FPU */
778c2ecf20Sopenharmony_ci		return SIGFPE;
788c2ecf20Sopenharmony_ci#endif
798c2ecf20Sopenharmony_ci		fallthrough;
808c2ecf20Sopenharmony_ci	case FPU_32BIT:
818c2ecf20Sopenharmony_ci		if (cpu_has_fre) {
828c2ecf20Sopenharmony_ci			/* clear FRE */
838c2ecf20Sopenharmony_ci			clear_c0_config5(MIPS_CONF5_FRE);
848c2ecf20Sopenharmony_ci		}
858c2ecf20Sopenharmony_cifr_common:
868c2ecf20Sopenharmony_ci		/* set CU1 & change FR appropriately */
878c2ecf20Sopenharmony_ci		fr = (int)mode & FPU_FR_MASK;
888c2ecf20Sopenharmony_ci		change_c0_status(ST0_CU1 | ST0_FR, ST0_CU1 | (fr ? ST0_FR : 0));
898c2ecf20Sopenharmony_ci		enable_fpu_hazard();
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci		/* check FR has the desired value */
928c2ecf20Sopenharmony_ci		if (!!(read_c0_status() & ST0_FR) == !!fr)
938c2ecf20Sopenharmony_ci			return 0;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci		/* unsupported FR value */
968c2ecf20Sopenharmony_ci		__disable_fpu();
978c2ecf20Sopenharmony_ci		return SIGFPE;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	default:
1008c2ecf20Sopenharmony_ci		BUG();
1018c2ecf20Sopenharmony_ci	}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	return SIGFPE;
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci#define clear_fpu_owner()	clear_thread_flag(TIF_USEDFPU)
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic inline int __is_fpu_owner(void)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	return test_thread_flag(TIF_USEDFPU);
1118c2ecf20Sopenharmony_ci}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic inline int is_fpu_owner(void)
1148c2ecf20Sopenharmony_ci{
1158c2ecf20Sopenharmony_ci	return cpu_has_fpu && __is_fpu_owner();
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_cistatic inline int __own_fpu(void)
1198c2ecf20Sopenharmony_ci{
1208c2ecf20Sopenharmony_ci	enum fpu_mode mode;
1218c2ecf20Sopenharmony_ci	int ret;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	if (test_thread_flag(TIF_HYBRID_FPREGS))
1248c2ecf20Sopenharmony_ci		mode = FPU_HYBRID;
1258c2ecf20Sopenharmony_ci	else
1268c2ecf20Sopenharmony_ci		mode = !test_thread_flag(TIF_32BIT_FPREGS);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	ret = __enable_fpu(mode);
1298c2ecf20Sopenharmony_ci	if (ret)
1308c2ecf20Sopenharmony_ci		return ret;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	KSTK_STATUS(current) |= ST0_CU1;
1338c2ecf20Sopenharmony_ci	if (mode == FPU_64BIT || mode == FPU_HYBRID)
1348c2ecf20Sopenharmony_ci		KSTK_STATUS(current) |= ST0_FR;
1358c2ecf20Sopenharmony_ci	else /* mode == FPU_32BIT */
1368c2ecf20Sopenharmony_ci		KSTK_STATUS(current) &= ~ST0_FR;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	set_thread_flag(TIF_USEDFPU);
1398c2ecf20Sopenharmony_ci	return 0;
1408c2ecf20Sopenharmony_ci}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic inline int own_fpu_inatomic(int restore)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	int ret = 0;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	if (cpu_has_fpu && !__is_fpu_owner()) {
1478c2ecf20Sopenharmony_ci		ret = __own_fpu();
1488c2ecf20Sopenharmony_ci		if (restore && !ret)
1498c2ecf20Sopenharmony_ci			_restore_fp(current);
1508c2ecf20Sopenharmony_ci	}
1518c2ecf20Sopenharmony_ci	return ret;
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic inline int own_fpu(int restore)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	int ret;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	preempt_disable();
1598c2ecf20Sopenharmony_ci	ret = own_fpu_inatomic(restore);
1608c2ecf20Sopenharmony_ci	preempt_enable();
1618c2ecf20Sopenharmony_ci	return ret;
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic inline void lose_fpu_inatomic(int save, struct task_struct *tsk)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	if (is_msa_enabled()) {
1678c2ecf20Sopenharmony_ci		if (save) {
1688c2ecf20Sopenharmony_ci			save_msa(tsk);
1698c2ecf20Sopenharmony_ci			tsk->thread.fpu.fcr31 =
1708c2ecf20Sopenharmony_ci					read_32bit_cp1_register(CP1_STATUS);
1718c2ecf20Sopenharmony_ci		}
1728c2ecf20Sopenharmony_ci		disable_msa();
1738c2ecf20Sopenharmony_ci		clear_tsk_thread_flag(tsk, TIF_USEDMSA);
1748c2ecf20Sopenharmony_ci		__disable_fpu();
1758c2ecf20Sopenharmony_ci	} else if (is_fpu_owner()) {
1768c2ecf20Sopenharmony_ci		if (save)
1778c2ecf20Sopenharmony_ci			_save_fp(tsk);
1788c2ecf20Sopenharmony_ci		__disable_fpu();
1798c2ecf20Sopenharmony_ci	} else {
1808c2ecf20Sopenharmony_ci		/* FPU should not have been left enabled with no owner */
1818c2ecf20Sopenharmony_ci		WARN(read_c0_status() & ST0_CU1,
1828c2ecf20Sopenharmony_ci		     "Orphaned FPU left enabled");
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci	KSTK_STATUS(tsk) &= ~ST0_CU1;
1858c2ecf20Sopenharmony_ci	clear_tsk_thread_flag(tsk, TIF_USEDFPU);
1868c2ecf20Sopenharmony_ci}
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_cistatic inline void lose_fpu(int save)
1898c2ecf20Sopenharmony_ci{
1908c2ecf20Sopenharmony_ci	preempt_disable();
1918c2ecf20Sopenharmony_ci	lose_fpu_inatomic(save, current);
1928c2ecf20Sopenharmony_ci	preempt_enable();
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci/**
1968c2ecf20Sopenharmony_ci * init_fp_ctx() - Initialize task FP context
1978c2ecf20Sopenharmony_ci * @target: The task whose FP context should be initialized.
1988c2ecf20Sopenharmony_ci *
1998c2ecf20Sopenharmony_ci * Initializes the FP context of the target task to sane default values if that
2008c2ecf20Sopenharmony_ci * target task does not already have valid FP context. Once the context has
2018c2ecf20Sopenharmony_ci * been initialized, the task will be marked as having used FP & thus having
2028c2ecf20Sopenharmony_ci * valid FP context.
2038c2ecf20Sopenharmony_ci *
2048c2ecf20Sopenharmony_ci * Returns: true if context is initialized, else false.
2058c2ecf20Sopenharmony_ci */
2068c2ecf20Sopenharmony_cistatic inline bool init_fp_ctx(struct task_struct *target)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	/* If FP has been used then the target already has context */
2098c2ecf20Sopenharmony_ci	if (tsk_used_math(target))
2108c2ecf20Sopenharmony_ci		return false;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	/* Begin with data registers set to all 1s... */
2138c2ecf20Sopenharmony_ci	memset(&target->thread.fpu.fpr, ~0, sizeof(target->thread.fpu.fpr));
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	/* FCSR has been preset by `mips_set_personality_nan'.  */
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	/*
2188c2ecf20Sopenharmony_ci	 * Record that the target has "used" math, such that the context
2198c2ecf20Sopenharmony_ci	 * just initialised, and any modifications made by the caller,
2208c2ecf20Sopenharmony_ci	 * aren't discarded.
2218c2ecf20Sopenharmony_ci	 */
2228c2ecf20Sopenharmony_ci	set_stopped_child_used_math(target);
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	return true;
2258c2ecf20Sopenharmony_ci}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_cistatic inline void save_fp(struct task_struct *tsk)
2288c2ecf20Sopenharmony_ci{
2298c2ecf20Sopenharmony_ci	if (cpu_has_fpu)
2308c2ecf20Sopenharmony_ci		_save_fp(tsk);
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_cistatic inline void restore_fp(struct task_struct *tsk)
2348c2ecf20Sopenharmony_ci{
2358c2ecf20Sopenharmony_ci	if (cpu_has_fpu)
2368c2ecf20Sopenharmony_ci		_restore_fp(tsk);
2378c2ecf20Sopenharmony_ci}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_cistatic inline union fpureg *get_fpu_regs(struct task_struct *tsk)
2408c2ecf20Sopenharmony_ci{
2418c2ecf20Sopenharmony_ci	if (tsk == current) {
2428c2ecf20Sopenharmony_ci		preempt_disable();
2438c2ecf20Sopenharmony_ci		if (is_fpu_owner())
2448c2ecf20Sopenharmony_ci			_save_fp(current);
2458c2ecf20Sopenharmony_ci		preempt_enable();
2468c2ecf20Sopenharmony_ci	}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	return tsk->thread.fpu.fpr;
2498c2ecf20Sopenharmony_ci}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci#else /* !CONFIG_MIPS_FP_SUPPORT */
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci/*
2548c2ecf20Sopenharmony_ci * When FP support is disabled we provide only a minimal set of stub functions
2558c2ecf20Sopenharmony_ci * to avoid callers needing to care too much about CONFIG_MIPS_FP_SUPPORT.
2568c2ecf20Sopenharmony_ci */
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_cistatic inline int __enable_fpu(enum fpu_mode mode)
2598c2ecf20Sopenharmony_ci{
2608c2ecf20Sopenharmony_ci	return SIGILL;
2618c2ecf20Sopenharmony_ci}
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_cistatic inline void __disable_fpu(void)
2648c2ecf20Sopenharmony_ci{
2658c2ecf20Sopenharmony_ci	/* no-op */
2668c2ecf20Sopenharmony_ci}
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_cistatic inline int is_fpu_owner(void)
2708c2ecf20Sopenharmony_ci{
2718c2ecf20Sopenharmony_ci	return 0;
2728c2ecf20Sopenharmony_ci}
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_cistatic inline void clear_fpu_owner(void)
2758c2ecf20Sopenharmony_ci{
2768c2ecf20Sopenharmony_ci	/* no-op */
2778c2ecf20Sopenharmony_ci}
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_cistatic inline int own_fpu_inatomic(int restore)
2808c2ecf20Sopenharmony_ci{
2818c2ecf20Sopenharmony_ci	return SIGILL;
2828c2ecf20Sopenharmony_ci}
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_cistatic inline int own_fpu(int restore)
2858c2ecf20Sopenharmony_ci{
2868c2ecf20Sopenharmony_ci	return SIGILL;
2878c2ecf20Sopenharmony_ci}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_cistatic inline void lose_fpu_inatomic(int save, struct task_struct *tsk)
2908c2ecf20Sopenharmony_ci{
2918c2ecf20Sopenharmony_ci	/* no-op */
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_cistatic inline void lose_fpu(int save)
2958c2ecf20Sopenharmony_ci{
2968c2ecf20Sopenharmony_ci	/* no-op */
2978c2ecf20Sopenharmony_ci}
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_cistatic inline bool init_fp_ctx(struct task_struct *target)
3008c2ecf20Sopenharmony_ci{
3018c2ecf20Sopenharmony_ci	return false;
3028c2ecf20Sopenharmony_ci}
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci/*
3058c2ecf20Sopenharmony_ci * The following functions should only be called in paths where we know that FP
3068c2ecf20Sopenharmony_ci * support is enabled, typically a path where own_fpu() or __enable_fpu() have
3078c2ecf20Sopenharmony_ci * returned successfully. When CONFIG_MIPS_FP_SUPPORT=n it is known at compile
3088c2ecf20Sopenharmony_ci * time that this should never happen, so calls to these functions should be
3098c2ecf20Sopenharmony_ci * optimized away & never actually be emitted.
3108c2ecf20Sopenharmony_ci */
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ciextern void save_fp(struct task_struct *tsk)
3138c2ecf20Sopenharmony_ci	__compiletime_error("save_fp() should not be called when CONFIG_MIPS_FP_SUPPORT=n");
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_ciextern void _save_fp(struct task_struct *)
3168c2ecf20Sopenharmony_ci	__compiletime_error("_save_fp() should not be called when CONFIG_MIPS_FP_SUPPORT=n");
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ciextern void restore_fp(struct task_struct *tsk)
3198c2ecf20Sopenharmony_ci	__compiletime_error("restore_fp() should not be called when CONFIG_MIPS_FP_SUPPORT=n");
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ciextern void _restore_fp(struct task_struct *)
3228c2ecf20Sopenharmony_ci	__compiletime_error("_restore_fp() should not be called when CONFIG_MIPS_FP_SUPPORT=n");
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ciextern union fpureg *get_fpu_regs(struct task_struct *tsk)
3258c2ecf20Sopenharmony_ci	__compiletime_error("get_fpu_regs() should not be called when CONFIG_MIPS_FP_SUPPORT=n");
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci#endif /* !CONFIG_MIPS_FP_SUPPORT */
3288c2ecf20Sopenharmony_ci#endif /* _ASM_FPU_H */
329