Merge branch 'bkl-removal' of git://git.lwn.net/linux-2.6
[linux-2.6] / arch / mn10300 / kernel / fpu.c
1 /* MN10300 FPU management
2  *
3  * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
4  * Written by David Howells (dhowells@redhat.com)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public Licence
8  * as published by the Free Software Foundation; either version
9  * 2 of the Licence, or (at your option) any later version.
10  */
11 #include <asm/uaccess.h>
12 #include <asm/fpu.h>
13 #include <asm/elf.h>
14 #include <asm/exceptions.h>
15
16 struct task_struct *fpu_state_owner;
17
18 /*
19  * handle an exception due to the FPU being disabled
20  */
21 asmlinkage void fpu_disabled(struct pt_regs *regs, enum exception_code code)
22 {
23         struct task_struct *tsk = current;
24
25         if (!user_mode(regs))
26                 die_if_no_fixup("An FPU Disabled exception happened in"
27                                 " kernel space\n",
28                                 regs, code);
29
30 #ifdef CONFIG_FPU
31         preempt_disable();
32
33         /* transfer the last process's FPU state to memory */
34         if (fpu_state_owner) {
35                 fpu_save(&fpu_state_owner->thread.fpu_state);
36                 fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE;
37         }
38
39         /* the current process now owns the FPU state */
40         fpu_state_owner = tsk;
41         regs->epsw |= EPSW_FE;
42
43         /* load the FPU with the current process's FPU state or invent a new
44          * clean one if the process doesn't have one */
45         if (is_using_fpu(tsk)) {
46                 fpu_restore(&tsk->thread.fpu_state);
47         } else {
48                 fpu_init_state();
49                 set_using_fpu(tsk);
50         }
51
52         preempt_enable();
53 #else
54         {
55                 siginfo_t info;
56
57                 info.si_signo = SIGFPE;
58                 info.si_errno = 0;
59                 info.si_addr = (void *) tsk->thread.uregs->pc;
60                 info.si_code = FPE_FLTINV;
61
62                 force_sig_info(SIGFPE, &info, tsk);
63         }
64 #endif  /* CONFIG_FPU */
65 }
66
67 /*
68  * handle an FPU operational exception
69  * - there's a possibility that if the FPU is asynchronous, the signal might
70  *   be meant for a process other than the current one
71  */
72 asmlinkage void fpu_exception(struct pt_regs *regs, enum exception_code code)
73 {
74         struct task_struct *tsk = fpu_state_owner;
75         siginfo_t info;
76
77         if (!user_mode(regs))
78                 die_if_no_fixup("An FPU Operation exception happened in"
79                                 " kernel space\n",
80                                 regs, code);
81
82         if (!tsk)
83                 die_if_no_fixup("An FPU Operation exception happened,"
84                                 " but the FPU is not in use",
85                                 regs, code);
86
87         info.si_signo = SIGFPE;
88         info.si_errno = 0;
89         info.si_addr = (void *) tsk->thread.uregs->pc;
90         info.si_code = FPE_FLTINV;
91
92 #ifdef CONFIG_FPU
93         {
94                 u32 fpcr;
95
96                 /* get FPCR (we need to enable the FPU whilst we do this) */
97                 asm volatile("  or      %1,epsw         \n"
98 #ifdef CONFIG_MN10300_PROC_MN103E010
99                              "  nop                     \n"
100                              "  nop                     \n"
101                              "  nop                     \n"
102 #endif
103                              "  fmov    fpcr,%0         \n"
104 #ifdef CONFIG_MN10300_PROC_MN103E010
105                              "  nop                     \n"
106                              "  nop                     \n"
107                              "  nop                     \n"
108 #endif
109                              "  and     %2,epsw         \n"
110                              : "=&d"(fpcr)
111                              : "i"(EPSW_FE), "i"(~EPSW_FE)
112                              );
113
114                 if (fpcr & FPCR_EC_Z)
115                         info.si_code = FPE_FLTDIV;
116                 else if (fpcr & FPCR_EC_O)
117                         info.si_code = FPE_FLTOVF;
118                 else if (fpcr & FPCR_EC_U)
119                         info.si_code = FPE_FLTUND;
120                 else if (fpcr & FPCR_EC_I)
121                         info.si_code = FPE_FLTRES;
122         }
123 #endif
124
125         force_sig_info(SIGFPE, &info, tsk);
126 }
127
128 /*
129  * save the FPU state to a signal context
130  */
131 int fpu_setup_sigcontext(struct fpucontext *fpucontext)
132 {
133 #ifdef CONFIG_FPU
134         struct task_struct *tsk = current;
135
136         if (!is_using_fpu(tsk))
137                 return 0;
138
139         /* transfer the current FPU state to memory and cause fpu_init() to be
140          * triggered by the next attempted FPU operation by the current
141          * process.
142          */
143         preempt_disable();
144
145         if (fpu_state_owner == tsk) {
146                 fpu_save(&tsk->thread.fpu_state);
147                 fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE;
148                 fpu_state_owner = NULL;
149         }
150
151         preempt_enable();
152
153         /* we no longer have a valid current FPU state */
154         clear_using_fpu(tsk);
155
156         /* transfer the saved FPU state onto the userspace stack */
157         if (copy_to_user(fpucontext,
158                          &tsk->thread.fpu_state,
159                          min(sizeof(struct fpu_state_struct),
160                              sizeof(struct fpucontext))))
161                 return -1;
162
163         return 1;
164 #else
165         return 0;
166 #endif
167 }
168
169 /*
170  * kill a process's FPU state during restoration after signal handling
171  */
172 void fpu_kill_state(struct task_struct *tsk)
173 {
174 #ifdef CONFIG_FPU
175         /* disown anything left in the FPU */
176         preempt_disable();
177
178         if (fpu_state_owner == tsk) {
179                 fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE;
180                 fpu_state_owner = NULL;
181         }
182
183         preempt_enable();
184 #endif
185         /* we no longer have a valid current FPU state */
186         clear_using_fpu(tsk);
187 }
188
189 /*
190  * restore the FPU state from a signal context
191  */
192 int fpu_restore_sigcontext(struct fpucontext *fpucontext)
193 {
194         struct task_struct *tsk = current;
195         int ret;
196
197         /* load up the old FPU state */
198         ret = copy_from_user(&tsk->thread.fpu_state,
199                              fpucontext,
200                              min(sizeof(struct fpu_state_struct),
201                                  sizeof(struct fpucontext)));
202         if (!ret)
203                 set_using_fpu(tsk);
204
205         return ret;
206 }
207
208 /*
209  * fill in the FPU structure for a core dump
210  */
211 int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpreg)
212 {
213         struct task_struct *tsk = current;
214         int fpvalid;
215
216         fpvalid = is_using_fpu(tsk);
217         if (fpvalid) {
218                 unlazy_fpu(tsk);
219                 memcpy(fpreg, &tsk->thread.fpu_state, sizeof(*fpreg));
220         }
221
222         return fpvalid;
223 }