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