#include <err.h>
#include <errno.h>
#include <ieeefp.h>
#include <spawn.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <thread.h>
#include <ucontext.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/fp.h>
#include <sys/processor.h>
#include <sys/procset.h>
#include <sys/time.h>
#include <sys/wait.h>
#include "xsave_util.h"
extern char **environ;
#define SPAWN_FPU_RUNTIME_MS 500
#define SPAWN_FPU_NRUNNERS 4
static uint_t failures;
typedef struct {
uint32_t fra_hwsup;
processorid_t fra_cpu;
} fpu_runner_arg_t;
static void *
fpu_runner(void *arg)
{
const fpu_runner_arg_t *ra = arg;
uint32_t seed = 0x80000000;
xsu_fpu_t buf;
if (ra->fra_cpu != -1)
(void) processor_bind(P_LWPID, P_MYID, ra->fra_cpu, NULL);
for (;;) {
xsu_fill(&buf, ra->fra_hwsup, seed);
xsu_setfpu(&buf, ra->fra_hwsup);
seed += 0x1000;
}
return (NULL);
}
static processorid_t
bind_to_cpu(void)
{
long maxcpu = sysconf(_SC_CPUID_MAX);
for (processorid_t cpu = 0; cpu <= maxcpu; cpu++) {
if (processor_bind(P_LWPID, P_MYID, cpu, NULL) == 0)
return (cpu);
}
return (-1);
}
static void
read_initial_fpu(uint32_t *cwp, uint32_t *mxcsrp)
{
ucontext_t uc;
if (getcontext(&uc) != 0)
err(EXIT_FAILURE, "getcontext failed");
#ifdef __amd64
*cwp = uc.uc_mcontext.fpregs.fp_reg_set.fpchip_state.cw & 0xffff;
#else
struct _fpstate fps;
(void) memcpy(&fps, &uc.uc_mcontext.fpregs.fp_reg_set.fpchip_state,
sizeof (fps));
*cwp = fps.cw & 0xffff;
#endif
*mxcsrp = uc.uc_mcontext.fpregs.fp_reg_set.fpchip_state.mxcsr;
}
static int
fpu_child(void)
{
static fpu_runner_arg_t ra;
uint32_t hwsup, cw, mxcsr;
uint32_t seed = 1;
long ncpu;
uint_t nrun;
hrtime_t end;
int ret = EXIT_SUCCESS;
read_initial_fpu(&cw, &mxcsr);
hwsup = xsu_hwsupport();
if (cw != FPU_CW_INIT) {
warnx("initial x87 control word is %#x, expected %#x", cw,
FPU_CW_INIT);
ret = EXIT_FAILURE;
}
if (mxcsr != SSE_MXCSR_INIT) {
warnx("initial MXCSR is %#x, expected %#x", mxcsr,
SSE_MXCSR_INIT);
ret = EXIT_FAILURE;
}
ra.fra_hwsup = hwsup;
ra.fra_cpu = bind_to_cpu();
if (ra.fra_cpu != -1) {
nrun = SPAWN_FPU_NRUNNERS;
} else {
ncpu = sysconf(_SC_NPROCESSORS_ONLN);
if (ncpu < 1)
ncpu = 1;
nrun = 2 * (uint_t)ncpu;
}
for (uint_t i = 0; i < nrun; i++) {
thread_t tid;
int e = thr_create(NULL, 0, fpu_runner, &ra, THR_DETACHED,
&tid);
if (e != 0)
errc(EXIT_FAILURE, e, "failed to create FPU runner");
}
end = gethrtime() + (hrtime_t)SPAWN_FPU_RUNTIME_MS *
(NANOSEC / MILLISEC);
do {
xsu_fpu_t set, got;
xsu_fill(&set, hwsup, seed);
xsu_setfpu(&set, hwsup);
for (volatile uint_t d = 0; d < 10000; d++)
continue;
xsu_getfpu(&got, hwsup);
if (!xsu_same(&set, &got, hwsup)) {
warnx("vector register state was not preserved across "
"a context switch (seed %#x)", seed);
ret = EXIT_FAILURE;
break;
}
seed += 0x40;
} while (gethrtime() < end);
return (ret);
}
static void
run_child(const char *desc, bool use_spawn, const char *path)
{
char *argv[] = { (char *)path, "child", NULL };
pid_t pid;
int status;
if (use_spawn) {
int e = posix_spawn(&pid, path, NULL, NULL, argv, environ);
if (e != 0)
errc(EXIT_FAILURE, e, "posix_spawn of %s failed", path);
} else {
pid = fork();
if (pid == -1)
err(EXIT_FAILURE, "fork failed");
if (pid == 0) {
(void) execv(path, argv);
err(127, "execv of %s failed", path);
}
}
while (waitpid(pid, &status, 0) != pid) {
if (errno != EINTR)
err(EXIT_FAILURE, "waitpid failed");
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) {
(void) fprintf(stderr, "TEST FAILED: %s: child status %#x\n",
desc, status);
failures++;
return;
}
(void) printf("TEST PASSED: %s\n", desc);
}
int
main(int argc, char *argv[])
{
const char *path;
if (argc > 1 && strcmp(argv[1], "child") == 0)
return (fpu_child());
path = getexecname();
if (path == NULL)
errx(EXIT_FAILURE, "could not determine own path");
run_child("fork+exec child FPU initialisation", false, path);
run_child("posix_spawn child FPU initialisation", true, path);
if (failures != 0) {
(void) fprintf(stderr, "%u test(s) failed\n", failures);
return (EXIT_FAILURE);
}
(void) printf("All tests passed\n");
return (EXIT_SUCCESS);
}