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 #include <asm/system.h>
16 
17 #ifdef CONFIG_LAZY_SAVE_FPU
18 struct task_struct *fpu_state_owner;
19 #endif
20 
21 /*
22  * error functions in FPU disabled exception
23  */
fpu_disabled_in_kernel(struct pt_regs * regs)24 asmlinkage void fpu_disabled_in_kernel(struct pt_regs *regs)
25 {
26 	die_if_no_fixup("An FPU Disabled exception happened in kernel space\n",
27 			regs, EXCEP_FPU_DISABLED);
28 }
29 
30 /*
31  * handle an FPU operational exception
32  * - there's a possibility that if the FPU is asynchronous, the signal might
33  *   be meant for a process other than the current one
34  */
fpu_exception(struct pt_regs * regs,enum exception_code code)35 asmlinkage void fpu_exception(struct pt_regs *regs, enum exception_code code)
36 {
37 	struct task_struct *tsk = current;
38 	siginfo_t info;
39 	u32 fpcr;
40 
41 	if (!user_mode(regs))
42 		die_if_no_fixup("An FPU Operation exception happened in"
43 				" kernel space\n",
44 				regs, code);
45 
46 	if (!is_using_fpu(tsk))
47 		die_if_no_fixup("An FPU Operation exception happened,"
48 				" but the FPU is not in use",
49 				regs, code);
50 
51 	info.si_signo = SIGFPE;
52 	info.si_errno = 0;
53 	info.si_addr = (void *) tsk->thread.uregs->pc;
54 	info.si_code = FPE_FLTINV;
55 
56 	unlazy_fpu(tsk);
57 
58 	fpcr = tsk->thread.fpu_state.fpcr;
59 
60 	if (fpcr & FPCR_EC_Z)
61 		info.si_code = FPE_FLTDIV;
62 	else if	(fpcr & FPCR_EC_O)
63 		info.si_code = FPE_FLTOVF;
64 	else if	(fpcr & FPCR_EC_U)
65 		info.si_code = FPE_FLTUND;
66 	else if	(fpcr & FPCR_EC_I)
67 		info.si_code = FPE_FLTRES;
68 
69 	force_sig_info(SIGFPE, &info, tsk);
70 }
71 
72 /*
73  * save the FPU state to a signal context
74  */
fpu_setup_sigcontext(struct fpucontext * fpucontext)75 int fpu_setup_sigcontext(struct fpucontext *fpucontext)
76 {
77 	struct task_struct *tsk = current;
78 
79 	if (!is_using_fpu(tsk))
80 		return 0;
81 
82 	/* transfer the current FPU state to memory and cause fpu_init() to be
83 	 * triggered by the next attempted FPU operation by the current
84 	 * process.
85 	 */
86 	preempt_disable();
87 
88 #ifndef CONFIG_LAZY_SAVE_FPU
89 	if (tsk->thread.fpu_flags & THREAD_HAS_FPU) {
90 		fpu_save(&tsk->thread.fpu_state);
91 		tsk->thread.uregs->epsw &= ~EPSW_FE;
92 		tsk->thread.fpu_flags &= ~THREAD_HAS_FPU;
93 	}
94 #else /* !CONFIG_LAZY_SAVE_FPU */
95 	if (fpu_state_owner == tsk) {
96 		fpu_save(&tsk->thread.fpu_state);
97 		fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE;
98 		fpu_state_owner = NULL;
99 	}
100 #endif /* !CONFIG_LAZY_SAVE_FPU */
101 
102 	preempt_enable();
103 
104 	/* we no longer have a valid current FPU state */
105 	clear_using_fpu(tsk);
106 
107 	/* transfer the saved FPU state onto the userspace stack */
108 	if (copy_to_user(fpucontext,
109 			 &tsk->thread.fpu_state,
110 			 min(sizeof(struct fpu_state_struct),
111 			     sizeof(struct fpucontext))))
112 		return -1;
113 
114 	return 1;
115 }
116 
117 /*
118  * kill a process's FPU state during restoration after signal handling
119  */
fpu_kill_state(struct task_struct * tsk)120 void fpu_kill_state(struct task_struct *tsk)
121 {
122 	/* disown anything left in the FPU */
123 	preempt_disable();
124 
125 #ifndef CONFIG_LAZY_SAVE_FPU
126 	if (tsk->thread.fpu_flags & THREAD_HAS_FPU) {
127 		tsk->thread.uregs->epsw &= ~EPSW_FE;
128 		tsk->thread.fpu_flags &= ~THREAD_HAS_FPU;
129 	}
130 #else /* !CONFIG_LAZY_SAVE_FPU */
131 	if (fpu_state_owner == tsk) {
132 		fpu_state_owner->thread.uregs->epsw &= ~EPSW_FE;
133 		fpu_state_owner = NULL;
134 	}
135 #endif /* !CONFIG_LAZY_SAVE_FPU */
136 
137 	preempt_enable();
138 
139 	/* we no longer have a valid current FPU state */
140 	clear_using_fpu(tsk);
141 }
142 
143 /*
144  * restore the FPU state from a signal context
145  */
fpu_restore_sigcontext(struct fpucontext * fpucontext)146 int fpu_restore_sigcontext(struct fpucontext *fpucontext)
147 {
148 	struct task_struct *tsk = current;
149 	int ret;
150 
151 	/* load up the old FPU state */
152 	ret = copy_from_user(&tsk->thread.fpu_state, fpucontext,
153 			     min(sizeof(struct fpu_state_struct),
154 				 sizeof(struct fpucontext)));
155 	if (!ret)
156 		set_using_fpu(tsk);
157 
158 	return ret;
159 }
160 
161 /*
162  * fill in the FPU structure for a core dump
163  */
dump_fpu(struct pt_regs * regs,elf_fpregset_t * fpreg)164 int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpreg)
165 {
166 	struct task_struct *tsk = current;
167 	int fpvalid;
168 
169 	fpvalid = is_using_fpu(tsk);
170 	if (fpvalid) {
171 		unlazy_fpu(tsk);
172 		memcpy(fpreg, &tsk->thread.fpu_state, sizeof(*fpreg));
173 	}
174 
175 	return fpvalid;
176 }
177