diff options
Diffstat (limited to 'arch/mn10300/kernel/fpu.c')
-rw-r--r-- | arch/mn10300/kernel/fpu.c | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/arch/mn10300/kernel/fpu.c b/arch/mn10300/kernel/fpu.c new file mode 100644 index 000000000000..e705f25ad5ff --- /dev/null +++ b/arch/mn10300/kernel/fpu.c | |||
@@ -0,0 +1,223 @@ | |||
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 | } | ||