diff options
Diffstat (limited to 'arch/sh/kernel/cpu/sh4/fpu.c')
-rw-r--r-- | arch/sh/kernel/cpu/sh4/fpu.c | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/arch/sh/kernel/cpu/sh4/fpu.c b/arch/sh/kernel/cpu/sh4/fpu.c new file mode 100644 index 000000000000..f486c07e10e2 --- /dev/null +++ b/arch/sh/kernel/cpu/sh4/fpu.c | |||
@@ -0,0 +1,335 @@ | |||
1 | /* $Id: fpu.c,v 1.4 2004/01/13 05:52:11 kkojima Exp $ | ||
2 | * | ||
3 | * linux/arch/sh/kernel/fpu.c | ||
4 | * | ||
5 | * Save/restore floating point context for signal handlers. | ||
6 | * | ||
7 | * This file is subject to the terms and conditions of the GNU General Public | ||
8 | * License. See the file "COPYING" in the main directory of this archive | ||
9 | * for more details. | ||
10 | * | ||
11 | * Copyright (C) 1999, 2000 Kaz Kojima & Niibe Yutaka | ||
12 | * | ||
13 | * FIXME! These routines can be optimized in big endian case. | ||
14 | */ | ||
15 | |||
16 | #include <linux/sched.h> | ||
17 | #include <linux/signal.h> | ||
18 | #include <asm/processor.h> | ||
19 | #include <asm/io.h> | ||
20 | |||
21 | /* The PR (precision) bit in the FP Status Register must be clear when | ||
22 | * an frchg instruction is executed, otherwise the instruction is undefined. | ||
23 | * Executing frchg with PR set causes a trap on some SH4 implementations. | ||
24 | */ | ||
25 | |||
26 | #define FPSCR_RCHG 0x00000000 | ||
27 | |||
28 | |||
29 | /* | ||
30 | * Save FPU registers onto task structure. | ||
31 | * Assume called with FPU enabled (SR.FD=0). | ||
32 | */ | ||
33 | void | ||
34 | save_fpu(struct task_struct *tsk, struct pt_regs *regs) | ||
35 | { | ||
36 | unsigned long dummy; | ||
37 | |||
38 | clear_tsk_thread_flag(tsk, TIF_USEDFPU); | ||
39 | enable_fpu(); | ||
40 | asm volatile("sts.l fpul, @-%0\n\t" | ||
41 | "sts.l fpscr, @-%0\n\t" | ||
42 | "lds %2, fpscr\n\t" | ||
43 | "frchg\n\t" | ||
44 | "fmov.s fr15, @-%0\n\t" | ||
45 | "fmov.s fr14, @-%0\n\t" | ||
46 | "fmov.s fr13, @-%0\n\t" | ||
47 | "fmov.s fr12, @-%0\n\t" | ||
48 | "fmov.s fr11, @-%0\n\t" | ||
49 | "fmov.s fr10, @-%0\n\t" | ||
50 | "fmov.s fr9, @-%0\n\t" | ||
51 | "fmov.s fr8, @-%0\n\t" | ||
52 | "fmov.s fr7, @-%0\n\t" | ||
53 | "fmov.s fr6, @-%0\n\t" | ||
54 | "fmov.s fr5, @-%0\n\t" | ||
55 | "fmov.s fr4, @-%0\n\t" | ||
56 | "fmov.s fr3, @-%0\n\t" | ||
57 | "fmov.s fr2, @-%0\n\t" | ||
58 | "fmov.s fr1, @-%0\n\t" | ||
59 | "fmov.s fr0, @-%0\n\t" | ||
60 | "frchg\n\t" | ||
61 | "fmov.s fr15, @-%0\n\t" | ||
62 | "fmov.s fr14, @-%0\n\t" | ||
63 | "fmov.s fr13, @-%0\n\t" | ||
64 | "fmov.s fr12, @-%0\n\t" | ||
65 | "fmov.s fr11, @-%0\n\t" | ||
66 | "fmov.s fr10, @-%0\n\t" | ||
67 | "fmov.s fr9, @-%0\n\t" | ||
68 | "fmov.s fr8, @-%0\n\t" | ||
69 | "fmov.s fr7, @-%0\n\t" | ||
70 | "fmov.s fr6, @-%0\n\t" | ||
71 | "fmov.s fr5, @-%0\n\t" | ||
72 | "fmov.s fr4, @-%0\n\t" | ||
73 | "fmov.s fr3, @-%0\n\t" | ||
74 | "fmov.s fr2, @-%0\n\t" | ||
75 | "fmov.s fr1, @-%0\n\t" | ||
76 | "fmov.s fr0, @-%0\n\t" | ||
77 | "lds %3, fpscr\n\t" | ||
78 | : "=r" (dummy) | ||
79 | : "0" ((char *)(&tsk->thread.fpu.hard.status)), | ||
80 | "r" (FPSCR_RCHG), | ||
81 | "r" (FPSCR_INIT) | ||
82 | : "memory"); | ||
83 | |||
84 | disable_fpu(); | ||
85 | release_fpu(regs); | ||
86 | } | ||
87 | |||
88 | static void | ||
89 | restore_fpu(struct task_struct *tsk) | ||
90 | { | ||
91 | unsigned long dummy; | ||
92 | |||
93 | enable_fpu(); | ||
94 | asm volatile("lds %2, fpscr\n\t" | ||
95 | "fmov.s @%0+, fr0\n\t" | ||
96 | "fmov.s @%0+, fr1\n\t" | ||
97 | "fmov.s @%0+, fr2\n\t" | ||
98 | "fmov.s @%0+, fr3\n\t" | ||
99 | "fmov.s @%0+, fr4\n\t" | ||
100 | "fmov.s @%0+, fr5\n\t" | ||
101 | "fmov.s @%0+, fr6\n\t" | ||
102 | "fmov.s @%0+, fr7\n\t" | ||
103 | "fmov.s @%0+, fr8\n\t" | ||
104 | "fmov.s @%0+, fr9\n\t" | ||
105 | "fmov.s @%0+, fr10\n\t" | ||
106 | "fmov.s @%0+, fr11\n\t" | ||
107 | "fmov.s @%0+, fr12\n\t" | ||
108 | "fmov.s @%0+, fr13\n\t" | ||
109 | "fmov.s @%0+, fr14\n\t" | ||
110 | "fmov.s @%0+, fr15\n\t" | ||
111 | "frchg\n\t" | ||
112 | "fmov.s @%0+, fr0\n\t" | ||
113 | "fmov.s @%0+, fr1\n\t" | ||
114 | "fmov.s @%0+, fr2\n\t" | ||
115 | "fmov.s @%0+, fr3\n\t" | ||
116 | "fmov.s @%0+, fr4\n\t" | ||
117 | "fmov.s @%0+, fr5\n\t" | ||
118 | "fmov.s @%0+, fr6\n\t" | ||
119 | "fmov.s @%0+, fr7\n\t" | ||
120 | "fmov.s @%0+, fr8\n\t" | ||
121 | "fmov.s @%0+, fr9\n\t" | ||
122 | "fmov.s @%0+, fr10\n\t" | ||
123 | "fmov.s @%0+, fr11\n\t" | ||
124 | "fmov.s @%0+, fr12\n\t" | ||
125 | "fmov.s @%0+, fr13\n\t" | ||
126 | "fmov.s @%0+, fr14\n\t" | ||
127 | "fmov.s @%0+, fr15\n\t" | ||
128 | "frchg\n\t" | ||
129 | "lds.l @%0+, fpscr\n\t" | ||
130 | "lds.l @%0+, fpul\n\t" | ||
131 | : "=r" (dummy) | ||
132 | : "0" (&tsk->thread.fpu), "r" (FPSCR_RCHG) | ||
133 | : "memory"); | ||
134 | disable_fpu(); | ||
135 | } | ||
136 | |||
137 | /* | ||
138 | * Load the FPU with signalling NANS. This bit pattern we're using | ||
139 | * has the property that no matter wether considered as single or as | ||
140 | * double precission represents signaling NANS. | ||
141 | */ | ||
142 | |||
143 | static void | ||
144 | fpu_init(void) | ||
145 | { | ||
146 | enable_fpu(); | ||
147 | asm volatile("lds %0, fpul\n\t" | ||
148 | "lds %1, fpscr\n\t" | ||
149 | "fsts fpul, fr0\n\t" | ||
150 | "fsts fpul, fr1\n\t" | ||
151 | "fsts fpul, fr2\n\t" | ||
152 | "fsts fpul, fr3\n\t" | ||
153 | "fsts fpul, fr4\n\t" | ||
154 | "fsts fpul, fr5\n\t" | ||
155 | "fsts fpul, fr6\n\t" | ||
156 | "fsts fpul, fr7\n\t" | ||
157 | "fsts fpul, fr8\n\t" | ||
158 | "fsts fpul, fr9\n\t" | ||
159 | "fsts fpul, fr10\n\t" | ||
160 | "fsts fpul, fr11\n\t" | ||
161 | "fsts fpul, fr12\n\t" | ||
162 | "fsts fpul, fr13\n\t" | ||
163 | "fsts fpul, fr14\n\t" | ||
164 | "fsts fpul, fr15\n\t" | ||
165 | "frchg\n\t" | ||
166 | "fsts fpul, fr0\n\t" | ||
167 | "fsts fpul, fr1\n\t" | ||
168 | "fsts fpul, fr2\n\t" | ||
169 | "fsts fpul, fr3\n\t" | ||
170 | "fsts fpul, fr4\n\t" | ||
171 | "fsts fpul, fr5\n\t" | ||
172 | "fsts fpul, fr6\n\t" | ||
173 | "fsts fpul, fr7\n\t" | ||
174 | "fsts fpul, fr8\n\t" | ||
175 | "fsts fpul, fr9\n\t" | ||
176 | "fsts fpul, fr10\n\t" | ||
177 | "fsts fpul, fr11\n\t" | ||
178 | "fsts fpul, fr12\n\t" | ||
179 | "fsts fpul, fr13\n\t" | ||
180 | "fsts fpul, fr14\n\t" | ||
181 | "fsts fpul, fr15\n\t" | ||
182 | "frchg\n\t" | ||
183 | "lds %2, fpscr\n\t" | ||
184 | : /* no output */ | ||
185 | : "r" (0), "r" (FPSCR_RCHG), "r" (FPSCR_INIT)); | ||
186 | disable_fpu(); | ||
187 | } | ||
188 | |||
189 | /** | ||
190 | * denormal_to_double - Given denormalized float number, | ||
191 | * store double float | ||
192 | * | ||
193 | * @fpu: Pointer to sh_fpu_hard structure | ||
194 | * @n: Index to FP register | ||
195 | */ | ||
196 | static void | ||
197 | denormal_to_double (struct sh_fpu_hard_struct *fpu, int n) | ||
198 | { | ||
199 | unsigned long du, dl; | ||
200 | unsigned long x = fpu->fpul; | ||
201 | int exp = 1023 - 126; | ||
202 | |||
203 | if (x != 0 && (x & 0x7f800000) == 0) { | ||
204 | du = (x & 0x80000000); | ||
205 | while ((x & 0x00800000) == 0) { | ||
206 | x <<= 1; | ||
207 | exp--; | ||
208 | } | ||
209 | x &= 0x007fffff; | ||
210 | du |= (exp << 20) | (x >> 3); | ||
211 | dl = x << 29; | ||
212 | |||
213 | fpu->fp_regs[n] = du; | ||
214 | fpu->fp_regs[n+1] = dl; | ||
215 | } | ||
216 | } | ||
217 | |||
218 | /** | ||
219 | * ieee_fpe_handler - Handle denormalized number exception | ||
220 | * | ||
221 | * @regs: Pointer to register structure | ||
222 | * | ||
223 | * Returns 1 when it's handled (should not cause exception). | ||
224 | */ | ||
225 | static int | ||
226 | ieee_fpe_handler (struct pt_regs *regs) | ||
227 | { | ||
228 | unsigned short insn = *(unsigned short *) regs->pc; | ||
229 | unsigned short finsn; | ||
230 | unsigned long nextpc; | ||
231 | int nib[4] = { | ||
232 | (insn >> 12) & 0xf, | ||
233 | (insn >> 8) & 0xf, | ||
234 | (insn >> 4) & 0xf, | ||
235 | insn & 0xf}; | ||
236 | |||
237 | if (nib[0] == 0xb || | ||
238 | (nib[0] == 0x4 && nib[2] == 0x0 && nib[3] == 0xb)) /* bsr & jsr */ | ||
239 | regs->pr = regs->pc + 4; | ||
240 | |||
241 | if (nib[0] == 0xa || nib[0] == 0xb) { /* bra & bsr */ | ||
242 | nextpc = regs->pc + 4 + ((short) ((insn & 0xfff) << 4) >> 3); | ||
243 | finsn = *(unsigned short *) (regs->pc + 2); | ||
244 | } else if (nib[0] == 0x8 && nib[1] == 0xd) { /* bt/s */ | ||
245 | if (regs->sr & 1) | ||
246 | nextpc = regs->pc + 4 + ((char) (insn & 0xff) << 1); | ||
247 | else | ||
248 | nextpc = regs->pc + 4; | ||
249 | finsn = *(unsigned short *) (regs->pc + 2); | ||
250 | } else if (nib[0] == 0x8 && nib[1] == 0xf) { /* bf/s */ | ||
251 | if (regs->sr & 1) | ||
252 | nextpc = regs->pc + 4; | ||
253 | else | ||
254 | nextpc = regs->pc + 4 + ((char) (insn & 0xff) << 1); | ||
255 | finsn = *(unsigned short *) (regs->pc + 2); | ||
256 | } else if (nib[0] == 0x4 && nib[3] == 0xb && | ||
257 | (nib[2] == 0x0 || nib[2] == 0x2)) { /* jmp & jsr */ | ||
258 | nextpc = regs->regs[nib[1]]; | ||
259 | finsn = *(unsigned short *) (regs->pc + 2); | ||
260 | } else if (nib[0] == 0x0 && nib[3] == 0x3 && | ||
261 | (nib[2] == 0x0 || nib[2] == 0x2)) { /* braf & bsrf */ | ||
262 | nextpc = regs->pc + 4 + regs->regs[nib[1]]; | ||
263 | finsn = *(unsigned short *) (regs->pc + 2); | ||
264 | } else if (insn == 0x000b) { /* rts */ | ||
265 | nextpc = regs->pr; | ||
266 | finsn = *(unsigned short *) (regs->pc + 2); | ||
267 | } else { | ||
268 | nextpc = regs->pc + 2; | ||
269 | finsn = insn; | ||
270 | } | ||
271 | |||
272 | if ((finsn & 0xf1ff) == 0xf0ad) { /* fcnvsd */ | ||
273 | struct task_struct *tsk = current; | ||
274 | |||
275 | save_fpu(tsk, regs); | ||
276 | if ((tsk->thread.fpu.hard.fpscr & (1 << 17))) { | ||
277 | /* FPU error */ | ||
278 | denormal_to_double (&tsk->thread.fpu.hard, | ||
279 | (finsn >> 8) & 0xf); | ||
280 | tsk->thread.fpu.hard.fpscr &= | ||
281 | ~(FPSCR_CAUSE_MASK | FPSCR_FLAG_MASK); | ||
282 | grab_fpu(regs); | ||
283 | restore_fpu(tsk); | ||
284 | set_tsk_thread_flag(tsk, TIF_USEDFPU); | ||
285 | } else { | ||
286 | tsk->thread.trap_no = 11; | ||
287 | tsk->thread.error_code = 0; | ||
288 | force_sig(SIGFPE, tsk); | ||
289 | } | ||
290 | |||
291 | regs->pc = nextpc; | ||
292 | return 1; | ||
293 | } | ||
294 | |||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | asmlinkage void | ||
299 | do_fpu_error(unsigned long r4, unsigned long r5, unsigned long r6, unsigned long r7, | ||
300 | struct pt_regs regs) | ||
301 | { | ||
302 | struct task_struct *tsk = current; | ||
303 | |||
304 | if (ieee_fpe_handler (®s)) | ||
305 | return; | ||
306 | |||
307 | regs.pc += 2; | ||
308 | save_fpu(tsk, ®s); | ||
309 | tsk->thread.trap_no = 11; | ||
310 | tsk->thread.error_code = 0; | ||
311 | force_sig(SIGFPE, tsk); | ||
312 | } | ||
313 | |||
314 | asmlinkage void | ||
315 | do_fpu_state_restore(unsigned long r4, unsigned long r5, unsigned long r6, | ||
316 | unsigned long r7, struct pt_regs regs) | ||
317 | { | ||
318 | struct task_struct *tsk = current; | ||
319 | |||
320 | grab_fpu(®s); | ||
321 | if (!user_mode(®s)) { | ||
322 | printk(KERN_ERR "BUG: FPU is used in kernel mode.\n"); | ||
323 | return; | ||
324 | } | ||
325 | |||
326 | if (used_math()) { | ||
327 | /* Using the FPU again. */ | ||
328 | restore_fpu(tsk); | ||
329 | } else { | ||
330 | /* First time FPU user. */ | ||
331 | fpu_init(); | ||
332 | set_used_math(); | ||
333 | } | ||
334 | set_tsk_thread_flag(tsk, TIF_USEDFPU); | ||
335 | } | ||