xref: /openbmc/linux/arch/mips/include/asm/fpu.h (revision 232b0b08)
1 /*
2  * Copyright (C) 2002 MontaVista Software Inc.
3  * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation;  either version 2 of the  License, or (at your
8  * option) any later version.
9  */
10 #ifndef _ASM_FPU_H
11 #define _ASM_FPU_H
12 
13 #include <linux/sched.h>
14 #include <linux/sched/task_stack.h>
15 #include <linux/ptrace.h>
16 #include <linux/thread_info.h>
17 #include <linux/bitops.h>
18 
19 #include <asm/mipsregs.h>
20 #include <asm/cpu.h>
21 #include <asm/cpu-features.h>
22 #include <asm/fpu_emulator.h>
23 #include <asm/hazards.h>
24 #include <asm/processor.h>
25 #include <asm/current.h>
26 #include <asm/msa.h>
27 
28 #ifdef CONFIG_MIPS_MT_FPAFF
29 #include <asm/mips_mt.h>
30 #endif
31 
32 struct sigcontext;
33 struct sigcontext32;
34 
35 extern void _init_fpu(unsigned int);
36 extern void _save_fp(struct task_struct *);
37 extern void _restore_fp(struct task_struct *);
38 
39 /*
40  * This enum specifies a mode in which we want the FPU to operate, for cores
41  * which implement the Status.FR bit. Note that the bottom bit of the value
42  * purposefully matches the desired value of the Status.FR bit.
43  */
44 enum fpu_mode {
45 	FPU_32BIT = 0,		/* FR = 0 */
46 	FPU_64BIT,		/* FR = 1, FRE = 0 */
47 	FPU_AS_IS,
48 	FPU_HYBRID,		/* FR = 1, FRE = 1 */
49 
50 #define FPU_FR_MASK		0x1
51 };
52 
53 #define __disable_fpu()							\
54 do {									\
55 	clear_c0_status(ST0_CU1);					\
56 	disable_fpu_hazard();						\
57 } while (0)
58 
59 static inline int __enable_fpu(enum fpu_mode mode)
60 {
61 	int fr;
62 
63 	switch (mode) {
64 	case FPU_AS_IS:
65 		/* just enable the FPU in its current mode */
66 		set_c0_status(ST0_CU1);
67 		enable_fpu_hazard();
68 		return 0;
69 
70 	case FPU_HYBRID:
71 		if (!cpu_has_fre)
72 			return SIGFPE;
73 
74 		/* set FRE */
75 		set_c0_config5(MIPS_CONF5_FRE);
76 		goto fr_common;
77 
78 	case FPU_64BIT:
79 #if !(defined(CONFIG_CPU_MIPSR2) || defined(CONFIG_CPU_MIPSR6) \
80       || defined(CONFIG_64BIT))
81 		/* we only have a 32-bit FPU */
82 		return SIGFPE;
83 #endif
84 		/* fall through */
85 	case FPU_32BIT:
86 		if (cpu_has_fre) {
87 			/* clear FRE */
88 			clear_c0_config5(MIPS_CONF5_FRE);
89 		}
90 fr_common:
91 		/* set CU1 & change FR appropriately */
92 		fr = (int)mode & FPU_FR_MASK;
93 		change_c0_status(ST0_CU1 | ST0_FR, ST0_CU1 | (fr ? ST0_FR : 0));
94 		enable_fpu_hazard();
95 
96 		/* check FR has the desired value */
97 		if (!!(read_c0_status() & ST0_FR) == !!fr)
98 			return 0;
99 
100 		/* unsupported FR value */
101 		__disable_fpu();
102 		return SIGFPE;
103 
104 	default:
105 		BUG();
106 	}
107 
108 	return SIGFPE;
109 }
110 
111 #define clear_fpu_owner()	clear_thread_flag(TIF_USEDFPU)
112 
113 static inline int __is_fpu_owner(void)
114 {
115 	return test_thread_flag(TIF_USEDFPU);
116 }
117 
118 static inline int is_fpu_owner(void)
119 {
120 	return cpu_has_fpu && __is_fpu_owner();
121 }
122 
123 static inline int __own_fpu(void)
124 {
125 	enum fpu_mode mode;
126 	int ret;
127 
128 	if (test_thread_flag(TIF_HYBRID_FPREGS))
129 		mode = FPU_HYBRID;
130 	else
131 		mode = !test_thread_flag(TIF_32BIT_FPREGS);
132 
133 	ret = __enable_fpu(mode);
134 	if (ret)
135 		return ret;
136 
137 	KSTK_STATUS(current) |= ST0_CU1;
138 	if (mode == FPU_64BIT || mode == FPU_HYBRID)
139 		KSTK_STATUS(current) |= ST0_FR;
140 	else /* mode == FPU_32BIT */
141 		KSTK_STATUS(current) &= ~ST0_FR;
142 
143 	set_thread_flag(TIF_USEDFPU);
144 	return 0;
145 }
146 
147 static inline int own_fpu_inatomic(int restore)
148 {
149 	int ret = 0;
150 
151 	if (cpu_has_fpu && !__is_fpu_owner()) {
152 		ret = __own_fpu();
153 		if (restore && !ret)
154 			_restore_fp(current);
155 	}
156 	return ret;
157 }
158 
159 static inline int own_fpu(int restore)
160 {
161 	int ret;
162 
163 	preempt_disable();
164 	ret = own_fpu_inatomic(restore);
165 	preempt_enable();
166 	return ret;
167 }
168 
169 static inline void lose_fpu_inatomic(int save, struct task_struct *tsk)
170 {
171 	if (is_msa_enabled()) {
172 		if (save) {
173 			save_msa(tsk);
174 			tsk->thread.fpu.fcr31 =
175 					read_32bit_cp1_register(CP1_STATUS);
176 		}
177 		disable_msa();
178 		clear_tsk_thread_flag(tsk, TIF_USEDMSA);
179 		__disable_fpu();
180 	} else if (is_fpu_owner()) {
181 		if (save)
182 			_save_fp(tsk);
183 		__disable_fpu();
184 	} else {
185 		/* FPU should not have been left enabled with no owner */
186 		WARN(read_c0_status() & ST0_CU1,
187 		     "Orphaned FPU left enabled");
188 	}
189 	KSTK_STATUS(tsk) &= ~ST0_CU1;
190 	clear_tsk_thread_flag(tsk, TIF_USEDFPU);
191 }
192 
193 static inline void lose_fpu(int save)
194 {
195 	preempt_disable();
196 	lose_fpu_inatomic(save, current);
197 	preempt_enable();
198 }
199 
200 static inline int init_fpu(void)
201 {
202 	unsigned int fcr31 = current->thread.fpu.fcr31;
203 	int ret = 0;
204 
205 	if (cpu_has_fpu) {
206 		unsigned int config5;
207 
208 		ret = __own_fpu();
209 		if (ret)
210 			return ret;
211 
212 		if (!cpu_has_fre) {
213 			_init_fpu(fcr31);
214 
215 			return 0;
216 		}
217 
218 		/*
219 		 * Ensure FRE is clear whilst running _init_fpu, since
220 		 * single precision FP instructions are used. If FRE
221 		 * was set then we'll just end up initialising all 32
222 		 * 64b registers.
223 		 */
224 		config5 = clear_c0_config5(MIPS_CONF5_FRE);
225 		enable_fpu_hazard();
226 
227 		_init_fpu(fcr31);
228 
229 		/* Restore FRE */
230 		write_c0_config5(config5);
231 		enable_fpu_hazard();
232 	} else
233 		fpu_emulator_init_fpu();
234 
235 	return ret;
236 }
237 
238 static inline void save_fp(struct task_struct *tsk)
239 {
240 	if (cpu_has_fpu)
241 		_save_fp(tsk);
242 }
243 
244 static inline void restore_fp(struct task_struct *tsk)
245 {
246 	if (cpu_has_fpu)
247 		_restore_fp(tsk);
248 }
249 
250 static inline union fpureg *get_fpu_regs(struct task_struct *tsk)
251 {
252 	if (tsk == current) {
253 		preempt_disable();
254 		if (is_fpu_owner())
255 			_save_fp(current);
256 		preempt_enable();
257 	}
258 
259 	return tsk->thread.fpu.fpr;
260 }
261 
262 #endif /* _ASM_FPU_H */
263