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