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