]>
Commit | Line | Data |
---|---|---|
c8c0a1ab | 1 | /* |
1da177e4 LT |
2 | * Save/restore floating point context for signal handlers. |
3 | * | |
4 | * This file is subject to the terms and conditions of the GNU General Public | |
5 | * License. See the file "COPYING" in the main directory of this archive | |
6 | * for more details. | |
7 | * | |
8 | * Copyright (C) 1999, 2000 Kaz Kojima & Niibe Yutaka | |
c8c0a1ab | 9 | * Copyright (C) 2006 ST Microelectronics Ltd. (denorm support) |
1da177e4 | 10 | * |
c8c0a1ab | 11 | * FIXME! These routines have not been tested for big endian case. |
1da177e4 | 12 | */ |
c3edc401 | 13 | #include <linux/sched/signal.h> |
c8c0a1ab | 14 | #include <linux/io.h> |
f15cbe6f | 15 | #include <cpu/fpu.h> |
1da177e4 | 16 | #include <asm/processor.h> |
9bbafce2 | 17 | #include <asm/fpu.h> |
f03c4866 | 18 | #include <asm/traps.h> |
1da177e4 LT |
19 | |
20 | /* The PR (precision) bit in the FP Status Register must be clear when | |
21 | * an frchg instruction is executed, otherwise the instruction is undefined. | |
22 | * Executing frchg with PR set causes a trap on some SH4 implementations. | |
23 | */ | |
24 | ||
25 | #define FPSCR_RCHG 0x00000000 | |
c8c0a1ab SM |
26 | extern unsigned long long float64_div(unsigned long long a, |
27 | unsigned long long b); | |
28 | extern unsigned long int float32_div(unsigned long int a, unsigned long int b); | |
29 | extern unsigned long long float64_mul(unsigned long long a, | |
30 | unsigned long long b); | |
31 | extern unsigned long int float32_mul(unsigned long int a, unsigned long int b); | |
32 | extern unsigned long long float64_add(unsigned long long a, | |
33 | unsigned long long b); | |
34 | extern unsigned long int float32_add(unsigned long int a, unsigned long int b); | |
35 | extern unsigned long long float64_sub(unsigned long long a, | |
36 | unsigned long long b); | |
37 | extern unsigned long int float32_sub(unsigned long int a, unsigned long int b); | |
b6ad1e8c | 38 | extern unsigned long int float64_to_float32(unsigned long long a); |
c8c0a1ab | 39 | static unsigned int fpu_exception_flags; |
1da177e4 LT |
40 | |
41 | /* | |
42 | * Save FPU registers onto task structure. | |
1da177e4 | 43 | */ |
d3ea9fa0 | 44 | void save_fpu(struct task_struct *tsk) |
1da177e4 LT |
45 | { |
46 | unsigned long dummy; | |
47 | ||
1da177e4 | 48 | enable_fpu(); |
c8c0a1ab SM |
49 | asm volatile ("sts.l fpul, @-%0\n\t" |
50 | "sts.l fpscr, @-%0\n\t" | |
51 | "lds %2, fpscr\n\t" | |
52 | "frchg\n\t" | |
53 | "fmov.s fr15, @-%0\n\t" | |
54 | "fmov.s fr14, @-%0\n\t" | |
55 | "fmov.s fr13, @-%0\n\t" | |
56 | "fmov.s fr12, @-%0\n\t" | |
57 | "fmov.s fr11, @-%0\n\t" | |
58 | "fmov.s fr10, @-%0\n\t" | |
59 | "fmov.s fr9, @-%0\n\t" | |
60 | "fmov.s fr8, @-%0\n\t" | |
61 | "fmov.s fr7, @-%0\n\t" | |
62 | "fmov.s fr6, @-%0\n\t" | |
63 | "fmov.s fr5, @-%0\n\t" | |
64 | "fmov.s fr4, @-%0\n\t" | |
65 | "fmov.s fr3, @-%0\n\t" | |
66 | "fmov.s fr2, @-%0\n\t" | |
67 | "fmov.s fr1, @-%0\n\t" | |
68 | "fmov.s fr0, @-%0\n\t" | |
69 | "frchg\n\t" | |
70 | "fmov.s fr15, @-%0\n\t" | |
71 | "fmov.s fr14, @-%0\n\t" | |
72 | "fmov.s fr13, @-%0\n\t" | |
73 | "fmov.s fr12, @-%0\n\t" | |
74 | "fmov.s fr11, @-%0\n\t" | |
75 | "fmov.s fr10, @-%0\n\t" | |
76 | "fmov.s fr9, @-%0\n\t" | |
77 | "fmov.s fr8, @-%0\n\t" | |
78 | "fmov.s fr7, @-%0\n\t" | |
79 | "fmov.s fr6, @-%0\n\t" | |
80 | "fmov.s fr5, @-%0\n\t" | |
81 | "fmov.s fr4, @-%0\n\t" | |
82 | "fmov.s fr3, @-%0\n\t" | |
83 | "fmov.s fr2, @-%0\n\t" | |
84 | "fmov.s fr1, @-%0\n\t" | |
85 | "fmov.s fr0, @-%0\n\t" | |
86 | "lds %3, fpscr\n\t":"=r" (dummy) | |
0ea820cf | 87 | :"0"((char *)(&tsk->thread.xstate->hardfpu.status)), |
c8c0a1ab SM |
88 | "r"(FPSCR_RCHG), "r"(FPSCR_INIT) |
89 | :"memory"); | |
1da177e4 | 90 | |
74d99a5e | 91 | disable_fpu(); |
1da177e4 LT |
92 | } |
93 | ||
0ea820cf | 94 | void restore_fpu(struct task_struct *tsk) |
1da177e4 LT |
95 | { |
96 | unsigned long dummy; | |
97 | ||
74d99a5e | 98 | enable_fpu(); |
c8c0a1ab SM |
99 | asm volatile ("lds %2, fpscr\n\t" |
100 | "fmov.s @%0+, fr0\n\t" | |
101 | "fmov.s @%0+, fr1\n\t" | |
102 | "fmov.s @%0+, fr2\n\t" | |
103 | "fmov.s @%0+, fr3\n\t" | |
104 | "fmov.s @%0+, fr4\n\t" | |
105 | "fmov.s @%0+, fr5\n\t" | |
106 | "fmov.s @%0+, fr6\n\t" | |
107 | "fmov.s @%0+, fr7\n\t" | |
108 | "fmov.s @%0+, fr8\n\t" | |
109 | "fmov.s @%0+, fr9\n\t" | |
110 | "fmov.s @%0+, fr10\n\t" | |
111 | "fmov.s @%0+, fr11\n\t" | |
112 | "fmov.s @%0+, fr12\n\t" | |
113 | "fmov.s @%0+, fr13\n\t" | |
114 | "fmov.s @%0+, fr14\n\t" | |
115 | "fmov.s @%0+, fr15\n\t" | |
116 | "frchg\n\t" | |
117 | "fmov.s @%0+, fr0\n\t" | |
118 | "fmov.s @%0+, fr1\n\t" | |
119 | "fmov.s @%0+, fr2\n\t" | |
120 | "fmov.s @%0+, fr3\n\t" | |
121 | "fmov.s @%0+, fr4\n\t" | |
122 | "fmov.s @%0+, fr5\n\t" | |
123 | "fmov.s @%0+, fr6\n\t" | |
124 | "fmov.s @%0+, fr7\n\t" | |
125 | "fmov.s @%0+, fr8\n\t" | |
126 | "fmov.s @%0+, fr9\n\t" | |
127 | "fmov.s @%0+, fr10\n\t" | |
128 | "fmov.s @%0+, fr11\n\t" | |
129 | "fmov.s @%0+, fr12\n\t" | |
130 | "fmov.s @%0+, fr13\n\t" | |
131 | "fmov.s @%0+, fr14\n\t" | |
132 | "fmov.s @%0+, fr15\n\t" | |
133 | "frchg\n\t" | |
134 | "lds.l @%0+, fpscr\n\t" | |
135 | "lds.l @%0+, fpul\n\t" | |
136 | :"=r" (dummy) | |
0ea820cf | 137 | :"0" (tsk->thread.xstate), "r" (FPSCR_RCHG) |
c8c0a1ab | 138 | :"memory"); |
1da177e4 LT |
139 | disable_fpu(); |
140 | } | |
141 | ||
1da177e4 | 142 | /** |
c8c0a1ab SM |
143 | * denormal_to_double - Given denormalized float number, |
144 | * store double float | |
1da177e4 | 145 | * |
c8c0a1ab SM |
146 | * @fpu: Pointer to sh_fpu_hard structure |
147 | * @n: Index to FP register | |
1da177e4 | 148 | */ |
c8c0a1ab | 149 | static void denormal_to_double(struct sh_fpu_hard_struct *fpu, int n) |
1da177e4 LT |
150 | { |
151 | unsigned long du, dl; | |
152 | unsigned long x = fpu->fpul; | |
153 | int exp = 1023 - 126; | |
154 | ||
155 | if (x != 0 && (x & 0x7f800000) == 0) { | |
156 | du = (x & 0x80000000); | |
157 | while ((x & 0x00800000) == 0) { | |
158 | x <<= 1; | |
159 | exp--; | |
160 | } | |
161 | x &= 0x007fffff; | |
162 | du |= (exp << 20) | (x >> 3); | |
163 | dl = x << 29; | |
164 | ||
165 | fpu->fp_regs[n] = du; | |
c8c0a1ab | 166 | fpu->fp_regs[n + 1] = dl; |
1da177e4 LT |
167 | } |
168 | } | |
169 | ||
170 | /** | |
171 | * ieee_fpe_handler - Handle denormalized number exception | |
172 | * | |
173 | * @regs: Pointer to register structure | |
174 | * | |
175 | * Returns 1 when it's handled (should not cause exception). | |
176 | */ | |
c8c0a1ab | 177 | static int ieee_fpe_handler(struct pt_regs *regs) |
1da177e4 | 178 | { |
c8c0a1ab | 179 | unsigned short insn = *(unsigned short *)regs->pc; |
1da177e4 LT |
180 | unsigned short finsn; |
181 | unsigned long nextpc; | |
182 | int nib[4] = { | |
183 | (insn >> 12) & 0xf, | |
184 | (insn >> 8) & 0xf, | |
185 | (insn >> 4) & 0xf, | |
c8c0a1ab SM |
186 | insn & 0xf |
187 | }; | |
188 | ||
189 | if (nib[0] == 0xb || (nib[0] == 0x4 && nib[2] == 0x0 && nib[3] == 0xb)) | |
190 | regs->pr = regs->pc + 4; /* bsr & jsr */ | |
191 | ||
192 | if (nib[0] == 0xa || nib[0] == 0xb) { | |
193 | /* bra & bsr */ | |
194 | nextpc = regs->pc + 4 + ((short)((insn & 0xfff) << 4) >> 3); | |
195 | finsn = *(unsigned short *)(regs->pc + 2); | |
196 | } else if (nib[0] == 0x8 && nib[1] == 0xd) { | |
197 | /* bt/s */ | |
1da177e4 | 198 | if (regs->sr & 1) |
c8c0a1ab | 199 | nextpc = regs->pc + 4 + ((char)(insn & 0xff) << 1); |
1da177e4 LT |
200 | else |
201 | nextpc = regs->pc + 4; | |
c8c0a1ab SM |
202 | finsn = *(unsigned short *)(regs->pc + 2); |
203 | } else if (nib[0] == 0x8 && nib[1] == 0xf) { | |
204 | /* bf/s */ | |
1da177e4 LT |
205 | if (regs->sr & 1) |
206 | nextpc = regs->pc + 4; | |
207 | else | |
c8c0a1ab SM |
208 | nextpc = regs->pc + 4 + ((char)(insn & 0xff) << 1); |
209 | finsn = *(unsigned short *)(regs->pc + 2); | |
1da177e4 | 210 | } else if (nib[0] == 0x4 && nib[3] == 0xb && |
c8c0a1ab SM |
211 | (nib[2] == 0x0 || nib[2] == 0x2)) { |
212 | /* jmp & jsr */ | |
1da177e4 | 213 | nextpc = regs->regs[nib[1]]; |
c8c0a1ab | 214 | finsn = *(unsigned short *)(regs->pc + 2); |
1da177e4 | 215 | } else if (nib[0] == 0x0 && nib[3] == 0x3 && |
c8c0a1ab SM |
216 | (nib[2] == 0x0 || nib[2] == 0x2)) { |
217 | /* braf & bsrf */ | |
1da177e4 | 218 | nextpc = regs->pc + 4 + regs->regs[nib[1]]; |
c8c0a1ab SM |
219 | finsn = *(unsigned short *)(regs->pc + 2); |
220 | } else if (insn == 0x000b) { | |
221 | /* rts */ | |
1da177e4 | 222 | nextpc = regs->pr; |
c8c0a1ab | 223 | finsn = *(unsigned short *)(regs->pc + 2); |
1da177e4 | 224 | } else { |
53f983a9 | 225 | nextpc = regs->pc + instruction_size(insn); |
1da177e4 LT |
226 | finsn = insn; |
227 | } | |
228 | ||
c8c0a1ab SM |
229 | if ((finsn & 0xf1ff) == 0xf0ad) { |
230 | /* fcnvsd */ | |
1da177e4 LT |
231 | struct task_struct *tsk = current; |
232 | ||
0ea820cf | 233 | if ((tsk->thread.xstate->hardfpu.fpscr & FPSCR_CAUSE_ERROR)) |
1da177e4 | 234 | /* FPU error */ |
0ea820cf | 235 | denormal_to_double(&tsk->thread.xstate->hardfpu, |
c8c0a1ab SM |
236 | (finsn >> 8) & 0xf); |
237 | else | |
238 | return 0; | |
239 | ||
240 | regs->pc = nextpc; | |
241 | return 1; | |
242 | } else if ((finsn & 0xf00f) == 0xf002) { | |
243 | /* fmul */ | |
244 | struct task_struct *tsk = current; | |
245 | int fpscr; | |
246 | int n, m, prec; | |
247 | unsigned int hx, hy; | |
248 | ||
249 | n = (finsn >> 8) & 0xf; | |
250 | m = (finsn >> 4) & 0xf; | |
0ea820cf PM |
251 | hx = tsk->thread.xstate->hardfpu.fp_regs[n]; |
252 | hy = tsk->thread.xstate->hardfpu.fp_regs[m]; | |
253 | fpscr = tsk->thread.xstate->hardfpu.fpscr; | |
c8c0a1ab SM |
254 | prec = fpscr & FPSCR_DBL_PRECISION; |
255 | ||
256 | if ((fpscr & FPSCR_CAUSE_ERROR) | |
257 | && (prec && ((hx & 0x7fffffff) < 0x00100000 | |
258 | || (hy & 0x7fffffff) < 0x00100000))) { | |
259 | long long llx, lly; | |
260 | ||
261 | /* FPU error because of denormal (doubles) */ | |
262 | llx = ((long long)hx << 32) | |
0ea820cf | 263 | | tsk->thread.xstate->hardfpu.fp_regs[n + 1]; |
c8c0a1ab | 264 | lly = ((long long)hy << 32) |
0ea820cf | 265 | | tsk->thread.xstate->hardfpu.fp_regs[m + 1]; |
c8c0a1ab | 266 | llx = float64_mul(llx, lly); |
0ea820cf PM |
267 | tsk->thread.xstate->hardfpu.fp_regs[n] = llx >> 32; |
268 | tsk->thread.xstate->hardfpu.fp_regs[n + 1] = llx & 0xffffffff; | |
c8c0a1ab SM |
269 | } else if ((fpscr & FPSCR_CAUSE_ERROR) |
270 | && (!prec && ((hx & 0x7fffffff) < 0x00800000 | |
271 | || (hy & 0x7fffffff) < 0x00800000))) { | |
272 | /* FPU error because of denormal (floats) */ | |
273 | hx = float32_mul(hx, hy); | |
0ea820cf | 274 | tsk->thread.xstate->hardfpu.fp_regs[n] = hx; |
c8c0a1ab SM |
275 | } else |
276 | return 0; | |
277 | ||
278 | regs->pc = nextpc; | |
279 | return 1; | |
280 | } else if ((finsn & 0xf00e) == 0xf000) { | |
281 | /* fadd, fsub */ | |
282 | struct task_struct *tsk = current; | |
283 | int fpscr; | |
284 | int n, m, prec; | |
285 | unsigned int hx, hy; | |
286 | ||
287 | n = (finsn >> 8) & 0xf; | |
288 | m = (finsn >> 4) & 0xf; | |
0ea820cf PM |
289 | hx = tsk->thread.xstate->hardfpu.fp_regs[n]; |
290 | hy = tsk->thread.xstate->hardfpu.fp_regs[m]; | |
291 | fpscr = tsk->thread.xstate->hardfpu.fpscr; | |
c8c0a1ab SM |
292 | prec = fpscr & FPSCR_DBL_PRECISION; |
293 | ||
294 | if ((fpscr & FPSCR_CAUSE_ERROR) | |
295 | && (prec && ((hx & 0x7fffffff) < 0x00100000 | |
296 | || (hy & 0x7fffffff) < 0x00100000))) { | |
297 | long long llx, lly; | |
298 | ||
299 | /* FPU error because of denormal (doubles) */ | |
300 | llx = ((long long)hx << 32) | |
0ea820cf | 301 | | tsk->thread.xstate->hardfpu.fp_regs[n + 1]; |
c8c0a1ab | 302 | lly = ((long long)hy << 32) |
0ea820cf | 303 | | tsk->thread.xstate->hardfpu.fp_regs[m + 1]; |
c8c0a1ab SM |
304 | if ((finsn & 0xf00f) == 0xf000) |
305 | llx = float64_add(llx, lly); | |
306 | else | |
307 | llx = float64_sub(llx, lly); | |
0ea820cf PM |
308 | tsk->thread.xstate->hardfpu.fp_regs[n] = llx >> 32; |
309 | tsk->thread.xstate->hardfpu.fp_regs[n + 1] = llx & 0xffffffff; | |
c8c0a1ab SM |
310 | } else if ((fpscr & FPSCR_CAUSE_ERROR) |
311 | && (!prec && ((hx & 0x7fffffff) < 0x00800000 | |
312 | || (hy & 0x7fffffff) < 0x00800000))) { | |
313 | /* FPU error because of denormal (floats) */ | |
314 | if ((finsn & 0xf00f) == 0xf000) | |
315 | hx = float32_add(hx, hy); | |
316 | else | |
317 | hx = float32_sub(hx, hy); | |
0ea820cf | 318 | tsk->thread.xstate->hardfpu.fp_regs[n] = hx; |
c8c0a1ab SM |
319 | } else |
320 | return 0; | |
321 | ||
322 | regs->pc = nextpc; | |
323 | return 1; | |
324 | } else if ((finsn & 0xf003) == 0xf003) { | |
325 | /* fdiv */ | |
326 | struct task_struct *tsk = current; | |
327 | int fpscr; | |
328 | int n, m, prec; | |
329 | unsigned int hx, hy; | |
330 | ||
331 | n = (finsn >> 8) & 0xf; | |
332 | m = (finsn >> 4) & 0xf; | |
0ea820cf PM |
333 | hx = tsk->thread.xstate->hardfpu.fp_regs[n]; |
334 | hy = tsk->thread.xstate->hardfpu.fp_regs[m]; | |
335 | fpscr = tsk->thread.xstate->hardfpu.fpscr; | |
c8c0a1ab SM |
336 | prec = fpscr & FPSCR_DBL_PRECISION; |
337 | ||
338 | if ((fpscr & FPSCR_CAUSE_ERROR) | |
339 | && (prec && ((hx & 0x7fffffff) < 0x00100000 | |
340 | || (hy & 0x7fffffff) < 0x00100000))) { | |
341 | long long llx, lly; | |
342 | ||
343 | /* FPU error because of denormal (doubles) */ | |
344 | llx = ((long long)hx << 32) | |
0ea820cf | 345 | | tsk->thread.xstate->hardfpu.fp_regs[n + 1]; |
c8c0a1ab | 346 | lly = ((long long)hy << 32) |
0ea820cf | 347 | | tsk->thread.xstate->hardfpu.fp_regs[m + 1]; |
c8c0a1ab SM |
348 | |
349 | llx = float64_div(llx, lly); | |
350 | ||
0ea820cf PM |
351 | tsk->thread.xstate->hardfpu.fp_regs[n] = llx >> 32; |
352 | tsk->thread.xstate->hardfpu.fp_regs[n + 1] = llx & 0xffffffff; | |
c8c0a1ab SM |
353 | } else if ((fpscr & FPSCR_CAUSE_ERROR) |
354 | && (!prec && ((hx & 0x7fffffff) < 0x00800000 | |
355 | || (hy & 0x7fffffff) < 0x00800000))) { | |
356 | /* FPU error because of denormal (floats) */ | |
357 | hx = float32_div(hx, hy); | |
0ea820cf | 358 | tsk->thread.xstate->hardfpu.fp_regs[n] = hx; |
b5a1bcbe | 359 | } else |
c8c0a1ab | 360 | return 0; |
1da177e4 | 361 | |
b6ad1e8c CS |
362 | regs->pc = nextpc; |
363 | return 1; | |
364 | } else if ((finsn & 0xf0bd) == 0xf0bd) { | |
365 | /* fcnvds - double to single precision convert */ | |
366 | struct task_struct *tsk = current; | |
367 | int m; | |
368 | unsigned int hx; | |
369 | ||
0f6dee23 | 370 | m = (finsn >> 8) & 0x7; |
0ea820cf | 371 | hx = tsk->thread.xstate->hardfpu.fp_regs[m]; |
b6ad1e8c | 372 | |
0ea820cf | 373 | if ((tsk->thread.xstate->hardfpu.fpscr & FPSCR_CAUSE_ERROR) |
b6ad1e8c CS |
374 | && ((hx & 0x7fffffff) < 0x00100000)) { |
375 | /* subnormal double to float conversion */ | |
376 | long long llx; | |
377 | ||
0ea820cf PM |
378 | llx = ((long long)tsk->thread.xstate->hardfpu.fp_regs[m] << 32) |
379 | | tsk->thread.xstate->hardfpu.fp_regs[m + 1]; | |
b6ad1e8c | 380 | |
0ea820cf | 381 | tsk->thread.xstate->hardfpu.fpul = float64_to_float32(llx); |
b6ad1e8c CS |
382 | } else |
383 | return 0; | |
384 | ||
1da177e4 LT |
385 | regs->pc = nextpc; |
386 | return 1; | |
387 | } | |
388 | ||
389 | return 0; | |
390 | } | |
391 | ||
c8c0a1ab SM |
392 | void float_raise(unsigned int flags) |
393 | { | |
394 | fpu_exception_flags |= flags; | |
395 | } | |
396 | ||
397 | int float_rounding_mode(void) | |
398 | { | |
399 | struct task_struct *tsk = current; | |
0ea820cf | 400 | int roundingMode = FPSCR_ROUNDING_MODE(tsk->thread.xstate->hardfpu.fpscr); |
c8c0a1ab SM |
401 | return roundingMode; |
402 | } | |
403 | ||
74d99a5e | 404 | BUILD_TRAP_HANDLER(fpu_error) |
1da177e4 LT |
405 | { |
406 | struct task_struct *tsk = current; | |
74d99a5e | 407 | TRAP_HANDLER_DECL; |
1da177e4 | 408 | |
d3ea9fa0 | 409 | __unlazy_fpu(tsk, regs); |
c8c0a1ab SM |
410 | fpu_exception_flags = 0; |
411 | if (ieee_fpe_handler(regs)) { | |
0ea820cf | 412 | tsk->thread.xstate->hardfpu.fpscr &= |
c8c0a1ab | 413 | ~(FPSCR_CAUSE_MASK | FPSCR_FLAG_MASK); |
0ea820cf | 414 | tsk->thread.xstate->hardfpu.fpscr |= fpu_exception_flags; |
c8c0a1ab SM |
415 | /* Set the FPSCR flag as well as cause bits - simply |
416 | * replicate the cause */ | |
0ea820cf | 417 | tsk->thread.xstate->hardfpu.fpscr |= (fpu_exception_flags >> 10); |
c8c0a1ab SM |
418 | grab_fpu(regs); |
419 | restore_fpu(tsk); | |
d3ea9fa0 | 420 | task_thread_info(tsk)->status |= TS_USEDFPU; |
0ea820cf | 421 | if ((((tsk->thread.xstate->hardfpu.fpscr & FPSCR_ENABLE_MASK) >> 7) & |
c8c0a1ab SM |
422 | (fpu_exception_flags >> 2)) == 0) { |
423 | return; | |
424 | } | |
425 | } | |
426 | ||
1da177e4 LT |
427 | force_sig(SIGFPE, tsk); |
428 | } |