extend FPU exception tests Also test #MF and #XM handling. Signed-off-by: Jan Beulich --- v3: New. --- /dev/null +++ b/arch/x86/include/arch/simd.h @@ -0,0 +1,32 @@ +#ifndef XTF_X86_SIMD_H +#define XTF_X86_SIMD_H + +#define X86_MXCSR_IE 0x00000001 +#define X86_MXCSR_DE 0x00000002 +#define X86_MXCSR_ZE 0x00000004 +#define X86_MXCSR_OE 0x00000008 +#define X86_MXCSR_UE 0x00000010 +#define X86_MXCSR_PE 0x00000020 +#define X86_MXCSR_DAZ 0x00000040 +#define X86_MXCSR_IM 0x00000080 +#define X86_MXCSR_DM 0x00000100 +#define X86_MXCSR_ZM 0x00000200 +#define X86_MXCSR_OM 0x00000400 +#define X86_MXCSR_UM 0x00000800 +#define X86_MXCSR_PM 0x00001000 +#define X86_MXCSR_RC_MASK 0x00006000 +#define X86_MXCSR_FZ 0x00008000 + +#define X86_MXCSR_DEFAULT 0x1f80 + +#endif /* XTF_X86_SIMD_H */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ --- a/arch/x86/include/arch/x87.h +++ b/arch/x86/include/arch/x87.h @@ -3,6 +3,27 @@ #include +#define X86_FCW_IM 0x0001 +#define X86_FCW_DM 0x0002 +#define X86_FCW_ZM 0x0004 +#define X86_FCW_OM 0x0008 +#define X86_FCW_UM 0x0010 +#define X86_FCW_PM 0x0020 +#define X86_FCW_PC_MASK 0x0300 +#define X86_FCW_RC_MASK 0x0c00 + +#define X86_PC_SINGLE 0 +#define X86_PC_DOUBLE 2 +#define X86_PC_EXTENDED 3 + +/* These also apply to MXCSR. */ +#define X86_RC_NEAREST 0 +#define X86_RC_DOWN 1 +#define X86_RC_UP 2 +#define X86_RC_ZERO 3 + +#define X86_FCW_DEFAULT 0x037f + struct x87_env_pm32 { uint16_t cw, :16; uint16_t sw, :16; --- a/tests/fpu-exception-emulation/main.c +++ b/tests/fpu-exception-emulation/main.c @@ -22,15 +22,14 @@ * checking that appropriate exceptions are raised (@#NM or @#UD), or that no * exception is raised. * + * Additionally #MF and #XM behavior is being tested. + * * Each test is run against real hardware, and forced through the x86 * instruction emulator (if FEP is available). * * This test covers XSA-190, where @#NM was not being raised appropriately, * therefore interfering with lazy FPU task switching in the guest. * - * @todo Extend to include unmasked pending exceptions. There is definitely - * work required in the instruction emulator to support this properly. - * * @see tests/fpu-exception-emulation/main.c */ #include @@ -38,16 +37,27 @@ #include #include #include +#include #include +#include const char test_title[] = "FPU Exception Emulation"; #define CR0_SYM(...) TOK_OR(X86_CR0_, ##__VA_ARGS__) -#define CR0_MASK CR0_SYM(EM, MP, TS) +#define CR0_MASK CR0_SYM(EM, MP, TS, NE) + +#define CR4_SYM(...) TOK_OR(X86_CR4_OS, ##__VA_ARGS__) +#define CR4_MASK CR4_SYM(FXSR, XMMEXCPT, XSAVE) + +#define FCW_SYM(...) TOK_OR(X86_FCW_, ##__VA_ARGS__) + +#define MXCSR_SYM(...) TOK_OR(X86_MXCSR_, ##__VA_ARGS__) struct test_cfg { unsigned long cr0; + unsigned long cr4; + unsigned int control; exinfo_t fault; }; @@ -59,20 +69,27 @@ static unsigned long default_cr0; */ static const struct test_cfg x87[] = { - { CR0_SYM( ), 0 }, - { CR0_SYM( TS), EXINFO_SYM(NM, 0) }, - { CR0_SYM( MP ), 0 }, - { CR0_SYM( MP, TS), EXINFO_SYM(NM, 0) }, - { CR0_SYM(EM ), EXINFO_SYM(NM, 0) }, - { CR0_SYM(EM, TS), EXINFO_SYM(NM, 0) }, - { CR0_SYM(EM, MP ), EXINFO_SYM(NM, 0) }, - { CR0_SYM(EM, MP, TS), EXINFO_SYM(NM, 0) }, + { CR0_SYM( ), 0, 0, 0 }, + { CR0_SYM( TS), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM( MP ), 0, 0, 0 }, + { CR0_SYM( MP, TS), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(EM ), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(EM, TS), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(EM, MP ), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(EM, MP, TS), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(NE), 0, FCW_SYM(IM), EXINFO_SYM(MF, 0) }, }; -exinfo_t probe_x87(bool force) +exinfo_t probe_x87(unsigned int control, bool force) { exinfo_t fault = 0; + if ( control ) + { + control = X86_FCW_DEFAULT & ~control; + asm volatile ("fninit; fldcw %0; faddp" :: "m" (control)); + } + asm volatile ("test %[fep], %[fep];" "jz 1f;" _ASM_XEN_FEP @@ -82,6 +99,9 @@ exinfo_t probe_x87(bool force) : [fep] "q" (force), "X" (ex_record_fault_eax)); + if ( control ) + asm volatile ("fnclex"); + return fault; } @@ -92,20 +112,27 @@ exinfo_t probe_x87(bool force) */ static const struct test_cfg x87_wait[] = { - { CR0_SYM( ), 0 }, - { CR0_SYM( TS), 0 }, - { CR0_SYM( MP ), 0 }, - { CR0_SYM( MP, TS), EXINFO_SYM(NM, 0) }, - { CR0_SYM(EM ), 0 }, - { CR0_SYM(EM, TS), 0 }, - { CR0_SYM(EM, MP ), 0 }, - { CR0_SYM(EM, MP, TS), EXINFO_SYM(NM, 0) }, + { CR0_SYM( ), 0, 0, 0 }, + { CR0_SYM( TS), 0, 0, 0 }, + { CR0_SYM( MP ), 0, 0, 0 }, + { CR0_SYM( MP, TS), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(EM ), 0, 0, 0 }, + { CR0_SYM(EM, TS), 0, 0, 0 }, + { CR0_SYM(EM, MP ), 0, 0, 0 }, + { CR0_SYM(EM, MP, TS), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(NE), 0, FCW_SYM(IM), EXINFO_SYM(MF, 0) }, }; -exinfo_t probe_x87_wait(bool force) +exinfo_t probe_x87_wait(unsigned int control, bool force) { exinfo_t fault = 0; + if ( control ) + { + control = X86_FCW_DEFAULT & ~control; + asm volatile ("fninit; fldcw %0; faddp" :: "m" (control)); + } + asm volatile ("test %[fep], %[fep];" "jz 1f;" _ASM_XEN_FEP @@ -115,26 +142,29 @@ exinfo_t probe_x87_wait(bool force) : [fep] "q" (force), "X" (ex_record_fault_eax)); + if ( control ) + asm volatile ("fnclex"); + return fault; } /** - * MMX and SSE instructions. Emulation is unsupported (thus raising @#UD), + * MMX instructions. Emulation is unsupported (thus raising @#UD), * but @#NM should be raised if the task has been switched. */ -static const struct test_cfg mmx_sse[] = +static const struct test_cfg mmx[] = { - { CR0_SYM( ), 0 }, - { CR0_SYM( TS), EXINFO_SYM(NM, 0) }, - { CR0_SYM( MP ), 0 }, - { CR0_SYM( MP, TS), EXINFO_SYM(NM, 0) }, - { CR0_SYM(EM ), EXINFO_SYM(UD, 0) }, - { CR0_SYM(EM, TS), EXINFO_SYM(UD, 0) }, - { CR0_SYM(EM, MP ), EXINFO_SYM(UD, 0) }, - { CR0_SYM(EM, MP, TS), EXINFO_SYM(UD, 0) }, + { CR0_SYM( ), 0, 0, 0 }, + { CR0_SYM( TS), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM( MP ), 0, 0, 0 }, + { CR0_SYM( MP, TS), 0, 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(EM ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, TS), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, MP ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, MP, TS), 0, 0, EXINFO_SYM(UD, 0) }, }; -exinfo_t probe_mmx(bool force) +exinfo_t probe_mmx(unsigned int control, bool force) { exinfo_t fault = 0; @@ -150,10 +180,56 @@ exinfo_t probe_mmx(bool force) return fault; } -exinfo_t probe_sse(bool force) +/** + * SSE instructions. Emulation is unsupported (thus raising @#UD), + * but @#NM should be raised if the task has been switched. + */ +static const struct test_cfg sse[] = +{ + { CR0_SYM( ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM( TS), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM( MP ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM( MP, TS), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, TS), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, MP ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, MP, TS), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM( ), CR4_SYM(FXSR), 0, 0 }, + { CR0_SYM( TS), CR4_SYM(FXSR), 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM( MP ), CR4_SYM(FXSR), 0, 0 }, + { CR0_SYM( MP, TS), CR4_SYM(FXSR), 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(EM ), CR4_SYM(FXSR), 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, TS), CR4_SYM(FXSR), 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, MP ), CR4_SYM(FXSR), 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, MP, TS), CR4_SYM(FXSR), 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM( MP ), CR4_SYM(FXSR), + MXCSR_SYM(IM), EXINFO_SYM(UD, 0) }, + { CR0_SYM( MP ), CR4_SYM(FXSR, XMMEXCPT), + MXCSR_SYM(IM), EXINFO_SYM(XM, 0) }, +}; + +exinfo_t probe_sse(unsigned int control, bool force) { exinfo_t fault = 0; + if ( control ) + { + control = X86_MXCSR_DEFAULT & ~control; + asm volatile ("0: xorps %%xmm0, %%xmm0;" + "ldmxcsr %[mxcsr];" + "test %[fep], %[fep];" + "jz 1f;" + _ASM_XEN_FEP + "1: divps %%xmm0, %%xmm0; 2:" + _ASM_EXTABLE_HANDLER(0b, 2b, ex_record_fault_eax) + _ASM_EXTABLE_HANDLER(1b, 2b, ex_record_fault_eax) + : "+a" (fault) + : [mxcsr] "m" (control), + [fep] "q" (force), + "X" (ex_record_fault_eax)); + return fault; + } + asm volatile ("test %[fep], %[fep];" "jz 1f;" _ASM_XEN_FEP @@ -172,17 +248,25 @@ exinfo_t probe_sse(bool force) */ static const struct test_cfg avx[] = { - { CR0_SYM( ), 0 }, - { CR0_SYM( TS), EXINFO_SYM(NM, 0) }, - { CR0_SYM( MP ), 0 }, - { CR0_SYM( MP, TS), EXINFO_SYM(NM, 0) }, - { CR0_SYM(EM ), 0 }, - { CR0_SYM(EM, TS), EXINFO_SYM(NM, 0) }, - { CR0_SYM(EM, MP ), 0 }, - { CR0_SYM(EM, MP, TS), EXINFO_SYM(NM, 0) }, + { CR0_SYM( ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM( TS), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM( MP ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM( MP, TS), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, TS), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, MP ), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM(EM, MP, TS), 0, 0, EXINFO_SYM(UD, 0) }, + { CR0_SYM( ), CR4_SYM(XSAVE), 0, 0 }, + { CR0_SYM( TS), CR4_SYM(XSAVE), 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM( MP ), CR4_SYM(XSAVE), 0, 0 }, + { CR0_SYM( MP, TS), CR4_SYM(XSAVE), 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(EM ), CR4_SYM(XSAVE), 0, 0 }, + { CR0_SYM(EM, TS), CR4_SYM(XSAVE), 0, EXINFO_SYM(NM, 0) }, + { CR0_SYM(EM, MP ), CR4_SYM(XSAVE), 0, 0 }, + { CR0_SYM(EM, MP, TS), CR4_SYM(XSAVE), 0, EXINFO_SYM(NM, 0) }, }; -static exinfo_t probe_avx(bool force) +static exinfo_t probe_avx(unsigned int control, bool force) { exinfo_t fault = 0; @@ -199,21 +283,27 @@ static exinfo_t probe_avx(bool force) } void run_sequence(const struct test_cfg *seq, unsigned int nr, - unsigned int (*fn)(bool), bool force, exinfo_t override) + unsigned int (*fn)(unsigned int, bool), bool force, + exinfo_t override) { unsigned int i; for ( i = 0; i < nr; ++i ) { const struct test_cfg *t = &seq[i]; + unsigned long cr4 = read_cr4(); exinfo_t res, exp = override ?: t->fault; write_cr0((default_cr0 & ~CR0_MASK) | t->cr0); - res = fn(force); + write_cr4((cr4 & ~CR4_MASK) | t->cr4); + + res = fn(t->control, force); + + write_cr4(cr4); if ( res != exp ) { - char expstr[12], gotstr[12], cr0str[12]; + char expstr[12], gotstr[12], cr0str[12], cr4str[24]; x86_decode_exinfo(expstr, ARRAY_SIZE(expstr), exp); x86_decode_exinfo(gotstr, ARRAY_SIZE(gotstr), res); @@ -223,8 +313,14 @@ void run_sequence(const struct test_cfg t->cr0 & X86_CR0_MP ? " MP" : "", t->cr0 & X86_CR0_TS ? " TS" : ""); - xtf_failure(" Expected %s, got %s (cr0:%s)\n", - expstr, gotstr, cr0str[0] ? cr0str : " - "); + snprintf(cr4str, sizeof(cr4str), "%s%s%s", + t->cr4 & X86_CR4_OSFXSR ? " FXSR" : "", + t->cr4 & X86_CR4_OSXMMEXCPT ? " XMMXCPT" : "", + t->cr4 & X86_CR4_OSXSAVE ? " XSAVE" : ""); + + xtf_failure(" Expected %s, got %s (cr0:%s cr4:%s ctrl:%04x)\n", + expstr, gotstr, cr0str[0] ? cr0str : " - ", + cr4str[0] ? cr4str : " - ", t->control); } } } @@ -243,23 +339,13 @@ void run_tests(bool force) if ( cpu_has_mmx ) { printk("Testing%s MMX\n", force ? " emulated" : ""); - run_sequence(mmx_sse, ARRAY_SIZE(mmx_sse), probe_mmx, force, 0); + run_sequence(mmx, ARRAY_SIZE(mmx), probe_mmx, force, 0); } if ( cpu_has_sse ) { - unsigned long cr4 = read_cr4(); - printk("Testing%s SSE\n", force ? " emulated" : ""); - write_cr4(cr4 & ~X86_CR4_OSFXSR); - run_sequence(mmx_sse, ARRAY_SIZE(mmx_sse), probe_sse, force, - EXINFO_SYM(UD, 0)); - - printk("Testing%s SSE (CR4.OSFXSR)\n", force ? " emulated" : ""); - write_cr4(cr4 | X86_CR4_OSFXSR); - run_sequence(mmx_sse, ARRAY_SIZE(mmx_sse), probe_sse, force, 0); - - write_cr4(cr4); + run_sequence(sse, ARRAY_SIZE(sse), probe_sse, force, 0); } if ( cpu_has_avx ) @@ -267,19 +353,15 @@ void run_tests(bool force) unsigned long cr4 = read_cr4(); unsigned long xcr0; - printk("Testing%s AVX\n", force ? " emulated" : ""); - write_cr4(cr4 & ~X86_CR4_OSXSAVE); - run_sequence(avx, ARRAY_SIZE(avx), probe_avx, force, - EXINFO_SYM(UD, 0)); - - printk("Testing%s AVX (CR4.OSXSAVE)\n", force ? " emulated" : ""); write_cr4(cr4 | X86_CR4_OSXSAVE); xcr0 = read_xcr0(); + + printk("Testing%s AVX\n", force ? " emulated" : ""); write_xcr0(xcr0 & ~XSTATE_YMM); run_sequence(avx, ARRAY_SIZE(avx), probe_avx, force, EXINFO_SYM(UD, 0)); - printk("Testing%s AVX (CR4.OSXSAVE+XCR0.YMM)\n", force ? " emulated" : ""); + printk("Testing%s AVX (XCR0.YMM)\n", force ? " emulated" : ""); write_xcr0(xcr0 | XSTATE_SSE | XSTATE_YMM); run_sequence(avx, ARRAY_SIZE(avx), probe_avx, force, 0);