add FPU/SIMD register state test Add tests to verify that - FPU insns leave correct (guest) values in FIP/FDP/FOP/FCS/FDS (at the example for FSTPS), - FPU insns writing memory don't update FPU register state when the write faults (at the example of FISTPS), - VCVTPS2PH (once implemented in the emulator) doesn't update MXCSR if its write faults (VCVTPS2PH is one of the very few SIMD insns writing to memory _and_ updating register state; the scatter family of insns also fall into this category, but we're quite far yet from supporting AVX-512 insns). Signed-off-by: Jan Beulich --- v3: Re-base. Add entry to all-tests.dox. v2: Introduce and use x87.h. Tolerate VCVTPS2PH misbehavior on Intel hardware. Tolerate AMD oddities in probe_fstp() and probe_fistp(). --- a/arch/x86/include/arch/cpuid.h +++ b/arch/x86/include/arch/cpuid.h @@ -78,6 +78,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/arch/x86/include/arch/x87.h @@ -0,0 +1,27 @@ +#ifndef XTF_X86_X87_H +#define XTF_X86_X87_H + +#include + +struct x87_env_pm32 { + 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; +}; + +#endif /* XTF_X86_X87_H */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ --- a/docs/all-tests.dox +++ b/docs/all-tests.dox @@ -20,6 +20,8 @@ and functionality. @subpage test-fpu-exception-emulation - FPU Exception Emulation. Covers XSA-190. +@subpage test-fpu-state - FPU state Emulation. + @subpage test-invlpg - `invlpg` instruction behaviour. @subpage test-lbr-tsx-vmentry - Haswell and later LBR/TSX Vmentry failure test. --- a/include/xen/arch-x86/xen.h +++ b/include/xen/arch-x86/xen.h @@ -15,6 +15,16 @@ #include "cpuid.h" +/* + * A number of GDT entries are reserved by Xen. These are not situated at the + * start of the GDT because some stupid OSes export hard-coded selector values + * in their ABI. These hard-coded values are always near the start of the GDT, + * so Xen places itself out of the way, at the far end of the GDT. + * + * NB The LDT is set using the MMUEXT_SET_LDT op of HYPERVISOR_mmuext_op + */ +#define FIRST_RESERVED_GDT_PAGE 14 + #ifndef __ASSEMBLY__ typedef unsigned long xen_pfn_t; --- /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,214 @@ +/** + * @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 +#include + +const char test_title[] = "FPU State"; + +void probe_fstp(bool force) +{ + const uint8_t *fstp_offs; + uint32_t flt; + struct x87_env_pm32 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 ) + { +#ifdef __x86_64__ + /* + * Tolerate CS being in the hypervisor reserved selector range on + * AMD hardware, as their 64-bit {F,}XRSTOR do not appear to clear + * FCS/FDS. + */ + if ( vendor_is_amd && !(fenv.cs & X86_SEL_LDT) && + (fenv.cs >> PAGE_SHIFT) == FIRST_RESERVED_GDT_PAGE ) + xtf_warning("Warning: FCS wrong (%04x)\n", fenv.cs); + else +#endif + 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_pm32 fenv; + typeof(xtf_failure) *diagfn; + const char *prefix; + + 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), + "X" (ex_record_fault_eax) ); + + 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) ); + /* + * The AMD-specific FPU pointer leak workaround in Xen (using FISTPL, + * which we check for below) causes all the remaining checks to fail. + */ + if ( !vendor_is_amd || (fenv.op & 0x738) != 0x300 ) + { + diagfn = xtf_failure; + prefix = "Fail"; + } + else + { + diagfn = xtf_warning; + prefix = "Warning"; + } + if ( fenv.ip != fldpi_offs ) + diagfn("%s: FIP changed unexpectedly (%08x)\n", prefix, fenv.ip); + if ( fenv.cs && fenv.cs != __KERN_CS ) + diagfn("%s: FCS changed unexpectedly (%04x)\n", prefix, fenv.cs); + if ( fenv.dp ) + diagfn("%s: FDP changed unexpectedly (%08x)\n", prefix, fenv.dp); + if ( fenv.ds ) + diagfn("%s: FDS changed unexpectedly (%04x)\n", prefix, fenv.ds); + if ( fenv.op && fenv.op != 0x1eb ) + diagfn("%s: FOP changed unexpectedly (%03x)\n", prefix, 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), + "X" (ex_record_fault_eax) ); + + 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 ) + { + /* + * Expect AMD hardware and emulation to behave correctly, but tolerate + * unexpected behavior on Intel hardware. + */ + if ( force || vendor_is_amd ) + xtf_failure("Fail: MXCSR changed unexpectedly (%08x)\n", mxcsr); + else + xtf_warning("Warning: 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: + */