xref: /illumos-gate/usr/src/test/os-tests/tests/xsave/spawn_fpu.c (revision ac2250cb76bb32944fd2c8a3ba2cd3f79747748d)
1*ac2250cbSAndy Fiddaman /*
2*ac2250cbSAndy Fiddaman  * This file and its contents are supplied under the terms of the
3*ac2250cbSAndy Fiddaman  * Common Development and Distribution License ("CDDL"), version 1.0.
4*ac2250cbSAndy Fiddaman  * You may only use this file in accordance with the terms of version
5*ac2250cbSAndy Fiddaman  * 1.0 of the CDDL.
6*ac2250cbSAndy Fiddaman  *
7*ac2250cbSAndy Fiddaman  * A full copy of the text of the CDDL should have accompanied this
8*ac2250cbSAndy Fiddaman  * source.  A copy of the CDDL is also available via the Internet at
9*ac2250cbSAndy Fiddaman  * http://www.illumos.org/license/CDDL.
10*ac2250cbSAndy Fiddaman  */
11*ac2250cbSAndy Fiddaman 
12*ac2250cbSAndy Fiddaman /*
13*ac2250cbSAndy Fiddaman  * Copyright 2026 Oxide Computer Company
14*ac2250cbSAndy Fiddaman  */
15*ac2250cbSAndy Fiddaman 
16*ac2250cbSAndy Fiddaman /*
17*ac2250cbSAndy Fiddaman  * Verify that a process born via posix_spawn(3C) comes to life with a correctly
18*ac2250cbSAndy Fiddaman  * initialised FPU.
19*ac2250cbSAndy Fiddaman  *
20*ac2250cbSAndy Fiddaman  * Unlike a fork(2) child, a spawned process is created as a bare kernel thread
21*ac2250cbSAndy Fiddaman  * that never passes through forklwp()/fp_new_lwp(), so it inherits neither FPU
22*ac2250cbSAndy Fiddaman  * register state nor the FPU thread context operations from its parent. Both
23*ac2250cbSAndy Fiddaman  * are instead established from scratch by fp_exec() when the child execs its
24*ac2250cbSAndy Fiddaman  * target, which is the same path the kernel uses to bring up init. This test
25*ac2250cbSAndy Fiddaman  * confirms, from inside a spawned child, that:
26*ac2250cbSAndy Fiddaman  *
27*ac2250cbSAndy Fiddaman  *   o the initial x87 control word and MXCSR are the architectural defaults,
28*ac2250cbSAndy Fiddaman  *     proving fp_exec()/fpinit() ran for the spawned LWP;
29*ac2250cbSAndy Fiddaman  *   o the full vector register file survives context switches while sibling
30*ac2250cbSAndy Fiddaman  *     threads hammer the FPU, making it very likely that the context
31*ac2250cbSAndy Fiddaman  *     operations were installed and actually save and restore our state.
32*ac2250cbSAndy Fiddaman  *
33*ac2250cbSAndy Fiddaman  * A fork(2)+exec(2) child is run through the identical checks first as a
34*ac2250cbSAndy Fiddaman  * control. The test needs xsave + AVX (it reuses the xsave test support
35*ac2250cbSAndy Fiddaman  * library) and is gated on that hardware by xsu_hwtype in the runfile.
36*ac2250cbSAndy Fiddaman  */
37*ac2250cbSAndy Fiddaman 
38*ac2250cbSAndy Fiddaman #include <err.h>
39*ac2250cbSAndy Fiddaman #include <errno.h>
40*ac2250cbSAndy Fiddaman #include <ieeefp.h>
41*ac2250cbSAndy Fiddaman #include <spawn.h>
42*ac2250cbSAndy Fiddaman #include <stdint.h>
43*ac2250cbSAndy Fiddaman #include <stdio.h>
44*ac2250cbSAndy Fiddaman #include <stdlib.h>
45*ac2250cbSAndy Fiddaman #include <stdbool.h>
46*ac2250cbSAndy Fiddaman #include <string.h>
47*ac2250cbSAndy Fiddaman #include <thread.h>
48*ac2250cbSAndy Fiddaman #include <ucontext.h>
49*ac2250cbSAndy Fiddaman #include <unistd.h>
50*ac2250cbSAndy Fiddaman #include <sys/types.h>
51*ac2250cbSAndy Fiddaman #include <sys/fp.h>
52*ac2250cbSAndy Fiddaman #include <sys/processor.h>
53*ac2250cbSAndy Fiddaman #include <sys/procset.h>
54*ac2250cbSAndy Fiddaman #include <sys/time.h>
55*ac2250cbSAndy Fiddaman #include <sys/wait.h>
56*ac2250cbSAndy Fiddaman 
57*ac2250cbSAndy Fiddaman #include "xsave_util.h"
58*ac2250cbSAndy Fiddaman 
59*ac2250cbSAndy Fiddaman extern char **environ;
60*ac2250cbSAndy Fiddaman 
61*ac2250cbSAndy Fiddaman /*
62*ac2250cbSAndy Fiddaman  * How long to spend repeatedly reloading and verifying the vector register
63*ac2250cbSAndy Fiddaman  * file while the sibling threads create FPU contention.
64*ac2250cbSAndy Fiddaman  */
65*ac2250cbSAndy Fiddaman #define	SPAWN_FPU_RUNTIME_MS	500
66*ac2250cbSAndy Fiddaman 
67*ac2250cbSAndy Fiddaman /*
68*ac2250cbSAndy Fiddaman  * Number of FPU-hammering sibling threads to share the bound CPU with the
69*ac2250cbSAndy Fiddaman  * thread under test. Pinning a handful to a single CPU gives reasonably
70*ac2250cbSAndy Fiddaman  * reliable contention.
71*ac2250cbSAndy Fiddaman  */
72*ac2250cbSAndy Fiddaman #define	SPAWN_FPU_NRUNNERS	4
73*ac2250cbSAndy Fiddaman 
74*ac2250cbSAndy Fiddaman static uint_t failures;
75*ac2250cbSAndy Fiddaman 
76*ac2250cbSAndy Fiddaman typedef struct {
77*ac2250cbSAndy Fiddaman 	uint32_t	fra_hwsup;
78*ac2250cbSAndy Fiddaman 	processorid_t	fra_cpu;
79*ac2250cbSAndy Fiddaman } fpu_runner_arg_t;
80*ac2250cbSAndy Fiddaman 
81*ac2250cbSAndy Fiddaman /*
82*ac2250cbSAndy Fiddaman  * Continually load the vector registers with a distinct pattern. Run by the
83*ac2250cbSAndy Fiddaman  * sibling threads purely to dirty the physical registers so that, on a kernel
84*ac2250cbSAndy Fiddaman  * that failed to install the spawned thread's FPU context operations, a context
85*ac2250cbSAndy Fiddaman  * switch would leak this state into the thread under test.
86*ac2250cbSAndy Fiddaman  */
87*ac2250cbSAndy Fiddaman static void *
fpu_runner(void * arg)88*ac2250cbSAndy Fiddaman fpu_runner(void *arg)
89*ac2250cbSAndy Fiddaman {
90*ac2250cbSAndy Fiddaman 	const fpu_runner_arg_t *ra = arg;
91*ac2250cbSAndy Fiddaman 	uint32_t seed = 0x80000000;
92*ac2250cbSAndy Fiddaman 	xsu_fpu_t buf;
93*ac2250cbSAndy Fiddaman 
94*ac2250cbSAndy Fiddaman 	if (ra->fra_cpu != -1)
95*ac2250cbSAndy Fiddaman 		(void) processor_bind(P_LWPID, P_MYID, ra->fra_cpu, NULL);
96*ac2250cbSAndy Fiddaman 
97*ac2250cbSAndy Fiddaman 	for (;;) {
98*ac2250cbSAndy Fiddaman 		xsu_fill(&buf, ra->fra_hwsup, seed);
99*ac2250cbSAndy Fiddaman 		xsu_setfpu(&buf, ra->fra_hwsup);
100*ac2250cbSAndy Fiddaman 		seed += 0x1000;
101*ac2250cbSAndy Fiddaman 	}
102*ac2250cbSAndy Fiddaman 
103*ac2250cbSAndy Fiddaman 	/* NOTREACHED */
104*ac2250cbSAndy Fiddaman 	return (NULL);
105*ac2250cbSAndy Fiddaman }
106*ac2250cbSAndy Fiddaman 
107*ac2250cbSAndy Fiddaman static processorid_t
bind_to_cpu(void)108*ac2250cbSAndy Fiddaman bind_to_cpu(void)
109*ac2250cbSAndy Fiddaman {
110*ac2250cbSAndy Fiddaman 	long maxcpu = sysconf(_SC_CPUID_MAX);
111*ac2250cbSAndy Fiddaman 
112*ac2250cbSAndy Fiddaman 	for (processorid_t cpu = 0; cpu <= maxcpu; cpu++) {
113*ac2250cbSAndy Fiddaman 		if (processor_bind(P_LWPID, P_MYID, cpu, NULL) == 0)
114*ac2250cbSAndy Fiddaman 			return (cpu);
115*ac2250cbSAndy Fiddaman 	}
116*ac2250cbSAndy Fiddaman 
117*ac2250cbSAndy Fiddaman 	return (-1);
118*ac2250cbSAndy Fiddaman }
119*ac2250cbSAndy Fiddaman 
120*ac2250cbSAndy Fiddaman /*
121*ac2250cbSAndy Fiddaman  * Snapshot the calling thread's initial FPU control word and MXCSR.
122*ac2250cbSAndy Fiddaman  * getcontext() forces an FPU save, after which we read them from the saved
123*ac2250cbSAndy Fiddaman  * state. Keeping this in its own function means getcontext(), which the
124*ac2250cbSAndy Fiddaman  * compiler treats like setjmp(), does not force the caller's locals volatile.
125*ac2250cbSAndy Fiddaman  */
126*ac2250cbSAndy Fiddaman static void
read_initial_fpu(uint32_t * cwp,uint32_t * mxcsrp)127*ac2250cbSAndy Fiddaman read_initial_fpu(uint32_t *cwp, uint32_t *mxcsrp)
128*ac2250cbSAndy Fiddaman {
129*ac2250cbSAndy Fiddaman 	ucontext_t uc;
130*ac2250cbSAndy Fiddaman 
131*ac2250cbSAndy Fiddaman 	if (getcontext(&uc) != 0)
132*ac2250cbSAndy Fiddaman 		err(EXIT_FAILURE, "getcontext failed");
133*ac2250cbSAndy Fiddaman 
134*ac2250cbSAndy Fiddaman 	/*
135*ac2250cbSAndy Fiddaman 	 * The x87 control word is a named field on amd64. On i386 the
136*ac2250cbSAndy Fiddaman 	 * fpchip_state is the opaque legacy save area, so we overlay the ILP32
137*ac2250cbSAndy Fiddaman 	 * struct _fpstate to read it, as xsave_util.c does.
138*ac2250cbSAndy Fiddaman 	 */
139*ac2250cbSAndy Fiddaman #ifdef __amd64
140*ac2250cbSAndy Fiddaman 	*cwp = uc.uc_mcontext.fpregs.fp_reg_set.fpchip_state.cw & 0xffff;
141*ac2250cbSAndy Fiddaman #else
142*ac2250cbSAndy Fiddaman 	struct _fpstate fps;
143*ac2250cbSAndy Fiddaman 
144*ac2250cbSAndy Fiddaman 	(void) memcpy(&fps, &uc.uc_mcontext.fpregs.fp_reg_set.fpchip_state,
145*ac2250cbSAndy Fiddaman 	    sizeof (fps));
146*ac2250cbSAndy Fiddaman 	*cwp = fps.cw & 0xffff;
147*ac2250cbSAndy Fiddaman #endif
148*ac2250cbSAndy Fiddaman 	*mxcsrp = uc.uc_mcontext.fpregs.fp_reg_set.fpchip_state.mxcsr;
149*ac2250cbSAndy Fiddaman }
150*ac2250cbSAndy Fiddaman 
151*ac2250cbSAndy Fiddaman static int
fpu_child(void)152*ac2250cbSAndy Fiddaman fpu_child(void)
153*ac2250cbSAndy Fiddaman {
154*ac2250cbSAndy Fiddaman 	static fpu_runner_arg_t ra;
155*ac2250cbSAndy Fiddaman 	uint32_t hwsup, cw, mxcsr;
156*ac2250cbSAndy Fiddaman 	uint32_t seed = 1;
157*ac2250cbSAndy Fiddaman 	long ncpu;
158*ac2250cbSAndy Fiddaman 	uint_t nrun;
159*ac2250cbSAndy Fiddaman 	hrtime_t end;
160*ac2250cbSAndy Fiddaman 	int ret = EXIT_SUCCESS;
161*ac2250cbSAndy Fiddaman 
162*ac2250cbSAndy Fiddaman 	read_initial_fpu(&cw, &mxcsr);
163*ac2250cbSAndy Fiddaman 
164*ac2250cbSAndy Fiddaman 	hwsup = xsu_hwsupport();
165*ac2250cbSAndy Fiddaman 
166*ac2250cbSAndy Fiddaman 	if (cw != FPU_CW_INIT) {
167*ac2250cbSAndy Fiddaman 		warnx("initial x87 control word is %#x, expected %#x", cw,
168*ac2250cbSAndy Fiddaman 		    FPU_CW_INIT);
169*ac2250cbSAndy Fiddaman 		ret = EXIT_FAILURE;
170*ac2250cbSAndy Fiddaman 	}
171*ac2250cbSAndy Fiddaman 	if (mxcsr != SSE_MXCSR_INIT) {
172*ac2250cbSAndy Fiddaman 		warnx("initial MXCSR is %#x, expected %#x", mxcsr,
173*ac2250cbSAndy Fiddaman 		    SSE_MXCSR_INIT);
174*ac2250cbSAndy Fiddaman 		ret = EXIT_FAILURE;
175*ac2250cbSAndy Fiddaman 	}
176*ac2250cbSAndy Fiddaman 
177*ac2250cbSAndy Fiddaman 	/*
178*ac2250cbSAndy Fiddaman 	 * Force the thread under test to share a CPU with FPU-hammering
179*ac2250cbSAndy Fiddaman 	 * siblings.
180*ac2250cbSAndy Fiddaman 	 */
181*ac2250cbSAndy Fiddaman 	ra.fra_hwsup = hwsup;
182*ac2250cbSAndy Fiddaman 	ra.fra_cpu = bind_to_cpu();
183*ac2250cbSAndy Fiddaman 	if (ra.fra_cpu != -1) {
184*ac2250cbSAndy Fiddaman 		nrun = SPAWN_FPU_NRUNNERS;
185*ac2250cbSAndy Fiddaman 	} else {
186*ac2250cbSAndy Fiddaman 		ncpu = sysconf(_SC_NPROCESSORS_ONLN);
187*ac2250cbSAndy Fiddaman 		if (ncpu < 1)
188*ac2250cbSAndy Fiddaman 			ncpu = 1;
189*ac2250cbSAndy Fiddaman 		nrun = 2 * (uint_t)ncpu;
190*ac2250cbSAndy Fiddaman 	}
191*ac2250cbSAndy Fiddaman 
192*ac2250cbSAndy Fiddaman 	for (uint_t i = 0; i < nrun; i++) {
193*ac2250cbSAndy Fiddaman 		thread_t tid;
194*ac2250cbSAndy Fiddaman 		int e = thr_create(NULL, 0, fpu_runner, &ra, THR_DETACHED,
195*ac2250cbSAndy Fiddaman 		    &tid);
196*ac2250cbSAndy Fiddaman 		if (e != 0)
197*ac2250cbSAndy Fiddaman 			errc(EXIT_FAILURE, e, "failed to create FPU runner");
198*ac2250cbSAndy Fiddaman 	}
199*ac2250cbSAndy Fiddaman 
200*ac2250cbSAndy Fiddaman 	/*
201*ac2250cbSAndy Fiddaman 	 * Load a known pattern and read it straight back. There is no
202*ac2250cbSAndy Fiddaman 	 * FPU-clobbering call between the two so the pattern should
203*ac2250cbSAndy Fiddaman 	 * survive even if we are preempted in the window.
204*ac2250cbSAndy Fiddaman 	 */
205*ac2250cbSAndy Fiddaman 	end = gethrtime() + (hrtime_t)SPAWN_FPU_RUNTIME_MS *
206*ac2250cbSAndy Fiddaman 	    (NANOSEC / MILLISEC);
207*ac2250cbSAndy Fiddaman 	do {
208*ac2250cbSAndy Fiddaman 		xsu_fpu_t set, got;
209*ac2250cbSAndy Fiddaman 
210*ac2250cbSAndy Fiddaman 		xsu_fill(&set, hwsup, seed);
211*ac2250cbSAndy Fiddaman 		xsu_setfpu(&set, hwsup);
212*ac2250cbSAndy Fiddaman 
213*ac2250cbSAndy Fiddaman 		/*
214*ac2250cbSAndy Fiddaman 		 * Widen the window in which our vector state has to survive a
215*ac2250cbSAndy Fiddaman 		 * context switch. This spin only touches integer registers, so
216*ac2250cbSAndy Fiddaman 		 * it cannot disturb the FPU state we just loaded.
217*ac2250cbSAndy Fiddaman 		 */
218*ac2250cbSAndy Fiddaman 		for (volatile uint_t d = 0; d < 10000; d++)
219*ac2250cbSAndy Fiddaman 			continue;
220*ac2250cbSAndy Fiddaman 
221*ac2250cbSAndy Fiddaman 		xsu_getfpu(&got, hwsup);
222*ac2250cbSAndy Fiddaman 		if (!xsu_same(&set, &got, hwsup)) {
223*ac2250cbSAndy Fiddaman 			warnx("vector register state was not preserved across "
224*ac2250cbSAndy Fiddaman 			    "a context switch (seed %#x)", seed);
225*ac2250cbSAndy Fiddaman 			ret = EXIT_FAILURE;
226*ac2250cbSAndy Fiddaman 			break;
227*ac2250cbSAndy Fiddaman 		}
228*ac2250cbSAndy Fiddaman 		seed += 0x40;
229*ac2250cbSAndy Fiddaman 	} while (gethrtime() < end);
230*ac2250cbSAndy Fiddaman 
231*ac2250cbSAndy Fiddaman 	return (ret);
232*ac2250cbSAndy Fiddaman }
233*ac2250cbSAndy Fiddaman 
234*ac2250cbSAndy Fiddaman static void
run_child(const char * desc,bool use_spawn,const char * path)235*ac2250cbSAndy Fiddaman run_child(const char *desc, bool use_spawn, const char *path)
236*ac2250cbSAndy Fiddaman {
237*ac2250cbSAndy Fiddaman 	char *argv[] = { (char *)path, "child", NULL };
238*ac2250cbSAndy Fiddaman 	pid_t pid;
239*ac2250cbSAndy Fiddaman 	int status;
240*ac2250cbSAndy Fiddaman 
241*ac2250cbSAndy Fiddaman 	if (use_spawn) {
242*ac2250cbSAndy Fiddaman 		int e = posix_spawn(&pid, path, NULL, NULL, argv, environ);
243*ac2250cbSAndy Fiddaman 		if (e != 0)
244*ac2250cbSAndy Fiddaman 			errc(EXIT_FAILURE, e, "posix_spawn of %s failed", path);
245*ac2250cbSAndy Fiddaman 	} else {
246*ac2250cbSAndy Fiddaman 		pid = fork();
247*ac2250cbSAndy Fiddaman 		if (pid == -1)
248*ac2250cbSAndy Fiddaman 			err(EXIT_FAILURE, "fork failed");
249*ac2250cbSAndy Fiddaman 		if (pid == 0) {
250*ac2250cbSAndy Fiddaman 			(void) execv(path, argv);
251*ac2250cbSAndy Fiddaman 			err(127, "execv of %s failed", path);
252*ac2250cbSAndy Fiddaman 		}
253*ac2250cbSAndy Fiddaman 	}
254*ac2250cbSAndy Fiddaman 
255*ac2250cbSAndy Fiddaman 	while (waitpid(pid, &status, 0) != pid) {
256*ac2250cbSAndy Fiddaman 		if (errno != EINTR)
257*ac2250cbSAndy Fiddaman 			err(EXIT_FAILURE, "waitpid failed");
258*ac2250cbSAndy Fiddaman 	}
259*ac2250cbSAndy Fiddaman 
260*ac2250cbSAndy Fiddaman 	if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) {
261*ac2250cbSAndy Fiddaman 		(void) fprintf(stderr, "TEST FAILED: %s: child status %#x\n",
262*ac2250cbSAndy Fiddaman 		    desc, status);
263*ac2250cbSAndy Fiddaman 		failures++;
264*ac2250cbSAndy Fiddaman 		return;
265*ac2250cbSAndy Fiddaman 	}
266*ac2250cbSAndy Fiddaman 
267*ac2250cbSAndy Fiddaman 	(void) printf("TEST PASSED: %s\n", desc);
268*ac2250cbSAndy Fiddaman }
269*ac2250cbSAndy Fiddaman 
270*ac2250cbSAndy Fiddaman int
main(int argc,char * argv[])271*ac2250cbSAndy Fiddaman main(int argc, char *argv[])
272*ac2250cbSAndy Fiddaman {
273*ac2250cbSAndy Fiddaman 	const char *path;
274*ac2250cbSAndy Fiddaman 
275*ac2250cbSAndy Fiddaman 	if (argc > 1 && strcmp(argv[1], "child") == 0)
276*ac2250cbSAndy Fiddaman 		return (fpu_child());
277*ac2250cbSAndy Fiddaman 
278*ac2250cbSAndy Fiddaman 	path = getexecname();
279*ac2250cbSAndy Fiddaman 	if (path == NULL)
280*ac2250cbSAndy Fiddaman 		errx(EXIT_FAILURE, "could not determine own path");
281*ac2250cbSAndy Fiddaman 
282*ac2250cbSAndy Fiddaman 	run_child("fork+exec child FPU initialisation", false, path);
283*ac2250cbSAndy Fiddaman 	run_child("posix_spawn child FPU initialisation", true, path);
284*ac2250cbSAndy Fiddaman 
285*ac2250cbSAndy Fiddaman 	if (failures != 0) {
286*ac2250cbSAndy Fiddaman 		(void) fprintf(stderr, "%u test(s) failed\n", failures);
287*ac2250cbSAndy Fiddaman 		return (EXIT_FAILURE);
288*ac2250cbSAndy Fiddaman 	}
289*ac2250cbSAndy Fiddaman 
290*ac2250cbSAndy Fiddaman 	(void) printf("All tests passed\n");
291*ac2250cbSAndy Fiddaman 	return (EXIT_SUCCESS);
292*ac2250cbSAndy Fiddaman }
293