From a2d57703b3e735c700689b030c126fa8ba6793b9 Mon Sep 17 00:00:00 2001 From: Alexander Boettcher Date: Mon, 29 Jun 2020 15:25:03 +0200 Subject: tcg/svm: use host cr4 during NPT page table walk During a page table walk of TCG+SVM the code in target/i386/excp_helper.c get_hphys() uses the cr4 register of the guest instead of the hypervisor to check for the PSE bit. In the test case we have, the guest have not enabled (yet) the PSE bit and so the page table walk results in a wrong host physical address resolution and wrong content read by the guest. Attached patch is against 4.2.1, but works also on 3.1.0. It fixes the issue for our automated testcase, which is a 32bit hypervisor w/o PAE support running a guest VM with tcg+svm. The test worked beforehand up to qemu 2.12, started to fail with qemu 3.0 and later. The added TCG/SVM NPT commit seems to introduce the regression. In case someone want to try to reproduce it, the iso is at [0], the good case is [1] and the failing case is [2]. The used commandline is: qemu-system-i386 -no-kvm -nographic -cpu phenom -m 512 -machine q35 -cdrom seoul-vmm-test.iso [0] https://depot.genode.org/alex-ab/images/seoul-vmm-test.iso [1] https://depot.genode.org/alex-ab/images/seoul-vmm-good.txt [2] https://depot.genode.org/alex-ab/images/seoul-vmm-bad.txt Signed-off-by: Alexander Boettcher Signed-off-by: Paolo Bonzini --- target/i386/excp_helper.c | 4 ++-- target/i386/svm.h | 1 + target/i386/svm_helper.c | 7 ++++++- 3 files changed, 9 insertions(+), 3 deletions(-) (limited to 'target') diff --git a/target/i386/excp_helper.c b/target/i386/excp_helper.c index 1447bda..b10c7ec 100644 --- a/target/i386/excp_helper.c +++ b/target/i386/excp_helper.c @@ -262,8 +262,8 @@ static hwaddr get_hphys(CPUState *cs, hwaddr gphys, MMUAccessType access_type, } ptep = pde | PG_NX_MASK; - /* if PSE bit is set, then we use a 4MB page */ - if ((pde & PG_PSE_MASK) && (env->cr[4] & CR4_PSE_MASK)) { + /* if host cr4 PSE bit is set, then we use a 4MB page */ + if ((pde & PG_PSE_MASK) && (env->nested_pg_mode & SVM_NPT_PSE)) { page_size = 4096 * 1024; pte_addr = pde_addr; diff --git a/target/i386/svm.h b/target/i386/svm.h index 23a3a04..ae30fc6 100644 --- a/target/i386/svm.h +++ b/target/i386/svm.h @@ -135,6 +135,7 @@ #define SVM_NPT_PAE (1 << 0) #define SVM_NPT_LMA (1 << 1) #define SVM_NPT_NXE (1 << 2) +#define SVM_NPT_PSE (1 << 3) #define SVM_NPTEXIT_P (1ULL << 0) #define SVM_NPTEXIT_RW (1ULL << 1) diff --git a/target/i386/svm_helper.c b/target/i386/svm_helper.c index 7b8105a..6224387 100644 --- a/target/i386/svm_helper.c +++ b/target/i386/svm_helper.c @@ -209,16 +209,21 @@ void helper_vmrun(CPUX86State *env, int aflag, int next_eip_addend) nested_ctl = x86_ldq_phys(cs, env->vm_vmcb + offsetof(struct vmcb, control.nested_ctl)); + + env->nested_pg_mode = 0; + if (nested_ctl & SVM_NPT_ENABLED) { env->nested_cr3 = x86_ldq_phys(cs, env->vm_vmcb + offsetof(struct vmcb, control.nested_cr3)); env->hflags2 |= HF2_NPT_MASK; - env->nested_pg_mode = 0; if (env->cr[4] & CR4_PAE_MASK) { env->nested_pg_mode |= SVM_NPT_PAE; } + if (env->cr[4] & CR4_PSE_MASK) { + env->nested_pg_mode |= SVM_NPT_PSE; + } if (env->hflags & HF_LMA_MASK) { env->nested_pg_mode |= SVM_NPT_LMA; } -- cgit v1.1 From 3ddc0eca2229846bfecc3485648a6cb85a466dc7 Mon Sep 17 00:00:00 2001 From: Joseph Myers Date: Thu, 25 Jun 2020 23:57:44 +0000 Subject: target/i386: set SSE FTZ in correct floating-point state The code to set floating-point state when MXCSR changes calls set_flush_to_zero on &env->fp_status, so affecting the x87 floating-point state rather than the SSE state. Fix to call it for &env->sse_status instead. Signed-off-by: Joseph Myers Message-Id: Signed-off-by: Paolo Bonzini --- target/i386/fpu_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'target') diff --git a/target/i386/fpu_helper.c b/target/i386/fpu_helper.c index 71cec39..ec5b9db 100644 --- a/target/i386/fpu_helper.c +++ b/target/i386/fpu_helper.c @@ -2972,7 +2972,7 @@ void update_mxcsr_status(CPUX86State *env) set_flush_inputs_to_zero((mxcsr & SSE_DAZ) ? 1 : 0, &env->sse_status); /* set flush to zero */ - set_flush_to_zero((mxcsr & SSE_FZ) ? 1 : 0, &env->fp_status); + set_flush_to_zero((mxcsr & SSE_FZ) ? 1 : 0, &env->sse_status); } void helper_ldmxcsr(CPUX86State *env, uint32_t val) -- cgit v1.1 From 418b0f93d12a1589d5031405de857844f32e9ccc Mon Sep 17 00:00:00 2001 From: Joseph Myers Date: Thu, 25 Jun 2020 23:58:31 +0000 Subject: target/i386: fix IEEE SSE floating-point exception raising The SSE instruction implementations all fail to raise the expected IEEE floating-point exceptions because they do nothing to convert the exception state from the softfloat machinery into the exception flags in MXCSR. Fix this by adding such conversions. Unlike for x87, emulated SSE floating-point operations might be optimized using hardware floating point on the host, and so a different approach is taken that is compatible with such optimizations. The required invariant is that all exceptions set in env->sse_status (other than "denormal operand", for which the SSE semantics are different from those in the softfloat code) are ones that are set in the MXCSR; the emulated MXCSR is updated lazily when code reads MXCSR, while when code sets MXCSR, the exceptions in env->sse_status are set accordingly. A few instructions do not raise all the exceptions that would be raised by the softfloat code, and those instructions are made to save and restore the softfloat exception state accordingly. Nothing is done about "denormal operand"; setting that (only for the case when input denormals are *not* flushed to zero, the opposite of the logic in the softfloat code for such an exception) will require custom code for relevant instructions, or else architecture-specific conditionals in the softfloat code for when to set such an exception together with custom code for various SSE conversion and rounding instructions that do not set that exception. Nothing is done about trapping exceptions (for which there is minimal and largely broken support in QEMU's emulation in the x87 case and no support at all in the SSE case). Signed-off-by: Joseph Myers Message-Id: Signed-off-by: Paolo Bonzini --- target/i386/Makefile.objs | 1 + target/i386/cpu.h | 1 + target/i386/fpu_helper.c | 35 +++++++++++++++++++++++++++++++++++ target/i386/gdbstub.c | 1 + target/i386/helper.c | 1 + target/i386/helper.h | 1 + target/i386/ops_sse.h | 28 ++++++++++++++++------------ target/i386/tcg-stub.c | 25 +++++++++++++++++++++++++ target/i386/translate.c | 1 + 9 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 target/i386/tcg-stub.c (limited to 'target') diff --git a/target/i386/Makefile.objs b/target/i386/Makefile.objs index 48e0c28..0b93143 100644 --- a/target/i386/Makefile.objs +++ b/target/i386/Makefile.objs @@ -3,6 +3,7 @@ obj-$(CONFIG_TCG) += translate.o obj-$(CONFIG_TCG) += bpt_helper.o cc_helper.o excp_helper.o fpu_helper.o obj-$(CONFIG_TCG) += int_helper.o mem_helper.o misc_helper.o mpx_helper.o obj-$(CONFIG_TCG) += seg_helper.o smm_helper.o svm_helper.o +obj-$(call lnot,$(CONFIG_TCG)) += tcg-stub.o obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o ifeq ($(CONFIG_SOFTMMU),y) obj-y += machine.o arch_memory_mapping.o arch_dump.o monitor.o diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 7d77efd..06b2e3a 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2143,6 +2143,7 @@ static inline bool cpu_vmx_maybe_enabled(CPUX86State *env) /* fpu_helper.c */ void update_fp_status(CPUX86State *env); void update_mxcsr_status(CPUX86State *env); +void update_mxcsr_from_sse_status(CPUX86State *env); static inline void cpu_set_mxcsr(CPUX86State *env, uint32_t mxcsr) { diff --git a/target/i386/fpu_helper.c b/target/i386/fpu_helper.c index ec5b9db..f5e6c4b 100644 --- a/target/i386/fpu_helper.c +++ b/target/i386/fpu_helper.c @@ -2539,6 +2539,7 @@ static void do_xsave_fpu(CPUX86State *env, target_ulong ptr, uintptr_t ra) static void do_xsave_mxcsr(CPUX86State *env, target_ulong ptr, uintptr_t ra) { + update_mxcsr_from_sse_status(env); cpu_stl_data_ra(env, ptr + XO(legacy.mxcsr), env->mxcsr, ra); cpu_stl_data_ra(env, ptr + XO(legacy.mxcsr_mask), 0x0000ffff, ra); } @@ -2968,6 +2969,14 @@ void update_mxcsr_status(CPUX86State *env) } set_float_rounding_mode(rnd_type, &env->sse_status); + /* Set exception flags. */ + set_float_exception_flags((mxcsr & FPUS_IE ? float_flag_invalid : 0) | + (mxcsr & FPUS_ZE ? float_flag_divbyzero : 0) | + (mxcsr & FPUS_OE ? float_flag_overflow : 0) | + (mxcsr & FPUS_UE ? float_flag_underflow : 0) | + (mxcsr & FPUS_PE ? float_flag_inexact : 0), + &env->sse_status); + /* set denormals are zero */ set_flush_inputs_to_zero((mxcsr & SSE_DAZ) ? 1 : 0, &env->sse_status); @@ -2975,6 +2984,32 @@ void update_mxcsr_status(CPUX86State *env) set_flush_to_zero((mxcsr & SSE_FZ) ? 1 : 0, &env->sse_status); } +void update_mxcsr_from_sse_status(CPUX86State *env) +{ + if (tcg_enabled()) { + uint8_t flags = get_float_exception_flags(&env->sse_status); + /* + * The MXCSR denormal flag has opposite semantics to + * float_flag_input_denormal (the softfloat code sets that flag + * only when flushing input denormals to zero, but SSE sets it + * only when not flushing them to zero), so is not converted + * here. + */ + env->mxcsr |= ((flags & float_flag_invalid ? FPUS_IE : 0) | + (flags & float_flag_divbyzero ? FPUS_ZE : 0) | + (flags & float_flag_overflow ? FPUS_OE : 0) | + (flags & float_flag_underflow ? FPUS_UE : 0) | + (flags & float_flag_inexact ? FPUS_PE : 0) | + (flags & float_flag_output_denormal ? FPUS_UE | FPUS_PE : + 0)); + } +} + +void helper_update_mxcsr(CPUX86State *env) +{ + update_mxcsr_from_sse_status(env); +} + void helper_ldmxcsr(CPUX86State *env, uint32_t val) { cpu_set_mxcsr(env, val); diff --git a/target/i386/gdbstub.c b/target/i386/gdbstub.c index b98a995..9ae43bd 100644 --- a/target/i386/gdbstub.c +++ b/target/i386/gdbstub.c @@ -184,6 +184,7 @@ int x86_cpu_gdb_read_register(CPUState *cs, GByteArray *mem_buf, int n) return gdb_get_reg32(mem_buf, 0); /* fop */ case IDX_MXCSR_REG: + update_mxcsr_from_sse_status(env); return gdb_get_reg32(mem_buf, env->mxcsr); case IDX_CTL_CR0_REG: diff --git a/target/i386/helper.c b/target/i386/helper.c index c3a6e4f..fa2a1dc 100644 --- a/target/i386/helper.c +++ b/target/i386/helper.c @@ -544,6 +544,7 @@ void x86_cpu_dump_state(CPUState *cs, FILE *f, int flags) for(i = 0; i < 8; i++) { fptag |= ((!env->fptags[i]) << i); } + update_mxcsr_from_sse_status(env); qemu_fprintf(f, "FCW=%04x FSW=%04x [ST=%d] FTW=%02x MXCSR=%08x\n", env->fpuc, (env->fpus & ~0x3800) | (env->fpstt & 0x7) << 11, diff --git a/target/i386/helper.h b/target/i386/helper.h index 8f9e190..c2ae2f7 100644 --- a/target/i386/helper.h +++ b/target/i386/helper.h @@ -207,6 +207,7 @@ DEF_HELPER_FLAGS_2(pext, TCG_CALL_NO_RWG_SE, tl, tl, tl) /* MMX/SSE */ DEF_HELPER_2(ldmxcsr, void, env, i32) +DEF_HELPER_1(update_mxcsr, void, env) DEF_HELPER_1(enter_mmx, void, env) DEF_HELPER_1(emms, void, env) DEF_HELPER_3(movq, void, env, ptr, ptr) diff --git a/target/i386/ops_sse.h b/target/i386/ops_sse.h index 14f2b16..c7614f8 100644 --- a/target/i386/ops_sse.h +++ b/target/i386/ops_sse.h @@ -843,6 +843,7 @@ int64_t helper_cvttsd2sq(CPUX86State *env, ZMMReg *s) void helper_rsqrtps(CPUX86State *env, ZMMReg *d, ZMMReg *s) { + uint8_t old_flags = get_float_exception_flags(&env->sse_status); d->ZMM_S(0) = float32_div(float32_one, float32_sqrt(s->ZMM_S(0), &env->sse_status), &env->sse_status); @@ -855,26 +856,33 @@ void helper_rsqrtps(CPUX86State *env, ZMMReg *d, ZMMReg *s) d->ZMM_S(3) = float32_div(float32_one, float32_sqrt(s->ZMM_S(3), &env->sse_status), &env->sse_status); + set_float_exception_flags(old_flags, &env->sse_status); } void helper_rsqrtss(CPUX86State *env, ZMMReg *d, ZMMReg *s) { + uint8_t old_flags = get_float_exception_flags(&env->sse_status); d->ZMM_S(0) = float32_div(float32_one, float32_sqrt(s->ZMM_S(0), &env->sse_status), &env->sse_status); + set_float_exception_flags(old_flags, &env->sse_status); } void helper_rcpps(CPUX86State *env, ZMMReg *d, ZMMReg *s) { + uint8_t old_flags = get_float_exception_flags(&env->sse_status); d->ZMM_S(0) = float32_div(float32_one, s->ZMM_S(0), &env->sse_status); d->ZMM_S(1) = float32_div(float32_one, s->ZMM_S(1), &env->sse_status); d->ZMM_S(2) = float32_div(float32_one, s->ZMM_S(2), &env->sse_status); d->ZMM_S(3) = float32_div(float32_one, s->ZMM_S(3), &env->sse_status); + set_float_exception_flags(old_flags, &env->sse_status); } void helper_rcpss(CPUX86State *env, ZMMReg *d, ZMMReg *s) { + uint8_t old_flags = get_float_exception_flags(&env->sse_status); d->ZMM_S(0) = float32_div(float32_one, s->ZMM_S(0), &env->sse_status); + set_float_exception_flags(old_flags, &env->sse_status); } static inline uint64_t helper_extrq(uint64_t src, int shift, int len) @@ -1764,6 +1772,7 @@ void glue(helper_phminposuw, SUFFIX)(CPUX86State *env, Reg *d, Reg *s) void glue(helper_roundps, SUFFIX)(CPUX86State *env, Reg *d, Reg *s, uint32_t mode) { + uint8_t old_flags = get_float_exception_flags(&env->sse_status); signed char prev_rounding_mode; prev_rounding_mode = env->sse_status.float_rounding_mode; @@ -1789,19 +1798,18 @@ void glue(helper_roundps, SUFFIX)(CPUX86State *env, Reg *d, Reg *s, d->ZMM_S(2) = float32_round_to_int(s->ZMM_S(2), &env->sse_status); d->ZMM_S(3) = float32_round_to_int(s->ZMM_S(3), &env->sse_status); -#if 0 /* TODO */ - if (mode & (1 << 3)) { + if (mode & (1 << 3) && !(old_flags & float_flag_inexact)) { set_float_exception_flags(get_float_exception_flags(&env->sse_status) & ~float_flag_inexact, &env->sse_status); } -#endif env->sse_status.float_rounding_mode = prev_rounding_mode; } void glue(helper_roundpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s, uint32_t mode) { + uint8_t old_flags = get_float_exception_flags(&env->sse_status); signed char prev_rounding_mode; prev_rounding_mode = env->sse_status.float_rounding_mode; @@ -1825,19 +1833,18 @@ void glue(helper_roundpd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s, d->ZMM_D(0) = float64_round_to_int(s->ZMM_D(0), &env->sse_status); d->ZMM_D(1) = float64_round_to_int(s->ZMM_D(1), &env->sse_status); -#if 0 /* TODO */ - if (mode & (1 << 3)) { + if (mode & (1 << 3) && !(old_flags & float_flag_inexact)) { set_float_exception_flags(get_float_exception_flags(&env->sse_status) & ~float_flag_inexact, &env->sse_status); } -#endif env->sse_status.float_rounding_mode = prev_rounding_mode; } void glue(helper_roundss, SUFFIX)(CPUX86State *env, Reg *d, Reg *s, uint32_t mode) { + uint8_t old_flags = get_float_exception_flags(&env->sse_status); signed char prev_rounding_mode; prev_rounding_mode = env->sse_status.float_rounding_mode; @@ -1860,19 +1867,18 @@ void glue(helper_roundss, SUFFIX)(CPUX86State *env, Reg *d, Reg *s, d->ZMM_S(0) = float32_round_to_int(s->ZMM_S(0), &env->sse_status); -#if 0 /* TODO */ - if (mode & (1 << 3)) { + if (mode & (1 << 3) && !(old_flags & float_flag_inexact)) { set_float_exception_flags(get_float_exception_flags(&env->sse_status) & ~float_flag_inexact, &env->sse_status); } -#endif env->sse_status.float_rounding_mode = prev_rounding_mode; } void glue(helper_roundsd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s, uint32_t mode) { + uint8_t old_flags = get_float_exception_flags(&env->sse_status); signed char prev_rounding_mode; prev_rounding_mode = env->sse_status.float_rounding_mode; @@ -1895,13 +1901,11 @@ void glue(helper_roundsd, SUFFIX)(CPUX86State *env, Reg *d, Reg *s, d->ZMM_D(0) = float64_round_to_int(s->ZMM_D(0), &env->sse_status); -#if 0 /* TODO */ - if (mode & (1 << 3)) { + if (mode & (1 << 3) && !(old_flags & float_flag_inexact)) { set_float_exception_flags(get_float_exception_flags(&env->sse_status) & ~float_flag_inexact, &env->sse_status); } -#endif env->sse_status.float_rounding_mode = prev_rounding_mode; } diff --git a/target/i386/tcg-stub.c b/target/i386/tcg-stub.c new file mode 100644 index 0000000..b00e23d --- /dev/null +++ b/target/i386/tcg-stub.c @@ -0,0 +1,25 @@ +/* + * x86 FPU, MMX/3DNow!/SSE/SSE2/SSE3/SSSE3/SSE4/PNI helpers + * + * Copyright (c) 2003 Fabrice Bellard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "qemu/osdep.h" +#include "cpu.h" + +void update_mxcsr_from_sse_status(CPUX86State *env) +{ +} diff --git a/target/i386/translate.c b/target/i386/translate.c index 5e5dbb4..b3fea54 100644 --- a/target/i386/translate.c +++ b/target/i386/translate.c @@ -8157,6 +8157,7 @@ static target_ulong disas_insn(DisasContext *s, CPUState *cpu) gen_exception(s, EXCP07_PREX, pc_start - s->cs_base); break; } + gen_helper_update_mxcsr(cpu_env); gen_lea_modrm(env, s, modrm); tcg_gen_ld32u_tl(s->T0, cpu_env, offsetof(CPUX86State, mxcsr)); gen_op_st_v(s, MO_32, s->T0, s->A0); -- cgit v1.1 From b16c0e20c74218f2d69710cedad11da7dd4d2190 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 20 May 2020 10:49:22 -0400 Subject: KVM: add support for AMD nested live migration Support for nested guest live migration is part of Linux 5.8, add the corresponding code to QEMU. The migration format consists of a few flags, is an opaque 4k blob. The blob is in VMCB format (the control area represents the L1 VMCB control fields, the save area represents the pre-vmentry state; KVM does not use the host save area since the AMD manual allows that) but QEMU does not really care about that. However, the flags need to be copied to hflags/hflags2 and back. In addition, support for retrieving and setting the AMD nested virtualization states allows the L1 guest to be reset while running a nested guest, but a small bug in CPU reset needs to be fixed for that to work. Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 1 + target/i386/cpu.h | 5 +++++ target/i386/kvm.c | 42 ++++++++++++++++++++++++++++++++++-------- target/i386/machine.c | 31 ++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 9 deletions(-) (limited to 'target') diff --git a/target/i386/cpu.c b/target/i386/cpu.c index e46ab8f..008fd93f 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -5968,6 +5968,7 @@ static void x86_cpu_reset(DeviceState *dev) /* init to reset state */ env->hflags2 |= HF2_GIF_MASK; + env->hflags &= ~HF_GUEST_MASK; cpu_x86_update_cr0(env, 0x60000010); env->a20_mask = ~0x0; diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 06b2e3a..9284f96 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -2118,6 +2118,11 @@ static inline bool cpu_has_vmx(CPUX86State *env) return env->features[FEAT_1_ECX] & CPUID_EXT_VMX; } +static inline bool cpu_has_svm(CPUX86State *env) +{ + return env->features[FEAT_8000_0001_ECX] & CPUID_EXT3_SVM; +} + /* * In order for a vCPU to enter VMX operation it must have CR4.VMXE set. * Since it was set, CR4.VMXE must remain set as long as vCPU is in diff --git a/target/i386/kvm.c b/target/i386/kvm.c index 6adbff3..2b6b744 100644 --- a/target/i386/kvm.c +++ b/target/i386/kvm.c @@ -1840,16 +1840,18 @@ int kvm_arch_init_vcpu(CPUState *cs) if (max_nested_state_len > 0) { assert(max_nested_state_len >= offsetof(struct kvm_nested_state, data)); - if (cpu_has_vmx(env)) { + if (cpu_has_vmx(env) || cpu_has_svm(env)) { struct kvm_vmx_nested_state_hdr *vmx_hdr; env->nested_state = g_malloc0(max_nested_state_len); env->nested_state->size = max_nested_state_len; env->nested_state->format = KVM_STATE_NESTED_FORMAT_VMX; - vmx_hdr = &env->nested_state->hdr.vmx; - vmx_hdr->vmxon_pa = -1ull; - vmx_hdr->vmcs12_pa = -1ull; + if (cpu_has_vmx(env)) { + vmx_hdr = &env->nested_state->hdr.vmx; + vmx_hdr->vmxon_pa = -1ull; + vmx_hdr->vmcs12_pa = -1ull; + } } } @@ -3873,6 +3875,20 @@ static int kvm_put_nested_state(X86CPU *cpu) return 0; } + /* + * Copy flags that are affected by reset from env->hflags and env->hflags2. + */ + if (env->hflags & HF_GUEST_MASK) { + env->nested_state->flags |= KVM_STATE_NESTED_GUEST_MODE; + } else { + env->nested_state->flags &= ~KVM_STATE_NESTED_GUEST_MODE; + } + if (env->hflags2 & HF2_GIF_MASK) { + env->nested_state->flags |= KVM_STATE_NESTED_GIF_SET; + } else { + env->nested_state->flags &= ~KVM_STATE_NESTED_GIF_SET; + } + assert(env->nested_state->size <= max_nested_state_len); return kvm_vcpu_ioctl(CPU(cpu), KVM_SET_NESTED_STATE, env->nested_state); } @@ -3901,11 +3917,19 @@ static int kvm_get_nested_state(X86CPU *cpu) return ret; } + /* + * Copy flags that are affected by reset to env->hflags and env->hflags2. + */ if (env->nested_state->flags & KVM_STATE_NESTED_GUEST_MODE) { env->hflags |= HF_GUEST_MASK; } else { env->hflags &= ~HF_GUEST_MASK; } + if (env->nested_state->flags & KVM_STATE_NESTED_GIF_SET) { + env->hflags2 |= HF2_GIF_MASK; + } else { + env->hflags2 &= ~HF2_GIF_MASK; + } return ret; } @@ -3917,6 +3941,12 @@ int kvm_arch_put_registers(CPUState *cpu, int level) assert(cpu_is_stopped(cpu) || qemu_cpu_is_self(cpu)); + /* must be before kvm_put_nested_state so that EFER.SVME is set */ + ret = kvm_put_sregs(x86_cpu); + if (ret < 0) { + return ret; + } + if (level >= KVM_PUT_RESET_STATE) { ret = kvm_put_nested_state(x86_cpu); if (ret < 0) { @@ -3950,10 +3980,6 @@ int kvm_arch_put_registers(CPUState *cpu, int level) if (ret < 0) { return ret; } - ret = kvm_put_sregs(x86_cpu); - if (ret < 0) { - return ret; - } /* must be before kvm_put_msrs */ ret = kvm_inject_mce_oldstyle(x86_cpu); if (ret < 0) { diff --git a/target/i386/machine.c b/target/i386/machine.c index 0c96531..b1acf7d 100644 --- a/target/i386/machine.c +++ b/target/i386/machine.c @@ -1071,13 +1071,41 @@ static const VMStateDescription vmstate_vmx_nested_state = { } }; +static bool svm_nested_state_needed(void *opaque) +{ + struct kvm_nested_state *nested_state = opaque; + + /* + * HF_GUEST_MASK and HF2_GIF_MASK are already serialized + * via hflags and hflags2, all that's left is the opaque + * nested state blob. + */ + return (nested_state->format == KVM_STATE_NESTED_FORMAT_SVM && + nested_state->size > offsetof(struct kvm_nested_state, data)); +} + +static const VMStateDescription vmstate_svm_nested_state = { + .name = "cpu/kvm_nested_state/svm", + .version_id = 1, + .minimum_version_id = 1, + .needed = svm_nested_state_needed, + .fields = (VMStateField[]) { + VMSTATE_U64(hdr.svm.vmcb_pa, struct kvm_nested_state), + VMSTATE_UINT8_ARRAY(data.svm[0].vmcb12, + struct kvm_nested_state, + KVM_STATE_NESTED_SVM_VMCB_SIZE), + VMSTATE_END_OF_LIST() + } +}; + static bool nested_state_needed(void *opaque) { X86CPU *cpu = opaque; CPUX86State *env = &cpu->env; return (env->nested_state && - vmx_nested_state_needed(env->nested_state)); + (vmx_nested_state_needed(env->nested_state) || + svm_nested_state_needed(env->nested_state))); } static int nested_state_post_load(void *opaque, int version_id) @@ -1139,6 +1167,7 @@ static const VMStateDescription vmstate_kvm_nested_state = { }, .subsections = (const VMStateDescription*[]) { &vmstate_vmx_nested_state, + &vmstate_svm_nested_state, NULL } }; -- cgit v1.1 From b8d864f64341eb5825aed28aa9a6d1c98316b3e9 Mon Sep 17 00:00:00 2001 From: Roman Bolshakov Date: Tue, 30 Jun 2020 13:28:16 +0300 Subject: i386: hvf: Set env->eip in macvm_set_rip() cpu_synchronize_state() is currently no-op for hvf but BIOS will hang in vAPIC option ROM when cpu_synchronize_state() is wired to hvf_cpu_synchronize_state(). cpu_synchronize_state() state is called from vapic_write() during option ROM initialization. It sets dirty flag on the cpu. macvm_set_rip() is then invoked to advance IP after the I/O write to vAPIC port. macvm_set_rip() only modifies VMCS, it doesn't change env->eip. Therefore on the next iteration of vCPU loop, vcpu_dirty flag is checked and hvf_put_registers() overwrites correct RIP in VMCS with the value of env->eip that points to the I/O write instruction. Execution of the CPU gets stuck on the instruction. The issue can be avoided if eip doesn't contain stale value when dirty flag is set on cpu. Cc: Cameron Esfahani Signed-off-by: Roman Bolshakov Message-Id: <20200630102824.77604-2-r.bolshakov@yadro.com> Signed-off-by: Paolo Bonzini --- target/i386/hvf/vmx.h | 1 + 1 file changed, 1 insertion(+) (limited to 'target') diff --git a/target/i386/hvf/vmx.h b/target/i386/hvf/vmx.h index ce2a153..1e8b29b 100644 --- a/target/i386/hvf/vmx.h +++ b/target/i386/hvf/vmx.h @@ -173,6 +173,7 @@ static inline void macvm_set_rip(CPUState *cpu, uint64_t rip) /* BUG, should take considering overlap.. */ wreg(cpu->hvf_fd, HV_X86_RIP, rip); + env->eip = rip; /* after moving forward in rip, we need to clean INTERRUPTABILITY */ val = rvmcs(cpu->hvf_fd, VMCS_GUEST_INTERRUPTIBILITY); -- cgit v1.1 From 5536c98e449fe832c6cb59612baf0f2936fb436d Mon Sep 17 00:00:00 2001 From: Roman Bolshakov Date: Tue, 30 Jun 2020 13:28:18 +0300 Subject: i386: hvf: Add hvf_cpu_synchronize_pre_loadvm() hvf lacks an implementation of cpu_synchronize_pre_loadvm(). Cc: Cameron Esfahani Signed-off-by: Roman Bolshakov Message-Id: <20200630102824.77604-4-r.bolshakov@yadro.com> Signed-off-by: Paolo Bonzini --- target/i386/hvf/hvf.c | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'target') diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c index be016b9..efe9802 100644 --- a/target/i386/hvf/hvf.c +++ b/target/i386/hvf/hvf.c @@ -325,6 +325,17 @@ void hvf_cpu_synchronize_post_init(CPUState *cpu_state) run_on_cpu(cpu_state, do_hvf_cpu_synchronize_post_init, RUN_ON_CPU_NULL); } +static void do_hvf_cpu_synchronize_pre_loadvm(CPUState *cpu, + run_on_cpu_data arg) +{ + cpu->vcpu_dirty = true; +} + +void hvf_cpu_synchronize_pre_loadvm(CPUState *cpu) +{ + run_on_cpu(cpu, do_hvf_cpu_synchronize_pre_loadvm, RUN_ON_CPU_NULL); +} + static bool ept_emulation_fault(hvf_slot *slot, uint64_t gpa, uint64_t ept_qual) { int read, write; -- cgit v1.1 From a4e26fa8583a215aa61107a623bfa25afd09a860 Mon Sep 17 00:00:00 2001 From: Cameron Esfahani Date: Tue, 30 Jun 2020 13:28:20 +0300 Subject: i386: hvf: Make long mode enter and exit clearer Intel SDM "9.8.5 Initializing IA-32e Mode" and "9.8.5.4 Switching Out of IA-32e Mode Operation" define activation and deactivation of long mode only upon a change of CR0.PG but current code invokes exit_long_mode() unconditionally until LME is cleared. Signed-off-by: Cameron Esfahani Signed-off-by: Roman Bolshakov Message-Id: <20200630102824.77604-6-r.bolshakov@yadro.com> Signed-off-by: Paolo Bonzini --- target/i386/hvf/vmx.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'target') diff --git a/target/i386/hvf/vmx.h b/target/i386/hvf/vmx.h index 1e8b29b..437238f 100644 --- a/target/i386/hvf/vmx.h +++ b/target/i386/hvf/vmx.h @@ -121,6 +121,7 @@ static inline void macvm_set_cr0(hv_vcpuid_t vcpu, uint64_t cr0) uint64_t pdpte[4] = {0, 0, 0, 0}; uint64_t efer = rvmcs(vcpu, VMCS_GUEST_IA32_EFER); uint64_t old_cr0 = rvmcs(vcpu, VMCS_GUEST_CR0); + uint64_t changed_cr0 = old_cr0 ^ cr0; uint64_t mask = CR0_PG | CR0_CD | CR0_NW | CR0_NE | CR0_ET; if ((cr0 & CR0_PG) && (rvmcs(vcpu, VMCS_GUEST_CR4) & CR4_PAE) && @@ -138,11 +139,12 @@ static inline void macvm_set_cr0(hv_vcpuid_t vcpu, uint64_t cr0) wvmcs(vcpu, VMCS_CR0_SHADOW, cr0); if (efer & MSR_EFER_LME) { - if (!(old_cr0 & CR0_PG) && (cr0 & CR0_PG)) { - enter_long_mode(vcpu, cr0, efer); - } - if (/*(old_cr0 & CR0_PG) &&*/ !(cr0 & CR0_PG)) { - exit_long_mode(vcpu, cr0, efer); + if (changed_cr0 & CR0_PG) { + if (cr0 & CR0_PG) { + enter_long_mode(vcpu, cr0, efer); + } else { + exit_long_mode(vcpu, cr0, efer); + } } } -- cgit v1.1 From 82695a1b9c696aaea491e932548f9787adb7d78b Mon Sep 17 00:00:00 2001 From: Roman Bolshakov Date: Tue, 30 Jun 2020 13:28:21 +0300 Subject: i386: hvf: Move Guest LMA reset to macvm_set_cr0() The only useful purpose of hvf_reset_vcpu() is to clear "IA-32e mode guest" (LMA) VM-Entry control. But it can be moved to macvm_set_cr0() which is indirectly used by post-init and post-reset to flush emulator state. That enables clean removal of hvf_reset_vcpu(). LMA is set only if IA32_EFER.LME = 1, according to Intel SDM "9.8.5 Initializing IA-32e Mode" and "9.8.5.4 Switching Out of IA-32e Mode Operation", otherwise the entry control can be safely cleared. Cc: Cameron Esfahani Signed-off-by: Roman Bolshakov Message-Id: <20200630102824.77604-7-r.bolshakov@yadro.com> Signed-off-by: Paolo Bonzini --- target/i386/hvf/hvf.c | 1 - target/i386/hvf/vmx.h | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'target') diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c index efe9802..31980f9 100644 --- a/target/i386/hvf/hvf.c +++ b/target/i386/hvf/hvf.c @@ -459,7 +459,6 @@ void hvf_reset_vcpu(CPUState *cpu) { /* TODO: this shouldn't be needed; there is already a call to * cpu_synchronize_all_post_reset in vl.c */ - wvmcs(cpu->hvf_fd, VMCS_ENTRY_CTLS, 0); wvmcs(cpu->hvf_fd, VMCS_GUEST_IA32_EFER, 0); /* Initialize PDPTE */ diff --git a/target/i386/hvf/vmx.h b/target/i386/hvf/vmx.h index 437238f..75ba1e2 100644 --- a/target/i386/hvf/vmx.h +++ b/target/i386/hvf/vmx.h @@ -123,6 +123,7 @@ static inline void macvm_set_cr0(hv_vcpuid_t vcpu, uint64_t cr0) uint64_t old_cr0 = rvmcs(vcpu, VMCS_GUEST_CR0); uint64_t changed_cr0 = old_cr0 ^ cr0; uint64_t mask = CR0_PG | CR0_CD | CR0_NW | CR0_NE | CR0_ET; + uint64_t entry_ctls; if ((cr0 & CR0_PG) && (rvmcs(vcpu, VMCS_GUEST_CR4) & CR4_PAE) && !(efer & MSR_EFER_LME)) { @@ -146,6 +147,9 @@ static inline void macvm_set_cr0(hv_vcpuid_t vcpu, uint64_t cr0) exit_long_mode(vcpu, cr0, efer); } } + } else { + entry_ctls = rvmcs(vcpu, VMCS_ENTRY_CTLS); + wvmcs(vcpu, VMCS_ENTRY_CTLS, entry_ctls & ~VM_ENTRY_GUEST_LMA); } /* Filter new CR0 after we are finished examining it above. */ -- cgit v1.1 From 5009ef22c6bb21aa741e9e354ccaa97edf56911d Mon Sep 17 00:00:00 2001 From: Roman Bolshakov Date: Tue, 30 Jun 2020 13:28:22 +0300 Subject: i386: hvf: Don't duplicate register reset hvf_reset_vcpu() duplicates actions performed by x86_cpu_reset(). The difference is that hvf_reset_vcpu() stores initial values directly to VMCS while x86_cpu_reset() stores it in CPUX86State and then cpu_synchronize_all_post_init() or cpu_synchronize_all_post_reset() flushes CPUX86State into VMCS. That makes hvf_reset_vcpu() a kind of no-op. Here's the trace of CPU state modifications during VM start: hvf_reset_vcpu (resets VMCS) cpu_synchronize_all_post_init (overwrites VMCS fields written by hvf_reset_vcpu()) cpu_synchronize_all_states hvf_reset_vcpu (resets VMCS) cpu_synchronize_all_post_reset (overwrites VMCS fields written by hvf_reset_vcpu()) General purpose registers, system registers, segment descriptors, flags and IP are set by hvf_put_segments() in post-init and post-reset, therefore it's safe to remove them from hvf_reset_vcpu(). PDPTE initialization can be dropped because Intel SDM (26.3.1.6 Checks on Guest Page-Directory-Pointer-Table Entries) doesn't require PDPTE to be clear unless PAE is used: "A VM entry to a guest that does not use PAE paging does not check the validity of any PDPTEs." And if PAE is used, PDPTE's are initialized from CR3 in macvm_set_cr0(). Cc: Cameron Esfahani Signed-off-by: Roman Bolshakov Message-Id: <20200630102824.77604-8-r.bolshakov@yadro.com> Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 3 -- target/i386/hvf/hvf.c | 89 --------------------------------------------------- 2 files changed, 92 deletions(-) (limited to 'target') diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 008fd93f..1546cd7 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6080,9 +6080,6 @@ static void x86_cpu_reset(DeviceState *dev) if (kvm_enabled()) { kvm_arch_reset_vcpu(cpu); } - else if (hvf_enabled()) { - hvf_reset_vcpu(s); - } #endif } diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c index 31980f9..2a12867 100644 --- a/target/i386/hvf/hvf.c +++ b/target/i386/hvf/hvf.c @@ -452,95 +452,6 @@ static MemoryListener hvf_memory_listener = { .log_sync = hvf_log_sync, }; -void hvf_reset_vcpu(CPUState *cpu) { - uint64_t pdpte[4] = {0, 0, 0, 0}; - int i; - - /* TODO: this shouldn't be needed; there is already a call to - * cpu_synchronize_all_post_reset in vl.c - */ - wvmcs(cpu->hvf_fd, VMCS_GUEST_IA32_EFER, 0); - - /* Initialize PDPTE */ - for (i = 0; i < 4; i++) { - wvmcs(cpu->hvf_fd, VMCS_GUEST_PDPTE0 + i * 2, pdpte[i]); - } - - macvm_set_cr0(cpu->hvf_fd, 0x60000010); - - wvmcs(cpu->hvf_fd, VMCS_CR4_MASK, CR4_VMXE_MASK); - wvmcs(cpu->hvf_fd, VMCS_CR4_SHADOW, 0x0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_CR4, CR4_VMXE_MASK); - - /* set VMCS guest state fields */ - wvmcs(cpu->hvf_fd, VMCS_GUEST_CS_SELECTOR, 0xf000); - wvmcs(cpu->hvf_fd, VMCS_GUEST_CS_LIMIT, 0xffff); - wvmcs(cpu->hvf_fd, VMCS_GUEST_CS_ACCESS_RIGHTS, 0x9b); - wvmcs(cpu->hvf_fd, VMCS_GUEST_CS_BASE, 0xffff0000); - - wvmcs(cpu->hvf_fd, VMCS_GUEST_DS_SELECTOR, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_DS_LIMIT, 0xffff); - wvmcs(cpu->hvf_fd, VMCS_GUEST_DS_ACCESS_RIGHTS, 0x93); - wvmcs(cpu->hvf_fd, VMCS_GUEST_DS_BASE, 0); - - wvmcs(cpu->hvf_fd, VMCS_GUEST_ES_SELECTOR, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_ES_LIMIT, 0xffff); - wvmcs(cpu->hvf_fd, VMCS_GUEST_ES_ACCESS_RIGHTS, 0x93); - wvmcs(cpu->hvf_fd, VMCS_GUEST_ES_BASE, 0); - - wvmcs(cpu->hvf_fd, VMCS_GUEST_FS_SELECTOR, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_FS_LIMIT, 0xffff); - wvmcs(cpu->hvf_fd, VMCS_GUEST_FS_ACCESS_RIGHTS, 0x93); - wvmcs(cpu->hvf_fd, VMCS_GUEST_FS_BASE, 0); - - wvmcs(cpu->hvf_fd, VMCS_GUEST_GS_SELECTOR, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_GS_LIMIT, 0xffff); - wvmcs(cpu->hvf_fd, VMCS_GUEST_GS_ACCESS_RIGHTS, 0x93); - wvmcs(cpu->hvf_fd, VMCS_GUEST_GS_BASE, 0); - - wvmcs(cpu->hvf_fd, VMCS_GUEST_SS_SELECTOR, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_SS_LIMIT, 0xffff); - wvmcs(cpu->hvf_fd, VMCS_GUEST_SS_ACCESS_RIGHTS, 0x93); - wvmcs(cpu->hvf_fd, VMCS_GUEST_SS_BASE, 0); - - wvmcs(cpu->hvf_fd, VMCS_GUEST_LDTR_SELECTOR, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_LDTR_LIMIT, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_LDTR_ACCESS_RIGHTS, 0x10000); - wvmcs(cpu->hvf_fd, VMCS_GUEST_LDTR_BASE, 0); - - wvmcs(cpu->hvf_fd, VMCS_GUEST_TR_SELECTOR, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_TR_LIMIT, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_TR_ACCESS_RIGHTS, 0x83); - wvmcs(cpu->hvf_fd, VMCS_GUEST_TR_BASE, 0); - - wvmcs(cpu->hvf_fd, VMCS_GUEST_GDTR_LIMIT, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_GDTR_BASE, 0); - - wvmcs(cpu->hvf_fd, VMCS_GUEST_IDTR_LIMIT, 0); - wvmcs(cpu->hvf_fd, VMCS_GUEST_IDTR_BASE, 0); - - /*wvmcs(cpu->hvf_fd, VMCS_GUEST_CR2, 0x0);*/ - wvmcs(cpu->hvf_fd, VMCS_GUEST_CR3, 0x0); - - wreg(cpu->hvf_fd, HV_X86_RIP, 0xfff0); - wreg(cpu->hvf_fd, HV_X86_RDX, 0x623); - wreg(cpu->hvf_fd, HV_X86_RFLAGS, 0x2); - wreg(cpu->hvf_fd, HV_X86_RSP, 0x0); - wreg(cpu->hvf_fd, HV_X86_RAX, 0x0); - wreg(cpu->hvf_fd, HV_X86_RBX, 0x0); - wreg(cpu->hvf_fd, HV_X86_RCX, 0x0); - wreg(cpu->hvf_fd, HV_X86_RSI, 0x0); - wreg(cpu->hvf_fd, HV_X86_RDI, 0x0); - wreg(cpu->hvf_fd, HV_X86_RBP, 0x0); - - for (int i = 0; i < 8; i++) { - wreg(cpu->hvf_fd, HV_X86_R8 + i, 0x0); - } - - hv_vcpu_invalidate_tlb(cpu->hvf_fd); - hv_vcpu_flush(cpu->hvf_fd); -} - void hvf_vcpu_destroy(CPUState *cpu) { X86CPU *x86_cpu = X86_CPU(cpu); -- cgit v1.1 From eae009de78b62e67ec887b4934cf62f4f5fb33c8 Mon Sep 17 00:00:00 2001 From: Roman Bolshakov Date: Tue, 30 Jun 2020 13:28:23 +0300 Subject: i386: hvf: Clean up synchronize functions Make them more concise and consitent with the rest of the code in the file and drop non-relevant TODO. Cc: Cameron Esfahani Signed-off-by: Roman Bolshakov Message-Id: <20200630102824.77604-9-r.bolshakov@yadro.com> Signed-off-by: Paolo Bonzini --- target/i386/hvf/hvf.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) (limited to 'target') diff --git a/target/i386/hvf/hvf.c b/target/i386/hvf/hvf.c index 2a12867..d81f569 100644 --- a/target/i386/hvf/hvf.c +++ b/target/i386/hvf/hvf.c @@ -282,47 +282,43 @@ void hvf_handle_io(CPUArchState *env, uint16_t port, void *buffer, } } -/* TODO: synchronize vcpu state */ static void do_hvf_cpu_synchronize_state(CPUState *cpu, run_on_cpu_data arg) { - CPUState *cpu_state = cpu; - if (cpu_state->vcpu_dirty == 0) { - hvf_get_registers(cpu_state); + if (!cpu->vcpu_dirty) { + hvf_get_registers(cpu); + cpu->vcpu_dirty = true; } - - cpu_state->vcpu_dirty = 1; } -void hvf_cpu_synchronize_state(CPUState *cpu_state) +void hvf_cpu_synchronize_state(CPUState *cpu) { - if (cpu_state->vcpu_dirty == 0) { - run_on_cpu(cpu_state, do_hvf_cpu_synchronize_state, RUN_ON_CPU_NULL); + if (!cpu->vcpu_dirty) { + run_on_cpu(cpu, do_hvf_cpu_synchronize_state, RUN_ON_CPU_NULL); } } -static void do_hvf_cpu_synchronize_post_reset(CPUState *cpu, run_on_cpu_data arg) +static void do_hvf_cpu_synchronize_post_reset(CPUState *cpu, + run_on_cpu_data arg) { - CPUState *cpu_state = cpu; - hvf_put_registers(cpu_state); - cpu_state->vcpu_dirty = false; + hvf_put_registers(cpu); + cpu->vcpu_dirty = false; } -void hvf_cpu_synchronize_post_reset(CPUState *cpu_state) +void hvf_cpu_synchronize_post_reset(CPUState *cpu) { - run_on_cpu(cpu_state, do_hvf_cpu_synchronize_post_reset, RUN_ON_CPU_NULL); + run_on_cpu(cpu, do_hvf_cpu_synchronize_post_reset, RUN_ON_CPU_NULL); } static void do_hvf_cpu_synchronize_post_init(CPUState *cpu, run_on_cpu_data arg) { - CPUState *cpu_state = cpu; - hvf_put_registers(cpu_state); - cpu_state->vcpu_dirty = false; + hvf_put_registers(cpu); + cpu->vcpu_dirty = false; } -void hvf_cpu_synchronize_post_init(CPUState *cpu_state) +void hvf_cpu_synchronize_post_init(CPUState *cpu) { - run_on_cpu(cpu_state, do_hvf_cpu_synchronize_post_init, RUN_ON_CPU_NULL); + run_on_cpu(cpu, do_hvf_cpu_synchronize_post_init, RUN_ON_CPU_NULL); } static void do_hvf_cpu_synchronize_pre_loadvm(CPUState *cpu, -- cgit v1.1 From 6c9cce1ff348709869262e4bd15ad4fedf513f17 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 26 Jun 2020 05:19:27 -0400 Subject: target/i386: remove gen_io_end Force the end of a translation block after an I/O instruction in icount mode. For consistency, all CF_USE_ICOUNT code is kept in disas_insn instead of having it in gen_ins and gen_outs. Reviewed-by: Richard Henderson Signed-off-by: Paolo Bonzini --- target/i386/translate.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) (limited to 'target') diff --git a/target/i386/translate.c b/target/i386/translate.c index b3fea54..5ef72ff 100644 --- a/target/i386/translate.c +++ b/target/i386/translate.c @@ -1128,9 +1128,6 @@ static void gen_bpt_io(DisasContext *s, TCGv_i32 t_port, int ot) static inline void gen_ins(DisasContext *s, MemOp ot) { - if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { - gen_io_start(); - } gen_string_movl_A0_EDI(s); /* Note: we must do this dummy write first to be restartable in case of page fault. */ @@ -1143,16 +1140,10 @@ static inline void gen_ins(DisasContext *s, MemOp ot) gen_op_movl_T0_Dshift(s, ot); gen_op_add_reg_T0(s, s->aflag, R_EDI); gen_bpt_io(s, s->tmp2_i32, ot); - if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { - gen_io_end(); - } } static inline void gen_outs(DisasContext *s, MemOp ot) { - if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { - gen_io_start(); - } gen_string_movl_A0_ESI(s); gen_op_ld_v(s, ot, s->T0, s->A0); @@ -1163,9 +1154,6 @@ static inline void gen_outs(DisasContext *s, MemOp ot) gen_op_movl_T0_Dshift(s, ot); gen_op_add_reg_T0(s, s->aflag, R_ESI); gen_bpt_io(s, s->tmp2_i32, ot); - if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { - gen_io_end(); - } } /* same method as Valgrind : we generate jumps to current or next @@ -6400,8 +6388,12 @@ static target_ulong disas_insn(DisasContext *s, CPUState *cpu) tcg_gen_ext16u_tl(s->T0, cpu_regs[R_EDX]); gen_check_io(s, ot, pc_start - s->cs_base, SVM_IOIO_TYPE_MASK | svm_is_rep(prefixes) | 4); + if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { + gen_io_start(); + } if (prefixes & (PREFIX_REPZ | PREFIX_REPNZ)) { gen_repz_ins(s, ot, pc_start - s->cs_base, s->pc - s->cs_base); + /* jump generated by gen_repz_ins */ } else { gen_ins(s, ot); if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { @@ -6415,8 +6407,12 @@ static target_ulong disas_insn(DisasContext *s, CPUState *cpu) tcg_gen_ext16u_tl(s->T0, cpu_regs[R_EDX]); gen_check_io(s, ot, pc_start - s->cs_base, svm_is_rep(prefixes) | 4); + if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { + gen_io_start(); + } if (prefixes & (PREFIX_REPZ | PREFIX_REPNZ)) { gen_repz_outs(s, ot, pc_start - s->cs_base, s->pc - s->cs_base); + /* jump generated by gen_repz_outs */ } else { gen_outs(s, ot); if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { @@ -8039,7 +8035,7 @@ static target_ulong disas_insn(DisasContext *s, CPUState *cpu) gen_helper_read_crN(s->T0, cpu_env, tcg_const_i32(reg)); gen_op_mov_reg_v(s, ot, rm, s->T0); if (tb_cflags(s->base.tb) & CF_USE_ICOUNT) { - gen_io_end(); + gen_jmp(s, s->pc - s->cs_base); } } break; -- cgit v1.1 From c0c8445255b2b5b440c355431c8b01b7b7b7c8cf Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 26 Jun 2020 05:53:36 -0400 Subject: target/i386: implement undocumented "smsw r32" behavior In 32-bit mode, the higher 16 bits of the destination register are undefined. In practice CR0[31:0] is stored, just like in 64-bit mode, so just remove the "if" that currently differentiates the behavior. Reviewed-by: Richard Henderson Reported-by: Roman Bolshakov Reviewed-by: Roman Bolshakov Signed-off-by: Paolo Bonzini --- target/i386/translate.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'target') diff --git a/target/i386/translate.c b/target/i386/translate.c index 5ef72ff..a1d31f0 100644 --- a/target/i386/translate.c +++ b/target/i386/translate.c @@ -7579,12 +7579,13 @@ static target_ulong disas_insn(DisasContext *s, CPUState *cpu) CASE_MODRM_OP(4): /* smsw */ gen_svm_check_intercept(s, pc_start, SVM_EXIT_READ_CR0); tcg_gen_ld_tl(s->T0, cpu_env, offsetof(CPUX86State, cr[0])); - if (CODE64(s)) { - mod = (modrm >> 6) & 3; - ot = (mod != 3 ? MO_16 : s->dflag); - } else { - ot = MO_16; - } + /* + * In 32-bit mode, the higher 16 bits of the destination + * register are undefined. In practice CR0[31:0] is stored + * just like in 64-bit mode. + */ + mod = (modrm >> 6) & 3; + ot = (mod != 3 ? MO_16 : s->dflag); gen_ldst_modrm(env, s, modrm, ot, OR_TMP0, 1); break; case 0xee: /* rdpkru */ -- cgit v1.1 From e1e43813e7908b063938a3d01f172f88f6190c80 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 30 Jun 2020 09:49:27 -0400 Subject: KVM: x86: believe what KVM says about WAITPKG Currently, QEMU is overriding KVM_GET_SUPPORTED_CPUID's answer for the WAITPKG bit depending on the "-overcommit cpu-pm" setting. This is a bad idea because it does not even check if the host supports it, but it can be done in x86_cpu_realizefn just like we do for the MONITOR bit. This patch moves it there, while making it conditional on host support for the related UMWAIT MSR. Cc: qemu-stable@nongnu.org Reported-by: Maxim Levitsky Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 3 +++ target/i386/kvm.c | 11 +++++------ target/i386/kvm_i386.h | 1 + 3 files changed, 9 insertions(+), 6 deletions(-) (limited to 'target') diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 1546cd7..105eb47 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6509,6 +6509,9 @@ static void x86_cpu_realizefn(DeviceState *dev, Error **errp) host_cpuid(5, 0, &cpu->mwait.eax, &cpu->mwait.ebx, &cpu->mwait.ecx, &cpu->mwait.edx); env->features[FEAT_1_ECX] |= CPUID_EXT_MONITOR; + if (kvm_enabled() && kvm_has_waitpkg()) { + env->features[FEAT_7_0_ECX] |= CPUID_7_0_ECX_WAITPKG; + } } if (kvm_enabled() && cpu->ucode_rev == 0) { cpu->ucode_rev = kvm_arch_get_supported_msr_feature(kvm_state, diff --git a/target/i386/kvm.c b/target/i386/kvm.c index 2b6b744..b8455c8 100644 --- a/target/i386/kvm.c +++ b/target/i386/kvm.c @@ -411,12 +411,6 @@ uint32_t kvm_arch_get_supported_cpuid(KVMState *s, uint32_t function, if (host_tsx_blacklisted()) { ret &= ~(CPUID_7_0_EBX_RTM | CPUID_7_0_EBX_HLE); } - } else if (function == 7 && index == 0 && reg == R_ECX) { - if (enable_cpu_pm) { - ret |= CPUID_7_0_ECX_WAITPKG; - } else { - ret &= ~CPUID_7_0_ECX_WAITPKG; - } } else if (function == 7 && index == 0 && reg == R_EDX) { /* * Linux v4.17-v4.20 incorrectly return ARCH_CAPABILITIES on SVM hosts. @@ -4730,3 +4724,8 @@ int kvm_arch_msi_data_to_gsi(uint32_t data) { abort(); } + +bool kvm_has_waitpkg(void) +{ + return has_msr_umwait; +} diff --git a/target/i386/kvm_i386.h b/target/i386/kvm_i386.h index 00bde7a..064b879 100644 --- a/target/i386/kvm_i386.h +++ b/target/i386/kvm_i386.h @@ -44,6 +44,7 @@ void kvm_put_apicbase(X86CPU *cpu, uint64_t value); bool kvm_enable_x2apic(void); bool kvm_has_x2apic_api(void); +bool kvm_has_waitpkg(void); bool kvm_hv_vpindex_settable(void); -- cgit v1.1 From e4f6278557148151e77260b872b41bcd7ceb4737 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 30 Jun 2020 11:35:46 -0400 Subject: target/i386: sev: provide proper error reporting for query-sev-capabilities The query-sev-capabilities was reporting errors through error_report; change it to use Error** so that the cause of the failure is clearer. Reviewed-by: Eric Blake Signed-off-by: Paolo Bonzini --- target/i386/monitor.c | 10 +--------- target/i386/sev-stub.c | 3 ++- target/i386/sev.c | 18 +++++++++--------- target/i386/sev_i386.h | 2 +- 4 files changed, 13 insertions(+), 20 deletions(-) (limited to 'target') diff --git a/target/i386/monitor.c b/target/i386/monitor.c index 27ebfa3..7abae3c 100644 --- a/target/i386/monitor.c +++ b/target/i386/monitor.c @@ -726,13 +726,5 @@ SevLaunchMeasureInfo *qmp_query_sev_launch_measure(Error **errp) SevCapability *qmp_query_sev_capabilities(Error **errp) { - SevCapability *data; - - data = sev_get_capabilities(); - if (!data) { - error_setg(errp, "SEV feature is not available"); - return NULL; - } - - return data; + return sev_get_capabilities(errp); } diff --git a/target/i386/sev-stub.c b/target/i386/sev-stub.c index e5ee133..88e3f39 100644 --- a/target/i386/sev-stub.c +++ b/target/i386/sev-stub.c @@ -44,7 +44,8 @@ char *sev_get_launch_measurement(void) return NULL; } -SevCapability *sev_get_capabilities(void) +SevCapability *sev_get_capabilities(Error **errp) { + error_setg(errp, "SEV is not available in this QEMU"); return NULL; } diff --git a/target/i386/sev.c b/target/i386/sev.c index f100a53..7012b1d 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -399,7 +399,7 @@ sev_get_info(void) static int sev_get_pdh_info(int fd, guchar **pdh, size_t *pdh_len, guchar **cert_chain, - size_t *cert_chain_len) + size_t *cert_chain_len, Error **errp) { guchar *pdh_data = NULL; guchar *cert_chain_data = NULL; @@ -410,8 +410,8 @@ sev_get_pdh_info(int fd, guchar **pdh, size_t *pdh_len, guchar **cert_chain, r = sev_platform_ioctl(fd, SEV_PDH_CERT_EXPORT, &export, &err); if (r < 0) { if (err != SEV_RET_INVALID_LEN) { - error_report("failed to export PDH cert ret=%d fw_err=%d (%s)", - r, err, fw_error_to_str(err)); + error_setg(errp, "failed to export PDH cert ret=%d fw_err=%d (%s)", + r, err, fw_error_to_str(err)); return 1; } } @@ -423,8 +423,8 @@ sev_get_pdh_info(int fd, guchar **pdh, size_t *pdh_len, guchar **cert_chain, r = sev_platform_ioctl(fd, SEV_PDH_CERT_EXPORT, &export, &err); if (r < 0) { - error_report("failed to export PDH cert ret=%d fw_err=%d (%s)", - r, err, fw_error_to_str(err)); + error_setg(errp, "failed to export PDH cert ret=%d fw_err=%d (%s)", + r, err, fw_error_to_str(err)); goto e_free; } @@ -441,7 +441,7 @@ e_free: } SevCapability * -sev_get_capabilities(void) +sev_get_capabilities(Error **errp) { SevCapability *cap = NULL; guchar *pdh_data = NULL; @@ -452,13 +452,13 @@ sev_get_capabilities(void) fd = open(DEFAULT_SEV_DEVICE, O_RDWR); if (fd < 0) { - error_report("%s: Failed to open %s '%s'", __func__, - DEFAULT_SEV_DEVICE, strerror(errno)); + error_setg_errno(errp, errno, "Failed to open %s", + DEFAULT_SEV_DEVICE); return NULL; } if (sev_get_pdh_info(fd, &pdh_data, &pdh_len, - &cert_chain_data, &cert_chain_len)) { + &cert_chain_data, &cert_chain_len, errp)) { goto out; } diff --git a/target/i386/sev_i386.h b/target/i386/sev_i386.h index 8eb7de1..4db6960 100644 --- a/target/i386/sev_i386.h +++ b/target/i386/sev_i386.h @@ -34,6 +34,6 @@ extern SevInfo *sev_get_info(void); extern uint32_t sev_get_cbit_position(void); extern uint32_t sev_get_reduced_phys_bits(void); extern char *sev_get_launch_measurement(void); -extern SevCapability *sev_get_capabilities(void); +extern SevCapability *sev_get_capabilities(Error **errp); #endif -- cgit v1.1 From 1b38750c40281dd0d068f8536b2ea95d7b9bd585 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Tue, 30 Jun 2020 11:38:18 -0400 Subject: target/i386: sev: fail query-sev-capabilities if QEMU cannot use SEV In some cases, such as if the kvm-amd "sev" module parameter is set to 0, SEV will be unavailable but query-sev-capabilities will still return all the information. This tricks libvirt into erroneously reporting that SEV is available. Check the actual usability of the feature and return the appropriate error if QEMU cannot use KVM or KVM cannot use SEV. Reviewed-by: Eric Blake Signed-off-by: Paolo Bonzini --- target/i386/sev.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'target') diff --git a/target/i386/sev.c b/target/i386/sev.c index 7012b1d..c3ecf86 100644 --- a/target/i386/sev.c +++ b/target/i386/sev.c @@ -450,6 +450,15 @@ sev_get_capabilities(Error **errp) uint32_t ebx; int fd; + if (!kvm_enabled()) { + error_setg(errp, "KVM not enabled"); + return NULL; + } + if (kvm_vm_ioctl(kvm_state, KVM_MEMORY_ENCRYPT_OP, NULL) < 0) { + error_setg(errp, "SEV is not enabled in KVM"); + return NULL; + } + fd = open(DEFAULT_SEV_DEVICE, O_RDWR); if (fd < 0) { error_setg_errno(errp, errno, "Failed to open %s", -- cgit v1.1 From cbe0dad1906d29d9ea83169b672e9e709c9fc1f8 Mon Sep 17 00:00:00 2001 From: Luwei Kang Date: Tue, 30 Jun 2020 14:38:33 +0800 Subject: target/i386: Correct the warning message of Intel PT The CPUID level need to be set to 0x14 manually on old machine-type if Intel PT is enabled in guest. E.g. the CPUID[0].EAX(level)=7 and CPUID[7].EBX[25](intel-pt)=1 when the Qemu with "-machine pc-i440fx-3.1 -cpu qemu64,+intel-pt" parameter. This patch corrects the warning message of the previous submission(ddc2fc9). Signed-off-by: Luwei Kang Message-Id: <1593499113-4768-1-git-send-email-luwei.kang@intel.com> Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'target') diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 105eb47..1dde34d 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -6398,7 +6398,7 @@ static void x86_cpu_expand_features(X86CPU *cpu, Error **errp) } else if (cpu->env.cpuid_min_level < 0x14) { mark_unavailable_features(cpu, FEAT_7_0_EBX, CPUID_7_0_EBX_INTEL_PT, - "Intel PT need CPUID leaf 0x14, please set by \"-cpu ...,+intel-pt,level=0x14\""); + "Intel PT need CPUID leaf 0x14, please set by \"-cpu ...,+intel-pt,min-level=0x14\""); } } -- cgit v1.1 From 5dd13f2a5b4d7335b2b0924e6fb28b63b34e70fe Mon Sep 17 00:00:00 2001 From: Cathy Zhang Date: Mon, 6 Jul 2020 07:17:15 +0800 Subject: target/i386: Add SERIALIZE cpu feature The availability of the SERIALIZATION instruction is indicated by the presence of the CPUID feature flag SERIALIZE, which is defined as CPUID.(EAX=7,ECX=0):ECX[bit 14]. The release spec link is as follows: https://software.intel.com/content/dam/develop/public/us/en/documents/\ architecture-instruction-set-extensions-programming-reference.pdf The associated kvm patch link is as follows: https://lore.kernel.org/patchwork/patch/1268025/ Signed-off-by: Cathy Zhang Message-Id: <1593991036-12183-2-git-send-email-cathy.zhang@intel.com> Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 2 +- target/i386/cpu.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'target') diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 1dde34d..a05d3a6 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -986,7 +986,7 @@ static FeatureWordInfo feature_word_info[FEATURE_WORDS] = { NULL, NULL, "avx512-4vnniw", "avx512-4fmaps", NULL, NULL, NULL, NULL, "avx512-vp2intersect", NULL, "md-clear", NULL, - NULL, NULL, NULL, NULL, + NULL, NULL, "serialize", NULL, NULL, NULL, NULL /* pconfig */, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "spec-ctrl", "stibp", diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 9284f96..bd71fe3 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -777,6 +777,8 @@ typedef uint64_t FeatureWordArray[FEATURE_WORDS]; #define CPUID_7_0_EDX_AVX512_4FMAPS (1U << 3) /* AVX512 Vector Pair Intersection to a Pair of Mask Registers */ #define CPUID_7_0_EDX_AVX512_VP2INTERSECT (1U << 8) +/* SERIALIZE instruction */ +#define CPUID_7_0_EDX_SERIALIZE (1U << 14) /* Speculation Control */ #define CPUID_7_0_EDX_SPEC_CTRL (1U << 26) /* Single Thread Indirect Branch Predictors */ -- cgit v1.1 From b3c7344e338b04be9feaf7ac6693c2f7fc951b89 Mon Sep 17 00:00:00 2001 From: Cathy Zhang Date: Mon, 6 Jul 2020 07:17:16 +0800 Subject: target/i386: Enable TSX Suspend Load Address Tracking feature This instruction aims to give a way to choose which memory accesses do not need to be tracked in the TSX read set, which is defined as CPUID.(EAX=7,ECX=0):EDX[bit 16]. The release spec link is as follows: https://software.intel.com/content/dam/develop/public/us/en/documents/\ architecture-instruction-set-extensions-programming-reference.pdf The associated kvm patch link is as follows: https://lore.kernel.org/patchwork/patch/1268026/ Signed-off-by: Cathy Zhang Message-Id: <1593991036-12183-3-git-send-email-cathy.zhang@intel.com> Signed-off-by: Paolo Bonzini --- target/i386/cpu.c | 2 +- target/i386/cpu.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'target') diff --git a/target/i386/cpu.c b/target/i386/cpu.c index a05d3a6..1e51232 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -987,7 +987,7 @@ static FeatureWordInfo feature_word_info[FEATURE_WORDS] = { NULL, NULL, NULL, NULL, "avx512-vp2intersect", NULL, "md-clear", NULL, NULL, NULL, "serialize", NULL, - NULL, NULL, NULL /* pconfig */, NULL, + "tsx-ldtrk", NULL, NULL /* pconfig */, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "spec-ctrl", "stibp", NULL, "arch-capabilities", "core-capability", "ssbd", diff --git a/target/i386/cpu.h b/target/i386/cpu.h index bd71fe3..37fffa5c 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -779,6 +779,8 @@ typedef uint64_t FeatureWordArray[FEATURE_WORDS]; #define CPUID_7_0_EDX_AVX512_VP2INTERSECT (1U << 8) /* SERIALIZE instruction */ #define CPUID_7_0_EDX_SERIALIZE (1U << 14) +/* TSX Suspend Load Address Tracking instruction */ +#define CPUID_7_0_EDX_TSX_LDTRK (1U << 16) /* Speculation Control */ #define CPUID_7_0_EDX_SPEC_CTRL (1U << 26) /* Single Thread Indirect Branch Predictors */ -- cgit v1.1 From 6e083c0de41a606f304168fce75ea77f3c031f98 Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Fri, 7 Feb 2020 07:43:42 +0100 Subject: apic: Report current_count via 'info lapic' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is helpful when debugging stuck guest timers. As we need apic_get_current_count for that, and it is really not emulation specific, move it to apic_common.c and export it. Fix its style at this chance as well. Signed-off-by: Jan Kiszka Reviewed-by: Philippe Mathieu-Daudé Message-Id: Signed-off-by: Paolo Bonzini --- target/i386/helper.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'target') diff --git a/target/i386/helper.c b/target/i386/helper.c index fa2a1dc..70be53e 100644 --- a/target/i386/helper.c +++ b/target/i386/helper.c @@ -370,10 +370,11 @@ void x86_cpu_dump_local_apic_state(CPUState *cs, int flags) dump_apic_lvt("LVTTHMR", lvt[APIC_LVT_THERMAL], false); dump_apic_lvt("LVTT", lvt[APIC_LVT_TIMER], true); - qemu_printf("Timer\t DCR=0x%x (divide by %u) initial_count = %u\n", + qemu_printf("Timer\t DCR=0x%x (divide by %u) initial_count = %u" + " current_count = %u\n", s->divide_conf & APIC_DCR_MASK, divider_conf(s->divide_conf), - s->initial_count); + s->initial_count, apic_get_current_count(s)); qemu_printf("SPIV\t 0x%08x APIC %s, focus=%s, spurious vec %u\n", s->spurious_vec, -- cgit v1.1