Merge branch 'intx' of master.kernel.org:/pub/scm/linux/kernel/git/jgarzik/misc-2.6
[pandora-kernel.git] / arch / sh / kernel / cpu / sh4 / fpu.c
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                         force_sig(SIGFPE, tsk);
287
288                 regs->pc = nextpc;
289                 return 1;
290         }
291
292         return 0;
293 }
294
295 asmlinkage void
296 do_fpu_error(unsigned long r4, unsigned long r5, unsigned long r6,
297              unsigned long r7, struct pt_regs __regs)
298 {
299         struct pt_regs *regs = RELOC_HIDE(&__regs, 0);
300         struct task_struct *tsk = current;
301
302         if (ieee_fpe_handler(regs))
303                 return;
304
305         regs->pc += 2;
306         save_fpu(tsk, regs);
307         force_sig(SIGFPE, tsk);
308 }
309
310 asmlinkage void
311 do_fpu_state_restore(unsigned long r4, unsigned long r5, unsigned long r6,
312                      unsigned long r7, struct pt_regs __regs)
313 {
314         struct pt_regs *regs = RELOC_HIDE(&__regs, 0);
315         struct task_struct *tsk = current;
316
317         grab_fpu(regs);
318         if (!user_mode(regs)) {
319                 printk(KERN_ERR "BUG: FPU is used in kernel mode.\n");
320                 return;
321         }
322
323         if (used_math()) {
324                 /* Using the FPU again.  */
325                 restore_fpu(tsk);
326         } else  {
327                 /* First time FPU user.  */
328                 fpu_init();
329                 set_used_math();
330         }
331         set_tsk_thread_flag(tsk, TIF_USEDFPU);
332 }