head	1.3;
access;
symbols
	netbsd-11-0-RC7:1.3.2.2
	netbsd-11:1.3.0.2;
locks; strict;
comment	@ * @;


1.3
date	2026.07.15.02.08.18;	author riastradh;	state Exp;
branches
	1.3.2.1;
next	1.2;
commitid	J0Zn7X9xucdCYGNG;

1.2
date	2026.07.15.01.02.42;	author riastradh;	state Exp;
branches;
next	1.1;
commitid	QvOzfUmF8kd7CGNG;

1.1
date	2026.07.09.02.05.45;	author riastradh;	state Exp;
branches;
next	;
commitid	Xe3vv8FEcLUo9VMG;

1.3.2.1
date	2026.07.15.02.08.18;	author martin;	state dead;
branches;
next	1.3.2.2;
commitid	UTsqisx2UKr5rhOG;

1.3.2.2
date	2026.07.19.15.57.28;	author martin;	state Exp;
branches;
next	;
commitid	UTsqisx2UKr5rhOG;


desc
@@


1.3
log
@t_signal_and_fpu: Fix XMM register trashing.

The units we read from memory into registers are 16 bytes (128 bits)
each, not 32 bytes (256 bits).

Fortunately, this mistake didn't prevent the test from generally
working: reading past the end of our stack frame will just give us
other garbage that is unlikely to coincide with the register content
that the interrupted code expects.

PR kern/60426: Signal handler corrupts AVX (YMM) registers
@
text
@/*	$NetBSD: sig_fpu.c,v 1.2 2026/07/15 01:02:42 riastradh Exp $	*/

/*
 * Copyright (c) 2026 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: sig_fpu.c,v 1.2 2026/07/15 01:02:42 riastradh Exp $");

#include "sig_fpu.h"

#include <sys/types.h>

#include <x86/cpu_extended_state.h>

#include <cpuid.h>
#include <inttypes.h>
#include <sched.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>

#include "h_macros.h"

#ifdef __x86_64__
#  define	NXMMREGS	16
#  define	NYMMREGS	16
#  define	NZMMREGS	32
#else  /* 32-bit */
#  define	NXMMREGS	8
#  define	NYMMREGS	8
#  define	NZMMREGS	8
#endif

#define	X87_CW_RC	__BITS(11,10) /* rounding control (mode) */
#define	X87_CW_PC	__BITS(9,8)   /* precision control */
#define	X87_CW_SW_EXC	__BITS(5,0)   /* exception bits */

struct cpuid {
	uint32_t edx;
	uint32_t ecx;
	uint32_t ebx;
	uint32_t eax;
};

static struct cpuid
cpuid(uint32_t eax_fn, uint32_t ecx_idx)
{
	struct cpuid c;

	if (!__get_cpuid_count(eax_fn, ecx_idx,
		&c.eax, &c.ebx, &c.ecx, &c.edx))
		memset(&c, 0, sizeof(c));

	return c;
}

__printflike(4,5)
__noinline
static void
hexdump(FILE *fp, const void *buf, size_t len, const char *title, ...)
{
	va_list va;
	const uint8_t *p = buf;
	size_t i;

	fprintf(fp, "# ");
	va_start(va, title);
	vfprintf(fp, title, va);
	va_end(va);
	fprintf(fp, "\n");
	for (i = 0; i < len; i++) {
		if ((i % 8) == 0)
			fprintf(fp, " ");
		fprintf(fp, " %02hhx", p[i]);
		if ((i % 16) == 15)
			fprintf(fp, "\n");
	}
	if (i % 16)
		fprintf(fp, "\n");
}

bool
x87_supported(void)
{
#ifdef __x86_64__
	return true;
#else  /* 32-bit */
	return cpuid(0x00000001, 0).edx & __BIT(0);
#endif
}

int
test_x87(volatile bool *ready, const volatile bool *done)
{
	struct save87 s, s1;
	uint32_t rcpc_exc;
	int error = 0;

	/*
	 * Gather the current FPU state so we have some reasonable
	 * content for the weird stuff when we restore from it.
	 */
	asm("wait\n"
	    "	fnsave	%0"
	    : /*out*/ "=m"(s));

	/*
	 * Randomize the floating-point stack entries ST(0),...,ST(7).
	 */
	arc4random_buf(s.s87_ac, sizeof(s.s87_ac));

	/*
	 * Randomize rounding control, precision control, and a set of
	 * exception bits.  We don't want to raise any unmasked
	 * exceptions, so we'll raise _and_ mask them.
	 *
	 * XXX Consider testing the condition code, exception status,
	 * and top of stack bits in the status word too.
	 */
	rcpc_exc = arc4random() & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC);
	if (__SHIFTOUT(rcpc_exc, X87_CW_PC) == 1) /* reserved */
		rcpc_exc |= __SHIFTIN(3, X87_CW_PC); /* extended */
	s.s87_cw &= ~(X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC);
	s.s87_cw |= rcpc_exc;
	s.s87_sw &= ~X87_CW_SW_EXC;
	s.s87_sw |= rcpc_exc & X87_CW_SW_EXC;

	/*
	 * Load up the registers, report that we're ready, busy-wait
	 * with the registers still live -- so no subroutine calls --
	 * until we're done, and then store the registers back to
	 * memory so we can check them.
	 */
	asm("\n"
	    "	frstor	%[s]\n"
	    "	lock\n"
	    "	orb	$1,%[ready]\n"
	    "0:	pause\n"
	    "	cmpb	$0,%[done]\n"
	    "	lfence\n"
	    "	je	0b\n"
	    "	wait\n"
	    "	fnsave	%[s1]\n"
	    : /*out*/ [ready]"=m"(*ready), [s1]"=m"(s1)
	    : /*in*/ [done]"m"(*done), [s]"m"(s));

	/*
	 * If there are any mismatches between the before and after
	 * ST(i) registers, or the control word, or the status word,
	 * print them and fail.
	 */
	if (memcmp(s.s87_ac, s1.s87_ac, sizeof(s.s87_ac)) != 0) {
		unsigned i;

		for (i = 0; i < 8; i++) {
			if (memcmp(&s.s87_ac[i], &s1.s87_ac[i],
				sizeof(s.s87_ac[i])) == 0)
				continue;
			fprintf(stderr, "ST(%u)=0x%08x%016"PRIx64","
			    " expected 0x%08x%016"PRIx64"\n",
			    i,
			    s1.s87_ac[i].f87_exp_sign,
			    s1.s87_ac[i].f87_mantissa,
			    s.s87_ac[i].f87_exp_sign,
			    s.s87_ac[i].f87_mantissa);
			error |= 1 << i;
		}
	}
	if ((s1.s87_cw & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC)) !=
	    (s.s87_cw & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC))) {
		fprintf(stderr, "cw=0x%04x, expected 0x%04x,"
		    " diff 0x%04x\n",
		    s1.s87_cw, s.s87_cw,
		    (uint32_t)((s1.s87_cw ^ s.s87_cw) &
			(X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC)));
		error |= 1 << 8;
	}
	if ((s1.s87_sw & X87_CW_SW_EXC) !=
	    (s.s87_cw & X87_CW_SW_EXC)) {
		fprintf(stderr, "sw=0x%04x, expected 0x%04x,"
		    " diff 0x%04x\n",
		    s1.s87_sw, s.s87_sw,
		    (uint32_t)((s1.s87_sw ^ s.s87_sw) &
			X87_CW_SW_EXC));
		error |= 1 << 9;
	}

	return error;
}

void
trash_x87(void)
{
	struct save87 s;
	uint32_t rcpc_exc;

	/*
	 * Gather the current FPU state so we have some reasonable
	 * content for the weird stuff when we restore from it.
	 */
	asm("wait\n"
	    "	fnsave	%0"
	    : /*out*/ "=m"(s));

	/*
	 * Randomize the floating-point stack entries ST(0),...,ST(7).
	 */
	arc4random_buf(s.s87_ac, sizeof(s.s87_ac));

	/*
	 * Randomize rounding control, precision control, and a set of
	 * exception bits.  We don't want to raise any unmasked
	 * exceptions, so we'll raise _and_ mask them.
	 *
	 * XXX Consider trashing the condition code, exception status,
	 * and top of stack bits in the status word too.
	 */
	rcpc_exc = arc4random() & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC);
	s.s87_cw &= ~(X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC);
	s.s87_cw |= rcpc_exc;
	s.s87_sw &= ~X87_CW_SW_EXC;
	s.s87_sw |= rcpc_exc & X87_CW_SW_EXC;

	/*
	 * Load the state into the FPU.
	 */
	asm volatile("frstor %0" :: /*in*/ "m"(s));
}

#define	MXCSR_MISALIGNEDEXC	__BIT(17)	/* misaligned exc fault */
#define	MXCSR_FTZ		__BIT(15)	/* flush to zero */
#define	MXCSR_RC		__BITS(14,13)	/* rounding control */
#define	MXCSR_EXCMASK		__BITS(12,7)	/* exceptions masked */
#define	MXCSR_DAZ		__BIT(6)	/* denormals are zero */
#define	MXCSR_EXCFLAG		__BITS(5,0)	/* exceptions raised */

struct xmmregs {
	struct {
		uint8_t b[16];
	} __aligned(16)	xmm[NXMMREGS];
	uint32_t mxcsr;
};

bool
xmm_supported(void)
{
#ifdef __x86_64__
	return true;
#else  /* 32-bit */
	return cpuid(0x00000001, 0).edx & __BIT(25);
#endif
}

__attribute__((target("sse")))
int
test_xmm(volatile bool *ready, const volatile bool *done)
{
	struct xmmregs before, after;
	uint32_t exc;
	unsigned i;
	int error = 0;

	/*
	 * Randomize the xmm register content.  Randomize plausible
	 * bits for the mxcsr.
	 */
	arc4random_buf(before.xmm, sizeof(before.xmm));
	before.mxcsr = arc4random() & (MXCSR_FTZ | MXCSR_RC | MXCSR_DAZ);
	exc = arc4random() & 0x3f;
	before.mxcsr |= __SHIFTIN(exc, MXCSR_EXCMASK);
	before.mxcsr |= __SHIFTIN(exc, MXCSR_EXCFLAG);

	/*
	 * Load up the registers, report that we're ready, busy-wait
	 * with the registers still live -- so no subroutine calls --
	 * until we're done, and then store the registers back to
	 * memory so we can check them.
	 */
	asm("\n"
	    "	ldmxcsr	%[mxcsr_before]\n"
	    "	movdqa	0*16(%[xmm_before]),%%xmm0\n"
	    "	movdqa	1*16(%[xmm_before]),%%xmm1\n"
	    "	movdqa	2*16(%[xmm_before]),%%xmm2\n"
	    "	movdqa	3*16(%[xmm_before]),%%xmm3\n"
	    "	movdqa	4*16(%[xmm_before]),%%xmm4\n"
	    "	movdqa	5*16(%[xmm_before]),%%xmm5\n"
	    "	movdqa	6*16(%[xmm_before]),%%xmm6\n"
	    "	movdqa	7*16(%[xmm_before]),%%xmm7\n"
#ifdef __x86_64__
	    "	movdqa	8*16(%[xmm_before]),%%xmm8\n"
	    "	movdqa	9*16(%[xmm_before]),%%xmm9\n"
	    "	movdqa	10*16(%[xmm_before]),%%xmm10\n"
	    "	movdqa	11*16(%[xmm_before]),%%xmm11\n"
	    "	movdqa	12*16(%[xmm_before]),%%xmm12\n"
	    "	movdqa	13*16(%[xmm_before]),%%xmm13\n"
	    "	movdqa	14*16(%[xmm_before]),%%xmm14\n"
	    "	movdqa	15*16(%[xmm_before]),%%xmm15\n"
#endif
	    "	lock\n"
	    "	orb	$1,%[ready]\n"
	    "0:	pause\n"
	    "	cmpb	$0,%[done]\n"
	    "	lfence\n"
	    "	je	0b\n"
	    "	movdqa	%%xmm0,0*16(%[xmm_after])\n"
	    "	movdqa	%%xmm1,1*16(%[xmm_after])\n"
	    "	movdqa	%%xmm2,2*16(%[xmm_after])\n"
	    "	movdqa	%%xmm3,3*16(%[xmm_after])\n"
	    "	movdqa	%%xmm4,4*16(%[xmm_after])\n"
	    "	movdqa	%%xmm5,5*16(%[xmm_after])\n"
	    "	movdqa	%%xmm6,6*16(%[xmm_after])\n"
	    "	movdqa	%%xmm7,7*16(%[xmm_after])\n"
#ifdef __x86_64__
	    "	movdqa	%%xmm8,8*16(%[xmm_after])\n"
	    "	movdqa	%%xmm9,9*16(%[xmm_after])\n"
	    "	movdqa	%%xmm10,10*16(%[xmm_after])\n"
	    "	movdqa	%%xmm11,11*16(%[xmm_after])\n"
	    "	movdqa	%%xmm12,12*16(%[xmm_after])\n"
	    "	movdqa	%%xmm13,13*16(%[xmm_after])\n"
	    "	movdqa	%%xmm14,14*16(%[xmm_after])\n"
	    "	movdqa	%%xmm15,15*16(%[xmm_after])\n"
#endif
	    "	stmxcsr	%[mxcsr_after]\n"
	    : /*out*/ [ready]"=m"(*ready), "=m"(after.xmm),
	      [mxcsr_after]"=m"(after.mxcsr)
	    : /*in*/ [done]"m"(*done), "m"(before.xmm),
	      [mxcsr_before]"m"(before.mxcsr),
	      [xmm_before]"r"(&before.xmm), [xmm_after]"r"(&after.xmm)
	    : /*clobber*/ "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5",
	      "xmm6", "xmm7"
#ifdef __x86_64__
	    , "xmm8", "xmm9", "xmm10", "xmm11", "xmm12",
	      "xmm13", "xmm14", "xmm15"
#endif
	);

	/*
	 * If there are any mismatches between the before and after xmm
	 * registers, or the mxcsr, print them and fail.
	 */
	for (i = 0; i < __arraycount(before.xmm); i++) {
		if (memcmp(&before.xmm[i], &after.xmm[i],
			sizeof(before.xmm[i]))) {
			fprintf(stderr, "xmm%u clobbered\n", i);
			hexdump(stderr, &before.xmm[i], sizeof(before.xmm[i]),
			    "before");
			hexdump(stderr, &after.xmm[i], sizeof(after.xmm[i]),
			    "after");
			error |= 1 << i;
		}
	}

	if (before.mxcsr != after.mxcsr) {
		fprintf(stderr, "mxcsr clobbered:"
		    " before=0x%08"PRIx32", after=0x%08"PRIx32"\n",
		    before.mxcsr, after.mxcsr);
		error |= 1 << 16;
	}

	return error;
}

__attribute__((target("sse")))
void
trash_xmm(void)
{
	struct xmmregs regs;

	arc4random_buf(&regs, sizeof(regs));

	asm("\n"
	    "	movdqa	0*16(%[xmm]),%%xmm0\n"
	    "	movdqa	1*16(%[xmm]),%%xmm1\n"
	    "	movdqa	2*16(%[xmm]),%%xmm2\n"
	    "	movdqa	3*16(%[xmm]),%%xmm3\n"
	    "	movdqa	4*16(%[xmm]),%%xmm4\n"
	    "	movdqa	5*16(%[xmm]),%%xmm5\n"
	    "	movdqa	6*16(%[xmm]),%%xmm6\n"
	    "	movdqa	7*16(%[xmm]),%%xmm7\n"
#ifdef __x86_64__
	    "	movdqa	8*16(%[xmm]),%%xmm8\n"
	    "	movdqa	9*16(%[xmm]),%%xmm9\n"
	    "	movdqa	10*16(%[xmm]),%%xmm10\n"
	    "	movdqa	11*16(%[xmm]),%%xmm11\n"
	    "	movdqa	12*16(%[xmm]),%%xmm12\n"
	    "	movdqa	13*16(%[xmm]),%%xmm13\n"
	    "	movdqa	14*16(%[xmm]),%%xmm14\n"
	    "	movdqa	15*16(%[xmm]),%%xmm15\n"
#endif
	    : /*out*/
	    : /*in*/ [xmm]"r"(regs.xmm), "m"(regs.xmm)
	    : /*clobber*/ "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5",
	      "xmm6", "xmm7"
#ifdef __x86_64__
	    , "xmm8", "xmm9", "xmm10", "xmm11", "xmm12",
	      "xmm13", "xmm14", "xmm15"
#endif
	);
}

bool
ymm_supported(void)
{
	return cpuid(0x00000001, 0).ecx & __BIT(28);
}

struct ymmregs {
	struct {
		uint8_t b[32];
	} __aligned(32)	ymm[NYMMREGS];
};

__attribute__((target("avx")))
int
test_ymm(volatile bool *ready, const volatile bool *done)
{
	struct ymmregs before, after;
	unsigned i;
	int error = 0;

	/*
	 * Randomize the ymm register content.
	 */
	arc4random_buf(&before, sizeof(before));

	/*
	 * Load up the registers, report that we're ready, busy-wait
	 * with the registers still live -- so no subroutine calls --
	 * until we're done, and then store the registers back to
	 * memory so we can check them.
	 */
	asm("\n"
	    "	vmovdqa	0*32(%[before_ymm]),%%ymm0\n"
	    "	vmovdqa	1*32(%[before_ymm]),%%ymm1\n"
	    "	vmovdqa	2*32(%[before_ymm]),%%ymm2\n"
	    "	vmovdqa	3*32(%[before_ymm]),%%ymm3\n"
	    "	vmovdqa	4*32(%[before_ymm]),%%ymm4\n"
	    "	vmovdqa	5*32(%[before_ymm]),%%ymm5\n"
	    "	vmovdqa	6*32(%[before_ymm]),%%ymm6\n"
	    "	vmovdqa	7*32(%[before_ymm]),%%ymm7\n"
#ifdef __x86_64__
	    "	vmovdqa	8*32(%[before_ymm]),%%ymm8\n"
	    "	vmovdqa	9*32(%[before_ymm]),%%ymm9\n"
	    "	vmovdqa	10*32(%[before_ymm]),%%ymm10\n"
	    "	vmovdqa	11*32(%[before_ymm]),%%ymm11\n"
	    "	vmovdqa	12*32(%[before_ymm]),%%ymm12\n"
	    "	vmovdqa	13*32(%[before_ymm]),%%ymm13\n"
	    "	vmovdqa	14*32(%[before_ymm]),%%ymm14\n"
	    "	vmovdqa	15*32(%[before_ymm]),%%ymm15\n"
#endif
	    "	lock\n"
	    "	orb	$1,%[ready]\n"
	    "0:	pause\n"
	    "	cmpb	$0,%[done]\n"
	    "	lfence\n"
	    "	je	0b\n"
	    "	vmovdqa	%%ymm0,0*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm1,1*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm2,2*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm3,3*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm4,4*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm5,5*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm6,6*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm7,7*32(%[after_ymm])\n"
#ifdef __x86_64__
	    "	vmovdqa	%%ymm8,8*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm9,9*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm10,10*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm11,11*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm12,12*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm13,13*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm14,14*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm15,15*32(%[after_ymm])\n"
#endif
	    : /*out*/ [ready]"=m"(*ready), "=m"(after.ymm)
	    : /*in*/ [done]"m"(*done), "m"(before.ymm),
	      [before_ymm]"r"(before.ymm), [after_ymm]"r"(after.ymm)
	    : /*clobber*/ "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5",
	      "ymm6", "ymm7"
#ifdef __x86_64__
	    , "ymm8", "ymm9", "ymm10", "ymm11", "ymm12",
	      "ymm13", "ymm14", "ymm15"
#endif
	);

	for (i = 0; i < __arraycount(before.ymm); i++) {
		if (memcmp(&before.ymm[i], &after.ymm[i],
			sizeof(before.ymm[i]))) {
			fprintf(stderr, "ymm%u clobbered\n", i);
			hexdump(stderr, &before.ymm[i], sizeof(before.ymm[i]),
			    "before");
			hexdump(stderr, &after.ymm[i], sizeof(after.ymm[i]),
			    "after");
			error |= 1 << i;
		}
	}

	return error;
}

__attribute__((target("avx")))
void
trash_ymm(void)
{
	struct ymmregs regs;

	arc4random_buf(&regs, sizeof(regs));

	asm("\n"
	    "	vmovdqa	0*32(%[ymm]),%%ymm0\n"
	    "	vmovdqa	1*32(%[ymm]),%%ymm1\n"
	    "	vmovdqa	2*32(%[ymm]),%%ymm2\n"
	    "	vmovdqa	3*32(%[ymm]),%%ymm3\n"
	    "	vmovdqa	4*32(%[ymm]),%%ymm4\n"
	    "	vmovdqa	5*32(%[ymm]),%%ymm5\n"
	    "	vmovdqa	6*32(%[ymm]),%%ymm6\n"
	    "	vmovdqa	7*32(%[ymm]),%%ymm7\n"
#ifdef __x86_64__
	    "	vmovdqa	8*32(%[ymm]),%%ymm8\n"
	    "	vmovdqa	9*32(%[ymm]),%%ymm9\n"
	    "	vmovdqa	10*32(%[ymm]),%%ymm10\n"
	    "	vmovdqa	11*32(%[ymm]),%%ymm11\n"
	    "	vmovdqa	12*32(%[ymm]),%%ymm12\n"
	    "	vmovdqa	13*32(%[ymm]),%%ymm13\n"
	    "	vmovdqa	14*32(%[ymm]),%%ymm14\n"
	    "	vmovdqa	15*32(%[ymm]),%%ymm15\n"
#endif
	    : /*out*/
	    : /*in*/ "m"(regs.ymm), [ymm]"r"(regs.ymm)
	    : /*clobber*/ "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5",
	      "ymm6", "ymm7"
#ifdef __x86_64__
	    , "ymm8", "ymm9", "ymm10", "ymm11", "ymm12",
	      "ymm13", "ymm14", "ymm15"
#endif
	);
}

bool
zmm_supported(void)
{
	return cpuid(0x00000007, 0).ebx & __BIT(16);
}

struct zmmregs {
	struct {
		uint8_t b[64];
	} __aligned(64)	zmm[NZMMREGS];
};

__attribute__((target("avx512f")))
int
test_zmm(volatile bool *ready, const volatile bool *done)
{
	struct zmmregs before, after;
	unsigned i;
	int error = 0;

	/*
	 * Randomize the zmm register content.
	 */
	arc4random_buf(&before, sizeof(before));

	/*
	 * Load up the registers, report that we're ready, busy-wait
	 * with the registers still live -- so no subroutine calls --
	 * until we're done, and then store the registers back to
	 * memory so we can check them.
	 */
	asm("\n"
	    "	vmovdqa64	0*64(%[before_zmm]),%%zmm0\n"
	    "	vmovdqa64	1*64(%[before_zmm]),%%zmm1\n"
	    "	vmovdqa64	2*64(%[before_zmm]),%%zmm2\n"
	    "	vmovdqa64	3*64(%[before_zmm]),%%zmm3\n"
	    "	vmovdqa64	4*64(%[before_zmm]),%%zmm4\n"
	    "	vmovdqa64	5*64(%[before_zmm]),%%zmm5\n"
	    "	vmovdqa64	6*64(%[before_zmm]),%%zmm6\n"
	    "	vmovdqa64	7*64(%[before_zmm]),%%zmm7\n"
#ifdef __x86_64__
	    "	vmovdqa64	8*64(%[before_zmm]),%%zmm8\n"
	    "	vmovdqa64	9*64(%[before_zmm]),%%zmm9\n"
	    "	vmovdqa64	10*64(%[before_zmm]),%%zmm10\n"
	    "	vmovdqa64	11*64(%[before_zmm]),%%zmm11\n"
	    "	vmovdqa64	12*64(%[before_zmm]),%%zmm12\n"
	    "	vmovdqa64	13*64(%[before_zmm]),%%zmm13\n"
	    "	vmovdqa64	14*64(%[before_zmm]),%%zmm14\n"
	    "	vmovdqa64	15*64(%[before_zmm]),%%zmm15\n"
	    "	vmovdqa64	16*64(%[before_zmm]),%%zmm16\n"
	    "	vmovdqa64	17*64(%[before_zmm]),%%zmm17\n"
	    "	vmovdqa64	18*64(%[before_zmm]),%%zmm18\n"
	    "	vmovdqa64	19*64(%[before_zmm]),%%zmm19\n"
	    "	vmovdqa64	20*64(%[before_zmm]),%%zmm20\n"
	    "	vmovdqa64	21*64(%[before_zmm]),%%zmm21\n"
	    "	vmovdqa64	22*64(%[before_zmm]),%%zmm22\n"
	    "	vmovdqa64	23*64(%[before_zmm]),%%zmm23\n"
	    "	vmovdqa64	24*64(%[before_zmm]),%%zmm24\n"
	    "	vmovdqa64	25*64(%[before_zmm]),%%zmm25\n"
	    "	vmovdqa64	26*64(%[before_zmm]),%%zmm26\n"
	    "	vmovdqa64	27*64(%[before_zmm]),%%zmm27\n"
	    "	vmovdqa64	28*64(%[before_zmm]),%%zmm28\n"
	    "	vmovdqa64	29*64(%[before_zmm]),%%zmm29\n"
	    "	vmovdqa64	30*64(%[before_zmm]),%%zmm30\n"
	    "	vmovdqa64	31*64(%[before_zmm]),%%zmm31\n"
#endif
	    "	lock\n"
	    "	orb	$1,%[ready]\n"
	    "0:	pause\n"
	    "	cmpb	$0,%[done]\n"
	    "	lfence\n"
	    "	je	0b\n"
	    "	vmovdqa64	%%zmm0,0*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm1,1*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm2,2*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm3,3*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm4,4*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm5,5*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm6,6*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm7,7*64(%[after_zmm])\n"
#ifdef __x86_64__
	    "	vmovdqa64	%%zmm8,8*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm9,9*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm10,10*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm11,11*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm12,12*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm13,13*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm14,14*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm15,15*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm16,16*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm17,17*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm18,18*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm19,19*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm20,20*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm21,21*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm22,22*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm23,23*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm24,24*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm25,25*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm26,26*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm27,27*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm28,28*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm29,29*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm30,30*64(%[after_zmm])\n"
	    "	vmovdqa64	%%zmm31,31*64(%[after_zmm])\n"
#endif
	    : /*out*/ [ready]"=m"(*ready), "=m"(after.zmm)
	    : /*in*/ [done]"m"(*done), "m"(before.zmm),
	      [before_zmm]"r"(before.zmm), [after_zmm]"r"(after.zmm)
	    : /*clobber*/ "zmm0", "zmm1", "zmm2", "zmm3", "zmm4", "zmm5",
	      "zmm6", "zmm7"
#ifdef __x86_64__
	    , "zmm8", "zmm9", "zmm10", "zmm11", "zmm12",
	      "zmm13", "zmm14", "zmm15", "zmm16", "zmm17",
	      "zmm18", "zmm19", "zmm20", "zmm21", "zmm22",
	      "zmm23", "zmm24", "zmm25", "zmm26", "zmm27",
	      "zmm28", "zmm29", "zmm30", "zmm31"
#endif
	);

	for (i = 0; i < __arraycount(before.zmm); i++) {
		if (memcmp(&before.zmm[i], &after.zmm[i],
			sizeof(before.zmm[i]))) {
			fprintf(stderr, "zmm%u clobbered\n", i);
			hexdump(stderr, &before.zmm[i], sizeof(before.zmm[i]),
			    "before");
			hexdump(stderr, &after.zmm[i], sizeof(after.zmm[i]),
			    "after");
			error |= 1 << i;
		}
	}

	return error;
}

__attribute__((target("avx512f")))
void
trash_zmm(void)
{
	struct zmmregs regs;

	arc4random_buf(&regs, sizeof(regs));

	asm("\n"
	    "	vmovdqa64	0*64(%[zmm]),%%zmm0\n"
	    "	vmovdqa64	1*64(%[zmm]),%%zmm1\n"
	    "	vmovdqa64	2*64(%[zmm]),%%zmm2\n"
	    "	vmovdqa64	3*64(%[zmm]),%%zmm3\n"
	    "	vmovdqa64	4*64(%[zmm]),%%zmm4\n"
	    "	vmovdqa64	5*64(%[zmm]),%%zmm5\n"
	    "	vmovdqa64	6*64(%[zmm]),%%zmm6\n"
	    "	vmovdqa64	7*64(%[zmm]),%%zmm7\n"
#ifdef __x86_64__
	    "	vmovdqa64	8*64(%[zmm]),%%zmm8\n"
	    "	vmovdqa64	9*64(%[zmm]),%%zmm9\n"
	    "	vmovdqa64	10*64(%[zmm]),%%zmm10\n"
	    "	vmovdqa64	11*64(%[zmm]),%%zmm11\n"
	    "	vmovdqa64	12*64(%[zmm]),%%zmm12\n"
	    "	vmovdqa64	13*64(%[zmm]),%%zmm13\n"
	    "	vmovdqa64	14*64(%[zmm]),%%zmm14\n"
	    "	vmovdqa64	15*64(%[zmm]),%%zmm15\n"
	    "	vmovdqa64	16*64(%[zmm]),%%zmm16\n"
	    "	vmovdqa64	17*64(%[zmm]),%%zmm17\n"
	    "	vmovdqa64	18*64(%[zmm]),%%zmm18\n"
	    "	vmovdqa64	19*64(%[zmm]),%%zmm19\n"
	    "	vmovdqa64	20*64(%[zmm]),%%zmm20\n"
	    "	vmovdqa64	21*64(%[zmm]),%%zmm21\n"
	    "	vmovdqa64	22*64(%[zmm]),%%zmm22\n"
	    "	vmovdqa64	23*64(%[zmm]),%%zmm23\n"
	    "	vmovdqa64	24*64(%[zmm]),%%zmm24\n"
	    "	vmovdqa64	25*64(%[zmm]),%%zmm25\n"
	    "	vmovdqa64	26*64(%[zmm]),%%zmm26\n"
	    "	vmovdqa64	27*64(%[zmm]),%%zmm27\n"
	    "	vmovdqa64	28*64(%[zmm]),%%zmm28\n"
	    "	vmovdqa64	29*64(%[zmm]),%%zmm29\n"
	    "	vmovdqa64	30*64(%[zmm]),%%zmm30\n"
	    "	vmovdqa64	31*64(%[zmm]),%%zmm31\n"
#endif
	    : /*out*/
	    : /*in*/ "m"(regs.zmm), [zmm]"r"(regs.zmm)
	    : /*clobber*/ "zmm0", "zmm1", "zmm2", "zmm3", "zmm4", "zmm5",
	      "zmm6", "zmm7"
#ifdef __x86_64__
	    , "zmm8", "zmm9", "zmm10", "zmm11", "zmm12",
	      "zmm13", "zmm14", "zmm15", "zmm16", "zmm17",
	      "zmm18", "zmm19", "zmm20", "zmm21", "zmm22",
	      "zmm23", "zmm24", "zmm25", "zmm26", "zmm27",
	      "zmm28", "zmm29", "zmm30", "zmm31"
#endif
	);
}
@


1.3.2.1
log
@file sig_fpu.c was added on branch netbsd-11 on 2026-07-19 15:57:28 +0000
@
text
@d1 752
@


1.3.2.2
log
@Pull up following revision(s) (requested by riastradh in ticket #381):

	sys/arch/amd64/amd64/machdep.c: revision 1.380
	sys/arch/amd64/include/mcontext.h: revision 1.25
	sys/arch/amd64/amd64/netbsd32_machdep.c: revision 1.143
	distrib/sets/lists/tests/mi: revision 1.1424
	sys/arch/x86/include/specialreg.h: revision 1.222
	sys/arch/x86/x86/identcpu.c: revision 1.140
	sys/arch/i386/include/mcontext.h: revision 1.20
	sys/arch/x86/x86/identcpu.c: revision 1.141
	tests/kernel/t_signal_and_fpu.c: revision 1.1
	tests/kernel/t_signal_and_fpu.c: revision 1.2
	tests/kernel/t_signal_and_fpu.c: revision 1.3
	sys/arch/x86/include/fpu.h: revision 1.24
	sys/arch/x86/include/cpu_extended_state.h: revision 1.20
	sys/arch/i386/i386/machdep.c: revision 1.852
	distrib/sets/lists/debug/mi: revision 1.516
	tests/kernel/Makefile: revision 1.98
	tests/kernel/arch/x86/sig_fpu.c: revision 1.1
	sys/arch/x86/x86/fpu.c: revision 1.95
	tests/kernel/arch/x86/sig_fpu.h: revision 1.1

Verify signal delivery preserves various FPU state.

Should also test AVX512 but I'm not sure I have hardware handy to do
this right now and qemu doesn't emulate it, as far as I know!  TBD.
(Plus: there's umpteen different cpuid feature bits to consider,
annoyingly, and that means a lot of manual reading to sort through
them.)

PR kern/60426: Signal handler corrupts AVX (YMM) registers


x86: Save and restore all supported extended CPU state on signals.

While here, disable Intel AMX, whose state size (>>8 KiB) exceeds
MINSIGSTKSZ (8 KiB), until we are ready to safely update the ABI for
sigaltstack(2).  This isn't a regression: we've never had a release
with Intel AMX support.

Previously, on signal delivery, we would only save and restore at
most what FXSAVE does, which is the x87 and SSE registers that always
exist on amd64.

To save and restore the upper halves of the YMM or ZMM registers
(AVX/AVX2/AVX512), or the AVX512 registers ZMM16..ZMM31, or the
enormous AMX state, we need to do more.  And we need to do that even if
the signal handler doesn't use AVX instructions, because an SSE
instruction modifying xmmN (e.g., in a vectorized memcpy) will, as a
side effect, zero the high half of ymmN.

Fortunately, the x86 architecture has an extensible mechanism for
saving and restoring extended CPU state, the XSAVE/XRSTOR instruction
family, so we don't need to update this code for every extension --
just handling the generic XSAVE/XRSTOR mechanism will work for a
while.

When delivering a signal to a thread that has actually made use of
these extended registers, we:

1. allocate a separate space on the user's stack for an XSAVE area of
   whatever size is necessary (as long as it fits in the sigaltstack,
   if one was provided), and
2. store a pointer to and the length of the separate XSAVE area in an
   architecturally unused section of the FXSAVE area of the
   mcontext_t, with the _UC_XSAVE bit set.

This way existing applications that have SA_SIGINFO signal handlers
and expect to find an FXSAVE area in the mcontext_t with x87 and SSE
register content will continue to work, but if the thread has used
any state beyond that like AVX registers requiring XSAVE/XRSTOR, on
return from signal it can restore everything.

Unfortunately, the _size_ that XSAVE needs is variable and might be
expanded by architectural extensions, such as Intel AMX.  So the
minimum stack size to store it all might exceed MINSIGSTKSZ.  The
current MINSIGSTKSZ is plenty for AVX/AVX2/AVX-512.  But before
raising MINSIGSTKSZ to support larger XSAVE sizes by adding them to
XCR0_FPU, we need to make sure that older binaries built with the old
MINSIGSTKSZ continue to work -- of course, they won't be able to use
(e.g.) Intel AMX state.

To make sure we don't break the ABI accidentally, I've added some
__CTASSERTs relating XSAVE_MAX_BYTES, the maximum supported XSAVE
area size, and MINSIGSTKSZ, and a panic at boot if the CPU advertises
an XSAVE size larger than XSAVE_MAX_BYTES, which would mean either:

(a) the CPU's advertised size for various architecturally defined
    extended CPU state does not match the documentation, or
(b) we have inadvertently bitten off more extended state CPU state
    than we can chew in XCR0_FPU and we have to adjust the ABI and
    add compatibility for userland programs.

Fortunately for getcontext(2), all of the registers in question are
caller-saves, so it doesn't need to store any extra state.

PR kern/60426: Signal handler corrupts AVX (YMM) registers


t_signal_and_fpu: Fix machine-independent double test.

Copypasta error!  Float and long double tests were fine.

PR kern/60426: Signal handler corrupts AVX (YMM) registers


x86: Redo boot-time XSAVE area size ABI check.
Instead of checking each component's offset+size from

    size = CPUID[EAX=0x0d,ECX=i].EAX,
    offset = CPUID[EAX=0x0d,ECX=i].EBX,

to make sure it fits in the XSAVE_MAX_BYTES implied by the
MINSIGSTKSZ ABI parameter, just check the total _enabled_ XSAVE area
size from

    CPUID[EAX=0x0d,ECX=0].EBX,

which is what we use to allocate the XSAVE area in software anyway.

The Intel documentation[1] is not very clear on exactly what
CPUID[EAX=0x0d,ECX=i] reports for i >= 2, saying `valid bit in the
XCR0 register' without distinguishing `supported' from `enabled'
bits, and the AMD documentation I skimmed didn't have these leaves in
any obvious place.  But it seems CPUID[EAX=0xd,ECX=i].EAX/.EBX give
the size and offset of the CPU's _supported_ XSAVE features, rather
than only those for _enabled_ XSAVE features, so the original panic
would have tripped on features we haven't enabled in XCR0_FPU.

In any case, better to verify the parameter we use directly for
allocating space.

[1] Intel 64 and IA-32 Architectures Software Developer's Manual,
    Volume 1: Basic Architecture, Intel, Order Number: 253665-092US,
    June 2026, Sec. 21.3 `CPUID Leaves', Subsection `CPUID.0DH --
    Processor Extended State', Subsubsection `CPUID.0DH.SUB-LEAVES -
    Sub-leaves', pp. 21-43 -- 21-44.
    https://web.archive.org/web/20260709150417/https://cdrdv2-public.intel.com/922477/253665-092-sdm-vol-1.pdf


PR kern/60426: Signal handler corrupts AVX (YMM) registers
@
text
@a0 556
/*	$NetBSD: sig_fpu.c,v 1.1 2026/07/09 02:05:45 riastradh Exp $	*/

/*
 * Copyright (c) 2026 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: sig_fpu.c,v 1.1 2026/07/09 02:05:45 riastradh Exp $");

#include "sig_fpu.h"

#include <sys/types.h>

#include <x86/cpu_extended_state.h>

#include <cpuid.h>
#include <inttypes.h>
#include <sched.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>

#include "h_macros.h"

#ifdef __x86_64__
#  define	NVECREGS	16
#else  /* 32-bit */
#  define	NVECREGS	8
#endif

#define	X87_CW_RC	__BITS(11,10) /* rounding control (mode) */
#define	X87_CW_PC	__BITS(9,8)   /* precision control */
#define	X87_CW_SW_EXC	__BITS(5,0)   /* exception bits */

struct cpuid {
	uint32_t edx;
	uint32_t ecx;
	uint32_t ebx;
	uint32_t eax;
};

static struct cpuid
cpuid(uint32_t eax_fn, uint32_t ecx_idx)
{
	struct cpuid c;

	if (!__get_cpuid_count(eax_fn, ecx_idx,
		&c.eax, &c.ebx, &c.ecx, &c.edx))
		memset(&c, 0, sizeof(c));

	return c;
}

__printflike(4,5)
__noinline
static void
hexdump(FILE *fp, const void *buf, size_t len, const char *title, ...)
{
	va_list va;
	const uint8_t *p = buf;
	size_t i;

	fprintf(fp, "# ");
	va_start(va, title);
	vfprintf(fp, title, va);
	va_end(va);
	fprintf(fp, "\n");
	for (i = 0; i < len; i++) {
		if ((i % 8) == 0)
			fprintf(fp, " ");
		fprintf(fp, " %02hhx", p[i]);
		if ((i % 16) == 15)
			fprintf(fp, "\n");
	}
	if (i % 16)
		fprintf(fp, "\n");
}

bool
x87_supported(void)
{
#ifdef __x86_64__
	return true;
#else  /* 32-bit */
	return cpuid(0x00000001, 0).edx & __BIT(0);
#endif
}

int
test_x87(volatile bool *ready, const volatile bool *done)
{
	struct save87 s, s1;
	uint32_t rcpc_exc;
	int error = 0;

	/*
	 * Gather the current FPU state so we have some reasonable
	 * content for the weird stuff when we restore from it.
	 */
	asm("wait\n"
	    "	fnsave	%0"
	    : /*out*/ "=m"(s));

	/*
	 * Randomize the floating-point stack entries ST(0),...,ST(7).
	 */
	arc4random_buf(s.s87_ac, sizeof(s.s87_ac));

	/*
	 * Randomize rounding control, precision control, and a set of
	 * exception bits.  We don't want to raise any unmasked
	 * exceptions, so we'll raise _and_ mask them.
	 *
	 * XXX Consider testing the condition code, exception status,
	 * and top of stack bits in the status word too.
	 */
	rcpc_exc = arc4random() & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC);
	if (__SHIFTOUT(rcpc_exc, X87_CW_PC) == 1) /* reserved */
		rcpc_exc |= __SHIFTIN(3, X87_CW_PC); /* extended */
	s.s87_cw &= ~(X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC);
	s.s87_cw |= rcpc_exc;
	s.s87_sw &= ~X87_CW_SW_EXC;
	s.s87_sw |= rcpc_exc & X87_CW_SW_EXC;

	/*
	 * Load up the registers, report that we're ready, busy-wait
	 * with the registers still live -- so no subroutine calls --
	 * until we're done, and then store the registers back to
	 * memory so we can check them.
	 */
	asm("\n"
	    "	frstor	%[s]\n"
	    "	lock\n"
	    "	orb	$1,%[ready]\n"
	    "0:	pause\n"
	    "	cmpb	$0,%[done]\n"
	    "	lfence\n"
	    "	je	0b\n"
	    "	wait\n"
	    "	fnsave	%[s1]\n"
	    : /*out*/ [ready]"=m"(*ready), [s1]"=m"(s1)
	    : /*in*/ [done]"m"(*done), [s]"m"(s));

	/*
	 * If there are any mismatches between the before and after
	 * ST(i) registers, or the control word, or the status word,
	 * print them and fail.
	 */
	if (memcmp(s.s87_ac, s1.s87_ac, sizeof(s.s87_ac)) != 0) {
		unsigned i;

		for (i = 0; i < 8; i++) {
			if (memcmp(&s.s87_ac[i], &s1.s87_ac[i],
				sizeof(s.s87_ac[i])) == 0)
				continue;
			fprintf(stderr, "ST(%u)=0x%08x%016"PRIx64","
			    " expected 0x%08x%016"PRIx64"\n",
			    i,
			    s1.s87_ac[i].f87_exp_sign,
			    s1.s87_ac[i].f87_mantissa,
			    s.s87_ac[i].f87_exp_sign,
			    s.s87_ac[i].f87_mantissa);
			error |= 1 << i;
		}
	}
	if ((s1.s87_cw & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC)) !=
	    (s.s87_cw & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC))) {
		fprintf(stderr, "cw=0x%04x, expected 0x%04x,"
		    " diff 0x%04x\n",
		    s1.s87_cw, s.s87_cw,
		    (uint32_t)((s1.s87_cw ^ s.s87_cw) &
			(X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC)));
		error |= 1 << 8;
	}
	if ((s1.s87_sw & X87_CW_SW_EXC) !=
	    (s.s87_cw & X87_CW_SW_EXC)) {
		fprintf(stderr, "sw=0x%04x, expected 0x%04x,"
		    " diff 0x%04x\n",
		    s1.s87_sw, s.s87_sw,
		    (uint32_t)((s1.s87_sw ^ s.s87_sw) &
			X87_CW_SW_EXC));
		error |= 1 << 9;
	}

	return error;
}

void
trash_x87(void)
{
	struct save87 s;
	uint32_t rcpc_exc;

	/*
	 * Gather the current FPU state so we have some reasonable
	 * content for the weird stuff when we restore from it.
	 */
	asm("wait\n"
	    "	fnsave	%0"
	    : /*out*/ "=m"(s));

	/*
	 * Randomize the floating-point stack entries ST(0),...,ST(7).
	 */
	arc4random_buf(s.s87_ac, sizeof(s.s87_ac));

	/*
	 * Randomize rounding control, precision control, and a set of
	 * exception bits.  We don't want to raise any unmasked
	 * exceptions, so we'll raise _and_ mask them.
	 *
	 * XXX Consider trashing the condition code, exception status,
	 * and top of stack bits in the status word too.
	 */
	rcpc_exc = arc4random() & (X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC);
	s.s87_cw &= ~(X87_CW_RC | X87_CW_PC | X87_CW_SW_EXC);
	s.s87_cw |= rcpc_exc;
	s.s87_sw &= ~X87_CW_SW_EXC;
	s.s87_sw |= rcpc_exc & X87_CW_SW_EXC;

	/*
	 * Load the state into the FPU.
	 */
	asm volatile("frstor %0" :: /*in*/ "m"(s));
}

#define	MXCSR_MISALIGNEDEXC	__BIT(17)	/* misaligned exc fault */
#define	MXCSR_FTZ		__BIT(15)	/* flush to zero */
#define	MXCSR_RC		__BITS(14,13)	/* rounding control */
#define	MXCSR_EXCMASK		__BITS(12,7)	/* exceptions masked */
#define	MXCSR_DAZ		__BIT(6)	/* denormals are zero */
#define	MXCSR_EXCFLAG		__BITS(5,0)	/* exceptions raised */

struct xmmregs {
	struct {
		uint8_t b[16];
	} __aligned(16)	xmm[NVECREGS];
	uint32_t mxcsr;
};

bool
xmm_supported(void)
{
#ifdef __x86_64__
	return true;
#else  /* 32-bit */
	return cpuid(0x00000001, 0).edx & __BIT(25);
#endif
}

__attribute__((target("sse")))
int
test_xmm(volatile bool *ready, const volatile bool *done)
{
	struct xmmregs before, after;
	uint32_t exc;
	unsigned i;
	int error = 0;

	/*
	 * Randomize the xmm register content.  Randomize plausible
	 * bits for the mxcsr.
	 */
	arc4random_buf(before.xmm, sizeof(before.xmm));
	before.mxcsr = arc4random() & (MXCSR_FTZ | MXCSR_RC | MXCSR_DAZ);
	exc = arc4random() & 0x3f;
	before.mxcsr |= __SHIFTIN(exc, MXCSR_EXCMASK);
	before.mxcsr |= __SHIFTIN(exc, MXCSR_EXCFLAG);

	/*
	 * Load up the registers, report that we're ready, busy-wait
	 * with the registers still live -- so no subroutine calls --
	 * until we're done, and then store the registers back to
	 * memory so we can check them.
	 */
	asm("\n"
	    "	ldmxcsr	%[mxcsr_before]\n"
	    "	movdqa	0*16(%[xmm_before]),%%xmm0\n"
	    "	movdqa	1*16(%[xmm_before]),%%xmm1\n"
	    "	movdqa	2*16(%[xmm_before]),%%xmm2\n"
	    "	movdqa	3*16(%[xmm_before]),%%xmm3\n"
	    "	movdqa	4*16(%[xmm_before]),%%xmm4\n"
	    "	movdqa	5*16(%[xmm_before]),%%xmm5\n"
	    "	movdqa	6*16(%[xmm_before]),%%xmm6\n"
	    "	movdqa	7*16(%[xmm_before]),%%xmm7\n"
#ifdef __x86_64__
	    "	movdqa	8*16(%[xmm_before]),%%xmm8\n"
	    "	movdqa	9*16(%[xmm_before]),%%xmm9\n"
	    "	movdqa	10*16(%[xmm_before]),%%xmm10\n"
	    "	movdqa	11*16(%[xmm_before]),%%xmm11\n"
	    "	movdqa	12*16(%[xmm_before]),%%xmm12\n"
	    "	movdqa	13*16(%[xmm_before]),%%xmm13\n"
	    "	movdqa	14*16(%[xmm_before]),%%xmm14\n"
	    "	movdqa	15*16(%[xmm_before]),%%xmm15\n"
#endif
	    "	lock\n"
	    "	orb	$1,%[ready]\n"
	    "0:	pause\n"
	    "	cmpb	$0,%[done]\n"
	    "	lfence\n"
	    "	je	0b\n"
	    "	movdqa	%%xmm0,0*16(%[xmm_after])\n"
	    "	movdqa	%%xmm1,1*16(%[xmm_after])\n"
	    "	movdqa	%%xmm2,2*16(%[xmm_after])\n"
	    "	movdqa	%%xmm3,3*16(%[xmm_after])\n"
	    "	movdqa	%%xmm4,4*16(%[xmm_after])\n"
	    "	movdqa	%%xmm5,5*16(%[xmm_after])\n"
	    "	movdqa	%%xmm6,6*16(%[xmm_after])\n"
	    "	movdqa	%%xmm7,7*16(%[xmm_after])\n"
#ifdef __x86_64__
	    "	movdqa	%%xmm8,8*16(%[xmm_after])\n"
	    "	movdqa	%%xmm9,9*16(%[xmm_after])\n"
	    "	movdqa	%%xmm10,10*16(%[xmm_after])\n"
	    "	movdqa	%%xmm11,11*16(%[xmm_after])\n"
	    "	movdqa	%%xmm12,12*16(%[xmm_after])\n"
	    "	movdqa	%%xmm13,13*16(%[xmm_after])\n"
	    "	movdqa	%%xmm14,14*16(%[xmm_after])\n"
	    "	movdqa	%%xmm15,15*16(%[xmm_after])\n"
#endif
	    "	stmxcsr	%[mxcsr_after]\n"
	    : /*out*/ [ready]"=m"(*ready), "=m"(after.xmm),
	      [mxcsr_after]"=m"(after.mxcsr)
	    : /*in*/ [done]"m"(*done), "m"(before.xmm),
	      [mxcsr_before]"m"(before.mxcsr),
	      [xmm_before]"r"(&before.xmm), [xmm_after]"r"(&after.xmm)
	    : /*clobber*/ "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5",
	      "xmm6", "xmm7"
#ifdef __x86_64__
	    , "xmm8", "xmm9", "xmm10", "xmm11", "xmm12",
	      "xmm13", "xmm14", "xmm15"
#endif
	);

	/*
	 * If there are any mismatches between the before and after xmm
	 * registers, or the mxcsr, print them and fail.
	 */
	for (i = 0; i < __arraycount(before.xmm); i++) {
		if (memcmp(&before.xmm[i], &after.xmm[i],
			sizeof(before.xmm[i]))) {
			fprintf(stderr, "xmm%u clobbered\n", i);
			hexdump(stderr, &before.xmm[i], sizeof(before.xmm[i]),
			    "before");
			hexdump(stderr, &after.xmm[i], sizeof(after.xmm[i]),
			    "after");
			error |= 1 << i;
		}
	}

	if (before.mxcsr != after.mxcsr) {
		fprintf(stderr, "mxcsr clobbered:"
		    " before=0x%08"PRIx32", after=0x%08"PRIx32"\n",
		    before.mxcsr, after.mxcsr);
		error |= 1 << 16;
	}

	return error;
}

__attribute__((target("sse")))
void
trash_xmm(void)
{
	struct xmmregs regs;

	arc4random_buf(&regs, sizeof(regs));

	asm("\n"
	    "	movdqa	0*32(%[xmm]),%%xmm0\n"
	    "	movdqa	1*32(%[xmm]),%%xmm1\n"
	    "	movdqa	2*32(%[xmm]),%%xmm2\n"
	    "	movdqa	3*32(%[xmm]),%%xmm3\n"
	    "	movdqa	4*32(%[xmm]),%%xmm4\n"
	    "	movdqa	5*32(%[xmm]),%%xmm5\n"
	    "	movdqa	6*32(%[xmm]),%%xmm6\n"
	    "	movdqa	7*32(%[xmm]),%%xmm7\n"
#ifdef __x86_64__
	    "	movdqa	8*32(%[xmm]),%%xmm8\n"
	    "	movdqa	9*32(%[xmm]),%%xmm9\n"
	    "	movdqa	10*32(%[xmm]),%%xmm10\n"
	    "	movdqa	11*32(%[xmm]),%%xmm11\n"
	    "	movdqa	12*32(%[xmm]),%%xmm12\n"
	    "	movdqa	13*32(%[xmm]),%%xmm13\n"
	    "	movdqa	14*32(%[xmm]),%%xmm14\n"
	    "	movdqa	15*32(%[xmm]),%%xmm15\n"
#endif
	    : /*out*/
	    : /*in*/ [xmm]"r"(regs.xmm), "m"(regs.xmm)
	    : /*clobber*/ "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5",
	      "xmm6", "xmm7"
#ifdef __x86_64__
	    , "xmm8", "xmm9", "xmm10", "xmm11", "xmm12",
	      "xmm13", "xmm14", "xmm15"
#endif
	);
}

bool
ymm_supported(void)
{
	return cpuid(0x00000001, 0).ecx & __BIT(28);
}

struct ymmregs {
	struct {
		uint8_t b[32];
	} __aligned(32)	ymm[NVECREGS];
};

__attribute__((target("avx")))
int
test_ymm(volatile bool *ready, const volatile bool *done)
{
	struct ymmregs before, after;
	unsigned i;
	int error = 0;

	/*
	 * Randomize the ymm register content.
	 */
	arc4random_buf(&before, sizeof(before));

	/*
	 * Load up the registers, report that we're ready, busy-wait
	 * with the registers still live -- so no subroutine calls --
	 * until we're done, and then store the registers back to
	 * memory so we can check them.
	 */
	asm("\n"
	    "	vmovdqa	0*32(%[before_ymm]),%%ymm0\n"
	    "	vmovdqa	1*32(%[before_ymm]),%%ymm1\n"
	    "	vmovdqa	2*32(%[before_ymm]),%%ymm2\n"
	    "	vmovdqa	3*32(%[before_ymm]),%%ymm3\n"
	    "	vmovdqa	4*32(%[before_ymm]),%%ymm4\n"
	    "	vmovdqa	5*32(%[before_ymm]),%%ymm5\n"
	    "	vmovdqa	6*32(%[before_ymm]),%%ymm6\n"
	    "	vmovdqa	7*32(%[before_ymm]),%%ymm7\n"
#ifdef __x86_64__
	    "	vmovdqa	8*32(%[before_ymm]),%%ymm8\n"
	    "	vmovdqa	9*32(%[before_ymm]),%%ymm9\n"
	    "	vmovdqa	10*32(%[before_ymm]),%%ymm10\n"
	    "	vmovdqa	11*32(%[before_ymm]),%%ymm11\n"
	    "	vmovdqa	12*32(%[before_ymm]),%%ymm12\n"
	    "	vmovdqa	13*32(%[before_ymm]),%%ymm13\n"
	    "	vmovdqa	14*32(%[before_ymm]),%%ymm14\n"
	    "	vmovdqa	15*32(%[before_ymm]),%%ymm15\n"
#endif
	    "	lock\n"
	    "	orb	$1,%[ready]\n"
	    "0:	pause\n"
	    "	cmpb	$0,%[done]\n"
	    "	lfence\n"
	    "	je	0b\n"
	    "	vmovdqa	%%ymm0,0*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm1,1*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm2,2*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm3,3*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm4,4*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm5,5*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm6,6*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm7,7*32(%[after_ymm])\n"
#ifdef __x86_64__
	    "	vmovdqa	%%ymm8,8*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm9,9*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm10,10*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm11,11*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm12,12*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm13,13*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm14,14*32(%[after_ymm])\n"
	    "	vmovdqa	%%ymm15,15*32(%[after_ymm])\n"
#endif
	    : /*out*/ [ready]"=m"(*ready), "=m"(after.ymm)
	    : /*in*/ [done]"m"(*done), "m"(before.ymm),
	      [before_ymm]"r"(before.ymm), [after_ymm]"r"(after.ymm)
	    : /*clobber*/ "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5",
	      "ymm6", "ymm7"
#ifdef __x86_64__
	    , "ymm8", "ymm9", "ymm10", "ymm11", "ymm12",
	      "ymm13", "ymm14", "ymm15"
#endif
	);

	for (i = 0; i < __arraycount(before.ymm); i++) {
		if (memcmp(&before.ymm[i], &after.ymm[i],
			sizeof(before.ymm[i]))) {
			fprintf(stderr, "ymm%u clobbered\n", i);
			hexdump(stderr, &before.ymm[i], sizeof(before.ymm[i]),
			    "before");
			hexdump(stderr, &after.ymm[i], sizeof(after.ymm[i]),
			    "after");
			error |= 1 << i;
		}
	}

	return error;
}

__attribute__((target("avx")))
void
trash_ymm(void)
{
	struct ymmregs regs;

	arc4random_buf(&regs, sizeof(regs));

	asm("\n"
	    "	vmovdqa	0*32(%[ymm]),%%ymm0\n"
	    "	vmovdqa	1*32(%[ymm]),%%ymm1\n"
	    "	vmovdqa	2*32(%[ymm]),%%ymm2\n"
	    "	vmovdqa	3*32(%[ymm]),%%ymm3\n"
	    "	vmovdqa	4*32(%[ymm]),%%ymm4\n"
	    "	vmovdqa	5*32(%[ymm]),%%ymm5\n"
	    "	vmovdqa	6*32(%[ymm]),%%ymm6\n"
	    "	vmovdqa	7*32(%[ymm]),%%ymm7\n"
#ifdef __x86_64__
	    "	vmovdqa	8*32(%[ymm]),%%ymm8\n"
	    "	vmovdqa	9*32(%[ymm]),%%ymm9\n"
	    "	vmovdqa	10*32(%[ymm]),%%ymm10\n"
	    "	vmovdqa	11*32(%[ymm]),%%ymm11\n"
	    "	vmovdqa	12*32(%[ymm]),%%ymm12\n"
	    "	vmovdqa	13*32(%[ymm]),%%ymm13\n"
	    "	vmovdqa	14*32(%[ymm]),%%ymm14\n"
	    "	vmovdqa	15*32(%[ymm]),%%ymm15\n"
#endif
	    : /*out*/
	    : /*in*/ "m"(regs.ymm), [ymm]"r"(regs.ymm)
	    : /*clobber*/ "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5",
	      "ymm6", "ymm7"
#ifdef __x86_64__
	    , "ymm8", "ymm9", "ymm10", "ymm11", "ymm12",
	      "ymm13", "ymm14", "ymm15"
#endif
	);
}
@


1.2
log
@t_signal_and_fpu: Add test case for AVX-512 ZMM registers.

PR kern/60426: Signal handler corrupts AVX (YMM) registers
@
text
@d1 1
a1 1
/*	$NetBSD: sig_fpu.c,v 1.1 2026/07/09 02:05:45 riastradh Exp $	*/
d30 1
a30 1
__RCSID("$NetBSD: sig_fpu.c,v 1.1 2026/07/09 02:05:45 riastradh Exp $");
d395 17
a411 17
	    "	movdqa	0*32(%[xmm]),%%xmm0\n"
	    "	movdqa	1*32(%[xmm]),%%xmm1\n"
	    "	movdqa	2*32(%[xmm]),%%xmm2\n"
	    "	movdqa	3*32(%[xmm]),%%xmm3\n"
	    "	movdqa	4*32(%[xmm]),%%xmm4\n"
	    "	movdqa	5*32(%[xmm]),%%xmm5\n"
	    "	movdqa	6*32(%[xmm]),%%xmm6\n"
	    "	movdqa	7*32(%[xmm]),%%xmm7\n"
#ifdef __x86_64__
	    "	movdqa	8*32(%[xmm]),%%xmm8\n"
	    "	movdqa	9*32(%[xmm]),%%xmm9\n"
	    "	movdqa	10*32(%[xmm]),%%xmm10\n"
	    "	movdqa	11*32(%[xmm]),%%xmm11\n"
	    "	movdqa	12*32(%[xmm]),%%xmm12\n"
	    "	movdqa	13*32(%[xmm]),%%xmm13\n"
	    "	movdqa	14*32(%[xmm]),%%xmm14\n"
	    "	movdqa	15*32(%[xmm]),%%xmm15\n"
@


1.1
log
@Verify signal delivery preserves various FPU state.

Should also test AVX512 but I'm not sure I have hardware handy to do
this right now and qemu doesn't emulate it, as far as I know!  TBD.
(Plus: there's umpteen different cpuid feature bits to consider,
annoyingly, and that means a lot of manual reading to sort through
them.)

PR kern/60426: Signal handler corrupts AVX (YMM) registers
@
text
@d1 1
a1 1
/*	$NetBSD$	*/
d30 1
a30 1
__RCSID("$NetBSD$");
d48 3
a50 1
#  define	NVECREGS	16
d52 3
a54 1
#  define	NVECREGS	8
d263 1
a263 1
	} __aligned(16)	xmm[NVECREGS];
d433 1
a433 1
	} __aligned(32)	ymm[NVECREGS];
d561 192
@

