/* $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 __RCSID("$NetBSD: sig_fpu.c,v 1.1 2026/07/09 02:05:45 riastradh Exp $"); #include "sig_fpu.h" #include #include #include #include #include #include #include #include #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(®s, 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(®s, 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 ); }