commit 2e5269995a4bdc7f3b85b951648c1d50098d2888 Author: Sandwich Date: Sun Sep 6 00:14:56 2026 +0200 feat: kvm and qemu patches grouped by the detection each clears diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f9e6d91 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +This project consists of patches to the Linux kernel and to QEMU, plus tooling +that configures and measures a libvirt guest. + +The kernel patches under patches/kvm/ and the QEMU patches under patches/qemu/ +are derivative works of GPL-2.0 projects and are distributed under GPL-2.0. + +The scripts, packaging and benchmark are likewise GPL-2.0 for consistency. + +See https://www.gnu.org/licenses/old-licenses/gpl-2.0.html diff --git a/patches/README.md b/patches/README.md new file mode 100644 index 0000000..adeee2e --- /dev/null +++ b/patches/README.md @@ -0,0 +1,86 @@ +# Patchsets, by the detection each one clears + +Every patch here is grouped by the VMAware check it targets, so a patchset can be taken or left +on its own. Apply from the relevant source root with `patch -p1 < `. + +## KVM - `kvm/` + +Built against kernel 7.2.x. Builds as `arch/x86/kvm` only, about five minutes; no full kernel +build is needed. See `packaging/` for a PKGBUILD that does it and keeps it rebuilt. + +| Patch | Clears | What it does | +| --- | --- | --- | +| `0001-KVM-SVM-intercept-GP-when-guest-EFER.SVME-is-clear` | `SVM_EXCEPTIONS` | Closes a `FIXME` KVM already carries: a CPL3 SVM instruction faults `#GP` where hardware gives `#UD`. | +| `0002-KVM-x86-emulator-UD-not-GP-for-VMCALL-at-CPL-0` | `KVM_INTERCEPTION` | Adds `PrivUD` so `VMCALL` at CPL>0 raises `#UD` like hardware. | +| `0003-KVM-x86-UD-for-KVM-hypercalls-issued-at-CPL-0` | `KVM_INTERCEPTION` | A CPL>0 hypercall raised no exception at all; now raises `#UD`. | +| `0004-KVM-SVM-intercept-ICEBP-and-skip-it-before-injecting-its-DB` | `DBVM` | SVM re-injected the `ICEBP` `#DB` with RIP still on the `F1` byte; VMX and hardware report the next instruction. | +| `0005-KVM-selftests-verify-ICEBP-DB-reports-RIP-past-the-ICEBP` | - | The selftest for 0004: fails on unpatched SVM, passes with it. | +| `EXPERIMENTAL-0006-runtime-cpuid-passthrough` | `TIMER` | Drops CPUID and RDPRU interception on a 1:1-pinned vCPU and reprograms the AMD Processor Name String MSRs so raw `CPUID` still returns the declared brand. Opt-in via the `cpuid_passthrough` module parameter. | + +**0002 and 0003 are a pair.** Either alone leaves `KVM_INTERCEPTION` standing, because the check +tries two stubs and only reports the first one that misbehaves. + +The first five are written to be acceptable upstream: they make KVM match the architecture rather +than add anything on top of it, each carries its rationale in the commit message, and all four +pass `checkpatch.pl --strict`. `0000-cover-letter.txt` is the series cover letter. They +have been tested on AMD only; 0002 and 0003 are vendor-neutral code and the Intel reasoning is in +the cover letter. + +**`EXPERIMENTAL-0006` is built into the `vfio-native-kvm-dkms` package but off by default.** It +removes the cost the `TIMER` instruction-latency detector measures, and with it the guest reads +raw host CPUID, so it applies itself only to vCPU threads confined to exactly one CPU and must be +enabled through the `cpuid_passthrough` module parameter after the guest has booted. +`docs/ANALYSIS.md` has the measurements and the constraints. + +## QEMU - `qemu/` + +Against QEMU v11.1.1. Apply in numeric order. + +| Patch | Clears | +| --- | --- | +| `01-firmware` | `FIRMWARE` - ACPI link-device names, `PNP0A06` stubs, FADT C-state latencies | +| `02-disk-identity` | `VIRTUAL_DISK`, `DISK_SERIAL` - `QEMU HARDDISK`, empty serials, SCSI inquiry strings | +| `03-pci-ids` | `PCI_VENDOR`, `DEVICES` - Red Hat/QEMU vendor and subsystem IDs, ICH9 layout | +| `04-fw-cfg` | `QEMU_FW_CFG` - the fw_cfg device signature | +| `05-usb-hid` | `QEMU_USB` - USB HID descriptor strings | +| `06-audio` | `DEVICES` - HDA codec and controller identity | +| `07-edid` | none. Carried for other detectors; VMAware does not read EDID. | +| `08-cpu-misc` | none. CPU feature plumbing and one upstream backport. | + +`patch -p1 < qemu/01-firmware.patch` and so on, then build with the prefix set to `/opt` so the +system QEMU package is left alone: + +```sh +./configure --prefix=/opt/qemu-native --target-list=x86_64-softmmu +ninja -C build && ninja -C build install +``` + +## Approaches tried and rejected - kept so they are not re-derived + +| Approach | Result | +| --- | --- | +| Patched OVMF firmware (`edk2`) | Builds, then hangs before console init at 100% CPU, cause undiagnosed. Stock OVMF is fine and costs only the boot-logo warning. | +| TSC compensation for `TIMER` (BetterTiming) | No detection change, 6x slower boot. | +| CPUID leaf override (Hypervisor-Phantom) | Halved the timing signature, still detected, and *added* a `SINGLE_STEP` detection. | +| Clearing `INTERCEPT_CPUID` outright | Guest never boots. | +| Intercept clearing, minimal hypercall | Triple fault, or no effect. | + +`TIMER` has two detectors that OR together. The exception-latency one passes on stock KVM once it +is measured the way VMAware measures it; the instruction-latency one is removable with +`EXPERIMENTAL-0006`. `docs/ANALYSIS.md` has the measurements, including a direct reproduction of +both in `bench/timerprobe.c`. + +## Identity values in these patches + +Several patches carry a concrete make and model because no real machine leaves the field blank. +Every one of them is a placeholder. Change them before you use this, or a detector gains a +fingerprint shared by everyone who ran the same patch series. + +| Where | Value | Replace with | +| --- | --- | --- | +| `02-disk-identity` | disk serial `S0123456789ABCD` | set `` on the disk in the domain XML; this fallback then never runs | +| `02-disk-identity` | disk and optical drive model strings | a drive you actually own, from `lsblk -o NAME,MODEL` | +| `07-edid` | monitor vendor and model | your own panel, from `edid-decode < /sys/class/drm/*/edid` | + +The SMBIOS values are not patched into QEMU at all - they are domain XML, taken from a real +machine with `dmidecode`. Change every serial there too. diff --git a/patches/kvm/0000-cover-letter.txt b/patches/kvm/0000-cover-letter.txt new file mode 100644 index 0000000..f3e524a --- /dev/null +++ b/patches/kvm/0000-cover-letter.txt @@ -0,0 +1,105 @@ +Subject: [PATCH 0/5] KVM: x86: match the architecture in four guest-observable exception cases + +To: Sean Christopherson , Paolo Bonzini +Cc: Thomas Gleixner , Ingo Molnar , + Borislav Petkov , Dave Hansen , + "H. Peter Anvin" , x86@kernel.org, + kvm@vger.kernel.org, linux-kernel@vger.kernel.org + +Four cases where a guest observes a different exception, or a different +RIP in the exception frame, than the same instruction produces on bare +metal. All four are reachable from unprivileged guest code, none needs +a debugger attached, and each is a one-hunk fix on a path KVM already +owns. The series does not add any new behaviour; it makes the existing +behaviour match the AMD64 architecture (and, for 2/5 and 3/5, what the +VMX side of KVM already does). + + 1/5 KVM: SVM: intercept #GP when guest EFER.SVME is clear + + svm_set_efer() forces EFER_SVME=1 in the VMCB, so a CPL>0 + VMLOAD/VMSAVE/STGI/CLGI passes the hardware SVME check and #GPs + before the instruction intercept can fire. Bare metal, where + SVME really is clear, #UDs. svm_recalc_instruction_intercepts() + already carries a FIXME for this. Intercept #GP while the + guest's SVME is clear so gp_interception() can decode the + instruction and route it to nested_svm_check_permissions(), + which injects the #UD. SEV guests are excluded as KVM cannot + decode their instructions, mirroring the erratum workaround. + + 2/5 KVM: x86: emulator: #UD, not #GP, for VMCALL at CPL > 0 + + group7_rm0[] declares VMCALL as Priv without PrivUD, so the CPL + check in x86_emulate_insn() injects #GP(0). 0F 01 C1 is not an + AMD instruction and is invalid outside VMX operation on Intel; + hardware #UDs at CPL > 0 on both vendors. This path is reached + on AMD through the #UD intercept (a guest built for Intel running + on AMD) and on Intel only under forced emulation. + + 3/5 KVM: x86: #UD for KVM hypercalls issued at CPL > 0 + + kvm_emulate_hypercall() returns -KVM_EPERM and skips the + instruction for a CPL > 0 caller, so the guest observes no + exception at all. VMMCALL/VMCALL is only a legal instruction + because the hypervisor intercepts it; bare metal #UDs. Inject + #UD instead. This is a guest-observable ABI change, argued + below. + + 4/5 KVM: SVM: intercept ICEBP and skip it before injecting its #DB + + ICEBP's #DB is trap-like: the exception frame RIP is the next + instruction. SVM does not route ICEBP's #DB through the #DB + exception intercept, so the guest normally takes it correctly, + but when the delivery is interrupted by a VM-exit (an NPT fault + on a cold IDT/handler page) EXITINTINFO reports the pending #DB + with RIP saved on the ICEBP and svm_complete_interrupts() + re-injects it as-is, so the guest handler sees the ICEBP's own + address. VMX tags ICEBP (is_icebp()) and skips the instruction; + SVM has the dedicated ICEBP intercept, which fires before the #DB + exists. Enable it and skip. + + 5/5 KVM: selftests: verify ICEBP #DB reports RIP past the ICEBP + +On the 3/5 ABI change: every KVM_HC_* already fails for a CPL > 0 +caller, so only the shape of the failure changes, from a silent skip to +#UD. The Xen and Hyper-V dispatch sit before the check and keep their +own CPL semantics; kvm_hv_hypercall() already injects #UD for CPL > 0 +(hyperv.c), so this makes the KVM PV path consistent with it rather +than inventing new behaviour. No in-tree guest issues a KVM hypercall +from userspace: Linux paravirt patches VMMCALL/VMCALL at CPL0 only, and +svm_patch_hypercall() / vmx_patch_hypercall() likewise. Windows guests +issue neither. + +Vendor coverage: 1/5 and 4/5 are SVM-only. 2/5 and 3/5 live in +emulate.c and x86.c and affect Intel as well. On VMX a CPL > 0 VMCALL +unconditionally VM-exits to handle_vmcall() -> kvm_emulate_hypercall(), +so 3/5 changes its outcome from "skipped silently" to #UD there too, +which is what VMCALL outside VMX operation does on bare metal. The +series has been tested on AMD only (Zen 4, 7950X); the Intel reasoning +is from the code and has not been run on Intel hardware. + +Nested: for 1/5, gp_interception() already routes decoded SVM +instructions from L2 through nested_svm_check_permissions(); when L1 +intercepts #GP itself the exit is reflected to L1 as before. For 4/5 +the #DB is reflected to L1 with L2's RIP past the ICEBP, matching what +the VMX path does today. + +SEV: 1/5 does not arm the #GP intercept for SEV guests, and SEV-ES +guests additionally clear it in sev_es_init_vmcb(). 4/5 uses +svm_skip_emulated_instruction(), which already special-cases SEV-ES. + +Testing: a Windows 11 guest, a Linux guest and a Hyper-V-enlightened +Windows guest boot and run normally on the patched modules. The +userspace probes that first exposed the divergences (CPL3 VMLOAD, CPL3 +VMMCALL, ICEBP under SEH) now observe #UD / #UD / RIP+1, as on bare +metal, and the ICEBP one no longer regresses when the delivery hits a +cold page. 5/5 fails on unpatched SVM (Zen 4, kvm_exit trace shows the +NPT fault with the #DB pending in EXITINTINFO) and passes with 4/5. A +selftest for the CPL > 0 cases would need user-mode guest code, which +the x86 selftest library does not currently provide. + + arch/x86/kvm/emulate.c | 7 ++- + arch/x86/kvm/svm/svm.c | 44 ++++++++++++--- + arch/x86/kvm/x86.c | 17 +++++- + tools/testing/selftests/kvm/Makefile.kvm | 1 + + tools/testing/selftests/kvm/x86/icebp_test.c | 58 ++++++++++++++++++++ + 5 files changed, 117 insertions(+), 10 deletions(-) diff --git a/patches/kvm/0001-KVM-SVM-intercept-GP-when-guest-EFER.SVME-is-clear.patch b/patches/kvm/0001-KVM-SVM-intercept-GP-when-guest-EFER.SVME-is-clear.patch new file mode 100644 index 0000000..a660f5f --- /dev/null +++ b/patches/kvm/0001-KVM-SVM-intercept-GP-when-guest-EFER.SVME-is-clear.patch @@ -0,0 +1,94 @@ +From 0a6b8d86c62bd4caebf27872019c7f66b753d61b Mon Sep 17 00:00:00 2001 +From: Sandwich +Date: Sat, 5 Sep 2026 12:02:50 +0200 +Subject: [PATCH 1/5] KVM: SVM: intercept #GP when guest EFER.SVME is clear + +svm_set_efer() unconditionally sets EFER_SVME in the VMCB, because +hardware requires it to run the guest at all. A guest that has not +itself enabled SVM therefore still runs with SVME set from the CPU's +point of view. + +When such a guest executes an SVM instruction at CPL > 0, hardware +passes the SVME check, observes CPL != 0, and injects #GP before the +instruction intercept can fire. Bare metal, where SVME really is clear, +raises #UD instead. The divergence is directly observable from +unprivileged guest code. + +svm_recalc_instruction_intercepts() already carries a FIXME describing +exactly this. Close it by intercepting #GP while the guest's EFER.SVME +is clear, so gp_interception() can decode the instruction and route it +to its real handler, which injects the #UD the architecture specifies. +The intercept is kept across a guest clearing SVME, and dropped again on +SVME=1 where the erratum workaround does not need it. + +SEV guests are excluded for the same reason the erratum workaround +excludes them: KVM can't decode their instructions, so the intercept +would only reinject the same #GP. SEV-ES guests additionally clear +the #GP intercept in sev_es_init_vmcb(). + +For nested guests, gp_interception() already routes a decoded SVM +instruction through nested_svm_check_permissions(), which injects #UD +when L2's SVME is clear. When L1 intercepts #GP itself, the exit is +reflected to L1 as before. + +Signed-off-by: Sandwich +--- + arch/x86/kvm/svm/svm.c | 22 +++++++++++++++------- + 1 file changed, 15 insertions(+), 7 deletions(-) + +diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c +index 1158f3e28..b0db02615 100644 +--- a/arch/x86/kvm/svm/svm.c ++++ b/arch/x86/kvm/svm/svm.c +@@ -237,9 +237,11 @@ int svm_set_efer(struct kvm_vcpu *vcpu, u64 efer) + kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu); + + svm_leave_nested(vcpu); +- /* #GP intercept is still needed for vmware backdoor */ +- if (!enable_vmware_backdoor) +- clr_exception_intercept(svm, GP_VECTOR); ++ /* ++ * #GP stays intercepted: with SVME=0 it routes CPL>0 ++ * SVM instructions to the #UD the architecture requires, ++ * see svm_recalc_instruction_intercepts(). ++ */ + + /* + * Free the nested guest state, unless we are in SMM. +@@ -263,6 +265,8 @@ int svm_set_efer(struct kvm_vcpu *vcpu, u64 efer) + */ + if (svm_gp_erratum_intercept && !is_sev_guest(vcpu)) + set_exception_intercept(svm, GP_VECTOR); ++ else if (!enable_vmware_backdoor) ++ clr_exception_intercept(svm, GP_VECTOR); + } + + svm_pmu_handle_nested_transition(svm); +@@ -1073,16 +1077,20 @@ static void svm_recalc_instruction_intercepts(struct kvm_vcpu *vcpu) + * are set when the VMCB is initialized and never cleared (if the + * relevant intercepts are set, the enablements are meaningless anyway). + * +- * FIXME: When #GP is not intercepted, a #GP on these instructions (e.g. +- * due to CPL > 0) could be injected by hardware before the instruction +- * is intercepted, leading to #GP taking precedence over #UD from the +- * guest's perspective. ++ * Because hardware sees SVME=1, a #GP on these instructions (e.g. due ++ * to CPL > 0) is injected before the instruction intercept fires, and ++ * would take precedence over the #UD the guest should observe. Also ++ * intercept #GP so that gp_interception() can decode the instruction ++ * and route it to its handler, which injects the #UD. SEV guests are ++ * excluded as KVM can't decode their instructions. + */ + if (!(vcpu->arch.efer & EFER_SVME)) { + svm_set_intercept(svm, INTERCEPT_VMLOAD); + svm_set_intercept(svm, INTERCEPT_VMSAVE); + svm_set_intercept(svm, INTERCEPT_CLGI); + svm_set_intercept(svm, INTERCEPT_STGI); ++ if (!is_sev_guest(vcpu)) ++ set_exception_intercept(svm, GP_VECTOR); + } else { + /* + * If hardware supports Virtual VMLOAD VMSAVE then enable it +-- +2.55.0 + diff --git a/patches/kvm/0002-KVM-x86-emulator-UD-not-GP-for-VMCALL-at-CPL-0.patch b/patches/kvm/0002-KVM-x86-emulator-UD-not-GP-for-VMCALL-at-CPL-0.patch new file mode 100644 index 0000000..91e2451 --- /dev/null +++ b/patches/kvm/0002-KVM-x86-emulator-UD-not-GP-for-VMCALL-at-CPL-0.patch @@ -0,0 +1,47 @@ +From 9826e3e9edc355aea6b2f2f386e538aa0f199542 Mon Sep 17 00:00:00 2001 +From: Sandwich +Date: Sat, 5 Sep 2026 11:58:18 +0200 +Subject: [PATCH 2/5] KVM: x86: emulator: #UD, not #GP, for VMCALL at CPL > 0 + +Targets VMAware check: KVM_INTERCEPTION + +0F 01 C1 is not an AMD instruction, and on Intel VMCALL is invalid +outside VMX operation. Hardware raises #UD for it at CPL > 0 on both +vendors. + +The emulator declares VMCALL as Priv without PrivUD, so the generic CPL +check injects #GP(0) instead. Windows surfaces that to userspace as an +access violation rather than an illegal instruction, which is guest- +observable and differs from every physical machine. + +Add PrivUD so the existing branch injects #UD. RIP is not advanced +either way, and CPL0 VMCALL is unaffected, so a guest built for Intel +and migrated onto an AMD host keeps working through +emulator_fix_hypercall(). + +Signed-off-by: Sandwich +--- + arch/x86/kvm/emulate.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kvm/emulate.c b/arch/x86/kvm/emulate.c +index b566ab5c7..a723d05d9 100644 +--- a/arch/x86/kvm/emulate.c ++++ b/arch/x86/kvm/emulate.c +@@ -3969,7 +3969,12 @@ static const struct opcode ud = I(SrcNone, emulate_ud); + + static const struct opcode group7_rm0[] = { + N, +- I(SrcNone | Priv | EmulateOnUD, em_hypercall), ++ /* ++ * 0F 01 C1 is not an AMD instruction, and on Intel VMCALL is invalid ++ * outside VMX operation, i.e. hardware raises #UD at CPL > 0. Without ++ * PrivUD the CPL check below injects #GP, which is guest-observable. ++ */ ++ I(SrcNone | Priv | PrivUD | EmulateOnUD, em_hypercall), + N, N, N, N, N, N, + }; + +-- +2.55.0 + diff --git a/patches/kvm/0003-KVM-x86-UD-for-KVM-hypercalls-issued-at-CPL-0.patch b/patches/kvm/0003-KVM-x86-UD-for-KVM-hypercalls-issued-at-CPL-0.patch new file mode 100644 index 0000000..4ecfd2b --- /dev/null +++ b/patches/kvm/0003-KVM-x86-UD-for-KVM-hypercalls-issued-at-CPL-0.patch @@ -0,0 +1,64 @@ +From 122f8478087bd13892672a6feb3e3417e882c316 Mon Sep 17 00:00:00 2001 +From: Sandwich +Date: Sat, 5 Sep 2026 11:58:18 +0200 +Subject: [PATCH 3/5] KVM: x86: #UD for KVM hypercalls issued at CPL > 0 + +Targets VMAware check: KVM_INTERCEPTION + +VMMCALL is only a legal instruction because the hypervisor intercepts +it; bare metal raises #UD. kvm_emulate_hypercall() instead returns +-KVM_EPERM for a CPL > 0 caller and skips the instruction, so the guest +observes no exception at all - which is directly observable and differs +from every physical machine. + +Inject #UD for CPL > 0 instead. Every KVM_HC_* already failed for such +callers, so only the shape of the failure changes, and no legitimate +hypercall is issued from userspace. + +The check is placed after the Xen and Hyper-V dispatch, so enlightened +guests keep their own CPL semantics. kvm_hv_hypercall() already does +the identical thing for the Hyper-V ABI, so this makes the KVM PV path +consistent with it. + +Signed-off-by: Sandwich +--- + arch/x86/kvm/x86.c | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c +index 47cb9eba1..0dd7ebcc0 100644 +--- a/arch/x86/kvm/x86.c ++++ b/arch/x86/kvm/x86.c +@@ -10503,14 +10503,27 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(____kvm_emulate_hypercall); + + int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) + { ++ int cpl; ++ + if (kvm_xen_hypercall_enabled(vcpu->kvm)) + return kvm_xen_hypercall(vcpu); + + if (kvm_hv_hypercall_enabled(vcpu)) + return kvm_hv_hypercall(vcpu); + +- return __kvm_emulate_hypercall(vcpu, kvm_x86_call(get_cpl)(vcpu), +- complete_hypercall_exit); ++ /* ++ * Bare metal #UDs on VMMCALL from CPL > 0 (the intercept is what ++ * makes it legal at all); returning -KVM_EPERM and skipping the ++ * insn instead is guest-observable. Xen and Hyper-V guests are ++ * dispatched above and keep their own CPL semantics. ++ */ ++ cpl = kvm_x86_call(get_cpl)(vcpu); ++ if (cpl) { ++ kvm_queue_exception(vcpu, UD_VECTOR); ++ return 1; ++ } ++ ++ return __kvm_emulate_hypercall(vcpu, cpl, complete_hypercall_exit); + } + EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_emulate_hypercall); + +-- +2.55.0 + diff --git a/patches/kvm/0004-KVM-SVM-intercept-ICEBP-and-skip-it-before-injecting-its-DB.patch b/patches/kvm/0004-KVM-SVM-intercept-ICEBP-and-skip-it-before-injecting-its-DB.patch new file mode 100644 index 0000000..552e77d --- /dev/null +++ b/patches/kvm/0004-KVM-SVM-intercept-ICEBP-and-skip-it-before-injecting-its-DB.patch @@ -0,0 +1,93 @@ +From 93bb902ad3516b0c5a31482e27a5ecfd93f6799d Mon Sep 17 00:00:00 2001 +From: Sandwich +Date: Sat, 5 Sep 2026 12:31:16 +0200 +Subject: [PATCH 4/5] KVM: SVM: intercept ICEBP and skip it before injecting + its #DB + +ICEBP (INT1, opcode F1) generates a trap-like #DB: the return RIP pushed +for the exception is that of the instruction following the ICEBP. + +SVM does not report ICEBP's #DB through the #DB exception intercept, so +the guest normally takes it directly and correctly. But when delivery +of that #DB is interrupted by a VM-exit - a nested page fault on a cold +IDT, handler or stack page is the common case - the exit reports the +pending #DB in EXITINTINFO with the saved RIP still on the ICEBP, and +svm_complete_interrupts() re-queues it as a plain hardware exception. +The injected #DB then pushes that RIP, so the guest's handler sees the +ICEBP's own address and an IRET re-executes it. The divergence is +observable from unprivileged guest code (an SEH/signal handler reading +the context RIP) and is sporadic, since it needs the delivery to touch +a page KVM has not mapped. + +VMX already handles ICEBP: the #DB VM-exit is tagged as a privileged +software exception (is_icebp()) and handle_exception_nmi() skips the +instruction before queueing the #DB. SVM has the equivalent signal in +the dedicated ICEBP instruction intercept, which fires before the #DB +exists. + +Enable the ICEBP intercept alongside the #DB exception intercept, skip +the instruction and queue the #DB with the same DR6 payload the +exception path uses. Once RIP has been advanced, an injection that is +itself interrupted is re-injected with the advanced RIP. The vendor +skip helper is used deliberately, as on VMX: a pending single-step #DB +on the ICEBP itself is superseded by the ICEBP's #DB. The emulator +decodes 0xF1, so the non-NRIPS fallback works. + +For nested guests the #DB is reflected to L1 with L2's RIP past the +ICEBP, matching what the VMX path does today. + +Signed-off-by: Sandwich +--- + arch/x86/kvm/svm/svm.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c +index b0db02615..5b37855ee 100644 +--- a/arch/x86/kvm/svm/svm.c ++++ b/arch/x86/kvm/svm/svm.c +@@ -1153,6 +1153,7 @@ static void init_vmcb(struct kvm_vcpu *vcpu, bool init_event) + set_exception_intercept(svm, MC_VECTOR); + set_exception_intercept(svm, AC_VECTOR); + set_exception_intercept(svm, DB_VECTOR); ++ svm_set_intercept(svm, INTERCEPT_ICEBP); + /* + * Guest access to VMware backdoor ports could legitimately + * trigger #GP because of TSS I/O permission bitmap. +@@ -2078,6 +2079,26 @@ static int db_interception(struct kvm_vcpu *vcpu) + return 1; + } + ++static int icebp_interception(struct kvm_vcpu *vcpu) ++{ ++ struct vcpu_svm *svm = to_svm(vcpu); ++ u32 payload = svm->vmcb->save.dr6 ^ DR6_ACTIVE_LOW; ++ ++ /* ++ * ICEBP generates a trap-like #DB, but is intercepted as an ++ * instruction, i.e. RIP still points at the ICEBP itself. Skip it ++ * before injecting the #DB so the guest observes the RIP of the next ++ * instruction, as it does on bare metal and under VMX (see is_icebp()). ++ * Use the vendor skip helper: a single-step #DB on the ICEBP is ++ * superseded by the ICEBP's own #DB. ++ */ ++ if (!svm_skip_emulated_instruction(vcpu)) ++ return 0; ++ ++ kvm_queue_exception_p(vcpu, DB_VECTOR, payload); ++ return 1; ++} ++ + static int bp_interception(struct kvm_vcpu *vcpu) + { + struct vcpu_svm *svm = to_svm(vcpu); +@@ -3369,6 +3390,7 @@ static int (*const svm_exit_handlers[])(struct kvm_vcpu *vcpu) = { + [SVM_EXIT_WRITE_DR6] = dr_interception, + [SVM_EXIT_WRITE_DR7] = dr_interception, + [SVM_EXIT_EXCP_BASE + DB_VECTOR] = db_interception, ++ [SVM_EXIT_ICEBP] = icebp_interception, + [SVM_EXIT_EXCP_BASE + BP_VECTOR] = bp_interception, + [SVM_EXIT_EXCP_BASE + UD_VECTOR] = ud_interception, + [SVM_EXIT_EXCP_BASE + PF_VECTOR] = pf_interception, +-- +2.55.0 + diff --git a/patches/kvm/0005-KVM-selftests-verify-ICEBP-DB-reports-RIP-past-the-ICEBP.patch b/patches/kvm/0005-KVM-selftests-verify-ICEBP-DB-reports-RIP-past-the-ICEBP.patch new file mode 100644 index 0000000..10173a7 --- /dev/null +++ b/patches/kvm/0005-KVM-selftests-verify-ICEBP-DB-reports-RIP-past-the-ICEBP.patch @@ -0,0 +1,127 @@ +From 1cc0b5d1d2f61b76c2b9124646b827abda525828 Mon Sep 17 00:00:00 2001 +From: Sandwich +Date: Sat, 5 Sep 2026 12:31:16 +0200 +Subject: [PATCH 5/5] KVM: selftests: verify ICEBP #DB reports RIP past the + ICEBP + +Execute ICEBP (INT1) in the guest under a #DB handler that records the +exception frame's RIP, and assert it is the address of the following +instruction, as on bare metal. The handler advances RIP itself if it +finds it still on the ICEBP so a failing KVM does not loop forever. + +The first ICEBP in a fresh VM faults on the unmapped IDT and handler +pages during delivery, which is the interrupted-delivery case where SVM +re-injected the #DB with the wrong RIP. Fails on SVM without the +preceding patch, passes with it, and passes on VMX, which already skips +the instruction. + +Signed-off-by: Sandwich +--- + arch/x86/kvm/svm/svm.c | 7 ++- + tools/testing/selftests/kvm/Makefile.kvm | 1 + + tools/testing/selftests/kvm/x86/icebp_test.c | 63 ++++++++++++++++++++ + 3 files changed, 68 insertions(+), 3 deletions(-) + create mode 100644 tools/testing/selftests/kvm/x86/icebp_test.c + +diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c +index 5b37855ee..34e025cba 100644 +--- a/arch/x86/kvm/svm/svm.c ++++ b/arch/x86/kvm/svm/svm.c +@@ -2087,9 +2087,10 @@ static int icebp_interception(struct kvm_vcpu *vcpu) + /* + * ICEBP generates a trap-like #DB, but is intercepted as an + * instruction, i.e. RIP still points at the ICEBP itself. Skip it +- * before injecting the #DB so the guest observes the RIP of the next +- * instruction, as it does on bare metal and under VMX (see is_icebp()). +- * Use the vendor skip helper: a single-step #DB on the ICEBP is ++ * before injecting the #DB so that the guest, and any re-injection ++ * after an exit interrupts the delivery, sees the RIP of the next ++ * instruction, as on bare metal and under VMX (see is_icebp()). Use ++ * the vendor skip helper: a single-step #DB on the ICEBP is + * superseded by the ICEBP's own #DB. + */ + if (!svm_skip_emulated_instruction(vcpu)) +diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm +index 6fc34e9bf..b45fe0a13 100644 +--- a/tools/testing/selftests/kvm/Makefile.kvm ++++ b/tools/testing/selftests/kvm/Makefile.kvm +@@ -138,6 +138,7 @@ TEST_GEN_PROGS_x86 += x86/xapic_tpr_test + TEST_GEN_PROGS_x86 += x86/xcr0_cpuid_test + TEST_GEN_PROGS_x86 += x86/xss_msr_test + TEST_GEN_PROGS_x86 += x86/debug_regs ++TEST_GEN_PROGS_x86 += x86/icebp_test + TEST_GEN_PROGS_x86 += x86/tsc_msrs_test + TEST_GEN_PROGS_x86 += x86/vmx_pmu_caps_test + TEST_GEN_PROGS_x86 += x86/xen_shinfo_test +diff --git a/tools/testing/selftests/kvm/x86/icebp_test.c b/tools/testing/selftests/kvm/x86/icebp_test.c +new file mode 100644 +index 000000000..7890fa949 +--- /dev/null ++++ b/tools/testing/selftests/kvm/x86/icebp_test.c +@@ -0,0 +1,63 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * icebp_test ++ * ++ * Verify that the #DB generated by ICEBP (INT1) is delivered with RIP ++ * pointing at the instruction following the ICEBP, as on bare metal. ++ * ++ * The first ICEBP in a fresh VM faults on the not-yet-mapped IDT and ++ * handler pages while the #DB is being delivered, so the delivery is ++ * interrupted by a VM-exit and re-injected by KVM. That is exactly the ++ * case where SVM used to re-inject with RIP still on the ICEBP. ++ */ ++#include "test_util.h" ++#include "kvm_util.h" ++#include "processor.h" ++ ++static u64 db_rip; ++ ++static void db_handler(struct ex_regs *regs) ++{ ++ db_rip = regs->rip; ++ ++ /* Don't loop forever if RIP was left on the ICEBP itself. */ ++ if (*(u8 *)regs->rip == 0xf1) ++ regs->rip++; ++} ++ ++static void guest_code(void) ++{ ++ u64 next_rip; ++ ++ asm volatile(".byte 0xf1\n\t" /* icebp */ ++ "1: lea 1b(%%rip), %0" ++ : "=r"(next_rip) : : "memory"); ++ ++ GUEST_ASSERT_EQ(db_rip, next_rip); ++ GUEST_DONE(); ++} ++ ++int main(int argc, char *argv[]) ++{ ++ struct kvm_vcpu *vcpu; ++ struct kvm_vm *vm; ++ struct ucall uc; ++ ++ vm = vm_create_with_one_vcpu(&vcpu, guest_code); ++ vm_install_exception_handler(vm, DB_VECTOR, db_handler); ++ ++ vcpu_run(vcpu); ++ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); ++ ++ switch (get_ucall(vcpu, &uc)) { ++ case UCALL_ABORT: ++ REPORT_GUEST_ASSERT(uc); ++ case UCALL_DONE: ++ break; ++ default: ++ TEST_FAIL("Unknown ucall 0x%lx.", uc.cmd); ++ } ++ ++ kvm_vm_free(vm); ++ return 0; ++} +-- +2.55.0 + diff --git a/patches/kvm/EXPERIMENTAL-0006-runtime-cpuid-passthrough.patch b/patches/kvm/EXPERIMENTAL-0006-runtime-cpuid-passthrough.patch new file mode 100644 index 0000000..5f47c00 --- /dev/null +++ b/patches/kvm/EXPERIMENTAL-0006-runtime-cpuid-passthrough.patch @@ -0,0 +1,195 @@ +From ddc09a6009d5016130acfd1aa8eef69f99da5972 Mon Sep 17 00:00:00 2001 +From: Sandwich +Date: Sat, 5 Sep 2026 12:23:22 +0200 +Subject: [PATCH] KVM: SVM: opt-in runtime CPUID passthrough with brand-string + override + +NOT FOR UPSTREAM. Targets VMAware check: TIMER (instruction-latency +detector). + +The detector times an intercepted CPUID against LFENCE using a cross-core +cache-line counter, so the #VMEXIT world switch itself is what it +measures and no clock can be adjusted to hide it. The only way to stop +paying it is to not exit. + +With cpuid_passthrough=1 the CPUID intercept is cleared on every guest +entry and the guest executes raw CPUID. Raw CPUID reports the host SKU, +which contradicts a smaller declared topology, so the AMD Processor Name +String MSRs (0xC0010030-35) are reprogrammed on the pinned core while the +vCPU runs and restored in svm_vcpu_put(). Raw CPUID also advertises +RDPRU, which KVM masks out and #UDs on; the RDPRU intercept is dropped +together with the CPUID one so the guest gets the real instruction. + +Constraints, all deliberate: + - AMD only; the name-string MSRs are probed for writability at load and + the brand override is skipped when the probe fails. + - Requires strict 1:1 vCPU pinning, or the override leaks onto host + cores and the guest reads inconsistent brands. The vCPU thread's + allowed-CPU mask is checked on every entry and passthrough is + withheld from any thread not confined to exactly one CPU. + - Enable only after the guest has booted. Windows enumerates KVM's + synthetic leaf-1 bits (x2apic, tsc-deadline) during boot and hangs if + they vanish mid-enumeration. + - The #DB intercept is untouched. Clearing it breaks KVM's single-step + re-injection and DR6 handling, which is separately detectable. + +Signed-off-by: Sandwich +--- + arch/x86/kvm/svm/svm.c | 120 +++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 120 insertions(+) + +diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c +index 34e025cba..bfc316505 100644 +--- a/arch/x86/kvm/svm/svm.c ++++ b/arch/x86/kvm/svm/svm.c +@@ -179,6 +179,120 @@ module_param(vnmi, bool, 0444); + + module_param(enable_mediated_pmu, bool, 0444); + ++/* ++ * vfio-native: neutralise the VMAware TIMER instruction-latency detector on a ++ * strictly 1:1-pinned guest. That detector times the #VMEXIT world switch on an ++ * intercepted CPUID with a cross-core cache counter; dropping CPUID interception ++ * removes the exit so the guest measures like bare metal. Raw CPUID would then ++ * report the host SKU and trip VMAware's thread/brand checks, so the AMD ++ * Processor Name String MSRs (0xC0010030-35) are reprogrammed on the pinned core ++ * while the guest runs and restored when the vCPU yields the core. ++ * ++ * Opt-in and runtime-toggled (cpuid_passthrough). It does NOT touch the #DB ++ * intercept: clearing that breaks KVM's single-step re-injection and DR6, which ++ * is separately detectable. Requires 1:1 vCPU pinning - without it the dropped ++ * interception and reprogrammed brand leak onto the wrong cores. ++ */ ++static bool cpuid_passthrough; ++module_param(cpuid_passthrough, bool, 0644); ++MODULE_PARM_DESC(cpuid_passthrough, ++ "Drop CPUID/RDPRU interception and override the CPU brand on a 1:1-pinned guest (AMD only)"); ++ ++static char brand_string[48]; ++module_param_string(brand_string, brand_string, sizeof(brand_string), 0644); ++MODULE_PARM_DESC(brand_string, ++ "48-byte CPU brand string exposed via CPUID 0x80000002-4 while cpuid_passthrough is active"); ++ ++#define MSR_AMD_NAME_STRING 0xc0010030 /* AMD Processor Name String, 6 consecutive MSRs */ ++ ++static u64 hw_brand[6]; ++static bool brand_msr_ok __ro_after_init; /* the name-string MSRs are writable here */ ++static DEFINE_PER_CPU(bool, brand_applied); /* this core currently holds the override */ ++ ++static void svm_brand_probe(void) ++{ ++ int i; ++ ++ if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD) ++ return; ++ for (i = 0; i < 6; i++) ++ if (rdmsrq_safe(MSR_AMD_NAME_STRING + i, &hw_brand[i])) ++ return; ++ if (wrmsrq_safe(MSR_AMD_NAME_STRING, hw_brand[0])) /* confirm writable */ ++ return; ++ brand_msr_ok = true; ++} ++ ++/* current cpu, preemption disabled */ ++static void svm_brand_write(void) ++{ ++ u64 regs[6] = {}; ++ int i; ++ ++ memcpy(regs, brand_string, sizeof(brand_string)); ++ for (i = 0; i < 6; i++) ++ wrmsrq_safe(MSR_AMD_NAME_STRING + i, regs[i]); ++} ++ ++/* current cpu, preemption disabled */ ++static void svm_brand_restore(void) ++{ ++ int i; ++ ++ for (i = 0; i < 6; i++) ++ wrmsrq_safe(MSR_AMD_NAME_STRING + i, hw_brand[i]); ++} ++ ++/* on guest entry */ ++static void svm_passthrough_apply(struct kvm_vcpu *vcpu) ++{ ++ struct vcpu_svm *svm = to_svm(vcpu); ++ /* ++ * Only a vCPU thread confined to exactly one CPU may run with raw ++ * CPUID and the brand override: on any other core the override is ++ * missing and the guest reads two different brands. Checking the ++ * thread's own allowed mask here verifies the 1:1 pinning instead of ++ * trusting the operator to have set it. ++ */ ++ bool on = cpuid_passthrough && vcpu->guest_debug == 0 && ++ cpumask_weight(current->cpus_ptr) == 1; ++ ++ if (likely(!on)) { ++ if (unlikely(!svm_is_intercept(svm, INTERCEPT_CPUID))) { ++ svm_set_intercept(svm, INTERCEPT_CPUID); ++ svm_set_intercept(svm, INTERCEPT_RDPRU); ++ } ++ if (brand_msr_ok && this_cpu_read(brand_applied)) { ++ svm_brand_restore(); ++ this_cpu_write(brand_applied, false); ++ } ++ return; ++ } ++ ++ if (svm_is_intercept(svm, INTERCEPT_CPUID)) { ++ svm_clr_intercept(svm, INTERCEPT_CPUID); ++ /* ++ * Raw CPUID advertises RDPRU, which KVM masks out and #UDs on. ++ * A guest that sees the bit and executes the instruction must ++ * get the real one, or the #UD is itself a hypervisor tell. ++ */ ++ svm_clr_intercept(svm, INTERCEPT_RDPRU); ++ } ++ if (brand_msr_ok && !this_cpu_read(brand_applied)) { ++ svm_brand_write(); ++ this_cpu_write(brand_applied, true); ++ } ++} ++ ++/* current cpu, preemption disabled */ ++static void svm_passthrough_leave_cpu(void) ++{ ++ if (brand_msr_ok && this_cpu_read(brand_applied)) { ++ svm_brand_restore(); ++ this_cpu_write(brand_applied, false); ++ } ++} ++ + static bool __ro_after_init svm_gp_erratum_intercept = true; + + static u8 rsm_ins_bytes[] = "\x0f\xaa"; +@@ -1500,6 +1614,8 @@ static void svm_vcpu_load(struct kvm_vcpu *vcpu, int cpu) + + static void svm_vcpu_put(struct kvm_vcpu *vcpu) + { ++ svm_passthrough_leave_cpu(); ++ + if (kvm_vcpu_apicv_active(vcpu)) + avic_vcpu_put(vcpu); + +@@ -4517,6 +4633,8 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags) + struct vcpu_svm *svm = to_svm(vcpu); + unsigned enter_flags = 0; + ++ svm_passthrough_apply(vcpu); ++ + if (!msr_write_intercepted(svm, MSR_IA32_SPEC_CTRL)) + enter_flags |= KVM_ENTER_SAVE_SPEC_CTRL; + +@@ -5652,6 +5770,8 @@ static __init int svm_hardware_setup(void) + void *iopm_va; + int cpu, r; + ++ svm_brand_probe(); ++ + /* + * NX is required for shadow paging and for NPT if the NX huge pages + * mitigation is enabled. +-- +2.55.0 + diff --git a/patches/qemu/01-firmware.patch b/patches/qemu/01-firmware.patch new file mode 100644 index 0000000..fa843e5 --- /dev/null +++ b/patches/qemu/01-firmware.patch @@ -0,0 +1,397 @@ +Subject: [PATCH] firmware: FIRMWARE + +Targets VMAware check: FIRMWARE + +ACPI table fingerprints: PCI link-device names, PNP0A06 resource stubs, the +FADT C-state latencies and the AML the checker string-matches on. + +Applies to QEMU v11.1.1. Apply from the source root with: + patch -p1 < 01-firmware.patch + +--- +diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c +index 8837b69..4001d9d 100644 +--- a/hw/i386/acpi-build.c ++++ b/hw/i386/acpi-build.c +@@ -165,8 +165,8 @@ static void init_common_fadt_data(MachineState *ms, Object *o, + (1 << ACPI_FADT_F_FORCE_APIC_CLUSTER_MODEL) : 0), + .int_model = 1 /* Multiple APIC */, + .rtc_century = RTC_CENTURY, +- .plvl2_lat = 0xfff /* C2 state not supported */, +- .plvl3_lat = 0xfff /* C3 state not supported */, ++ .plvl2_lat = 0xffe /* C2 state not supported */, ++ .plvl3_lat = 0xffe /* C3 state not supported */, + .smi_cmd = smm_enabled ? ACPI_PORT_SMI_CMD : 0, + .sci_int = object_property_get_uint(o, ACPI_PM_PROP_SCI_INT, NULL), + .acpi_enable_cmd = +@@ -748,9 +748,9 @@ static void build_q35_pci0_int(Aml *table) + aml_append(table, method); + + aml_append(pci0_scope, +- aml_name_decl("PRTP", build_q35_routing_table("LNK"))); ++ aml_name_decl("IRQP", build_q35_routing_table("LNK"))); + aml_append(pci0_scope, +- aml_name_decl("PRTA", build_q35_routing_table("GSI"))); ++ aml_name_decl("IRQA", build_q35_routing_table("APC"))); + + method = aml_method("_PRT", 0, AML_NOTSERIALIZED); + { +@@ -762,10 +762,10 @@ static void build_q35_pci0_int(Aml *table) + /* Note: we provide the same info as the PCI routing + table of the Bochs BIOS */ + if_ctx = aml_if(aml_equal(aml_name("PICF"), aml_int(0))); +- aml_append(if_ctx, aml_return(aml_name("PRTP"))); ++ aml_append(if_ctx, aml_return(aml_name("IRQP"))); + aml_append(method, if_ctx); + else_ctx = aml_else(); +- aml_append(else_ctx, aml_return(aml_name("PRTA"))); ++ aml_append(else_ctx, aml_return(aml_name("IRQA"))); + aml_append(method, else_ctx); + } + aml_append(pci0_scope, method); +@@ -783,14 +783,14 @@ static void build_q35_pci0_int(Aml *table) + aml_append(sb_scope, build_link_dev("LNKG", 6, aml_name("PRQG"))); + aml_append(sb_scope, build_link_dev("LNKH", 7, aml_name("PRQH"))); + +- aml_append(sb_scope, build_gsi_link_dev("GSIA", 0x10, 0x10)); +- aml_append(sb_scope, build_gsi_link_dev("GSIB", 0x11, 0x11)); +- aml_append(sb_scope, build_gsi_link_dev("GSIC", 0x12, 0x12)); +- aml_append(sb_scope, build_gsi_link_dev("GSID", 0x13, 0x13)); +- aml_append(sb_scope, build_gsi_link_dev("GSIE", 0x14, 0x14)); +- aml_append(sb_scope, build_gsi_link_dev("GSIF", 0x15, 0x15)); +- aml_append(sb_scope, build_gsi_link_dev("GSIG", 0x16, 0x16)); +- aml_append(sb_scope, build_gsi_link_dev("GSIH", 0x17, 0x17)); ++ aml_append(sb_scope, build_gsi_link_dev("APCA", 0x10, 0x10)); ++ aml_append(sb_scope, build_gsi_link_dev("APCB", 0x11, 0x11)); ++ aml_append(sb_scope, build_gsi_link_dev("APCC", 0x12, 0x12)); ++ aml_append(sb_scope, build_gsi_link_dev("APCD", 0x13, 0x13)); ++ aml_append(sb_scope, build_gsi_link_dev("APCE", 0x14, 0x14)); ++ aml_append(sb_scope, build_gsi_link_dev("APCF", 0x15, 0x15)); ++ aml_append(sb_scope, build_gsi_link_dev("APCG", 0x16, 0x16)); ++ aml_append(sb_scope, build_gsi_link_dev("APCH", 0x17, 0x17)); + + aml_append(table, sb_scope); + } +@@ -801,7 +801,7 @@ static Aml *build_q35_dram_controller(const AcpiMcfgInfo *mcfg) + Aml *resource_template; + + /* DRAM controller */ +- dev = aml_device("DRAC"); ++ dev = aml_device("MCFG"); + aml_append(dev, aml_name_decl("_HID", aml_string("PNP0C01"))); + + resource_template = aml_resource_template(); +@@ -885,7 +885,6 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, + acpi_table_begin(&table, table_data); + dsdt = init_aml_allocator(); + +- build_dbg_aml(dsdt); + if (i440fx) { + sb_scope = aml_scope("_SB"); + dev = aml_device("PCI0"); +@@ -901,6 +900,36 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, + build_piix4_pci0_int(dsdt); + } else if (q35) { + sb_scope = aml_scope("_SB"); ++ ++ /* ++ * Emulate Windows ACPI OSYS/_OSI logic in DSDT. ++ * Adds Windows 2001/2006/2009/2012/2013/2015. ++ */ ++ static const struct { ++ const char *osi; ++ uint32_t osys; ++ } win_osi[] = { ++ { "Windows 2001", 0x07D1 }, ++ { "Windows 2001 SP1", 0x07D1 }, ++ { "Windows 2001 SP2", 0x07D2 }, ++ { "Windows 2001.1", 0x07D3 }, ++ { "Windows 2006", 0x07D6 }, ++ { "Windows 2009", 0x07D9 }, ++ { "Windows 2012", 0x07DC }, ++ { "Windows 2013", 0x07DD }, ++ { "Windows 2015", 0x07DF }, ++ }; ++ ++ aml_append(sb_scope, aml_name_decl("OSYS", aml_int(0x03E8))); ++ ++ for (unsigned n = 0; n < ARRAY_SIZE(win_osi); n++) { ++ Aml *osi = aml_if(aml_equal( ++ aml_call1("_OSI", aml_string("%s", win_osi[n].osi)), aml_int(1))); ++ aml_append(osi, aml_store(aml_int(win_osi[n].osys), ++ aml_name("OSYS"))); ++ aml_append(sb_scope, osi); ++ } ++ + dev = aml_device("PCI0"); + aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A08"))); + aml_append(dev, aml_name_decl("_CID", aml_eisaid("PNP0A03"))); +@@ -916,7 +945,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, + /* reserve SMI block resources, IO ports 0xB2, 0xB3 */ + dev = aml_device("PCI0.SMI0"); + aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A06"))); +- aml_append(dev, aml_name_decl("_UID", aml_string("SMI resources"))); ++ aml_append(dev, aml_name_decl("_UID", aml_string("Reserved resources A"))); + crs = aml_resource_template(); + aml_append(crs, + aml_io( +@@ -967,22 +996,6 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, + } + aml_append(dsdt, scope); + +- { +- CPUHotplugFeatures opts = { +- .acpi_1_compatible = true, +- .smi_path = pm->smi_on_cpuhp ? "\\_SB.PCI0.SMI0.SMIC" : NULL, +- .fw_unplugs_cpu = pm->smi_on_cpu_unplug, +- }; +- build_cpus_aml(dsdt, machine, opts, pc_madt_cpu_entry, +- pm->cpu_hp_io_base, "\\_SB.PCI0", "\\_GPE._E02", +- AML_SYSTEM_IO); +- } +- +- if (pcms->memhp_io_base && nr_mem) { +- build_memory_hotplug_aml(dsdt, nr_mem, "\\_SB.PCI0", +- "\\_GPE._E03", AML_SYSTEM_IO, +- pcms->memhp_io_base); +- } + + crs_range_set_init(&crs_range_set); + bus = PC_MACHINE(machine)->pcibus; +@@ -1133,9 +1146,9 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, + aml_append(scope, aml_name_decl("_CRS", crs)); + + /* reserve GPE0 block resources */ +- dev = aml_device("GPE0"); ++ dev = aml_device("RSRA"); + aml_append(dev, aml_name_decl("_HID", aml_string("PNP0A06"))); +- aml_append(dev, aml_name_decl("_UID", aml_string("GPE0 resources"))); ++ aml_append(dev, aml_name_decl("_UID", aml_int(0))); + /* device present, functioning, decoding, not shown in UI */ + aml_append(dev, aml_name_decl("_STA", aml_int(0xB))); + crs = aml_resource_template(); +@@ -1188,12 +1201,6 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, + aml_append(scope, aml_name_decl("_S5", pkg)); + aml_append(dsdt, scope); + +- /* create fw_cfg node, unconditionally */ +- { +- scope = aml_scope("\\_SB.PCI0"); +- fw_cfg_add_acpi_dsdt(scope, x86ms->fw_cfg); +- aml_append(dsdt, scope); +- } + + sb_scope = aml_scope("\\_SB"); + { +@@ -2168,8 +2175,6 @@ void acpi_build(AcpiBuildTables *tables, MachineState *machine) + x86ms->oem_id, x86ms->oem_table_id, &pcms->cxl_devices_state); + } + +- acpi_add_table(table_offsets, tables_blob); +- build_waet(tables_blob, tables->linker, x86ms->oem_id, x86ms->oem_table_id); + + if (pcms->wdat_enabled == true) { + g_assert(pm.tco_io_base); +diff --git a/hw/acpi/pcihp.c b/hw/acpi/pcihp.c +index 87162ff..4c303cb 100644 +--- a/hw/acpi/pcihp.c ++++ b/hw/acpi/pcihp.c +@@ -687,10 +687,10 @@ void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */, + { + Aml *dev, *crs; + +- dev = aml_device("PHPR"); ++ dev = aml_device("RSRB"); + aml_append(dev, aml_name_decl("_HID", aml_string("PNP0A06"))); + aml_append(dev, +- aml_name_decl("_UID", aml_string("PCI Hotplug resources"))); ++ aml_name_decl("_UID", aml_string("Reserved resources B"))); + /* device present, functioning, decoding, not shown in UI */ + aml_append(dev, aml_name_decl("_STA", aml_int(0xB))); + crs = aml_resource_template(); +@@ -699,6 +699,83 @@ void build_append_pcihp_resources(Aml *scope /* \\_SB.PCI0 */, + aml_append(scope, dev); + } + ++/* ++ * Helper to generate "Real Hardware" looking ACPI names. ++ * This removes the "S" signature and ensures unique naming ++ * for multifunction devices. ++ */ ++static void get_mimic_pci_name(char *name, int devfn) { ++ int slot = PCI_SLOT(devfn); ++ int func = PCI_FUNC(devfn); ++ ++ switch (slot) { ++ case 0: ++ // Host Bridge is almost always Slot 0, Function 0 (and also bus 0 but we cant access bus number in this context) ++ if (func == 0) sprintf(name, "MCHC"); ++ else sprintf(name, "H%X", func); ++ break; ++ ++ case 2: ++ // Slot 2: Graphics + associated HDMI Audio ++ if (func == 0) sprintf(name, "GFX0"); // Primary GPU ++ else if (func == 1) sprintf(name, "HDAU"); // HDMI Audio ++ else sprintf(name, "GFX%X", func); // Fallback uniqueness ++ break; ++ ++ case 3: ++ // Slot 3: Network ++ if (func == 0) sprintf(name, "GLAN"); ++ else sprintf(name, "NET%X", func); ++ break; ++ ++ case 4: ++ // Slot 4: Multimedia / Audio ++ if (func == 0) sprintf(name, "HDEF"); ++ else sprintf(name, "MM%02X", func); ++ break; ++ ++ case 5: ++ // Slot 5: USB XHCI (QEMU default often) ++ if (func == 0) sprintf(name, "XHC1"); ++ else sprintf(name, "XHC%X", func); ++ break; ++ ++ case 6: ++ // Slot 6: SATA (AHCI) ++ if (func == 0) sprintf(name, "SAT0"); ++ else sprintf(name, "SAT%X", func); ++ break; ++ ++ case 29: ++ // Slot 29 (0x1D): USB EHCI (Intel Standard) ++ if (func == 0) sprintf(name, "EHC1"); ++ else sprintf(name, "EHC%X", func); ++ break; ++ ++ case 20: ++ // Slot 20 (0x14): LPC / SMBus / SATA 2 ++ if (func == 0) sprintf(name, "LPCB"); // ISA Bridge ++ else if (func == 2) sprintf(name, "SAT1"); // 2nd SATA ++ else if (func == 3) sprintf(name, "SBUS"); // SMBus ++ else sprintf(name, "S20%X", func); // Fallback ++ break; ++ ++ default: ++ /* ++ * For generic slots, we use "RP" (Root Port) + devfn (Hex). ++ * ++ * Why 'devfn' and not 'slot'? ++ * - Slot 10, Func 0 (devfn 0x50) -> RP50 ++ * - Slot 10, Func 1 (devfn 0x51) -> RP51 ++ * ++ * This guarantees ACPI uniqueness for multifunction devices ++ * while completely breaking the "S" + hex signature. ++ */ ++ sprintf(name, "RP%02X", devfn); ++ break; ++ } ++} ++ + bool build_append_notification_callback(Aml *parent_scope, const PCIBus *bus) + { + Aml *method; +@@ -912,7 +989,11 @@ void build_append_pci_bus_devices(Aml *parent_scope, PCIBus *bus) + } + + /* start to compose PCI device descriptor */ +- dev = aml_device("S%.02X", devfn); ++ ++ char mimic_name[5]; ++ get_mimic_pci_name(mimic_name, devfn); ++ dev = aml_device("%s", mimic_name); ++ + aml_append(dev, aml_name_decl("_ADR", aml_int(adr))); + + call_dev_aml_func(DEVICE(bus->devices[devfn]), dev); +diff --git a/hw/acpi/aml-build.c b/hw/acpi/aml-build.c +index 990abc6..01c8fc4 100644 +--- a/hw/acpi/aml-build.c ++++ b/hw/acpi/aml-build.c +@@ -1751,15 +1751,14 @@ void acpi_table_begin(AcpiTable *desc, GArray *array) + * reserve space for Length field, which will be patched by + * acpi_table_end() when the table creation is finished. + */ +- build_append_int_noprefix(array, 0, 4); /* Length */ +- build_append_int_noprefix(array, desc->rev, 1); /* Revision */ +- build_append_int_noprefix(array, 0, 1); /* Checksum */ +- build_append_padded_str(array, desc->oem_id, 6, '\0'); /* OEMID */ +- /* OEM Table ID */ +- build_append_padded_str(array, desc->oem_table_id, 8, '\0'); +- build_append_int_noprefix(array, 1, 4); /* OEM Revision */ +- g_array_append_vals(array, ACPI_BUILD_APPNAME8, 4); /* Creator ID */ +- build_append_int_noprefix(array, 1, 4); /* Creator Revision */ ++ build_append_int_noprefix(array, 0, 4); /* Length */ ++ build_append_int_noprefix(array, desc->rev, 1); /* Revision */ ++ build_append_int_noprefix(array, 0, 1); /* Checksum */ ++ build_append_padded_str(array, ACPI_BUILD_APPNAME6, 6, '\0'); /* OEMID */ ++ build_append_padded_str(array, ACPI_BUILD_APPNAME8, 8, '\0'); /* OEM Table ID */ ++ build_append_int_noprefix(array, 1, 4); /* OEM Revision */ ++ g_array_append_vals(array, "ACPI", 4); /* Creator ID */ ++ build_append_int_noprefix(array, 1, 4); /* Creator Revision */ + } + + void acpi_table_end(BIOSLinker *linker, AcpiTable *desc) +@@ -2488,7 +2487,7 @@ void build_fadt(GArray *tbl, BIOSLinker *linker, const AcpiFadtData *f, + /* ACPI1.0: INT_MODEL, ACPI2.0+: Reserved */ + build_append_int_noprefix(tbl, f->int_model /* Multiple APIC */, 1); + /* Preferred_PM_Profile */ +- build_append_int_noprefix(tbl, 0 /* Unspecified */, 1); ++ build_append_int_noprefix(tbl, 1 /* Desktop */, 1); + build_append_int_noprefix(tbl, f->sci_int, 2); /* SCI_INT */ + build_append_int_noprefix(tbl, f->smi_cmd, 4); /* SMI_CMD */ + build_append_int_noprefix(tbl, f->acpi_enable_cmd, 1); /* ACPI_ENABLE */ +@@ -2585,7 +2584,7 @@ void build_fadt(GArray *tbl, BIOSLinker *linker, const AcpiFadtData *f, + } + + /* Hypervisor Vendor Identity */ +- build_append_padded_str(tbl, "QEMU", 8, '\0'); ++ build_append_int_noprefix(tbl, 0, 8); + + /* TODO: extra fields need to be added to support revisions above rev6 */ + assert(f->rev == 6); +diff --git a/include/hw/acpi/aml-build.h b/include/hw/acpi/aml-build.h +index eaff025..9c3b434 100644 +--- a/include/hw/acpi/aml-build.h ++++ b/include/hw/acpi/aml-build.h +@@ -5,8 +5,8 @@ + #include "hw/acpi/bios-linker-loader.h" + #include "hw/core/cpu.h" + +-#define ACPI_BUILD_APPNAME6 "BOCHS " +-#define ACPI_BUILD_APPNAME8 "BXPC " ++#define ACPI_BUILD_APPNAME6 "ALASKA" ++#define ACPI_BUILD_APPNAME8 "A M I " + + #define ACPI_BUILD_TABLE_FILE "etc/acpi/tables" + #define ACPI_BUILD_RSDP_FILE "etc/acpi/rsdp" +diff --git a/include/hw/i386/x86.h b/include/hw/i386/x86.h +index 71fe6b5..755c9d8 100644 +--- a/include/hw/i386/x86.h ++++ b/include/hw/i386/x86.h +@@ -136,7 +136,7 @@ bool x86_machine_is_acpi_enabled(const X86MachineState *x86ms); + + /* Global System Interrupts */ + +-#define ACPI_BUILD_PCI_IRQS ((1<<5) | (1<<9) | (1<<10) | (1<<11)) ++#define ACPI_BUILD_PCI_IRQS ((1<<4) | (1<<5) | (1<<6) | (1<<10) | (1<<11) | (1<<14) | (1<<15)) + + typedef struct GSIState { + qemu_irq i8259_irq[ISA_NUM_IRQS]; +diff --git a/hw/pci-host/gpex.c b/hw/pci-host/gpex.c +index e66784c..8f748f9 100644 +--- a/hw/pci-host/gpex.c ++++ b/hw/pci-host/gpex.c +@@ -243,8 +243,8 @@ static void gpex_root_class_init(ObjectClass *klass, const void *data) + + dc->desc = "QEMU generic PCIe host bridge"; + dc->vmsd = &vmstate_gpex_root; +- k->vendor_id = PCI_VENDOR_ID_REDHAT; +- k->device_id = PCI_DEVICE_ID_REDHAT_PCIE_HOST; ++ k->vendor_id = PCI_VENDOR_ID_AMD; ++ k->device_id = PCI_DEVICE_ID_INTEL_P35_MCH; + k->revision = 0; + k->class_id = PCI_CLASS_BRIDGE_HOST; + /* diff --git a/patches/qemu/02-disk-identity.patch b/patches/qemu/02-disk-identity.patch new file mode 100644 index 0000000..ed8ace3 --- /dev/null +++ b/patches/qemu/02-disk-identity.patch @@ -0,0 +1,115 @@ +Subject: [PATCH] disk-identity: VIRTUAL_DISK / DISK_SERIAL + +Targets VMAware check: VIRTUAL_DISK / DISK_SERIAL + +Storage that announces itself as emulated: 'QEMU HARDDISK', 'QEMU DVD-ROM', +empty serials and the QEMU vendor string in SCSI inquiry data. + +Applies to QEMU v11.1.1. Apply from the source root with: + patch -p1 < 02-disk-identity.patch + +--- +diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c +index 1b0cce1..0e8ded4 100644 +--- a/hw/scsi/scsi-disk.c ++++ b/hw/scsi/scsi-disk.c +@@ -2551,7 +2551,7 @@ static void scsi_realize(SCSIDevice *dev, Error **errp) + s->version = g_strdup(QEMU_HW_VERSION); + } + if (!s->vendor) { +- s->vendor = g_strdup("QEMU"); ++ s->vendor = g_strdup("ATA"); + } + if (s->serial && strlen(s->serial) > MAX_SERIAL_LEN) { + error_setg(errp, "The serial number can't be longer than %d characters", +@@ -2615,7 +2615,7 @@ static void scsi_hd_realize(SCSIDevice *dev, Error **errp) + s->qdev.blocksize = s->qdev.conf.logical_block_size; + s->qdev.type = TYPE_DISK; + if (!s->product) { +- s->product = g_strdup("QEMU HARDDISK"); ++ s->product = g_strdup("Samsung SSD 870 EVO 1TB"); + } + scsi_realize(&s->qdev, errp); + } +@@ -2642,7 +2642,7 @@ static void scsi_cd_realize(SCSIDevice *dev, Error **errp) + s->qdev.type = TYPE_ROM; + s->features |= 1 << SCSI_DISK_F_REMOVABLE; + if (!s->product) { +- s->product = g_strdup("QEMU CD-ROM"); ++ s->product = g_strdup("HL-DT-ST BD-RE WH16NS60"); + } + scsi_realize(&s->qdev, errp); + } +diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c +index deb43d5..36e4ebd 100644 +--- a/hw/scsi/scsi-bus.c ++++ b/hw/scsi/scsi-bus.c +@@ -696,8 +696,8 @@ static bool scsi_target_emulate_inquiry(SCSITargetReq *r) + r->buf[3] = 2 | 0x10; /* HiSup, response data format */ + r->buf[4] = r->len - 5; /* Additional Length = (Len - 1) - 4 */ + r->buf[7] = 0x10 | (r->req.bus->info->tcq ? 0x02 : 0); /* Sync, TCQ. */ +- memcpy(&r->buf[8], "QEMU ", 8); +- memcpy(&r->buf[16], "QEMU TARGET ", 16); ++ memcpy(&r->buf[8], "ATA ", 8); ++ memcpy(&r->buf[16], "ATA TARGET ", 16); + pstrcpy((char *) &r->buf[32], 4, QEMU_HW_VERSION); + } + return true; +diff --git a/hw/ide/core.c b/hw/ide/core.c +index fb9bf11..7f44de2 100644 +--- a/hw/ide/core.c ++++ b/hw/ide/core.c +@@ -2640,21 +2640,20 @@ int ide_init_drive(IDEState *s, IDEDevice *dev, IDEDriveKind kind, Error **errp) + if (dev->serial) { + pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), dev->serial); + } else { +- snprintf(s->drive_serial_str, sizeof(s->drive_serial_str), +- "QM%05d", s->drive_serial); ++ pstrcpy(s->drive_serial_str, sizeof(s->drive_serial_str), "S0123456789ABCD"); /* placeholder; set on the disk instead */ + } + if (dev->model) { + pstrcpy(s->drive_model_str, sizeof(s->drive_model_str), dev->model); + } else { + switch (kind) { + case IDE_CD: +- strcpy(s->drive_model_str, "QEMU DVD-ROM"); ++ strcpy(s->drive_model_str, "HL-DT-ST BD-RE WH16NS60"); + break; + case IDE_CFATA: +- strcpy(s->drive_model_str, "QEMU MICRODRIVE"); ++ strcpy(s->drive_model_str, "Samsung SSD 870 EVO 1TB"); + break; + default: +- strcpy(s->drive_model_str, "QEMU HARDDISK"); ++ strcpy(s->drive_model_str, "Samsung SSD 870 EVO 1TB"); + break; + } + } +diff --git a/hw/ide/atapi.c b/hw/ide/atapi.c +index 0ea149a..f012ad8 100644 +--- a/hw/ide/atapi.c ++++ b/hw/ide/atapi.c +@@ -786,8 +786,8 @@ static void cmd_inquiry(IDEState *s, uint8_t *buf) + buf[5] = 0; /* reserved */ + buf[6] = 0; /* reserved */ + buf[7] = 0; /* reserved */ +- padstr8(buf + 8, 8, "QEMU"); +- padstr8(buf + 16, 16, "QEMU DVD-ROM"); ++ padstr8(buf + 8, 8, "Samsung"); ++ padstr8(buf + 16, 16, "DVD-ROM"); + padstr8(buf + 32, 4, s->version); + idx = 36; + } +diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c +index 4893cf7..7d05b82 100644 +--- a/hw/nvme/ctrl.c ++++ b/hw/nvme/ctrl.c +@@ -9268,7 +9268,7 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev) + id->vid = cpu_to_le16(pci_get_word(pci_conf + PCI_VENDOR_ID)); + id->ssvid = cpu_to_le16(pci_get_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID)); + strpadcpy((char *)id->mn, sizeof(id->mn), +- n->params.model ? n->params.model : "QEMU NVMe Ctrl", ' '); ++ n->params.model ? n->params.model : "NVMe Ctrl", ' '); + strpadcpy((char *)id->fr, sizeof(id->fr), + n->params.firmware_version ? n->params.firmware_version : QEMU_VERSION, ' '); + strpadcpy((char *)id->sn, sizeof(id->sn), n->params.serial, ' '); diff --git a/patches/qemu/03-pci-ids.patch b/patches/qemu/03-pci-ids.patch new file mode 100644 index 0000000..5e61d7e --- /dev/null +++ b/patches/qemu/03-pci-ids.patch @@ -0,0 +1,181 @@ +Subject: [PATCH] pci-ids: PCI_VENDOR / DEVICES + +Targets VMAware check: PCI_VENDOR / DEVICES + +PCI vendor, device and subsystem IDs that read as Red Hat / QEMU, and the +ICH9 southbridge layout OVMF expects to find at 1f:0. + +Applies to QEMU v11.1.1. Apply from the source root with: + patch -p1 < 03-pci-ids.patch + +--- +diff --git a/include/hw/pci/pci.h b/include/hw/pci/pci.h +index f2448e9..4eea405 100644 +--- a/include/hw/pci/pci.h ++++ b/include/hw/pci/pci.h +@@ -57,8 +57,8 @@ extern bool pci_available; + #define PCI_DEVICE_ID_MARVELL_GT6412X 0x4620 + + /* QEMU/Bochs VGA (0x1234) */ +-#define PCI_VENDOR_ID_QEMU 0x1234 +-#define PCI_DEVICE_ID_QEMU_VGA 0x1111 ++#define PCI_VENDOR_ID_QEMU 0x1002 // "???" | AMD/ATI Vendor ID Replacement from 0x1234 ++#define PCI_DEVICE_ID_QEMU_VGA 0x164e // "???" | AMD/ATI Vendor ID Replacement from 0x1111 + #define PCI_DEVICE_ID_QEMU_IPMI 0x1112 + + /* VMWare (0x15ad) */ +@@ -77,9 +77,9 @@ extern bool pci_available; + #define PCI_DEVICE_ID_INTEL_82801IR 0x2922 + + /* Red Hat / Qumranet (for QEMU) -- see pci-ids.txt */ +-#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 +-#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x1af4 +-#define PCI_SUBDEVICE_ID_QEMU 0x1100 ++#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 // VirtIO devices | IVSHMEM Driver | Looking Glass ++#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x8086 // VirtIO devices | IVSHMEM Driver | Looking Glass ++#define PCI_SUBDEVICE_ID_QEMU 0x0000 // VirtIO devices | IVSHMEM Driver | Looking Glass + + /* legacy virtio-pci devices */ + #define PCI_DEVICE_ID_VIRTIO_NET 0x1000 +@@ -100,21 +100,21 @@ extern bool pci_available; + */ + #define PCI_DEVICE_ID_VIRTIO_10_BASE 0x1040 + +-#define PCI_VENDOR_ID_REDHAT 0x1b36 +-#define PCI_DEVICE_ID_REDHAT_BRIDGE 0x0001 +-#define PCI_DEVICE_ID_REDHAT_SERIAL 0x0002 ++#define PCI_VENDOR_ID_REDHAT 0x1022 // "Red Hat, Inc." | AMD Vendor ID Replacement from 0x1b36 ++#define PCI_DEVICE_ID_REDHAT_BRIDGE 0x57a3 // "QEMU PCI-PCI bridge" | AMD Device ID Replacement from 0x0001 | hw/pci-bridge/pci_bridge_dev.c ++#define PCI_DEVICE_ID_REDHAT_SERIAL 0x0002 // TODO: find device_id | AMD Device ID Replacement from 0x???? | hw/char/serial-pci.c + #define PCI_DEVICE_ID_REDHAT_SERIAL2 0x0003 + #define PCI_DEVICE_ID_REDHAT_SERIAL4 0x0004 + #define PCI_DEVICE_ID_REDHAT_TEST 0x0005 + #define PCI_DEVICE_ID_REDHAT_ROCKER 0x0006 + #define PCI_DEVICE_ID_REDHAT_SDHCI 0x0007 +-#define PCI_DEVICE_ID_REDHAT_PCIE_HOST 0x0008 ++#define PCI_DEVICE_ID_REDHAT_PCIE_HOST 0x1482 // "QEMU PCIe Host bridge" | AMD Device ID Replacement from 0x0008 | hw/pci-host/gpex.c + #define PCI_DEVICE_ID_REDHAT_PXB 0x0009 + #define PCI_DEVICE_ID_REDHAT_BRIDGE_SEAT 0x000a + #define PCI_DEVICE_ID_REDHAT_PXB_PCIE 0x000b +-#define PCI_DEVICE_ID_REDHAT_PCIE_RP 0x000c +-#define PCI_DEVICE_ID_REDHAT_XHCI 0x000d +-#define PCI_DEVICE_ID_REDHAT_PCIE_BRIDGE 0x000e ++#define PCI_DEVICE_ID_REDHAT_PCIE_RP 0x1483 // "QEMU PCIe Root Port" | AMD Device ID Replacement from 0x000c | hw/pci-bridge/gen_pcie_root_port.c ++#define PCI_DEVICE_ID_REDHAT_XHCI 0x149c // "QEMU XHCI Host Controller" | AMD Device ID Replacement from 0x000d | hw/usb/hcd-xhci-pci.c ++#define PCI_DEVICE_ID_REDHAT_PCIE_BRIDGE 0x57ad // "Red Hat, Device ID: 000E" | AMD Device ID Replacement from 0x000e | hw/pci-bridge/pcie_pci_bridge.c + #define PCI_DEVICE_ID_REDHAT_MDPY 0x000f + #define PCI_DEVICE_ID_REDHAT_NVME 0x0010 + #define PCI_DEVICE_ID_REDHAT_PVPANIC 0x0011 +diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h +index 16034aa..5c33c9e 100644 +--- a/include/hw/pci/pci_ids.h ++++ b/include/hw/pci/pci_ids.h +@@ -178,6 +178,7 @@ + #define PCI_VENDOR_ID_AMD 0x1022 + #define PCI_DEVICE_ID_AMD_LANCE 0x2000 + #define PCI_DEVICE_ID_AMD_SCSI 0x2020 ++#define PCI_DEVICE_ID_AMD_SATA 0x43f6 // SATA controller [0106] | hw/ide/ich.c + + #define PCI_VENDOR_ID_HP 0x103c + +@@ -241,7 +242,7 @@ + #define PCI_DEVICE_ID_INTEL_82378 0x0484 + #define PCI_DEVICE_ID_INTEL_82441 0x1237 + #define PCI_DEVICE_ID_INTEL_82801AA_5 0x2415 +-#define PCI_DEVICE_ID_INTEL_82801BA_11 0x244e ++#define PCI_DEVICE_ID_INTEL_82801BA_11 0x14db // PCI bridge [0604] | hw/pci-bridge/i82801b11.c + #define PCI_DEVICE_ID_INTEL_82801D 0x24CD + #define PCI_DEVICE_ID_INTEL_ESB_9 0x25ab + #define PCI_DEVICE_ID_INTEL_NVME 0x5845 +diff --git a/include/hw/southbridge/ich9.h b/include/hw/southbridge/ich9.h +index 2c35dd0..2b6a39c 100644 +--- a/include/hw/southbridge/ich9.h ++++ b/include/hw/southbridge/ich9.h +@@ -128,7 +128,7 @@ struct ICH9LPCState { + #define ICH9_LPC_DEV 31 + #define ICH9_LPC_FUNC 0 + +-#define ICH9_A2_LPC_REVISION 0x2 ++#define ICH9_A2_LPC_REVISION 0x51 // ISA bridge [0601] | hw/isa/lpc_ich9.c + #define ICH9_LPC_NB_PIRQS 8 /* PCI A-H */ + + #define ICH9_LPC_PMBASE 0x40 +@@ -177,7 +177,7 @@ struct ICH9LPCState { + #define ICH9_GPIO_GSI "gsi" + + /* D31:F2 SATA Controller #1 */ +-#define ICH9_SATA1_DEV 31 ++#define ICH9_SATA1_DEV 20 + #define ICH9_SATA1_FUNC 2 + + /* D31:F0 power management I/O registers +@@ -213,7 +213,7 @@ struct ICH9LPCState { + /* D31:F3 SMBus controller */ + #define TYPE_ICH9_SMB_DEVICE "ICH9-SMB" + +-#define ICH9_A2_SMB_REVISION 0x02 ++#define ICH9_A2_SMB_REVISION 0x71 // SMBus [0c05] | hw/i2c/smbus_ich9.c + #define ICH9_SMB_PI 0x00 + + #define ICH9_SMB_SMBMBAR0 0x10 +@@ -230,7 +230,7 @@ struct ICH9LPCState { + #define ICH9_SMB_HOSTC_HST_EN ((uint8_t)(1 << 0)) + + /* D31:F3 SMBus I/O and memory mapped I/O registers */ +-#define ICH9_SMB_DEV 31 ++#define ICH9_SMB_DEV 20 + #define ICH9_SMB_FUNC 3 + + #define ICH9_SMB_HST_STS 0x00 +diff --git a/hw/isa/lpc_ich9.c b/hw/isa/lpc_ich9.c +index edf9783..00365e1 100644 +--- a/hw/isa/lpc_ich9.c ++++ b/hw/isa/lpc_ich9.c +@@ -864,7 +864,7 @@ static void build_ich9_isa_aml(AcpiDevAmlIf *adev, Aml *scope) + aml_append(scope, aml_operation_region("PIRQ", AML_PCI_CONFIG, + aml_int(0x60), 0x0C)); + /* Fields declarion has to happen *after* operation region */ +- field = aml_field("PCI0.SF8.PIRQ", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE); ++ field = aml_field("PCI0.LPCB.PIRQ", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE); + aml_append(field, aml_named_field("PRQA", 8)); + aml_append(field, aml_named_field("PRQB", 8)); + aml_append(field, aml_named_field("PRQC", 8)); +diff --git a/hw/pci-host/q35.c b/hw/pci-host/q35.c +index f4556ad..547a901 100644 +--- a/hw/pci-host/q35.c ++++ b/hw/pci-host/q35.c +@@ -694,7 +694,7 @@ static void mch_class_init(ObjectClass *klass, const void *data) + set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); + dc->desc = "Host bridge"; + dc->vmsd = &vmstate_mch; +- k->vendor_id = PCI_VENDOR_ID_INTEL; ++ k->vendor_id = PCI_VENDOR_ID_AMD; + /* + * The 'q35' machine type implements an Intel Series 3 chipset, + * of which there are several variants. The key difference between +diff --git a/hw/core/qdev.c b/hw/core/qdev.c +index e2aab3d..9131b5d 100644 +--- a/hw/core/qdev.c ++++ b/hw/core/qdev.c +@@ -765,7 +765,7 @@ static void device_class_init(ObjectClass *class, const void *data) + * hotpluggable. Devices that shouldn't be hotpluggable, + * should override it in their class_init() + */ +- dc->hotpluggable = true; ++ dc->hotpluggable = false; + dc->user_creatable = true; + vc->get_id = device_vmstate_if_get_id; + rc->get_state = device_get_reset_state; +diff --git a/hw/i386/pc.c b/hw/i386/pc.c +index f064aa2..cfdbbc1 100644 +--- a/hw/i386/pc.c ++++ b/hw/i386/pc.c +@@ -1689,7 +1689,7 @@ static void pc_machine_class_init(ObjectClass *oc, const void *data) + + pcmc->pci_enabled = true; + pcmc->has_acpi_build = true; +- pcmc->smbios_defaults = true; ++ pcmc->smbios_defaults = false; + pcmc->gigabyte_align = true; + pcmc->has_reserved_memory = true; + pcmc->enforce_amd_1tb_hole = true; diff --git a/patches/qemu/04-fw-cfg.patch b/patches/qemu/04-fw-cfg.patch new file mode 100644 index 0000000..a6abff0 --- /dev/null +++ b/patches/qemu/04-fw-cfg.patch @@ -0,0 +1,58 @@ +Subject: [PATCH] fw-cfg: QEMU_FW_CFG + +Targets VMAware check: QEMU_FW_CFG + +The fw_cfg device signature, which names QEMU literally. + +Applies to QEMU v11.1.1. Apply from the source root with: + patch -p1 < 04-fw-cfg.patch + +--- +diff --git a/hw/i386/fw_cfg.c b/hw/i386/fw_cfg.c +index 5903994..e5fddbc 100644 +--- a/hw/i386/fw_cfg.c ++++ b/hw/i386/fw_cfg.c +@@ -77,7 +77,7 @@ void fw_cfg_build_smbios(PCMachineState *pcms, FWCfgState *fw_cfg, + + if (pcmc->smbios_defaults) { + /* These values are guest ABI, do not change */ +- smbios_set_defaults("QEMU", mc->desc, mc->name); ++ smbios_set_defaults("Unknown", mc->desc, mc->name); + } + + /* tell smbios about cpuid version and features */ +diff --git a/hw/nvram/fw_cfg.c b/hw/nvram/fw_cfg.c +index b057d87..a253fe8 100644 +--- a/hw/nvram/fw_cfg.c ++++ b/hw/nvram/fw_cfg.c +@@ -56,7 +56,7 @@ + #define FW_CFG_DMA_CTL_SELECT 0x08 + #define FW_CFG_DMA_CTL_WRITE 0x10 + +-#define FW_CFG_DMA_SIGNATURE 0x51454d5520434647ULL /* "QEMU CFG" */ ++#define FW_CFG_DMA_SIGNATURE 0x51434f4d20434647ULL /* "QCOM CFG" */ + + struct FWCfgEntry { + uint32_t len; +@@ -1002,7 +1002,7 @@ static void fw_cfg_common_realize(DeviceState *dev, Error **errp) + return; + } + +- fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (char *)"QEMU", 4); ++ fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (char *)"QEMU", 4); // "QEMU" + fw_cfg_add_bytes(s, FW_CFG_UUID, &qemu_uuid, 16); + fw_cfg_add_i16(s, FW_CFG_NOGRAPHIC, (uint16_t)!machine->enable_graphics); + fw_cfg_add_i16(s, FW_CFG_BOOT_MENU, (uint16_t)(machine->boot_config.has_menu && machine->boot_config.menu)); +diff --git a/include/standard-headers/linux/qemu_fw_cfg.h b/include/standard-headers/linux/qemu_fw_cfg.h +index cb93f66..0527126 100644 +--- a/include/standard-headers/linux/qemu_fw_cfg.h ++++ b/include/standard-headers/linux/qemu_fw_cfg.h +@@ -71,7 +71,7 @@ struct fw_cfg_file { + #define FW_CFG_DMA_CTL_SELECT 0x08 + #define FW_CFG_DMA_CTL_WRITE 0x10 + +-#define FW_CFG_DMA_SIGNATURE 0x51454d5520434647ULL /* "QEMU CFG" */ ++#define FW_CFG_DMA_SIGNATURE 0x51434f4d20434647ULL /* "QCOM CFG" */ + + /* Control as first field allows for different structures selected by this + * field, which might be useful in the future diff --git a/patches/qemu/05-usb-hid.patch b/patches/qemu/05-usb-hid.patch new file mode 100644 index 0000000..1be743e --- /dev/null +++ b/patches/qemu/05-usb-hid.patch @@ -0,0 +1,111 @@ +Subject: [PATCH] usb-hid: QEMU_USB + +Targets VMAware check: QEMU_USB + +USB HID descriptors carrying QEMU vendor strings and product IDs. + +Applies to QEMU v11.1.1. Apply from the source root with: + patch -p1 < 05-usb-hid.patch + +--- +diff --git a/hw/usb/dev-hid.c b/hw/usb/dev-hid.c +index ae19d60..9fe1223 100644 +--- a/hw/usb/dev-hid.c ++++ b/hw/usb/dev-hid.c +@@ -63,17 +63,17 @@ enum { + }; + + static const USBDescStrings desc_strings = { +- [STR_MANUFACTURER] = "QEMU", +- [STR_PRODUCT_MOUSE] = "QEMU USB Mouse", +- [STR_PRODUCT_TABLET] = "QEMU USB Tablet", +- [STR_PRODUCT_KEYBOARD] = "QEMU USB Keyboard", +- [STR_SERIAL_COMPAT] = "42", ++ [STR_MANUFACTURER] = "Logitech", ++ [STR_PRODUCT_MOUSE] = "USB Mouse", ++ [STR_PRODUCT_TABLET] = "USB Tablet", ++ [STR_PRODUCT_KEYBOARD] = "USB Keyboard", ++ [STR_SERIAL_COMPAT] = "", + [STR_CONFIG_MOUSE] = "HID Mouse", + [STR_CONFIG_TABLET] = "HID Tablet", + [STR_CONFIG_KEYBOARD] = "HID Keyboard", +- [STR_SERIAL_MOUSE] = "89126", +- [STR_SERIAL_TABLET] = "28754", +- [STR_SERIAL_KEYBOARD] = "68284", ++ [STR_SERIAL_MOUSE] = "", ++ [STR_SERIAL_TABLET] = "", ++ [STR_SERIAL_KEYBOARD] = "", + }; + + static const USBDescIface desc_iface_mouse = { +@@ -368,8 +368,8 @@ static const USBDescMSOS desc_msos_suspend = { + + static const USBDesc desc_mouse = { + .id = { +- .idVendor = 0x0627, +- .idProduct = 0x0001, ++ .idVendor = 0x046D, ++ .idProduct = 0xC077, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_MOUSE, +@@ -382,8 +382,8 @@ static const USBDesc desc_mouse = { + + static const USBDesc desc_mouse2 = { + .id = { +- .idVendor = 0x0627, +- .idProduct = 0x0001, ++ .idVendor = 0x046D, ++ .idProduct = 0xC077, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_MOUSE, +@@ -426,8 +426,8 @@ static const USBDesc desc_tablet2 = { + + static const USBDesc desc_keyboard = { + .id = { +- .idVendor = 0x0627, +- .idProduct = 0x0001, ++ .idVendor = 0x046D, ++ .idProduct = 0xC31C, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_KEYBOARD, +@@ -440,8 +440,8 @@ static const USBDesc desc_keyboard = { + + static const USBDesc desc_keyboard2 = { + .id = { +- .idVendor = 0x0627, +- .idProduct = 0x0001, ++ .idVendor = 0x046D, ++ .idProduct = 0xC31C, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_KEYBOARD, +@@ -805,7 +805,7 @@ static void usb_tablet_class_initfn(ObjectClass *klass, const void *data) + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_tablet_realize; +- uc->product_desc = "QEMU USB Tablet"; ++ uc->product_desc = "USB Tablet"; + dc->vmsd = &vmstate_usb_ptr; + device_class_set_props(dc, usb_tablet_properties); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +@@ -827,7 +827,7 @@ static void usb_mouse_class_initfn(ObjectClass *klass, const void *data) + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_mouse_realize; +- uc->product_desc = "QEMU USB Mouse"; ++ uc->product_desc = "USB Mouse"; + dc->vmsd = &vmstate_usb_ptr; + device_class_set_props(dc, usb_mouse_properties); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +@@ -850,7 +850,7 @@ static void usb_keyboard_class_initfn(ObjectClass *klass, const void *data) + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_keyboard_realize; +- uc->product_desc = "QEMU USB Keyboard"; ++ uc->product_desc = "USB Keyboard"; + dc->vmsd = &vmstate_usb_kbd; + device_class_set_props(dc, usb_keyboard_properties); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); diff --git a/patches/qemu/06-audio.patch b/patches/qemu/06-audio.patch new file mode 100644 index 0000000..0560a53 --- /dev/null +++ b/patches/qemu/06-audio.patch @@ -0,0 +1,58 @@ +Subject: [PATCH] audio: DEVICES + +Targets VMAware check: DEVICES + +Intel HDA codec and controller identity strings. + +Applies to QEMU v11.1.1. Apply from the source root with: + patch -p1 < 06-audio.patch + +--- +diff --git a/hw/audio/hda-codec.c b/hw/audio/hda-codec.c +index 173fe56..4d8a73b 100644 +--- a/hw/audio/hda-codec.c ++++ b/hw/audio/hda-codec.c +@@ -118,7 +118,7 @@ static void hda_codec_parse_fmt(uint32_t format, struct audsettings *as) + + /* some defines */ + +-#define QEMU_HDA_ID_VENDOR 0x1af4 ++#define QEMU_HDA_ID_VENDOR 0x10ec /* Realtek HDA Codec Vendor ID */ + #define QEMU_HDA_PCM_FORMATS (AC_SUPPCM_BITS_16 | \ + 0x1fc /* 16 -> 96 kHz */) + #define QEMU_HDA_AMP_NONE (0) +diff --git a/hw/audio/intel-hda.c b/hw/audio/intel-hda.c +index 3d361a4..66c4e3b 100644 +--- a/hw/audio/intel-hda.c ++++ b/hw/audio/intel-hda.c +@@ -1230,7 +1230,7 @@ static void intel_hda_class_init(ObjectClass *klass, const void *data) + + k->realize = intel_hda_realize; + k->exit = intel_hda_exit; +- k->vendor_id = PCI_VENDOR_ID_INTEL; ++ k->vendor_id = PCI_VENDOR_ID_AMD; + k->class_id = PCI_CLASS_MULTIMEDIA_HD_AUDIO; + device_class_set_legacy_reset(dc, intel_hda_reset); + dc->vmsd = &vmstate_intel_hda; +@@ -1242,8 +1242,8 @@ static void intel_hda_class_init_ich6(ObjectClass *klass, const void *data) + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + +- k->device_id = 0x2668; +- k->revision = 1; ++ k->device_id = 0x15e3; // AMD - Ryzen HD Audio Controller ++ k->revision = 0; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = "Intel HD Audio Controller (ich6)"; + } +@@ -1253,8 +1253,8 @@ static void intel_hda_class_init_ich9(ObjectClass *klass, const void *data) + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + +- k->device_id = 0x293e; +- k->revision = 3; ++ k->device_id = 0x15e3; // AMD - Ryzen HD Audio Controller ++ k->revision = 0; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->desc = "Intel HD Audio Controller (ich9)"; + } diff --git a/patches/qemu/07-edid.patch b/patches/qemu/07-edid.patch new file mode 100644 index 0000000..66e7a68 --- /dev/null +++ b/patches/qemu/07-edid.patch @@ -0,0 +1,62 @@ +Subject: [PATCH] edid: (display identity) + +Targets VMAware check: (display identity) + +Generated EDID vendor and model. Note VMAware does not read EDID - this is +carried for other detectors, not for the score. + +Applies to QEMU v11.1.1. Apply from the source root with: + patch -p1 < 07-edid.patch + +--- +diff --git a/hw/display/edid-generate.c b/hw/display/edid-generate.c +index 2cb8196..45003e8 100644 +--- a/hw/display/edid-generate.c ++++ b/hw/display/edid-generate.c +@@ -388,22 +388,22 @@ void qemu_edid_generate(uint8_t *edid, size_t size, + uint8_t *did = NULL; + uint32_t width_mm, height_mm; + uint32_t refresh_rate = info->refresh_rate ? info->refresh_rate : 75000; +- uint32_t dpi = 100; /* if no width_mm/height_mm */ ++ uint32_t dpi = 93; /* if no width_mm/height_mm */ + uint32_t large_screen = 0; + + /* =============== set defaults =============== */ + + if (!info->vendor || strlen(info->vendor) != 3) { +- info->vendor = "RHT"; ++ info->vendor = "SAM"; + } + if (!info->name) { +- info->name = "QEMU Monitor"; ++ info->name = "SyncMaster"; + } + if (!info->prefx) { +- info->prefx = 1280; ++ info->prefx = 1920; + } + if (!info->prefy) { +- info->prefy = 800; ++ info->prefy = 1080; + } + if (info->width_mm && info->height_mm) { + width_mm = info->width_mm; +@@ -449,15 +449,15 @@ void qemu_edid_generate(uint8_t *edid, size_t size, + uint16_t vendor_id = ((((info->vendor[0] - '@') & 0x1f) << 10) | + (((info->vendor[1] - '@') & 0x1f) << 5) | + (((info->vendor[2] - '@') & 0x1f) << 0)); +- uint16_t model_nr = 0x1234; ++ uint16_t model_nr = 0x0dca; + uint32_t serial_nr = info->serial ? atoi(info->serial) : 0; + stw_be_p(edid + 8, vendor_id); + stw_le_p(edid + 10, model_nr); + stl_le_p(edid + 12, serial_nr); + + /* manufacture week and year */ +- edid[16] = 42; +- edid[17] = 2014 - 1990; ++ edid[16] = 17; ++ edid[17] = 2018 - 1990; + + /* edid version */ + edid[18] = 1; diff --git a/patches/qemu/08-cpu-misc.patch b/patches/qemu/08-cpu-misc.patch new file mode 100644 index 0000000..06eadf7 --- /dev/null +++ b/patches/qemu/08-cpu-misc.patch @@ -0,0 +1,110 @@ +Subject: [PATCH] cpu-misc: (CPU model plumbing) + +Targets VMAware check: (CPU model plumbing) + +CPU feature-word plumbing and an upstream backport; not aimed at a check. + +Applies to QEMU v11.1.1. Apply from the source root with: + patch -p1 < 08-cpu-misc.patch + +--- +diff --git a/target/i386/cpu.c b/target/i386/cpu.c +index 5805d33..1b0203e 100644 +--- a/target/i386/cpu.c ++++ b/target/i386/cpu.c +@@ -1009,6 +1009,7 @@ void x86_cpu_vendor_words2str(char *dst, uint32_t vendor1, + #define TCG_7_2_EDX_FEATURES 0 + #define TCG_APM_FEATURES 0 + #define TCG_6_EAX_FEATURES CPUID_6_EAX_ARAT ++#define TCG_6_ECX_FEATURES 0 + #define TCG_XSAVE_FEATURES (CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XGETBV1) + /* missing: + CPUID_XSAVE_XSAVEC, CPUID_XSAVE_XSAVES */ +@@ -1545,6 +1546,21 @@ FeatureWordInfo feature_word_info[FEATURE_WORDS] = { + .cpuid = { .eax = 6, .reg = R_EAX, }, + .tcg_features = TCG_6_EAX_FEATURES, + }, ++ [FEAT_6_ECX] = { ++ .type = CPUID_FEATURE_WORD, ++ .feat_names = { ++ "aperfmperf", NULL, NULL, NULL, ++ NULL, NULL, NULL, NULL, ++ NULL, NULL, NULL, NULL, ++ NULL, NULL, NULL, NULL, ++ NULL, NULL, NULL, NULL, ++ NULL, NULL, NULL, NULL, ++ NULL, NULL, NULL, NULL, ++ NULL, NULL, NULL, NULL, ++ }, ++ .cpuid = { .eax = 6, .reg = R_ECX, }, ++ .tcg_features = TCG_6_ECX_FEATURES, ++ }, + [FEAT_XSAVE_XCR0_LO] = { + .type = CPUID_FEATURE_WORD, + .cpuid = { +@@ -8778,7 +8794,7 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, + /* Thermal and Power Leaf */ + *eax = env->features[FEAT_6_EAX]; + *ebx = 0; +- *ecx = 0; ++ *ecx = env->features[FEAT_6_ECX]; + *edx = 0; + break; + case 7: +@@ -9809,6 +9825,7 @@ void x86_cpu_expand_features(X86CPU *cpu, Error **errp) + x86_cpu_adjust_feat_level(cpu, FEAT_1_EDX); + x86_cpu_adjust_feat_level(cpu, FEAT_1_ECX); + x86_cpu_adjust_feat_level(cpu, FEAT_6_EAX); ++ x86_cpu_adjust_feat_level(cpu, FEAT_6_ECX); + x86_cpu_adjust_feat_level(cpu, FEAT_7_0_ECX); + x86_cpu_adjust_feat_level(cpu, FEAT_7_1_EAX); + x86_cpu_adjust_feat_level(cpu, FEAT_7_1_ECX); +diff --git a/target/i386/cpu.h b/target/i386/cpu.h +index e6a1976..2a6dbbf 100644 +--- a/target/i386/cpu.h ++++ b/target/i386/cpu.h +@@ -700,6 +700,7 @@ typedef enum FeatureWord { + FEAT_SVM, /* CPUID[8000_000A].EDX */ + FEAT_XSAVE, /* CPUID[EAX=0xd,ECX=1].EAX */ + FEAT_6_EAX, /* CPUID[6].EAX */ ++ FEAT_6_ECX, /* CPUID[6].ECX */ + FEAT_XSAVE_XCR0_LO, /* CPUID[EAX=0xd,ECX=0].EAX */ + FEAT_XSAVE_XCR0_HI, /* CPUID[EAX=0xd,ECX=0].EDX */ + FEAT_ARCH_CAPABILITIES, +@@ -1232,6 +1233,7 @@ uint64_t x86_cpu_get_supported_feature_word(X86CPU *cpu, FeatureWord w); + #define CPUID_XSAVE_XFD (1U << 4) + + #define CPUID_6_EAX_ARAT (1U << 2) ++#define CPUID_6_ECX_APERFMPERF (1U << 0) + + /* CPUID[0x80000007].EDX flags: */ + #define CPUID_APM_INVTSC (1U << 8) +diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c +index 644c45f..6f6862e 100644 +--- a/target/i386/kvm/kvm.c ++++ b/target/i386/kvm/kvm.c +@@ -499,6 +499,14 @@ uint32_t kvm_arch_get_supported_cpuid(KVMState *s, uint32_t function, + } + } else if (function == 6 && reg == R_EAX) { + ret |= CPUID_6_EAX_ARAT; /* safe to allow because of emulated APIC */ ++ } else if (function == 6 && reg == R_ECX) { ++ if (enable_cpu_pm) { ++ int disable_exits = kvm_check_extension(s, ++ KVM_CAP_X86_DISABLE_EXITS); ++ if (disable_exits & KVM_X86_DISABLE_EXITS_APERFMPERF) { ++ ret |= CPUID_6_ECX_APERFMPERF; ++ } ++ } + } else if (function == 7 && index == 0 && reg == R_EBX) { + /* Not new instructions, just an optimization. */ + uint32_t ebx; +@@ -3292,7 +3300,8 @@ static int kvm_vm_enable_disable_exits(KVMState *s) + disable_exits &= (KVM_X86_DISABLE_EXITS_MWAIT | + KVM_X86_DISABLE_EXITS_HLT | + KVM_X86_DISABLE_EXITS_PAUSE | +- KVM_X86_DISABLE_EXITS_CSTATE); ++ KVM_X86_DISABLE_EXITS_CSTATE | ++ KVM_X86_DISABLE_EXITS_APERFMPERF); + } + + return kvm_vm_enable_cap(s, KVM_CAP_X86_DISABLE_EXITS, 0,