xref: /freebsd/sys/arm/arm/vfp.c (revision ce2f34ad)
1ba1c2daaSIan Lepore /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3af3dc4a7SPedro F. Giffuni  *
4ba1c2daaSIan Lepore  * Copyright (c) 2014 Ian Lepore <ian@freebsd.org>
5cf1a573fSOleksandr Tymoshenko  * Copyright (c) 2012 Mark Tinguely
6cf1a573fSOleksandr Tymoshenko  *
7cf1a573fSOleksandr Tymoshenko  * All rights reserved.
8cf1a573fSOleksandr Tymoshenko  *
9cf1a573fSOleksandr Tymoshenko  * Redistribution and use in source and binary forms, with or without
10cf1a573fSOleksandr Tymoshenko  * modification, are permitted provided that the following conditions
11cf1a573fSOleksandr Tymoshenko  * are met:
12cf1a573fSOleksandr Tymoshenko  * 1. Redistributions of source code must retain the above copyright
13cf1a573fSOleksandr Tymoshenko  *    notice, this list of conditions and the following disclaimer.
14cf1a573fSOleksandr Tymoshenko  * 2. Redistributions in binary form must reproduce the above copyright
15cf1a573fSOleksandr Tymoshenko  *    notice, this list of conditions and the following disclaimer in the
16cf1a573fSOleksandr Tymoshenko  *    documentation and/or other materials provided with the distribution.
17cf1a573fSOleksandr Tymoshenko  *
18cf1a573fSOleksandr Tymoshenko  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19cf1a573fSOleksandr Tymoshenko  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20cf1a573fSOleksandr Tymoshenko  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21cf1a573fSOleksandr Tymoshenko  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22cf1a573fSOleksandr Tymoshenko  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23cf1a573fSOleksandr Tymoshenko  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24cf1a573fSOleksandr Tymoshenko  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25cf1a573fSOleksandr Tymoshenko  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26cf1a573fSOleksandr Tymoshenko  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27cf1a573fSOleksandr Tymoshenko  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28cf1a573fSOleksandr Tymoshenko  * SUCH DAMAGE.
29cf1a573fSOleksandr Tymoshenko  */
30c5de7237SAndrew Turner 
31cf1a573fSOleksandr Tymoshenko #include <sys/param.h>
32cf1a573fSOleksandr Tymoshenko #include <sys/systm.h>
33cf1a573fSOleksandr Tymoshenko #include <sys/kernel.h>
34a6a481eaSMark Johnston #include <sys/limits.h>
35a6a481eaSMark Johnston #include <sys/malloc.h>
36a6a481eaSMark Johnston #include <sys/proc.h>
37cf1a573fSOleksandr Tymoshenko 
38ba1c2daaSIan Lepore #include <machine/armreg.h>
3919e1bd01SJohn Baldwin #include <machine/elf.h>
4064894120SIan Lepore #include <machine/frame.h>
41c890751dSKonstantin Belousov #include <machine/md_var.h>
42cf1a573fSOleksandr Tymoshenko #include <machine/pcb.h>
43cf1a573fSOleksandr Tymoshenko #include <machine/undefined.h>
44cf1a573fSOleksandr Tymoshenko #include <machine/vfp.h>
45cf1a573fSOleksandr Tymoshenko 
46cf1a573fSOleksandr Tymoshenko /* function prototypes */
4715a922dfSAndrew Turner static int vfp_bounce(u_int, u_int, struct trapframe *, int);
4815a922dfSAndrew Turner static void vfp_restore(struct vfp_state *);
49cf1a573fSOleksandr Tymoshenko 
50c46c2523SAndrew Turner extern int vfp_exists;
51cf1a573fSOleksandr Tymoshenko static struct undefined_handler vfp10_uh, vfp11_uh;
5293ef7ecbSAndrew Turner /* If true the VFP unit has 32 double registers, otherwise it has 16 */
5393ef7ecbSAndrew Turner static int is_d32;
54cf1a573fSOleksandr Tymoshenko 
55a6a481eaSMark Johnston static MALLOC_DEFINE(M_FPUKERN_CTX, "fpukern_ctx",
56a6a481eaSMark Johnston     "Kernel contexts for VFP state");
57a6a481eaSMark Johnston 
586926e269SKornel Dulęba struct fpu_kern_ctx {
596926e269SKornel Dulęba 	struct vfp_state	*prev;
606926e269SKornel Dulęba #define	FPU_KERN_CTX_DUMMY	0x01	/* avoided save for the kern thread */
616926e269SKornel Dulęba #define	FPU_KERN_CTX_INUSE	0x02
626926e269SKornel Dulęba 	uint32_t	 flags;
636926e269SKornel Dulęba 	struct vfp_state	 state;
646926e269SKornel Dulęba };
656926e269SKornel Dulęba 
66bd96d089SIan Lepore /*
67bd96d089SIan Lepore  * About .fpu directives in this file...
68bd96d089SIan Lepore  *
69bd96d089SIan Lepore  * We should need simply .fpu vfpv3, but clang 3.5 has a quirk where setting
70bd96d089SIan Lepore  * vfpv3 doesn't imply that vfp2 features are also available -- both have to be
71bd96d089SIan Lepore  * explicitly set to get all the features of both.  This is probably a bug in
72bd96d089SIan Lepore  * clang, so it may get fixed and require changes here some day.  Other changes
73bd96d089SIan Lepore  * are probably coming in clang too, because there is email and open PRs
74bd96d089SIan Lepore  * indicating they want to completely disable the ability to use .fpu and
75bd96d089SIan Lepore  * similar directives in inline asm.  That would be catastrophic for us,
76bd96d089SIan Lepore  * hopefully they come to their senses.  There was also some discusion of a new
77bd96d089SIan Lepore  * syntax such as .push fpu=vfpv3; ...; .pop fpu; and that would be ideal for
78bd96d089SIan Lepore  * us, better than what we have now really.
79bd96d089SIan Lepore  *
80bd96d089SIan Lepore  * For gcc, each .fpu directive completely overrides the prior directive, unlike
81bd96d089SIan Lepore  * with clang, but luckily on gcc saying v3 implies all the v2 features as well.
82bd96d089SIan Lepore  */
83cf1a573fSOleksandr Tymoshenko 
84bd96d089SIan Lepore #define fmxr(reg, val) \
85bd96d089SIan Lepore     __asm __volatile("	.fpu vfpv2\n .fpu vfpv3\n"			\
86bd96d089SIan Lepore 		     "	vmsr	" __STRING(reg) ", %0"   :: "r"(val));
87bd96d089SIan Lepore 
88cf1a573fSOleksandr Tymoshenko #define fmrx(reg) \
89cf1a573fSOleksandr Tymoshenko ({ u_int val = 0;\
90bd96d089SIan Lepore     __asm __volatile(" .fpu vfpv2\n .fpu vfpv3\n"			\
91bd96d089SIan Lepore 		     "	vmrs	%0, " __STRING(reg) : "=r"(val));	\
92cf1a573fSOleksandr Tymoshenko     val; \
93cf1a573fSOleksandr Tymoshenko })
94cf1a573fSOleksandr Tymoshenko 
95ba1c2daaSIan Lepore static u_int
get_coprocessorACR(void)96cf1a573fSOleksandr Tymoshenko get_coprocessorACR(void)
97cf1a573fSOleksandr Tymoshenko {
98cf1a573fSOleksandr Tymoshenko 	u_int val;
99cf1a573fSOleksandr Tymoshenko 	__asm __volatile("mrc p15, 0, %0, c1, c0, 2" : "=r" (val) : : "cc");
100cf1a573fSOleksandr Tymoshenko 	return val;
101cf1a573fSOleksandr Tymoshenko }
102cf1a573fSOleksandr Tymoshenko 
103ba1c2daaSIan Lepore static void
set_coprocessorACR(u_int val)104cf1a573fSOleksandr Tymoshenko set_coprocessorACR(u_int val)
105cf1a573fSOleksandr Tymoshenko {
106cf1a573fSOleksandr Tymoshenko 	__asm __volatile("mcr p15, 0, %0, c1, c0, 2\n\t"
107cf1a573fSOleksandr Tymoshenko 	 : : "r" (val) : "cc");
10848617973SOlivier Houchard 	isb();
109cf1a573fSOleksandr Tymoshenko }
110cf1a573fSOleksandr Tymoshenko 
1116926e269SKornel Dulęba static void
vfp_enable(void)1126926e269SKornel Dulęba vfp_enable(void)
1136926e269SKornel Dulęba {
1146926e269SKornel Dulęba 	uint32_t fpexc;
1156926e269SKornel Dulęba 
1166926e269SKornel Dulęba 	fpexc = fmrx(fpexc);
1176926e269SKornel Dulęba 	fmxr(fpexc, fpexc | VFPEXC_EN);
1186926e269SKornel Dulęba 	isb();
1196926e269SKornel Dulęba }
1206926e269SKornel Dulęba 
1216926e269SKornel Dulęba static void
vfp_disable(void)1226926e269SKornel Dulęba vfp_disable(void)
1236926e269SKornel Dulęba {
1246926e269SKornel Dulęba 	uint32_t fpexc;
1256926e269SKornel Dulęba 
1266926e269SKornel Dulęba 	fpexc = fmrx(fpexc);
1276926e269SKornel Dulęba 	fmxr(fpexc, fpexc & ~VFPEXC_EN);
1286926e269SKornel Dulęba 	isb();
1296926e269SKornel Dulęba }
1306926e269SKornel Dulęba 
131cf1a573fSOleksandr Tymoshenko 	/* called for each cpu */
132cf1a573fSOleksandr Tymoshenko void
vfp_init(void)133cf1a573fSOleksandr Tymoshenko vfp_init(void)
134cf1a573fSOleksandr Tymoshenko {
135feb08ab0SWarner Losh 	u_int fpsid, tmp;
13693ef7ecbSAndrew Turner 	u_int coproc, vfp_arch;
137cf1a573fSOleksandr Tymoshenko 
138cf1a573fSOleksandr Tymoshenko 	coproc = get_coprocessorACR();
139cf1a573fSOleksandr Tymoshenko 	coproc |= COPROC10 | COPROC11;
140cf1a573fSOleksandr Tymoshenko 	set_coprocessorACR(coproc);
141cf1a573fSOleksandr Tymoshenko 
142bd96d089SIan Lepore 	fpsid = fmrx(fpsid);		/* read the vfp system id */
143cf1a573fSOleksandr Tymoshenko 
144cf1a573fSOleksandr Tymoshenko 	if (!(fpsid & VFPSID_HARDSOFT_IMP)) {
145cf1a573fSOleksandr Tymoshenko 		vfp_exists = 1;
14693ef7ecbSAndrew Turner 		is_d32 = 0;
147bd96d089SIan Lepore 		PCPU_SET(vfpsid, fpsid);	/* save the fpsid */
14819e1bd01SJohn Baldwin 		elf_hwcap |= HWCAP_VFP;
14993ef7ecbSAndrew Turner 
15093ef7ecbSAndrew Turner 		vfp_arch =
15193ef7ecbSAndrew Turner 		    (fpsid & VFPSID_SUBVERSION2_MASK) >> VFPSID_SUBVERSION_OFF;
15293ef7ecbSAndrew Turner 
15393ef7ecbSAndrew Turner 		if (vfp_arch >= VFP_ARCH3) {
154bd96d089SIan Lepore 			tmp = fmrx(mvfr0);
155cf1a573fSOleksandr Tymoshenko 			PCPU_SET(vfpmvfr0, tmp);
15619e1bd01SJohn Baldwin 			elf_hwcap |= HWCAP_VFPv3;
15793ef7ecbSAndrew Turner 
15819e1bd01SJohn Baldwin 			if ((tmp & VMVFR0_RB_MASK) == 2) {
15919e1bd01SJohn Baldwin 				elf_hwcap |= HWCAP_VFPD32;
16093ef7ecbSAndrew Turner 				is_d32 = 1;
16119e1bd01SJohn Baldwin 			} else
16219e1bd01SJohn Baldwin 				elf_hwcap |= HWCAP_VFPv3D16;
16393ef7ecbSAndrew Turner 
164bd96d089SIan Lepore 			tmp = fmrx(mvfr1);
165cf1a573fSOleksandr Tymoshenko 			PCPU_SET(vfpmvfr1, tmp);
166c890751dSKonstantin Belousov 
167c890751dSKonstantin Belousov 			if (PCPU_GET(cpuid) == 0) {
168c890751dSKonstantin Belousov 				if ((tmp & VMVFR1_FZ_MASK) == 0x1) {
169c890751dSKonstantin Belousov 					/* Denormals arithmetic support */
170c890751dSKonstantin Belousov 					initial_fpscr &= ~VFPSCR_FZ;
171c890751dSKonstantin Belousov 					thread0.td_pcb->pcb_vfpstate.fpscr =
172c890751dSKonstantin Belousov 					    initial_fpscr;
173c890751dSKonstantin Belousov 				}
174c890751dSKonstantin Belousov 			}
17564f73a4cSJohn Baldwin 
17664f73a4cSJohn Baldwin 			if ((tmp & VMVFR1_LS_MASK) >> VMVFR1_LS_OFF == 1 &&
17764f73a4cSJohn Baldwin 			    (tmp & VMVFR1_I_MASK) >> VMVFR1_I_OFF == 1 &&
17864f73a4cSJohn Baldwin 			    (tmp & VMVFR1_SP_MASK) >> VMVFR1_SP_OFF == 1)
17964f73a4cSJohn Baldwin 				elf_hwcap |= HWCAP_NEON;
1800cbf724eSMichal Meloun 			if ((tmp & VMVFR1_FMAC_MASK) >>  VMVFR1_FMAC_OFF == 1)
1810cbf724eSMichal Meloun 				elf_hwcap |= HWCAP_VFPv4;
182cf1a573fSOleksandr Tymoshenko 		}
18393ef7ecbSAndrew Turner 
184ce2f34adSMark Johnston 		vfp_disable();
185ce2f34adSMark Johnston 
186cf1a573fSOleksandr Tymoshenko 		/* initialize the coprocess 10 and 11 calls
187cf1a573fSOleksandr Tymoshenko 		 * These are called to restore the registers and enable
188cf1a573fSOleksandr Tymoshenko 		 * the VFP hardware.
189cf1a573fSOleksandr Tymoshenko 		 */
190cf1a573fSOleksandr Tymoshenko 		if (vfp10_uh.uh_handler == NULL) {
191cf1a573fSOleksandr Tymoshenko 			vfp10_uh.uh_handler = vfp_bounce;
192cf1a573fSOleksandr Tymoshenko 			vfp11_uh.uh_handler = vfp_bounce;
193cf1a573fSOleksandr Tymoshenko 			install_coproc_handler_static(10, &vfp10_uh);
194cf1a573fSOleksandr Tymoshenko 			install_coproc_handler_static(11, &vfp11_uh);
195cf1a573fSOleksandr Tymoshenko 		}
196cf1a573fSOleksandr Tymoshenko 	}
197cf1a573fSOleksandr Tymoshenko }
198cf1a573fSOleksandr Tymoshenko 
199cf1a573fSOleksandr Tymoshenko SYSINIT(vfp, SI_SUB_CPU, SI_ORDER_ANY, vfp_init, NULL);
200cf1a573fSOleksandr Tymoshenko 
20103a07de0SMark Johnston /*
20203a07de0SMark Johnston  * Start the VFP unit, restore the VFP registers from the PCB and retry
20303a07de0SMark Johnston  * the instruction.
204cf1a573fSOleksandr Tymoshenko  */
20515a922dfSAndrew Turner static int
vfp_bounce(u_int addr,u_int insn,struct trapframe * frame,int code)206cf1a573fSOleksandr Tymoshenko vfp_bounce(u_int addr, u_int insn, struct trapframe *frame, int code)
207cf1a573fSOleksandr Tymoshenko {
208ba1c2daaSIan Lepore 	u_int cpu, fpexc;
209cf1a573fSOleksandr Tymoshenko 	struct pcb *curpcb;
210d4f9011cSAndrew Turner 	ksiginfo_t ksi;
211cf1a573fSOleksandr Tymoshenko 
212ba1c2daaSIan Lepore 	critical_enter();
213ba1c2daaSIan Lepore 
214ba1c2daaSIan Lepore 	/*
215ba1c2daaSIan Lepore 	 * If the VFP is already on and we got an undefined instruction, then
216ba1c2daaSIan Lepore 	 * something tried to executate a truly invalid instruction that maps to
217ba1c2daaSIan Lepore 	 * the VFP.
218cf1a573fSOleksandr Tymoshenko 	 */
219bd96d089SIan Lepore 	fpexc = fmrx(fpexc);
220ba1c2daaSIan Lepore 	if (fpexc & VFPEXC_EN) {
221d4f9011cSAndrew Turner 		/* Clear any exceptions */
22223e00b90SOleksandr Tymoshenko 		fmxr(fpexc, fpexc & ~(VFPEXC_EX | VFPEXC_FP2V));
223d4f9011cSAndrew Turner 
224cf1a573fSOleksandr Tymoshenko 		/* kill the process - we do not handle emulation */
225ba1c2daaSIan Lepore 		critical_exit();
226d4f9011cSAndrew Turner 
22723e00b90SOleksandr Tymoshenko 		if (fpexc & VFPEXC_EX) {
228d4f9011cSAndrew Turner 			/* We have an exception, signal a SIGFPE */
229d4f9011cSAndrew Turner 			ksiginfo_init_trap(&ksi);
230d4f9011cSAndrew Turner 			ksi.ksi_signo = SIGFPE;
231d4f9011cSAndrew Turner 			if (fpexc & VFPEXC_UFC)
232d4f9011cSAndrew Turner 				ksi.ksi_code = FPE_FLTUND;
233d4f9011cSAndrew Turner 			else if (fpexc & VFPEXC_OFC)
234d4f9011cSAndrew Turner 				ksi.ksi_code = FPE_FLTOVF;
235d4f9011cSAndrew Turner 			else if (fpexc & VFPEXC_IOC)
236d4f9011cSAndrew Turner 				ksi.ksi_code = FPE_FLTINV;
237d4f9011cSAndrew Turner 			ksi.ksi_addr = (void *)addr;
238d4f9011cSAndrew Turner 			trapsignal(curthread, &ksi);
239d4f9011cSAndrew Turner 			return 0;
240d4f9011cSAndrew Turner 		}
241d4f9011cSAndrew Turner 
242cf1a573fSOleksandr Tymoshenko 		return 1;
243cf1a573fSOleksandr Tymoshenko 	}
244ba1c2daaSIan Lepore 
24503a07de0SMark Johnston 	curpcb = curthread->td_pcb;
24603a07de0SMark Johnston 	if ((code & FAULT_USER) == 0 &&
24703a07de0SMark Johnston 	    (curpcb->pcb_fpflags & PCB_FP_KERN) == 0) {
24803a07de0SMark Johnston 		critical_exit();
24903a07de0SMark Johnston 		return (1);
25003a07de0SMark Johnston 	}
25103a07de0SMark Johnston 
252ba1c2daaSIan Lepore 	/*
253ba1c2daaSIan Lepore 	 * If the last time this thread used the VFP it was on this core, and
254ba1c2daaSIan Lepore 	 * the last thread to use the VFP on this core was this thread, then the
255ba1c2daaSIan Lepore 	 * VFP state is valid, otherwise restore this thread's state to the VFP.
256cf1a573fSOleksandr Tymoshenko 	 */
257bd96d089SIan Lepore 	fmxr(fpexc, fpexc | VFPEXC_EN);
258e1f2f9bdSAndrew Turner 	cpu = PCPU_GET(cpuid);
259ba1c2daaSIan Lepore 	if (curpcb->pcb_vfpcpu != cpu || curthread != PCPU_GET(fpcurthread)) {
2606926e269SKornel Dulęba 		vfp_restore(curpcb->pcb_vfpsaved);
261ba1c2daaSIan Lepore 		curpcb->pcb_vfpcpu = cpu;
262ba1c2daaSIan Lepore 		PCPU_SET(fpcurthread, curthread);
263cf1a573fSOleksandr Tymoshenko 	}
264cf1a573fSOleksandr Tymoshenko 
265ba1c2daaSIan Lepore 	critical_exit();
26698c666cfSKornel Dulęba 
26703a07de0SMark Johnston 	KASSERT((code & FAULT_USER) == 0 ||
26803a07de0SMark Johnston 	    curpcb->pcb_vfpsaved == &curpcb->pcb_vfpstate,
26998c666cfSKornel Dulęba 	    ("Kernel VFP state in use when entering userspace"));
27098c666cfSKornel Dulęba 
271ba1c2daaSIan Lepore 	return (0);
272ba1c2daaSIan Lepore }
273ba1c2daaSIan Lepore 
274ba1c2daaSIan Lepore /*
27598c666cfSKornel Dulęba  * Update the VFP state for a forked process or new thread. The PCB will
27698c666cfSKornel Dulęba  * have been copied from the old thread.
27798c666cfSKornel Dulęba  * The code is heavily based on arm64 logic.
27898c666cfSKornel Dulęba  */
27998c666cfSKornel Dulęba void
vfp_new_thread(struct thread * newtd,struct thread * oldtd,bool fork)28098c666cfSKornel Dulęba vfp_new_thread(struct thread *newtd, struct thread *oldtd, bool fork)
28198c666cfSKornel Dulęba {
28298c666cfSKornel Dulęba 	struct pcb *newpcb;
28398c666cfSKornel Dulęba 
28498c666cfSKornel Dulęba 	newpcb = newtd->td_pcb;
28598c666cfSKornel Dulęba 
28698c666cfSKornel Dulęba 	/* Kernel threads start with clean VFP */
28798c666cfSKornel Dulęba 	if ((oldtd->td_pflags & TDP_KTHREAD) != 0) {
28898c666cfSKornel Dulęba 		newpcb->pcb_fpflags &=
28998c666cfSKornel Dulęba 		    ~(PCB_FP_STARTED | PCB_FP_KERN | PCB_FP_NOSAVE);
29098c666cfSKornel Dulęba 	} else {
29198c666cfSKornel Dulęba 		MPASS((newpcb->pcb_fpflags & (PCB_FP_KERN|PCB_FP_NOSAVE)) == 0);
29298c666cfSKornel Dulęba 		if (!fork) {
29398c666cfSKornel Dulęba 			newpcb->pcb_fpflags &= ~PCB_FP_STARTED;
29498c666cfSKornel Dulęba 		}
29598c666cfSKornel Dulęba 	}
29698c666cfSKornel Dulęba 
29798c666cfSKornel Dulęba 	newpcb->pcb_vfpsaved = &newpcb->pcb_vfpstate;
29898c666cfSKornel Dulęba 	newpcb->pcb_vfpcpu = UINT_MAX;
29998c666cfSKornel Dulęba }
30098c666cfSKornel Dulęba /*
301ba1c2daaSIan Lepore  * Restore the given state to the VFP hardware.
302cf1a573fSOleksandr Tymoshenko  */
30315a922dfSAndrew Turner static void
vfp_restore(struct vfp_state * vfpsave)304cf1a573fSOleksandr Tymoshenko vfp_restore(struct vfp_state *vfpsave)
305cf1a573fSOleksandr Tymoshenko {
306d4f9011cSAndrew Turner 	uint32_t fpexc;
307cf1a573fSOleksandr Tymoshenko 
308bd96d089SIan Lepore 	/* On vfpv3 we may need to restore FPINST and FPINST2 */
309d4f9011cSAndrew Turner 	fpexc = vfpsave->fpexec;
310d4f9011cSAndrew Turner 	if (fpexc & VFPEXC_EX) {
311bd96d089SIan Lepore 		fmxr(fpinst, vfpsave->fpinst);
312d4f9011cSAndrew Turner 		if (fpexc & VFPEXC_FP2V)
313bd96d089SIan Lepore 			fmxr(fpinst2, vfpsave->fpinst2);
314d4f9011cSAndrew Turner 	}
315bd96d089SIan Lepore 	fmxr(fpscr, vfpsave->fpscr);
316d4f9011cSAndrew Turner 
317bd96d089SIan Lepore 	__asm __volatile(
318bd96d089SIan Lepore 	    " .fpu	vfpv2\n"
319bd96d089SIan Lepore 	    " .fpu	vfpv3\n"
320bd96d089SIan Lepore 	    " vldmia	%0!, {d0-d15}\n"	/* d0-d15 */
321d4f9011cSAndrew Turner 	    " cmp	%1, #0\n"		/* -D16 or -D32? */
322bd96d089SIan Lepore 	    " vldmiane	%0!, {d16-d31}\n"	/* d16-d31 */
323d4f9011cSAndrew Turner 	    " addeq	%0, %0, #128\n"		/* skip missing regs */
324bd96d089SIan Lepore 	    : "+&r" (vfpsave) : "r" (is_d32) : "cc"
325bd96d089SIan Lepore 	    );
326d4f9011cSAndrew Turner 
327bd96d089SIan Lepore 	fmxr(fpexc, fpexc);
328cf1a573fSOleksandr Tymoshenko }
329cf1a573fSOleksandr Tymoshenko 
330ba1c2daaSIan Lepore /*
331ba1c2daaSIan Lepore  * If the VFP is on, save its current state and turn it off if requested to do
332ba1c2daaSIan Lepore  * so.  If the VFP is not on, does not change the values at *vfpsave.  Caller is
333ba1c2daaSIan Lepore  * responsible for preventing a context switch while this is running.
334cf1a573fSOleksandr Tymoshenko  */
335cf1a573fSOleksandr Tymoshenko void
vfp_store(struct vfp_state * vfpsave,boolean_t disable_vfp)336ba1c2daaSIan Lepore vfp_store(struct vfp_state *vfpsave, boolean_t disable_vfp)
337cf1a573fSOleksandr Tymoshenko {
338d4f9011cSAndrew Turner 	uint32_t fpexc;
339cf1a573fSOleksandr Tymoshenko 
340bd96d089SIan Lepore 	fpexc = fmrx(fpexc);		/* Is the vfp enabled? */
341d4f9011cSAndrew Turner 	if (fpexc & VFPEXC_EN) {
342d4f9011cSAndrew Turner 		vfpsave->fpexec = fpexc;
343bd96d089SIan Lepore 		vfpsave->fpscr = fmrx(fpscr);
344d4f9011cSAndrew Turner 
345bd96d089SIan Lepore 		/* On vfpv3 we may need to save FPINST and FPINST2 */
346d4f9011cSAndrew Turner 		if (fpexc & VFPEXC_EX) {
347bd96d089SIan Lepore 			vfpsave->fpinst = fmrx(fpinst);
348d4f9011cSAndrew Turner 			if (fpexc & VFPEXC_FP2V)
349bd96d089SIan Lepore 				vfpsave->fpinst2 = fmrx(fpinst2);
350d4f9011cSAndrew Turner 			fpexc &= ~VFPEXC_EX;
351d4f9011cSAndrew Turner 		}
352d4f9011cSAndrew Turner 
353ba1c2daaSIan Lepore 		__asm __volatile(
354bd96d089SIan Lepore 		    " .fpu	vfpv2\n"
355bd96d089SIan Lepore 		    " .fpu	vfpv3\n"
356bd96d089SIan Lepore 		    " vstmia	%0!, {d0-d15}\n"	/* d0-d15 */
357d4f9011cSAndrew Turner 		    " cmp	%1, #0\n"		/* -D16 or -D32? */
3589ea04586SAndrew Turner 		    " vstmiane	%0!, {d16-d31}\n"	/* d16-d31 */
359d4f9011cSAndrew Turner 		    " addeq	%0, %0, #128\n"		/* skip missing regs */
360bd96d089SIan Lepore 		    : "+&r" (vfpsave) : "r" (is_d32) : "cc"
361bd96d089SIan Lepore 		    );
362d4f9011cSAndrew Turner 
363ba1c2daaSIan Lepore 		if (disable_vfp)
364bd96d089SIan Lepore 			fmxr(fpexc , fpexc & ~VFPEXC_EN);
365cf1a573fSOleksandr Tymoshenko 	}
366cf1a573fSOleksandr Tymoshenko }
367cf1a573fSOleksandr Tymoshenko 
368ba1c2daaSIan Lepore /*
3697a797a24SIan Lepore  * The current thread is dying.  If the state currently in the hardware belongs
3707a797a24SIan Lepore  * to the current thread, set fpcurthread to NULL to indicate that the VFP
3717a797a24SIan Lepore  * hardware state does not belong to any thread.  If the VFP is on, turn it off.
372cf1a573fSOleksandr Tymoshenko  */
373cf1a573fSOleksandr Tymoshenko void
vfp_discard(struct thread * td)3747a797a24SIan Lepore vfp_discard(struct thread *td)
375cf1a573fSOleksandr Tymoshenko {
376ba1c2daaSIan Lepore 	u_int tmp;
377cf1a573fSOleksandr Tymoshenko 
3787a797a24SIan Lepore 	if (PCPU_GET(fpcurthread) == td)
3797a797a24SIan Lepore 		PCPU_SET(fpcurthread, NULL);
3807a797a24SIan Lepore 
381bd96d089SIan Lepore 	tmp = fmrx(fpexc);
3827a797a24SIan Lepore 	if (tmp & VFPEXC_EN)
383bd96d089SIan Lepore 		fmxr(fpexc, tmp & ~VFPEXC_EN);
384cf1a573fSOleksandr Tymoshenko }
385cf1a573fSOleksandr Tymoshenko 
3866926e269SKornel Dulęba void
vfp_save_state(struct thread * td,struct pcb * pcb)3876926e269SKornel Dulęba vfp_save_state(struct thread *td, struct pcb *pcb)
3886926e269SKornel Dulęba {
3896926e269SKornel Dulęba 	int32_t fpexc;
3906926e269SKornel Dulęba 
3916926e269SKornel Dulęba 	KASSERT(pcb != NULL, ("NULL vfp pcb"));
3926926e269SKornel Dulęba 	KASSERT(td == NULL || td->td_pcb == pcb, ("Invalid vfp pcb"));
3936926e269SKornel Dulęba 
3946926e269SKornel Dulęba 	/*
3956926e269SKornel Dulęba 	 * savectx() will be called on panic with dumppcb as an argument,
3966926e269SKornel Dulęba 	 * dumppcb doesn't have pcb_vfpsaved set, so set it to save
3976926e269SKornel Dulęba 	 * the VFP registers.
3986926e269SKornel Dulęba 	 */
3996926e269SKornel Dulęba 	if (pcb->pcb_vfpsaved == NULL)
4006926e269SKornel Dulęba 		pcb->pcb_vfpsaved = &pcb->pcb_vfpstate;
4016926e269SKornel Dulęba 
4026926e269SKornel Dulęba 	if (td == NULL)
4036926e269SKornel Dulęba 		td = curthread;
4046926e269SKornel Dulęba 
4056926e269SKornel Dulęba 	critical_enter();
4066926e269SKornel Dulęba 	/*
4076926e269SKornel Dulęba 	 * Only store the registers if the VFP is enabled,
4086926e269SKornel Dulęba 	 * i.e. return if we are trapping on FP access.
4096926e269SKornel Dulęba 	 */
4106926e269SKornel Dulęba 	fpexc = fmrx(fpexc);
4116926e269SKornel Dulęba 	if (fpexc & VFPEXC_EN) {
4126926e269SKornel Dulęba 		KASSERT(PCPU_GET(fpcurthread) == td,
4136926e269SKornel Dulęba 		    ("Storing an invalid VFP state"));
4146926e269SKornel Dulęba 
4156926e269SKornel Dulęba 		vfp_store(pcb->pcb_vfpsaved, true);
4166926e269SKornel Dulęba 	}
4176926e269SKornel Dulęba 	critical_exit();
4186926e269SKornel Dulęba }
4196926e269SKornel Dulęba 
420a6a481eaSMark Johnston struct fpu_kern_ctx *
fpu_kern_alloc_ctx(u_int flags)421a6a481eaSMark Johnston fpu_kern_alloc_ctx(u_int flags)
422a6a481eaSMark Johnston {
423a6a481eaSMark Johnston 	return (malloc(sizeof(struct fpu_kern_ctx), M_FPUKERN_CTX,
424a6a481eaSMark Johnston 	    ((flags & FPU_KERN_NOWAIT) ? M_NOWAIT : M_WAITOK) | M_ZERO));
425a6a481eaSMark Johnston }
426a6a481eaSMark Johnston 
427a6a481eaSMark Johnston void
fpu_kern_free_ctx(struct fpu_kern_ctx * ctx)428a6a481eaSMark Johnston fpu_kern_free_ctx(struct fpu_kern_ctx *ctx)
429a6a481eaSMark Johnston {
430a6a481eaSMark Johnston 	KASSERT((ctx->flags & FPU_KERN_CTX_INUSE) == 0, ("freeing in-use ctx"));
431a6a481eaSMark Johnston 
432a6a481eaSMark Johnston 	free(ctx, M_FPUKERN_CTX);
433a6a481eaSMark Johnston }
434a6a481eaSMark Johnston 
4356926e269SKornel Dulęba void
fpu_kern_enter(struct thread * td,struct fpu_kern_ctx * ctx,u_int flags)4366926e269SKornel Dulęba fpu_kern_enter(struct thread *td, struct fpu_kern_ctx *ctx, u_int flags)
4376926e269SKornel Dulęba {
4386926e269SKornel Dulęba 	struct pcb *pcb;
4396926e269SKornel Dulęba 
4406926e269SKornel Dulęba 	pcb = td->td_pcb;
4416926e269SKornel Dulęba 	KASSERT((flags & FPU_KERN_NOCTX) != 0 || ctx != NULL,
4426926e269SKornel Dulęba 	    ("ctx is required when !FPU_KERN_NOCTX"));
4436926e269SKornel Dulęba 	KASSERT(ctx == NULL || (ctx->flags & FPU_KERN_CTX_INUSE) == 0,
4446926e269SKornel Dulęba 	    ("using inuse ctx"));
4456926e269SKornel Dulęba 	KASSERT((pcb->pcb_fpflags & PCB_FP_NOSAVE) == 0,
4466926e269SKornel Dulęba 	    ("recursive fpu_kern_enter while in PCB_FP_NOSAVE state"));
4476926e269SKornel Dulęba 
4486926e269SKornel Dulęba 	if ((flags & FPU_KERN_NOCTX) != 0) {
4496926e269SKornel Dulęba 		critical_enter();
4506926e269SKornel Dulęba 		if (curthread == PCPU_GET(fpcurthread)) {
4516926e269SKornel Dulęba 			vfp_save_state(curthread, pcb);
4526926e269SKornel Dulęba 		}
4536926e269SKornel Dulęba 		PCPU_SET(fpcurthread, NULL);
4546926e269SKornel Dulęba 
4556926e269SKornel Dulęba 		vfp_enable();
4566926e269SKornel Dulęba 		pcb->pcb_fpflags |= PCB_FP_KERN | PCB_FP_NOSAVE |
4576926e269SKornel Dulęba 		    PCB_FP_STARTED;
4586926e269SKornel Dulęba 		return;
4596926e269SKornel Dulęba 	}
4606926e269SKornel Dulęba 
4616926e269SKornel Dulęba 	if ((flags & FPU_KERN_KTHR) != 0 && is_fpu_kern_thread(0)) {
4626926e269SKornel Dulęba 		ctx->flags = FPU_KERN_CTX_DUMMY | FPU_KERN_CTX_INUSE;
4636926e269SKornel Dulęba 		return;
4646926e269SKornel Dulęba 	}
4656926e269SKornel Dulęba 	/*
4666926e269SKornel Dulęba 	 * Check either we are already using the VFP in the kernel, or
4676926e269SKornel Dulęba 	 * the the saved state points to the default user space.
4686926e269SKornel Dulęba 	 */
4696926e269SKornel Dulęba 	KASSERT((pcb->pcb_fpflags & PCB_FP_KERN) != 0 ||
4706926e269SKornel Dulęba 	    pcb->pcb_vfpsaved == &pcb->pcb_vfpstate,
4716926e269SKornel Dulęba 	    ("Mangled pcb_vfpsaved %x %p %p", pcb->pcb_fpflags, pcb->pcb_vfpsaved,
4726926e269SKornel Dulęba 	     &pcb->pcb_vfpstate));
4736926e269SKornel Dulęba 	ctx->flags = FPU_KERN_CTX_INUSE;
4746926e269SKornel Dulęba 	vfp_save_state(curthread, pcb);
4756926e269SKornel Dulęba 	ctx->prev = pcb->pcb_vfpsaved;
4766926e269SKornel Dulęba 	pcb->pcb_vfpsaved = &ctx->state;
4776926e269SKornel Dulęba 	pcb->pcb_fpflags |= PCB_FP_KERN;
4786926e269SKornel Dulęba 	pcb->pcb_fpflags &= ~PCB_FP_STARTED;
4796926e269SKornel Dulęba 
4806926e269SKornel Dulęba 	return;
4816926e269SKornel Dulęba }
4826926e269SKornel Dulęba 
4836926e269SKornel Dulęba int
fpu_kern_leave(struct thread * td,struct fpu_kern_ctx * ctx)4846926e269SKornel Dulęba fpu_kern_leave(struct thread *td, struct fpu_kern_ctx *ctx)
4856926e269SKornel Dulęba {
4866926e269SKornel Dulęba 	struct pcb *pcb;
4876926e269SKornel Dulęba 
4886926e269SKornel Dulęba 	pcb = td->td_pcb;
4896926e269SKornel Dulęba 
4906926e269SKornel Dulęba 	if ((pcb->pcb_fpflags & PCB_FP_NOSAVE) != 0) {
4916926e269SKornel Dulęba 		KASSERT(ctx == NULL, ("non-null ctx after FPU_KERN_NOCTX"));
4926926e269SKornel Dulęba 		KASSERT(PCPU_GET(fpcurthread) == NULL,
4936926e269SKornel Dulęba 		    ("non-NULL fpcurthread for PCB_FP_NOSAVE"));
4946926e269SKornel Dulęba 		CRITICAL_ASSERT(td);
4956926e269SKornel Dulęba 
4966926e269SKornel Dulęba 		vfp_disable();
4976926e269SKornel Dulęba 		pcb->pcb_fpflags &= ~(PCB_FP_NOSAVE | PCB_FP_STARTED);
4986926e269SKornel Dulęba 		critical_exit();
4996926e269SKornel Dulęba 	} else {
5006926e269SKornel Dulęba 		KASSERT((ctx->flags & FPU_KERN_CTX_INUSE) != 0,
5016926e269SKornel Dulęba 		    ("FPU context not inuse"));
5026926e269SKornel Dulęba 		ctx->flags &= ~FPU_KERN_CTX_INUSE;
5036926e269SKornel Dulęba 
5046926e269SKornel Dulęba 		if (is_fpu_kern_thread(0) &&
5056926e269SKornel Dulęba 		    (ctx->flags & FPU_KERN_CTX_DUMMY) != 0)
5066926e269SKornel Dulęba 			return (0);
5076926e269SKornel Dulęba 		KASSERT((ctx->flags & FPU_KERN_CTX_DUMMY) == 0, ("dummy ctx"));
5086926e269SKornel Dulęba 		critical_enter();
5096926e269SKornel Dulęba 		vfp_discard(td);
5106926e269SKornel Dulęba 		critical_exit();
5116926e269SKornel Dulęba 		pcb->pcb_fpflags &= ~PCB_FP_STARTED;
5126926e269SKornel Dulęba 		pcb->pcb_vfpsaved = ctx->prev;
5136926e269SKornel Dulęba 	}
5146926e269SKornel Dulęba 
5156926e269SKornel Dulęba 	if (pcb->pcb_vfpsaved == &pcb->pcb_vfpstate) {
5166926e269SKornel Dulęba 		pcb->pcb_fpflags &= ~PCB_FP_KERN;
5176926e269SKornel Dulęba 	} else {
5186926e269SKornel Dulęba 		KASSERT((pcb->pcb_fpflags & PCB_FP_KERN) != 0,
5196926e269SKornel Dulęba 		    ("unpaired fpu_kern_leave"));
5206926e269SKornel Dulęba 	}
5216926e269SKornel Dulęba 
5226926e269SKornel Dulęba 	return (0);
5236926e269SKornel Dulęba }
5246926e269SKornel Dulęba 
5256926e269SKornel Dulęba int
fpu_kern_thread(u_int flags __unused)5266926e269SKornel Dulęba fpu_kern_thread(u_int flags __unused)
5276926e269SKornel Dulęba {
5286926e269SKornel Dulęba 	struct pcb *pcb = curthread->td_pcb;
5296926e269SKornel Dulęba 
5306926e269SKornel Dulęba 	KASSERT((curthread->td_pflags & TDP_KTHREAD) != 0,
5316926e269SKornel Dulęba 	    ("Only kthread may use fpu_kern_thread"));
5326926e269SKornel Dulęba 	KASSERT(pcb->pcb_vfpsaved == &pcb->pcb_vfpstate,
5336926e269SKornel Dulęba 	    ("Mangled pcb_vfpsaved"));
5346926e269SKornel Dulęba 	KASSERT((pcb->pcb_fpflags & PCB_FP_KERN) == 0,
5356926e269SKornel Dulęba 	    ("Thread already setup for the VFP"));
5366926e269SKornel Dulęba 	pcb->pcb_fpflags |= PCB_FP_KERN;
5376926e269SKornel Dulęba 	return (0);
5386926e269SKornel Dulęba }
5396926e269SKornel Dulęba 
5406926e269SKornel Dulęba int
is_fpu_kern_thread(u_int flags __unused)5416926e269SKornel Dulęba is_fpu_kern_thread(u_int flags __unused)
5426926e269SKornel Dulęba {
5436926e269SKornel Dulęba 	struct pcb *curpcb;
5446926e269SKornel Dulęba 
5456926e269SKornel Dulęba 	if ((curthread->td_pflags & TDP_KTHREAD) == 0)
5466926e269SKornel Dulęba 		return (0);
5476926e269SKornel Dulęba 	curpcb = curthread->td_pcb;
5486926e269SKornel Dulęba 	return ((curpcb->pcb_fpflags & PCB_FP_KERN) != 0);
5496926e269SKornel Dulęba }
550