add FPU/SIMD register state test Add tests to verify that - FPU insns leave correct (guest) values in FIP/FDP/FOP/FCS/FDS, - FPU insns writing memory don't update FPU register state when the write faults (at the example of FISTP), - VCVTPS2PH (once implemented in the emulator) doesn't update MXCSR if its write faults (VCVTPS2PH currently is the only SIMD insn writing to memory and updating register state). Note that the AMD-specific code in fpu_fxrstor() and xrstor() causes the FISTP part of this test to always fail. I don't really have any idea what to do about this (other than perhaps skipping the test on AMD altogether). Note further that the FCS part of 64-bit variant of the FSTP test also always fails on AMD, since, other than on Intel, {F,}XRSTOR with REX.W set do not appear to clear FCS/FDS (I can't find any statement either way in the PM). Signed-off-by: Jan Beulich --- TBD: It is not clear to me how to deal with the native execution failure of VCVTPS2PH on at least some Intel models. --- a/include/arch/x86/cpuid.h +++ b/include/arch/x86/cpuid.h @@ -77,6 +77,7 @@ static inline bool cpu_has(unsigned int #define cpu_has_pcid cpu_has(X86_FEATURE_PCID) #define cpu_has_xsave cpu_has(X86_FEATURE_XSAVE) #define cpu_has_avx cpu_has(X86_FEATURE_AVX) +#define cpu_has_f16c cpu_has(X86_FEATURE_F16C) #define cpu_has_syscall cpu_has(X86_FEATURE_SYSCALL) #define cpu_has_nx cpu_has(X86_FEATURE_NX) --- /dev/null +++ b/tests/fpu-state/Makefile @@ -0,0 +1,9 @@ +include $(ROOT)/build/common.mk + +NAME := fpu-state +CATEGORY := functional +TEST-ENVS := hvm64 hvm32pse + +obj-perenv += main.o + +include $(ROOT)/build/gen.mk --- /dev/null +++ b/tests/fpu-state/main.c @@ -0,0 +1,184 @@ +/** + * @file tests/fpu-state/main.c + * @ref test-fpu-state - Emulation of FPU state + * + * @page test-fpu-state FPU State Emulation + * + * FPU code/data pointers and opcode must not be the ones resulting + * from the stub execution in the hypervisor. + * + * FPU and SIMD instructions faulting during memory write must not + * update the respective register files. + * + * @see tests/fpu-state/main.c + */ +#include + +#include + +const char test_title[] = "FPU State"; + +struct x87_env { + uint16_t cw, :16; + uint16_t sw, :16; + uint16_t tw, :16; + uint32_t ip; + uint16_t cs; + uint16_t op:11, :5; + uint32_t dp; + uint16_t ds, :16; +}; + +void probe_fstp(bool force) +{ + const uint8_t *fstp_offs; + uint32_t flt; + struct x87_env fenv; + + fenv.cw = 0x35f; /* unmask PE */ + asm volatile ( "fninit;" + "fldcw %[cw];" + "fldpi;" + "mov $1f, %[offs];" + "test %[fep], %[fep];" + "jz 1f;" + _ASM_XEN_FEP + "1: fstps %[data]; 2:" + : [offs] "=&g" (fstp_offs), [data] "=m" (flt) + : [cw] "m" (fenv.cw), [fep] "q" (force) ); + + asm ( "fnstenv %0" : "=m" (fenv) ); + if ( fenv.ip != (unsigned long)fstp_offs ) + xtf_failure("Fail: FIP wrong (%08x)\n", fenv.ip); + if ( fenv.cs && fenv.cs != __KERN_CS ) + xtf_failure("Fail: FCS wrong (%04x)\n", fenv.cs); + if ( fenv.dp != (unsigned long)&flt ) + xtf_failure("Fail: FDP wrong (%08x)\n", fenv.dp); + if ( fenv.ds && fenv.ds != __KERN_DS ) + xtf_failure("Fail: FDS wrong (%04x)\n", fenv.ds); + /* Skip possible opcode prefixes before checking the opcode. */ + while ( (fstp_offs[0] & ~7) != 0xd8 ) + ++fstp_offs; + if ( fenv.op && fenv.op != (((fstp_offs[0] & 7) << 8) | fstp_offs[1]) ) + xtf_failure("Fail: FOP wrong (%03x)\n", fenv.op); +} + +void probe_fistp(bool force) +{ + unsigned long fldpi_offs; + exinfo_t fault = 0; + uint16_t fsw; + struct x87_env fenv; + + asm volatile ( "fninit;" + "0: fldpi;" + "mov $0b, %[offs];" + "test %[fep], %[fep];" + "jz 1f;" + _ASM_XEN_FEP + "1: fistps (%[ptr]); 2:" + _ASM_EXTABLE_HANDLER(1b, 2b, ex_record_fault_eax) + : [offs] "=&g" (fldpi_offs), "+a" (fault) + : [ptr] "r" (0), [fep] "q" (force) ); + + if ( !fault ) + xtf_error("Error: FISTP to NULL did not fault\n"); + + asm ( "fnstsw %0" : "=am" (fsw) ); + if ( fsw != 0x3800 ) + xtf_failure("Fail: FSW changed unexpectedly (%04x)\n", fsw); + + asm ( "fnstenv %0" : "=m" (fenv) ); + if ( fenv.ip != fldpi_offs ) + xtf_failure("Fail: FIP changed unexpectedly (%08x)\n", fenv.ip); + if ( fenv.cs && fenv.cs != __KERN_CS ) + xtf_failure("Fail: FCS changed unexpectedly (%04x)\n", fenv.cs); + if ( fenv.dp ) + xtf_failure("Fail: FDP changed unexpectedly (%08x)\n", fenv.dp); + if ( fenv.ds ) + xtf_failure("Fail: FDS changed unexpectedly (%04x)\n", fenv.ds); + if ( fenv.op && fenv.op != 0x1eb ) + xtf_failure("Fail: FOP changed unexpectedly (%03x)\n", fenv.op); +} + +void probe_vcvtps2ph(bool force) +{ + exinfo_t fault = 0; + uint32_t mxcsr = 0x1f80; + + asm volatile ( "vldmxcsr %[mxcsr];" + "vpcmpeqb %%xmm0,%%xmm0,%%xmm0;" + "vpcmpgtb %%xmm0,%%xmm0,%%xmm1;" + "vpunpcklbw %%xmm1,%%xmm0,%%xmm2;" + "test %[fep], %[fep];" + "jz 1f;" + _ASM_XEN_FEP + "1: vcvtps2ph $0,%%xmm2,(%[ptr]); 2:" + _ASM_EXTABLE_HANDLER(1b, 2b, ex_record_fault_eax) + : "+a" (fault) + : [ptr] "r" (0), [mxcsr] "m" (mxcsr), [fep] "q" (force) ); + + if ( !fault ) + xtf_error("Error: VCVTPS2PH to NULL did not fault\n"); + else if ( exinfo_vec(fault) == X86_EXC_UD ) + { + if ( force ) + xtf_skip("Emulator does not support VCVTPS2PH\n"); + else + xtf_failure("Fail: VCVTPS2PH did #UD\n"); + } + + asm ( "vstmxcsr %0" : "=m" (mxcsr) ); + if ( mxcsr != 0x1f80 ) + xtf_failure("Fail: MXCSR changed unexpectedly (%08x)\n", mxcsr); +} + +void run_tests(bool force) +{ + if ( cpu_has_fpu ) + { + printk("Testing%s FSTP\n", force ? " emulated" : ""); + probe_fstp(force); + + printk("Testing%s FISTP (to NULL)\n", force ? " emulated" : ""); + probe_fistp(force); + } + + if ( cpu_has_f16c ) + { + unsigned long cr4 = read_cr4(); + unsigned long xcr0; + + write_cr4(cr4 | X86_CR4_OSXSAVE); + xcr0 = read_xcr0(); + write_xcr0(xcr0 | XSTATE_SSE | XSTATE_YMM); + + printk("Testing%s VCVTPS2PH (to NULL)\n", force ? " emulated" : ""); + probe_vcvtps2ph(force); + + write_xcr0(xcr0); + write_cr4(cr4); + } +} + +void test_main(void) +{ + run_tests(false); + + if ( !xtf_has_fep ) + xtf_skip("FEP support not detected - some tests will be skipped\n"); + else + run_tests(true); + + xtf_success(NULL); +} + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */